Skip to content

Commit c41c62b

Browse files
committed
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
1 parent 9bf6c32 commit c41c62b

File tree

9 files changed

+170
-56
lines changed

9 files changed

+170
-56
lines changed

editor/example/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ export const editorExampleObj: {
453453
image: THUMBNAILS.SPATIAL_YEAST,
454454
forceShow: true
455455
},
456+
SPATIAL_LAYOUT_SUPERIMP: {
457+
group: 'Spatial Layout',
458+
name: 'Multiple tracks',
459+
spec: JsonExampleSpecs.EX_SPEC_SPATIAL_MULTIPLE_TRACKS,
460+
image: THUMBNAILS.SPATIAL_MULTIPLE_TRACKS
461+
},
456462
CORCES_ET_AL: {
457463
group: 'Coordinated Multiple Views',
458464
name: 'Corces et al. 2020',

editor/example/json-spec/3d-yeast-model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,13 @@ export const EX_SPEC_3D_YEAST_MODEL: GoslingSpec = {
9595
layout: { ...spatialLayoutDef },
9696
views: [
9797
{
98-
tracks: [{ ...base, height: trackWidth }]
98+
tracks: [{ ...base, mark: "sphere", height: trackWidth }]
9999
},
100100
{
101101
tracks: [
102102
{
103103
...base,
104+
mark: "sphere",
104105
data: undefined,
105106
color: { field: 'chr', type: 'nominal', range: colors },
106107
height: trackWidth

editor/example/json-spec/spatial-layout.ts

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,37 +163,128 @@ export const EX_SPEC_SPATIAL_MULTIPLE_TRACKS: GoslingSpec = {
163163
type: 'spatial',
164164
model: {
165165
type: 'csv',
166-
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/main/gosling-3d/yeast_model.csv',
166+
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/refs/heads/main/gosling-3d/stevens-2017/full-model.csv',
167167
xyz: ['x', 'y', 'z'],
168168
chromosome: 'chr',
169169
position: 'coord'
170170
}
171171
},
172+
width: 500,
173+
height: 500,
174+
alignment: "overlay",
175+
tracks: [
176+
{
177+
color: {
178+
value: 'gainsboro'
179+
},
180+
size: {
181+
value: 0.005,
182+
},
183+
mark: 'sphere',
184+
},
185+
{
186+
color: {
187+
value: 'darkslateblue'
188+
},
189+
size: {
190+
value: 0.02,
191+
},
192+
mark: 'box',
193+
dataTransform: [{
194+
type: "filter",
195+
field: "chr",
196+
oneOf: ["chr b"],
197+
}],
198+
},
199+
{
200+
color: {
201+
value: 'darkseagreen'
202+
},
203+
size: {
204+
value: 0.02,
205+
},
206+
mark: 'octahedron',
207+
dataTransform: [{
208+
type: "filter",
209+
field: "chr",
210+
oneOf: ["chr a"],
211+
}],
212+
}
213+
]
214+
}
215+
]
216+
};
217+
218+
export const EX_SPEC_SPATIAL_SUPERIMPOSITION: GoslingSpec = {
219+
title: 'Spatial Layout: superimposition',
220+
views: [
221+
{
222+
layout: 'spatial',
172223
tracks: [
173224
{
174225
data: {
175226
type: 'csv',
176-
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/main/gosling-3d/yeast_model.csv'
227+
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/main/gosling-3d/yeast_model.csv',
228+
},
229+
spatial: {
230+
x: "x",
231+
y: "y",
232+
z: "z",
233+
chr: "chr",
234+
coord: "coord",
177235
},
178236
color: {
179-
value: '#ffffff'
237+
value: "#ff0000",
238+
},
239+
size: {
240+
value: 0.01,
180241
},
181242
width: 500,
182243
height: 500
183244
},
184245
{
185246
data: {
186247
type: 'csv',
187-
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/main/gosling-3d/yeast_model_only_one_chromosome.csv'
248+
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/refs/heads/main/gosling-3d/stevens-2017/single_chromosome.csv',
249+
},
250+
spatial: {
251+
x: "x",
252+
y: "y",
253+
z: "z",
254+
chr: "chr",
255+
coord: "coord",
188256
},
189257
color: {
190-
value: '#ffffff'
258+
value: "#00ff00",
259+
},
260+
size: {
261+
value: 0.01,
262+
},
263+
width: 500,
264+
height: 500
265+
},
266+
{
267+
data: {
268+
type: 'csv',
269+
url: 'https://raw.githubusercontent.com/dvdkouril/chromospace-sample-data/refs/heads/main/gosling-3d/stevens-2017/single_chromosome.csv',
270+
},
271+
spatial: {
272+
x: "x",
273+
y: "y",
274+
z: "z",
275+
chr: "chr",
276+
coord: "coord",
277+
},
278+
color: {
279+
value: "#0000ff",
280+
},
281+
size: {
282+
value: 0.01,
191283
},
192-
mark: 'box',
193284
width: 500,
194285
height: 500
195286
}
196287
]
197288
}
198-
]
289+
],
199290
};

editor/example/thumbnails.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import SPATIAL_MINIMAL from './thumbnails/SPATIAL_MINIMAL.png';
3434
import SPATIAL_YEAST from './thumbnails/SPATIAL_YEAST.png';
3535
import SPATIAL_COLOR from './thumbnails/SPATIAL_COLOR.png';
3636
import SPATIAL_DENSITY from './thumbnails/SPATIAL_DENSITY.png';
37+
import SPATIAL_MULTIPLE_TRACKS from './thumbnails/SPATIAL_MULTIPLE_TRACKS.png';
3738

3839
export const THUMBNAILS = {
3940
ALIGNMENT,
@@ -72,4 +73,5 @@ export const THUMBNAILS = {
7273
SPATIAL_YEAST,
7374
SPATIAL_COLOR,
7475
SPATIAL_DENSITY,
76+
SPATIAL_MULTIPLE_TRACKS,
7577
};
230 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"allotment": "^1.19.0",
6363
"bezier-js": "4.0.3",
6464
"buffer": "^6.0.3",
65-
"chromospace": "^0.1.6",
65+
"chromospace": "^0.1.7",
6666
"css-element-queries": "^1.2.3",
6767
"d3-array": "^3.2.4",
6868
"d3-brush": "^3.0.0",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler/_normalize-3d-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function _fixTrackToWalkaround(t: Track) {
3434
}
3535

3636
// @ts-expect-error
37-
t.dataTransform = [{ ...dataTransform, to }];
37+
t.dataTransform = [{ ...dataTransform, to }, ...(t.dataTransform ?? [])];
3838
t.layout = 'spatial';
3939
} else if (typeof layout == 'object') {
4040
t.layout = layout.type;

src/tracks/spatial-track/spatial-track.ts

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ import { tableFromArrays, tableFromIPC, tableToIPC } from '@uwdata/flechette';
55
import { transform } from '../../core/utils/data-transform';
66
import { getTabularData } from '../gosling-track/data-abstraction';
77

8+
/**
9+
* Make an assertion.
10+
*
11+
* Usage
12+
* @example
13+
* ```ts
14+
* const value: boolean = Math.random() <= 0.5;
15+
* assert(value, "value is greater than than 0.5!");
16+
* value // true
17+
* ```
18+
*
19+
* @copyright Trevor Manz 2025
20+
* @license MIT
21+
* @see {@link https://github.com/manzt/manzt/blob/f7faee/utils/assert.js}
22+
*/
23+
export function assert(expression: unknown, msg: string | undefined = ""): asserts expression {
24+
if (!expression) throw new Error(msg);
25+
}
26+
827
export type SpatialTrackOptions = {
928
spec: SingleTrack | OverlaidTrack;
1029
color: string | undefined;
@@ -25,10 +44,10 @@ async function transformObjectToArrow(t: LoadedTiles, options: SpatialTrackOptio
2544
// So, create one if missing by running `getTabularData()`.
2645
// The tile ID of '0.0' extracts all data for a given file at the lowest resolution.
2746
let tabularData = t['0.0'].tabularData ?? getTabularData(options.spec, t['0.0']);
28-
if (options.spec.dataTransform?.[0]) {
29-
// This basically ensures to do join operation (e.g., combining bigwig data to 3D model)
30-
tabularData = await transform(options.spec.dataTransform?.[0], tabularData, undefined, options.spec.assembly);
31-
console.error('Joined Data', tabularData);
47+
48+
tabularData = structuredClone(tabularData);
49+
for (const t of options.spec.dataTransform ?? []) {
50+
tabularData = await transform(t, tabularData, undefined, options.spec.assembly);
3251
}
3352
const xArr: number[] = [];
3453
const yArr: number[] = [];
@@ -227,27 +246,10 @@ function handleSizeField(size?: ChannelValue | Size | number, arrowIpc: Uint8Arr
227246
}
228247
}
229248

230-
/**
231-
* The idea is to "cache" scenes associated with particular `views`.
232-
*/
233-
const spatialViewsMap = new Map<string, chs.ChromatinScene>();
234-
235-
function fetchScene(viewsScenesMap: Map<string, chs.ChromatinScene>, viewId: string): chs.ChromatinScene {
236-
const s = viewsScenesMap.get(viewId);
237-
238-
if (!s) {
239-
const newScene = chs.initScene();
240-
viewsScenesMap.set(viewId, newScene);
241-
return newScene;
242-
}
243-
244-
return s;
245-
}
246-
247249
export function createSpatialTrack(
248250
options: SpatialTrackOptions,
249251
dataFetcher: CsvDataFetcherClass,
250-
container: HTMLDivElement
252+
container: HTMLDivElement,
251253
) {
252254
console.log('SPEC OPTIONS', options);
253255
dataFetcher.tilesetInfo(info => {
@@ -260,37 +262,49 @@ export function createSpatialTrack(
260262
// This information is needed to create tabular data (i.e., running getTabularData())
261263
t['0.0'].tileWidth = info.max_width;
262264
}
265+
263266
const ipcBuffer = await transformObjectToArrow(t, options);
264-
if (ipcBuffer) {
265-
const viewId = '123'; //~ TODO: need to actually get the ID of the view parent of this track
266-
let chromatinScene = fetchScene(spatialViewsMap, viewId);
267-
//let chromatinScene = chs.initScene();
268-
const arrowIpc = ipcBuffer.buffer;
269-
const color = handleColorField(options.spec.color, arrowIpc);
270-
const scale = handleSizeField(options.spec.size, arrowIpc);
271-
console.log('scale config', scale);
267+
if (!ipcBuffer) {
268+
console.error("could not tranform into Apache Arrow");
269+
return;
270+
}
271+
console.warn("spec", options.spec);
272+
let chromatinScene = chs.initScene();
273+
const tracks = options.spec._overlay ?? [options.spec];
274+
for (const ov of tracks) {
275+
console.log("ov", ov);
276+
277+
const color = handleColorField(ov.color, ipcBuffer.buffer);
278+
const scale = handleSizeField(ov.size, ipcBuffer.buffer);
272279
const viewConfig = {
273280
scale: scale,
274281
color: color,
275-
mark: options.spec.mark
282+
mark: ov.mark
276283
};
277-
console.log('viewConfig', viewConfig);
278-
const s = chs.load(ipcBuffer.buffer, { center: true, normalize: true });
279284

280-
const result = s;
285+
let s = chs.load(ipcBuffer.buffer, { center: true, normalize: true });
281286

282-
const isModel = 'parts' in result; //~ ChromatinModel has .parts
283-
console.log(`isModel: ${isModel}`);
287+
const isModel = "parts" in s; //~ ChromatinModel has .parts
284288
if (isModel) {
285-
chromatinScene = chs.addModelToScene(chromatinScene, result, viewConfig);
289+
const filterTransform = (ov.dataTransform ?? []).find(t => t.type === "filter");
290+
if (filterTransform) {
291+
const field: string = filterTransform.field;
292+
assert(field === "chr", "Field for transform should be 'chr'");
293+
const oneOf = filterTransform.oneOf;
294+
const first = oneOf[0];
295+
const res = chs.get(s, first)!;
296+
if (res) {
297+
const [selectedModel, _] = res;
298+
s = { parts: [selectedModel] };
299+
}
300+
}
301+
chromatinScene = chs.addModelToScene(chromatinScene, s, viewConfig);
286302
} else {
287-
chromatinScene = chs.addChunkToScene(chromatinScene, result, viewConfig);
303+
chromatinScene = chs.addChunkToScene(chromatinScene, s, viewConfig);
288304
}
289-
const [_, canvas] = chs.display(chromatinScene, { alwaysRedraw: false });
290-
291-
container.appendChild(canvas);
292-
} else {
293305
}
306+
const [_, createdCanvas] = chs.display(chromatinScene, { alwaysRedraw: false, withHUD: false });
307+
container.appendChild(createdCanvas);
294308
},
295309
['0.0']
296310
);

0 commit comments

Comments
 (0)