From be763d4630dd142ca3b1f245516288827cb4749e Mon Sep 17 00:00:00 2001 From: xzhuo Date: Fri, 10 May 2019 09:21:44 -0500 Subject: [PATCH 01/15] begin multiAlignment --- .../trackContainers/TrackViewManager.tsx | 10 + .../alignment/AlignmentViewCalculator.ts | 22 +- .../alignment/MultiAlignmentViewCalculator.ts | 573 ++++++++++++++++++ 3 files changed, 595 insertions(+), 10 deletions(-) create mode 100644 frontend/src/model/alignment/MultiAlignmentViewCalculator.ts diff --git a/frontend/src/components/trackContainers/TrackViewManager.tsx b/frontend/src/components/trackContainers/TrackViewManager.tsx index e059ee67..3ac76130 100644 --- a/frontend/src/components/trackContainers/TrackViewManager.tsx +++ b/frontend/src/components/trackContainers/TrackViewManager.tsx @@ -8,6 +8,7 @@ import DisplayedRegionModel from '../../model/DisplayedRegionModel'; import { ViewExpansion } from '../../model/RegionExpander'; import { GuaranteeMap } from '../../model/GuaranteeMap'; import { AlignmentViewCalculator, Alignment } from '../../model/alignment/AlignmentViewCalculator'; +// import { MultiAlignmentViewCalculator } from '../../model/alignment/MultiAlignmentViewCalculator'; interface DataManagerProps { genome: string; // The primary genome @@ -36,11 +37,15 @@ interface WrappedComponentProps { export function withTrackView(WrappedComponent: React.ComponentType) { class TrackViewManager extends React.Component { private _primaryGenome: string; + // private _multialignmentCalculator: GuaranteeMap private _alignmentCalculatorForGenome: GuaranteeMap constructor(props: DataManagerProps) { super(props); this._primaryGenome = props.genome; + // this._multialignmentCalculator = new GuaranteeMap( + // queryGenomes => new MultiAlignmentViewCalculator(this._primaryGenome, queryGenomes) + // ) this._alignmentCalculatorForGenome = new GuaranteeMap( queryGenome => new AlignmentViewCalculator(this._primaryGenome, queryGenome) ); @@ -66,10 +71,13 @@ export function withTrackView(WrappedComponent: React.ComponentType plotStrand = "-". - const aggregateStrandsNumber = records.reduce((aggregateStrand, record) => - aggregateStrand + (record.getIsReverseStrandQuery()? - (-1 * record.getLength()):record.getLength()), 0 - ); - const plotStrand = aggregateStrandsNumber < 0?"-":"+"; - return isFineMode ? this.alignFine(records, visData) : this.alignRough(records, visData, plotStrand); + return isFineMode ? this.alignFine(records, visData) : this.alignRough(records, visData); } alignFine(records: AlignmentRecord[], visData: ViewExpansion): Alignment { @@ -274,11 +266,21 @@ export class AlignmentViewCalculator { * @param {number} width - view width of the primary genome * @return {PlacedMergedAlignment[]} placed merged alignments */ - alignRough(alignmentRecords: AlignmentRecord[], visData: ViewExpansion, plotStrand: string): Alignment { + alignRough(alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { const {visRegion, visWidth} = visData; const drawModel = new LinearDrawingModel(visRegion, visWidth); const mergeDistance = drawModel.xWidthToBases(MERGE_PIXEL_DISTANCE); + // Count how many bases are in positive strand and how many of them are in negative strand. + // More in negative strand (<0) => plotStrand = "-". + const aggregateStrandsNumber = alignmentRecords.reduce((aggregateStrand, record) => + aggregateStrand + (record.getIsReverseStrandQuery() + ?(-1 * record.getLength()) + :record.getLength() + ),0 + ); + const plotStrand = aggregateStrandsNumber < 0?"-":"+"; + const placedRecords = this._computeContextLocations(alignmentRecords, visData); // First, merge the alignments by query genome coordinates let queryLocusMerges = ChromosomeInterval.mergeAdvanced( diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts new file mode 100644 index 00000000..4414036f --- /dev/null +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -0,0 +1,573 @@ +import _ from 'lodash'; +import memoizeOne from 'memoize-one'; +import { GuaranteeMap } from '../../model/GuaranteeMap'; + +import { segmentSequence, makeBaseNumberLookup, countBases, SequenceSegment } from './AlignmentStringUtils'; +import { AlignmentRecord } from './AlignmentRecord'; +import { AlignmentSegment } from './AlignmentSegment'; +import { AlignmentFetcher } from './AlignmentFetcher'; +import { NavContextBuilder, Gap } from './NavContextBuilder'; + +import ChromosomeInterval from '../interval/ChromosomeInterval'; +import OpenInterval from '../interval/OpenInterval'; +import NavigationContext from '../NavigationContext'; +import LinearDrawingModel from '../LinearDrawingModel'; +import { Feature } from '../Feature'; + +import { ViewExpansion } from '../RegionExpander'; +import { FeaturePlacer } from '../FeaturePlacer'; +import DisplayedRegionModel from '../DisplayedRegionModel'; +import { niceBpCount } from '../../util'; + +export interface PlacedAlignment { + record: AlignmentRecord; + visiblePart: AlignmentSegment; + contextSpan: OpenInterval; + targetXSpan: OpenInterval; + queryXSpan: OpenInterval; + targetSegments?: PlacedSequenceSegment[]; // These only present in fine mode + querySegments?: PlacedSequenceSegment[]; +} + +export interface PlacedSequenceSegment extends SequenceSegment { + xSpan: OpenInterval; +} + +interface QueryGenomePiece { + queryFeature: Feature; + queryXSpan: OpenInterval; +} + +export interface PlacedMergedAlignment extends QueryGenomePiece { + segments: PlacedAlignment[]; + targetXSpan: OpenInterval; +} + +export interface GapText { + targetGapText: string; + targetXSpan: OpenInterval; + targetTextXSpan: OpenInterval; + queryGapText: string; + queryXSpan: OpenInterval; + queryTextXSpan: OpenInterval; + shiftTarget: boolean; // Whether target txt width > gap width + shiftQuery: boolean; // Whether query txt width > gap width +} + +export interface Alignment { + isFineMode: boolean; // Display mode + primaryVisData: ViewExpansion; // Primary genome view region data + queryRegion: DisplayedRegionModel; // Query genome view region + /** + * PlacedAlignment[] in fine mode; PlacedMergedAlignment in rough mode. + */ + drawData: PlacedAlignment[] | PlacedMergedAlignment[]; + drawGapText?: GapText[]; // An array holding gap size information between placedAlignments, fineMode only + plotStrand?: string; // rough mode plot positive or negative + primaryGenome: string; + queryGenome: string; + basesPerPixel: number; +} + +export interface MultiAlignment { + [genome: string]: Alignment +} + +const MAX_FINE_MODE_BASES_PER_PIXEL = 10; +const MARGIN = 5; +// const MIN_GAP_DRAW_WIDTH = 3; +const MERGE_PIXEL_DISTANCE = 200; +const MIN_MERGE_DRAW_WIDTH = 5; +const FEATURE_PLACER = new FeaturePlacer(); + +export class MultiAlignmentViewCalculator { + private _alignmentFetcher: {}; + private _viewBeingFetched: ViewExpansion; + + constructor(primaryGenome: string, queryGenomes: string[]) { + this._alignmentFetcher = queryGenomes.reduce( + (obj, queryGenome) => ({ ...obj, [queryGenome]: new AlignmentFetcher(primaryGenome, queryGenome) }), {} + ) + this._alignmentFetcher = queryGenomes.map(queryGenome => + new AlignmentFetcher(primaryGenome, queryGenome)); + this._viewBeingFetched = null; + this.multiAlign = memoizeOne(this.multiAlign); + } + + cleanUp() { + this._alignmentFetcher.map(fetcher => fetcher.cleanUp()); + } + async multiAlign(visData: ViewExpansion): Promise { + const {visRegion, visWidth, viewWindowRegion} = visData; + this._viewBeingFetched = visData; + + const drawModel = new LinearDrawingModel(visRegion, visWidth); + const isFineMode = drawModel.xWidthToBases(1) < MAX_FINE_MODE_BASES_PER_PIXEL; + let recordsArray; + if (isFineMode) { + recordsArray = await this._alignmentFetcher.map( + fetcher => fetcher.fetchAlignment(visRegion, visData, false) + ); + } else { + recordsArray = await this._alignmentFetcher.map( + fetcher => fetcher.fetchAlignment(viewWindowRegion, visData, true) + ); + console.log(recordsArray); + let multiAlign; + for (let i = 0; i < recordsArray.length; i++) { + const records = recordsArray[i]; + const alignment = this.alignRough(records, visData); + multiAlign[queryGenomes[i]] = alignment; + } + } + // manipulate recordsArray, feed each element to alignFine/alignRough. + + return {}; + // return isFineMode ? this.alignFine(records, visData) : this.alignRough(records, visData, plotStrand); + } + + alignFine(records: AlignmentRecord[], visData: ViewExpansion): Alignment { + // There's a lot of steps, so bear with me... + const {visRegion, viewWindow, viewWindowRegion} = visData; + const oldNavContext = visRegion.getNavigationContext(); + // const drawModel = new LinearDrawingModel(visRegion, visWidth); + // const minGapLength = drawModel.xWidthToBases(MIN_GAP_DRAW_WIDTH); + const minGapLength = 0.99; + + // Calculate context coordinates of the records and gaps within. + const placements = this._computeContextLocations(records, visData); + const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); + + // Build a new primary navigation context using the gaps + const navContextBuilder = new NavContextBuilder(oldNavContext); + navContextBuilder.setGaps(primaryGaps); + const newNavContext = navContextBuilder.build(); + + // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context + const newVisRegion = convertOldVisRegion(visRegion); + const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); + const newPixelsPerBase = viewWindow.getLength() / newViewWindowRegion.getWidth(); + const newVisWidth = newVisRegion.getWidth() * newPixelsPerBase; + const newDrawModel = new LinearDrawingModel(newVisRegion, newVisWidth); + const newViewWindow = newDrawModel.baseSpanToXSpan(newViewWindowRegion.getContextCoordinates()); + + // With the draw model, we can set x spans for each placed alignment + for (const placement of placements) { + const oldContextSpan = placement.contextSpan; + const visiblePart = placement.visiblePart; + const newContextSpan = new OpenInterval( + navContextBuilder.convertOldCoordinates(oldContextSpan.start), + navContextBuilder.convertOldCoordinates(oldContextSpan.end) + ); + const xSpan = newDrawModel.baseSpanToXSpan(newContextSpan); + const targetSeq = visiblePart.getTargetSequence(); + const querySeq = visiblePart.getQuerySequence(); + + placement.contextSpan = newContextSpan; + placement.targetXSpan = xSpan; + placement.queryXSpan = xSpan; + placement.targetSegments = this._placeSequenceSegments(targetSeq, minGapLength, xSpan.start, newDrawModel); + placement.querySegments = this._placeSequenceSegments(querySeq, minGapLength, xSpan.start, newDrawModel); + } + + const drawGapTexts = []; + const targetIntervalPlacer = new IntervalPlacer(MARGIN); + const queryIntervalPlacer = new IntervalPlacer(MARGIN); + for(let i=1; i= lastQueryEnd ? "" : "overlap "; + placementQueryGap += niceBpCount(Math.abs(queryStart - lastQueryEnd)); + + } + else if (lastStrand === "-" && queryStrand === "-") { + placementQueryGap = lastQueryEnd >= queryStart ? "" : "overlap "; + placementQueryGap += niceBpCount(Math.abs(lastQueryEnd - queryStart)); + } + else { + placementQueryGap = "reverse direction"; + } + } else { + placementQueryGap = "not connected"; + } + const placementGapX = (lastXEnd + xStart) / 2; + const queryPlacementGapX = (lastPlacement.queryXSpan.end + placement.queryXSpan.start) / 2 + const placementTargetGap = lastTargetChr === targetChr ? + niceBpCount(targetStart - lastTargetEnd) : "not connected"; + + const targetTextWidth = placementTargetGap.length * 5; // use font size 10... + const halfTargetTextWidth = 0.5 * targetTextWidth; + const preferredTargetStart = placementGapX - halfTargetTextWidth; + const preferredTargetEnd = placementGapX + halfTargetTextWidth; + // shift text position only if the width of text is bigger than the gap size: + const shiftTargetTxt = (preferredTargetStart <= lastXEnd || preferredTargetEnd >= xStart); + const targetGapTextXSpan = shiftTargetTxt? + targetIntervalPlacer.place(new OpenInterval(preferredTargetStart, preferredTargetEnd)): + new OpenInterval(preferredTargetStart, preferredTargetEnd); + const targetGapXSpan = new OpenInterval(lastXEnd, xStart); + + const queryTextWidth = placementQueryGap.length * 5; // use font size 10... + const halfQueryTextWidth = 0.5 * queryTextWidth; + const preferredQueryStart = queryPlacementGapX - halfQueryTextWidth; + const preferredQueryEnd = queryPlacementGapX + halfQueryTextWidth; + // shift text position only if the width of text is bigger than the gap size: + const shiftQueryTxt = (preferredQueryStart <= lastPlacement.queryXSpan.end || + preferredQueryEnd >= placement.queryXSpan.start); + const queryGapTextXSpan = shiftQueryTxt? + queryIntervalPlacer.place(new OpenInterval(preferredQueryStart, preferredQueryEnd)): + new OpenInterval(preferredQueryStart, preferredQueryEnd); + const queryGapXSpan = new OpenInterval(lastPlacement.queryXSpan.end, placement.queryXSpan.start); + drawGapTexts.push({ + targetGapText: placementTargetGap, + targetXSpan: targetGapXSpan, + targetTextXSpan: targetGapTextXSpan, + queryGapText: placementQueryGap, + queryXSpan: queryGapXSpan, + queryTextXSpan: queryGapTextXSpan, + shiftTarget: shiftTargetTxt, + shiftQuery: shiftQueryTxt + }); + } + // Finally, using the x coordinates, construct the query nav context + const queryPieces = this._getQueryPieces(placements); + const queryRegion = this._makeQueryGenomeRegion(queryPieces, newVisWidth, newDrawModel); + + return { + isFineMode: true, + primaryVisData: { + visRegion: newVisRegion, + visWidth: newVisWidth, + viewWindowRegion: newViewWindowRegion, + viewWindow: newViewWindow, + }, + queryRegion, + drawData: placements, + drawGapText: drawGapTexts, + primaryGenome: this._alignmentFetcher.primaryGenome, + queryGenome: this._alignmentFetcher.queryGenome, + basesPerPixel: newDrawModel.xWidthToBases(1) + }; + + function convertOldVisRegion(visRegion: DisplayedRegionModel) { + const [contextStart, contextEnd] = visRegion.getContextCoordinates(); + return new DisplayedRegionModel( + newNavContext, + navContextBuilder.convertOldCoordinates(contextStart), + navContextBuilder.convertOldCoordinates(contextEnd) + ); + } + } + + /** + * Groups and merges alignment records based on their proximity in the query (secondary) genome. Then, calculates + * draw positions for all records. + * + * @param {AlignmentRecord[]} alignmentRecords - records to process + * @param {DisplayedRegionModel} viewRegion - view region of the primary genome + * @param {number} width - view width of the primary genome + * @return {PlacedMergedAlignment[]} placed merged alignments + */ + alignRough(alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { + const {visRegion, visWidth} = visData; + const drawModel = new LinearDrawingModel(visRegion, visWidth); + const mergeDistance = drawModel.xWidthToBases(MERGE_PIXEL_DISTANCE); + + // Count how many bases are in positive strand and how many of them are in negative strand. + // More in negative strand (<0) => plotStrand = "-". + const aggregateStrandsNumber = alignmentRecords.reduce((aggregateStrand, record) => + aggregateStrand + (record.getIsReverseStrandQuery() + ?(-1 * record.getLength()) + :record.getLength() + ),0 + ); + const plotStrand = aggregateStrandsNumber < 0?"-":"+"; + + const placedRecords = this._computeContextLocations(alignmentRecords, visData); + // First, merge the alignments by query genome coordinates + let queryLocusMerges = ChromosomeInterval.mergeAdvanced( + // Note that the third parameter gets query loci + placedRecords, mergeDistance, placement => placement.visiblePart.getQueryLocus() + ); + + // Sort so we place the largest query loci first in the next step + queryLocusMerges = queryLocusMerges.sort((a, b) => b.locus.getLength() - a.locus.getLength()); + + const intervalPlacer = new IntervalPlacer(MARGIN); + const drawData: PlacedMergedAlignment[] = []; + for (const merge of queryLocusMerges) { + const mergeLocus = merge.locus; + const placementsInMerge = merge.sources; // Placements that made the merged locus + const mergeDrawWidth = drawModel.basesToXWidth(mergeLocus.getLength()); + const halfDrawWidth = 0.5 * mergeDrawWidth; + if (mergeDrawWidth < MIN_MERGE_DRAW_WIDTH) { + continue; + } + + // Find the center of the primary segments, and try to center the merged query locus there too. + const drawCenter = computeCentroid(placementsInMerge.map(segment => segment.targetXSpan)); + const targetXStart = Math.min(...placementsInMerge.map(segment => segment.targetXSpan.start)); + const targetEnd = Math.max(...placementsInMerge.map(segment => segment.targetXSpan.end)); + const mergeTargetXSpan = new OpenInterval(targetXStart, targetEnd); + const preferredStart = drawCenter - halfDrawWidth; + const preferredEnd = drawCenter + halfDrawWidth; + // Place it so it doesn't overlap other segments + const mergeXSpan = intervalPlacer.place(new OpenInterval(preferredStart, preferredEnd)); + + // Put the actual secondary/query genome segments in the placed merged query locus from above + const queryLoci = placementsInMerge.map(placement => placement.record.queryLocus); + const isReverse = plotStrand==="-"?true:false; + const lociXSpans = this._placeInternalLoci(mergeLocus, queryLoci, mergeXSpan, isReverse, drawModel); + for (let i = 0; i < queryLoci.length; i++) { + placementsInMerge[i].queryXSpan = lociXSpans[i]; + } + + drawData.push({ + queryFeature: new Feature(undefined, mergeLocus, plotStrand), + targetXSpan: mergeTargetXSpan, + queryXSpan: mergeXSpan, + segments: placementsInMerge + }); + } + + return { + isFineMode: false, + primaryVisData: visData, + queryRegion: this._makeQueryGenomeRegion(drawData, visWidth, drawModel), + drawData, + plotStrand, + primaryGenome: this._alignmentFetcher.primaryGenome, + queryGenome: this._alignmentFetcher.queryGenome, + basesPerPixel: drawModel.xWidthToBases(1) + }; + } + + /** + * Calculates context coordinates in the *primary* genome for alignment records. Returns PlacedAlignments with NO x + * coordinates set. Make sure you set them before returning them in any public API! + * + * @param records + * @param visData + */ + _computeContextLocations(records: AlignmentRecord[], visData: ViewExpansion): PlacedAlignment[] { + const {visRegion, visWidth} = visData; + return FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => { + return { + record: placement.feature as AlignmentRecord, + visiblePart: AlignmentSegment.fromFeatureSegment(placement.visiblePart), + contextSpan: placement.contextLocation, + targetXSpan: placement.xSpan, + queryXSpan: null, + }; + }); + } + + /** + * + * @param placedAlignment + * @param minGapLength + */ + _getPrimaryGenomeGaps(placements: PlacedAlignment[], minGapLength: number): Gap[] { + const gaps = []; + for (const placement of placements) { + const {visiblePart, contextSpan} = placement; + const segments = segmentSequence(visiblePart.getTargetSequence(), minGapLength, true); + const baseLookup = makeBaseNumberLookup(visiblePart.getTargetSequence(), contextSpan.start); + for (const segment of segments) { + gaps.push({ + contextBase: baseLookup[segment.index], + length: segment.length + }); + } + } + return gaps; + } + + _placeSequenceSegments(sequence: string, minGapLength: number, startX: number, drawModel: LinearDrawingModel) { + const segments = segmentSequence(sequence, minGapLength); + segments.sort((a, b) => a.index - b.index); + let x = startX; + for (const segment of segments) { + const bases = segment.isGap ? segment.length : countBases(sequence.substr(segment.index, segment.length)); + const xSpanLength = drawModel.basesToXWidth(bases); + (segment as PlacedSequenceSegment).xSpan = new OpenInterval(x, x + xSpanLength); + x += xSpanLength; + } + return (segments as PlacedSequenceSegment[]); + } + + /** + * + * @param placements + * @param minGapLength + * @param pixelsPerBase + */ + _getQueryPieces(placements: PlacedAlignment[]): QueryGenomePiece[] { + const queryPieces: QueryGenomePiece[] = []; + for (const placement of placements) { + const {record, visiblePart} = placement; + const isReverse = record.getIsReverseStrandQuery(); + const querySeq = visiblePart.getQuerySequence(); + let baseLookup; + if (isReverse) { + baseLookup = makeBaseNumberLookup(querySeq, visiblePart.getQueryLocusFine().end, true); + } else { + baseLookup = makeBaseNumberLookup(querySeq, visiblePart.getQueryLocusFine().start); + } + const queryChr = record.queryLocus.chr; + + for (const segment of placement.querySegments) { + const {isGap, index, length, xSpan} = segment; + if (isGap) { + continue; + } + + const base = baseLookup[index]; + const locusLength = countBases(querySeq.substr(index, length)); + let segmentLocus; + if (isReverse) { + segmentLocus = new ChromosomeInterval(queryChr, base - locusLength, base); + } else { + segmentLocus = new ChromosomeInterval(queryChr, base, base + locusLength); + } + queryPieces.push({ + queryFeature: new Feature(undefined, segmentLocus, record.queryStrand), + queryXSpan: xSpan + }); + } + } + + return queryPieces; + } + + _makeQueryGenomeRegion(genomePieces: QueryGenomePiece[], visWidth: number, + drawModel: LinearDrawingModel): DisplayedRegionModel + { + // Sort by start + const sortedPieces = genomePieces.slice().sort((a, b) => a.queryXSpan.start - b.queryXSpan.start); + const features = []; + + let x = 0; + let prevLocus = new ChromosomeInterval('', -1, -1); // Placeholder + for (const piece of sortedPieces) { + const {queryXSpan, queryFeature} = piece; + const queryLocus = queryFeature.getLocus(); + + const gapPixels = queryXSpan.start - x; // Compute potential gap + const gapBases = Math.round(drawModel.xWidthToBases(gapPixels)); + if (gapBases >= 1) { + const specialName = doLociTouchInGenome(queryLocus, prevLocus) ? + `${niceBpCount(gapBases)} gap` : undefined; + features.push(NavigationContext.makeGap(gapBases, specialName)); + } + + features.push(queryFeature); + x = queryXSpan.end; + prevLocus = queryLocus; + } + + const finalGapBases = Math.round(drawModel.xWidthToBases(visWidth - x)); + if (finalGapBases > 0) { + features.push(NavigationContext.makeGap(finalGapBases)); + } + return new DisplayedRegionModel( new NavigationContext('', features) ); + } + + _placeInternalLoci(parentLocus: ChromosomeInterval, internalLoci: ChromosomeInterval[], parentXSpan: OpenInterval, + drawReverse: boolean, drawModel: LinearDrawingModel) + { + const xSpans = []; + if (drawReverse) { // place segments from right to left if drawReverse + for (const locus of internalLoci) { + const distanceFromParent = locus.start - parentLocus.start; + const xDistanceFromParent = drawModel.basesToXWidth(distanceFromParent); + const locusXEnd = parentXSpan.end - xDistanceFromParent; + const xWidth = drawModel.basesToXWidth(locus.getLength()); + const xEnd = locusXEnd parentXSpan.start?(locusXEnd - xWidth):parentXSpan.start; + xSpans.push(new OpenInterval(xStart, xEnd)); + } + } + else { + for (const locus of internalLoci) { + const distanceFromParent = locus.start - parentLocus.start; + const xDistanceFromParent = drawModel.basesToXWidth(distanceFromParent); + const locusXStart = parentXSpan.start + xDistanceFromParent; + const xWidth = drawModel.basesToXWidth(locus.getLength()); + const xStart = locusXStart>parentXSpan.start?locusXStart:parentXSpan.start; + const xEnd = (locusXStart + xWidth) placement.getOverlap(preferredLocation) != null)) { + const center = 0.5 * (preferredLocation.start + preferredLocation.end) + const isInsertLeft = Math.abs(center - this.leftExtent) < Math.abs(center - this.rightExtent); + finalLocation = isInsertLeft ? + new OpenInterval(this.leftExtent - preferredLocation.getLength(), this.leftExtent) : + new OpenInterval(this.rightExtent, this.rightExtent + preferredLocation.getLength()); + } + + this._placements.push(finalLocation); + if (finalLocation.start < this.leftExtent) { + this.leftExtent = finalLocation.start - this.margin; + } + if (finalLocation.end > this.rightExtent) { + this.rightExtent = finalLocation.end + this.margin; + } + + return finalLocation; + } + + retrievePlacements() { + return this._placements; + } +} + +function computeCentroid(intervals: OpenInterval[]) { + const numerator = _.sumBy(intervals, interval => 0.5 * interval.getLength() * (interval.start + interval.end)); + const denominator = _.sumBy(intervals, interval => interval.getLength()); + return numerator / denominator; +} + +function doLociTouchInGenome(locus1: ChromosomeInterval, locus2: ChromosomeInterval) { + if (locus1.chr !== locus2.chr) { + return false; + } + + return locus1.end === locus2.start || locus2.end === locus1.start; +} \ No newline at end of file From 036b6472728b5525d0ee01117186f4c2323aed87 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Fri, 10 May 2019 13:12:28 -0500 Subject: [PATCH 02/15] multialignment in progress --- .../alignment/MultiAlignmentViewCalculator.ts | 93 +++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 4414036f..7ca34623 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -73,6 +73,10 @@ export interface MultiAlignment { [genome: string]: Alignment } +interface RecordsObj { + [queryGenome: string]: AlignmentRecord[] +} + const MAX_FINE_MODE_BASES_PER_PIXEL = 10; const MARGIN = 5; // const MIN_GAP_DRAW_WIDTH = 3; @@ -81,21 +85,25 @@ const MIN_MERGE_DRAW_WIDTH = 5; const FEATURE_PLACER = new FeaturePlacer(); export class MultiAlignmentViewCalculator { - private _alignmentFetcher: {}; + private primaryGenome: string; + // private _alignmentFetcher: {[queryGenome: string]: AlignmentFetcher}; + private _alignmentFetchers: AlignmentFetcher[]; private _viewBeingFetched: ViewExpansion; constructor(primaryGenome: string, queryGenomes: string[]) { - this._alignmentFetcher = queryGenomes.reduce( - (obj, queryGenome) => ({ ...obj, [queryGenome]: new AlignmentFetcher(primaryGenome, queryGenome) }), {} - ) - this._alignmentFetcher = queryGenomes.map(queryGenome => + // this._alignmentFetcher = queryGenomes.reduce( + // (obj, queryGenome) => ({ ...obj, [queryGenome]: new AlignmentFetcher(primaryGenome, queryGenome) }), {} + // ); + this._alignmentFetchers = queryGenomes.map(queryGenome => new AlignmentFetcher(primaryGenome, queryGenome)); this._viewBeingFetched = null; + this.primaryGenome = primaryGenome; this.multiAlign = memoizeOne(this.multiAlign); } cleanUp() { - this._alignmentFetcher.map(fetcher => fetcher.cleanUp()); + // Object.values(this._alignmentFetcher).map(fetcher => fetcher.cleanUp()); + this._alignmentFetchers.map(fetcher => fetcher.cleanUp()); } async multiAlign(visData: ViewExpansion): Promise { const {visRegion, visWidth, viewWindowRegion} = visData; @@ -103,30 +111,61 @@ export class MultiAlignmentViewCalculator { const drawModel = new LinearDrawingModel(visRegion, visWidth); const isFineMode = drawModel.xWidthToBases(1) < MAX_FINE_MODE_BASES_PER_PIXEL; - let recordsArray; + let recordsObj: RecordsObj; if (isFineMode) { - recordsArray = await this._alignmentFetcher.map( - fetcher => fetcher.fetchAlignment(visRegion, visData, false) + recordsObj = await this._alignmentFetchers.reduce( + (obj, fetcher) => + ({...obj, [fetcher.queryGenome]: fetcher.fetchAlignment(visRegion, visData, false)}), {} + // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) ); + // manipulate recordsObj to insert primary gaps using all info. Feed each element to alignFine/alignRough. + fineRecordsObj = this.refineRecordsObj(recordsObj, visData); + return Object.keys(fineRecordsObj).reduce( + (multiAlign, queryGenome) => + ({...multiAlign, [queryGenome]: + this.alignFine(queryGenome, recordsObj[queryGenome], visData) + }), {} + ); + // let multiAlign: MultiAlignment; + // for (var queryGenome in fineRecordsObj) { + // if (fineRecordsObj.hasOwnProperty(queryGenome)) { + // multiAlign[queryGenome] = this.alignFine(queryGenome, recordsObj[queryGenome], visData) + // } + // } + // return multiAlign; } else { - recordsArray = await this._alignmentFetcher.map( - fetcher => fetcher.fetchAlignment(viewWindowRegion, visData, true) + recordsObj = await this._alignmentFetchers.reduce( + (obj, fetcher) => + ({...obj, [fetcher.queryGenome]: fetcher.fetchAlignment(viewWindowRegion, visData, true)}), {} + // fetcher => fetcher.fetchAlignment(viewWindowRegion, visData, true) ); - console.log(recordsArray); - let multiAlign; - for (let i = 0; i < recordsArray.length; i++) { - const records = recordsArray[i]; - const alignment = this.alignRough(records, visData); - multiAlign[queryGenomes[i]] = alignment; - } + // Don't need to change each records for rough alignment, directly loop through them: + return Object.keys(recordsObj).reduce( + (multiAlign, queryGenome) => + ({...multiAlign, [queryGenome]: + this.alignRough(queryGenome, recordsObj[queryGenome], visData) + }), {} + ); + // let multiAlign: MultiAlignment; + // for (var queryGenome in recordsObj) { + // if (recordsObj.hasOwnProperty(queryGenome)) { + // multiAlign[queryGenome] = this.alignRough(queryGenome, recordsObj[queryGenome], visData) + // } + // } + // return multiAlign; } - // manipulate recordsArray, feed each element to alignFine/alignRough. - return {}; // return isFineMode ? this.alignFine(records, visData) : this.alignRough(records, visData, plotStrand); } - - alignFine(records: AlignmentRecord[], visData: ViewExpansion): Alignment { + refineRecordsObj(recordsObj: RecordsObj, visData: ViewExpansion): RecordsObj { + const {visRegion, visWidth} = visData; + FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => { + return { + visiblePart: AlignmentSegment.fromFeatureSegment(placement.visiblePart), + }; + }); + } + alignFine(queryGenome: string, records: AlignmentRecord[], visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... const {visRegion, viewWindow, viewWindowRegion} = visData; const oldNavContext = visRegion.getNavigationContext(); @@ -260,8 +299,8 @@ export class MultiAlignmentViewCalculator { queryRegion, drawData: placements, drawGapText: drawGapTexts, - primaryGenome: this._alignmentFetcher.primaryGenome, - queryGenome: this._alignmentFetcher.queryGenome, + primaryGenome: this.primaryGenome, + queryGenome: queryGenome, basesPerPixel: newDrawModel.xWidthToBases(1) }; @@ -284,7 +323,7 @@ export class MultiAlignmentViewCalculator { * @param {number} width - view width of the primary genome * @return {PlacedMergedAlignment[]} placed merged alignments */ - alignRough(alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { + alignRough(queryGenome: string, alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { const {visRegion, visWidth} = visData; const drawModel = new LinearDrawingModel(visRegion, visWidth); const mergeDistance = drawModel.xWidthToBases(MERGE_PIXEL_DISTANCE); @@ -352,8 +391,8 @@ export class MultiAlignmentViewCalculator { queryRegion: this._makeQueryGenomeRegion(drawData, visWidth, drawModel), drawData, plotStrand, - primaryGenome: this._alignmentFetcher.primaryGenome, - queryGenome: this._alignmentFetcher.queryGenome, + primaryGenome: this.primaryGenome, + queryGenome: queryGenome, basesPerPixel: drawModel.xWidthToBases(1) }; } From c21baf2dcca8346a9f3c96a13729847efae0aa11 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Mon, 13 May 2019 17:12:20 -0500 Subject: [PATCH 03/15] multialign in progress --- .../alignment/MultiAlignmentViewCalculator.ts | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 7ca34623..0b2fc6dc 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -115,17 +115,19 @@ export class MultiAlignmentViewCalculator { if (isFineMode) { recordsObj = await this._alignmentFetchers.reduce( (obj, fetcher) => - ({...obj, [fetcher.queryGenome]: fetcher.fetchAlignment(visRegion, visData, false)}), {} + ({...obj, [fetcher.queryGenome]: + fetcher.fetchAlignment(visRegion, visData, false) + }), {} // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) ); // manipulate recordsObj to insert primary gaps using all info. Feed each element to alignFine/alignRough. - fineRecordsObj = this.refineRecordsObj(recordsObj, visData); + const fineRecordsObj = this.refineRecordsObj(recordsObj, visData); return Object.keys(fineRecordsObj).reduce( (multiAlign, queryGenome) => - ({...multiAlign, [queryGenome]: - this.alignFine(queryGenome, recordsObj[queryGenome], visData) - }), {} - ); + ({...multiAlign, [queryGenome]: + this.alignFine(queryGenome, recordsObj[queryGenome], visData) + }), {} + ); // let multiAlign: MultiAlignment; // for (var queryGenome in fineRecordsObj) { // if (fineRecordsObj.hasOwnProperty(queryGenome)) { @@ -136,16 +138,18 @@ export class MultiAlignmentViewCalculator { } else { recordsObj = await this._alignmentFetchers.reduce( (obj, fetcher) => - ({...obj, [fetcher.queryGenome]: fetcher.fetchAlignment(viewWindowRegion, visData, true)}), {} + ({...obj, [fetcher.queryGenome]: + fetcher.fetchAlignment(viewWindowRegion, visData, true) + }), {} // fetcher => fetcher.fetchAlignment(viewWindowRegion, visData, true) ); // Don't need to change each records for rough alignment, directly loop through them: return Object.keys(recordsObj).reduce( (multiAlign, queryGenome) => - ({...multiAlign, [queryGenome]: - this.alignRough(queryGenome, recordsObj[queryGenome], visData) - }), {} - ); + ({...multiAlign, [queryGenome]: + this.alignRough(queryGenome, recordsObj[queryGenome], visData) + }), {} + ); // let multiAlign: MultiAlignment; // for (var queryGenome in recordsObj) { // if (recordsObj.hasOwnProperty(queryGenome)) { @@ -159,13 +163,22 @@ export class MultiAlignmentViewCalculator { } refineRecordsObj(recordsObj: RecordsObj, visData: ViewExpansion): RecordsObj { const {visRegion, visWidth} = visData; - FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => { - return { - visiblePart: AlignmentSegment.fromFeatureSegment(placement.visiblePart), - }; - }); + const visibleAlignObj = Object.keys(recordsObj).reduce( + (visibleAlignObj, queryGenome) => + { + const placements = this._computeContextLocations(recordsObj[queryGenome], visData); + const visibleParts = placements.map(placement => placement.visiblePart); + return ({...visibleAlignObj, [queryGenome]: visibleParts}) + }, {} + ); + + + // Object.values(recordsObj).map( records => + // FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => + // AlignmentSegment.fromFeatureSegment(placement.visiblePart)) + // ); } - alignFine(queryGenome: string, records: AlignmentRecord[], visData: ViewExpansion): Alignment { + alignFine(query: string, records: AlignmentRecord[], visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... const {visRegion, viewWindow, viewWindowRegion} = visData; const oldNavContext = visRegion.getNavigationContext(); @@ -300,7 +313,7 @@ export class MultiAlignmentViewCalculator { drawData: placements, drawGapText: drawGapTexts, primaryGenome: this.primaryGenome, - queryGenome: queryGenome, + queryGenome: query, basesPerPixel: newDrawModel.xWidthToBases(1) }; @@ -323,7 +336,7 @@ export class MultiAlignmentViewCalculator { * @param {number} width - view width of the primary genome * @return {PlacedMergedAlignment[]} placed merged alignments */ - alignRough(queryGenome: string, alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { + alignRough(query: string, alignmentRecords: AlignmentRecord[], visData: ViewExpansion): Alignment { const {visRegion, visWidth} = visData; const drawModel = new LinearDrawingModel(visRegion, visWidth); const mergeDistance = drawModel.xWidthToBases(MERGE_PIXEL_DISTANCE); @@ -392,7 +405,7 @@ export class MultiAlignmentViewCalculator { drawData, plotStrand, primaryGenome: this.primaryGenome, - queryGenome: queryGenome, + queryGenome: query, basesPerPixel: drawModel.xWidthToBases(1) }; } From 0ea25db263d5e4d684cf53610e41a6286ce47c35 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 15 May 2019 17:06:48 -0500 Subject: [PATCH 04/15] multi align inprogress --- .../trackContainers/TrackViewManager.tsx | 61 ++++++++++--------- .../alignment/MultiAlignmentViewCalculator.ts | 33 +++++++--- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/frontend/src/components/trackContainers/TrackViewManager.tsx b/frontend/src/components/trackContainers/TrackViewManager.tsx index 3a86ab87..c9fc7d0c 100644 --- a/frontend/src/components/trackContainers/TrackViewManager.tsx +++ b/frontend/src/components/trackContainers/TrackViewManager.tsx @@ -8,7 +8,7 @@ import DisplayedRegionModel from '../../model/DisplayedRegionModel'; import { ViewExpansion } from '../../model/RegionExpander'; import { GuaranteeMap } from '../../model/GuaranteeMap'; import { AlignmentViewCalculator, Alignment } from '../../model/alignment/AlignmentViewCalculator'; -// import { MultiAlignmentViewCalculator } from '../../model/alignment/MultiAlignmentViewCalculator'; +import { MultiAlignmentViewCalculator, MultiAlignment } from '../../model/alignment/MultiAlignmentViewCalculator'; interface DataManagerProps { genome: string; // The primary genome @@ -23,12 +23,13 @@ interface DataManagerState { primaryView: ViewExpansion; } -export interface AlignmentPromises { - [genome: string]: Promise -} +// export interface AlignmentPromises { +// [genome: string]: Promise +// } interface WrappedComponentProps { - alignments: AlignmentPromises; + alignments: Promise + // alignments: AlignmentPromises; basesPerPixel: number; primaryViewPromise: Promise; primaryView: ViewExpansion; @@ -37,18 +38,17 @@ interface WrappedComponentProps { export function withTrackView(WrappedComponent: React.ComponentType) { class TrackViewManager extends React.Component { private _primaryGenome: string; - // private _multialignmentCalculator: GuaranteeMap - private _alignmentCalculatorForGenome: GuaranteeMap + private _multialignmentCalculator: MultiAlignmentViewCalculator + // private _alignmentCalculatorForGenome: GuaranteeMap constructor(props: DataManagerProps) { super(props); this._primaryGenome = props.genome; - // this._multialignmentCalculator = new GuaranteeMap( - // queryGenomes => new MultiAlignmentViewCalculator(this._primaryGenome, queryGenomes) - // ) - this._alignmentCalculatorForGenome = new GuaranteeMap( - queryGenome => new AlignmentViewCalculator(this._primaryGenome, queryGenome) - ); + const queryGenomes = this.getSecondaryGenomes(props.tracks); + this._multialignmentCalculator = new MultiAlignmentViewCalculator(this._primaryGenome, queryGenomes); + // this._alignmentCalculatorForGenome = new GuaranteeMap( + // queryGenome => new AlignmentViewCalculator(this._primaryGenome, queryGenome) + // ); this.state = { primaryView: this.props.expansionAmount.calculateExpansion(props.viewRegion, this.getVisualizationWidth()) @@ -69,19 +69,19 @@ export function withTrackView(WrappedComponent: React.ComponentType { const visData = this.props.expansionAmount.calculateExpansion(viewRegion, this.getVisualizationWidth()); - const secondaryGenome = this.getSecondaryGenomes(tracks)[0]; // Just the first one - if (!secondaryGenome) { - // const secondaryGenomes = this.getSecondaryGenomes(tracks); // Just the first one - // if (!secondaryGenomes) { + // const secondaryGenome = this.getSecondaryGenomes(tracks)[0]; // Just the first one + // if (!secondaryGenome) { + const secondaryGenomes = this.getSecondaryGenomes(tracks); // Just the first one + if (!secondaryGenomes) { return visData; } - const alignmentCalculator = this._alignmentCalculatorForGenome.get(secondaryGenome); - // const alignmentCalculator = this._multialignmentCalculator.get(secondaryGenomes); + // const alignmentCalculator = this._alignmentCalculatorForGenome.get(secondaryGenome); try { - const alignment = await alignmentCalculator.align(visData); - this.setState({ primaryView: alignment.primaryVisData }); - return alignment.primaryVisData; + const alignment = await this._multialignmentCalculator.multiAlign(visData); + const primaryVisData = await alignment[0].primaryVisData; + this.setState({ primaryView: primaryVisData }); + return primaryVisData; } catch (error) { console.error(error); console.error("Falling back to nonaligned primary view"); @@ -90,17 +90,18 @@ export function withTrackView(WrappedComponent: React.ComponentType { const secondaryGenomes = this.getSecondaryGenomes(tracks); const visData = this.props.expansionAmount.calculateExpansion(viewRegion, this.getVisualizationWidth()); // const alignmentCalculator = this._multialignmentCalculator.get(secondaryGenomes); - // return alignmentCalculator.align(visData); - const alignmentForGenome: AlignmentPromises = {}; - for (const genome of secondaryGenomes) { - const alignmentCalculator = this._alignmentCalculatorForGenome.get(genome); - alignmentForGenome[genome] = alignmentCalculator.align(visData); - } - return alignmentForGenome; + const alignmentCalculator = this._multialignmentCalculator; + return alignmentCalculator.multiAlign(visData); + // const alignmentForGenome: AlignmentPromises = {}; + // for (const genome of secondaryGenomes) { + // const alignmentCalculator = this._alignmentCalculatorForGenome.get(genome); + // alignmentForGenome[genome] = alignmentCalculator.align(visData); + // } + // return alignmentForGenome; } async componentDidUpdate(prevProps: DataManagerProps) { diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 0b2fc6dc..3d4038a4 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -121,8 +121,9 @@ export class MultiAlignmentViewCalculator { // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) ); // manipulate recordsObj to insert primary gaps using all info. Feed each element to alignFine/alignRough. - const fineRecordsObj = this.refineRecordsObj(recordsObj, visData); - return Object.keys(fineRecordsObj).reduce( + // const fineRecordsObj = this.refineRecordsObj(recordsObj, visData); + console.log(recordsObj); + return Object.keys(recordsObj).reduce( (multiAlign, queryGenome) => ({...multiAlign, [queryGenome]: this.alignFine(queryGenome, recordsObj[queryGenome], visData) @@ -163,15 +164,26 @@ export class MultiAlignmentViewCalculator { } refineRecordsObj(recordsObj: RecordsObj, visData: ViewExpansion): RecordsObj { const {visRegion, visWidth} = visData; - const visibleAlignObj = Object.keys(recordsObj).reduce( - (visibleAlignObj, queryGenome) => - { - const placements = this._computeContextLocations(recordsObj[queryGenome], visData); - const visibleParts = placements.map(placement => placement.visiblePart); - return ({...visibleAlignObj, [queryGenome]: visibleParts}) - }, {} - ); + const minGapLength = 0.99; + // const placementsObj = Object.keys(recordsObj).reduce( + // (placementsObj, queryGenome) => + // { + // const placements = this._computeContextLocations(recordsObj[queryGenome], visData); + // const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); + // const placementSubObj = {'placements': placements, 'primaryGaps': primaryGaps}; + // return ({...placementsObj, [queryGenome]: placementSubObj}); + // }, {} + // ); + + // const allGaps = Object.values(placementsObj).reduce((gaps, subObj) => { + // gaps.push(subObj.map(placement => placement.primaryGaps)); + // return gaps; + // }, []); + // const distinctCoordinates = [...new Set (Object.values(placementsObj).map(sub => sub['primaryGaps']))]; + + + return recordsObj; // Object.values(recordsObj).map( records => // FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => @@ -187,6 +199,7 @@ export class MultiAlignmentViewCalculator { const minGapLength = 0.99; // Calculate context coordinates of the records and gaps within. + console.log(records); const placements = this._computeContextLocations(records, visData); const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); From 2b6f052d73e5a662d23560e6a8368e8dbf2d7858 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Tue, 21 May 2019 17:10:08 -0500 Subject: [PATCH 05/15] multi align refactor in progress --- .../trackContainers/TrackDataManager.tsx | 4 +-- .../alignment/MultiAlignmentViewCalculator.ts | 25 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/trackContainers/TrackDataManager.tsx b/frontend/src/components/trackContainers/TrackDataManager.tsx index 65d1cbc0..d9ae83b8 100644 --- a/frontend/src/components/trackContainers/TrackDataManager.tsx +++ b/frontend/src/components/trackContainers/TrackDataManager.tsx @@ -10,8 +10,8 @@ import { TrackModel } from '../../model/TrackModel'; import NavigationContext from '../../model/NavigationContext'; import { GuaranteeMap } from '../../model/GuaranteeMap'; import { ViewExpansion } from '../../model/RegionExpander'; -import { Alignment } from '../../model/alignment/AlignmentViewCalculator'; - +// import { Alignment } from '../../model/alignment/AlignmentViewCalculator'; +import { Alignment, MultiAlignment } from '../../model/alignment/MultiAlignmentViewCalculator'; interface TrackDataMap { [id: number]: TrackData } diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 3d4038a4..7df6a03d 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -113,13 +113,24 @@ export class MultiAlignmentViewCalculator { const isFineMode = drawModel.xWidthToBases(1) < MAX_FINE_MODE_BASES_PER_PIXEL; let recordsObj: RecordsObj; if (isFineMode) { - recordsObj = await this._alignmentFetchers.reduce( - (obj, fetcher) => - ({...obj, [fetcher.queryGenome]: - fetcher.fetchAlignment(visRegion, visData, false) - }), {} - // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) - ); + const recordsPromise = this._alignmentFetchers.map(async fetcher => { + const records = await fetcher.fetchAlignment(visRegion, visData, false); + return {"query": fetcher.queryGenome, "records": records}; + }); + const recordsArray = await Promise.all(recordsPromise); + for (const records of recordsArray) { + recordsObj[records.query] = records.records; + } + // for (const fetcher of this._alignmentFetchers) { + // recordsObj[fetcher.queryGenome] = await fetcher.fetchAlignment(visRegion, visData, false); + // } + // recordsObj = await this._alignmentFetchers.reduce( + // (obj, fetcher) => + // ({...obj, [fetcher.queryGenome]: + // fetcher.fetchAlignment(visRegion, visData, false) + // }), {} + // // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) + // ); // manipulate recordsObj to insert primary gaps using all info. Feed each element to alignFine/alignRough. // const fineRecordsObj = this.refineRecordsObj(recordsObj, visData); console.log(recordsObj); From b5b029ca2c2e950cd0067c3f6b1fc0051947144f Mon Sep 17 00:00:00 2001 From: xzhuo Date: Tue, 21 May 2019 17:11:21 -0500 Subject: [PATCH 06/15] multi align refactor in progress --- .../src/components/trackContainers/TrackDataManager.tsx | 4 ++-- .../src/components/trackContainers/TrackDataManager2.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/trackContainers/TrackDataManager.tsx b/frontend/src/components/trackContainers/TrackDataManager.tsx index d9ae83b8..43d31fd4 100644 --- a/frontend/src/components/trackContainers/TrackDataManager.tsx +++ b/frontend/src/components/trackContainers/TrackDataManager.tsx @@ -1,7 +1,7 @@ import React from 'react'; import _ from 'lodash'; -import { AlignmentPromises } from './TrackViewManager'; +// import { AlignmentPromises } from './TrackViewManager'; import { getTrackConfig } from '../trackConfig/getTrackConfig'; import DataSource from '../../dataSources/DataSource'; @@ -21,7 +21,7 @@ interface DataManagerProps { tracks: TrackModel[]; // Tracks viewRegion: DisplayedRegionModel; // Region that the user requested basesPerPixel: number; - alignments: AlignmentPromises; + alignments: MultiAlignment; primaryViewPromise: Promise; } diff --git a/frontend/src/components/trackContainers/TrackDataManager2.tsx b/frontend/src/components/trackContainers/TrackDataManager2.tsx index d0827d8c..71e405a9 100644 --- a/frontend/src/components/trackContainers/TrackDataManager2.tsx +++ b/frontend/src/components/trackContainers/TrackDataManager2.tsx @@ -1,7 +1,7 @@ import React from 'react'; import _ from 'lodash'; -import { AlignmentPromises } from './TrackViewManager'; +// import { AlignmentPromises } from './TrackViewManager'; import { getTrackConfig } from '../trackConfig/getTrackConfig'; import DataSource from '../../dataSources/DataSource'; @@ -21,7 +21,7 @@ interface DataManagerProps { tracks: TrackModel[]; // Tracks viewRegion: DisplayedRegionModel; // Region that the user requested basesPerPixel: number; - alignments: AlignmentPromises; + // alignments: AlignmentPromises; primaryViewPromise: Promise; } @@ -103,8 +103,8 @@ export function withTrackData(WrappedComponent: React.ComponentType<{trackData: const primaryView = await this.props.primaryViewPromise; visRegion = primaryView.visRegion; } else { - alignment = await this.props.alignments[genome]; - visRegion = alignment.queryRegion; + // alignment = await this.props.alignments[genome]; + // visRegion = alignment.queryRegion; } if (!this.isViewStillFresh(view)) { From fed5ba8bafd6952de096f2bfd1766af4ed88f4b6 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 22 May 2019 17:06:45 -0500 Subject: [PATCH 07/15] multiple alignment in progress --- .../trackContainers/TrackDataManager.tsx | 5 +- .../trackContainers/TrackDataManager2.tsx | 394 +++++++++--------- .../trackContainers/TrackViewManager.tsx | 27 +- .../alignment/MultiAlignmentViewCalculator.ts | 118 +++--- 4 files changed, 262 insertions(+), 282 deletions(-) diff --git a/frontend/src/components/trackContainers/TrackDataManager.tsx b/frontend/src/components/trackContainers/TrackDataManager.tsx index 43d31fd4..4a95528a 100644 --- a/frontend/src/components/trackContainers/TrackDataManager.tsx +++ b/frontend/src/components/trackContainers/TrackDataManager.tsx @@ -1,7 +1,6 @@ import React from 'react'; import _ from 'lodash'; -// import { AlignmentPromises } from './TrackViewManager'; import { getTrackConfig } from '../trackConfig/getTrackConfig'; import DataSource from '../../dataSources/DataSource'; @@ -10,7 +9,6 @@ import { TrackModel } from '../../model/TrackModel'; import NavigationContext from '../../model/NavigationContext'; import { GuaranteeMap } from '../../model/GuaranteeMap'; import { ViewExpansion } from '../../model/RegionExpander'; -// import { Alignment } from '../../model/alignment/AlignmentViewCalculator'; import { Alignment, MultiAlignment } from '../../model/alignment/MultiAlignmentViewCalculator'; interface TrackDataMap { [id: number]: TrackData @@ -124,7 +122,8 @@ export function withTrackData(WrappedComponent: React.ComponentType<{trackData: const primaryView = await this.props.primaryViewPromise; visRegion = primaryView.visRegion; } else { - alignment = await this.props.alignments[genome]; + const alignments = await this.props.alignments; + alignment = alignments[genome]; visRegion = alignment.queryRegion; } diff --git a/frontend/src/components/trackContainers/TrackDataManager2.tsx b/frontend/src/components/trackContainers/TrackDataManager2.tsx index 71e405a9..616e397e 100644 --- a/frontend/src/components/trackContainers/TrackDataManager2.tsx +++ b/frontend/src/components/trackContainers/TrackDataManager2.tsx @@ -1,199 +1,201 @@ -import React from 'react'; -import _ from 'lodash'; +// import React from 'react'; +// import _ from 'lodash'; // import { AlignmentPromises } from './TrackViewManager'; -import { getTrackConfig } from '../trackConfig/getTrackConfig'; -import DataSource from '../../dataSources/DataSource'; - -import DisplayedRegionModel from '../../model/DisplayedRegionModel'; -import { TrackModel } from '../../model/TrackModel'; -import NavigationContext from '../../model/NavigationContext'; -import { GuaranteeMap } from '../../model/GuaranteeMap'; -import { ViewExpansion } from '../../model/RegionExpander'; -import { Alignment } from '../../model/alignment/AlignmentViewCalculator'; - -interface TrackDataMap { - [id: number]: TrackData -} - -interface DataManagerProps { - genome: string; // The primary genome - tracks: TrackModel[]; // Tracks - viewRegion: DisplayedRegionModel; // Region that the user requested - basesPerPixel: number; - // alignments: AlignmentPromises; - primaryViewPromise: Promise; -} - -export interface TrackData { - alignment: Alignment; - visRegion: DisplayedRegionModel; - data: any[]; - isLoading: boolean; - error?: any; -} -const INITIAL_TRACK_DATA: TrackData = { - alignment: null, - visRegion: new DisplayedRegionModel(new NavigationContext('', [NavigationContext.makeGap(1000)])), - data: [], - isLoading: true, - error: null -}; - -export function withTrackData(WrappedComponent: React.ComponentType<{trackData: TrackDataMap}>) { - return class TrackDataManager extends React.Component { - private _dataSourceManager: DataSourceManager; - private _primaryGenome: string; - - constructor(props: DataManagerProps) { - super(props); - this._primaryGenome = props.genome; - this._dataSourceManager = new DataSourceManager(); - this.state = { - dataForId: {} - }; - } - - componentDidMount() { - this.detectNeededTrackUpdates(); - } - - componentDidUpdate(prevProps: DataManagerProps) { - if (this.props.viewRegion !== prevProps.viewRegion) { - this.detectNeededTrackUpdates(); // Fetch all - } else if (this.props.tracks !== prevProps.tracks) { - this.detectNeededTrackUpdates(prevProps.tracks); // Fetch some - } - } - - componentWillUnmount() { - this._dataSourceManager.cleanUpAll(); - } - - isViewStillFresh(region: DisplayedRegionModel) { - return region === this.props.viewRegion; - } - - detectNeededTrackUpdates(prevTracks: TrackModel[] = []) { - const addedTracks = _.differenceBy(this.props.tracks, prevTracks, track => track.getId()); - const removedTracks = _.differenceBy(prevTracks, this.props.tracks, track => track.getId()); - for (const track of addedTracks) { - this.fetchTrack(track); - } - - // Clean up the data sources and state of removed tracks - const deletionUpdate = _.clone(this.state.dataForId); - for (const track of removedTracks) { - const id = track.getId(); - this._dataSourceManager.cleanUp(id); - delete deletionUpdate[id]; - } - this.setState({dataForId: deletionUpdate}); - } - - async fetchTrack(track: TrackModel) { - this.dispatchTrackUpdate(track, { isLoading: true }); - - const view = this.props.viewRegion; - const genome = track.getMetadata('genome'); - try { - let visRegion; - let alignment = null; - if (!genome || genome === this._primaryGenome) { - const primaryView = await this.props.primaryViewPromise; - visRegion = primaryView.visRegion; - } else { - // alignment = await this.props.alignments[genome]; - // visRegion = alignment.queryRegion; - } - - if (!this.isViewStillFresh(view)) { - return; - } - - const trackConfig = getTrackConfig(track); - const dataSource = this._dataSourceManager.getDataSource(track); - const rawData = await dataSource.getData(visRegion, this.props.basesPerPixel, trackConfig.getOptions()); +// import { getTrackConfig } from '../trackConfig/getTrackConfig'; +// import DataSource from '../../dataSources/DataSource'; + +// import DisplayedRegionModel from '../../model/DisplayedRegionModel'; +// import { TrackModel } from '../../model/TrackModel'; +// import NavigationContext from '../../model/NavigationContext'; +// import { GuaranteeMap } from '../../model/GuaranteeMap'; +// import { ViewExpansion } from '../../model/RegionExpander'; +// import { Alignment } from '../../model/alignment/AlignmentViewCalculator'; + +// interface TrackDataMap { +// [id: number]: TrackData +// } + +// interface DataManagerProps { +// genome: string; // The primary genome +// tracks: TrackModel[]; // Tracks +// viewRegion: DisplayedRegionModel; // Region that the user requested +// basesPerPixel: number; +// // alignments: AlignmentPromises; +// primaryViewPromise: Promise; +// } + +// export interface TrackData { +// alignment: Alignment; +// visRegion: DisplayedRegionModel; +// data: any[]; +// isLoading: boolean; +// error?: any; +// } +// const INITIAL_TRACK_DATA: TrackData = { +// alignment: null, +// visRegion: new DisplayedRegionModel(new NavigationContext('', [NavigationContext.makeGap(1000)])), +// data: [], +// isLoading: true, +// error: null +// }; + +// export function withTrackData(WrappedComponent: React.ComponentType<{trackData: TrackDataMap}>) { +// return class TrackDataManager extends React.Component { +// private _dataSourceManager: DataSourceManager; +// private _primaryGenome: string; + +// constructor(props: DataManagerProps) { +// super(props); +// this._primaryGenome = props.genome; +// this._dataSourceManager = new DataSourceManager(); +// this.state = { +// dataForId: {} +// }; +// } + +// componentDidMount() { +// this.detectNeededTrackUpdates(); +// } + +// componentDidUpdate(prevProps: DataManagerProps) { +// if (this.props.viewRegion !== prevProps.viewRegion) { +// this.detectNeededTrackUpdates(); // Fetch all +// } else if (this.props.tracks !== prevProps.tracks) { +// this.detectNeededTrackUpdates(prevProps.tracks); // Fetch some +// } +// } + +// componentWillUnmount() { +// this._dataSourceManager.cleanUpAll(); +// } + +// isViewStillFresh(region: DisplayedRegionModel) { +// return region === this.props.viewRegion; +// } + +// detectNeededTrackUpdates(prevTracks: TrackModel[] = []) { +// const addedTracks = _.differenceBy(this.props.tracks, prevTracks, track => track.getId()); +// const removedTracks = _.differenceBy(prevTracks, this.props.tracks, track => track.getId()); +// for (const track of addedTracks) { +// this.fetchTrack(track); +// } + +// // Clean up the data sources and state of removed tracks +// const deletionUpdate = _.clone(this.state.dataForId); +// for (const track of removedTracks) { +// const id = track.getId(); +// this._dataSourceManager.cleanUp(id); +// delete deletionUpdate[id]; +// } +// this.setState({dataForId: deletionUpdate}); +// } + +// async fetchTrack(track: TrackModel) { +// this.dispatchTrackUpdate(track, { isLoading: true }); + +// const view = this.props.viewRegion; +// const genome = track.getMetadata('genome'); +// try { +// let visRegion; +// let alignment = null; +// if (!genome || genome === this._primaryGenome) { +// const primaryView = await this.props.primaryViewPromise; +// visRegion = primaryView.visRegion; +// } else { +// alignment = await this.props.alignments[genome]; +// visRegion = alignment.queryRegion; +// } + +// if (!this.isViewStillFresh(view)) { +// return; +// } + +// const trackConfig = getTrackConfig(track); +// const dataSource = this._dataSourceManager.getDataSource(track); +// const rawData = await dataSource.getData( +// visRegion, this.props.basesPerPixel, trackConfig.getOptions() +// ); - if (this.isViewStillFresh(view)) { - this.dispatchTrackUpdate(track, { - alignment, - visRegion, - data: trackConfig.formatData(rawData), - isLoading: false, - error: null, - }); - } - } catch (error) { - if (this.isViewStillFresh(view)) { - console.error(error); - this.dispatchTrackUpdate(track, { - isLoading: false, - error - }); - } - } - } - - dispatchTrackUpdate(track: TrackModel, newTrackState: Partial) { - const id = track.getId(); - this.setState(prevState => { - const update = _.clone(prevState.dataForId); - const prevTrackData = prevState[id] || INITIAL_TRACK_DATA; - update[id] = { - ...prevTrackData, - ...newTrackState - }; - return {dataForId: update}; - }); - } - - getTrackData() { - const ids = this.props.tracks.map(track => track.getId()); - const result: TrackDataMap = {}; - let isMissingData = false; - for (const id of ids) { - if (!(id in this.state.dataForId)) { - result[id] = INITIAL_TRACK_DATA; - isMissingData = true; - } - } - - if (isMissingData) { - return Object.assign(result, this.state.dataForId); - } else { - return this.state.dataForId; - } - } - - render() { - return - } - } -} - -class DataSourceManager { - private _dataSourceForTrackId: GuaranteeMap; - constructor() { - const initDataSource = (id: number, track: TrackModel) => getTrackConfig(track).initDataSource(); - this._dataSourceForTrackId = new GuaranteeMap(initDataSource); - } - - cleanUpAll() { - for (const dataSource of this._dataSourceForTrackId.values()) { - dataSource.cleanUp(); - } - this._dataSourceForTrackId.clear(); - } - - cleanUp(id: number) { - if (this._dataSourceForTrackId.has(id)) { - this._dataSourceForTrackId.get(id).cleanUp(); - this._dataSourceForTrackId.delete(id); - } - } - - getDataSource(track: TrackModel): DataSource { - return this._dataSourceForTrackId.get(track.getId(), track); - } -} +// if (this.isViewStillFresh(view)) { +// this.dispatchTrackUpdate(track, { +// alignment, +// visRegion, +// data: trackConfig.formatData(rawData), +// isLoading: false, +// error: null, +// }); +// } +// } catch (error) { +// if (this.isViewStillFresh(view)) { +// console.error(error); +// this.dispatchTrackUpdate(track, { +// isLoading: false, +// error +// }); +// } +// } +// } + +// dispatchTrackUpdate(track: TrackModel, newTrackState: Partial) { +// const id = track.getId(); +// this.setState(prevState => { +// const update = _.clone(prevState.dataForId); +// const prevTrackData = prevState[id] || INITIAL_TRACK_DATA; +// update[id] = { +// ...prevTrackData, +// ...newTrackState +// }; +// return {dataForId: update}; +// }); +// } + +// getTrackData() { +// const ids = this.props.tracks.map(track => track.getId()); +// const result: TrackDataMap = {}; +// let isMissingData = false; +// for (const id of ids) { +// if (!(id in this.state.dataForId)) { +// result[id] = INITIAL_TRACK_DATA; +// isMissingData = true; +// } +// } + +// if (isMissingData) { +// return Object.assign(result, this.state.dataForId); +// } else { +// return this.state.dataForId; +// } +// } + +// render() { +// return +// } +// } +// } + +// class DataSourceManager { +// private _dataSourceForTrackId: GuaranteeMap; +// constructor() { +// const initDataSource = (id: number, track: TrackModel) => getTrackConfig(track).initDataSource(); +// this._dataSourceForTrackId = new GuaranteeMap(initDataSource); +// } + +// cleanUpAll() { +// for (const dataSource of this._dataSourceForTrackId.values()) { +// dataSource.cleanUp(); +// } +// this._dataSourceForTrackId.clear(); +// } + +// cleanUp(id: number) { +// if (this._dataSourceForTrackId.has(id)) { +// this._dataSourceForTrackId.get(id).cleanUp(); +// this._dataSourceForTrackId.delete(id); +// } +// } + +// getDataSource(track: TrackModel): DataSource { +// return this._dataSourceForTrackId.get(track.getId(), track); +// } +// } diff --git a/frontend/src/components/trackContainers/TrackViewManager.tsx b/frontend/src/components/trackContainers/TrackViewManager.tsx index c9fc7d0c..602109ab 100644 --- a/frontend/src/components/trackContainers/TrackViewManager.tsx +++ b/frontend/src/components/trackContainers/TrackViewManager.tsx @@ -6,8 +6,6 @@ import { withTrackLegendWidth } from '../withTrackLegendWidth'; import { TrackModel } from '../../model/TrackModel'; import DisplayedRegionModel from '../../model/DisplayedRegionModel'; import { ViewExpansion } from '../../model/RegionExpander'; -import { GuaranteeMap } from '../../model/GuaranteeMap'; -import { AlignmentViewCalculator, Alignment } from '../../model/alignment/AlignmentViewCalculator'; import { MultiAlignmentViewCalculator, MultiAlignment } from '../../model/alignment/MultiAlignmentViewCalculator'; interface DataManagerProps { @@ -23,10 +21,6 @@ interface DataManagerState { primaryView: ViewExpansion; } -// export interface AlignmentPromises { -// [genome: string]: Promise -// } - interface WrappedComponentProps { alignments: Promise // alignments: AlignmentPromises; @@ -39,16 +33,13 @@ export function withTrackView(WrappedComponent: React.ComponentType { private _primaryGenome: string; private _multialignmentCalculator: MultiAlignmentViewCalculator - // private _alignmentCalculatorForGenome: GuaranteeMap constructor(props: DataManagerProps) { super(props); this._primaryGenome = props.genome; const queryGenomes = this.getSecondaryGenomes(props.tracks); this._multialignmentCalculator = new MultiAlignmentViewCalculator(this._primaryGenome, queryGenomes); - // this._alignmentCalculatorForGenome = new GuaranteeMap( - // queryGenome => new AlignmentViewCalculator(this._primaryGenome, queryGenome) - // ); + this.state = { primaryView: this.props.expansionAmount.calculateExpansion(props.viewRegion, this.getVisualizationWidth()) @@ -69,17 +60,15 @@ export function withTrackView(WrappedComponent: React.ComponentType { const visData = this.props.expansionAmount.calculateExpansion(viewRegion, this.getVisualizationWidth()); - // const secondaryGenome = this.getSecondaryGenomes(tracks)[0]; // Just the first one - // if (!secondaryGenome) { - const secondaryGenomes = this.getSecondaryGenomes(tracks); // Just the first one + const secondaryGenomes = this.getSecondaryGenomes(tracks); if (!secondaryGenomes) { return visData; } - // const alignmentCalculator = this._alignmentCalculatorForGenome.get(secondaryGenome); try { const alignment = await this._multialignmentCalculator.multiAlign(visData); - const primaryVisData = await alignment[0].primaryVisData; + // All the primaryVisData in alignment should be the same: + const primaryVisData = await Object.values(alignment)[0].primaryVisData; this.setState({ primaryView: primaryVisData }); return primaryVisData; } catch (error) { @@ -91,17 +80,9 @@ export function withTrackView(WrappedComponent: React.ComponentType { - const secondaryGenomes = this.getSecondaryGenomes(tracks); const visData = this.props.expansionAmount.calculateExpansion(viewRegion, this.getVisualizationWidth()); - // const alignmentCalculator = this._multialignmentCalculator.get(secondaryGenomes); const alignmentCalculator = this._multialignmentCalculator; return alignmentCalculator.multiAlign(visData); - // const alignmentForGenome: AlignmentPromises = {}; - // for (const genome of secondaryGenomes) { - // const alignmentCalculator = this._alignmentCalculatorForGenome.get(genome); - // alignmentForGenome[genome] = alignmentCalculator.align(visData); - // } - // return alignmentForGenome; } async componentDidUpdate(prevProps: DataManagerProps) { diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 7df6a03d..2ae1ea68 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -74,26 +74,24 @@ export interface MultiAlignment { } interface RecordsObj { - [queryGenome: string]: AlignmentRecord[] + query: string; + records: AlignmentRecord[]; } const MAX_FINE_MODE_BASES_PER_PIXEL = 10; const MARGIN = 5; // const MIN_GAP_DRAW_WIDTH = 3; +const MIN_GAP_LENGTH = 0.99; const MERGE_PIXEL_DISTANCE = 200; const MIN_MERGE_DRAW_WIDTH = 5; const FEATURE_PLACER = new FeaturePlacer(); export class MultiAlignmentViewCalculator { private primaryGenome: string; - // private _alignmentFetcher: {[queryGenome: string]: AlignmentFetcher}; private _alignmentFetchers: AlignmentFetcher[]; private _viewBeingFetched: ViewExpansion; constructor(primaryGenome: string, queryGenomes: string[]) { - // this._alignmentFetcher = queryGenomes.reduce( - // (obj, queryGenome) => ({ ...obj, [queryGenome]: new AlignmentFetcher(primaryGenome, queryGenome) }), {} - // ); this._alignmentFetchers = queryGenomes.map(queryGenome => new AlignmentFetcher(primaryGenome, queryGenome)); this._viewBeingFetched = null; @@ -102,7 +100,6 @@ export class MultiAlignmentViewCalculator { } cleanUp() { - // Object.values(this._alignmentFetcher).map(fetcher => fetcher.cleanUp()); this._alignmentFetchers.map(fetcher => fetcher.cleanUp()); } async multiAlign(visData: ViewExpansion): Promise { @@ -111,71 +108,73 @@ export class MultiAlignmentViewCalculator { const drawModel = new LinearDrawingModel(visRegion, visWidth); const isFineMode = drawModel.xWidthToBases(1) < MAX_FINE_MODE_BASES_PER_PIXEL; - let recordsObj: RecordsObj; + let recordsArray: RecordsObj[]; if (isFineMode) { const recordsPromise = this._alignmentFetchers.map(async fetcher => { const records = await fetcher.fetchAlignment(visRegion, visData, false); return {"query": fetcher.queryGenome, "records": records}; }); - const recordsArray = await Promise.all(recordsPromise); - for (const records of recordsArray) { - recordsObj[records.query] = records.records; - } - // for (const fetcher of this._alignmentFetchers) { - // recordsObj[fetcher.queryGenome] = await fetcher.fetchAlignment(visRegion, visData, false); - // } - // recordsObj = await this._alignmentFetchers.reduce( - // (obj, fetcher) => - // ({...obj, [fetcher.queryGenome]: - // fetcher.fetchAlignment(visRegion, visData, false) - // }), {} - // // (fetcher) => fetcher.fetchAlignment(visRegion, visData, false) - // ); - // manipulate recordsObj to insert primary gaps using all info. Feed each element to alignFine/alignRough. - // const fineRecordsObj = this.refineRecordsObj(recordsObj, visData); - console.log(recordsObj); - return Object.keys(recordsObj).reduce( - (multiAlign, queryGenome) => - ({...multiAlign, [queryGenome]: - this.alignFine(queryGenome, recordsObj[queryGenome], visData) + const oldRecordsArray = await Promise.all(recordsPromise); + recordsArray = this.refineRecordsArray(oldRecordsArray, visData); + return recordsArray.reduce( + (multiAlign, records) => + ({...multiAlign, [records.query]: + this.alignFine(records.query, records.records, visData) }), {} ); - // let multiAlign: MultiAlignment; - // for (var queryGenome in fineRecordsObj) { - // if (fineRecordsObj.hasOwnProperty(queryGenome)) { - // multiAlign[queryGenome] = this.alignFine(queryGenome, recordsObj[queryGenome], visData) - // } - // } - // return multiAlign; + } else { - recordsObj = await this._alignmentFetchers.reduce( - (obj, fetcher) => - ({...obj, [fetcher.queryGenome]: - fetcher.fetchAlignment(viewWindowRegion, visData, true) - }), {} - // fetcher => fetcher.fetchAlignment(viewWindowRegion, visData, true) - ); + const recordsPromise = this._alignmentFetchers.map(async fetcher => { + const records = await fetcher.fetchAlignment(viewWindowRegion, visData, true); + return {"query": fetcher.queryGenome, "records": records}; + }); + recordsArray = await Promise.all(recordsPromise); + // Don't need to change each records for rough alignment, directly loop through them: - return Object.keys(recordsObj).reduce( - (multiAlign, queryGenome) => - ({...multiAlign, [queryGenome]: - this.alignRough(queryGenome, recordsObj[queryGenome], visData) + return recordsArray.reduce( + (multiAlign, records) => + ({...multiAlign, [records.query]: + this.alignRough(records.query, records.records, visData) }), {} ); - // let multiAlign: MultiAlignment; - // for (var queryGenome in recordsObj) { - // if (recordsObj.hasOwnProperty(queryGenome)) { - // multiAlign[queryGenome] = this.alignRough(queryGenome, recordsObj[queryGenome], visData) - // } - // } - // return multiAlign; } - - // return isFineMode ? this.alignFine(records, visData) : this.alignRough(records, visData, plotStrand); } - refineRecordsObj(recordsObj: RecordsObj, visData: ViewExpansion): RecordsObj { + refineRecordsArray(recordsArray: RecordsObj[], visData: ViewExpansion): RecordsObj[] { const {visRegion, visWidth} = visData; - const minGapLength = 0.99; + const minGapLength = MIN_GAP_LENGTH; + + // use a new array of objects to manipulate later, and + // Combine all gaps from all alignments into a new array: + const refineRecords = []; + const allGaps = []; + for (const recordsObj of recordsArray) { + // Calculate context coordinates of the records and gaps within. + const placements = this._computeContextLocations(recordsObj.records, visData); + const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); + console.log(placements); + console.log(primaryGaps); + refineRecords.push({"recordsObj": recordsObj, "placements": placements, "primaryGaps": primaryGaps}); + allGaps.push(...primaryGaps); + } + + // Combine allGaps to a allGapsSet with unique contextBase. For each of them, the longest length is chosen: + const allGapsSet = []; + const contextBaseSet = [...new Set(allGaps.map(i => i.contextBase))]; + contextBaseSet.sort((a, b) => a - b); + for (const gapContextBase of contextBaseSet) { + const lengths = allGaps.reduce( + (lengths, {contextBase, length}) => [...lengths, ...contextBase===gapContextBase ? [length] : []], [] + ); + const maxLength = Math.max(...lengths); + allGapsSet.push({contextBase: gapContextBase, length: maxLength}) + } + + // For each records, insertion gaps to sequences if for contextBase only in allGapsSet: + console.log(allGapsSet); + for (const records of refineRecords) { + console.log(records.primaryGaps); + + } // const placementsObj = Object.keys(recordsObj).reduce( // (placementsObj, queryGenome) => // { @@ -194,7 +193,7 @@ export class MultiAlignmentViewCalculator { - return recordsObj; + return recordsArray; // Object.values(recordsObj).map( records => // FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => @@ -207,10 +206,9 @@ export class MultiAlignmentViewCalculator { const oldNavContext = visRegion.getNavigationContext(); // const drawModel = new LinearDrawingModel(visRegion, visWidth); // const minGapLength = drawModel.xWidthToBases(MIN_GAP_DRAW_WIDTH); - const minGapLength = 0.99; + const minGapLength = MIN_GAP_LENGTH; // Calculate context coordinates of the records and gaps within. - console.log(records); const placements = this._computeContextLocations(records, visData); const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); From fa635dd0b11d123307efcd3909ff95be2ff38cdc Mon Sep 17 00:00:00 2001 From: xzhuo Date: Thu, 23 May 2019 17:07:06 -0500 Subject: [PATCH 08/15] fix a fine mode hober box bug, multi in progress --- .../commonComponents/AlignmentCoordinates.js | 3 +- .../alignment/MultiAlignmentViewCalculator.ts | 118 +++++++++++------- 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/frontend/src/components/trackVis/commonComponents/AlignmentCoordinates.js b/frontend/src/components/trackVis/commonComponents/AlignmentCoordinates.js index 6c3badc2..a05e6446 100644 --- a/frontend/src/components/trackVis/commonComponents/AlignmentCoordinates.js +++ b/frontend/src/components/trackVis/commonComponents/AlignmentCoordinates.js @@ -29,7 +29,7 @@ class AlignmentSequence extends React.Component { return
{"No alignment available"}
; } else { - const highlightLength = Math.round(basesPerPixel); + const highlightLength = Math.ceil(basesPerPixel); const halfHighlightLength = Math.floor(highlightLength/2); const {visiblePart, record} = alignment; const [start, end] = visiblePart.sequenceInterval; @@ -42,7 +42,6 @@ class AlignmentSequence extends React.Component { const relativeHighlightStart = cusorLocus - halfHighlightLength > 0 ? cusorLocus - halfHighlightLength : 0; const relativeHighlightEnd = cusorLocus + halfHighlightLength < length ? cusorLocus + halfHighlightLength : (length - 1); - const cusorTargetSeqLeft = record.targetSeq.substr( start + relativeDisplayStart, relativeHighlightStart - relativeDisplayStart).toUpperCase(); const cusorTargetSeqMid = record.targetSeq.substr(start + relativeHighlightStart, highlightLength).toUpperCase(); diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 2ae1ea68..ac703b82 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -18,6 +18,7 @@ import { ViewExpansion } from '../RegionExpander'; import { FeaturePlacer } from '../FeaturePlacer'; import DisplayedRegionModel from '../DisplayedRegionModel'; import { niceBpCount } from '../../util'; +import { object } from 'prop-types'; export interface PlacedAlignment { record: AlignmentRecord; @@ -115,7 +116,8 @@ export class MultiAlignmentViewCalculator { return {"query": fetcher.queryGenome, "records": records}; }); const oldRecordsArray = await Promise.all(recordsPromise); - recordsArray = this.refineRecordsArray(oldRecordsArray, visData); + const {records, gaps} = this.refineRecordsArray(oldRecordsArray, visData); + recordsArray = records; return recordsArray.reduce( (multiAlign, records) => ({...multiAlign, [records.query]: @@ -139,66 +141,94 @@ export class MultiAlignmentViewCalculator { ); } } - refineRecordsArray(recordsArray: RecordsObj[], visData: ViewExpansion): RecordsObj[] { - const {visRegion, visWidth} = visData; + refineRecordsArray(recordsArray: RecordsObj[], visData: ViewExpansion): {records: RecordsObj[], gaps: {}} { + const {visRegion, viewWindow, viewWindowRegion} = visData; + const oldNavContext = visRegion.getNavigationContext(); const minGapLength = MIN_GAP_LENGTH; // use a new array of objects to manipulate later, and // Combine all gaps from all alignments into a new array: const refineRecords = []; - const allGaps = []; + const allGapsObj = {}; for (const recordsObj of recordsArray) { // Calculate context coordinates of the records and gaps within. const placements = this._computeContextLocations(recordsObj.records, visData); const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); - console.log(placements); - console.log(primaryGaps); - refineRecords.push({"recordsObj": recordsObj, "placements": placements, "primaryGaps": primaryGaps}); - allGaps.push(...primaryGaps); + const primaryGapsObj = primaryGaps.reduce((resultObj, gap) => { + return {...resultObj, ...{[gap.contextBase]: gap.length}} + }, {}); + refineRecords.push({"recordsObj": recordsObj, "placements": placements, "primaryGapsObj": primaryGapsObj}); + for (const contextBase of Object.keys(primaryGapsObj)) { + if (contextBase in allGapsObj) { + allGapsObj[contextBase] = Math.max(allGapsObj[contextBase], primaryGapsObj[contextBase]); + } else { + allGapsObj[contextBase] = primaryGapsObj[contextBase]; + } + } } - // Combine allGaps to a allGapsSet with unique contextBase. For each of them, the longest length is chosen: - const allGapsSet = []; - const contextBaseSet = [...new Set(allGaps.map(i => i.contextBase))]; - contextBaseSet.sort((a, b) => a - b); - for (const gapContextBase of contextBaseSet) { - const lengths = allGaps.reduce( - (lengths, {contextBase, length}) => [...lengths, ...contextBase===gapContextBase ? [length] : []], [] - ); - const maxLength = Math.max(...lengths); - allGapsSet.push({contextBase: gapContextBase, length: maxLength}) + // Build a new primary navigation context using the gaps + const allPrimaryGaps = []; + for (const contextBase of Object.keys(allGapsObj)) { + allPrimaryGaps.push({"contextBase": Number(contextBase), "length": allGapsObj[contextBase]}); } + allPrimaryGaps.sort((a,b) => a.contextBase - b.contextBase); // ascending. + const navContextBuilder = new NavContextBuilder(oldNavContext); + navContextBuilder.setGaps(allPrimaryGaps); + const newNavContext = navContextBuilder.build(); + + // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context + const newVisRegion = convertOldVisRegion(visRegion); + const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); + const newPixelsPerBase = viewWindow.getLength() / newViewWindowRegion.getWidth(); + const newVisWidth = newVisRegion.getWidth() * newPixelsPerBase; + const newDrawModel = new LinearDrawingModel(newVisRegion, newVisWidth); + const newViewWindow = newDrawModel.baseSpanToXSpan(newViewWindowRegion.getContextCoordinates()); // For each records, insertion gaps to sequences if for contextBase only in allGapsSet: - console.log(allGapsSet); for (const records of refineRecords) { - console.log(records.primaryGaps); + const insertionContexts = []; + for (const contextBase of Object.keys(allGapsObj)) { + if (contextBase in records.primaryGapsObj) { + const lengthDiff = allGapsObj[contextBase] - records.primaryGapsObj[contextBase]; + if (lengthDiff > 0) { + insertionContexts.push({"contextBase": Number(contextBase), "length": lengthDiff}); + } + } else { + insertionContexts.push({"contextBase": Number(contextBase), "length": allGapsObj[contextBase]}); + } + } + insertionContexts.sort((a, b) => b.contextBase - a.contextBase); // sort descending... + + for (const insertPosition of insertionContexts) { + const gapString = '-'.repeat(insertPosition.length); + const insertBase = insertPosition.contextBase; + const thePlacement = records.placements.filter(placement => + placement.contextSpan.start < insertBase && placement.contextSpan.end > insertBase + )[0]; // There is only one placement + const relativePosition = thePlacement.visiblePart.relativeStart + + insertBase - thePlacement.contextSpan.start; + const targetSeq = thePlacement.record.targetSeq; + const querySeq = thePlacement.record.querySeq; + thePlacement.record.targetSeq = + targetSeq.slice(0, relativePosition) + gapString + targetSeq.slice(relativePosition); + thePlacement.record.querySeq = + querySeq.slice(0, relativePosition) + gapString + querySeq.slice(relativePosition); + } + records.recordsObj.records = records.placements.map(placement => placement.record); } - // const placementsObj = Object.keys(recordsObj).reduce( - // (placementsObj, queryGenome) => - // { - // const placements = this._computeContextLocations(recordsObj[queryGenome], visData); - // const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); - // const placementSubObj = {'placements': placements, 'primaryGaps': primaryGaps}; - // return ({...placementsObj, [queryGenome]: placementSubObj}); - // }, {} - // ); - - // const allGaps = Object.values(placementsObj).reduce((gaps, subObj) => { - // gaps.push(subObj.map(placement => placement.primaryGaps)); - // return gaps; - // }, []); - // const distinctCoordinates = [...new Set (Object.values(placementsObj).map(sub => sub['primaryGaps']))]; - + const newRecords = refineRecords.map(final => final.recordsObj); + return {records: newRecords, gaps: allPrimaryGaps}; - - return recordsArray; - - // Object.values(recordsObj).map( records => - // FEATURE_PLACER.placeFeatures(records, visRegion, visWidth).map(placement => - // AlignmentSegment.fromFeatureSegment(placement.visiblePart)) - // ); + function convertOldVisRegion(visRegion: DisplayedRegionModel) { + const [contextStart, contextEnd] = visRegion.getContextCoordinates(); + return new DisplayedRegionModel( + newNavContext, + navContextBuilder.convertOldCoordinates(contextStart), + navContextBuilder.convertOldCoordinates(contextEnd) + ); + } } alignFine(query: string, records: AlignmentRecord[], visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... @@ -207,7 +237,7 @@ export class MultiAlignmentViewCalculator { // const drawModel = new LinearDrawingModel(visRegion, visWidth); // const minGapLength = drawModel.xWidthToBases(MIN_GAP_DRAW_WIDTH); const minGapLength = MIN_GAP_LENGTH; - + // Calculate context coordinates of the records and gaps within. const placements = this._computeContextLocations(records, visData); const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); From f8b361e3f8de1ee6747830e70709627728657861 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Fri, 24 May 2019 17:03:09 -0500 Subject: [PATCH 09/15] mutipleAlignment in progress --- .../alignment/MultiAlignmentViewCalculator.ts | 149 +++++++++--------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index ac703b82..8cda5734 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import memoizeOne from 'memoize-one'; import { GuaranteeMap } from '../../model/GuaranteeMap'; -import { segmentSequence, makeBaseNumberLookup, countBases, SequenceSegment } from './AlignmentStringUtils'; +import { segmentSequence, makeBaseNumberLookup, countBases, SequenceSegment, GAP_CHAR } from './AlignmentStringUtils'; import { AlignmentRecord } from './AlignmentRecord'; import { AlignmentSegment } from './AlignmentSegment'; import { AlignmentFetcher } from './AlignmentFetcher'; @@ -36,12 +36,12 @@ export interface PlacedSequenceSegment extends SequenceSegment { interface QueryGenomePiece { queryFeature: Feature; - queryXSpan: OpenInterval; + queryXSpan: OpenInterval } export interface PlacedMergedAlignment extends QueryGenomePiece { segments: PlacedAlignment[]; - targetXSpan: OpenInterval; + targetXSpan: OpenInterval } export interface GapText { @@ -76,7 +76,12 @@ export interface MultiAlignment { interface RecordsObj { query: string; - records: AlignmentRecord[]; + records: AlignmentRecord[] +} + +interface RefinedObj { + newRecordsArray: RecordsObj[], + allGaps: Gap[] } const MAX_FINE_MODE_BASES_PER_PIXEL = 10; @@ -116,12 +121,14 @@ export class MultiAlignmentViewCalculator { return {"query": fetcher.queryGenome, "records": records}; }); const oldRecordsArray = await Promise.all(recordsPromise); - const {records, gaps} = this.refineRecordsArray(oldRecordsArray, visData); - recordsArray = records; + const {newRecordsArray, allGaps} = this.refineRecordsArray(oldRecordsArray, visData); + recordsArray = newRecordsArray; + + const primaryVisData = this.calculatePrimaryVis(allGaps, visData); return recordsArray.reduce( (multiAlign, records) => ({...multiAlign, [records.query]: - this.alignFine(records.query, records.records, visData) + this.alignFine(records.query, records.records, visData, primaryVisData) }), {} ); @@ -141,9 +148,40 @@ export class MultiAlignmentViewCalculator { ); } } - refineRecordsArray(recordsArray: RecordsObj[], visData: ViewExpansion): {records: RecordsObj[], gaps: {}} { + + calculatePrimaryVis(allGaps: Gap[], visData: ViewExpansion): ViewExpansion { const {visRegion, viewWindow, viewWindowRegion} = visData; const oldNavContext = visRegion.getNavigationContext(); + const navContextBuilder = new NavContextBuilder(oldNavContext); + navContextBuilder.setGaps(allGaps); + const newNavContext = navContextBuilder.build(); + + // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context + const newVisRegion = convertOldVisRegion(visRegion); + const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); + const newPixelsPerBase = viewWindow.getLength() / newViewWindowRegion.getWidth(); + const newVisWidth = newVisRegion.getWidth() * newPixelsPerBase; + const newDrawModel = new LinearDrawingModel(newVisRegion, newVisWidth); + const newViewWindow = newDrawModel.baseSpanToXSpan(newViewWindowRegion.getContextCoordinates()); + + return { + visRegion: newVisRegion, + visWidth: newVisWidth, + viewWindowRegion: newViewWindowRegion, + viewWindow: newViewWindow, + } + + function convertOldVisRegion(visRegion: DisplayedRegionModel) { + const [contextStart, contextEnd] = visRegion.getContextCoordinates(); + return new DisplayedRegionModel( + newNavContext, + navContextBuilder.convertOldCoordinates(contextStart), + navContextBuilder.convertOldCoordinates(contextEnd) + ); + } + } + // return a new recordObj array with gaps inserted, and allGap contextBase. + refineRecordsArray(recordsArray: RecordsObj[], visData: ViewExpansion): RefinedObj { const minGapLength = MIN_GAP_LENGTH; // use a new array of objects to manipulate later, and @@ -173,17 +211,6 @@ export class MultiAlignmentViewCalculator { allPrimaryGaps.push({"contextBase": Number(contextBase), "length": allGapsObj[contextBase]}); } allPrimaryGaps.sort((a,b) => a.contextBase - b.contextBase); // ascending. - const navContextBuilder = new NavContextBuilder(oldNavContext); - navContextBuilder.setGaps(allPrimaryGaps); - const newNavContext = navContextBuilder.build(); - - // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context - const newVisRegion = convertOldVisRegion(visRegion); - const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); - const newPixelsPerBase = viewWindow.getLength() / newViewWindowRegion.getWidth(); - const newVisWidth = newVisRegion.getWidth() * newPixelsPerBase; - const newDrawModel = new LinearDrawingModel(newVisRegion, newVisWidth); - const newViewWindow = newDrawModel.baseSpanToXSpan(newViewWindowRegion.getContextCoordinates()); // For each records, insertion gaps to sequences if for contextBase only in allGapsSet: for (const records of refineRecords) { @@ -199,15 +226,15 @@ export class MultiAlignmentViewCalculator { } } insertionContexts.sort((a, b) => b.contextBase - a.contextBase); // sort descending... - for (const insertPosition of insertionContexts) { const gapString = '-'.repeat(insertPosition.length); const insertBase = insertPosition.contextBase; const thePlacement = records.placements.filter(placement => placement.contextSpan.start < insertBase && placement.contextSpan.end > insertBase )[0]; // There is only one placement - const relativePosition = thePlacement.visiblePart.relativeStart - + insertBase - thePlacement.contextSpan.start; + const visibleTargetSeq = thePlacement.visiblePart.getTargetSequence(); + const insertIndex = indexLookup(visibleTargetSeq, insertBase - thePlacement.contextSpan.start); + const relativePosition = thePlacement.visiblePart.sequenceInterval.start + insertIndex; const targetSeq = thePlacement.record.targetSeq; const querySeq = thePlacement.record.querySeq; thePlacement.record.targetSeq = @@ -219,61 +246,51 @@ export class MultiAlignmentViewCalculator { records.recordsObj.records = records.placements.map(placement => placement.record); } const newRecords = refineRecords.map(final => final.recordsObj); - return {records: newRecords, gaps: allPrimaryGaps}; - - function convertOldVisRegion(visRegion: DisplayedRegionModel) { - const [contextStart, contextEnd] = visRegion.getContextCoordinates(); - return new DisplayedRegionModel( - newNavContext, - navContextBuilder.convertOldCoordinates(contextStart), - navContextBuilder.convertOldCoordinates(contextEnd) - ); + return {newRecordsArray: newRecords, allGaps: allPrimaryGaps}; + + function indexLookup (sequence: string, base: number): number { + let index = 0; + for(const char of sequence) { + index++; + if (char !== GAP_CHAR) { + base--; + } + if (base === 0){ break; } + } + return index; } } - alignFine(query: string, records: AlignmentRecord[], visData: ViewExpansion): Alignment { + alignFine(query: string, records: AlignmentRecord[], OldvisData: ViewExpansion, visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... - const {visRegion, viewWindow, viewWindowRegion} = visData; - const oldNavContext = visRegion.getNavigationContext(); - // const drawModel = new LinearDrawingModel(visRegion, visWidth); + const {visRegion, visWidth} = visData; + const drawModel = new LinearDrawingModel(visRegion, visWidth); // const minGapLength = drawModel.xWidthToBases(MIN_GAP_DRAW_WIDTH); const minGapLength = MIN_GAP_LENGTH; // Calculate context coordinates of the records and gaps within. - const placements = this._computeContextLocations(records, visData); - const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); - - // Build a new primary navigation context using the gaps - const navContextBuilder = new NavContextBuilder(oldNavContext); - navContextBuilder.setGaps(primaryGaps); - const newNavContext = navContextBuilder.build(); - - // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context - const newVisRegion = convertOldVisRegion(visRegion); - const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); - const newPixelsPerBase = viewWindow.getLength() / newViewWindowRegion.getWidth(); - const newVisWidth = newVisRegion.getWidth() * newPixelsPerBase; - const newDrawModel = new LinearDrawingModel(newVisRegion, newVisWidth); - const newViewWindow = newDrawModel.baseSpanToXSpan(newViewWindowRegion.getContextCoordinates()); - + const navContext = visRegion.getNavigationContext(); + const navContextBuilder = new NavContextBuilder(navContext); + const placements = this._computeContextLocations(records, OldvisData); // With the draw model, we can set x spans for each placed alignment for (const placement of placements) { const oldContextSpan = placement.contextSpan; + console.log(oldContextSpan) const visiblePart = placement.visiblePart; const newContextSpan = new OpenInterval( navContextBuilder.convertOldCoordinates(oldContextSpan.start), navContextBuilder.convertOldCoordinates(oldContextSpan.end) ); - const xSpan = newDrawModel.baseSpanToXSpan(newContextSpan); + console.log(newContextSpan); + const xSpan = drawModel.baseSpanToXSpan(newContextSpan); const targetSeq = visiblePart.getTargetSequence(); const querySeq = visiblePart.getQuerySequence(); - + placement.contextSpan = newContextSpan; placement.targetXSpan = xSpan; placement.queryXSpan = xSpan; - placement.targetSegments = this._placeSequenceSegments(targetSeq, minGapLength, xSpan.start, newDrawModel); - placement.querySegments = this._placeSequenceSegments(querySeq, minGapLength, xSpan.start, newDrawModel); + placement.targetSegments = this._placeSequenceSegments(targetSeq, minGapLength, xSpan.start, drawModel); + placement.querySegments = this._placeSequenceSegments(querySeq, minGapLength, xSpan.start, drawModel); } - const drawGapTexts = []; const targetIntervalPlacer = new IntervalPlacer(MARGIN); const queryIntervalPlacer = new IntervalPlacer(MARGIN); @@ -351,32 +368,18 @@ export class MultiAlignmentViewCalculator { } // Finally, using the x coordinates, construct the query nav context const queryPieces = this._getQueryPieces(placements); - const queryRegion = this._makeQueryGenomeRegion(queryPieces, newVisWidth, newDrawModel); + const queryRegion = this._makeQueryGenomeRegion(queryPieces, visWidth, drawModel); return { isFineMode: true, - primaryVisData: { - visRegion: newVisRegion, - visWidth: newVisWidth, - viewWindowRegion: newViewWindowRegion, - viewWindow: newViewWindow, - }, + primaryVisData: visData, queryRegion, drawData: placements, drawGapText: drawGapTexts, primaryGenome: this.primaryGenome, queryGenome: query, - basesPerPixel: newDrawModel.xWidthToBases(1) + basesPerPixel: drawModel.xWidthToBases(1) }; - - function convertOldVisRegion(visRegion: DisplayedRegionModel) { - const [contextStart, contextEnd] = visRegion.getContextCoordinates(); - return new DisplayedRegionModel( - newNavContext, - navContextBuilder.convertOldCoordinates(contextStart), - navContextBuilder.convertOldCoordinates(contextEnd) - ); - } } /** From 353c054f19e29260bd344ba2f9fabd55a4f1036a Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 29 May 2019 15:47:46 -0500 Subject: [PATCH 10/15] finalize multiAlign feature --- .../alignment/MultiAlignmentViewCalculator.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 8cda5734..04a5d6af 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -155,7 +155,8 @@ export class MultiAlignmentViewCalculator { const navContextBuilder = new NavContextBuilder(oldNavContext); navContextBuilder.setGaps(allGaps); const newNavContext = navContextBuilder.build(); - + console.log(oldNavContext); + console.log(newNavContext); // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context const newVisRegion = convertOldVisRegion(visRegion); const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); @@ -260,7 +261,7 @@ export class MultiAlignmentViewCalculator { return index; } } - alignFine(query: string, records: AlignmentRecord[], OldvisData: ViewExpansion, visData: ViewExpansion): Alignment { + alignFine(query: string, records: AlignmentRecord[], oldVisData: ViewExpansion, visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... const {visRegion, visWidth} = visData; const drawModel = new LinearDrawingModel(visRegion, visWidth); @@ -268,19 +269,19 @@ export class MultiAlignmentViewCalculator { const minGapLength = MIN_GAP_LENGTH; // Calculate context coordinates of the records and gaps within. - const navContext = visRegion.getNavigationContext(); + const navContext = oldVisData.visRegion.getNavigationContext(); + const placements = this._computeContextLocations(records, oldVisData); + const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); const navContextBuilder = new NavContextBuilder(navContext); - const placements = this._computeContextLocations(records, OldvisData); + navContextBuilder.setGaps(primaryGaps); // With the draw model, we can set x spans for each placed alignment for (const placement of placements) { const oldContextSpan = placement.contextSpan; - console.log(oldContextSpan) const visiblePart = placement.visiblePart; const newContextSpan = new OpenInterval( navContextBuilder.convertOldCoordinates(oldContextSpan.start), navContextBuilder.convertOldCoordinates(oldContextSpan.end) ); - console.log(newContextSpan); const xSpan = drawModel.baseSpanToXSpan(newContextSpan); const targetSeq = visiblePart.getTargetSequence(); const querySeq = visiblePart.getQuerySequence(); @@ -369,7 +370,7 @@ export class MultiAlignmentViewCalculator { // Finally, using the x coordinates, construct the query nav context const queryPieces = this._getQueryPieces(placements); const queryRegion = this._makeQueryGenomeRegion(queryPieces, visWidth, drawModel); - + console.log(placements); return { isFineMode: true, primaryVisData: visData, From 855b8faf16c24e9a72cc8b981fd7d2ce2cdcfb3d Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 29 May 2019 15:57:45 -0500 Subject: [PATCH 11/15] delete debug console log --- frontend/src/model/alignment/MultiAlignmentViewCalculator.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 04a5d6af..936440ee 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -155,8 +155,6 @@ export class MultiAlignmentViewCalculator { const navContextBuilder = new NavContextBuilder(oldNavContext); navContextBuilder.setGaps(allGaps); const newNavContext = navContextBuilder.build(); - console.log(oldNavContext); - console.log(newNavContext); // Calculate new DisplayedRegionModel and LinearDrawingModel from the new nav context const newVisRegion = convertOldVisRegion(visRegion); const newViewWindowRegion = convertOldVisRegion(viewWindowRegion); @@ -370,7 +368,6 @@ export class MultiAlignmentViewCalculator { // Finally, using the x coordinates, construct the query nav context const queryPieces = this._getQueryPieces(placements); const queryRegion = this._makeQueryGenomeRegion(queryPieces, visWidth, drawModel); - console.log(placements); return { isFineMode: true, primaryVisData: visData, From 80123e898538b7c7cf1f66b81788415ab7b890f7 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 29 May 2019 16:21:46 -0500 Subject: [PATCH 12/15] remove things I don't need --- .../src/model/alignment/MultiAlignmentViewCalculator.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 936440ee..22767a8f 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import memoizeOne from 'memoize-one'; -import { GuaranteeMap } from '../../model/GuaranteeMap'; +// import { GuaranteeMap } from '../../model/GuaranteeMap'; import { segmentSequence, makeBaseNumberLookup, countBases, SequenceSegment, GAP_CHAR } from './AlignmentStringUtils'; import { AlignmentRecord } from './AlignmentRecord'; @@ -18,7 +18,7 @@ import { ViewExpansion } from '../RegionExpander'; import { FeaturePlacer } from '../FeaturePlacer'; import DisplayedRegionModel from '../DisplayedRegionModel'; import { niceBpCount } from '../../util'; -import { object } from 'prop-types'; +// import { object } from 'prop-types'; export interface PlacedAlignment { record: AlignmentRecord; @@ -95,12 +95,10 @@ const FEATURE_PLACER = new FeaturePlacer(); export class MultiAlignmentViewCalculator { private primaryGenome: string; private _alignmentFetchers: AlignmentFetcher[]; - private _viewBeingFetched: ViewExpansion; constructor(primaryGenome: string, queryGenomes: string[]) { this._alignmentFetchers = queryGenomes.map(queryGenome => new AlignmentFetcher(primaryGenome, queryGenome)); - this._viewBeingFetched = null; this.primaryGenome = primaryGenome; this.multiAlign = memoizeOne(this.multiAlign); } @@ -110,7 +108,6 @@ export class MultiAlignmentViewCalculator { } async multiAlign(visData: ViewExpansion): Promise { const {visRegion, visWidth, viewWindowRegion} = visData; - this._viewBeingFetched = visData; const drawModel = new LinearDrawingModel(visRegion, visWidth); const isFineMode = drawModel.xWidthToBases(1) < MAX_FINE_MODE_BASES_PER_PIXEL; From f8972231c31c080ada396116e911374f13dfc0a2 Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 29 May 2019 16:33:04 -0500 Subject: [PATCH 13/15] add some simple comments --- frontend/src/model/alignment/MultiAlignmentViewCalculator.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 22767a8f..8b507dbb 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -147,6 +147,7 @@ export class MultiAlignmentViewCalculator { } calculatePrimaryVis(allGaps: Gap[], visData: ViewExpansion): ViewExpansion { + // Calculate primary visData that have all the primary gaps from different alignemnts insertions. const {visRegion, viewWindow, viewWindowRegion} = visData; const oldNavContext = visRegion.getNavigationContext(); const navContextBuilder = new NavContextBuilder(oldNavContext); @@ -259,17 +260,20 @@ export class MultiAlignmentViewCalculator { alignFine(query: string, records: AlignmentRecord[], oldVisData: ViewExpansion, visData: ViewExpansion): Alignment { // There's a lot of steps, so bear with me... const {visRegion, visWidth} = visData; + // drawModel is derived from visData: const drawModel = new LinearDrawingModel(visRegion, visWidth); // const minGapLength = drawModel.xWidthToBases(MIN_GAP_DRAW_WIDTH); const minGapLength = MIN_GAP_LENGTH; // Calculate context coordinates of the records and gaps within. + // calculate navContext and placements using oldVisData so small gaps won't seperate different features: const navContext = oldVisData.visRegion.getNavigationContext(); const placements = this._computeContextLocations(records, oldVisData); const primaryGaps = this._getPrimaryGenomeGaps(placements, minGapLength); const navContextBuilder = new NavContextBuilder(navContext); navContextBuilder.setGaps(primaryGaps); // With the draw model, we can set x spans for each placed alignment + // Adjust contextSpan and xSpan in placements using visData: for (const placement of placements) { const oldContextSpan = placement.contextSpan; const visiblePart = placement.visiblePart; From 879cbfc843f0214ba84f8c89da1fa3a3bb745e6e Mon Sep 17 00:00:00 2001 From: xzhuo Date: Mon, 1 Jul 2019 17:08:03 -0500 Subject: [PATCH 14/15] skip seq string manipulation for pairwise align --- .../alignment/MultiAlignmentViewCalculator.ts | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 8b507dbb..8681ada4 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -210,37 +210,39 @@ export class MultiAlignmentViewCalculator { allPrimaryGaps.sort((a,b) => a.contextBase - b.contextBase); // ascending. // For each records, insertion gaps to sequences if for contextBase only in allGapsSet: - for (const records of refineRecords) { - const insertionContexts = []; - for (const contextBase of Object.keys(allGapsObj)) { - if (contextBase in records.primaryGapsObj) { - const lengthDiff = allGapsObj[contextBase] - records.primaryGapsObj[contextBase]; - if (lengthDiff > 0) { - insertionContexts.push({"contextBase": Number(contextBase), "length": lengthDiff}); + if (refineRecords.length > 1) { // skip this part for pairwise alignment. + for (const records of refineRecords) { + const insertionContexts = []; + for (const contextBase of Object.keys(allGapsObj)) { + if (contextBase in records.primaryGapsObj) { + const lengthDiff = allGapsObj[contextBase] - records.primaryGapsObj[contextBase]; + if (lengthDiff > 0) { + insertionContexts.push({"contextBase": Number(contextBase), "length": lengthDiff}); + } + } else { + insertionContexts.push({"contextBase": Number(contextBase), "length": allGapsObj[contextBase]}); } - } else { - insertionContexts.push({"contextBase": Number(contextBase), "length": allGapsObj[contextBase]}); } - } - insertionContexts.sort((a, b) => b.contextBase - a.contextBase); // sort descending... - for (const insertPosition of insertionContexts) { - const gapString = '-'.repeat(insertPosition.length); - const insertBase = insertPosition.contextBase; - const thePlacement = records.placements.filter(placement => - placement.contextSpan.start < insertBase && placement.contextSpan.end > insertBase - )[0]; // There is only one placement - const visibleTargetSeq = thePlacement.visiblePart.getTargetSequence(); - const insertIndex = indexLookup(visibleTargetSeq, insertBase - thePlacement.contextSpan.start); - const relativePosition = thePlacement.visiblePart.sequenceInterval.start + insertIndex; - const targetSeq = thePlacement.record.targetSeq; - const querySeq = thePlacement.record.querySeq; - thePlacement.record.targetSeq = - targetSeq.slice(0, relativePosition) + gapString + targetSeq.slice(relativePosition); - thePlacement.record.querySeq = - querySeq.slice(0, relativePosition) + gapString + querySeq.slice(relativePosition); - } + insertionContexts.sort((a, b) => b.contextBase - a.contextBase); // sort descending... + for (const insertPosition of insertionContexts) { + const gapString = '-'.repeat(insertPosition.length); + const insertBase = insertPosition.contextBase; + const thePlacement = records.placements.filter(placement => + placement.contextSpan.start < insertBase && placement.contextSpan.end > insertBase + )[0]; // There is only one placement + const visibleTargetSeq = thePlacement.visiblePart.getTargetSequence(); + const insertIndex = indexLookup(visibleTargetSeq, insertBase - thePlacement.contextSpan.start); + const relativePosition = thePlacement.visiblePart.sequenceInterval.start + insertIndex; + const targetSeq = thePlacement.record.targetSeq; + const querySeq = thePlacement.record.querySeq; + thePlacement.record.targetSeq = + targetSeq.slice(0, relativePosition) + gapString + targetSeq.slice(relativePosition); + thePlacement.record.querySeq = + querySeq.slice(0, relativePosition) + gapString + querySeq.slice(relativePosition); + } - records.recordsObj.records = records.placements.map(placement => placement.record); + records.recordsObj.records = records.placements.map(placement => placement.record); + } } const newRecords = refineRecords.map(final => final.recordsObj); return {newRecordsArray: newRecords, allGaps: allPrimaryGaps}; From b02fa139c2b32ed46c945c92fd73442a05abbc6b Mon Sep 17 00:00:00 2001 From: xzhuo Date: Wed, 3 Jul 2019 10:05:40 -0500 Subject: [PATCH 15/15] fix a bug related to context in gap region --- .../alignment/MultiAlignmentViewCalculator.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts index 8681ada4..3902058d 100644 --- a/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts +++ b/frontend/src/model/alignment/MultiAlignmentViewCalculator.ts @@ -229,16 +229,18 @@ export class MultiAlignmentViewCalculator { const insertBase = insertPosition.contextBase; const thePlacement = records.placements.filter(placement => placement.contextSpan.start < insertBase && placement.contextSpan.end > insertBase - )[0]; // There is only one placement - const visibleTargetSeq = thePlacement.visiblePart.getTargetSequence(); - const insertIndex = indexLookup(visibleTargetSeq, insertBase - thePlacement.contextSpan.start); - const relativePosition = thePlacement.visiblePart.sequenceInterval.start + insertIndex; - const targetSeq = thePlacement.record.targetSeq; - const querySeq = thePlacement.record.querySeq; - thePlacement.record.targetSeq = - targetSeq.slice(0, relativePosition) + gapString + targetSeq.slice(relativePosition); - thePlacement.record.querySeq = - querySeq.slice(0, relativePosition) + gapString + querySeq.slice(relativePosition); + )[0]; // There could only be 0 or 1 placement pass the filter. + if (thePlacement) { + const visibleTargetSeq = thePlacement.visiblePart.getTargetSequence(); + const insertIndex = indexLookup(visibleTargetSeq, insertBase - thePlacement.contextSpan.start); + const relativePosition = thePlacement.visiblePart.sequenceInterval.start + insertIndex; + const targetSeq = thePlacement.record.targetSeq; + const querySeq = thePlacement.record.querySeq; + thePlacement.record.targetSeq = + targetSeq.slice(0, relativePosition) + gapString + targetSeq.slice(relativePosition); + thePlacement.record.querySeq = + querySeq.slice(0, relativePosition) + gapString + querySeq.slice(relativePosition); + } } records.recordsObj.records = records.placements.map(placement => placement.record);