diff --git a/package.json b/package.json index 735127c56..d0f686f81 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "lodash": "^4.17.15", "mixwith": "^0.1.1", "pubsub-js": "^1.9.3", - "react-grid-layout": "^1.2.5", "threads": "^1.6.4", "worker-loader": "^3.0.8" }, diff --git a/schema/gosling.schema.json b/schema/gosling.schema.json index d5121bac2..2a63ae0d0 100644 --- a/schema/gosling.schema.json +++ b/schema/gosling.schema.json @@ -212,6 +212,9 @@ "items": { "additionalProperties": false, "properties": { + "assembly": { + "$ref": "#/definitions/Assembly" + }, "chromosomeField": { "type": "string" }, @@ -486,6 +489,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -514,9 +520,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -556,6 +559,9 @@ "width": { "type": "number" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -572,9 +578,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } @@ -911,9 +936,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -932,6 +954,9 @@ }, "type": "array" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -948,9 +973,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } @@ -1602,9 +1646,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -1630,6 +1671,9 @@ }, "type": "array" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -1646,9 +1690,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } @@ -1753,6 +1816,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -1799,9 +1865,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -1821,6 +1884,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -1861,9 +1927,6 @@ "innerRadius": { "type": "number" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -1939,6 +2002,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -1958,6 +2024,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -1973,6 +2042,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -2056,6 +2141,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -2075,6 +2163,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -2090,6 +2181,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -2110,6 +2217,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -2160,9 +2270,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -2253,6 +2360,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -2272,6 +2382,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -2287,6 +2400,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -2320,6 +2449,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -2372,9 +2504,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -2394,6 +2523,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -2434,9 +2566,6 @@ "innerRadius": { "type": "number" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -2512,6 +2641,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -2531,6 +2663,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -2546,6 +2681,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -2632,6 +2783,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -2651,6 +2805,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -2666,6 +2823,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -2746,9 +2919,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -2780,6 +2950,9 @@ }, "type": "array" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -2796,9 +2969,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } @@ -2816,6 +3008,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -2869,9 +3064,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -2962,6 +3154,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -2981,6 +3176,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -2996,6 +3194,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -3017,6 +3231,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -3070,9 +3287,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -3170,6 +3384,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -3189,6 +3406,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -3204,6 +3424,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -3232,9 +3468,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -3259,6 +3492,9 @@ }, "type": "array" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -3275,9 +3511,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } @@ -3295,6 +3550,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -3341,9 +3599,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -3428,6 +3683,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -3447,6 +3705,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -3462,6 +3723,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -3542,6 +3819,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -3592,9 +3872,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -3692,6 +3969,9 @@ "x1": { "$ref": "#/definitions/Channel" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/Channel" }, @@ -3711,6 +3991,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -3726,6 +4009,22 @@ "y1e": { "$ref": "#/definitions/Channel" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, @@ -3934,6 +4233,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -3968,9 +4270,6 @@ "layout": { "$ref": "#/definitions/Layout" }, - "linkingId": { - "type": "string" - }, "orientation": { "$ref": "#/definitions/Orientation" }, @@ -4013,6 +4312,9 @@ "width": { "type": "number" }, + "x1LinkingId": { + "type": "string" + }, "xAxis": { "$ref": "#/definitions/AxisPosition" }, @@ -4029,9 +4331,28 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" } diff --git a/schema/template.schema.json b/schema/template.schema.json index 20d8c2cf0..6b832db5b 100644 --- a/schema/template.schema.json +++ b/schema/template.schema.json @@ -1253,6 +1253,9 @@ "_invalidTrack": { "type": "boolean" }, + "_parentViewId": { + "type": "string" + }, "_renderingId": { "type": "string" }, @@ -1290,9 +1293,6 @@ "innerRadius": { "type": "number" }, - "linkingId": { - "type": "string" - }, "mark": { "$ref": "#/definitions/Mark" }, @@ -1368,6 +1368,9 @@ "x1": { "$ref": "#/definitions/ChannelWithBase" }, + "x1LinkingId": { + "type": "string" + }, "x1e": { "$ref": "#/definitions/ChannelWithBase" }, @@ -1387,6 +1390,9 @@ } ] }, + "xLinkingId": { + "type": "string" + }, "xOffset": { "type": "number" }, @@ -1402,6 +1408,22 @@ "y1e": { "$ref": "#/definitions/ChannelWithBase" }, + "yDomain": { + "anyOf": [ + { + "$ref": "#/definitions/DomainInterval" + }, + { + "$ref": "#/definitions/DomainChrInterval" + }, + { + "$ref": "#/definitions/DomainChr" + } + ] + }, + "yLinkingId": { + "type": "string" + }, "yOffset": { "type": "number" }, diff --git a/src/core/example/hg-view-config-1.ts b/src/core/example/hg-view-config-1.ts index c8f4a1aa9..e70d60b22 100644 --- a/src/core/example/hg-view-config-1.ts +++ b/src/core/example/hg-view-config-1.ts @@ -1,6 +1,6 @@ const example = { compactLayout: false, - trackSourceServers: ['https://server.gosling-lang.org/api/v1/'], + trackSourceServers: [], views: [ { genomePositionSearchBoxVisible: false, @@ -10,352 +10,18 @@ const example = { chromInfoServer: 'https://higlass.io/api/v1', chromInfoId: 'hg38' }, - layout: { x: 0, y: 0, w: 6.4, h: 600 }, + layout: { x: 0, y: 50, w: 12, h: 44 }, tracks: { - top: [], - left: [ - { - type: 'combined', - width: 130, - height: 600, - contents: [ - { - type: 'gosling-track', - server: 'https://server.gosling-lang.org/api/v1/', - tilesetUid: 'cistrome-multivec', - width: 130, - height: 600, - options: { - showMousePosition: true, - mousePositionColor: '#000000', - fontSize: 24, - labelPosition: 'none', - labelShowResolution: false, - labelColor: 'black', - labelBackgroundColor: 'white', - labelTextOpacity: 1, - labelLeftMargin: 1, - labelTopMargin: 1, - labelRightMargin: 0, - labelBottomMargin: 0, - backgroundColor: 'transparent', - spec: { - spacing: 10, - orientation: 'vertical', - assembly: 'hg38', - layout: 'linear', - static: false, - xDomain: { interval: [0, 1000000000] }, - centerRadius: 0.3, - xOffset: 0, - yOffset: 0, - style: { outlineWidth: 0.5 }, - data: { - url: - 'https://server.gosling-lang.org/api/v1/tileset_info/?d=cistrome-multivec', - type: 'multivec', - row: 'sample', - column: 'position', - value: 'peak', - categories: ['sample 1', 'sample 2', 'sample 3', 'sample 4'], - binSize: 4 - }, - mark: 'rect', - x: { - field: 'start', - type: 'genomic', - axis: 'left', - domain: { interval: [0, 1000000000] }, - linkingId: '7f94b74e-25ec-4e67-8c75-3dc0f1b0d6bd' - }, - xe: { field: 'end', type: 'genomic' }, - row: { - field: 'sample', - type: 'nominal', - legend: true - }, - color: { - field: 'peak', - type: 'quantitative', - legend: true - }, - tooltip: [ - { - field: 'start', - type: 'genomic', - alt: 'Start Position' - }, - { - field: 'end', - type: 'genomic', - alt: 'End Position' - }, - { - field: 'peak', - type: 'quantitative', - alt: 'Value', - format: '.2' - }, - { field: 'sample', type: 'nominal', alt: 'Sample' } - ], - width: 160, - height: 600, - overlayOnPreviousTrack: false - }, - theme: { - base: 'light', - root: { - background: 'white', - titleColor: 'black', - titleBackgroundColor: 'transparent', - titleFontSize: 18, - titleFontFamily: 'Arial', - titleAlign: 'left', - titleFontWeight: 'bold', - subtitleColor: 'gray', - subtitleBackgroundColor: 'transparent', - subtitleFontSize: 16, - subtitleFontFamily: 'Arial', - subtitleFontWeight: 'normal', - subtitleAlign: 'left', - mousePositionColor: '#000000' - }, - track: { - background: 'transparent', - alternatingBackground: 'transparent', - titleColor: 'black', - titleBackground: 'white', - titleFontSize: 24, - titleAlign: 'left', - outline: 'black', - outlineWidth: 1 - }, - legend: { - position: 'top', - background: 'white', - backgroundOpacity: 0.7, - labelColor: 'black', - labelFontSize: 12, - labelFontWeight: 'normal', - labelFontFamily: 'Arial', - backgroundStroke: '#DBDBDB', - tickColor: 'black' - }, - axis: { - tickColor: 'black', - labelColor: 'black', - labelFontSize: 12, - labelFontWeight: 'normal', - labelFontFamily: 'Arial', - baselineColor: 'black', - gridColor: '#E3E3E3', - gridStrokeWidth: 1, - gridStrokeType: 'solid', - gridStrokeDash: [4, 4] - }, - markCommon: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - point: { - color: '#E79F00', - size: 3, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - rect: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - triangle: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - area: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - line: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - bar: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - rule: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 1, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - link: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 1, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - }, - text: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6], - textAnchor: 'middle', - textFontWeight: 'normal' - }, - brush: { - color: 'gray', - size: 1, - stroke: 'black', - strokeWidth: 1, - opacity: 0.3, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] - } - } - } - } - ] - }, + top: [ { - uid: '15483511-f6fb-11eb-914b-976b6ecda162', + uid: 'dbed29d0-fc48-11eb-b136-51c77f658267', type: 'axis-track', chromInfoPath: 'https://s3.amazonaws.com/gosling-lang.org/data/hg38.chrom.sizes', options: { layout: 'linear', - outerRadius: null, - width: 160, - height: 600, + innerRadius: null, + width: 1000, + height: 44, theme: { base: 'light', root: { @@ -560,68 +226,805 @@ const example = { ], quantitativeSizeRange: [2, 6] }, - text: { - color: '#E79F00', - size: 1, - stroke: 'black', - strokeWidth: 0, - opacity: 1, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' + text: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6], + textAnchor: 'middle', + textFontWeight: 'normal' + }, + brush: { + color: 'gray', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 0.3, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + } + }, + assembly: 'hg38', + stroke: 'transparent', + color: 'black', + fontSize: 12, + fontFamily: 'Arial', + fontWeight: 'normal', + tickColor: 'black', + tickFormat: 'plain', + tickPositions: 'even', + reverseOrientation: false + }, + height: 30 + } + ], + left: [], + center: [ + { + type: 'combined', + width: 399, + height: 14, + contents: [ + { + type: 'gosling-track', + width: 1000, + height: 14, + options: { + showMousePosition: true, + mousePositionColor: '#000000', + fontSize: 24, + labelPosition: 'none', + labelShowResolution: false, + labelColor: 'black', + labelBackgroundColor: 'white', + labelTextOpacity: 1, + labelLeftMargin: 1, + labelTopMargin: 1, + labelRightMargin: 0, + labelBottomMargin: 0, + backgroundColor: 'transparent', + spec: { + data: { + url: + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'] + }, + x: { + field: 'chromStart', + type: 'genomic', + // domain: { + // chromosome: '17', + // interval: [20000000, 50000000] + // }, + linkingId: 'top', + axis: 'top' + }, + xe: { field: 'chromEnd', type: 'genomic' }, + color: { value: 'white' }, + size: { value: 14 }, + stroke: { value: 'black' }, + strokeWidth: { value: 0.5 }, + width: 1000, + height: 44, + overlay: [ + { + mark: 'rect', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'], + not: true + } + ], + style: { outlineWidth: 0 } + }, + { + mark: 'triangleRight', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'] + }, + { type: 'filter', field: 'Name', include: 'q' } + ], + style: { outlineWidth: 0 } + }, + { + mark: 'triangleLeft', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'] + }, + { type: 'filter', field: 'Name', include: 'p' } + ], + style: { outlineWidth: 0 } + } + ], + assembly: 'hg38', + layout: 'linear', + orientation: 'horizontal', + static: false, + style: { outlineWidth: 0 }, + overlayOnPreviousTrack: false + }, + theme: { + base: 'light', + root: { + background: 'white', + titleColor: 'black', + titleBackgroundColor: 'transparent', + titleFontSize: 18, + titleFontFamily: 'Arial', + titleAlign: 'left', + titleFontWeight: 'bold', + subtitleColor: 'gray', + subtitleBackgroundColor: 'transparent', + subtitleFontSize: 16, + subtitleFontFamily: 'Arial', + subtitleFontWeight: 'normal', + subtitleAlign: 'left', + mousePositionColor: '#000000' + }, + track: { + background: 'transparent', + alternatingBackground: 'transparent', + titleColor: 'black', + titleBackground: 'white', + titleFontSize: 24, + titleAlign: 'left', + outline: 'black', + outlineWidth: 1 + }, + legend: { + position: 'top', + background: 'white', + backgroundOpacity: 0.7, + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + backgroundStroke: '#DBDBDB', + tickColor: 'black' + }, + axis: { + tickColor: 'black', + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + baselineColor: 'black', + gridColor: '#E3E3E3', + gridStrokeWidth: 1, + gridStrokeType: 'solid', + gridStrokeDash: [4, 4] + }, + markCommon: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + point: { + color: '#E79F00', + size: 3, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rect: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + triangle: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + area: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + line: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + bar: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rule: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + link: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + text: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6], + textAnchor: 'middle', + textFontWeight: 'normal' + }, + brush: { + color: 'gray', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 0.3, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + } + } + }, + data: { + url: + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'], + assembly: 'hg38' + } + } + ] + } + ], + right: [], + bottom: [], + gallery: [], + whole: [] + }, + initialXDomain: [0, 3088269832], + initialYDomain: [0, 3088269832], + zoomFixed: false, + zoomLimits: [1, null], + uid: 'view-1', + chromInfoPath: 'https://s3.amazonaws.com/gosling-lang.org/data/hg38.chrom.sizes' + }, + { + genomePositionSearchBoxVisible: false, + genomePositionSearchBox: { + autocompleteServer: 'https://higlass.io/api/v1', + autocompleteId: 'P0PLbQMwTYGy-5uPIQid7A', + chromInfoServer: 'https://higlass.io/api/v1', + chromInfoId: 'hg38' + }, + layout: { x: 0, y: 104, w: 12, h: 400 }, + tracks: { + top: [], + left: [], + center: [ + { + type: 'combined', + width: 999, + height: 430, + contents: [ + { + type: 'gosling-2d-track', + 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'] + } ], - quantitativeSizeRange: [2, 6], - textAnchor: 'middle', - textFontWeight: 'normal' + separator: '\t', + assembly: 'hg38' }, - brush: { - color: 'gray', - size: 1, - stroke: 'black', - strokeWidth: 1, - opacity: 0.3, - nominalColorRange: [ - '#E79F00', - '#029F73', - '#0072B2', - '#CB7AA7', - '#D45E00', - '#57B4E9', - '#EFE441' - ], - quantitativeSizeRange: [2, 6] + options: { + showMousePosition: true, + mousePositionColor: '#000000', + fontSize: 24, + labelPosition: 'none', + labelShowResolution: false, + labelColor: 'black', + labelBackgroundColor: 'white', + labelTextOpacity: 1, + labelLeftMargin: 1, + labelTopMargin: 1, + labelRightMargin: 0, + labelBottomMargin: 0, + backgroundColor: 'transparent', + spec: { + layout: 'linear', + // xDomain: { chromosome: '1' }, + assembly: 'hg38', + orientation: 'horizontal', + static: false, + centerRadius: 0.3, + xOffset: 0, + yOffset: 0, + style: { outlineWidth: 0 }, + 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' + }, + mark: 'betweenLink', + x: { + field: 'Start_spec1', + type: 'genomic', + axis: 'none', + linkingId: 'top' + // domain: { chromosome: '1' } + }, + 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' + ] + }, + opacity: { value: 0.5 }, + width: 1000, + height: 430, + overlayOnPreviousTrack: false + }, + theme: { + base: 'light', + root: { + background: 'white', + titleColor: 'black', + titleBackgroundColor: 'transparent', + titleFontSize: 18, + titleFontFamily: 'Arial', + titleAlign: 'left', + titleFontWeight: 'bold', + subtitleColor: 'gray', + subtitleBackgroundColor: 'transparent', + subtitleFontSize: 16, + subtitleFontFamily: 'Arial', + subtitleFontWeight: 'normal', + subtitleAlign: 'left', + mousePositionColor: '#000000' + }, + track: { + background: 'transparent', + alternatingBackground: 'transparent', + titleColor: 'black', + titleBackground: 'white', + titleFontSize: 24, + titleAlign: 'left', + outline: 'black', + outlineWidth: 1 + }, + legend: { + position: 'top', + background: 'white', + backgroundOpacity: 0.7, + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + backgroundStroke: '#DBDBDB', + tickColor: 'black' + }, + axis: { + tickColor: 'black', + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + baselineColor: 'black', + gridColor: '#E3E3E3', + gridStrokeWidth: 1, + gridStrokeType: 'solid', + gridStrokeDash: [4, 4] + }, + markCommon: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + point: { + color: '#E79F00', + size: 3, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rect: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + triangle: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + area: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + line: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + bar: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rule: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + link: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + text: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6], + textAnchor: 'middle', + textFontWeight: 'normal' + }, + brush: { + color: 'gray', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 0.3, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + } + } } - }, - assembly: 'hg38', - stroke: 'transparent', - color: 'black', - fontSize: 12, - fontFamily: 'Arial', - fontWeight: 'normal', - tickColor: 'black', - tickFormat: 'plain', - tickPositions: 'even', - reverseOrientation: false - }, - width: 30 + } + ] } ], - center: [], right: [], bottom: [], gallery: [], whole: [] }, - initialXDomain: [0, 1000000000], - initialYDomain: [0, 1000000000], + initialXDomain: [1, 3088269832], + initialYDomain: [0, 3088269832], zoomFixed: false, zoomLimits: [1, null], - uid: '15483510-f6fb-11eb-914b-976b6ecda162', + uid: 'view-2', chromInfoPath: 'https://s3.amazonaws.com/gosling-lang.org/data/hg38.chrom.sizes' }, { @@ -632,21 +1035,20 @@ const example = { chromInfoServer: 'https://higlass.io/api/v1', chromInfoId: 'hg38' }, - layout: { x: 6.8, y: 0, w: 5.2, h: 600 }, + layout: { x: 0, y: 514, w: 12, h: 44 }, tracks: { top: [], - left: [ + left: [], + center: [ { type: 'combined', - width: 130, - height: 600, + width: 999, + height: 14, contents: [ { type: 'gosling-track', - server: 'https://server.gosling-lang.org/api/v1/', - tilesetUid: 'cistrome-multivec', - width: 130, - height: 600, + width: 1000, + height: 14, options: { showMousePosition: true, mousePositionColor: '#000000', @@ -662,65 +1064,71 @@ const example = { labelBottomMargin: 0, backgroundColor: 'transparent', spec: { - spacing: 10, - orientation: 'vertical', - assembly: 'hg38', - layout: 'linear', - static: false, - xDomain: { interval: [0, 1000000000] }, - centerRadius: 0.3, - xOffset: 0, - yOffset: 0, - style: { outlineWidth: 0.5 }, data: { url: - 'https://server.gosling-lang.org/api/v1/tileset_info/?d=cistrome-multivec', - type: 'multivec', - row: 'sample', - column: 'position', - value: 'peak', - categories: ['sample 1', 'sample 2', 'sample 3', 'sample 4'], - binSize: 4 - }, - mark: 'rect', + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'] + }, x: { - field: 'start', + field: 'chromStart', type: 'genomic', - domain: { interval: [0, 1000000000] }, - linkingId: '7f94b74e-25ec-4e67-8c75-3dc0f1b0d6bd' - }, - xe: { field: 'end', type: 'genomic' }, - row: { - field: 'sample', - type: 'nominal', - legend: true - }, - color: { - field: 'peak', - type: 'quantitative', - legend: true + axis: 'bottom', + // domain: { chromosome: '3' }, + linkingId: 'bottom' }, - tooltip: [ + xe: { field: 'chromEnd', type: 'genomic' }, + color: { value: 'white' }, + size: { value: 14 }, + stroke: { value: 'black' }, + strokeWidth: { value: 0.5 }, + width: 1000, + height: 44, + overlay: [ { - field: 'start', - type: 'genomic', - alt: 'Start Position' + mark: 'rect', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'], + not: true + } + ], + style: { outlineWidth: 0 } }, { - field: 'end', - type: 'genomic', - alt: 'End Position' + mark: 'triangleRight', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'] + }, + { type: 'filter', field: 'Name', include: 'q' } + ], + style: { outlineWidth: 0 } }, { - field: 'peak', - type: 'quantitative', - alt: 'Value', - format: '.2' - }, - { field: 'sample', type: 'nominal', alt: 'Sample' } + mark: 'triangleLeft', + dataTransform: [ + { + type: 'filter', + field: 'Stain', + oneOf: ['acen'] + }, + { type: 'filter', field: 'Name', include: 'p' } + ], + style: { outlineWidth: 0 } + } ], - width: 130, - height: 600 + assembly: 'hg38', + layout: 'linear', + orientation: 'horizontal', + static: false, + style: { outlineWidth: 0 }, + overlayOnPreviousTrack: false }, theme: { base: 'light', @@ -963,26 +1371,329 @@ const example = { quantitativeSizeRange: [2, 6] } } + }, + data: { + url: + 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/UCSC.HG38.Human.CytoBandIdeogram.csv', + type: 'csv', + chromosomeField: 'Chromosome', + genomicFields: ['chromStart', 'chromEnd'], + assembly: 'hg38' + } + } + ] + } + ], + right: [], + bottom: [ + { + uid: 'dbf196a0-fc48-11eb-b136-51c77f658267', + type: 'axis-track', + chromInfoPath: 'https://s3.amazonaws.com/gosling-lang.org/data/hg38.chrom.sizes', + options: { + layout: 'linear', + outerRadius: null, + width: 400, + height: 44, + theme: { + base: 'light', + root: { + background: 'white', + titleColor: 'black', + titleBackgroundColor: 'transparent', + titleFontSize: 18, + titleFontFamily: 'Arial', + titleAlign: 'left', + titleFontWeight: 'bold', + subtitleColor: 'gray', + subtitleBackgroundColor: 'transparent', + subtitleFontSize: 16, + subtitleFontFamily: 'Arial', + subtitleFontWeight: 'normal', + subtitleAlign: 'left', + mousePositionColor: '#000000' + }, + track: { + background: 'transparent', + alternatingBackground: 'transparent', + titleColor: 'black', + titleBackground: 'white', + titleFontSize: 24, + titleAlign: 'left', + outline: 'black', + outlineWidth: 1 + }, + legend: { + position: 'top', + background: 'white', + backgroundOpacity: 0.7, + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + backgroundStroke: '#DBDBDB', + tickColor: 'black' + }, + axis: { + tickColor: 'black', + labelColor: 'black', + labelFontSize: 12, + labelFontWeight: 'normal', + labelFontFamily: 'Arial', + baselineColor: 'black', + gridColor: '#E3E3E3', + gridStrokeWidth: 1, + gridStrokeType: 'solid', + gridStrokeDash: [4, 4] + }, + markCommon: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + point: { + color: '#E79F00', + size: 3, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rect: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + triangle: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + area: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + line: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + bar: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + rule: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + link: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] + }, + text: { + color: '#E79F00', + size: 1, + stroke: 'black', + strokeWidth: 0, + opacity: 1, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6], + textAnchor: 'middle', + textFontWeight: 'normal' + }, + brush: { + color: 'gray', + size: 1, + stroke: 'black', + strokeWidth: 1, + opacity: 0.3, + nominalColorRange: [ + '#E79F00', + '#029F73', + '#0072B2', + '#CB7AA7', + '#D45E00', + '#57B4E9', + '#EFE441' + ], + quantitativeSizeRange: [2, 6] } - } - ] + }, + assembly: 'hg38', + stroke: 'transparent', + color: 'black', + fontSize: 12, + fontFamily: 'Arial', + fontWeight: 'normal', + tickColor: 'black', + tickFormat: 'plain', + tickPositions: 'even', + reverseOrientation: true + }, + height: 30 } ], - center: [], - right: [], - bottom: [], gallery: [], whole: [] }, - initialXDomain: [0, 1000000000], - initialYDomain: [0, 1000000000], + initialXDomain: [0, 3088269832], + initialYDomain: [0, 3088269832], zoomFixed: false, zoomLimits: [1, null], - uid: '154b1b40-f6fb-11eb-914b-976b6ecda162' + uid: 'view-3' } ], - zoomLocks: { locksByViewUid: {}, locksDict: {} }, - locationLocks: { locksByViewUid: {}, locksDict: {} }, + zoomLocks: { + locksByViewUid: { + 'view-1': 'all', + 'view-2': 'all', + 'view-3': 'all' + }, + locksDict: { + all: { + uid: 'all', + 'view-1': [124625310.5, 124625310.5, 249250.621], + 'view-2': [124625310.5, 124625310.5, 249250.621], + 'view-3': [124625310.5, 124625310.5, 249250.621] + } + } + }, + locationLocks: { + locksByViewUid: { + 'view-1': { x: { lock: 'top', axis: 'x' } }, + 'view-2': { x: { lock: 'top', axis: 'x' }, y: { lock: 'bottom', axis: 'x' } }, + 'view-3': { x: { lock: 'bottom', axis: 'y' } } + }, + locksDict: { + top: { + uid: 'top', + 'view-1': [124625310.5, 124625310.5, 249250.621], + 'view-2': [124625310.5, 124625310.5, 249250.621] + }, + bottom: { + uid: 'bottom', + 'view-2': [124625310.5, 124625310.5, 249250.621], + 'view-3': [124625310.5, 124625310.5, 249250.621] + } + } + }, editable: false, chromInfoPath: 'https://s3.amazonaws.com/gosling-lang.org/data/hg38.chrom.sizes' }; diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index b4bc8e9e8..b12353b75 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -29,7 +29,7 @@ export const GoslingComponent = forwardRef<{ api: GoslingApi }, GoslingCompProps // HiGlass API const hgRef = useRef(); - const theme = getTheme(props.theme || 'light'); + const theme = getTheme(props.theme); // Gosling APIs useEffect(() => { @@ -40,7 +40,7 @@ export const GoslingComponent = forwardRef<{ api: GoslingApi }, GoslingCompProps } else { ref.current = { api }; } - }, [ref, hgRef, viewConfig, theme]); + }, [ref, hgRef, viewConfig, props.theme]); useEffect(() => { if (props.spec) { @@ -74,7 +74,7 @@ export const GoslingComponent = forwardRef<{ api: GoslingApi }, GoslingCompProps theme ); } - }, [props.spec, theme]); + }, [props.spec, props.theme]); // HiGlass component should be mounted only once const higlassComponent = useMemo( @@ -93,7 +93,7 @@ export const GoslingComponent = forwardRef<{ api: GoslingApi }, GoslingCompProps }} /> ), - [viewConfig, size, theme] + [viewConfig, size, props.theme] ); return higlassComponent; diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 94e2b33b4..ffd118eeb 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -70,7 +70,11 @@ export function goslingToHiGlass( height, options: { /* Mouse hover position */ - showMousePosition: firstResolvedSpec.layout === 'circular' ? false : theme.root.showMousePosition, // show mouse position only for linear tracks // TODO: or vertical + showMousePosition: + firstResolvedSpec.layout === 'circular' || + (Is2DTrack(firstResolvedSpec) && firstResolvedSpec.mark === 'betweenLink') + ? false + : theme.root.showMousePosition, // show mouse position only for linear tracks // TODO: or vertical mousePositionColor: theme.root.mousePositionColor, /* Track title */ name: firstResolvedSpec.layout === 'linear' ? firstResolvedSpec.title : ' ', @@ -130,22 +134,20 @@ export function goslingToHiGlass( hgTrack.options.colorbarPosition = (firstResolvedSpec.color as any)?.legend ? 'topRight' : 'hidden'; } - if (firstResolvedSpec.overlayOnPreviousTrack) { - hgModel - .setViewOrientation(firstResolvedSpec.orientation) // TODO: Orientation should be assigned to 'individual' views - .addTrackToCombined(hgTrack); - } else { - hgModel - .setViewOrientation(firstResolvedSpec.orientation) // TODO: Orientation should be assigned to 'individual' views - .setAssembly(assembly) // TODO: Assembly should be assigned to 'individual' views - .addDefaultView(firstResolvedSpec.id ? `${firstResolvedSpec.id}-view` : uuid.v1(), assembly) - .setDomain(xDomain, Is2DTrack(firstResolvedSpec) ? yDomain : xDomain) - .adjustDomain(firstResolvedSpec.orientation, width, height) - .setMainTrack(hgTrack) - .addTrackSourceServers(server) - .setZoomFixed(firstResolvedSpec.static === true) - .setLayout(layout); - } + hgModel + .setViewOrientation(firstResolvedSpec.orientation) // TODO: Orientation should be assigned to 'individual' views + .setAssembly(assembly) // TODO: Assembly should be assigned to 'individual' views + .addDefaultView(firstResolvedSpec.id ? `${firstResolvedSpec.id}-view` : uuid.v1(), assembly) + .setDomain(xDomain, Is2DTrack(firstResolvedSpec) ? yDomain : xDomain) + .adjustDomain(firstResolvedSpec.orientation, width, height) + .setMainTrack(hgTrack) + .addTrackSourceServers(server) + .setZoomFixed( + Is2DTrack(firstResolvedSpec) && firstResolvedSpec.mark === 'betweenLink' + ? true + : firstResolvedSpec.static === true + ) + .setLayout(layout); // determine the compactness type of an axis considering the size of a track const getAxisNarrowType = ( @@ -239,8 +241,5 @@ export function goslingToHiGlass( } } - // Uncomment the following code to test with specific HiGlass viewConfig - // hgModel.setExampleHiglassViewConfig(); - return hgModel; } diff --git a/src/core/gosling-track-model.ts b/src/core/gosling-track-model.ts index 444329f16..5f7f57974 100644 --- a/src/core/gosling-track-model.ts +++ b/src/core/gosling-track-model.ts @@ -819,7 +819,14 @@ export class GoslingTrackModel { /** * Set a new scale for a certain channel. */ - public setChannelScale(channelKey: keyof typeof ChannelTypes, scale: ScaleType) { + public setChannelScale( + channelKey: keyof typeof ChannelTypes, + scale: ScaleType, + config?: { range: [number, number] } + ) { + if (config?.range) { + (scale as any).range(config.range); + } this.channelScales[channelKey] = scale; } diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index 2028fcbe7..22df6fcdc 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -128,12 +128,38 @@ export function IsTemplateTrack(track: Partial): track is TemplateTrack { * Is this 2D track, i.e., two genomic axes? */ export function Is2DTrack(track: Track) { + // TODO: For the readability, use if-else statements. return ( IsSingleTrack(track) && + ((IsChannelDeep(track['x']) && + track['x'].type === 'genomic' && + IsChannelDeep(track['y']) && + track['y'].type === 'genomic') || + (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)) + ); +} + +/** + * 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['y']) && - track['y'].type === 'genomic' + IsChannelDeep(track['x1']) && + track['x1'].type === 'genomic' && + track['x'].linkingId && + track['x1'].linkingId && + track['x'].linkingId !== track['x1'].linkingId ); } diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 19f988773..e9d0a17b8 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -60,9 +60,13 @@ export interface CommonViewDef { assembly?: Assembly; - // TODO: Change to domain? xDomain?: DomainInterval | DomainChrInterval | DomainChr; - linkingId?: string; + yDomain?: DomainInterval | DomainChrInterval | DomainChr; + + xLinkingId?: string; + x1LinkingId?: string; // TODO: + yLinkingId?: string; // TODO: + xAxis?: AxisPosition; // not supported currently /** @@ -102,6 +106,7 @@ export interface CommonTrackDef extends CommonViewDef, CommonRequiredTrackDef { // Internally used properties _renderingId?: string; _invalidTrack?: boolean; // flag to ignore rendering certain tracks if they have problems // !!! TODO: add tests + _parentViewId?: string; // To test upcoming feature. prerelease?: { testUsingNewRectRenderingForBAM?: boolean }; @@ -389,6 +394,7 @@ export interface CSVData { genomicFieldsToConvert?: { chromosomeField: string; genomicFields: string[]; + assembly?: Assembly; }[]; } diff --git a/src/core/higlass-component-wrapper.tsx b/src/core/higlass-component-wrapper.tsx index 004924baf..025a2f4a0 100644 --- a/src/core/higlass-component-wrapper.tsx +++ b/src/core/higlass-component-wrapper.tsx @@ -58,7 +58,8 @@ export const HiGlassComponentWrapper = forwardRef { const { track, boundingBox: bb, layout } = tb; goslingToHiGlass(hgModel, track, bb, layout, theme); @@ -29,8 +28,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 => { @@ -38,35 +36,105 @@ export function renderHiGlass( info.layout, info.viewId, theme, + // TODO: perhaps need some changes linkingInfos.find(d => !d.isBrush && d.linkId === info.linkId)?.viewId, info.style ); }); - // 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]; + }); + }); - uniqueLinkIds.forEach(linkId => { - hgModel.spec().zoomLocks.locksDict[linkId] = { uid: linkId }; - hgModel.spec().locationLocks.locksDict[linkId] = { uid: linkId }; + /* + * Linking locations between views + */ + // Set `locksByViewUid` + const AXIS_NOT_SET = `axis-not-set-${uuid.v4()}`; + 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: AXIS_NOT_SET // source axis that should be set in the following codes + }; + }); + + // set source `axis` + const { locksByViewUid } = hgModel.spec().locationLocks; + Object.keys(locksByViewUid).forEach(targetId => { + Object.keys(locksByViewUid[targetId]).forEach(targetChannel => { + const lockId = locksByViewUid[targetId][targetChannel].lock; + // Find a track of a view that has the identical lock id and belongs to another view + Object.keys(locksByViewUid) + .filter( + id => + targetId !== + id /* && linkingInfos.find(d => d.viewId === targetId)?.parentViewId !== linkingInfos.find(d => d.viewId === id)?.parentViewId */ + ) + .forEach(sourceId => { + Object.keys(locksByViewUid[sourceId]).forEach(sourceChannel => { + if (locksByViewUid[sourceId][sourceChannel].lock === lockId) { + locksByViewUid[targetId][targetChannel].axis = sourceChannel; + locksByViewUid[sourceId][sourceChannel].axis = targetChannel; + } + }); + }); + }); + }); + + // Remove locks that do not have proper source axis + Object.keys(locksByViewUid).forEach(viewId => { + Object.keys(locksByViewUid[viewId]).forEach(channel => { + if (locksByViewUid[viewId][channel].axis === AXIS_NOT_SET) { + console.warn(`${channel} axis of a view (${viewId}) does not have a target axis to link with.`); + delete locksByViewUid[viewId][channel]; + } + }); + if (Object.keys(locksByViewUid[viewId]).length === 0) { + // we removed all channels, so remove their parent as well + console.warn(`A view (${viewId}) does not have a target view to link with.`); + delete locksByViewUid[viewId]; + } + }); + + // 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(); + setHg(hgModel.spec(), getBoundingBox(trackInfos)); } diff --git a/src/core/mark/index.ts b/src/core/mark/index.ts index 86528b43a..7d0f5f423 100644 --- a/src/core/mark/index.ts +++ b/src/core/mark/index.ts @@ -61,11 +61,41 @@ export function drawMark(HGC: any, trackInfo: any, tile: any, model: GoslingTrac // console.log(trackInfo._xScale.domain(), trackInfo._yScale.domain(), (model.getChannelScale('x') as any)?.domain(), (model.getChannelScale('y') as any)?.domain()); + // Size of a track + const [trackWidth, trackHeight] = trackInfo.dimensions; + // Replace the scale of a genomic axis with the one that is generated by the HiGlass data fetcher. - ['x', 'x1', 'x1e', 'xe'].forEach((d: any) => { + ['x', 'xe', 'x1', 'x1e'].forEach((d: any) => { // const c = tm.spec()[d as keyof typeof ChannelTypes]; // if(IsChannelDeep(c) && c.type === 'genomic') { - model.setChannelScale(d, trackInfo._xScale); + if (Is2DTrack(model.spec()) && model.spec().mark === 'betweenLink' && (d === 'x1' || d === 'x1e')) { + const xRange = trackInfo._xScale.range(); + const xRangeSize = Math.abs(xRange[1] - xRange[0]); + const yRange = trackInfo._yScale.range(); + const yRangeSize = Math.abs(yRange[1] - yRange[0]); + + const factor = xRangeSize / yRangeSize; + + const [yDomainMin, yDomainMax] = trackInfo._yScale.domain(); + const yDomainSize = Math.abs(yDomainMax - yDomainMin); + const yDomainCenter = yDomainMin + yDomainSize / 2.0; + const newYDOmainSize = yDomainSize * factor; + + const useDefinedXRange = (model.spec() as any)?.[d]?.range; + const adjustedYScale = trackInfo._yScale + .copy() + .domain([yDomainCenter - newYDOmainSize / 2.0, yDomainCenter + newYDOmainSize / 2.0]) + .range(useDefinedXRange ?? xRange); + model.setChannelScale(d, adjustedYScale); + } else { + const range = (model.spec() as any)?.[d]?.range; // model.getChannelRangeArray(d); + const updatedScale = trackInfo._xScale.copy(); + if (range) { + // If specified, use it! + updatedScale.range(range); + } + model.setChannelScale(d, updatedScale); + } // } }); @@ -79,9 +109,6 @@ export function drawMark(HGC: any, trackInfo: any, tile: any, model: GoslingTrac }); } - // Size of a track - const [trackWidth, trackHeight] = trackInfo.dimensions; - // DEBUG // drawChartOutlines(HGC, trackInfo, model); // diff --git a/src/core/mark/link.ts b/src/core/mark/link.ts index e471f54ea..b81e798f5 100644 --- a/src/core/mark/link.ts +++ b/src/core/mark/link.ts @@ -107,11 +107,16 @@ export function drawLink(g: PIXI.Graphics, trackInfo: any, model: GoslingTrackMo [_x1, _x2, _x3, _x4] = [_x1, _x2, _x3, _x4].sort((a, b) => a - b); } - if (_x1 > trackWidth || _x4 < 0 || Math.abs(_x4 - _x1) < 0.5) { + if (Math.abs(_x4 - _x1) < 0.5) { // Do not draw very small visual marks return; } + if (_x1 > trackWidth && _x4 < 0) { + // Do not draw if both target and source of bands are outside the visible area + return; + } + if (circular) { if (_x1 < 0 || _x4 > trackWidth) { // Do not show bands that are partly outside of the current domain diff --git a/src/core/utils/bounding-box.test.ts b/src/core/utils/bounding-box.test.ts index 3691b9085..89e4b15f7 100644 --- a/src/core/utils/bounding-box.test.ts +++ b/src/core/utils/bounding-box.test.ts @@ -277,7 +277,7 @@ describe('Arrangement', () => { tracks: [t] }, { - linkingId: '_', + xLinkingId: '_', tracks: [t] } ] diff --git a/src/core/utils/linking.ts b/src/core/utils/linking.ts index e56604fd5..265df903b 100644 --- a/src/core/utils/linking.ts +++ b/src/core/utils/linking.ts @@ -1,25 +1,33 @@ +import uuid from 'uuid'; +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; + parentViewId: 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 +41,24 @@ 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) { linkingInfo.push({ layout: spec.layout === 'circular' ? 'circular' : 'linear', + orientation: spec.orientation ?? 'horizontal', + channel: cKey, viewId, + parentViewId: spec._parentViewId ?? uuid.v4(), 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 +70,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/scales.ts b/src/core/utils/scales.ts index c55290d9c..61abc16f6 100644 --- a/src/core/utils/scales.ts +++ b/src/core/utils/scales.ts @@ -48,7 +48,7 @@ export function getNumericDomain(domain: Domain, assembly?: Assembly) { export function shareScaleAcrossTracks(trackModels: GoslingTrackModel[], force?: boolean) { // we update the spec with a global domain const globalDomain: { [k: string]: number[] | string[] } = {}; - const channelKeys = SUPPORTED_CHANNELS; + const channelKeys = SUPPORTED_CHANNELS.filter(c => !c.includes('x')); // generate global domains trackModels.forEach(model => { diff --git a/src/core/utils/spec-preprocess.ts b/src/core/utils/spec-preprocess.ts index 544cc406d..515e5cd7f 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, @@ -137,7 +138,8 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if (spec.orientation === undefined) spec.orientation = parentDef.orientation; if (spec.static === undefined) spec.static = parentDef.static !== undefined ? parentDef.static : false; if (spec.xDomain === undefined) spec.xDomain = parentDef.xDomain; - if (spec.linkingId === undefined) spec.linkingId = parentDef.linkingId; + if (spec.yDomain === undefined) spec.yDomain = parentDef.yDomain; + if (spec.xLinkingId === undefined) spec.xLinkingId = parentDef.xLinkingId; if (spec.centerRadius === undefined) spec.centerRadius = parentDef.centerRadius; if (spec.spacing === undefined && !('tracks' in spec)) spec.spacing = parentDef.spacing; if (spec.xOffset === undefined) spec.xOffset = parentDef.xOffset; @@ -157,6 +159,7 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if (spec.xOffset === undefined) spec.xOffset = 0; if (spec.yOffset === undefined) spec.yOffset = 0; // Nothing to do when `xDomain` not suggested + // Nothing to do when `yDomain` not suggested // Nothing to do when `xLinkID` not suggested } @@ -171,11 +174,15 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare tracks = spreadTracksByData(tracks); const linkID = uuid.v4(); + const parentViewId = uuid.v4(); tracks.forEach((track, i, array) => { // If size not defined, set default ones if (!track.width) track.width = DEFAULT_TRACK_WIDTH_LINEAR; if (!track.height) track.height = DEFAULT_TRACK_HEIGHT_LINEAR; + // Set the parent view ID so that we can use this information when linking views + track._parentViewId = parentViewId; + /** * Process a stack option. */ @@ -242,7 +249,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'; } @@ -260,11 +267,24 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare }); } + /** + * Add y-axis domain + */ + if ((IsSingleTrack(track) || IsOverlaidTrack(track)) && IsChannelDeep(track.y) && !track.y.domain) { + track.y.domain = spec.yDomain; + } else if (IsOverlaidTrack(track)) { + track.overlay.forEach(o => { + if (IsChannelDeep(o.y) && !o.y.domain) { + o.y.domain = spec.yDomain; + } + }); + } + /** * Link tracks in a single view */ if ((IsSingleTrack(track) || IsOverlaidTrack(track)) && IsChannelDeep(track.x) && !track.x.linkingId) { - track.x.linkingId = spec.linkingId ?? linkID; + track.x.linkingId = spec.xLinkingId ?? linkID; } else if (IsOverlaidTrack(track)) { let isAdded = false; track.overlay.forEach(o => { @@ -272,7 +292,7 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if (IsChannelDeep(o.x) && !o.x.linkingId) { // TODO: Is this safe? - o.x.linkingId = spec.linkingId ?? linkID; + o.x.linkingId = spec.xLinkingId ?? linkID; isAdded = true; } }); diff --git a/src/core/utils/template.ts b/src/core/utils/template.ts index 1100d3cdc..159012db6 100644 --- a/src/core/utils/template.ts +++ b/src/core/utils/template.ts @@ -149,7 +149,7 @@ export const GoslingTemplates: TemplateTrackDef[] = [ domain: ['gneg', 'gpos25', 'gpos50', 'gpos75', 'gpos100', 'gvar', 'acen'], range: ['white', 'lightgray', 'gray', 'gray', 'black', '#7B9CC8', '#DC4542'] }, - size: { base: 'chrHeight', value: 18 }, + size: { base: 'chrHeight' }, x: { base: 'startPosition', type: 'genomic' }, xe: { base: 'endPosition', type: 'genomic' }, stroke: { base: 'stainStroke', value: 'gray' }, @@ -167,7 +167,7 @@ export const GoslingTemplates: TemplateTrackDef[] = [ domain: ['gneg', 'gpos25', 'gpos50', 'gpos75', 'gpos100', 'gvar', 'acen'], range: ['white', 'lightgray', 'gray', 'gray', 'black', '#7B9CC8', '#DC4542'] }, - size: { base: 'chrHeight', value: 18 }, + size: { base: 'chrHeight' }, x: { base: 'startPosition', type: 'genomic' }, xe: { base: 'endPosition', type: 'genomic' }, stroke: { base: 'stainStroke', value: 'gray' }, @@ -185,7 +185,7 @@ export const GoslingTemplates: TemplateTrackDef[] = [ domain: ['gneg', 'gpos25', 'gpos50', 'gpos75', 'gpos100', 'gvar', 'acen'], range: ['white', 'lightgray', 'gray', 'gray', 'black', '#7B9CC8', '#DC4542'] }, - size: { base: 'chrHeight', value: 18 }, + size: { base: 'chrHeight' }, x: { base: 'startPosition', type: 'genomic' }, xe: { base: 'endPosition', type: 'genomic' }, stroke: { base: 'stainStroke', value: 'gray' }, diff --git a/src/core/utils/theme.ts b/src/core/utils/theme.ts index 23dc716b3..2134c9312 100644 --- a/src/core/utils/theme.ts +++ b/src/core/utils/theme.ts @@ -136,7 +136,7 @@ export interface MarkStyle { export function getTheme(theme: Theme = 'light'): Required { if (typeof theme === 'string') { if (gt.isThereTheme(theme)) { - return gt.getTheme(theme); + return JSON.parse(JSON.stringify(gt.getTheme(theme))); } else if (theme === 'dark' || theme === 'light') { return THEMES[theme]; } else { @@ -146,7 +146,7 @@ export function getTheme(theme: Theme = 'light'): Required { // Iterate all keys to override from base let baseSpec = JSON.parse(JSON.stringify(THEMES['light'])); if (gt.isThereTheme(theme.base)) { - baseSpec = gt.getTheme(theme.base); + baseSpec = JSON.parse(JSON.stringify(gt.getTheme(theme.base))); } else if (theme.base === 'light' || theme.base === 'dark') { baseSpec = JSON.parse(JSON.stringify(THEMES[theme.base])); } diff --git a/src/data-fetcher/csv/higlass-csv-datafetcher.ts b/src/data-fetcher/csv/higlass-csv-datafetcher.ts index f5c581d2f..4e9dcc897 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. @@ -245,31 +246,35 @@ function CSVDataFetcher(HGC: any, ...args: any): any { tile(z: any, x: any, y: any) { return this.tilesetInfo()?.then((tsInfo: any) => { + const sizeLimit = this.dataConfig.sampleLength ?? 1000; const tileWidth = +tsInfo.max_width / 2 ** +z; // get the bounds of the tile const minX = tsInfo.min_pos[0] + x * tileWidth; const maxX = tsInfo.min_pos[0] + (x + 1) * tileWidth; - // filter the data so that only the visible data is sent to tracks - let tabularData = this.values.filter((d: any) => { - if (this.dataConfig.genomicFields) { - return this.dataConfig.genomicFields.find((g: any) => minX < d[g] && d[g] <= maxX); - } else { - const allGenomicFields: string[] = []; - this.dataConfig.genomicFieldsToConvert.forEach((d: any) => - allGenomicFields.push(...d.genomicFields) - ); - return allGenomicFields.find((g: any) => minX < d[g] && d[g] <= maxX); - } - }); + let tabularData = this.values; // filter data based on the `DataTransform` spec this.filter?.forEach(f => { tabularData = filterData(f, tabularData); }); - const sizeLimit = this.dataConfig.sampleLength ?? 1000; + // filter the data so that only the visible data is sent to tracks + if (tabularData.length > sizeLimit) { + tabularData = tabularData.filter((d: any) => { + if (this.dataConfig.genomicFields) { + return this.dataConfig.genomicFields.find((g: any) => minX < d[g] && d[g] <= maxX); + } else { + const allGenomicFields: string[] = []; + this.dataConfig.genomicFieldsToConvert.forEach((d: any) => + allGenomicFields.push(...d.genomicFields) + ); + return allGenomicFields.find((g: any) => minX < d[g] && d[g] <= maxX); + } + }); + } + return { // sample the data to make it managable for visualization components tabularData: tabularData.length > sizeLimit ? sampleSize(tabularData, sizeLimit) : tabularData, diff --git a/src/editor/editor.tsx b/src/editor/editor.tsx index 6d601d6b6..eda568903 100644 --- a/src/editor/editor.tsx +++ b/src/editor/editor.tsx @@ -24,6 +24,7 @@ import './editor.css'; import { ICONS, ICON_INFO } from './icon'; // @ts-ignore import { Themes } from 'gosling-theme'; +import { Theme } from '../core/utils/theme'; const INIT_DEMO_INDEX = examples.findIndex(d => d.forceShow) !== -1 ? examples.findIndex(d => d.forceShow) : 0; @@ -147,7 +148,7 @@ function Editor(props: any) { const [refreshData, setRefreshData] = useState(false); const [demo, setDemo] = useState(examples[urlExampleIndex === -1 ? INIT_DEMO_INDEX : urlExampleIndex]); - const [theme, setTheme] = useState('light'); + const [theme, setTheme] = useState('light'); const [hg, setHg] = useState(); const [code, setCode] = useState(defaultCode); const [goslingSpec, setGoslingSpec] = useState(); @@ -232,6 +233,7 @@ function Editor(props: any) { setSelectedPreviewData(0); setCode(urlSpec ?? (urlGist ? emptySpec() : stringify(demo.spec as GoslingSpec))); setHg(undefined); + setTheme(demo.theme ?? 'light'); }, [demo]); useEffect(() => { @@ -462,7 +464,7 @@ function Editor(props: any) { setTheme(e.target.value); } }} - defaultValue={theme} + defaultValue={'light'} > {Object.keys(Themes).map((d: string) => (