diff --git a/demo/App.jsx b/demo/App.jsx
index 5b37077..4d0483f 100644
--- a/demo/App.jsx
+++ b/demo/App.jsx
@@ -1,293 +1,32 @@
-import React from 'react';
-import {GoslingMetaComponent} from '../src/index.ts';
+import * as React from 'react';
+import { Routes, Route, Link } from 'react-router-dom';
+import IslandViewer from './IslandViewer';
+import 'higlass/dist/hglib.css';
+import GeneSpy from "./GeneSpy";
-const islandData = {
- data: {
- type: 'csv',
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_islands.csv',
- chromosomeField: 'Accession',
- genomicFields: ['Island start', 'Island end']
- },
- x: {field: 'Island start', type: 'genomic'},
- xe: {field: 'Island end', type: 'genomic'}
-};
-const detailID = 'detailedView';
-const circularRadius = 200;
-const centerRadius = 0.5;
-
-const linearHeight = 120;
-const linearSize = linearHeight / 6;
-
-const goslingSpec = {
- title: 'IslandViewer 4 (Bertelli et al. 2017)',
- subtitle: 'Salmonella enterica subsp. enterica serovar Typhi Ty2, complete genome.',
- description: 'Reimplementation of https://www.pathogenomics.sfu.ca/islandviewer/accession/NC_004631.1/',
- assembly: [['NC_004631.1', 4791961]],
- spacing: 50,
- views: [
- {
- layout: 'circular',
- static: true,
- alignment: 'overlay',
- spacing: 0.1,
- tracks: [
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_GCcontent.csv',
- type: 'csv',
- separator: '\t',
- genomicFields: ['Position']
- },
- y: {field: 'GCcontent', type: 'quantitative', range: [-250, 0], axis: 'none'},
- mark: 'line',
- size: {value: 0.5},
- x: {field: 'Position', type: 'genomic'},
- color: {
- value: 'black'
- }
- },
- {
- experimental: {mouseEvents: true},
- style: {outlineWidth: 1, outline: 'black'},
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_annotations.csv',
- type: 'csv',
- genomicFields: ['Gene start']
- },
- dataTransform: [
- {
- type: 'displace',
- method: 'pile',
- boundingBox: {
- padding: 3.5,
- startField: 'Gene start',
- endField: 'Gene start'
- },
- newField: 'row'
- }
- ],
- y: {field: 'row', type: 'nominal', flip: true},
- mark: 'point',
- x: {field: 'Gene start', type: 'genomic'},
- size: {value: 3},
- color: {
- field: 'Type',
- type: 'nominal',
- domain: ['Victors', 'BLAST', 'RGI', 'PAG'],
- range: ['#460B80', '#A684EA', '#FF9CC1', '#FF9CC1']
- }
- },
- {
- ...islandData,
- row: {
- field: 'Method',
- domain: [
- 'Predicted by at least one method',
- 'IslandPath-DIMOB',
- 'SIGI-HMM',
- 'IslandPick',
- 'Islander'
- ],
- type: 'nominal'
- },
- color: {
- field: 'Method',
- type: 'nominal',
- domain: [
- 'Predicted by at least one method',
- 'IslandPath-DIMOB',
- 'SIGI-HMM',
- 'IslandPick',
- 'Islander'
- ],
- range: ['#B22222', '#4169E1', '#FF8C00', '#008001', '#40E0D0']
- },
- mark: 'rect'
- },
- {
- mark: 'brush',
- x: {linkingId: 'detail'}
- }
- ],
- width: circularRadius * 2,
- centerRadius: centerRadius
- },
- {
- layout: 'linear',
- xDomain: {chromosome: 'NC_004631.1', interval: [1000000, 1500000]},
- linkingId: 'detail',
- alignment: 'overlay',
- tracks: [
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
- type: 'csv',
- chromosomeField: 'Accession',
- genomicFields: ['Gene start', 'Gene end']
- },
- id: detailID,
- x: {field: 'Gene start', type: 'genomic'},
- xe: {field: 'Gene end', type: 'genomic'},
- y: {value: 5.5 * linearSize},
- size: {value: linearSize},
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['1']}],
- color: {value: '#E9967A'},
- tooltip: [{field: 'Gene name', type: 'nominal', alt: 'Name'}]
- },
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
- type: 'csv',
- chromosomeField: 'Accession',
- genomicFields: ['Gene start', 'Gene end']
- },
- x: {field: 'Gene start', type: 'genomic'},
- xe: {field: 'Gene end', type: 'genomic'},
- y: {value: 4.5 * linearSize},
- size: {value: linearSize},
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['-1']}],
- color: {value: '#87976E'},
- tooltip: [{field: 'Gene name', type: 'nominal', alt: 'Name'}]
- },
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
- type: 'csv',
- chromosomeField: 'Accession',
- genomicFields: ['Gene start', 'Gene end']
- },
- x: {field: 'Gene start', type: 'genomic'},
- xe: {field: 'Gene end', type: 'genomic'},
- y: {value: 5.5 * linearSize},
- mark: 'text',
- text: {field: 'Gene name', type: 'nominal'},
- dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['1']}],
- color: {value: '#ffffff'},
- visibility: [
- {
- operation: 'less-than',
- measure: 'width',
- threshold: '|xe-x|',
- transitionPadding: 10,
- target: 'mark'
- }
- ]
- },
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
- type: 'csv',
- chromosomeField: 'Accession',
- genomicFields: ['Gene start', 'Gene end']
- },
- x: {field: 'Gene start', type: 'genomic'},
- xe: {field: 'Gene end', type: 'genomic'},
- y: {value: 4.5 * linearSize},
- mark: 'text',
- text: {field: 'Gene name', type: 'nominal'},
- dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['-1']}],
- color: {value: '#ffffff'},
- visibility: [
- {
- operation: 'less-than',
- measure: 'width',
- threshold: '|xe-x|',
- transitionPadding: 10,
- target: 'mark'
- }
- ]
- },
- {
- ...islandData,
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Method', oneOf: ['IslandPath-DIMOB']}],
- y: {value: 0.5 * linearSize},
- size: {value: linearSize},
- color: {value: '#4169E1'}
- },
- {
- ...islandData,
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Method', oneOf: ['SIGI-HMM']}],
- y: {value: 1.5 * linearSize},
- size: {value: linearSize},
- color: {value: '#FF8C00'}
- },
- {
- ...islandData,
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Method', oneOf: ['IslandPick']}],
- y: {value: 2.5 * linearSize},
- size: {value: linearSize},
- color: {value: '#008001'}
- },
- {
- ...islandData,
- mark: 'rect',
- dataTransform: [{type: 'filter', field: 'Method', oneOf: ['Islander']}],
- y: {value: 3.5 * linearSize},
- size: {value: linearSize},
- color: {value: '#40E0D0'}
- },
- {
- data: {
- url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_annotations.csv',
- type: 'csv',
- genomicFields: ['Gene start']
- },
- dataTransform: [
- {
- type: 'displace',
- method: 'pile',
- boundingBox: {
- padding: 3.5,
- startField: 'Gene start',
- endField: 'Gene start'
- },
- newField: 'row'
- }
- ],
- row: {field: 'row', type: 'nominal'},
- mark: 'point',
- x: {field: 'Gene start', type: 'genomic'},
- size: {value: 3},
- color: {
- field: 'Type',
- type: 'nominal',
- domain: ['Victors', 'BLAST', 'RGI', 'PAG'],
- range: ['#460B80', '#A684EA', '#FF9CC1', '#FF9CC1']
- },
- tooltip: [{field: 'Type', type: 'nominal', alt: 'Name'}]
- }
- ],
- width: circularRadius * 2,
- height: linearHeight
- }
- ]
-};
-const metaSpec = {
- type: 'table',
- dataTransform: [
- {
- type: "merge",
- fields: ["Islands", "Annotations"],
- mergeChar: "/",
- newField: "Prediction Method",
- }
- ],
- width: 600,
- genomicColumns: ['Gene start', 'Gene end'],
- columns: ['Prediction Method', 'Gene name', 'Accnum', 'Product']
+// The full list of examples
+const examples = {
+ IslandViewer: ,
+ GeneSpy: ,
}
-export default function App() {
- return (
-
- );
+function App() {
+ return (
+
+
+
Examples
+
+ {Object.entries(examples).map(entry => - {entry[0]}
)}
+
+
+
+
+
+ {Object.entries(examples).map(entry => )}
+
+
+
+ );
}
+
+export default App;
\ No newline at end of file
diff --git a/demo/GeneSpy.jsx b/demo/GeneSpy.jsx
new file mode 100644
index 0000000..a6c97c5
--- /dev/null
+++ b/demo/GeneSpy.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import {GoslingMetaComponent} from '../src/index.ts';
+
+const gosId = 'gosID';
+const goslingSpec = {
+ title: 'GeneSpy + iTol',
+ subtitle: 'Genomic neighborhood visualization with aligned phylogenetic tree',
+ description: 'Idea: https://doi.org/10.1093/bioinformatics/bty459',
+ assembly: [['', 11000]],
+ arrangement: 'horizontal',
+ spacing:0,
+ static: true,
+ views: [
+ {
+ tracks: [
+ {
+ type: "dummy-track",
+ id: "tree",
+ }
+ ],
+ width: 350,
+ // TODO: see issue #980
+ height: 330
+ },
+ {
+ alignment: 'overlay',
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/GeneSpy/gene_spy_example.csv',
+ type: 'csv',
+ genomicFields: ['Gene start', 'Gene end']
+ },
+ color: {
+ field: 'type',
+ type: 'nominal',
+ domain: ['anchor', 'conserved', 'disrupted'],
+ range: ['red', 'gray', 'yellow']
+ },
+ id: gosId,
+ row: {
+ field: 'Accession',
+ type: 'nominal',
+ },
+ tracks: [
+ {
+ dataTransform: [
+ {type: 'filter', field: 'Strand', oneOf: ['+']}
+ ],
+ mark: 'triangleRight',
+ style: {align: 'right'},
+ x: {field: 'Gene start', type: 'genomic', axis: 'none'},
+ xe: {field: 'Gene end', type: 'genomic'}
+ },
+ {
+ dataTransform: [
+ {type: 'filter', field: 'Strand', oneOf: ['-']}
+ ],
+ mark: 'triangleLeft',
+ style: {align: 'left'},
+ x: {field: 'Gene start', type: 'genomic'},
+ xe: {field: 'Gene end', type: 'genomic'}
+ }
+ ],
+ height: 300
+ }
+ ]
+};
+const metaSpec = {
+ type: 'tree',
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/GeneSpy/gene_spy_example.nwk',
+ type: 'nwk',
+ },
+ width: 400,
+}
+
+export default function GeneSpy() {
+ return (
+
+ );
+}
diff --git a/demo/IslandViewer.jsx b/demo/IslandViewer.jsx
new file mode 100644
index 0000000..02dff47
--- /dev/null
+++ b/demo/IslandViewer.jsx
@@ -0,0 +1,268 @@
+import React from 'react';
+import {GoslingMetaComponent} from '../src/index.ts';
+
+const islandData = {
+ data: {
+ type: 'csv',
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_islands.csv',
+ chromosomeField: 'Accession',
+ genomicFields: ['Island start', 'Island end']
+ },
+ x: {field: 'Island start', type: 'genomic'},
+ xe: {field: 'Island end', type: 'genomic'}
+};
+const detailID = 'detailedView';
+const circularRadius = 200;
+const centerRadius = 0.5;
+
+const linearHeight = 120;
+const linearSize = linearHeight / 6;
+
+const goslingSpec = {
+ title: 'IslandViewer 4 (Bertelli et al. 2017)',
+ subtitle: 'Salmonella enterica subsp. enterica serovar Typhi Ty2, complete genome.',
+ description: 'Reimplementation of https://www.pathogenomics.sfu.ca/islandviewer/accession/NC_004631.1/',
+ assembly: [['NC_004631.1', 4791961]],
+ arrangement: 'horizontal',
+ spacing: 0,
+ views: [
+ {
+ tracks: [
+ {
+ type: "dummy-track",
+ id: "table",
+ }
+ ],
+ width: 600,
+ // TODO: automatically align dummy track to other tracks or find a way to get height of titles etc., see issue #980
+ height: linearHeight + circularRadius * 2 + 30
+ },
+ {
+ arrangement: 'vertical',
+ views: [
+ {
+ layout: 'circular',
+ static: true,
+ alignment: 'overlay',
+ spacing: 0.1,
+ tracks: [
+ {
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_GCcontent.csv',
+ type: 'csv',
+ separator: '\t',
+ genomicFields: ['Position']
+ },
+ y: {field: 'GCcontent', type: 'quantitative', range: [-250, 0], axis: 'none'},
+ mark: 'line',
+ size: {value: 0.5},
+ x: {field: 'Position', type: 'genomic'},
+ color: {
+ value: 'black'
+ }
+ },
+ {
+ style: {outlineWidth: 1, outline: 'black'},
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_annotations.csv',
+ type: 'csv',
+ genomicFields: ['Gene start']
+ },
+ dataTransform: [
+ {
+ type: 'displace',
+ method: 'pile',
+ boundingBox: {
+ padding: 3.5,
+ startField: 'Gene start',
+ endField: 'Gene start'
+ },
+ newField: 'row'
+ }
+ ],
+ y: {field: 'row', type: 'nominal', flip: true},
+ mark: 'point',
+ x: {field: 'Gene start', type: 'genomic'},
+ size: {value: 3},
+ color: {
+ field: 'Type',
+ type: 'nominal',
+ domain: ['Victors', 'BLAST', 'RGI', 'PAG'],
+ range: ['#460B80', '#A684EA', '#FF9CC1', '#FF9CC1']
+ }
+ },
+ {
+ ...islandData,
+ row: {
+ field: 'Method',
+ domain: [
+ 'Predicted by at least one method',
+ 'IslandPath-DIMOB',
+ 'SIGI-HMM',
+ 'IslandPick',
+ 'Islander'
+ ],
+ type: 'nominal'
+ },
+ color: {
+ field: 'Method',
+ type: 'nominal',
+ domain: [
+ 'Predicted by at least one method',
+ 'IslandPath-DIMOB',
+ 'SIGI-HMM',
+ 'IslandPick',
+ 'Islander'
+ ],
+ range: ['#B22222', '#4169E1', '#FF8C00', '#008001', '#40E0D0']
+ },
+ mark: 'rect'
+ },
+ {
+ mark: 'brush',
+ x: {linkingId: 'detail'}
+ }
+ ],
+ width: circularRadius * 2,
+ centerRadius: centerRadius
+ },
+ {
+ layout: 'linear',
+ xDomain: {chromosome: 'NC_004631.1', interval: [1000000, 1500000]},
+ linkingId: 'detail',
+ alignment: 'overlay',
+ tracks: [
+ {
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
+ type: 'csv',
+ chromosomeField: 'Accession',
+ genomicFields: ['Gene start', 'Gene end']
+ },
+ id: detailID,
+ x: {field: 'Gene start', type: 'genomic'},
+ xe: {field: 'Gene end', type: 'genomic'},
+ y: {value: 5.5 * linearSize},
+ size: {value: linearSize},
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['1']}],
+ color: {value: '#E9967A'},
+ tooltip: [{field: 'Gene name', type: 'nominal', alt: 'Name'}]
+ },
+ {
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_genes.csv',
+ type: 'csv',
+ chromosomeField: 'Accession',
+ genomicFields: ['Gene start', 'Gene end']
+ },
+ x: {field: 'Gene start', type: 'genomic'},
+ xe: {field: 'Gene end', type: 'genomic'},
+ y: {value: 4.5 * linearSize},
+ size: {value: linearSize},
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Strand', oneOf: ['-1']}],
+ color: {value: '#87976E'},
+ tooltip: [{field: 'Gene name', type: 'nominal', alt: 'Name'}]
+ },
+ {
+ ...islandData,
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Method', oneOf: ['IslandPath-DIMOB']}],
+ y: {value: 0.5 * linearSize},
+ size: {value: linearSize},
+ color: {value: '#4169E1'}
+ },
+ {
+ ...islandData,
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Method', oneOf: ['SIGI-HMM']}],
+ y: {value: 1.5 * linearSize},
+ size: {value: linearSize},
+ color: {value: '#FF8C00'}
+ },
+ {
+ ...islandData,
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Method', oneOf: ['IslandPick']}],
+ y: {value: 2.5 * linearSize},
+ size: {value: linearSize},
+ color: {value: '#008001'}
+ },
+ {
+ ...islandData,
+ mark: 'rect',
+ dataTransform: [{type: 'filter', field: 'Method', oneOf: ['Islander']}],
+ y: {value: 3.5 * linearSize},
+ size: {value: linearSize},
+ color: {value: '#40E0D0'}
+ },
+ {
+ data: {
+ url: 'https://s3.amazonaws.com/gosling-lang.org/data/IslandViewer/NC_004631.1_annotations.csv',
+ type: 'csv',
+ genomicFields: ['Gene start']
+ },
+ dataTransform: [
+ {
+ type: 'displace',
+ method: 'pile',
+ boundingBox: {
+ padding: 3.5,
+ startField: 'Gene start',
+ endField: 'Gene start'
+ },
+ newField: 'row'
+ }
+ ],
+ row: {field: 'row', type: 'nominal'},
+ mark: 'point',
+ x: {field: 'Gene start', type: 'genomic'},
+ size: {value: 3},
+ color: {
+ field: 'Type',
+ type: 'nominal',
+ domain: ['Victors', 'BLAST', 'RGI', 'PAG'],
+ range: ['#460B80', '#A684EA', '#FF9CC1', '#FF9CC1']
+ },
+ tooltip: [{field: 'Type', type: 'nominal', alt: 'Name'}]
+ }
+ ],
+ width: circularRadius * 2,
+ height: linearHeight
+ }
+ ]
+ }
+
+ ]
+};
+const metaSpec = {
+ type: 'table',
+ dataTransform: [
+ {
+ type: "merge",
+ fields: ["Islands", "Annotations"],
+ mergeChar: "/",
+ newField: "Prediction Method",
+ }
+ ],
+ genomicColumns: ['Gene start', 'Gene end'],
+ metadataColumns: [
+ {type: 'genomic', columnName: 'Gene start'},
+ {type: 'genomic', columnName: 'Gene end'},
+ {type: 'nominal', columnName: 'Prediction Method'},
+ {type: 'nominal', columnName: 'Gene name'},
+ {type: 'nominal', columnName: 'Accnum'},
+ {type: 'nominal', columnName: 'Product'}
+ ]
+}
+
+export default function IslandViewer() {
+ return (
+
+ );
+}
diff --git a/demo/index.jsx b/demo/index.jsx
index 07325ab..e1d00d7 100644
--- a/demo/index.jsx
+++ b/demo/index.jsx
@@ -1,5 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+import {HashRouter} from "react-router-dom";
+
const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render();
\ No newline at end of file
+root.render(
+
+);
\ No newline at end of file
diff --git a/package.json b/package.json
index 2462aa8..b235d59 100644
--- a/package.json
+++ b/package.json
@@ -16,11 +16,15 @@
"deploy": "touch dist/.nojekyll; gh-pages -d dist -t --git git"
},
"dependencies": {
- "gosling.js": "^0.9.30",
+ "gosling.js": "^0.10.3",
"higlass": "^1.12.4",
"pixi.js": "^6.3.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.11.2",
+ "react-vega": "^7.6.0",
+ "vega": "^5.25.0",
+ "vega-lite": "^5.9.3"
},
"devDependencies": {
"@types/react": "^18.2.6",
diff --git a/src/GoslingComponentWrapper.tsx b/src/GoslingComponentWrapper.tsx
new file mode 100644
index 0000000..3ed3b7a
--- /dev/null
+++ b/src/GoslingComponentWrapper.tsx
@@ -0,0 +1,50 @@
+import React, {useEffect, useRef} from 'react';
+import {GoslingComponent, GoslingRef, GoslingSpec} from "gosling.js";
+import type {Datum} from 'gosling.js/dist/src/gosling-schema';
+
+interface GoslingComponentWrapperProps {
+ type: "table" | "tree";
+ spec: GoslingSpec;
+ trackId: string;
+ placeholderId: string;
+ setData: (data: Datum[]) => void;
+ setRange: (range: [{ chromosome: string, position: number }, {
+ chromosome: string,
+ position: number
+ }]) => void;
+ setMetaDimensions: (shape: { x: number, y: number, width: number, height: number }) => void;
+}
+
+
+/**
+ * Wrapper for gosling component
+ * @param props
+ * @returns
+ */
+export default function GoslingComponentWrapper(props: GoslingComponentWrapperProps) {
+ const {type, spec, trackId, placeholderId, setData, setRange, setMetaDimensions} = props;
+ const gosRef = useRef(null)
+ useEffect(() => {
+ if (gosRef.current == null) return;
+ setMetaDimensions(gosRef.current.api.getTrack(placeholderId).shape)
+ if (type === "table") {
+ gosRef.current.api.subscribe('rawData', (type, eventData) => {
+ if (trackId === eventData.id) {
+ setData(eventData.data);
+ }
+ })
+ gosRef.current.api.subscribe('location', (type, eventData) => {
+ if (eventData.id === trackId) {
+ setRange(eventData.genomicRange);
+ }
+ });
+ return () => {
+ gosRef.current?.api.unsubscribe('location');
+ gosRef.current?.api.unsubscribe('rawData');
+
+ };
+ }
+ }, []);
+ return (
);
+}
diff --git a/src/GoslingMetaComponent.tsx b/src/GoslingMetaComponent.tsx
index 3f26485..c09d91d 100644
--- a/src/GoslingMetaComponent.tsx
+++ b/src/GoslingMetaComponent.tsx
@@ -1,17 +1,19 @@
-import React, {useRef, useState, useEffect} from 'react';
-import {GoslingComponent, type GoslingRef, type GoslingSpec} from 'gosling.js';
-import MetaTable, {MetaTableSpec} from './MetaTable';
+import React, {useState} from 'react';
+import {type GoslingSpec} from 'gosling.js';
+import {MetaTableSpec} from './MetaTable';
import 'higlass/dist/hglib.css';
import './index.css';
+import {PhyloTreeSpec} from "./PhyloTree";
+import GoslingComponentWrapper from "./GoslingComponentWrapper";
+import type {Datum} from "gosling.js/dist/src/gosling-schema";
+import MetaComponentWrapper from "./MetaComponentWrapper";
-export type MetaSpec = {
- width: number;
- height?: number;
-} & MetaTableSpec
+export type MetaSpec = (MetaTableSpec | PhyloTreeSpec)
interface ConnectionType {
type: 'weak' | 'strong';
trackId: string;
+ placeholderId: string;
}
@@ -28,43 +30,32 @@ interface GoslingMetaComponentProps {
*/
export default function GoslingMetaComponent(props: GoslingMetaComponentProps) {
const {goslingSpec, metaSpec, connectionType} = props;
-
- const gosRef = useRef(null);
- const containerRef = useRef(null);
-
- let gosPos, metaPos;
- if (connectionType.type == 'weak') {
- gosPos = {left: 100 + metaSpec.width, top: 100};
- metaPos = {left: 100, top: 100};
- } else {
- // TODO: get position of track that is used for alignment when connectionType=="strong" (Related to issue #909)
- }
-
- const [metaHeight, setMetaHeight] = useState(metaSpec.height ?? 100);
- useEffect(() => {
- if (containerRef.current == null) return;
- // if the user does not provide a height and the alignmentType is "loose" use the full height of the gosling component
- if (!metaSpec.height && connectionType.type === "weak") {
- setMetaHeight(containerRef.current.clientHeight)
- }
- // TODO: get height of spec when connectionType=="strong" (Related to issue #909)
- }, [metaSpec.height, connectionType.type, containerRef.current])
-
-
+ const [goslingSpecUpdateable, setGoslingSpec] = useState(structuredClone(goslingSpec));
+ const [metaDimensions, setMetaDimensions] = useState({x: 0, y: 0, width: 100, height: 100})
+ // range of data relevant for the meta visualization
+ const [range, setRange] = useState<[{ chromosome: string, position: number }, {
+ chromosome: string,
+ position: number
+ }]>([{chromosome: "", position: 0}, {chromosome: "", position: 0}])
+ // data relevant for the meta visualization
+ const [data, setData] = useState([])
return (
-
-
+
+
-
);
diff --git a/src/MetaComponentWrapper.tsx b/src/MetaComponentWrapper.tsx
new file mode 100644
index 0000000..5192c1a
--- /dev/null
+++ b/src/MetaComponentWrapper.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import {GoslingSpec} from "gosling.js";
+import type {Datum} from 'gosling.js/dist/src/gosling-schema';
+import MetaTable from "./MetaTable";
+import PhyloTree from "./PhyloTree";
+import {MetaSpec} from "./GoslingMetaComponent";
+
+interface MetaComponentWrapperProps {
+ metaSpec: MetaSpec;
+ goslingSpec: GoslingSpec;
+ setGoslingSpec: (object) => void;
+ linkedTrackId: string;
+ data: Datum[];
+ range: [{ chromosome: string, position: number }, {
+ chromosome: string,
+ position: number
+ }];
+ height: number;
+ width: number;
+}
+
+/**
+ * Wrapper for gosling component
+ * @param props
+ * @returns
+ */
+export default function MetaComponentWrapper(props: MetaComponentWrapperProps) {
+ const {metaSpec, goslingSpec, setGoslingSpec, linkedTrackId, data, range, height, width} = props;
+ let metaView: React.ReactElement | null = null;
+ switch (metaSpec.type) {
+ case "table":
+ metaView =
+ break;
+ case "tree":
+ metaView =
+ break;
+ }
+ return ({metaView}
);
+}
diff --git a/src/MetaTable.tsx b/src/MetaTable.tsx
index 1ccf061..842b60e 100644
--- a/src/MetaTable.tsx
+++ b/src/MetaTable.tsx
@@ -1,21 +1,22 @@
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useCallback, useMemo} from 'react';
import {mergeData, renameColumns} from "./table-data-transform";
-import {DataDeep, Datum} from "gosling.js/dist/src/core/gosling.schema";
-import {GoslingRef} from "gosling.js";
+import type {Datum, DataDeep} from 'gosling.js/dist/src/gosling-schema';
export type MetaTableSpec = {
type: "table",
// TODO: allow custom data specification for metatable
data?: DataDeep;
dataTransform: tableDataTransform[];
- genomicColumns: [string,string] | [string];
- columns?: string[];
+ genomicColumns: [string] | [string, string];
+ metadataColumns: { type: 'genomic' | 'nominal' | 'quantitative', columnName: string, columnFormat: string }[];
}
interface MetaTableProps extends Omit {
- data?: Datum[];
- gosRef: React.RefObject;
- linkedTrack: string;
+ data: Datum[];
+ range: [{ chromosome: string, position: number }, {
+ chromosome: string,
+ position: number
+ }]
width: number | string;
height: number | string;
}
@@ -37,70 +38,56 @@ export interface RenameColumnsTransform {
newFields: string[];
}
-
+/**
+ * Metadata table component
+ * @param props
+ * @constructor
+ */
export default function MetaTable(props: MetaTableProps) {
- const { dataTransform, gosRef, linkedTrack, genomicColumns, columns, width, height} = props;
- const [dataInRange, setDataInRange] = useState([]);
- const [columnNames, setColumnNames]=useState([])
- const transformData = useCallback((data)=>{
- let dataTransformed: Datum[] = Array.from(data);
- dataTransform.forEach(transform => {
- switch (transform.type) {
- case("merge"):
- dataTransformed = mergeData(transform, data);
- break;
- case("rename"):
- dataTransformed = renameColumns(transform, data);
- break;
- }
- })
- return(dataTransformed);
- },[dataTransform]);
- useEffect(() => {
- if (gosRef.current == null) return;
- // TODO Better: Use a brush event in gosling.js (related issue: #910)
- gosRef.current.api.subscribe('rawData', (type, rawdata) => {
- if(rawdata.data.length > 0 && rawdata.id === linkedTrack) {
- // TODO remove next two lines when rawdata event is adapted (related issues: #909, #894)
- // gets the column names after applying transformations
- const transformedColumns=Object.keys(transformData([rawdata.data[0]])[0])
- const tableKeys = columns && columns.length > 0 ? columns : transformedColumns;
- setColumnNames(tableKeys);
- const range = gosRef.current?.hgApi.api.getLocation(linkedTrack).xDomain;
- // TODO remove column check when rawdata event is adapted (related issues: #909, #894)
- if (tableKeys.every((col) => transformedColumns.includes(col))) {
- let dataInRange: Datum[] = [];
- // features have start and end
- if (genomicColumns.length === 2) {
- const start = genomicColumns[0];
- const end = genomicColumns[1];
- dataInRange = rawdata.data.filter(
- entry =>
- (entry[start] > range[0] && entry[start] < range[1]) ||
- (entry[end] > range[0] && entry[end] < range[1])
- );
- // features have only start (point features)
- } else {
- const position = genomicColumns[0];
- dataInRange = rawdata.data.filter(
- entry => entry[position] > range[0] && entry[position] < range[1]
- );
- }
- const uniqueInRange = dataInRange.filter(
- (v, i, a) => a.findIndex(v2 => v2['Gene name'] === v['Gene name']) === i
- );
- setDataInRange(transformData(uniqueInRange));
- }
+ const {data, range, dataTransform, genomicColumns, metadataColumns, width, height} = props;
+ const transformData = useCallback((data) => {
+ let dataTransformed: Datum[] = Array.from(data);
+ dataTransform.forEach(transform => {
+ switch (transform.type) {
+ case("merge"):
+ dataTransformed = mergeData(transform, data);
+ break;
+ case("rename"):
+ dataTransformed = renameColumns(transform, data);
+ break;
}
- });
- return () => {
- gosRef.current?.api.unsubscribe('rawData');
- };
- }, []);
-
+ })
+ return (dataTransformed);
+ }, [dataTransform]);
+ const dataInRange = useMemo(() => {
+ let inRange: Datum[];
+ // features have start and end
+ if (genomicColumns.length === 2) {
+ const start = genomicColumns[0];
+ const end = genomicColumns[1];
+ inRange = data.filter(
+ entry =>
+ (Number(entry[start]) > range[0].position && Number(entry[start]) < range[1].position) ||
+ (Number(entry[end]) > range[0].position && Number(entry[end]) < range[1].position)
+ );
+ // features have only start (point features)
+ } else {
+ const position = genomicColumns[0];
+ inRange = data.filter(
+ entry => Number(entry[position]) > range[0].position && Number(entry[position]) < range[1].position
+ );
+ }
+ const uniqueInRange = inRange.filter(
+ (v, i, a) => a.findIndex(v2 => JSON.stringify(v2) === JSON.stringify(v)) === i
+ );
+ return (transformData(uniqueInRange));
+ }, [genomicColumns, data, range])
+ const columnNames = useMemo(() => {
+ return metadataColumns.map(d => d.columnName) ?? (dataInRange.length > 0 ? Object.keys(dataInRange[0]) : []);
+ }, [metadataColumns, dataInRange])
return (
<>
- {dataInRange.length === 0 ? null : (
+ {dataInRange.length === 0 ? null :
{dataInRange.map(d => (
-
+
{columnNames.map(key => {
return (
@@ -134,7 +121,7 @@ export default function MetaTable(props: MetaTableProps) {
|
- )}
+ }
>
);
}
diff --git a/src/PhyloTree.tsx b/src/PhyloTree.tsx
new file mode 100644
index 0000000..7a45294
--- /dev/null
+++ b/src/PhyloTree.tsx
@@ -0,0 +1,183 @@
+import React, {useCallback, useState, useMemo, useEffect} from 'react';
+import type {
+ DataDeep,
+ Datum,
+ PartialTrack,
+ Track,
+ View
+} from 'gosling.js/dist/src/gosling-schema';
+import {GoslingSpec} from "gosling.js";
+import {Vega} from "react-vega";
+
+
+export type PhyloTreeSpec = {
+ type: "tree",
+ data: DataDeep;
+}
+
+interface PhyloTreeProps {
+ data?: Datum[];
+ gosSpec: GoslingSpec;
+ setGoslingSpec: (object) => void;
+ linkedTrackId: string;
+ width: number;
+ height: number;
+}
+
+
+export default function PhyloTree(props: PhyloTreeProps) {
+ const {gosSpec, setGoslingSpec, linkedTrackId, width, height} = props;
+ const [maxDist, setMaxDist] = useState(1);
+ const [trackOrder, setTrackOrder] = useState([])
+ const [containerWidth, setContainerWidth] = useState(width);
+ // the width of the tree needs to be recalculated since the width of the tree leaves can vary
+ const localWidth = useMemo(() => {
+ if (containerWidth > width && containerWidth < 2 * width) {
+ return width - (containerWidth - width);
+ } else {
+ return width;
+ }
+ }, [width, containerWidth])
+ const traverseTracks = useCallback((
+ spec: GoslingSpec | View | PartialTrack,
+ callback: (t: Partial