higlass 1.13.0 → 1.13.1
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/app/scripts/PixiTrack.js +106 -42
- package/app/scripts/Track.js +153 -45
- package/app/scripts/TrackRenderer.jsx +54 -53
- package/app/scripts/hocs/with-pub-sub.jsx +3 -1
- package/app/scripts/types.ts +26 -0
- package/app/scripts/utils/type-guards.js +24 -0
- package/dist/esm.html +283 -0
- package/dist/hglib.js +42 -82877
- package/dist/hglib.min.js +86 -105
- package/dist/higlass.mjs +207 -0
- package/dist/index.html +6 -6
- package/package.json +8 -1
package/app/scripts/PixiTrack.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { formatPrefix, precisionPrefix } from 'd3-format';
|
|
2
3
|
import slugid from 'slugid';
|
|
3
4
|
|
|
@@ -7,14 +8,18 @@ import { colorToHex } from './utils';
|
|
|
7
8
|
|
|
8
9
|
// Configs
|
|
9
10
|
import { GLOBALS } from './configs';
|
|
11
|
+
import {
|
|
12
|
+
isResolutionsTilesetInfo,
|
|
13
|
+
isLegacyTilesetInfo,
|
|
14
|
+
} from './utils/type-guards';
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Format a resolution relative to the highest possible resolution.
|
|
13
18
|
*
|
|
14
19
|
* The highest possible resolution determines the granularity of the
|
|
15
20
|
* formatting (e.g. 20K vs 20000)
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {
|
|
21
|
+
* @param {number} resolution The resolution to format (e.g. 30000)
|
|
22
|
+
* @param {number} maxResolutionSize The maximum possible resolution (e.g. 1000)
|
|
18
23
|
*
|
|
19
24
|
* @returns {string} A formatted resolution string (e.g. "30K")
|
|
20
25
|
*/
|
|
@@ -30,11 +35,10 @@ function formatResolutionText(resolution, maxResolutionSize) {
|
|
|
30
35
|
* Get a text description of a resolution based on a zoom level
|
|
31
36
|
* and a list of resolutions
|
|
32
37
|
*
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {
|
|
38
|
+
* @param {Array<number>} resolutions: A list of resolutions (e.g. [1000,2000,3000])
|
|
39
|
+
* @param {number} zoomLevel: The current zoom level (e.g. 4)
|
|
35
40
|
*
|
|
36
|
-
* @returns {string} A formatted string representation of the zoom level
|
|
37
|
-
* (e.g. "30K")
|
|
41
|
+
* @returns {string} A formatted string representation of the zoom level (e.g. "30K")
|
|
38
42
|
*/
|
|
39
43
|
function getResolutionBasedResolutionText(resolutions, zoomLevel) {
|
|
40
44
|
const sortedResolutions = resolutions.map((x) => +x).sort((a, b) => b - a);
|
|
@@ -46,18 +50,14 @@ function getResolutionBasedResolutionText(resolutions, zoomLevel) {
|
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
52
|
* Get a text description of the resolution based on the zoom level
|
|
49
|
-
* max width of the dataset, the bins per dimension and the maximum
|
|
50
|
-
* zoom.
|
|
53
|
+
* max width of the dataset, the bins per dimension and the maximum zoom.
|
|
51
54
|
*
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {
|
|
54
|
-
*
|
|
55
|
-
* @param {
|
|
56
|
-
* (e.g. 256)
|
|
57
|
-
* @param {int} maxZoom The maximum zoom level for this tileset
|
|
55
|
+
* @param {number} zoomLevel - The current zoomLevel (e.g. 0)
|
|
56
|
+
* @param {number} maxWidth - The max width (e.g. 2 ** maxZoom * highestResolution * binsPerDimension)
|
|
57
|
+
* @param {number} binsPerDimension - The number of bins per tile dimension (e.g. 256)
|
|
58
|
+
* @param {number} maxZoom - The maximum zoom level for this tileset
|
|
58
59
|
*
|
|
59
|
-
* @returns {string} A formatted string representation of the zoom level
|
|
60
|
-
* (e.g. "30K")
|
|
60
|
+
* @returns {string} A formatted string representation of the zoom level (e.g. "30K")
|
|
61
61
|
*/
|
|
62
62
|
function getWidthBasedResolutionText(
|
|
63
63
|
zoomLevel,
|
|
@@ -84,14 +84,35 @@ function getWidthBasedResolutionText(
|
|
|
84
84
|
return '';
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @typedef PixiTrackOptions
|
|
89
|
+
* @property {string} labelPosition - If the label is to be drawn, where should it be drawn?
|
|
90
|
+
* @property {string} labelText - What should be drawn in the label.
|
|
91
|
+
* If either labelPosition or labelText are false, no label will be drawn.
|
|
92
|
+
* @property {number=} trackBorderWidth
|
|
93
|
+
* @property {string=} trackBorderColor
|
|
94
|
+
* @property {string=} backgroundColor
|
|
95
|
+
* @property {string=} labelColor
|
|
96
|
+
* @property {string=} lineStrokeColor
|
|
97
|
+
* @property {string=} barFillColor
|
|
98
|
+
* @property {string=} name
|
|
99
|
+
* @property {number=} labelTextOpacity
|
|
100
|
+
* @property {string=} labelBackgroundColor
|
|
101
|
+
* @property {number=} labelLeftMargin
|
|
102
|
+
* @property {number=} labelRightMargin
|
|
103
|
+
* @property {number=} labelTopMargin
|
|
104
|
+
* @property {number=} labelBottomMargin
|
|
105
|
+
* @property {number=} labelBackgroundOpacity
|
|
106
|
+
* @property {boolean=} labelShowAssembly
|
|
107
|
+
* @property {boolean=} labelShowResolution
|
|
108
|
+
* @property {string=} dataTransform
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/** @extends {Track<PixiTrackOptions>} */
|
|
87
112
|
class PixiTrack extends Track {
|
|
88
113
|
/**
|
|
89
|
-
* @param scene:
|
|
90
|
-
* @param options
|
|
91
|
-
this.pMain.position.x = this.position[0];
|
|
92
|
-
* - labelPosition: If the label is to be drawn, where should it be drawn?
|
|
93
|
-
* - labelText: What should be drawn in the label. If either labelPosition
|
|
94
|
-
* or labelText are false, no label will be drawn.
|
|
114
|
+
* @param {import('./Track').ExtendedTrackContext<{ scene: import('pixi.js').Container}>} context - Includes the PIXI.js scene to draw to.
|
|
115
|
+
* @param {PixiTrackOptions} options - The options for this track.
|
|
95
116
|
*/
|
|
96
117
|
constructor(context, options) {
|
|
97
118
|
super(context, options);
|
|
@@ -99,27 +120,39 @@ class PixiTrack extends Track {
|
|
|
99
120
|
|
|
100
121
|
// the PIXI drawing areas
|
|
101
122
|
// pMain will have transforms applied to it as users scroll to and fro
|
|
123
|
+
/** @type {import('pixi.js').Container} */
|
|
102
124
|
this.scene = scene;
|
|
103
125
|
|
|
104
126
|
// this option is used to temporarily prevent drawing so that
|
|
105
127
|
// updates can be batched (e.g. zoomed and options changed)
|
|
128
|
+
/** @type {boolean} */
|
|
106
129
|
this.delayDrawing = false;
|
|
107
130
|
|
|
131
|
+
/** @type {import('pixi.js').Graphics} */
|
|
108
132
|
this.pBase = new GLOBALS.PIXI.Graphics();
|
|
109
|
-
|
|
133
|
+
/** @type {import('pixi.js').Graphics} */
|
|
110
134
|
this.pMasked = new GLOBALS.PIXI.Graphics();
|
|
135
|
+
/** @type {import('pixi.js').Graphics} */
|
|
111
136
|
this.pMask = new GLOBALS.PIXI.Graphics();
|
|
137
|
+
/** @type {import('pixi.js').Graphics} */
|
|
112
138
|
this.pMain = new GLOBALS.PIXI.Graphics();
|
|
113
139
|
|
|
114
140
|
// for drawing the track label (often its name)
|
|
141
|
+
/** @type {import('pixi.js').Graphics} */
|
|
115
142
|
this.pBorder = new GLOBALS.PIXI.Graphics();
|
|
143
|
+
/** @type {import('pixi.js').Graphics} */
|
|
116
144
|
this.pBackground = new GLOBALS.PIXI.Graphics();
|
|
145
|
+
/** @type {import('pixi.js').Graphics} */
|
|
117
146
|
this.pForeground = new GLOBALS.PIXI.Graphics();
|
|
147
|
+
/** @type {import('pixi.js').Graphics} */
|
|
118
148
|
this.pLabel = new GLOBALS.PIXI.Graphics();
|
|
149
|
+
/** @type {import('pixi.js').Graphics} */
|
|
119
150
|
this.pMobile = new GLOBALS.PIXI.Graphics();
|
|
151
|
+
/** @type {import('pixi.js').Graphics} */
|
|
120
152
|
this.pAxis = new GLOBALS.PIXI.Graphics();
|
|
121
153
|
|
|
122
154
|
// for drawing information on mouseover events
|
|
155
|
+
/** @type {import('pixi.js').Graphics} */
|
|
123
156
|
this.pMouseOver = new GLOBALS.PIXI.Graphics();
|
|
124
157
|
|
|
125
158
|
this.scene.addChild(this.pBase);
|
|
@@ -138,20 +171,28 @@ class PixiTrack extends Track {
|
|
|
138
171
|
|
|
139
172
|
this.pMasked.mask = this.pMask;
|
|
140
173
|
|
|
174
|
+
/** @type {string} */
|
|
141
175
|
this.prevOptions = '';
|
|
142
176
|
|
|
143
177
|
// pMobile will be a graphics object that is moved around
|
|
144
178
|
// tracks that wish to use it will replace this.pMain with it
|
|
145
179
|
|
|
180
|
+
/** @type {PixiTrackOptions} */
|
|
146
181
|
this.options = Object.assign(this.options, options);
|
|
147
182
|
|
|
183
|
+
/** @type {string} */
|
|
148
184
|
const labelTextText = this.getName();
|
|
149
|
-
|
|
185
|
+
/** @type {string} */
|
|
150
186
|
this.labelTextFontFamily = 'Arial';
|
|
187
|
+
/** @type {number} */
|
|
151
188
|
this.labelTextFontSize = 12;
|
|
152
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Used to avoid label/colormap clashes
|
|
191
|
+
* @type {number}
|
|
192
|
+
*/
|
|
153
193
|
this.labelXOffset = 0;
|
|
154
194
|
|
|
195
|
+
/** @type {import('pixi.js').Text} */
|
|
155
196
|
this.labelText = new GLOBALS.PIXI.Text(labelTextText, {
|
|
156
197
|
fontSize: `${this.labelTextFontSize}px`,
|
|
157
198
|
fontFamily: this.labelTextFontFamily,
|
|
@@ -159,6 +200,7 @@ class PixiTrack extends Track {
|
|
|
159
200
|
});
|
|
160
201
|
this.pLabel.addChild(this.labelText);
|
|
161
202
|
|
|
203
|
+
/** @type {import('pixi.js').Text} */
|
|
162
204
|
this.errorText = new GLOBALS.PIXI.Text('', {
|
|
163
205
|
fontSize: '12px',
|
|
164
206
|
fontFamily: 'Arial',
|
|
@@ -167,12 +209,19 @@ class PixiTrack extends Track {
|
|
|
167
209
|
this.errorText.anchor.x = 0.5;
|
|
168
210
|
this.errorText.anchor.y = 0.5;
|
|
169
211
|
this.pLabel.addChild(this.errorText);
|
|
212
|
+
/** @type {string} */
|
|
213
|
+
this.errorTextText = '';
|
|
214
|
+
/** @type {boolean} */
|
|
215
|
+
this.flipText = false;
|
|
216
|
+
/** @type {import('./types').TilesetInfo | undefined} */
|
|
217
|
+
this.tilesetInfo = undefined;
|
|
170
218
|
}
|
|
171
219
|
|
|
172
220
|
setLabelText() {
|
|
173
221
|
// will be drawn in draw() anyway
|
|
174
222
|
}
|
|
175
223
|
|
|
224
|
+
/** @param {[number, number]} newPosition */
|
|
176
225
|
setPosition(newPosition) {
|
|
177
226
|
this.position = newPosition;
|
|
178
227
|
|
|
@@ -183,6 +232,7 @@ class PixiTrack extends Track {
|
|
|
183
232
|
this.setForeground();
|
|
184
233
|
}
|
|
185
234
|
|
|
235
|
+
/** @param {[number, number]} newDimensions */
|
|
186
236
|
setDimensions(newDimensions) {
|
|
187
237
|
super.setDimensions(newDimensions);
|
|
188
238
|
|
|
@@ -193,6 +243,10 @@ class PixiTrack extends Track {
|
|
|
193
243
|
this.setForeground();
|
|
194
244
|
}
|
|
195
245
|
|
|
246
|
+
/**
|
|
247
|
+
* @param {[number, number]} position
|
|
248
|
+
* @param {[number, number]} dimensions
|
|
249
|
+
*/
|
|
196
250
|
setMask(position, dimensions) {
|
|
197
251
|
this.pMask.clear();
|
|
198
252
|
this.pMask.beginFill();
|
|
@@ -320,6 +374,8 @@ class PixiTrack extends Track {
|
|
|
320
374
|
|
|
321
375
|
graphics.clear();
|
|
322
376
|
|
|
377
|
+
// TODO(Trevor): I don't think this can ever be true. Options are always defined,
|
|
378
|
+
// and options.labelPosition can't be defined if this.options is undefined.
|
|
323
379
|
if (
|
|
324
380
|
!this.options ||
|
|
325
381
|
!this.options.labelPosition ||
|
|
@@ -330,11 +386,11 @@ class PixiTrack extends Track {
|
|
|
330
386
|
return;
|
|
331
387
|
}
|
|
332
388
|
|
|
389
|
+
const { labelBackgroundColor = 'white', labelBackgroundOpacity = 0.5 } =
|
|
390
|
+
this.options;
|
|
333
391
|
graphics.beginFill(
|
|
334
|
-
colorToHex(
|
|
335
|
-
+
|
|
336
|
-
? +this.options.labelBackgroundOpacity
|
|
337
|
-
: 0.5,
|
|
392
|
+
colorToHex(labelBackgroundColor),
|
|
393
|
+
+labelBackgroundOpacity,
|
|
338
394
|
);
|
|
339
395
|
|
|
340
396
|
const fontColor = colorToHex(this.getLabelColor());
|
|
@@ -356,8 +412,7 @@ class PixiTrack extends Track {
|
|
|
356
412
|
|
|
357
413
|
if (
|
|
358
414
|
this.options.labelShowResolution &&
|
|
359
|
-
this.tilesetInfo &&
|
|
360
|
-
this.tilesetInfo.max_width &&
|
|
415
|
+
isLegacyTilesetInfo(this.tilesetInfo) &&
|
|
361
416
|
this.tilesetInfo.bins_per_dimension
|
|
362
417
|
) {
|
|
363
418
|
const formattedResolution = getWidthBasedResolutionText(
|
|
@@ -370,8 +425,7 @@ class PixiTrack extends Track {
|
|
|
370
425
|
labelTextText += `\n[Current data resolution: ${formattedResolution}]`;
|
|
371
426
|
} else if (
|
|
372
427
|
this.options.labelShowResolution &&
|
|
373
|
-
this.tilesetInfo
|
|
374
|
-
this.tilesetInfo.resolutions
|
|
428
|
+
isResolutionsTilesetInfo(this.tilesetInfo)
|
|
375
429
|
) {
|
|
376
430
|
const formattedResolution = getResolutionBasedResolutionText(
|
|
377
431
|
this.tilesetInfo.resolutions,
|
|
@@ -418,10 +472,12 @@ class PixiTrack extends Track {
|
|
|
418
472
|
this.labelText.scale.x = -1;
|
|
419
473
|
}
|
|
420
474
|
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
475
|
+
const {
|
|
476
|
+
labelLeftMargin = 0,
|
|
477
|
+
labelRightMargin = 0,
|
|
478
|
+
labelTopMargin = 0,
|
|
479
|
+
labelBottomMargin = 0,
|
|
480
|
+
} = this.options;
|
|
425
481
|
|
|
426
482
|
if (this.options.labelPosition === 'topLeft') {
|
|
427
483
|
this.labelText.x = this.position[0] + labelLeftMargin + this.labelXOffset;
|
|
@@ -575,6 +631,7 @@ class PixiTrack extends Track {
|
|
|
575
631
|
}
|
|
576
632
|
}
|
|
577
633
|
|
|
634
|
+
/** @param {PixiTrackOptions} options */
|
|
578
635
|
rerender(options) {
|
|
579
636
|
this.options = options;
|
|
580
637
|
|
|
@@ -598,8 +655,8 @@ class PixiTrack extends Track {
|
|
|
598
655
|
/**
|
|
599
656
|
* Export an SVG representation of this track
|
|
600
657
|
*
|
|
601
|
-
* @returns {
|
|
602
|
-
* elements [base,track]. Base is a parent which contains track as a
|
|
658
|
+
* @returns {[HTMLElement, HTMLElement]} The two returned DOM nodes are both SVG
|
|
659
|
+
* elements [base, track]. Base is a parent which contains track as a
|
|
603
660
|
* child. Track is clipped with a clipping rectangle contained in base.
|
|
604
661
|
*
|
|
605
662
|
*/
|
|
@@ -693,15 +750,15 @@ class PixiTrack extends Track {
|
|
|
693
750
|
this.options.labelPosition === 'topRight'
|
|
694
751
|
) {
|
|
695
752
|
const dy = ddy + (i + 1) * (this.labelTextFontSize + 2);
|
|
696
|
-
text.setAttribute('dy', dy);
|
|
753
|
+
text.setAttribute('dy', String(dy));
|
|
697
754
|
} else if (
|
|
698
755
|
this.options.labelPosition === 'bottomLeft' ||
|
|
699
756
|
this.options.labelPosition === 'bottomRight'
|
|
700
757
|
) {
|
|
701
|
-
text.setAttribute('dy', ddy + i * (this.labelTextFontSize + 2));
|
|
758
|
+
text.setAttribute('dy', String(ddy + i * (this.labelTextFontSize + 2)));
|
|
702
759
|
}
|
|
703
760
|
|
|
704
|
-
text.setAttribute('fill', this.options.labelColor);
|
|
761
|
+
text.setAttribute('fill', this.options.labelColor ?? '');
|
|
705
762
|
|
|
706
763
|
if (this.labelText.anchor.x === 0.5) {
|
|
707
764
|
text.setAttribute('text-anchor', 'middle');
|
|
@@ -721,6 +778,13 @@ class PixiTrack extends Track {
|
|
|
721
778
|
// contents
|
|
722
779
|
return [gBase, gTrack];
|
|
723
780
|
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* @returns {number}
|
|
784
|
+
*/
|
|
785
|
+
calculateZoomLevel() {
|
|
786
|
+
throw new Error('Must be implemented by subclass');
|
|
787
|
+
}
|
|
724
788
|
}
|
|
725
789
|
|
|
726
790
|
export default PixiTrack;
|
package/app/scripts/Track.js
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { scaleLinear } from 'd3-scale';
|
|
2
3
|
import { fake as fakePubSub } from './hocs/with-pub-sub';
|
|
3
4
|
|
|
4
5
|
// Services
|
|
5
6
|
import { isWithin } from './utils';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @typedef TrackContext
|
|
10
|
+
* @property {string} id - The track ID.
|
|
11
|
+
* @property {import('pub-sub-es').PubSub & { __fake__?: boolean }} [pubSub] - The pub-sub channel.
|
|
12
|
+
* @property {() => import('./types').Theme} [getTheme] - A function that returns the current theme.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @template T
|
|
17
|
+
* @typedef {T & TrackContext} ExtendedTrackContext
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @template Options */
|
|
7
21
|
class Track {
|
|
8
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @param {TrackContext} context
|
|
24
|
+
* @param {Options} options
|
|
25
|
+
*/
|
|
26
|
+
constructor(context, options) {
|
|
9
27
|
this.context = context;
|
|
10
28
|
|
|
11
29
|
const { id, pubSub, getTheme } = context;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} else {
|
|
15
|
-
this.pubSub = fakePubSub;
|
|
16
|
-
}
|
|
30
|
+
/** @type {import('pub-sub-es').PubSub} */
|
|
31
|
+
this.pubSub = pubSub ?? fakePubSub;
|
|
17
32
|
|
|
33
|
+
/** @type {string} */
|
|
18
34
|
this.id = id;
|
|
35
|
+
/** @type {import('./types').Scale} */
|
|
19
36
|
this._xScale = scaleLinear();
|
|
37
|
+
/** @type {import('./types').Scale} */
|
|
20
38
|
this._yScale = scaleLinear();
|
|
21
39
|
|
|
22
40
|
// reference scales used for tracks that can translate and scale
|
|
23
41
|
// their graphics
|
|
24
42
|
// They will draw their graphics on the reference scales and then translate
|
|
25
43
|
// and pan them as needed
|
|
44
|
+
/** @type {import('./types').Scale} */
|
|
26
45
|
this._refXScale = scaleLinear();
|
|
46
|
+
/** @type {import('./types').Scale} */
|
|
27
47
|
this._refYScale = scaleLinear();
|
|
28
48
|
|
|
49
|
+
/** @type {[number, number]} */
|
|
29
50
|
this.position = [0, 0];
|
|
51
|
+
/** @type {[number, number]} */
|
|
30
52
|
this.dimensions = [1, 1];
|
|
31
|
-
|
|
53
|
+
/** @type {Options} */
|
|
54
|
+
this.options = options;
|
|
55
|
+
/** @type {Array<import('pub-sub-es').Subscription>} */
|
|
32
56
|
this.pubSubs = [];
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} else {
|
|
37
|
-
this.getTheme = () => {};
|
|
38
|
-
}
|
|
58
|
+
/** @type {() => (import('./types').Theme | undefined)} */
|
|
59
|
+
this.getTheme = getTheme ?? (() => undefined);
|
|
39
60
|
|
|
40
61
|
this.pubSubs.push(
|
|
41
62
|
this.pubSub.subscribe(
|
|
@@ -43,14 +64,16 @@ class Track {
|
|
|
43
64
|
this.defaultMouseMoveHandler.bind(this),
|
|
44
65
|
),
|
|
45
66
|
);
|
|
67
|
+
|
|
68
|
+
this.isLeftModified = false;
|
|
46
69
|
}
|
|
47
70
|
|
|
48
71
|
/**
|
|
49
72
|
* Check if a 2d location (x, y) is within the bounds of this track.
|
|
50
73
|
*
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {
|
|
53
|
-
* @return {
|
|
74
|
+
* @param {number} x - X position to be tested.
|
|
75
|
+
* @param {number} y - Y position to be tested.
|
|
76
|
+
* @return {boolean} If `true` location is within the track.
|
|
54
77
|
*/
|
|
55
78
|
isWithin(x, y) {
|
|
56
79
|
let xx = x;
|
|
@@ -75,14 +98,26 @@ class Track {
|
|
|
75
98
|
);
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Get a property from the track.
|
|
103
|
+
* @template {keyof this} T
|
|
104
|
+
* @param {T} prop - The property to get.
|
|
105
|
+
* @return {() => this[T]}
|
|
106
|
+
*/
|
|
78
107
|
getProp(prop) {
|
|
79
108
|
return () => this[prop];
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
getData() {}
|
|
83
112
|
|
|
84
|
-
/**
|
|
85
|
-
* position
|
|
113
|
+
/**
|
|
114
|
+
* Capture click events. x and y are relative to the track position
|
|
115
|
+
* @template T
|
|
116
|
+
* @param {number} x - X position of the click event.
|
|
117
|
+
* @param {number} y - Y position of the click event.
|
|
118
|
+
* @param {T} evt - The event.
|
|
119
|
+
* @return {{ type: 'generic', event: T, payload: null }}
|
|
120
|
+
*/
|
|
86
121
|
click(x, y, evt) {
|
|
87
122
|
return {
|
|
88
123
|
type: 'generic',
|
|
@@ -94,10 +129,12 @@ class Track {
|
|
|
94
129
|
/** There was a click event outside the track * */
|
|
95
130
|
clickOutside() {}
|
|
96
131
|
|
|
132
|
+
/** @returns {[number, number]} */
|
|
97
133
|
getDimensions() {
|
|
98
134
|
return this.dimensions;
|
|
99
135
|
}
|
|
100
136
|
|
|
137
|
+
/** @param {[number, number]} newDimensions */
|
|
101
138
|
setDimensions(newDimensions) {
|
|
102
139
|
this.dimensions = newDimensions;
|
|
103
140
|
|
|
@@ -105,82 +142,136 @@ class Track {
|
|
|
105
142
|
this._yScale.range([0, this.dimensions[1]]);
|
|
106
143
|
}
|
|
107
144
|
|
|
145
|
+
/**
|
|
146
|
+
* @overload
|
|
147
|
+
* @return {import('./types').Scale}
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* @overload
|
|
151
|
+
* @param {import('./types').Scale} scale
|
|
152
|
+
* @return {this}
|
|
153
|
+
*/
|
|
108
154
|
/**
|
|
109
155
|
* Either get or set the reference xScale
|
|
156
|
+
*
|
|
157
|
+
* @param {import('./types').Scale=} scale
|
|
158
|
+
* @return {import('./types').Scale | this}
|
|
110
159
|
*/
|
|
111
|
-
refXScale(
|
|
112
|
-
if (!
|
|
113
|
-
|
|
114
|
-
this._refXScale = _;
|
|
115
|
-
|
|
160
|
+
refXScale(scale) {
|
|
161
|
+
if (!scale) return this._refXScale;
|
|
162
|
+
this._refXScale = scale;
|
|
116
163
|
return this;
|
|
117
164
|
}
|
|
118
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @overload
|
|
168
|
+
* @return {import('./types').Scale}
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* @overload
|
|
172
|
+
* @param {import('./types').Scale} scale
|
|
173
|
+
* @return {this}
|
|
174
|
+
*/
|
|
119
175
|
/**
|
|
120
176
|
* Either get or set the reference yScale
|
|
177
|
+
*
|
|
178
|
+
* @param {import('./types').Scale=} scale
|
|
179
|
+
* @return {import('./types').Scale | this}
|
|
121
180
|
*/
|
|
122
|
-
refYScale(
|
|
123
|
-
if (!
|
|
124
|
-
|
|
125
|
-
this._refYScale = _;
|
|
126
|
-
|
|
181
|
+
refYScale(scale) {
|
|
182
|
+
if (!scale) return this._refYScale;
|
|
183
|
+
this._refYScale = scale;
|
|
127
184
|
return this;
|
|
128
185
|
}
|
|
129
186
|
|
|
187
|
+
/**
|
|
188
|
+
* @overload
|
|
189
|
+
* @return {import('./types').Scale}
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* @overload
|
|
193
|
+
* @param {import('./types').Scale} scale
|
|
194
|
+
* @return {this}
|
|
195
|
+
*/
|
|
130
196
|
/**
|
|
131
197
|
* Either get or set the xScale
|
|
198
|
+
*
|
|
199
|
+
* @param {import('./types').Scale=} scale
|
|
200
|
+
* @return {import('./types').Scale | this}
|
|
132
201
|
*/
|
|
133
|
-
xScale(
|
|
134
|
-
if (!
|
|
135
|
-
|
|
136
|
-
this._xScale = _;
|
|
137
|
-
|
|
202
|
+
xScale(scale) {
|
|
203
|
+
if (!scale) return this._xScale;
|
|
204
|
+
this._xScale = scale;
|
|
138
205
|
return this;
|
|
139
206
|
}
|
|
140
207
|
|
|
208
|
+
/**
|
|
209
|
+
* @overload
|
|
210
|
+
* @return {import('./types').Scale}
|
|
211
|
+
*/
|
|
212
|
+
/**
|
|
213
|
+
* @overload
|
|
214
|
+
* @param {import('./types').Scale} scale
|
|
215
|
+
* @return {this}
|
|
216
|
+
*/
|
|
141
217
|
/**
|
|
142
218
|
* Either get or set the yScale
|
|
219
|
+
*
|
|
220
|
+
* @param {import('./types').Scale=} scale
|
|
221
|
+
* @return {import('./types').Scale | this}
|
|
143
222
|
*/
|
|
144
|
-
yScale(
|
|
145
|
-
if (!
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this._yScale = _;
|
|
150
|
-
|
|
223
|
+
yScale(scale) {
|
|
224
|
+
if (!scale) return this._yScale;
|
|
225
|
+
this._yScale = scale;
|
|
151
226
|
return this;
|
|
152
227
|
}
|
|
153
228
|
|
|
229
|
+
/**
|
|
230
|
+
* @param {import('./types').Scale} newXScale
|
|
231
|
+
* @param {import('./types').Scale} newYScale
|
|
232
|
+
* @returns {void}
|
|
233
|
+
*/
|
|
154
234
|
zoomed(newXScale, newYScale) {
|
|
155
235
|
this.xScale(newXScale);
|
|
156
236
|
this.yScale(newYScale);
|
|
157
237
|
}
|
|
158
238
|
|
|
239
|
+
/**
|
|
240
|
+
* @param {import('./types').Scale} refXScale
|
|
241
|
+
* @param {import('./types').Scale} refYScale
|
|
242
|
+
* @returns {void}
|
|
243
|
+
*/
|
|
159
244
|
refScalesChanged(refXScale, refYScale) {
|
|
160
245
|
this._refXScale = refXScale;
|
|
161
246
|
this._refYScale = refYScale;
|
|
162
247
|
}
|
|
163
248
|
|
|
249
|
+
/** @returns {void} */
|
|
164
250
|
draw() {}
|
|
165
251
|
|
|
252
|
+
/** @returns {[number, number]} */
|
|
166
253
|
getPosition() {
|
|
167
254
|
return this.position;
|
|
168
255
|
}
|
|
169
256
|
|
|
257
|
+
/**
|
|
258
|
+
* @param {[number, number]} newPosition
|
|
259
|
+
* @returns {void}
|
|
260
|
+
*/
|
|
170
261
|
setPosition(newPosition) {
|
|
171
262
|
this.position = newPosition;
|
|
172
263
|
}
|
|
173
264
|
|
|
174
|
-
|
|
265
|
+
/**
|
|
175
266
|
* A blank handler for MouseMove / Zoom events. Should be overriden
|
|
176
267
|
* by individual tracks to provide
|
|
177
268
|
*
|
|
178
|
-
* @param {
|
|
179
|
-
*
|
|
180
|
-
* @returns nothing
|
|
269
|
+
* @param {{}} evt
|
|
270
|
+
* @returns {void}
|
|
181
271
|
*/
|
|
182
272
|
defaultMouseMoveHandler(evt) {}
|
|
183
273
|
|
|
274
|
+
/** @returns {void} */
|
|
184
275
|
remove() {
|
|
185
276
|
// Clear all pubSub subscriptions
|
|
186
277
|
this.pubSubs.forEach((subscription) =>
|
|
@@ -189,19 +280,36 @@ class Track {
|
|
|
189
280
|
this.pubSubs = [];
|
|
190
281
|
}
|
|
191
282
|
|
|
192
|
-
|
|
283
|
+
/**
|
|
284
|
+
* @param {Options} options
|
|
285
|
+
* @returns {void}
|
|
286
|
+
*/
|
|
287
|
+
rerender(options) {}
|
|
193
288
|
|
|
194
|
-
|
|
289
|
+
/**
|
|
195
290
|
* This function is for seeing whether this track should respond
|
|
196
291
|
* to events at this mouse position. The difference to `isWithin()` is that it
|
|
197
292
|
* can be overwritten if a track is inactive for example.
|
|
293
|
+
*
|
|
294
|
+
* @param {number} x - X position to be tested.
|
|
295
|
+
* @param {number} y - Y position to be tested.
|
|
296
|
+
* @returns {boolean}
|
|
198
297
|
*/
|
|
199
298
|
respondsToPosition(x, y) {
|
|
200
299
|
return this.isWithin(x, y);
|
|
201
300
|
}
|
|
202
301
|
|
|
302
|
+
/**
|
|
303
|
+
* @param {number} trackY
|
|
304
|
+
* @param {number} kMultiplier
|
|
305
|
+
* @returns {void}
|
|
306
|
+
*/
|
|
203
307
|
zoomedY(trackY, kMultiplier) {}
|
|
204
308
|
|
|
309
|
+
/**
|
|
310
|
+
* @param {number} dY
|
|
311
|
+
* @returns {void}
|
|
312
|
+
*/
|
|
205
313
|
movedY(dY) {}
|
|
206
314
|
}
|
|
207
315
|
|