Skip to content

Commit b8f6d6d

Browse files
sehilyidvdkouril
andauthored
feat: add capabilities for spatial layout type (#1138)
* feat: spatial MVP * chore(editor): spatial examples are the default demo in the editor (#1127) chore: make the spatial examples the default * feat: 3D model for a spatial view specified in the layout (#1131) * feat: minimal join data transform * chore: add a todo statement * first working demo * more work on bigwig join * first demo to load bigwig and full chromosomes * all d3 models drawn * chore: remove console log statements and clean up code * chore: fix typo in the comment statement * chore: more clean up * chore: compact axis * fix: fix loading json-based track * feat: grammar extension for 3D layout (#1136) feat: better grammar extension for 3d vis * chore: move the 3d examples to the top; use the new grammar in example specs (#1139) chore: move the 3d examples to the top; use the new grammar in examples * feat(editor): add a 3D yeast model example displayed in three layout options (#1140) * feat: add a Yeast model example * add thumbnail for yeast example * make the yeast example the default at least for now --------- Co-authored-by: David Kouril <[email protected]> * Clean up basic examples (#1141) * chore: minimal example replaced the default (draft) thumbnail, also removed the very early example i used (with linked views) and the thumbnail * chore: remove the multiple tracks example for now still needs some implementation to be able to show that * feat: remove data requirement for 3D tracks (#1142) * feat: remove data requirement for 3D tracks * comment statement * chore: update demo examples * fix: parsed genomic coordinates shouldn't be NaN even if assembly is not specified (#1143) * fix: parse genomic coordinates properly even if assembly is incorrectly defined * remove console logs * join correctly when there is only a single genomic field * Example: three different ways of setting color (#1144) * feat: add example of different coloring strategies constant vs nominal vs quantitative * chore: remove data fields in tracks since it's not needed anymore * chore: change the default model for minimal to stevens also reverted back to being really 'minimal' * fix: pass the mark option from spec * feat: density example (#1145) * feat(editor): example of composing multiple tracks within one spatial view (#1147) * wip: combining multiple tracks within a single spatial view very very bad so far, just a first checkpoint toward something sorta working * feat: add a new example for superimposition * wip: multiple overlaid track * feat: (wip)example for composing multiple tracks within one spatial view touches the join implementation a bit * chore: remove computation of parent views * chore: clean up of unused properties * chore: clean up (log, unused var) * chore: delete commented line * chore: delete log and commented out line * feat(editor): add example using models from Tan 2018 (#1148) * feat(editor): Tan example highlights using sequential color (#1149) * format * more format/eslint * format: remove unused vars * dirty assert options as SpatialTrackOptions * refactor: somewhat better type check when fetching column from tables * refactor: getting rid of squiggly lines * refactor: typing of the handleColor function * refactor: type errors in handleSizeField * wip * temporary fix: doesn't make sense now but new uchimata will fix it * refactor: use new library name and update api usage renamed the underlying library from `chromospace` to `uchimata` * (wip)fix: type errors around spatial views/tracks * suppress warning about `ProcessedSpatialTrack` Same thing is used for GoslingTrack. There's probably a better solution * assert xScale when using certain transforms also moved the assert function to separate file * fix import path * cleaning up some properties * rename ChromospaceTrack to SpatialTrack * mark a todo for later * regenerate json schema * add Encoding in the SpatialTrack type * add data to spatialtrack * wip: copying over some properties some done by claude * wip(broken): sorted out the types for the most part, the typing should be fine. next will actually make it work again * working version without type errors * docs * regenerate schema * cleaning up the changes * remove console logs * run prettier on package.json was getting some weird merge conflicts * format * Update editor/example/json-spec/3d-yeast-model.ts Co-authored-by: SEHI L'YI <[email protected]> * Update editor/example/json-spec/3d-yeast-model.ts Co-authored-by: SEHI L'YI <[email protected]> --------- Co-authored-by: David Kouril <[email protected]>
1 parent 98b0ae9 commit b8f6d6d

32 files changed

+2290
-99
lines changed

demo/gosling-component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export function GoslingComponent(props: GoslingComponentProps) {
8585
></div>
8686
);
8787
}
88+
8889
/**
8990
* This is the main function. It takes a Gosling spec and renders it using the PixiManager
9091
*/

demo/renderer/dataFetcher.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export function getDataFetcher(spec: SingleTrack | OverlaidTrack, urlToFetchOpti
2222

2323
if (typeof data === 'undefined') {
2424
console.warn('No data in the track spec', spec);
25-
return;
25+
// A data object can be missing in 3D tracks. In this case, just pass an empty data.
26+
// The 3d track can still use the 3D genome model for visual encoding.
27+
return new JsonDataFetcher({ type: 'json', values: [], assembly: spec.assembly ?? 'unknown' });
2628
}
2729

2830
const { type } = data;

demo/renderer/main.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import type { UrlToFetchOptions } from 'src/compiler/compile';
1818
import type { Tile } from '@higlass/services';
1919
import type { DataFetcher } from '@higlass/datafetcher';
2020
import type { OverlaidTrack, SingleTrack } from '@gosling-lang/gosling-schema';
21+
import { createSpatialTrack, type SpatialTrackOptions } from '../../src/tracks/spatial-track/spatial-track';
22+
import type { CsvDataFetcherClass } from 'src/data-fetchers/csv/csv-data-fetcher';
2123

2224
/**
2325
* Takes a list of track definitions and linkedEncodings and renders them
@@ -146,6 +148,23 @@ export function renderTrackDefs(
146148
const dummyPlot = new DummyTrack(dummyOptions, pixiManager.makeContainer(boundingBox).overlayDiv);
147149
plotDict[trackId] = dummyPlot;
148150
}
151+
// Add a new track type for `spatial` layout (rendered via uchimata)
152+
if (type === TrackType.Spatial) {
153+
// Even though uchimata doesn't use PixiJS, we can use the PixiManager to create a div container that the canvas can be placed into.
154+
// In the final version, we would probably want uchimata to use an existing canvas element (to limit the creation of new elements).
155+
// But for now this gets the job done.
156+
const container = pixiManager.makeContainer(boundingBox).overlayDiv;
157+
const spatialTrackOptions = options as SpatialTrackOptions; //~ TODO: properly assert the type!
158+
console.warn('!@$!#%@# detected spatial track !@#$!#%@#');
159+
if (spatialTrackOptions.spec.data) {
160+
// Ensure to pull all data needed
161+
if ('sampleLength' in spatialTrackOptions.spec.data) {
162+
spatialTrackOptions.spec.data.sampleLength = 30000;
163+
}
164+
}
165+
const datafetcher = getDataFetcher(spatialTrackOptions.spec, urlToFetchOptions);
166+
createSpatialTrack(spatialTrackOptions, datafetcher as CsvDataFetcherClass, container);
167+
}
149168
});
150169
return plotDict;
151170
}

demo/track-def/main.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type BrushLinearTrackOptions } from '@gosling-lang/brush-linear';
66
import type { TrackInfo } from '../../src/compiler/bounding-box';
77
import type { CompleteThemeDeep } from '../../src/core/utils/theme';
88
import type { GoslingTrackOptions } from '../../src/tracks/gosling-track/gosling-track';
9+
import type { SpatialTrackOptions } from 'src/tracks/spatial-track/spatial-track';
910

1011
import { proccessTextHeader } from './text';
1112
import { processHeatmapTrack, isHeatmapTrack } from './heatmap';
@@ -25,7 +26,8 @@ export enum TrackType {
2526
Axis,
2627
BrushLinear,
2728
BrushCircular,
28-
Heatmap
29+
Heatmap,
30+
Spatial
2931
}
3032

3133
/**
@@ -39,6 +41,7 @@ interface TrackOptionsMap {
3941
[TrackType.BrushLinear]: BrushLinearTrackOptions;
4042
[TrackType.BrushCircular]: BrushCircularTrackOptions;
4143
[TrackType.Heatmap]: HeatmapTrackOptions;
44+
[TrackType.Spatial]: SpatialTrackOptions;
4245
}
4346

4447
/**
@@ -81,6 +84,19 @@ export function createTrackDefs(trackInfos: TrackInfo[], theme: Required<Complet
8184
// We have a dummy track
8285
const dummyTrackDefs = processDummyTrack(track, boundingBox, theme);
8386
trackDefs.push(...dummyTrackDefs);
87+
} else if ('layout' in track && track.layout === 'spatial') {
88+
// We have a 3D track
89+
const trackDef: TrackDef<SpatialTrackOptions> = {
90+
type: TrackType.Spatial,
91+
trackId: track.id,
92+
boundingBox,
93+
options: {
94+
// @ts-expect-error At this point, the spec is processed
95+
spec: track,
96+
processedSpec: track
97+
}
98+
};
99+
trackDefs.push(trackDef);
84100
} else {
85101
// We have a gosling track
86102
const goslingAxisDefs = processGoslingTrack(track, boundingBox, theme);

demo/track-def/types.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ import type { DataDeep, Assembly, DummyTrackStyle, Mark, X, Y } from '@gosling-l
1313
*/
1414

1515
/** A Track after it has been compiled */
16-
export type ProcessedTrack = ProcessedLinearTrack | ProcessedCircularTrack | ProcessedDummyTrack;
17-
// TODO: This should be much better expanded to include all the properties that are actually present.
16+
export type ProcessedTrack =
17+
| ProcessedLinearTrack
18+
| ProcessedCircularTrack
19+
| ProcessedDummyTrack
20+
| ProcessedSpatialTrack;
1821
/** All tracks potentially have these properties */
1922
export interface ProcessedTrackBase {
20-
layout?: 'linear' | 'circular';
23+
layout?: 'linear' | 'circular' | 'spatial';
2124
id: string;
2225
height: number;
2326
width: number;
2427
static: boolean;
25-
mark?: string;
28+
mark?: Mark;
2629
orientation: 'horizontal' | 'vertical';
2730
title?: string;
2831
subtitle?: string;
@@ -51,6 +54,19 @@ export type ProcessedCircularTrack = ProcessedTrackBase & {
5154
innerRadius: number;
5255
};
5356

57+
//~ DK: AFAIK, this needs to loosely fit definitions in Track.
58+
//~ it is force casted via `as ProcessedTrack`, so there's no strict type checking.
59+
export type ProcessedSpatialTrack = ProcessedTrackBase & {
60+
layout: 'spatial';
61+
spatial: {
62+
x: string;
63+
y: string;
64+
z: string;
65+
chr: string;
66+
coord: string;
67+
};
68+
};
69+
5470
export type ProcessedDummyTrack = (ProcessedLinearTrack | ProcessedCircularTrack) & {
5571
type: 'dummy-track';
5672
style?: DummyTrackStyle;

docs/spatial-layout.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# 3D genome structures in Gosling
2+
3+
This file documents the changes in the specification and gosling.js library to
4+
support visualizing 3D structures of genomes.
5+
6+
In principle, there are two ways to define a spatial view:
7+
1. on the **view level**
8+
2. on the **track level**
9+
10+
## view-level specification
11+
We can supply a 3D model data file and bind it to spatial layout properties in
12+
the definition of view's `layout`. This has two advantages: 1) there is no need
13+
to repeat the definitions of a model in each track, and 2) this enables to
14+
merge two data files (one to define the 3D model, and another with a genomic
15+
signal).
16+
17+
```javascript
18+
{
19+
views: [
20+
{
21+
layout: {
22+
type: "spatial",
23+
model: {
24+
type: "csv",
25+
url: "https://.../full-model.csv",
26+
xyz: ["x", "y", "z"],
27+
chromosome: "chr",
28+
position: "coord"
29+
}
30+
},
31+
tracks: [
32+
{ mark: "sphere" },
33+
{ mark: "box" },
34+
]
35+
}
36+
]
37+
}
38+
```
39+
40+
The other method uses simply specifying layout as `spatial` and using the data
41+
in a track for defining the model:
42+
```javascript
43+
{
44+
views: [
45+
{
46+
layout: "spatial",
47+
tracks: [
48+
{
49+
data: { type: "csv", url: "https://.../full-model.csv" },
50+
spatial: {
51+
x: "xfield",
52+
y: "yfield",
53+
z: "zfield"
54+
chr: "chrfield",
55+
coord: "positionfield",
56+
},
57+
mark: "sphere",
58+
}
59+
]
60+
}
61+
]
62+
}
63+
```
64+
This doesn't allow for combining the structural data with any other genomic
65+
formats, unless the genomic data are integrated in the csv already.
66+
67+
Internally, the first method is converted into the second one. The model data
68+
file from the `layout` is pushed down to a dataTransform.

editor/example/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import JS_SPEC_ISLANDVIEWER from './spec/islandviewer.ts?raw';
2929
import { spec as JSON_SPEC_ISLANDVIEWER } from './spec/islandviewer';
3030

3131
export type ExampleGroup =
32+
| 'Spatial Layout'
3233
| 'Visual Encoding'
3334
| 'Mouse Events'
3435
| 'Semantic Zooming'
@@ -44,6 +45,10 @@ export const ExampleGroups: {
4445
name: ExampleGroup;
4546
description: string;
4647
}[] = [
48+
{
49+
name: 'Spatial Layout',
50+
description: 'Visualizations using 3D genome models'
51+
},
4752
{
4853
name: 'Visual Encoding',
4954
description:
@@ -423,6 +428,43 @@ export const editorExampleObj: {
423428
spec: JsonExampleSpecs.EX_SPEC_PERF_ALIGNMENT,
424429
image: THUMBNAILS.PERF_ALIGNMENT
425430
},
431+
SPATIAL_LAYOUT_MIN: {
432+
group: 'Spatial Layout',
433+
name: 'Minimal Example',
434+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_MINIMAL,
435+
image: THUMBNAILS.SPATIAL_MINIMAL
436+
},
437+
SPATIAL_LAYOUT_COLORS: {
438+
group: 'Spatial Layout',
439+
name: 'Coloring Strategies',
440+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_COLOR,
441+
image: THUMBNAILS.SPATIAL_COLOR
442+
},
443+
SPATIAL_LAYOUT_DENSITY: {
444+
group: 'Spatial Layout',
445+
name: 'Density',
446+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_DENSITY,
447+
image: THUMBNAILS.SPATIAL_DENSITY
448+
},
449+
SPATIAL_LAYOUT_SUPERIMP: {
450+
group: 'Spatial Layout',
451+
name: 'Multiple tracks',
452+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_MULTIPLE_TRACKS,
453+
image: THUMBNAILS.SPATIAL_MULTIPLE_TRACKS
454+
},
455+
SPATIAL_LAYOUT_TAN_COMP: {
456+
group: 'Spatial Layout',
457+
name: 'Human Cell Model (Tan et al., Science 2018): Comparison',
458+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_TAN_COMP,
459+
image: THUMBNAILS.SPATIAL_TAN
460+
},
461+
SPATIAL_LAYOUT_3D_YEAST_MODEL: {
462+
group: 'Spatial Layout',
463+
name: '3D Yeast Model (Nature 2010)',
464+
spec: JsonExampleSpecs.EX_SPEC_3D_YEAST_MODEL,
465+
image: THUMBNAILS.SPATIAL_YEAST,
466+
forceShow: true
467+
},
426468
CORCES_ET_AL: {
427469
group: 'Coordinated Multiple Views',
428470
name: 'Corces et al. 2020',

0 commit comments

Comments
 (0)