higlass 1.12.4 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/app/globals.d.ts +11 -0
- package/app/missing-types.d.ts +21 -0
- package/app/scripts/CenterTrack.jsx +0 -1
- package/app/scripts/DraggableDiv.jsx +0 -1
- package/app/scripts/GenomePositionSearchBox.jsx +0 -1
- package/app/scripts/TrackRenderer.jsx +405 -89
- package/app/scripts/VerticalTiledPlot.jsx +0 -1
- package/app/scripts/configs/default-tracks-for-datatype.js +3 -2
- package/app/scripts/configs/primitives.js +3 -2
- package/app/scripts/configs/tracks-info-by-type.js +4 -1
- package/app/scripts/configs/tracks-info.js +25 -1
- package/app/scripts/types.ts +79 -0
- package/app/scripts/utils/abs-to-chr.js +16 -1
- package/app/scripts/utils/accessor-transposition.js +5 -4
- package/app/scripts/utils/add-arrays.js +9 -7
- package/app/scripts/utils/add-class.js +4 -2
- package/app/scripts/utils/add-event-listener-once.js +9 -3
- package/app/scripts/utils/background-task-scheduler.js +58 -2
- package/app/scripts/utils/base64-to-canvas.js +12 -5
- package/app/scripts/utils/chr-to-abs.js +10 -0
- package/app/scripts/utils/chrom-info-bisector.js +4 -1
- package/app/scripts/utils/clone-event.js +11 -4
- package/app/scripts/utils/color-to-hex.js +8 -0
- package/app/scripts/utils/color-to-rgba.js +8 -0
- package/app/scripts/utils/data-to-genomic-loci.js +13 -1
- package/app/scripts/utils/debounce.js +16 -11
- package/app/scripts/utils/dec-to-hex-str.js +7 -0
- package/app/scripts/utils/dict-from-tuples.js +11 -3
- package/app/scripts/utils/dict-items.js +15 -0
- package/app/scripts/utils/dict-keys.js +11 -1
- package/app/scripts/utils/dict-values.js +7 -0
- package/app/scripts/utils/download.js +14 -11
- package/app/scripts/utils/flatten.js +5 -2
- package/app/scripts/utils/for-each.js +7 -5
- package/app/scripts/utils/forward-event.js +3 -2
- package/app/scripts/utils/genome-loci-to-pixels.js +8 -0
- package/app/scripts/utils/genomic-range-to-chromosome-chunks.js +13 -6
- package/app/scripts/utils/get-aggregation-function.js +10 -2
- package/app/scripts/utils/get-element-dim.js +6 -0
- package/app/scripts/utils/gradient.js +14 -0
- package/app/scripts/utils/has-class.js +6 -4
- package/app/scripts/utils/hex-string-to-int.js +11 -4
- package/app/scripts/utils/index.js +1 -0
- package/app/scripts/utils/into-the-void.js +2 -1
- package/app/scripts/utils/is-track-or-child-track.js +6 -0
- package/app/scripts/utils/is-track-range-selectable.js +10 -1
- package/app/scripts/utils/is-within.js +9 -7
- package/app/scripts/utils/lat-to-y.js +6 -3
- package/app/scripts/utils/lng-to-x.js +4 -3
- package/app/scripts/utils/map.js +5 -2
- package/app/scripts/utils/max-non-zero.js +6 -0
- package/app/scripts/utils/max.js +4 -3
- package/app/scripts/utils/min-non-zero.js +6 -0
- package/app/scripts/utils/min.js +4 -3
- package/app/scripts/utils/mod.js +4 -3
- package/app/scripts/utils/numericify-version.js +5 -0
- package/app/scripts/utils/obj-vals.js +3 -2
- package/app/scripts/utils/or.js +4 -3
- package/app/scripts/utils/parse-chromsizes-rows.js +26 -5
- package/app/scripts/utils/q.js +3 -2
- package/app/scripts/utils/rad-to-deg.js +3 -2
- package/app/scripts/utils/reduce.js +2 -2
- package/app/scripts/utils/rel-to-abs-chrom-pos.js +10 -0
- package/app/scripts/utils/remove-class.js +3 -2
- package/app/scripts/utils/reset-d3-brush-style.js +7 -2
- package/app/scripts/utils/rgb-to-hex.js +9 -0
- package/app/scripts/utils/scales-center-and-k.js +5 -3
- package/app/scripts/utils/scales-to-genome-loci.js +10 -0
- package/app/scripts/utils/selected-items-to-cum-weights.js +12 -4
- package/app/scripts/utils/selected-items-to-size.js +5 -2
- package/app/scripts/utils/show-mouse-position.js +62 -19
- package/app/scripts/utils/some.js +6 -4
- package/app/scripts/utils/sum.js +4 -3
- package/app/scripts/utils/throttle-and-debounce.js +15 -6
- package/app/scripts/utils/tile-to-canvas.js +8 -4
- package/app/scripts/utils/timeout.js +2 -0
- package/app/scripts/utils/to-void.js +2 -0
- package/app/scripts/utils/total-track-pixel-height.js +11 -12
- package/app/scripts/utils/trim-trailing-slash.js +3 -2
- package/app/scripts/utils/type-guards.js +17 -0
- package/app/scripts/utils/value-to-color.js +7 -6
- package/app/scripts/utils/visit-positioned-tracks.js +16 -11
- package/app/scripts/utils/visit-tracks.js +12 -13
- package/dist/hglib.js +204 -131
- package/dist/hglib.min.js +70 -70
- package/package.json +16 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import React from 'react';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
|
|
@@ -72,6 +73,8 @@ import {
|
|
|
72
73
|
trimTrailingSlash,
|
|
73
74
|
} from './utils';
|
|
74
75
|
|
|
76
|
+
import { isCombinedTrackConfig, isWheelEvent } from './utils/type-guards';
|
|
77
|
+
|
|
75
78
|
// Configs
|
|
76
79
|
import { GLOBALS, THEME_DARK, TRACKS_INFO_BY_TYPE } from './configs';
|
|
77
80
|
|
|
@@ -85,6 +88,141 @@ const { getDataFetcher } = AVAILABLE_FOR_PLUGINS.dataFetchers;
|
|
|
85
88
|
|
|
86
89
|
const SCROLL_TIMEOUT = 100;
|
|
87
90
|
|
|
91
|
+
/** @typedef {import('./types').Scale} Scale */
|
|
92
|
+
/** @typedef {import('./types').TrackConfig} TrackConfig */
|
|
93
|
+
/** @typedef {import('./types').TrackObject} TrackObject */
|
|
94
|
+
|
|
95
|
+
/** @typedef {TrackRenderer["setCenter"]} SetCentersFunction */
|
|
96
|
+
/** @typedef {(x: Scale, y: Scale) => [Scale, Scale]} ProjectorFunction */
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @typedef TrackDefinition
|
|
100
|
+
* @property {TrackConfig} track
|
|
101
|
+
* @property {number} width
|
|
102
|
+
* @property {number} height
|
|
103
|
+
* @property {number} top
|
|
104
|
+
* @property {number} left
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/** @typedef {Record<string, unknown>} TilesetInfo */
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef MetaPluginTrackContext
|
|
111
|
+
* @property {(trackId: string) => TrackObject | undefined} getTrackObject
|
|
112
|
+
* @property {() => void} onNewTilesLoaded
|
|
113
|
+
* @property {TrackConfig} definition
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @typedef {Object} PluginTrackContext
|
|
118
|
+
* @property {string} id
|
|
119
|
+
* @property {string} trackUid
|
|
120
|
+
* @property {string} trackType
|
|
121
|
+
* @property {string} viewUid
|
|
122
|
+
* @property {import('pub-sub-es').PubSub} pubSub
|
|
123
|
+
* @property {import("pixi.js").Graphics} scene
|
|
124
|
+
* @property {Record<string, unknown>} dataConfig
|
|
125
|
+
* @property {unknown} dataFetcher
|
|
126
|
+
* @property {() => unknown} getLockGroupExtrema
|
|
127
|
+
* @property {(tilesetInfo: TilesetInfo) => void} handleTilesetInfoReceived
|
|
128
|
+
* @property {() => void} animate
|
|
129
|
+
* @property {HTMLElement} svgElement
|
|
130
|
+
* @property {() => boolean} isValueScaleLocked
|
|
131
|
+
* @property {() => void} onValueScaleChanged
|
|
132
|
+
* @property {(newOption: Record<string, unknown>) => void} onTrackOptionsChanged
|
|
133
|
+
* @property {() => void} onMouseMoveZoom
|
|
134
|
+
* @property {string} chromInfoPath
|
|
135
|
+
* @property {() => boolean} isShowGlobalMousePosition
|
|
136
|
+
* @property {() => (string | typeof THEME_DARK)} getTheme
|
|
137
|
+
* @property {unknown=} AVAILABLE_FOR_PLUGINS
|
|
138
|
+
* @property {(HTMLDivElement | null)=} baseEl
|
|
139
|
+
* @property {TrackConfig=} definition
|
|
140
|
+
* @property {number=} x
|
|
141
|
+
* @property {number=} y
|
|
142
|
+
* @property {number=} xPosition
|
|
143
|
+
* @property {number=} yPosition
|
|
144
|
+
* @property {[number, number]=} projectionXDomain
|
|
145
|
+
* @property {[number, number]=} projectionYDomain
|
|
146
|
+
* @property {unknown=} registerViewportChanged
|
|
147
|
+
* @property {unknown=} removeViewportChanged
|
|
148
|
+
* @property {unknown=} setDomainsCallback
|
|
149
|
+
* @property {TrackConfig[]=} tracks
|
|
150
|
+
* @property {TrackRenderer["createTrackObject"]=} createTrackObject
|
|
151
|
+
* @property {string=} orientation
|
|
152
|
+
* @property {boolean=} isOverlay
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @typedef PluginTrack
|
|
157
|
+
* @property {{ new (availableForPlugins: unknown, context: PluginTrackContext, options: Record<string, unknown>): TrackObject }} track
|
|
158
|
+
* @property {false=} isMetaTrack
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @typedef MetaPluginTrack
|
|
163
|
+
* @property {{ new (availableForPlugins: unknown, context: MetaPluginTrackContext, options: Record<string, unknown>): TrackObject }} track
|
|
164
|
+
* @property {true} isMetaTrack
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @template T
|
|
169
|
+
* @typedef {T & { __zoom?: import('d3-zoom').ZoomTransform }} WithZoomTransform
|
|
170
|
+
*/
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @typedef TrackRendererProps
|
|
174
|
+
* @property {HTMLElement} canvasElement
|
|
175
|
+
* @property {number} centerHeight
|
|
176
|
+
* @property {number} centerWidth
|
|
177
|
+
* @property {Array<JSX.Element>} children
|
|
178
|
+
* @property {number} galleryDim
|
|
179
|
+
* @property {number} height
|
|
180
|
+
* @property {[number, number]} initialXDomain
|
|
181
|
+
* @property {[number, number]} initialYDomain
|
|
182
|
+
* @property {boolean} isShowGlobalMousePosition
|
|
183
|
+
* @property {boolean} isRangeSelection
|
|
184
|
+
* @property {number} leftWidth
|
|
185
|
+
* @property {number} leftWidthNoGallery
|
|
186
|
+
* @property {number} paddingLeft
|
|
187
|
+
* @property {number} paddingTop
|
|
188
|
+
* @property {Array<TrackConfig>} metaTracks
|
|
189
|
+
* @property {() => void} onMouseMoveZoom
|
|
190
|
+
* @property {(trackId?: string) => void} onNewTilesLoaded
|
|
191
|
+
* @property {(x: Scale, y: Scale) => void} onScalesChanged
|
|
192
|
+
* @property {import("pixi.js").Renderer} pixiRenderer
|
|
193
|
+
* @property {import("pixi.js").Container} pixiStage
|
|
194
|
+
* @property {Record<string, unknown>} pluginDataFetchers
|
|
195
|
+
* @property {Record<string, PluginTrack | MetaPluginTrack>} pluginTracks
|
|
196
|
+
* @property {Array<TrackDefinition>} positionedTracks
|
|
197
|
+
* @property {import('pub-sub-es').PubSub} pubSub
|
|
198
|
+
* @property {(func: SetCentersFunction) => void} setCentersFunction
|
|
199
|
+
* @property {HTMLElement} svgElement
|
|
200
|
+
* @property {string | typeof THEME_DARK} theme
|
|
201
|
+
* @property {number} topHeight
|
|
202
|
+
* @property {number} topHeightNoGallery
|
|
203
|
+
* @property {{ backgroundColor?: string }} viewOptions
|
|
204
|
+
* @property {number} width
|
|
205
|
+
* @property {[number, number]} xDomainLimits
|
|
206
|
+
* @property {[number, number]} yDomainLimits
|
|
207
|
+
* @property {boolean} valueScaleZoom
|
|
208
|
+
* @property {boolean} zoomable
|
|
209
|
+
* @property {[number, number]} zoomDomain
|
|
210
|
+
* @property {[number, number]} zoomLimits
|
|
211
|
+
* @property {string} uid
|
|
212
|
+
* @property {boolean} dragging
|
|
213
|
+
* @property {(func: (draggingStatus: boolean) => void) => void} registerDraggingChangedListener
|
|
214
|
+
* @property {boolean} disableTrackMenu
|
|
215
|
+
* @property {(listener: (draggingStatus: boolean) => void) => void} removeDraggingChangedListener
|
|
216
|
+
* @property {(trackId: string, tilesetInfo: TilesetInfo) => void} onTilesetInfoReceived
|
|
217
|
+
* @property {(trackId: string) => unknown} getLockGroupExtrema
|
|
218
|
+
* @property {(trackId: string) => boolean} isValueScaleLocked
|
|
219
|
+
* @property {(trackId: string) => void} onValueScaleChanged
|
|
220
|
+
* @property {(trackId: string, newOption: Record<string, unknown>) => void} onTrackOptionsChanged
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @extends {React.Component<TrackRendererProps>}
|
|
225
|
+
*/
|
|
88
226
|
class TrackRenderer extends React.Component {
|
|
89
227
|
/**
|
|
90
228
|
* Maintain a list of tracks, and re-render them whenever either
|
|
@@ -93,29 +231,52 @@ class TrackRenderer extends React.Component {
|
|
|
93
231
|
* Zooming changes the domain of the scales.
|
|
94
232
|
*
|
|
95
233
|
* Resizing changes the range. Both trigger a rerender.
|
|
234
|
+
*
|
|
235
|
+
* @param {TrackRendererProps} props
|
|
96
236
|
*/
|
|
97
237
|
constructor(props) {
|
|
98
238
|
super(props);
|
|
239
|
+
/** @type {boolean} */
|
|
99
240
|
this.dragging = false; // is this element being dragged?
|
|
241
|
+
/** @type {WithZoomTransform<HTMLElement> | null} */
|
|
100
242
|
this.element = null;
|
|
243
|
+
/** @type {HTMLElement | null} */
|
|
244
|
+
this.eventTracker = null;
|
|
245
|
+
/** @type {HTMLElement | null} */
|
|
246
|
+
this.eventTrackerOld = null;
|
|
247
|
+
/** @type {boolean} */
|
|
101
248
|
this.closing = false;
|
|
102
249
|
|
|
250
|
+
/** @type {number} */
|
|
103
251
|
this.yPositionOffset = 0;
|
|
252
|
+
/** @type {number} */
|
|
104
253
|
this.xPositionOffset = 0;
|
|
254
|
+
/** @type {number} */
|
|
105
255
|
this.scrollTop = 0;
|
|
106
256
|
|
|
257
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
107
258
|
this.scrollTimeout = null;
|
|
259
|
+
/** @type {number} */
|
|
108
260
|
this.activeTransitions = 0;
|
|
109
261
|
|
|
262
|
+
/** @type {import('d3-zoom').ZoomTransform} */
|
|
110
263
|
this.zoomTransform = zoomIdentity;
|
|
264
|
+
/** @type {() => void} */
|
|
111
265
|
this.windowScrolledBound = this.windowScrolled.bind(this);
|
|
266
|
+
/** @type {(event?: import('d3-zoom').D3ZoomEvent<HTMLElement, unknown>) => void} */
|
|
112
267
|
this.zoomStartedBound = this.zoomStarted.bind(this);
|
|
268
|
+
/** @type {(event: import('d3-zoom').D3ZoomEvent<HTMLElement, unknown> & { shiftKey?: boolean }) => void} */
|
|
113
269
|
this.zoomedBound = this.zoomed.bind(this);
|
|
270
|
+
/** @type {() => void} */
|
|
114
271
|
this.zoomEndedBound = this.zoomEnded.bind(this);
|
|
115
272
|
|
|
273
|
+
/** @type {string} */
|
|
116
274
|
this.uid = slugid.nice();
|
|
275
|
+
|
|
276
|
+
/** @type {string} */
|
|
117
277
|
this.viewUid = this.props.uid;
|
|
118
278
|
|
|
279
|
+
/** @type {unknown} */
|
|
119
280
|
this.availableForPlugins = {
|
|
120
281
|
...AVAILABLE_FOR_PLUGINS,
|
|
121
282
|
services: {
|
|
@@ -125,55 +286,72 @@ class TrackRenderer extends React.Component {
|
|
|
125
286
|
},
|
|
126
287
|
};
|
|
127
288
|
|
|
289
|
+
/** @type {boolean} */
|
|
128
290
|
this.mounted = false;
|
|
129
291
|
|
|
130
292
|
// create a zoom behavior that we'll just use to transform selections
|
|
131
293
|
// without having it fire an "onZoom" event
|
|
294
|
+
/** @type {import("d3-zoom").ZoomBehavior<HTMLElement, unknown>} */
|
|
132
295
|
this.emptyZoomBehavior = zoom();
|
|
133
296
|
|
|
134
297
|
// a lot of the updates in TrackRenderer happen in response to
|
|
135
298
|
// componentWillReceiveProps so we need to perform them with the
|
|
136
299
|
// newest set of props. When cWRP is called, this.props still contains
|
|
137
300
|
// the old props, so we need to store them in a new variable
|
|
301
|
+
/** @type {TrackRendererProps} */
|
|
138
302
|
this.currentProps = props;
|
|
303
|
+
/** @type {string} */
|
|
139
304
|
this.prevPropsStr = '';
|
|
140
305
|
|
|
141
306
|
// catch any zooming behavior within all of the tracks in this plot
|
|
142
307
|
// this.zoomTransform = zoomIdentity();
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
308
|
+
/** @type {import("d3-zoom").ZoomBehavior<HTMLElement, unknown>} */
|
|
309
|
+
this.zoomBehavior =
|
|
310
|
+
/** @type {import("d3-zoom").ZoomBehavior<HTMLElement, any>} */ (zoom())
|
|
311
|
+
.filter((event) => {
|
|
312
|
+
if (event.target.classList.contains('no-zoom')) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (event.target.classList.contains('react-resizable-handle')) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
})
|
|
320
|
+
.on('start', this.zoomStartedBound)
|
|
321
|
+
.on('zoom', this.zoomedBound)
|
|
322
|
+
.on('end', this.zoomEndedBound);
|
|
156
323
|
|
|
324
|
+
/** @type {import('d3-zoom').ZoomTransform} */
|
|
157
325
|
this.zoomTransform = zoomIdentity;
|
|
326
|
+
/** @type {import('d3-zoom').ZoomTransform} */
|
|
158
327
|
this.prevZoomTransform = zoomIdentity;
|
|
159
328
|
|
|
329
|
+
/** @type {[number, number]} */
|
|
160
330
|
this.initialXDomain = [0, 1];
|
|
331
|
+
/** @type {[number, number]} */
|
|
161
332
|
this.initialYDomain = [0, 1];
|
|
333
|
+
/** @type {[number, number]} */
|
|
162
334
|
this.xDomainLimits = [-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
|
|
335
|
+
/** @type {[number, number]} */
|
|
163
336
|
this.yDomainLimits = [-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
|
|
337
|
+
/** @type {[number, number]} */
|
|
164
338
|
this.zoomLimits = [0, Number.MAX_SAFE_INTEGER];
|
|
165
339
|
|
|
340
|
+
/** @type {number} */
|
|
166
341
|
this.prevCenterX =
|
|
167
342
|
this.currentProps.paddingLeft +
|
|
168
343
|
this.currentProps.leftWidth +
|
|
169
344
|
this.currentProps.centerWidth / 2;
|
|
345
|
+
/** @type {number} */
|
|
170
346
|
this.prevCenterY =
|
|
171
347
|
this.currentProps.paddingTop +
|
|
172
348
|
this.currentProps.topHeight +
|
|
173
349
|
this.currentProps.centerHeight / 2;
|
|
174
350
|
|
|
175
351
|
// The offset of the center from the original. Used to keep the scales centered on resize events
|
|
352
|
+
/** @type {number} */
|
|
176
353
|
this.cumCenterXOffset = 0;
|
|
354
|
+
/** @type {number} */
|
|
177
355
|
this.cumCenterYOffset = 0;
|
|
178
356
|
|
|
179
357
|
this.setUpInitialScales(
|
|
@@ -191,10 +369,13 @@ class TrackRenderer extends React.Component {
|
|
|
191
369
|
// Each object will contain a trackDef
|
|
192
370
|
// {'top': 100, 'left': 50,... 'track': {'source': 'http:...', 'type': 'heatmap'}}
|
|
193
371
|
// And a trackObject which will be responsible for rendering it
|
|
372
|
+
/** @type {Record<string, { trackObject: TrackObject, trackDef: TrackDefinition }>} */
|
|
194
373
|
this.trackDefObjects = {};
|
|
195
374
|
|
|
375
|
+
/** @type {Record<string, { trackObject: TrackObject | UnknownPixiTrack, trackDef: TrackConfig }>} */
|
|
196
376
|
this.metaTracks = {};
|
|
197
377
|
|
|
378
|
+
/** @type {Array<import("pub-sub-es").Subscription>} */
|
|
198
379
|
this.pubSubs = [];
|
|
199
380
|
|
|
200
381
|
// if there's plugin tracks, they'll define new track
|
|
@@ -202,18 +383,43 @@ class TrackRenderer extends React.Component {
|
|
|
202
383
|
// we look up the orientation of a track
|
|
203
384
|
if (window.higlassTracksByType) {
|
|
204
385
|
// Extend `TRACKS_INFO_BY_TYPE` with the configs of plugin tracks.
|
|
205
|
-
|
|
386
|
+
for (const pluginTrackType in window.higlassTracksByType) {
|
|
206
387
|
TRACKS_INFO_BY_TYPE[pluginTrackType] =
|
|
207
388
|
window.higlassTracksByType[pluginTrackType].config;
|
|
208
|
-
}
|
|
389
|
+
}
|
|
209
390
|
}
|
|
210
391
|
|
|
392
|
+
/** @type {<T extends Event>(event: T & { sourceUid?: string, forwarded?: boolean }) => void} */
|
|
211
393
|
this.boundForwardEvent = this.forwardEvent.bind(this);
|
|
394
|
+
/** @type {() => void} */
|
|
212
395
|
this.boundScrollEvent = this.scrollEvent.bind(this);
|
|
396
|
+
/** @type {(event: { altKey: boolean, preventDefault(): void }) => void} */
|
|
213
397
|
this.boundForwardContextMenu = this.forwardContextMenu.bind(this);
|
|
398
|
+
/** @type {(event: Event & { sourceUid: string, type: string }) => void} */
|
|
214
399
|
this.dispatchEventBound = this.dispatchEvent.bind(this);
|
|
400
|
+
/** @type {(opts: { pos: [number, number, number, number], animateTime: number, isMercator: boolean }) => void} */
|
|
215
401
|
this.zoomToDataPosHandlerBound = this.zoomToDataPosHandler.bind(this);
|
|
402
|
+
/** @type {(scrollTop: number) => void} */
|
|
216
403
|
this.onScrollHandlerBound = this.onScrollHandler.bind(this);
|
|
404
|
+
|
|
405
|
+
/** @type {{ height: number, width: number, left: number, top: number }} */
|
|
406
|
+
this.elementPos = { height: 0, width: 0, left: 0, top: 0 };
|
|
407
|
+
/** @type {import('d3-selection').Selection<WithZoomTransform<HTMLElement>, unknown, null, unknown> | null} */
|
|
408
|
+
this.elementSelection = null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
get xScale() {
|
|
412
|
+
if (!this._xScale) {
|
|
413
|
+
throw new Error('xScale is not defined');
|
|
414
|
+
}
|
|
415
|
+
return this._xScale;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
get yScale() {
|
|
419
|
+
if (!this._yScale) {
|
|
420
|
+
throw new Error('yScale is not defined');
|
|
421
|
+
}
|
|
422
|
+
return this._yScale;
|
|
217
423
|
}
|
|
218
424
|
|
|
219
425
|
// eslint-disable-next-line camelcase
|
|
@@ -237,13 +443,19 @@ class TrackRenderer extends React.Component {
|
|
|
237
443
|
}
|
|
238
444
|
|
|
239
445
|
componentDidMount() {
|
|
446
|
+
if (!this.element) {
|
|
447
|
+
throw new Error('Component did not mount, this.element is not defined.');
|
|
448
|
+
}
|
|
240
449
|
this.elementPos = this.element.getBoundingClientRect();
|
|
241
450
|
this.elementSelection = select(this.element);
|
|
242
|
-
this.svgTrackAreaSelection = select(this.svgTrackArea);
|
|
243
451
|
|
|
452
|
+
/** @type {import('pixi.js').Graphics} */
|
|
244
453
|
this.pStage = new GLOBALS.PIXI.Graphics();
|
|
454
|
+
/** @type {import('pixi.js').Graphics} */
|
|
245
455
|
this.pMask = new GLOBALS.PIXI.Graphics();
|
|
456
|
+
/** @type {import('pixi.js').Graphics} */
|
|
246
457
|
this.pOutline = new GLOBALS.PIXI.Graphics();
|
|
458
|
+
/** @type {import('pixi.js').Graphics} */
|
|
247
459
|
this.pBackground = new GLOBALS.PIXI.Graphics();
|
|
248
460
|
|
|
249
461
|
this.pStage.addChild(this.pMask);
|
|
@@ -274,6 +486,7 @@ class TrackRenderer extends React.Component {
|
|
|
274
486
|
this.addEventTracker();
|
|
275
487
|
|
|
276
488
|
// Init zoom and scale extent
|
|
489
|
+
/** @type {[[number, number], [number, number]]} */
|
|
277
490
|
const transExt = [
|
|
278
491
|
[this.xScale(this.xDomainLimits[0]), this.yScale(this.yDomainLimits[0])],
|
|
279
492
|
[this.xScale(this.xDomainLimits[1]), this.yScale(this.yDomainLimits[1])],
|
|
@@ -281,6 +494,7 @@ class TrackRenderer extends React.Component {
|
|
|
281
494
|
|
|
282
495
|
const svgBBox = this.svgElement.getBoundingClientRect();
|
|
283
496
|
|
|
497
|
+
/** @type {[[number, number], [number, number]]} */
|
|
284
498
|
const ext = [
|
|
285
499
|
[Math.max(transExt[0][0], 0), Math.max(transExt[0][1], 0)],
|
|
286
500
|
[
|
|
@@ -296,6 +510,7 @@ class TrackRenderer extends React.Component {
|
|
|
296
510
|
}
|
|
297
511
|
|
|
298
512
|
// eslint-disable-next-line camelcase
|
|
513
|
+
/** @param {TrackRendererProps} nextProps */
|
|
299
514
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
300
515
|
/**
|
|
301
516
|
* The size of some tracks probably changed, so let's just
|
|
@@ -336,6 +551,7 @@ class TrackRenderer extends React.Component {
|
|
|
336
551
|
|
|
337
552
|
this.svgElement = nextProps.svgElement;
|
|
338
553
|
|
|
554
|
+
/** @type {[[number, number], [number, number]]} */
|
|
339
555
|
const transExt = [
|
|
340
556
|
[this.xScale(this.xDomainLimits[0]), this.yScale(this.yDomainLimits[0])],
|
|
341
557
|
[this.xScale(this.xDomainLimits[1]), this.yScale(this.yDomainLimits[1])],
|
|
@@ -343,6 +559,7 @@ class TrackRenderer extends React.Component {
|
|
|
343
559
|
|
|
344
560
|
const svgBBox = this.svgElement.getBoundingClientRect();
|
|
345
561
|
|
|
562
|
+
/** @type {[[number, number], [number, number]]} */
|
|
346
563
|
const ext = [
|
|
347
564
|
[Math.max(transExt[0][0], 0), Math.max(transExt[0][1], 0)],
|
|
348
565
|
[
|
|
@@ -365,7 +582,8 @@ class TrackRenderer extends React.Component {
|
|
|
365
582
|
const trackObject = this.trackDefObjects[track.track.uid].trackObject;
|
|
366
583
|
trackObject.rerender(options);
|
|
367
584
|
|
|
368
|
-
if (track.track
|
|
585
|
+
if (isCombinedTrackConfig(track.track)) {
|
|
586
|
+
/** @type {Record<string, TrackConfig>} */
|
|
369
587
|
const ctDefs = {};
|
|
370
588
|
for (const ct of track.track.contents) {
|
|
371
589
|
ctDefs[ct.uid] = ct;
|
|
@@ -386,6 +604,7 @@ class TrackRenderer extends React.Component {
|
|
|
386
604
|
}
|
|
387
605
|
}
|
|
388
606
|
|
|
607
|
+
/** @param {TrackRendererProps} prevProps */
|
|
389
608
|
componentDidUpdate(prevProps) {
|
|
390
609
|
// If the initial domain changed, a new view config
|
|
391
610
|
// probably has loaded. Reset the element's zoomTransform in this case.
|
|
@@ -396,7 +615,7 @@ class TrackRenderer extends React.Component {
|
|
|
396
615
|
this.props.initialYDomain[0] !== prevProps.initialYDomain[0] ||
|
|
397
616
|
this.props.initialYDomain[1] !== prevProps.initialYDomain[1]
|
|
398
617
|
) {
|
|
399
|
-
this.element.__zoom = zoomIdentity;
|
|
618
|
+
if (this.element) this.element.__zoom = zoomIdentity;
|
|
400
619
|
}
|
|
401
620
|
|
|
402
621
|
if (prevProps.isRangeSelection !== this.props.isRangeSelection) {
|
|
@@ -427,10 +646,10 @@ class TrackRenderer extends React.Component {
|
|
|
427
646
|
this.removeMetaTracks(Object.keys(this.metaTracks));
|
|
428
647
|
this.currentProps.removeDraggingChangedListener(this.draggingChanged);
|
|
429
648
|
|
|
430
|
-
this.currentProps.pixiStage.removeChild(this.pStage);
|
|
649
|
+
if (this.pStage) this.currentProps.pixiStage.removeChild(this.pStage);
|
|
431
650
|
|
|
432
|
-
this.pMask
|
|
433
|
-
this.pStage
|
|
651
|
+
this.pMask?.destroy(true);
|
|
652
|
+
this.pStage?.destroy(true);
|
|
434
653
|
|
|
435
654
|
this.pubSubs.forEach((subscription) =>
|
|
436
655
|
this.props.pubSub.unsubscribe(subscription),
|
|
@@ -445,24 +664,22 @@ class TrackRenderer extends React.Component {
|
|
|
445
664
|
/**
|
|
446
665
|
* Dispatch a forwarded event on the main DOM element
|
|
447
666
|
*
|
|
448
|
-
* @param {
|
|
667
|
+
* @param {Event & { sourceUid: string, type: string }} event Event to be dispatched.
|
|
449
668
|
*/
|
|
450
|
-
dispatchEvent(
|
|
451
|
-
if (
|
|
452
|
-
forwardEvent(
|
|
669
|
+
dispatchEvent(event) {
|
|
670
|
+
if (event.sourceUid === this.uid && event.type !== 'contextmenu') {
|
|
671
|
+
if (this.element) forwardEvent(event, this.element);
|
|
453
672
|
}
|
|
454
673
|
}
|
|
455
674
|
|
|
456
675
|
/**
|
|
457
676
|
* Check of a view position (i.e., pixel coords) is within this view
|
|
458
677
|
*
|
|
459
|
-
* @param
|
|
460
|
-
* @param
|
|
461
|
-
* @return
|
|
678
|
+
* @param {number} x - X position to be tested.
|
|
679
|
+
* @param {number} y - Y position to be tested.
|
|
680
|
+
* @return {boolean} If `true` position is within this view.
|
|
462
681
|
*/
|
|
463
682
|
isWithin(x, y) {
|
|
464
|
-
if (!this.element) return false;
|
|
465
|
-
|
|
466
683
|
const withinX =
|
|
467
684
|
x >= this.elementPos.left &&
|
|
468
685
|
x <= this.elementPos.width + this.elementPos.left;
|
|
@@ -473,8 +690,9 @@ class TrackRenderer extends React.Component {
|
|
|
473
690
|
return withinX && withinY;
|
|
474
691
|
}
|
|
475
692
|
|
|
476
|
-
|
|
477
|
-
|
|
693
|
+
/** @param {{ pos: [number, number, number, number], animateTime: number }} opts */
|
|
694
|
+
zoomToDataPosHandler({ pos, animateTime }) {
|
|
695
|
+
this.zoomToDataPos(...pos, animateTime);
|
|
478
696
|
}
|
|
479
697
|
|
|
480
698
|
addZoom() {
|
|
@@ -496,15 +714,15 @@ class TrackRenderer extends React.Component {
|
|
|
496
714
|
* don't overflow its bounds.
|
|
497
715
|
*/
|
|
498
716
|
setMask() {
|
|
499
|
-
this.pMask
|
|
500
|
-
this.pMask
|
|
501
|
-
this.pMask
|
|
717
|
+
this.pMask?.clear();
|
|
718
|
+
this.pMask?.beginFill();
|
|
719
|
+
this.pMask?.drawRect(
|
|
502
720
|
this.xPositionOffset,
|
|
503
721
|
this.yPositionOffset,
|
|
504
722
|
this.currentProps.width,
|
|
505
723
|
this.currentProps.height,
|
|
506
724
|
);
|
|
507
|
-
this.pMask
|
|
725
|
+
this.pMask?.endFill();
|
|
508
726
|
|
|
509
727
|
// show the bounds of this view
|
|
510
728
|
/*
|
|
@@ -519,20 +737,18 @@ class TrackRenderer extends React.Component {
|
|
|
519
737
|
setBackground() {
|
|
520
738
|
const defBgColor = this.props.theme === THEME_DARK ? 'black' : 'white';
|
|
521
739
|
const bgColor = colorToHex(
|
|
522
|
-
|
|
523
|
-
this.currentProps.viewOptions.backgroundColor) ||
|
|
524
|
-
defBgColor,
|
|
740
|
+
this.currentProps.viewOptions?.backgroundColor ?? defBgColor,
|
|
525
741
|
);
|
|
526
742
|
|
|
527
|
-
this.pBackground
|
|
528
|
-
this.pBackground
|
|
529
|
-
this.pBackground
|
|
743
|
+
this.pBackground?.clear();
|
|
744
|
+
this.pBackground?.beginFill(bgColor);
|
|
745
|
+
this.pBackground?.drawRect(
|
|
530
746
|
this.xPositionOffset,
|
|
531
747
|
this.yPositionOffset,
|
|
532
748
|
this.currentProps.width,
|
|
533
749
|
this.currentProps.height,
|
|
534
750
|
);
|
|
535
|
-
this.pBackground
|
|
751
|
+
this.pBackground?.endFill();
|
|
536
752
|
}
|
|
537
753
|
|
|
538
754
|
windowScrolled() {
|
|
@@ -547,6 +763,13 @@ class TrackRenderer extends React.Component {
|
|
|
547
763
|
}, SCROLL_TIMEOUT);
|
|
548
764
|
}
|
|
549
765
|
|
|
766
|
+
/**
|
|
767
|
+
* @param {[number, number]} initialXDomain
|
|
768
|
+
* @param {[number, number]} initialYDomain
|
|
769
|
+
* @param {[number, number]} xDomainLimits
|
|
770
|
+
* @param {[number, number]} yDomainLimits
|
|
771
|
+
* @param {[number, number]} zoomLimits
|
|
772
|
+
*/
|
|
550
773
|
setUpInitialScales(
|
|
551
774
|
initialXDomain = [0, 1],
|
|
552
775
|
initialYDomain = [0, 1],
|
|
@@ -644,6 +867,7 @@ class TrackRenderer extends React.Component {
|
|
|
644
867
|
this.currentProps.centerHeight / 2;
|
|
645
868
|
}
|
|
646
869
|
|
|
870
|
+
/** @param {TrackRendererProps} props */
|
|
647
871
|
updatablePropsToString(props) {
|
|
648
872
|
return JSON.stringify({
|
|
649
873
|
positionedTracks: props.positionedTracks,
|
|
@@ -660,6 +884,7 @@ class TrackRenderer extends React.Component {
|
|
|
660
884
|
});
|
|
661
885
|
}
|
|
662
886
|
|
|
887
|
+
/** @param {boolean} draggingStatus */
|
|
663
888
|
draggingChanged(draggingStatus) {
|
|
664
889
|
this.dragging = draggingStatus;
|
|
665
890
|
|
|
@@ -686,6 +911,10 @@ class TrackRenderer extends React.Component {
|
|
|
686
911
|
// if the window is resized, we don't want to change the scale, but we do
|
|
687
912
|
// want to move the center point. this needs to be tempered by the zoom
|
|
688
913
|
// factor so that we keep the visible center point in the center
|
|
914
|
+
if (!this.drawableToDomainX || !this.drawableToDomainY) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
689
918
|
const centerDomainXOffset =
|
|
690
919
|
(this.drawableToDomainX(currentCenterX) -
|
|
691
920
|
this.drawableToDomainX(this.prevCenterX)) /
|
|
@@ -715,11 +944,11 @@ class TrackRenderer extends React.Component {
|
|
|
715
944
|
// if the screen has been resized, then the domain width should remain the same
|
|
716
945
|
|
|
717
946
|
// this.xScale should always span the region that the zoom behavior is being called on
|
|
718
|
-
this.
|
|
947
|
+
this._xScale = scaleLinear()
|
|
719
948
|
.domain(visibleXDomain)
|
|
720
949
|
.range([0, this.currentProps.width]);
|
|
721
950
|
|
|
722
|
-
this.
|
|
951
|
+
this._yScale = scaleLinear()
|
|
723
952
|
.domain(visibleYDomain)
|
|
724
953
|
.range([0, this.currentProps.height]);
|
|
725
954
|
|
|
@@ -736,6 +965,8 @@ class TrackRenderer extends React.Component {
|
|
|
736
965
|
|
|
737
966
|
/**
|
|
738
967
|
* Get a track's viewconf definition by its object
|
|
968
|
+
*
|
|
969
|
+
* @param {TrackObject} trackObjectIn
|
|
739
970
|
*/
|
|
740
971
|
getTrackDef(trackObjectIn) {
|
|
741
972
|
const trackDefItems = dictItems(this.trackDefObjects);
|
|
@@ -744,7 +975,7 @@ class TrackRenderer extends React.Component {
|
|
|
744
975
|
if (trackObject === trackObjectIn) {
|
|
745
976
|
return trackDef.track;
|
|
746
977
|
}
|
|
747
|
-
if (trackDef.track
|
|
978
|
+
if (isCombinedTrackConfig(trackDef.track)) {
|
|
748
979
|
// this is a combined track
|
|
749
980
|
for (const subTrackDef of trackDef.track.contents) {
|
|
750
981
|
if (trackObject.createdTracks[subTrackDef.uid] === trackObjectIn) {
|
|
@@ -757,8 +988,11 @@ class TrackRenderer extends React.Component {
|
|
|
757
988
|
return null;
|
|
758
989
|
}
|
|
759
990
|
|
|
760
|
-
|
|
991
|
+
/**
|
|
761
992
|
* Fetch the trackObject for a track with a given ID
|
|
993
|
+
*
|
|
994
|
+
* @param {string} trackId
|
|
995
|
+
* @return {TrackObject | undefined}
|
|
762
996
|
*/
|
|
763
997
|
getTrackObject(trackId) {
|
|
764
998
|
const trackDefItems = dictItems(this.trackDefObjects);
|
|
@@ -817,6 +1051,9 @@ class TrackRenderer extends React.Component {
|
|
|
817
1051
|
}
|
|
818
1052
|
}
|
|
819
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* @param {Array<TrackConfig>} trackDefinitions
|
|
1056
|
+
*/
|
|
820
1057
|
syncMetaTracks(trackDefinitions) {
|
|
821
1058
|
const knownMetaTrackIds = Object.keys(this.metaTracks);
|
|
822
1059
|
const newMetaTracks = new Set(trackDefinitions.map((def) => def.uid));
|
|
@@ -837,6 +1074,9 @@ class TrackRenderer extends React.Component {
|
|
|
837
1074
|
);
|
|
838
1075
|
}
|
|
839
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* @param {Array<TrackDefinition>} trackDefinitions
|
|
1079
|
+
*/
|
|
840
1080
|
syncTrackObjects(trackDefinitions) {
|
|
841
1081
|
/**
|
|
842
1082
|
* Make sure we have a track object for every passed track definition.
|
|
@@ -858,6 +1098,7 @@ class TrackRenderer extends React.Component {
|
|
|
858
1098
|
*/
|
|
859
1099
|
this.prevTrackDefinitions = JSON.stringify(trackDefinitions);
|
|
860
1100
|
|
|
1101
|
+
/** @type {Record<string, TrackDefinition>} */
|
|
861
1102
|
const receivedTracksDict = {};
|
|
862
1103
|
for (let i = 0; i < trackDefinitions.length; i++) {
|
|
863
1104
|
receivedTracksDict[trackDefinitions[i].track.uid] = trackDefinitions[i];
|
|
@@ -899,7 +1140,7 @@ class TrackRenderer extends React.Component {
|
|
|
899
1140
|
/**
|
|
900
1141
|
* Add new meta tracks
|
|
901
1142
|
*
|
|
902
|
-
* @param
|
|
1143
|
+
* @param {Array<TrackConfig>} metaTrackDefs Definitions of meta tracks to be added.
|
|
903
1144
|
*/
|
|
904
1145
|
addMetaTracks(metaTrackDefs) {
|
|
905
1146
|
metaTrackDefs
|
|
@@ -912,6 +1153,9 @@ class TrackRenderer extends React.Component {
|
|
|
912
1153
|
});
|
|
913
1154
|
}
|
|
914
1155
|
|
|
1156
|
+
/**
|
|
1157
|
+
* @param {Array<TrackDefinition>} newTrackDefinitions
|
|
1158
|
+
*/
|
|
915
1159
|
addNewTracks(newTrackDefinitions) {
|
|
916
1160
|
/**
|
|
917
1161
|
* We need to create new track objects for the given track
|
|
@@ -919,12 +1163,16 @@ class TrackRenderer extends React.Component {
|
|
|
919
1163
|
*/
|
|
920
1164
|
if (!this.currentProps.pixiStage) {
|
|
921
1165
|
return;
|
|
922
|
-
}
|
|
1166
|
+
}
|
|
1167
|
+
// we need a pixi stage to start rendering
|
|
923
1168
|
// the parent component where it lives probably
|
|
924
1169
|
// hasn't been mounted yet
|
|
925
1170
|
|
|
926
1171
|
for (let i = 0; i < newTrackDefinitions.length; i++) {
|
|
927
1172
|
const newTrackDef = newTrackDefinitions[i];
|
|
1173
|
+
|
|
1174
|
+
/** @type {TrackObject} */
|
|
1175
|
+
// @ts-expect-error - FIXME: Should not need to lie about the return type from createTrackObject.
|
|
928
1176
|
const newTrackObj = this.createTrackObject(newTrackDef.track);
|
|
929
1177
|
|
|
930
1178
|
// newTrackObj.refXScale(this.xScale);
|
|
@@ -949,21 +1197,23 @@ class TrackRenderer extends React.Component {
|
|
|
949
1197
|
this.applyZoomTransform(false);
|
|
950
1198
|
}
|
|
951
1199
|
|
|
952
|
-
|
|
1200
|
+
/** @param {unknown} _unused */
|
|
1201
|
+
updateMetaTracks(_unused) {
|
|
953
1202
|
// Nothing
|
|
954
1203
|
}
|
|
955
1204
|
|
|
1205
|
+
/** @param {Array<TrackDefinition>} newTrackDefs */
|
|
956
1206
|
updateExistingTrackDefs(newTrackDefs) {
|
|
957
|
-
for (
|
|
958
|
-
this.trackDefObjects[
|
|
959
|
-
|
|
1207
|
+
for (const trackDef of newTrackDefs) {
|
|
1208
|
+
const ref = this.trackDefObjects[trackDef.track.uid];
|
|
1209
|
+
ref.trackDef = trackDef;
|
|
960
1210
|
|
|
961
1211
|
// if it's a CombinedTrack, we have to see if its contents have changed
|
|
962
1212
|
// e.g. somebody may have added a new Series
|
|
963
|
-
if (
|
|
964
|
-
|
|
1213
|
+
if (isCombinedTrackConfig(trackDef.track)) {
|
|
1214
|
+
ref.trackObject
|
|
965
1215
|
.updateContents(
|
|
966
|
-
|
|
1216
|
+
trackDef.track.contents,
|
|
967
1217
|
this.createTrackObject.bind(this),
|
|
968
1218
|
)
|
|
969
1219
|
.refScalesChanged(this.xScale, this.yScale);
|
|
@@ -988,10 +1238,13 @@ class TrackRenderer extends React.Component {
|
|
|
988
1238
|
const prevPosition = trackObject.position;
|
|
989
1239
|
const prevDimensions = trackObject.dimensions;
|
|
990
1240
|
|
|
1241
|
+
/** @type {[number, number]} */
|
|
991
1242
|
const newPosition = [
|
|
992
1243
|
this.xPositionOffset + trackDef.left,
|
|
993
1244
|
this.yPositionOffset + trackDef.top,
|
|
994
1245
|
];
|
|
1246
|
+
|
|
1247
|
+
/** @type {[number, number]} */
|
|
995
1248
|
const newDimensions = [trackDef.width, trackDef.height];
|
|
996
1249
|
|
|
997
1250
|
// check if any of the track's positions have changed
|
|
@@ -1024,14 +1277,17 @@ class TrackRenderer extends React.Component {
|
|
|
1024
1277
|
return updated;
|
|
1025
1278
|
}
|
|
1026
1279
|
|
|
1280
|
+
/** @param {string[]} trackIds */
|
|
1027
1281
|
removeMetaTracks(trackIds) {
|
|
1028
1282
|
trackIds.forEach((id) => {
|
|
1029
1283
|
this.metaTracks[id].trackObject.remove();
|
|
1284
|
+
// @ts-expect-error - We are deleting the track object here
|
|
1030
1285
|
this.metaTracks[id] = undefined;
|
|
1031
1286
|
delete this.metaTracks[id];
|
|
1032
1287
|
});
|
|
1033
1288
|
}
|
|
1034
1289
|
|
|
1290
|
+
/** @param {string[]} trackUids */
|
|
1035
1291
|
removeTracks(trackUids) {
|
|
1036
1292
|
for (let i = 0; i < trackUids.length; i++) {
|
|
1037
1293
|
this.trackDefObjects[trackUids[i]].trackObject.remove();
|
|
@@ -1047,10 +1303,10 @@ class TrackRenderer extends React.Component {
|
|
|
1047
1303
|
* @param {boolean} notify If `true` notify listeners that the scales
|
|
1048
1304
|
* have changed. This can be turned off to prevent circular updates when
|
|
1049
1305
|
* scales are locked.
|
|
1050
|
-
* @param {boolean} animate If `true` transition smoothly from the
|
|
1051
|
-
* current to the desired location.
|
|
1052
1306
|
* @param {number} animateTime Animation time in milliseconds. Only used
|
|
1053
1307
|
* when `animate` is true.
|
|
1308
|
+
* @param {Scale} xScale The scale to use for the X axis.
|
|
1309
|
+
* @param {Scale} yScale The scale to use for the Y axis.
|
|
1054
1310
|
*/
|
|
1055
1311
|
setCenter(
|
|
1056
1312
|
centerX,
|
|
@@ -1080,6 +1336,7 @@ class TrackRenderer extends React.Component {
|
|
|
1080
1336
|
const translateX = middleViewX - xScale(centerX) * k;
|
|
1081
1337
|
const translateY = middleViewY - yScale(centerY) * k;
|
|
1082
1338
|
|
|
1339
|
+
/** @type {[Scale, Scale] | undefined} */
|
|
1083
1340
|
let last;
|
|
1084
1341
|
|
|
1085
1342
|
const setZoom = () => {
|
|
@@ -1088,18 +1345,21 @@ class TrackRenderer extends React.Component {
|
|
|
1088
1345
|
.scale(k);
|
|
1089
1346
|
|
|
1090
1347
|
this.zoomTransform = newTransform;
|
|
1091
|
-
|
|
1348
|
+
if (this.elementSelection) {
|
|
1349
|
+
this.emptyZoomBehavior.transform(this.elementSelection, newTransform);
|
|
1350
|
+
}
|
|
1092
1351
|
|
|
1093
1352
|
last = this.applyZoomTransform(notify);
|
|
1094
1353
|
};
|
|
1095
1354
|
|
|
1096
|
-
if (animateTime) {
|
|
1355
|
+
if (animateTime && this.elementSelection) {
|
|
1097
1356
|
let selection = this.elementSelection;
|
|
1098
1357
|
|
|
1099
1358
|
this.activeTransitions += 1;
|
|
1100
1359
|
|
|
1101
1360
|
if (!document.hidden) {
|
|
1102
1361
|
// only transition if the window is hidden
|
|
1362
|
+
// @ts-expect-error - Returns a TransitionSelection, which should be OK to use below
|
|
1103
1363
|
selection = selection.transition().duration(animateTime);
|
|
1104
1364
|
}
|
|
1105
1365
|
|
|
@@ -1119,21 +1379,37 @@ class TrackRenderer extends React.Component {
|
|
|
1119
1379
|
return last;
|
|
1120
1380
|
}
|
|
1121
1381
|
|
|
1382
|
+
/** @param {number} movement */
|
|
1122
1383
|
valueScaleMove(movement) {
|
|
1384
|
+
if (!this.zoomStartPos) {
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1123
1387
|
// mouse wheel from zoom event
|
|
1124
1388
|
// const cp = pointer(event.sourceEvent, this.props.canvasElement);
|
|
1125
1389
|
for (const track of this.getTracksAtPosition(...this.zoomStartPos)) {
|
|
1126
1390
|
track.movedY(movement);
|
|
1127
1391
|
}
|
|
1128
1392
|
|
|
1129
|
-
this.zoomTransform = this.zoomStartTransform;
|
|
1393
|
+
if (this.zoomStartTransform) this.zoomTransform = this.zoomStartTransform;
|
|
1130
1394
|
}
|
|
1131
1395
|
|
|
1396
|
+
/**
|
|
1397
|
+
* @param {{ sourceEvent: Event }} event
|
|
1398
|
+
* @param {string | null} orientation
|
|
1399
|
+
*/
|
|
1132
1400
|
valueScaleZoom(event, orientation) {
|
|
1133
1401
|
// mouse move probably from a drag event
|
|
1402
|
+
if (!isWheelEvent(event.sourceEvent)) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1134
1405
|
const mdy = event.sourceEvent.deltaY;
|
|
1135
1406
|
const mdm = event.sourceEvent.deltaMode;
|
|
1136
1407
|
|
|
1408
|
+
/**
|
|
1409
|
+
* @param {number} dy
|
|
1410
|
+
* @param {number} dm
|
|
1411
|
+
* @return {number}
|
|
1412
|
+
*/
|
|
1137
1413
|
const myWheelDelta = (dy, dm) => (dy * (dm ? 120 : 1)) / 500;
|
|
1138
1414
|
const mwd = myWheelDelta(mdy, mdm);
|
|
1139
1415
|
|
|
@@ -1148,7 +1424,7 @@ class TrackRenderer extends React.Component {
|
|
|
1148
1424
|
}
|
|
1149
1425
|
|
|
1150
1426
|
// reset the zoom transform
|
|
1151
|
-
this.zoomTransform = this.zoomStartTransform;
|
|
1427
|
+
if (this.zoomStartTransform) this.zoomTransform = this.zoomStartTransform;
|
|
1152
1428
|
}
|
|
1153
1429
|
|
|
1154
1430
|
/**
|
|
@@ -1156,11 +1432,14 @@ class TrackRenderer extends React.Component {
|
|
|
1156
1432
|
*
|
|
1157
1433
|
* We need to update our local record of the zoom transform and apply it
|
|
1158
1434
|
* to all the tracks.
|
|
1435
|
+
*
|
|
1436
|
+
* @param {import("d3-zoom").D3ZoomEvent<HTMLElement, unknown> & { shiftKey?: boolean }} event
|
|
1159
1437
|
*/
|
|
1160
1438
|
zoomed(event) {
|
|
1161
1439
|
// the orientation of the track where we started zooming
|
|
1162
1440
|
// if it's a 1d-horizontal, then mousemove events shouldn't
|
|
1163
1441
|
// move the center track vertically
|
|
1442
|
+
/** @type {string | null} */
|
|
1164
1443
|
let trackOrientation = null;
|
|
1165
1444
|
|
|
1166
1445
|
// see what orientation of track we're over so that we decide
|
|
@@ -1171,7 +1450,11 @@ class TrackRenderer extends React.Component {
|
|
|
1171
1450
|
const trackAtZoomStart = tracksAtZoomStart[0];
|
|
1172
1451
|
const trackDef = this.getTrackDef(trackAtZoomStart);
|
|
1173
1452
|
|
|
1174
|
-
if (
|
|
1453
|
+
if (!trackDef) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (TRACKS_INFO_BY_TYPE[trackDef.type]?.orientation) {
|
|
1175
1458
|
// some track types (like overlay-track don't have a track info)
|
|
1176
1459
|
trackOrientation = TRACKS_INFO_BY_TYPE[trackDef.type].orientation;
|
|
1177
1460
|
}
|
|
@@ -1231,8 +1514,7 @@ class TrackRenderer extends React.Component {
|
|
|
1231
1514
|
.translate(this.prevZoomTransform.x, this.zoomTransform.y)
|
|
1232
1515
|
.scale(this.zoomTransform.k);
|
|
1233
1516
|
}
|
|
1234
|
-
|
|
1235
|
-
this.element.__zoom = this.zoomTransform;
|
|
1517
|
+
if (this.element) this.element.__zoom = this.zoomTransform;
|
|
1236
1518
|
}
|
|
1237
1519
|
|
|
1238
1520
|
this.applyZoomTransform(true);
|
|
@@ -1250,13 +1532,15 @@ class TrackRenderer extends React.Component {
|
|
|
1250
1532
|
*
|
|
1251
1533
|
* The position should be relative to this.props.canvasElement.
|
|
1252
1534
|
*
|
|
1253
|
-
* @param
|
|
1254
|
-
* @param
|
|
1255
|
-
* @return {Array}
|
|
1535
|
+
* @param {number} x The query x position
|
|
1536
|
+
* @param {number} y The query y position
|
|
1537
|
+
* @return {Array<TrackObject>} An array of tracks at this position
|
|
1256
1538
|
*/
|
|
1257
1539
|
getTracksAtPosition(x, y) {
|
|
1540
|
+
/** @type {Array<TrackObject>} */
|
|
1258
1541
|
const foundTracks = [];
|
|
1259
1542
|
|
|
1543
|
+
/** @type {Array<TrackObject>} */
|
|
1260
1544
|
let tracksToVisit = [];
|
|
1261
1545
|
|
|
1262
1546
|
for (const uid in this.trackDefObjects) {
|
|
@@ -1283,6 +1567,7 @@ class TrackRenderer extends React.Component {
|
|
|
1283
1567
|
return foundTracks;
|
|
1284
1568
|
}
|
|
1285
1569
|
|
|
1570
|
+
/** @param {import('d3-zoom').D3ZoomEvent<HTMLElement, unknown>=} event */
|
|
1286
1571
|
zoomStarted(event) {
|
|
1287
1572
|
this.zooming = true;
|
|
1288
1573
|
|
|
@@ -1307,18 +1592,26 @@ class TrackRenderer extends React.Component {
|
|
|
1307
1592
|
|
|
1308
1593
|
if (this.valueScaleZooming) {
|
|
1309
1594
|
this.valueScaleZooming = false;
|
|
1310
|
-
this.element.__zoom = this.zoomStartTransform;
|
|
1595
|
+
if (this.element) this.element.__zoom = this.zoomStartTransform;
|
|
1311
1596
|
}
|
|
1312
1597
|
|
|
1313
1598
|
this.props.pubSub.publish('app.zoomEnd');
|
|
1314
1599
|
}
|
|
1315
1600
|
|
|
1601
|
+
/**
|
|
1602
|
+
* @param {boolean=} notify
|
|
1603
|
+
* @returns {[Scale, Scale] | undefined}
|
|
1604
|
+
*/
|
|
1316
1605
|
applyZoomTransform(notify = true) {
|
|
1317
1606
|
const props = this.currentProps;
|
|
1318
1607
|
const paddingleft = props.paddingLeft + props.leftWidth;
|
|
1319
1608
|
const paddingTop = props.paddingTop + props.topHeight;
|
|
1320
1609
|
|
|
1321
1610
|
// These props are apparently used elsewhere, for example the context menu
|
|
1611
|
+
if (!this.xScale || !this.yScale) {
|
|
1612
|
+
return undefined;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1322
1615
|
this.zoomedXScale = this.zoomTransform.rescaleX(this.xScale);
|
|
1323
1616
|
this.zoomedYScale = this.zoomTransform.rescaleY(this.yScale);
|
|
1324
1617
|
|
|
@@ -1428,13 +1721,15 @@ class TrackRenderer extends React.Component {
|
|
|
1428
1721
|
return [newXScale, newYScale];
|
|
1429
1722
|
}
|
|
1430
1723
|
|
|
1724
|
+
/** @param {TrackConfig} track */
|
|
1431
1725
|
createMetaTrack(track) {
|
|
1432
1726
|
switch (track.type) {
|
|
1433
1727
|
default: {
|
|
1434
1728
|
// Check if a plugin track is available
|
|
1435
1729
|
const pluginTrack = this.props.pluginTracks[track.type];
|
|
1436
1730
|
|
|
1437
|
-
if (pluginTrack
|
|
1731
|
+
if (pluginTrack?.isMetaTrack) {
|
|
1732
|
+
/** @type {MetaPluginTrackContext} */
|
|
1438
1733
|
const context = {
|
|
1439
1734
|
getTrackObject: this.getTrackObject.bind(this),
|
|
1440
1735
|
onNewTilesLoaded: () => {
|
|
@@ -1459,15 +1754,15 @@ class TrackRenderer extends React.Component {
|
|
|
1459
1754
|
}
|
|
1460
1755
|
|
|
1461
1756
|
console.warn(`Unknown meta track of type: ${track.type}`);
|
|
1462
|
-
return new UnknownPixiTrack(
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
);
|
|
1757
|
+
return new UnknownPixiTrack(this.pStage, {
|
|
1758
|
+
name: 'Unknown Track Type',
|
|
1759
|
+
type: track.type,
|
|
1760
|
+
});
|
|
1467
1761
|
}
|
|
1468
1762
|
}
|
|
1469
1763
|
}
|
|
1470
1764
|
|
|
1765
|
+
/** @param {TrackConfig} track */
|
|
1471
1766
|
createTrackObject(track) {
|
|
1472
1767
|
const trackObject = this.createLocationAgnosticTrackObject(track);
|
|
1473
1768
|
if (track.position === 'left' || track.position === 'right') {
|
|
@@ -1478,11 +1773,8 @@ class TrackRenderer extends React.Component {
|
|
|
1478
1773
|
return trackObject;
|
|
1479
1774
|
}
|
|
1480
1775
|
|
|
1776
|
+
/** @param {TrackConfig} track */
|
|
1481
1777
|
createLocationAgnosticTrackObject(track) {
|
|
1482
|
-
const handleTilesetInfoReceived = (x) => {
|
|
1483
|
-
this.currentProps.onTilesetInfoReceived(track.uid, x);
|
|
1484
|
-
};
|
|
1485
|
-
|
|
1486
1778
|
// See if this track has a data config section.
|
|
1487
1779
|
// If it doesn't, we assume that it has the standard
|
|
1488
1780
|
// server / tilesetUid sections
|
|
@@ -1507,7 +1799,13 @@ class TrackRenderer extends React.Component {
|
|
|
1507
1799
|
this.availableForPlugins,
|
|
1508
1800
|
);
|
|
1509
1801
|
|
|
1802
|
+
// FIXME: non-null assert?
|
|
1803
|
+
if (!this.pStage || !this.svgElement) {
|
|
1804
|
+
throw new Error('No PIXI stage or svg element');
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1510
1807
|
// To simplify the context creation via ES6 object shortcuts.
|
|
1808
|
+
/** @type {PluginTrackContext} */
|
|
1511
1809
|
const context = {
|
|
1512
1810
|
id: track.uid,
|
|
1513
1811
|
trackUid: track.uid,
|
|
@@ -1519,7 +1817,9 @@ class TrackRenderer extends React.Component {
|
|
|
1519
1817
|
dataFetcher,
|
|
1520
1818
|
getLockGroupExtrema: () =>
|
|
1521
1819
|
this.currentProps.getLockGroupExtrema(track.uid),
|
|
1522
|
-
handleTilesetInfoReceived
|
|
1820
|
+
handleTilesetInfoReceived: (tilesetInfo) => {
|
|
1821
|
+
this.currentProps.onTilesetInfoReceived(track.uid, tilesetInfo);
|
|
1822
|
+
},
|
|
1523
1823
|
animate: () => {
|
|
1524
1824
|
this.currentProps.onNewTilesLoaded(track.uid);
|
|
1525
1825
|
},
|
|
@@ -1627,7 +1927,7 @@ class TrackRenderer extends React.Component {
|
|
|
1627
1927
|
context.setDomainsCallback = track.setDomainsCallback;
|
|
1628
1928
|
return new ViewportTracker2D(context, options);
|
|
1629
1929
|
}
|
|
1630
|
-
return new Track(context
|
|
1930
|
+
return new Track(context);
|
|
1631
1931
|
|
|
1632
1932
|
case 'viewport-projection-horizontal':
|
|
1633
1933
|
// TODO: Fix this so that these functions are defined somewhere else
|
|
@@ -1641,7 +1941,7 @@ class TrackRenderer extends React.Component {
|
|
|
1641
1941
|
context.setDomainsCallback = track.setDomainsCallback;
|
|
1642
1942
|
return new ViewportTrackerHorizontal(context, options);
|
|
1643
1943
|
}
|
|
1644
|
-
return new Track(context
|
|
1944
|
+
return new Track(context);
|
|
1645
1945
|
|
|
1646
1946
|
case 'viewport-projection-vertical':
|
|
1647
1947
|
// TODO: Fix this so that these functions are defined somewhere else
|
|
@@ -1655,7 +1955,7 @@ class TrackRenderer extends React.Component {
|
|
|
1655
1955
|
context.setDomainsCallback = track.setDomainsCallback;
|
|
1656
1956
|
return new ViewportTrackerVertical(context, options);
|
|
1657
1957
|
}
|
|
1658
|
-
return new Track(context
|
|
1958
|
+
return new Track(context);
|
|
1659
1959
|
|
|
1660
1960
|
case 'gene-annotations':
|
|
1661
1961
|
case 'horizontal-gene-annotations': // legacy, included for backwards compatiblity
|
|
@@ -1686,9 +1986,11 @@ class TrackRenderer extends React.Component {
|
|
|
1686
1986
|
return new SquareMarkersTrack(context, options);
|
|
1687
1987
|
|
|
1688
1988
|
case 'combined':
|
|
1989
|
+
// @ts-expect-error - FIXME: Our typing should be able to narrow track config
|
|
1990
|
+
// based on the type, but this isn't communicated in the type system yet.
|
|
1689
1991
|
context.tracks = track.contents;
|
|
1690
1992
|
context.createTrackObject = this.createTrackObject.bind(this);
|
|
1691
|
-
return new CombinedTrack(context
|
|
1993
|
+
return new CombinedTrack(context);
|
|
1692
1994
|
|
|
1693
1995
|
case '2d-chromosome-labels':
|
|
1694
1996
|
return new Chromosome2DLabels(context, options);
|
|
@@ -1818,7 +2120,7 @@ class TrackRenderer extends React.Component {
|
|
|
1818
2120
|
* @param {number} dataYStart Data start Y coordinate.
|
|
1819
2121
|
* @param {number} dataYEnd Data end Y coordinate.
|
|
1820
2122
|
* @param {number} animateTime Animation time in milliseconds.
|
|
1821
|
-
* @param {
|
|
2123
|
+
* @param {ProjectorFunction | null} projector If not `null` a projector function that
|
|
1822
2124
|
* provides adjusted x and y scales.
|
|
1823
2125
|
*/
|
|
1824
2126
|
zoomToDataPos(
|
|
@@ -1849,6 +2151,9 @@ class TrackRenderer extends React.Component {
|
|
|
1849
2151
|
);
|
|
1850
2152
|
}
|
|
1851
2153
|
|
|
2154
|
+
/**
|
|
2155
|
+
* @param {{ altKey: boolean, preventDefault: () => void }} e
|
|
2156
|
+
*/
|
|
1852
2157
|
forwardContextMenu(e) {
|
|
1853
2158
|
// Do never forward the contextmenu event when ALT is being hold down.
|
|
1854
2159
|
if (this.props.disableTrackMenu || e.altKey) return;
|
|
@@ -1958,15 +2263,24 @@ class TrackRenderer extends React.Component {
|
|
|
1958
2263
|
}
|
|
1959
2264
|
|
|
1960
2265
|
scrollEvent() {
|
|
2266
|
+
if (!this.element) return;
|
|
1961
2267
|
this.elementPos = this.element.getBoundingClientRect();
|
|
1962
2268
|
}
|
|
1963
2269
|
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2270
|
+
/**
|
|
2271
|
+
* Publishes an event to the pubSub channel, first overriding the
|
|
2272
|
+
* sourceUid to be the uid of this track renderer.
|
|
2273
|
+
*
|
|
2274
|
+
* @template {Event} T
|
|
2275
|
+
* @param {T & { sourceUid?: string; forwarded?: boolean }} event
|
|
2276
|
+
*/
|
|
2277
|
+
forwardEvent(event) {
|
|
2278
|
+
event.sourceUid = this.uid;
|
|
2279
|
+
event.forwarded = true;
|
|
2280
|
+
this.props.pubSub.publish('app.event', event);
|
|
1968
2281
|
}
|
|
1969
2282
|
|
|
2283
|
+
/** @param {number} scrollTop */
|
|
1970
2284
|
onScrollHandler(scrollTop) {
|
|
1971
2285
|
this.scrollTop = scrollTop;
|
|
1972
2286
|
}
|
|
@@ -2072,6 +2386,8 @@ TrackRenderer.propTypes = {
|
|
|
2072
2386
|
valueScaleZoom: PropTypes.bool,
|
|
2073
2387
|
zoomable: PropTypes.bool.isRequired,
|
|
2074
2388
|
zoomDomain: PropTypes.array,
|
|
2389
|
+
uid: PropTypes.string,
|
|
2390
|
+
zoomLimits: PropTypes.array,
|
|
2075
2391
|
};
|
|
2076
2392
|
|
|
2077
2393
|
export default withPubSub(withTheme(TrackRenderer));
|