From 259abd381948b718a31cd348d2cc3ce85a97c3a8 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 21 Jun 2021 16:33:55 -0400 Subject: [PATCH 1/4] feat: rotated 2D matrix --- src/core/gosling-to-higlass.ts | 10 +- src/core/gosling.schema.guards.ts | 35 +++++- src/core/gosling.schema.ts | 2 +- src/core/higlass.schema.ts | 2 + src/editor/example/index.ts | 8 +- src/editor/example/matrix-hffc6.ts | 80 +++++++++++- src/gosling-track/data-abstraction.ts | 45 +++++++ src/gosling-track/gosling-track.ts | 170 +++++++++++++++++++++++++- 8 files changed, 341 insertions(+), 11 deletions(-) diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 6011bece1..30ed0c6cc 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -7,7 +7,7 @@ import { BoundingBox, RelativePosition } from './utils/bounding-box'; import { resolveSuperposedTracks } from './utils/overlay'; import { getGenomicChannelKeyFromTrack, getGenomicChannelFromTrack } from './utils/validate'; import { viridisColorMap } from './utils/colors'; -import { IsDataDeep, IsChannelDeep, IsDataDeepTileset } from './gosling.schema.guards'; +import { IsDataDeep, IsChannelDeep, IsDataDeepTileset, Is2DTrack } from './gosling.schema.guards'; import { DEFAULT_SUBTITLE_HEIGHT, DEFAULT_TITLE_HEIGHT } from './layout/defaults'; import { getTheme, Theme } from './utils/theme'; @@ -96,7 +96,7 @@ export function goslingToHiGlass( }; } - const isMatrix = gosTrack.data?.type === 'matrix'; + const isMatrix = Is2DTrack(gosTrack); if (isMatrix) { // Use HiGlass' heatmap track for matrix data hgTrack.type = 'heatmap'; @@ -109,6 +109,12 @@ export function goslingToHiGlass( hgTrack.options.colorbarPosition = (firstResolvedSpec.color as any)?.legend ? 'topRight' : 'hidden'; } + // TODO: Experimental + if (gosTrack.data?.type === 'matrix') { + hgTrack.filetype = 'cooler'; + // hgTrack.type = 'linear-heatmap'; + } + if (gosTrack.overlayOnPreviousTrack) { hgModel .setViewOrientation(gosTrack.orientation) // TODO: Orientation should be assigned to 'individual' views diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index df75a45b0..85a47fe1d 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -32,7 +32,8 @@ import { OverlaidTracks, StackedTracks, BAMData, - Range + Range, + MatrixData } from './gosling.schema'; import { SUPPORTED_CHANNELS } from './mark'; import { isArray } from 'lodash'; @@ -123,7 +124,35 @@ export function IsOverlaidTrack(track: Partial): track is OverlaidTrack { * TODO: This should be more correctly determined, but we currently only support 2D tracks for matrix datasets. */ export function Is2DTrack(track: Track) { - return IsSingleTrack(track) && track.data?.type === 'matrix'; + if (!IsSingleTrack(track)) { + return false; + } + const xChannel = track.x; + const yChannel = track.y; + return ( + track.data?.type === 'matrix' && + IsChannelDeep(xChannel) && + IsChannelDeep(yChannel) && + xChannel.type === 'genomic' && + yChannel.type === 'genomic' + ); +} + +// TODO: To support overlaid tracks in rotated matrix tracks, this function should be updated as this only accepts single tracks. +export function IsRotatedMatrixTrack(track: Track) { + if (!IsSingleTrack(track)) { + return false; + } + const xChannel = track.x; + const yChannel = track.y; + return ( + track.data?.type === 'matrix' && + IsChannelDeep(xChannel) && + xChannel.type === 'genomic' && + ( + !IsChannelDeep(yChannel) || yChannel.type !== 'genomic' + ) + ); } export function IsChannelValue( @@ -140,7 +169,7 @@ export function IsChannelBind( export function IsDataDeepTileset( _: DataDeep | undefined -): _ is BEDDBData | VectorData | MultivecData | BIGWIGData | BAMData { +): _ is BEDDBData | VectorData | MultivecData | BIGWIGData | BAMData | MatrixData { return ( _ !== undefined && (_.type === 'vector' || diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 5eadc42c3..5d09f1902 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -427,12 +427,12 @@ export interface BAMData { url: string; } -/* ----------------------------- DATA TRANSFORM ----------------------------- */ export interface MatrixData { type: 'matrix'; url: string; } +/* ----------------------------- DATA TRANSFORM ----------------------------- */ export type DataTransform = | FilterTransform | StrConcatTransform diff --git a/src/core/higlass.schema.ts b/src/core/higlass.schema.ts index 50434db94..1f8be7503 100644 --- a/src/core/higlass.schema.ts +++ b/src/core/higlass.schema.ts @@ -92,6 +92,7 @@ export interface EnumTrack { server?: string; tilesetUid?: string; chromInfoPath?: string; + filetype?: string; // Added manually data?: Data; fromViewUid?: null | string; width?: number; @@ -183,6 +184,7 @@ export type EnumTrackType = | 'horizontal-chromosome-labels' | 'horizontal-divergent-bar' | 'horizontal-gene-annotations' + | 'linear-heatmap' | 'horizontal-heatmap' | 'horizontal-line' | 'horizontal-multivec' diff --git a/src/editor/example/index.ts b/src/editor/example/index.ts index 1e956b479..776a662dc 100644 --- a/src/editor/example/index.ts +++ b/src/editor/example/index.ts @@ -2,7 +2,7 @@ import { GoslingSpec } from '../../core/gosling.schema'; import { EX_SPEC_LAYOUT_AND_ARRANGEMENT_1, EX_SPEC_LAYOUT_AND_ARRANGEMENT_2 } from './layout-and-arrangement'; import { EX_SPEC_DARK_THEME, EX_SPEC_VISUAL_ENCODING, EX_SPEC_VISUAL_ENCODING_CIRCULAR } from './visual-encoding'; import { EX_SPEC_CANCER_VARIANT_PROTOTYPE } from './cancer-variant'; -import { EX_SPEC_MATRIX_HFFC6 } from './matrix-hffc6'; +import { EX_SPEC_MATRIX_HFFC6, EX_SPEC_ROTATED_MATRIX } from './matrix-hffc6'; import { EX_SPEC_LINKING } from './visual-linking'; import { EX_SPEC_BASIC_SEMANTIC_ZOOM } from './basic-semantic-zoom'; import { EX_SPEC_MARK_DISPLACEMENT } from './mark-displacement'; @@ -101,6 +101,12 @@ export const examples: ReadonlyArray<{ id: 'MATRIX_HFFC6', spec: EX_SPEC_MATRIX_HFFC6 }, + { + name: 'Rotated Matrix (Hi-C)', + id: 'ROTATED_MATRIX', + spec: EX_SPEC_ROTATED_MATRIX, + forceShow: true + }, { name: 'Circos', id: 'CIRCOS', diff --git a/src/editor/example/matrix-hffc6.ts b/src/editor/example/matrix-hffc6.ts index 45604787b..d25e79e4c 100644 --- a/src/editor/example/matrix-hffc6.ts +++ b/src/editor/example/matrix-hffc6.ts @@ -1,4 +1,4 @@ -import { GoslingSpec } from '../../core/gosling.schema'; +import { GoslingSpec, Track } from '../../core/gosling.schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const EX_SPEC_MATRIX_HFFC6: GoslingSpec = { @@ -486,3 +486,81 @@ export const EX_SPEC_MATRIX_HFFC6: GoslingSpec = { ], style: { outlineWidth: 0 } }; + +export const EX_SPEC_ROTATED_MATRIX: GoslingSpec = { + title: 'Rotated Matrix Visualization', + subtitle: 'Hi-C for HFFc6 Cells', + arrangement: 'vertical', + xDomain: { chromosome: '7', interval: [77700000, 81000000] }, + spacing: 0, + style: { outlineWidth: 1 }, + views: [ + { + tracks: [ + { + title: 'HFFc6 Hi-C', + data: { + url: GOSLING_PUBLIC_DATA.matrixHiC, + type: 'matrix' + }, + mark: 'rect', + x: { field: 'position1', type: 'genomic' }, + color: { field: 'value', type: 'quantitative', range: 'warm' }, + width: 600, + height: 100 + }, + { + alignment: 'overlay', + tracks: [ + { + data: { + url: GOSLING_PUBLIC_DATA.geneAnnotation, + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ] + }, + dataTransform: [{ type: 'filter', field: 'strand', oneOf: ['+'] }], + mark: 'triangleRight', + x: { field: 'start', type: 'genomic' }, + size: { value: 13 }, + stroke: { value: 'white' }, + strokeWidth: { value: 1 }, + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + color: { value: '#CB7AA7' } + }, + { + data: { + url: GOSLING_PUBLIC_DATA.geneAnnotation, + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ] + }, + dataTransform: [{ type: 'filter', field: 'strand', oneOf: ['-'] }], + mark: 'triangleLeft', + x: { field: 'start', type: 'genomic' }, + stroke: { value: 'white' }, + strokeWidth: { value: 1 }, + size: { value: 13 }, + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + color: { value: '#029F73' } + } + ], + width: 570, + height: 40 + } + ].slice(0, 1) as Track[] + } + ] +}; diff --git a/src/gosling-track/data-abstraction.ts b/src/gosling-track/data-abstraction.ts index 180b68be8..082597f26 100644 --- a/src/gosling-track/data-abstraction.ts +++ b/src/gosling-track/data-abstraction.ts @@ -11,7 +11,9 @@ export function getTabularData( raw?: Datum[]; shape?: [number, number]; tileX: number; + tileY?: number; // Used for 2D tracks tileWidth: number; + tileHeight?: number; // Used for 2D tracks tileSize: number; } ) { @@ -162,6 +164,49 @@ export function getTabularData( } }); }); + } else if (spec.data.type === 'matrix') { + if (!data.dense) { + // we did not get sufficient data. + return; + } + + const binSize = Math.sqrt(data.dense.length); + + if(binSize !== 256) { + console.warn('Bin size of the matrix dataset is not 256'); + } + + const numericValues = data.dense; + + // data.dense.forEach((value, i) => { + // const + // }); + + // // calculate the tile's position in bins + // const tileXStartBin = Math.floor(data.tileX / data.tileRes); + // const tileXEndBin = Math.floor((data.tileX + data.tileWidth) / data.tileRes); + // const tileYStartBin = Math.floor(data.tileY / data.tileRes); + // const tileYEndBin = Math.floor((data.tileY + data.tileHeight) / data.tileRes); + + // // calculate which part of this tile is present in the current window + // let tileSliceXStart = Math.max(leftXBin, tileXStartBin) - tileXStartBin; + // let tileSliceYStart = Math.max(leftYBin, tileYStartBin) - tileYStartBin; + // const tileSliceXEnd = + // Math.min(leftXBin + binWidth, tileXEndBin) - tileXStartBin; + // const tileSliceYEnd = + // Math.min(leftYBin + binHeight, tileYEndBin) - tileYStartBin; + + // // where in the output array will the portion of this tile which is in the visible window be placed? + // const tileXOffset = Math.max(tileXStartBin - leftXBin, 0); + // const tileYOffset = Math.max(tileYStartBin - leftYBin, 0); + // const tileSliceWidth = tileSliceXEnd - tileSliceXStart; + // const tileSliceHeight = tileSliceYEnd - tileSliceYStart; + + // // the region is outside of this tile + // if (tileSliceWidth < 0 || tileSliceHeight < 0) { + // return; + // } + } else if (spec.data.type === 'beddb') { if (!data.raw) { // we did not get sufficient data. diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 89cc4735b..823218b95 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -27,6 +27,7 @@ import { getTabularData } from './data-abstraction'; import { BAMDataFetcher } from '../data-fetcher/bam'; import { spawn, Worker } from 'threads'; import { getRelativeGenomicPosition } from '../core/utils/assembly'; +import { IsChannelDeep, IsRotatedMatrixTrack } from '../core/gosling.schema.guards'; // Set `true` to print in what order each function is called export const PRINT_RENDERING_CYCLE = false; @@ -393,7 +394,68 @@ function GoslingTrack(HGC: any, ...args: any[]): any { return this.visibleAndFetchedIds().map((x: any) => this.fetchedTiles[x]); } + calculateZoomLevel() { + if(IsRotatedMatrixTrack(this.originalSpec)) { + // For the rotated matrix, we need special treatment for calculating zoom level. + return this.calculateZoomLevelForRotatedMatrix(); + } else { + return super.calculateZoomLevel(); + } + } + + calculateZoomLevelForRotatedMatrix() { + let zoomLevel = null; + + if (this.tilesetInfo.resolutions) { + const zoomIndexX = HGC.services.tileProxy.calculateZoomLevelFromResolutions( + this.tilesetInfo.resolutions, + this._xScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0] + ); + + const zoomIndexY = HGC.services.tileProxy.calculateZoomLevelFromResolutions( + this.tilesetInfo.resolutions, + this._xScale, + this.tilesetInfo.min_pos[1], + this.tilesetInfo.max_pos[1] + ); + + zoomLevel = Math.min(zoomIndexX, zoomIndexY); + } else { + const xZoomLevel = HGC.services.tileProxy.calculateZoomLevel( + this._xScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0] + ); + + const yZoomLevel = HGC.services.tileProxy.calculateZoomLevel( + this._xScale, + this.tilesetInfo.min_pos[1], + this.tilesetInfo.max_pos[1] + ); + + zoomLevel = Math.max(xZoomLevel, yZoomLevel); + zoomLevel = Math.min(zoomLevel, this.maxZoom); + } + + if (this.options && this.options.maxZoom) { + if (this.options.maxZoom >= 0) { + zoomLevel = Math.min(this.options.maxZoom, zoomLevel); + } else { + console.error('Invalid maxZoom on track:', this); + } + } + return zoomLevel; + } + calculateVisibleTiles() { + if(IsRotatedMatrixTrack(this.originalSpec)) { + // We need special treatment for calculating visible tiles. + this.calculateVisibleTilesForRotatedMatrix(); + return; + } + if (!usePrereleaseRendering(this.originalSpec)) { // This is the common way of calculating visible tiles. super.calculateVisibleTiles(); @@ -423,6 +485,98 @@ function GoslingTrack(HGC: any, ...args: any[]): any { this.setVisibleTiles(tiles); } + calculateVisibleTilesForRotatedMatrix() { + // if we don't know anything about this dataset, no point + // in trying to get tiles + if (!this.tilesetInfo) { + return; + } + + this.zoomLevel = this.calculateZoomLevel(); + + // this.zoomLevel = 0; + const expandedXScale = this._xScale.copy(); + + // we need to expand the domain of the X-scale because we are showing diagonal tiles. + // to make sure the view is covered up the entire height, we need to expand by + // viewHeight * sqrt(2) + // on each side + expandedXScale.domain([ + this._xScale.invert(this._xScale.range()[0] - this.dimensions[1] * Math.sqrt(2)), + this._xScale.invert(this._xScale.range()[1] + this.dimensions[1] * Math.sqrt(2)) + ]); + + if (this.tilesetInfo.resolutions) { + const sortedResolutions = this.tilesetInfo.resolutions + .map((x: any) => +x) + .sort((a: any, b: any) => b - a); + + this.xTiles = HGC.services.tileProxy.calculateTilesFromResolution( + sortedResolutions[this.zoomLevel], + expandedXScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0] + ); + this.yTiles = HGC.services.tileProxy.calculateTilesFromResolution( + sortedResolutions[this.zoomLevel], + expandedXScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0] + ); + } else { + this.xTiles = HGC.services.tileProxy.calculateTiles( + this.zoomLevel, + expandedXScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0], + this.tilesetInfo.max_zoom, + this.tilesetInfo.max_width + ); + + this.yTiles = HGC.services.tileProxy.calculateTiles( + this.zoomLevel, + expandedXScale, + this.tilesetInfo.min_pos[0], + this.tilesetInfo.max_pos[0], + this.tilesetInfo.max_zoom, + this.tilesetInfo.max_width + ); + } + + const rows = this.xTiles; + const cols = this.yTiles; + const zoomLevel = this.zoomLevel; + + const maxWidth = this.tilesetInfo.max_width; + const tileWidth = maxWidth / 2 ** zoomLevel; + + // if we're mirroring tiles, then we only need tiles along the diagonal + const tiles = []; + + // calculate the ids of the tiles that should be visible + for (let i = 0; i < rows.length; i++) { + for (let j = i; j < cols.length; j++) { + // the length between the bottom of the track and the bottom corner of the tile + // draw it out to understand better! + const tileBottomPosition = + ((j - i - 2) * (this._xScale(tileWidth) - this._xScale(0)) * Math.sqrt(2)) / 2; + + if (tileBottomPosition > this.dimensions[1]) { + // this tile won't be visible so we don't need to fetch it + continue; + } + + const newTile: any = [zoomLevel, rows[i], cols[j]]; + newTile.mirrored = false; + newTile.dataTransform = this.options.dataTransform ? this.options.dataTransform : 'default'; + + tiles.push(newTile); + } + } + + this.setVisibleTiles(tiles); + } + /** * This function reorganize the tileset information so that it can be more conveniently managed afterwards. */ @@ -563,14 +717,22 @@ function GoslingTrack(HGC: any, ...args: any[]): any { return; } - if (resolved.data.type === 'matrix') { + const xChannel = resolved.x; + const yChannel = resolved.y; + if ( + resolved.data.type === 'matrix' && + IsChannelDeep(xChannel) && + xChannel.type === 'genomic' && + IsChannelDeep(yChannel) && + yChannel.type === 'genomic' + ) { // we do not draw matrix ourselves, higlass does. return; } - // console.log(tile); + if (!tile.gos.tabularData) { // If the data is not already stored in a tabular form, convert them. - const { tileX, tileWidth } = this.getTilePosAndDimensions( + const { tileX, tileY, tileWidth, tileHeight } = this.getTilePosAndDimensions( tile.gos.zoomLevel, tile.gos.tilePos, this.tileSize @@ -579,7 +741,9 @@ function GoslingTrack(HGC: any, ...args: any[]): any { tile.gos.tabularData = getTabularData(resolved, { ...tile.gos, tileX, + tileY, // Used for 2D tracks tileWidth, + tileHeight, // Used for 2D tracks tileSize: this.tileSize }); } From ffe1446f3a162ec6720d4503f1d7b844e0538bd6 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 21 Jun 2021 20:40:16 -0400 Subject: [PATCH 2/4] chore: testing with data transformation --- schema/gosling.schema.json | 24 +++++++++++++++++ src/core/gosling-to-higlass.ts | 1 + src/core/gosling.schema.ts | 10 +++++++ src/core/utils/data-transform.ts | 35 +++++++++++++++++++++++- src/editor/example/matrix-hffc6.ts | 14 +++++++--- src/gosling-track/data-abstraction.ts | 38 +++++++++++++++++++++++---- src/gosling-track/gosling-track.ts | 14 ++++++++-- 7 files changed, 124 insertions(+), 12 deletions(-) diff --git a/schema/gosling.schema.json b/schema/gosling.schema.json index b4a5e8d39..631872764 100644 --- a/schema/gosling.schema.json +++ b/schema/gosling.schema.json @@ -614,6 +614,9 @@ { "$ref": "#/definitions/ExonSplitTransform" }, + { + "$ref": "#/definitions/RotateMatrixTransform" + }, { "$ref": "#/definitions/CoverageTransform" }, @@ -3974,6 +3977,27 @@ }, "type": "object" }, + "RotateMatrixTransform": { + "additionalProperties": false, + "properties": { + "genomicField1": { + "type": "string" + }, + "genomicField2": { + "type": "string" + }, + "type": { + "const": "rotateMatrix", + "type": "string" + } + }, + "required": [ + "type", + "genomicField1", + "genomicField2" + ], + "type": "object" + }, "SingleTrack": { "additionalProperties": false, "properties": { diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 30ed0c6cc..960542ac1 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -112,6 +112,7 @@ export function goslingToHiGlass( // TODO: Experimental if (gosTrack.data?.type === 'matrix') { hgTrack.filetype = 'cooler'; + // ! To let HiGlass draw the linear heatmap, uncomment the following line. // hgTrack.type = 'linear-heatmap'; } diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 5d09f1902..93d9e0359 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -440,6 +440,7 @@ export type DataTransform = | LogTransform | DisplaceTransform | ExonSplitTransform + | RotateMatrixTransform | CoverageTransform | JSONParseTransform; @@ -512,6 +513,15 @@ export interface ExonSplitTransform { fields: { field: string; type: FieldType; newField: string; chrField: string }[]; } +export interface RotateMatrixTransform { + type: 'rotateMatrix'; + genomicField1: string; + genomicField2: string; + // Currently concatenate a post string, '_rotated' + // newField1: string; + // newField2: string; +} + /** * Aggregate rows and calculate coverage */ diff --git a/src/core/utils/data-transform.ts b/src/core/utils/data-transform.ts index aec6e4fdc..671cd0674 100644 --- a/src/core/utils/data-transform.ts +++ b/src/core/utils/data-transform.ts @@ -11,7 +11,8 @@ import { StrReplaceTransform, CoverageTransform, DisplaceTransform, - JSONParseTransform + JSONParseTransform, + RotateMatrixTransform } from '../gosling.schema'; import { getChannelKeysByAggregateFnc, @@ -300,6 +301,38 @@ export function displace(t: DisplaceTransform, data: Datum[], scale: ScaleLinear return base; } +export function rotateMatrix(rotate: RotateMatrixTransform, data: Datum[], scale: ScaleLinear, trackWidth: number): Datum[] { + const { genomicField1, genomicField2 } = rotate; + let output: Datum[] = []; + + Array.from(data).forEach(d => { + if(d[genomicField1] && d[genomicField2]) { + d[`x_rotated`] = (+d[genomicField1] + +d[genomicField2]) / 2.0; + d[`y_rotated`] = Math.abs(+d[genomicField1] - +d[genomicField2]) / 2.0; + + output.push(d); + + // if(scale.invert(0) <= d[`x_rotated`] && d[`x_rotated`] <= scale.invert(trackWidth)) { + // // For the performance issue, we only store the data rows that are visible in the current view. + // output.push(d); + // } + + // if(d[`y_rotated`] <= 5000) { + // For the performance issue, we only store the data rows that are visible in the current view. + // output.push(d); + // } + + // TESSTING + // if(scale.invert(0) <= d.x && d.x <= scale.invert(trackWidth)) { + // // For the performance issue, we only store the data rows that are visible in the current view. + // output.push(d); + // } + } + }); + console.log('scale.invert(0)', scale.invert(0), 'scale.invert(trackWidth)', scale.invert(trackWidth)); + return output; +} + export function splitExon(split: ExonSplitTransform, data: Datum[], assembly: Assembly = 'hg38'): Datum[] { const { separator, fields, flag } = split; let output: Datum[] = Array.from(data); diff --git a/src/editor/example/matrix-hffc6.ts b/src/editor/example/matrix-hffc6.ts index d25e79e4c..415c4e8eb 100644 --- a/src/editor/example/matrix-hffc6.ts +++ b/src/editor/example/matrix-hffc6.ts @@ -491,7 +491,9 @@ export const EX_SPEC_ROTATED_MATRIX: GoslingSpec = { title: 'Rotated Matrix Visualization', subtitle: 'Hi-C for HFFc6 Cells', arrangement: 'vertical', - xDomain: { chromosome: '7', interval: [77700000, 81000000] }, + xDomain: { chromosome: '1', interval: [1, 10000] }, + // xDomain: { chromosome: '1', interval: [1000000, 1010000] }, + // xDomain: { chromosome: '7', interval: [77700000, 81000000] }, spacing: 0, style: { outlineWidth: 1 }, views: [ @@ -503,9 +505,13 @@ export const EX_SPEC_ROTATED_MATRIX: GoslingSpec = { url: GOSLING_PUBLIC_DATA.matrixHiC, type: 'matrix' }, - mark: 'rect', - x: { field: 'position1', type: 'genomic' }, - color: { field: 'value', type: 'quantitative', range: 'warm' }, + dataTransform: [ + { type: 'rotateMatrix', genomicField1: 'x', genomicField2: 'y' } + ], + mark: 'point', + x: { field: 'x_rotated', type: 'genomic' }, + y: { field: 'y_rotated', type: 'quantitative' }, + color: { field: 'value', type: 'quantitative', legend: true }, width: 600, height: 100 }, diff --git a/src/gosling-track/data-abstraction.ts b/src/gosling-track/data-abstraction.ts index 082597f26..84ceddd0e 100644 --- a/src/gosling-track/data-abstraction.ts +++ b/src/gosling-track/data-abstraction.ts @@ -1,5 +1,6 @@ import { Datum, SingleTrack } from '../core/gosling.schema'; import { IsDataDeepTileset } from '../core/gosling.schema.guards'; +import Logging from '../core/utils/log' /** * Convert genomic data formats to common tabular formats for given tile. @@ -165,22 +166,49 @@ export function getTabularData( }); }); } else if (spec.data.type === 'matrix') { - if (!data.dense) { + if (!data.dense || typeof data.tileY === 'undefined' || typeof data.tileHeight === 'undefined') { // we did not get sufficient data. return; } + console.log(data); const binSize = Math.sqrt(data.dense.length); if(binSize !== 256) { - console.warn('Bin size of the matrix dataset is not 256'); + console.warn('The bin size of the matrix tilesets is not 256'); } + const { tileX, tileY } = data; + const tileSize = 256; const numericValues = data.dense; + const tileXUnitSize = data.tileWidth / tileSize; + const tileYUnitSize = data.tileHeight / tileSize; - // data.dense.forEach((value, i) => { - // const - // }); + console.log(tileXUnitSize, tileYUnitSize, tileSize, data.tileWidth); + + Logging.recordTime('matrix-processing'); + + numericValues.forEach((value, i) => { + const xIndex = i % binSize; + const yIndex = Math.floor(i / binSize); + + console.log(xIndex, yIndex, value); + + // add individual rows + tabularData.push({ + value, + x: tileX + (xIndex + 0.5) * tileXUnitSize, + xs: tileX + xIndex * tileXUnitSize, + xe: tileX + (xIndex + 1) * tileXUnitSize, + y: tileY + (yIndex + 0.5) * tileYUnitSize, + ys: tileY + yIndex * tileYUnitSize, + ye: tileY + (yIndex + 1) * tileYUnitSize + }); + }); + + Logging.printTime('matrix-processing'); + + console.log(tabularData); // // calculate the tile's position in bins // const tileXStartBin = Math.floor(data.tileX / data.tileRes); diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 823218b95..a0152ae95 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -21,6 +21,7 @@ import { filterData, parseSubJSON, replaceString, + rotateMatrix, splitExon } from '../core/utils/data-transform'; import { getTabularData } from './data-abstraction'; @@ -210,7 +211,7 @@ function GoslingTrack(HGC: any, ...args: any[]): any { const tm = tile.goslingModels[0]; // check visibility condition - const trackWidth = this.dimensions[1]; + const trackWidth = this.dimensions[0]; const zoomLevel = this._xScale.invert(trackWidth) - this._xScale.invert(0); if (!tm.trackVisibility({ zoomLevel })) { return; @@ -223,7 +224,7 @@ function GoslingTrack(HGC: any, ...args: any[]): any { // A single tile contains one track or multiple tracks overlaid tile.goslingModels.forEach((tm: GoslingTrackModel) => { // check visibility condition - const trackWidth = this.dimensions[1]; + const trackWidth = this.dimensions[0]; const zoomLevel = this._xScale.invert(trackWidth) - this._xScale.invert(0); if (!tm.trackVisibility({ zoomLevel })) { return; @@ -774,6 +775,15 @@ function GoslingTrack(HGC: any, ...args: any[]): any { resolved.assembly ); break; + case 'rotateMatrix': + tile.gos.tabularDataFiltered = rotateMatrix( + t, + tile.gos.tabularDataFiltered, + this._xScale.copy(), + this.dimensions[0] + ); + console.log(tile.gos.tabularDataFiltered); + break; case 'coverage': tile.gos.tabularDataFiltered = aggregateCoverage( t, From 094f9edd43019e5ef73edc8d52456b87b06ce086 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 21 Jun 2021 20:43:31 -0400 Subject: [PATCH 3/4] chore: schema --- src/core/gosling.schema.guards.ts | 4 +--- src/core/utils/data-transform.ts | 15 ++++++++++----- src/editor/example/matrix-hffc6.ts | 4 +--- src/gosling-track/data-abstraction.ts | 9 ++++----- src/gosling-track/gosling-track.ts | 6 +++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index 85a47fe1d..bf0d0b9be 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -149,9 +149,7 @@ export function IsRotatedMatrixTrack(track: Track) { track.data?.type === 'matrix' && IsChannelDeep(xChannel) && xChannel.type === 'genomic' && - ( - !IsChannelDeep(yChannel) || yChannel.type !== 'genomic' - ) + (!IsChannelDeep(yChannel) || yChannel.type !== 'genomic') ); } diff --git a/src/core/utils/data-transform.ts b/src/core/utils/data-transform.ts index 671cd0674..12c6a7e77 100644 --- a/src/core/utils/data-transform.ts +++ b/src/core/utils/data-transform.ts @@ -301,12 +301,17 @@ export function displace(t: DisplaceTransform, data: Datum[], scale: ScaleLinear return base; } -export function rotateMatrix(rotate: RotateMatrixTransform, data: Datum[], scale: ScaleLinear, trackWidth: number): Datum[] { +export function rotateMatrix( + rotate: RotateMatrixTransform, + data: Datum[], + scale: ScaleLinear, + trackWidth: number +): Datum[] { const { genomicField1, genomicField2 } = rotate; - let output: Datum[] = []; + const output: Datum[] = []; Array.from(data).forEach(d => { - if(d[genomicField1] && d[genomicField2]) { + if (d[genomicField1] && d[genomicField2]) { d[`x_rotated`] = (+d[genomicField1] + +d[genomicField2]) / 2.0; d[`y_rotated`] = Math.abs(+d[genomicField1] - +d[genomicField2]) / 2.0; @@ -318,8 +323,8 @@ export function rotateMatrix(rotate: RotateMatrixTransform, data: Datum[], scale // } // if(d[`y_rotated`] <= 5000) { - // For the performance issue, we only store the data rows that are visible in the current view. - // output.push(d); + // For the performance issue, we only store the data rows that are visible in the current view. + // output.push(d); // } // TESSTING diff --git a/src/editor/example/matrix-hffc6.ts b/src/editor/example/matrix-hffc6.ts index 415c4e8eb..e9df81774 100644 --- a/src/editor/example/matrix-hffc6.ts +++ b/src/editor/example/matrix-hffc6.ts @@ -505,9 +505,7 @@ export const EX_SPEC_ROTATED_MATRIX: GoslingSpec = { url: GOSLING_PUBLIC_DATA.matrixHiC, type: 'matrix' }, - dataTransform: [ - { type: 'rotateMatrix', genomicField1: 'x', genomicField2: 'y' } - ], + dataTransform: [{ type: 'rotateMatrix', genomicField1: 'x', genomicField2: 'y' }], mark: 'point', x: { field: 'x_rotated', type: 'genomic' }, y: { field: 'y_rotated', type: 'quantitative' }, diff --git a/src/gosling-track/data-abstraction.ts b/src/gosling-track/data-abstraction.ts index 84ceddd0e..a269f7695 100644 --- a/src/gosling-track/data-abstraction.ts +++ b/src/gosling-track/data-abstraction.ts @@ -1,6 +1,6 @@ import { Datum, SingleTrack } from '../core/gosling.schema'; import { IsDataDeepTileset } from '../core/gosling.schema.guards'; -import Logging from '../core/utils/log' +import Logging from '../core/utils/log'; /** * Convert genomic data formats to common tabular formats for given tile. @@ -173,8 +173,8 @@ export function getTabularData( console.log(data); const binSize = Math.sqrt(data.dense.length); - - if(binSize !== 256) { + + if (binSize !== 256) { console.warn('The bin size of the matrix tilesets is not 256'); } @@ -183,7 +183,7 @@ export function getTabularData( const numericValues = data.dense; const tileXUnitSize = data.tileWidth / tileSize; const tileYUnitSize = data.tileHeight / tileSize; - + console.log(tileXUnitSize, tileYUnitSize, tileSize, data.tileWidth); Logging.recordTime('matrix-processing'); @@ -234,7 +234,6 @@ export function getTabularData( // if (tileSliceWidth < 0 || tileSliceHeight < 0) { // return; // } - } else if (spec.data.type === 'beddb') { if (!data.raw) { // we did not get sufficient data. diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index a0152ae95..2dac60412 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -396,7 +396,7 @@ function GoslingTrack(HGC: any, ...args: any[]): any { } calculateZoomLevel() { - if(IsRotatedMatrixTrack(this.originalSpec)) { + if (IsRotatedMatrixTrack(this.originalSpec)) { // For the rotated matrix, we need special treatment for calculating zoom level. return this.calculateZoomLevelForRotatedMatrix(); } else { @@ -451,12 +451,12 @@ function GoslingTrack(HGC: any, ...args: any[]): any { } calculateVisibleTiles() { - if(IsRotatedMatrixTrack(this.originalSpec)) { + if (IsRotatedMatrixTrack(this.originalSpec)) { // We need special treatment for calculating visible tiles. this.calculateVisibleTilesForRotatedMatrix(); return; } - + if (!usePrereleaseRendering(this.originalSpec)) { // This is the common way of calculating visible tiles. super.calculateVisibleTiles(); From 718523c38d60fafa07c3ec68fb2d1ef4b5fc4a13 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Tue, 22 Jun 2021 18:52:21 -0400 Subject: [PATCH 4/4] chore: minor update --- src/core/utils/data-transform.ts | 2 +- src/gosling-track/gosling-track.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/utils/data-transform.ts b/src/core/utils/data-transform.ts index 12c6a7e77..a0aa6ef89 100644 --- a/src/core/utils/data-transform.ts +++ b/src/core/utils/data-transform.ts @@ -315,7 +315,7 @@ export function rotateMatrix( d[`x_rotated`] = (+d[genomicField1] + +d[genomicField2]) / 2.0; d[`y_rotated`] = Math.abs(+d[genomicField1] - +d[genomicField2]) / 2.0; - output.push(d); + // output.push(d); // if(scale.invert(0) <= d[`x_rotated`] && d[`x_rotated`] <= scale.invert(trackWidth)) { // // For the performance issue, we only store the data rows that are visible in the current view. diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 2dac60412..8d6c67e44 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -702,7 +702,7 @@ function GoslingTrack(HGC: any, ...args: any[]): any { return; } - if (tile.goslingModels && tile.goslingModels.length !== 0) { + if (tile.goslingModels && tile.goslingModels.length !== 0 && this.originalSpec.data?.type !== 'matrix') { // already have the gosling models constructed return tile.goslingModels; }