diff --git a/schema/gosling.schema.json b/schema/gosling.schema.json index a4c70b449..2ab68e38f 100644 --- a/schema/gosling.schema.json +++ b/schema/gosling.schema.json @@ -208,6 +208,9 @@ "items": { "additionalProperties": false, "properties": { + "assembly": { + "$ref": "#/definitions/Assembly" + }, "chromosomeField": { "type": "string" }, diff --git a/src/core/example/hg-view-config-1.ts b/src/core/example/hg-view-config-1.ts index 47c652e46..e70d60b22 100644 --- a/src/core/example/hg-view-config-1.ts +++ b/src/core/example/hg-view-config-1.ts @@ -20,7 +20,7 @@ const example = { options: { layout: 'linear', innerRadius: null, - width: 400, + width: 1000, height: 44, theme: { base: 'light', @@ -286,7 +286,7 @@ const example = { contents: [ { type: 'gosling-track', - width: 400, + width: 1000, height: 14, options: { showMousePosition: true, @@ -313,10 +313,10 @@ const example = { x: { field: 'chromStart', type: 'genomic', - domain: { - chromosome: '17', - interval: [20000000, 50000000] - }, + // domain: { + // chromosome: '17', + // interval: [20000000, 50000000] + // }, linkingId: 'top', axis: 'top' }, @@ -325,7 +325,7 @@ const example = { size: { value: 14 }, stroke: { value: 'black' }, strokeWidth: { value: 0.5 }, - width: 400, + width: 1000, height: 44, overlay: [ { @@ -631,8 +631,8 @@ const example = { gallery: [], whole: [] }, - initialXDomain: [2510780562, 2540780562], - initialYDomain: [2510780562, 2540780562], + initialXDomain: [0, 3088269832], + initialYDomain: [0, 3088269832], zoomFixed: false, zoomLimits: [1, null], uid: 'view-1', @@ -653,13 +653,30 @@ const example = { center: [ { type: 'combined', - width: 399, - height: 400, + width: 999, + height: 430, contents: [ { type: 'gosling-2d-track', - width: 400, - height: 400, + width: 1000, + height: 430, + data: { + url: + 'https://s3.amazonaws.com/gosling-lang.org/data/ideogram.js/homo_sapiens-mus_musculus-synteny-v73-adjusted.tsv', + type: 'csv', + genomicFieldsToConvert: [ + { + chromosomeField: 'Chromosome_spec1', + genomicFields: ['Start_spec1', 'End_spec1'] + }, + { + chromosomeField: 'Chromosome_spec2', + genomicFields: ['Start_spec2', 'End_spec2'] + } + ], + separator: '\t', + assembly: 'hg38' + }, options: { showMousePosition: true, mousePositionColor: '#000000', @@ -676,7 +693,7 @@ const example = { backgroundColor: 'transparent', spec: { layout: 'linear', - xDomain: { chromosome: '1' }, + // xDomain: { chromosome: '1' }, assembly: 'hg38', orientation: 'horizontal', static: false, @@ -686,32 +703,39 @@ const example = { style: { outlineWidth: 0 }, data: { url: - 'https://raw.githubusercontent.com/vigsterkr/circos/master/data/5/segdup.txt', + 'https://s3.amazonaws.com/gosling-lang.org/data/ideogram.js/homo_sapiens-mus_musculus-synteny-v73-adjusted.tsv', type: 'csv', - headerNames: ['id', 'chr', 'p1', 'p2'], - chromosomePrefix: 'hs', - chromosomeField: 'chr', - genomicFields: ['p1', 'p2'], - separator: ' ', - longToWideId: 'id' + genomicFieldsToConvert: [ + { + chromosomeField: 'Chromosome_spec1', + genomicFields: ['Start_spec1', 'End_spec1'] + }, + { + chromosomeField: 'Chromosome_spec2', + genomicFields: ['Start_spec2', 'End_spec2'] + } + ], + separator: '\t', + assembly: 'hg38' }, mark: 'betweenLink', x: { - field: 'p1', + field: 'Start_spec1', type: 'genomic', axis: 'none', - linkingId: 'top', - domain: { chromosome: '1' } + linkingId: 'top' + // domain: { chromosome: '1' } }, - xe: { field: 'p2', type: 'genomic' }, + xe: { field: 'End_spec1', type: 'genomic' }, x1: { - field: 'p1_2', + field: 'Start_spec2', type: 'genomic', linkingId: 'bottom' }, - x1e: { field: 'p2_2', type: 'genomic' }, - stroke: { - field: 'chr', + x1e: { field: 'End_spec2', type: 'genomic' }, + strokeWidth: { value: 0 }, + color: { + field: 'Chromosome_spec1', type: 'nominal', domain: [ 'chr1', @@ -741,8 +765,8 @@ const example = { ] }, opacity: { value: 0.5 }, - width: 400, - height: 400, + width: 1000, + height: 430, overlayOnPreviousTrack: false }, theme: { @@ -986,17 +1010,6 @@ const example = { quantitativeSizeRange: [2, 6] } } - }, - data: { - url: 'https://raw.githubusercontent.com/vigsterkr/circos/master/data/5/segdup.txt', - type: 'csv', - headerNames: ['id', 'chr', 'p1', 'p2'], - chromosomePrefix: 'hs', - chromosomeField: 'chr', - genomicFields: ['p1', 'p2'], - separator: ' ', - longToWideId: 'id', - assembly: 'hg38' } } ] @@ -1007,7 +1020,7 @@ const example = { gallery: [], whole: [] }, - initialXDomain: [1, 248956422], + initialXDomain: [1, 3088269832], initialYDomain: [0, 3088269832], zoomFixed: false, zoomLimits: [1, null], @@ -1029,12 +1042,12 @@ const example = { center: [ { type: 'combined', - width: 399, + width: 999, height: 14, contents: [ { type: 'gosling-track', - width: 400, + width: 1000, height: 14, options: { showMousePosition: true, @@ -1062,7 +1075,7 @@ const example = { field: 'chromStart', type: 'genomic', axis: 'bottom', - domain: { chromosome: '3' }, + // domain: { chromosome: '3' }, linkingId: 'bottom' }, xe: { field: 'chromEnd', type: 'genomic' }, @@ -1070,7 +1083,7 @@ const example = { size: { value: 14 }, stroke: { value: 'black' }, strokeWidth: { value: 0.5 }, - width: 400, + width: 1000, height: 44, overlay: [ { @@ -1640,8 +1653,8 @@ const example = { gallery: [], whole: [] }, - initialXDomain: [491149952, 689445510], - initialYDomain: [491149952, 689445510], + initialXDomain: [0, 3088269832], + initialYDomain: [0, 3088269832], zoomFixed: false, zoomLimits: [1, null], uid: 'view-3' diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index d3dc39dd3..9064f9827 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -145,7 +145,11 @@ export function goslingToHiGlass( .adjustDomain(gosTrack.orientation, width, height) .setMainTrack(hgTrack) .addTrackSourceServers(server) - .setZoomFixed(firstResolvedSpec.static === true) + .setZoomFixed( + Is2DTrack(firstResolvedSpec) && firstResolvedSpec.mark === 'betweenLink' + ? true + : firstResolvedSpec.static === true + ) .setLayout(layout); } diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index b6f00a6c3..22df6fcdc 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -145,6 +145,24 @@ export function Is2DTrack(track: Track) { ); } +/** + * Is this a between link view with two independent axes? + * TODO: should we include orthorgonal view that use both x and y for genomics fields? + */ +export function IsConnectorView(track: Track) { + return ( + IsSingleTrack(track) && + track.mark === 'betweenLink' && + IsChannelDeep(track['x']) && + track['x'].type === 'genomic' && + IsChannelDeep(track['x1']) && + track['x1'].type === 'genomic' && + track['x'].linkingId && + track['x1'].linkingId && + track['x'].linkingId !== track['x1'].linkingId + ); +} + export function IsChannelValue( channel: ChannelDeep | ChannelValue | ChannelBind | undefined | 'none' ): channel is ChannelValue { diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index fac3160db..a873191ab 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -389,6 +389,7 @@ export interface CSVData { genomicFieldsToConvert?: { chromosomeField: string; genomicFields: string[]; + assembly?: Assembly; }[]; } diff --git a/src/core/higlass.schema.ts b/src/core/higlass.schema.ts index 85b907cc9..80029669c 100644 --- a/src/core/higlass.schema.ts +++ b/src/core/higlass.schema.ts @@ -152,7 +152,7 @@ export interface GenericLocks { } export interface LocksByViewUid { - [k: string]: string; + [k: string]: any; // string | { [k: string]: { lock: string, axis: string } }; } export interface ValueScaleLocks { diff --git a/src/core/layout/higlass.ts b/src/core/layout/higlass.ts index 33af5370e..8a61eabee 100644 --- a/src/core/layout/higlass.ts +++ b/src/core/layout/higlass.ts @@ -13,14 +13,12 @@ export function renderHiGlass( theme: CompleteThemeDeep ) { if (trackInfos.length === 0) { - // no tracks to render + // no tracks to render, so no point to render HiGlass. return; } - // HiGlass model + /* Generate/update the HiGlass model by iterating tracks */ const hgModel = new HiGlassModel(); - - /* Update the HiGlass model by iterating tracks */ trackInfos.forEach(tb => { const { track, boundingBox: bb, layout } = tb; goslingToHiGlass(hgModel, track, bb, layout, theme); @@ -29,8 +27,7 @@ export function renderHiGlass( /* Add linking information to the HiGlass model */ const linkingInfos = getLinkingInfo(hgModel); - // Brushing - // (between a view with `brush` and a view having the same linking name) + /* Linking between a brush and a view */ linkingInfos .filter(d => d.isBrush) .forEach(info => { @@ -43,33 +40,60 @@ export function renderHiGlass( ); }); - // location/zoom lock information - // fill `locksByViewUid` + /* + * Linking zoom levels between views + */ + + // Set `locksByViewUid` linkingInfos .filter(d => !d.isBrush) .forEach(d => { - hgModel.spec().zoomLocks.locksByViewUid[d.viewId] = d.linkId; - hgModel.spec().locationLocks.locksByViewUid[d.viewId] = d.linkId; + hgModel.spec().zoomLocks.locksByViewUid[d.viewId] = d.zoomLinkingId; }); - // fill `locksDict` - const uniqueLinkIds = Array.from(new Set(linkingInfos.map(d => d.linkId))); + // Set `locksDict` + const uniqueZoomLinkIds = Array.from(new Set(linkingInfos.map(d => d.zoomLinkingId))); + uniqueZoomLinkIds.forEach(zoomLinkingId => { + hgModel.spec().zoomLocks.locksDict[zoomLinkingId] = { uid: zoomLinkingId }; + linkingInfos + .filter(d => !d.isBrush) + .filter(d => d.zoomLinkingId === zoomLinkingId) + .forEach(d => { + hgModel.spec().zoomLocks.locksDict[zoomLinkingId][d.viewId] = [124625310.5, 124625310.5, 249250.621]; + }); + }); + + /* + * Linking locations between views + */ - uniqueLinkIds.forEach(linkId => { - hgModel.spec().zoomLocks.locksDict[linkId] = { uid: linkId }; - hgModel.spec().locationLocks.locksDict[linkId] = { uid: linkId }; + // Set `locksByViewUid` + linkingInfos + .filter(d => !d.isBrush) + .forEach(d => { + if (!hgModel.spec().locationLocks.locksByViewUid[d.viewId]) { + hgModel.spec().locationLocks.locksByViewUid[d.viewId] = {}; + } + hgModel.spec().locationLocks.locksByViewUid[d.viewId][d.channel === 'x' ? 'x' : 'y'] = { + lock: d.linkId, + axis: d.viewId === 'view-3' ? 'y' : 'x' + }; + }); + // Set `locksDict` + const uniqueLocationLinkIds = Array.from(new Set(linkingInfos.map(d => d.linkId))); + uniqueLocationLinkIds.forEach(linkId => { + hgModel.spec().locationLocks.locksDict[linkId] = { uid: linkId }; linkingInfos .filter(d => !d.isBrush) .filter(d => d.linkId === linkId) .forEach(d => { - hgModel.spec().zoomLocks.locksDict[linkId][d.viewId] = [124625310.5, 124625310.5, 249250.621]; hgModel.spec().locationLocks.locksDict[linkId][d.viewId] = [124625310.5, 124625310.5, 249250.621]; }); }); // !! Uncomment the following code to test with specific HiGlass viewConfig - hgModel.setExampleHiglassViewConfig(); + // hgModel.setExampleHiglassViewConfig(); setHg(hgModel.spec(), getBoundingBox(trackInfos)); } diff --git a/src/core/utils/linking.ts b/src/core/utils/linking.ts index e56604fd5..90f4f2eb4 100644 --- a/src/core/utils/linking.ts +++ b/src/core/utils/linking.ts @@ -1,25 +1,31 @@ +import { Orientation } from '../gosling.schema'; import { IsChannelDeep } from '../gosling.schema.guards'; import { HiGlassModel } from '../higlass-model'; -import { SUPPORTED_CHANNELS } from '../mark'; import { resolveSuperposedTracks } from './overlay'; /** - * + * Construct information for linking views. This is used to generate HiGlass viewConfig and render interactive brushes. */ export function getLinkingInfo(hgModel: HiGlassModel) { - const linkingInfo: { + let linkingInfo: { layout: 'circular' | 'linear'; + orientation: Orientation; + channel: 'x' | 'x1' | 'y'; viewId: string; linkId: string; + zoomLinkingId: string; isBrush: boolean; style: any; }[] = []; + const sharedZoomIds: string[][] = []; + + // TODO: remove duplicated linkingIds before reaching this for the simplicity (e.g., x: { ..., linkingId: 'top'}, x1: {..., linkingId: 'top'}}) hgModel.spec().views.forEach(v => { const viewId = v.uid; // TODO: Better way to get view specifications? - // Get spec of a view + // Get spec of a main track let spec = /* TODO: */ (v.tracks as any).center?.[0]?.contents?.[0]?.options?.spec; if (!spec) { @@ -33,18 +39,23 @@ export function getLinkingInfo(hgModel: HiGlassModel) { if (!viewId || !spec) return; - const resolved = resolveSuperposedTracks(spec); + const resolvedTracks = resolveSuperposedTracks(spec); - resolved.forEach(spec => { - SUPPORTED_CHANNELS.forEach(cKey => { + resolvedTracks.forEach(spec => { + // TODO: support all other channels as well (`SUPPORTED_CHANNELS`) + (['x', 'x1', 'y'] as ('x' | 'x1' | 'y')[]).forEach(cKey => { const channel = spec[cKey]; + const isBrush = spec.mark === 'brush'; - if (IsChannelDeep(channel) && channel.linkingId) { + if (IsChannelDeep(channel) && channel.linkingId && channel.type === 'genomic') { linkingInfo.push({ layout: spec.layout === 'circular' ? 'circular' : 'linear', + orientation: spec.orientation ?? 'horizontal', + channel: cKey, viewId, linkId: channel.linkingId, - isBrush: spec.mark === 'brush', + zoomLinkingId: '', // This will be added very soon below + isBrush, style: { color: (spec as any).color?.value, stroke: (spec as any).stroke?.value, @@ -56,10 +67,52 @@ export function getLinkingInfo(hgModel: HiGlassModel) { outerRadius: spec.outerRadius } }); - return; } }); + + /* Search for the shared zoom locks */ + const { x, x1, y } = spec; // TODO: support all other non-genomic channels as well + + const xLinkingId = IsChannelDeep(x) && x.type === 'genomic' && x.linkingId ? x.linkingId : undefined; + const x1LinkingId = IsChannelDeep(x1) && x1.type === 'genomic' && x1.linkingId ? x1.linkingId : undefined; + const yLinkingId = IsChannelDeep(y) && y.type === 'genomic' && y.linkingId ? y.linkingId : undefined; + + const uniqueLinkingIds = Array.from(new Set([xLinkingId, x1LinkingId, yLinkingId].filter(d => d))); + if (uniqueLinkingIds.length === 2) { + // Store these information so that zoom levels should be locked across all views that use either one of these linkingIds + let foundOrUpdated = false; + sharedZoomIds.forEach((d, i, arr) => { + const combinedUniqueIds = Array.from([...uniqueLinkingIds, ...d]); + if (combinedUniqueIds.length === d.length) { + // This means linkingIds have been already added, so no need additional action + foundOrUpdated = true; + } else if (combinedUniqueIds.length === d.length + 1) { + // This means linkingIds have been already added, so no need to do anything + arr[i] = combinedUniqueIds as string[]; + foundOrUpdated = true; + } else { + // This means we did not find any overlap, so keep iterate + } + }); + + if (!foundOrUpdated) { + // This means we have to add an additional item to the array + sharedZoomIds.push(uniqueLinkingIds as string[]); + } + } else if (uniqueLinkingIds.length === 3) { + // Does not make sense for all three channels to have unique linkingIds + } }); }); + + // Use common `linkingId` for shared zoom levels + linkingInfo = linkingInfo.map(d => { + const sharedIds = sharedZoomIds.find(ids => ids.indexOf(d.linkId) !== -1); + return { + ...d, + zoomLinkingId: sharedIds ? sharedIds.sort().join('-') : d.linkId + }; + }); + return linkingInfo; } diff --git a/src/core/utils/spec-preprocess.ts b/src/core/utils/spec-preprocess.ts index 8a51b01be..24a948089 100644 --- a/src/core/utils/spec-preprocess.ts +++ b/src/core/utils/spec-preprocess.ts @@ -17,7 +17,8 @@ import { IsOverlaidTrack, IsFlatTracks, IsStackedTracks, - Is2DTrack + Is2DTrack, + IsConnectorView } from '../gosling.schema.guards'; import { DEFAULT_INNER_RADIUS_PROP, @@ -245,7 +246,7 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare /** * A track with 2D genomic coordinates is forced to use a linear layout */ - if (Is2DTrack(track)) { + if (Is2DTrack(track) && !IsConnectorView(track)) { // TODO: Add a test for this. track.layout = 'linear'; } diff --git a/src/data-fetcher/csv/higlass-csv-datafetcher.ts b/src/data-fetcher/csv/higlass-csv-datafetcher.ts index f5c581d2f..cd1e628c5 100644 --- a/src/data-fetcher/csv/higlass-csv-datafetcher.ts +++ b/src/data-fetcher/csv/higlass-csv-datafetcher.ts @@ -98,16 +98,17 @@ function CSVDataFetcher(HGC: any, ...args: any): any { // This spec is used when multiple chromosomes are stored in a single row genomicFieldsToConvert.forEach((d: any) => { const cField = d.chromosomeField; + const assembly = d.assembly ?? this.assembly; d.genomicFields.forEach((g: string) => { try { - if (this.assembly !== 'unknown') { + if (assembly !== 'unknown') { // This means we need to use the relative position considering the start position of individual chr. const chr = chromosomePrefix ? row[cField].replace(chromosomePrefix, 'chr') : row[cField].includes('chr') ? row[cField] : `chr${row[cField]}`; - row[g] = GET_CHROM_SIZES(this.assembly).interval[chr][0] + +row[g]; + row[g] = GET_CHROM_SIZES(assembly).interval[chr][0] + +row[g]; } else { // In this case, we use the genomic position as it is w/o adding the cumulative length of chr. // So, nothing to do additionally. diff --git a/src/editor/example/index.ts b/src/editor/example/index.ts index cedf64851..12c8442a9 100644 --- a/src/editor/example/index.ts +++ b/src/editor/example/index.ts @@ -16,8 +16,9 @@ import { EX_SPEC_GIVE } from './give'; import { EX_SPEC_CORCES_ET_AL } from './corces'; import { EX_SPEC_CYTOBANDS } from './ideograms'; import { EX_SPEC_PILEUP } from './pileup'; -import { EX_SPEC_BAND, EX_SPEC_VERTICAL_BAND } from './vertical-band'; +import { EX_SPEC_BAND } from './vertical-band'; import { EX_SPEC_TEMPLATE } from './track-template'; +import { EX_SPEC_ALIGNMENT } from './synteny'; import { EX_SPEC_DEBUG } from './debug'; export const examples: ReadonlyArray<{ @@ -38,8 +39,7 @@ export const examples: ReadonlyArray<{ { name: 'Basic Example: Visual Encoding', id: 'VISUAL_ENCODING', - spec: EX_SPEC_VISUAL_ENCODING, - forceShow: true + spec: EX_SPEC_VISUAL_ENCODING }, { name: 'Basic Example: Circular Visual Encoding', @@ -84,24 +84,10 @@ export const examples: ReadonlyArray<{ id: 'CIRCULAR_OVERVIEW_LINEAR_DETAIL', spec: EX_SPEC_CIRCULAR_OVERVIEW_LINEAR_DETAIL }, - // { - // name: 'Basic Example: Vertical Band Connection w/ Independent Axes', - // id: 'VERTICAL_BAND', - // spec: EX_SPEC_VERTICAL_BAND, - // forceShow: true, - // underDevelopment: true - // }, - { - name: 'Basic Example: Vertical Band Connection w/ Independent Axes', - id: 'VERTICAL_BAND', - spec: EX_SPEC_VERTICAL_BAND, - forceShow: true, - underDevelopment: true - }, { name: 'Basic Example: Vertical Band Connection w/ Independent Axes', - id: 'VERTICAL_BAND', - spec: EX_SPEC_VERTICAL_BAND, + id: 'GENOME_ALIGNMENT', + spec: EX_SPEC_ALIGNMENT, forceShow: true, underDevelopment: true }, diff --git a/src/editor/example/synteny.ts b/src/editor/example/synteny.ts new file mode 100644 index 000000000..2453febfc --- /dev/null +++ b/src/editor/example/synteny.ts @@ -0,0 +1,164 @@ +import { GoslingSpec } from '../../core/gosling.schema'; + +export const EX_SPEC_ALIGNMENT: GoslingSpec = { + title: 'Genome Alignment', + subtitle: 'Reimplementation of Genome Alignment Example in Ideogram.js', + arrangement: 'parallel', + layout: 'circular', + spacing: 0, + style: { outlineWidth: 0 }, + xDomain: { interval: [0, 500000000] }, + views: [ + { + xLinkingId: 'top', + assembly: 'mm10', + tracks: [ + { + id: 'view-1', + template: 'ideogram', + data: { + url: + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'] + }, + encoding: { + startPosition: { field: 'chromStart' }, + endPosition: { field: 'chromEnd' }, + stainBackgroundColor: { field: 'Stain' }, + stainLabelColor: { field: 'Stain' }, + name: { field: 'Name' }, + stainStroke: { value: 'black' } + }, + width: 1000, + height: 20 + } + ] + }, + { + tracks: [ + { + id: 'view-2', + data: { + url: + 'https://s3.amazonaws.com/gosling-lang.org/data/ideogram.js/homo_sapiens-mus_musculus-synteny-v73-adjusted.tsv', + type: 'csv', + genomicFieldsToConvert: [ + { + assembly: 'mm10', + chromosomeField: 'Chromosome_spec1', + genomicFields: ['Start_spec1', 'End_spec1'] + }, + { + chromosomeField: 'Chromosome_spec2', + genomicFields: ['Start_spec2', 'End_spec2'] + } + ], + separator: '\t' + }, + mark: 'betweenLink', + x: { + field: 'Start_spec1', + type: 'genomic', + axis: 'none', + linkingId: 'top' + }, + xe: { field: 'End_spec1', type: 'genomic' }, + x1: { + field: 'Start_spec2', + type: 'genomic', + linkingId: 'bottom' + }, + x1e: { field: 'End_spec2', type: 'genomic' }, + strokeWidth: { value: 0 }, + color: { + field: 'Chromosome_spec1', + type: 'nominal', + domain: [ + 'chr1', + 'chr2', + 'chr3', + 'chr4', + 'chr5', + 'chr6', + 'chr7', + 'chr8', + 'chr9', + 'chr10', + 'chr11', + 'chr12', + 'chr13', + 'chr14', + 'chr15', + 'chr16', + 'chr17', + 'chr18', + 'chr19', + 'chr20', + 'chr21', + 'chr22', + 'chrX', + 'chrY' + ], + range: [ + 'rgb(153, 102, 0)', + 'rgb(102, 102, 0)', + 'rgb(153, 153, 30)', + 'rgb(204, 0, 0)', + 'rgb(255, 0, 0)', + 'rgb(255, 0, 204)', + 'rgb(255, 204, 204)', + 'rgb(255, 153, 0)', + 'rgb(255, 204, 0)', + 'rgb(255, 255, 0)', + 'rgb(204, 255, 0)', + 'rgb(0, 255, 0)', + 'rgb(53, 128, 0)', + 'rgb(0, 0, 204)', + 'rgb(102, 153, 255)', + 'rgb(153, 204, 255)', + 'rgb(0, 255, 255)', + 'rgb(204, 255, 255)', + 'rgb(153, 0, 204)', + 'rgb(204, 51, 255)', + 'rgb(204, 153, 255)', + 'rgb(102, 102, 102)', + 'rgb(255, 102, 102)', + 'rgb(102, 102, 255)' + ] + }, + opacity: { value: 0.5 }, + width: 1000, + height: 430 + } + ] + }, + { + xLinkingId: 'bottom', + tracks: [ + { + id: 'view-3', + template: 'ideogram', + data: { + url: + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'] + }, + encoding: { + startPosition: { field: 'chromStart', axis: 'bottom' }, + endPosition: { field: 'chromEnd' }, + stainBackgroundColor: { field: 'Stain' }, + stainLabelColor: { field: 'Stain' }, + name: { field: 'Name' }, + stainStroke: { value: 'black' } + }, + width: 1000, + height: 20 + } + ] + } + ] +};