higlass 1.13.4 → 1.13.5

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.
@@ -2,9 +2,11 @@
2
2
  import { format } from 'd3-format';
3
3
 
4
4
  // Configs
5
- import { GLOBALS, THEME_DARK } from './configs';
5
+ import GLOBALS from './configs/globals';
6
+ import { THEME_DARK } from './configs/themes';
6
7
 
7
- import { colorToHex } from './utils';
8
+ // Utils
9
+ import colorToHex from './utils/color-to-hex';
8
10
 
9
11
  const TICK_HEIGHT = 40;
10
12
  const TICK_MARGIN = 0;
@@ -1,9 +1,11 @@
1
1
  // @ts-nocheck
2
2
  import { tsvParseRows } from 'd3-dsv';
3
3
  import { tileProxy } from './services';
4
- import { absToChr, chrToAbs, parseChromsizesRows } from './utils';
5
4
 
6
- import { fake as fakePubSub } from './hocs/with-pub-sub';
5
+ import absToChr from './utils/abs-to-chr';
6
+ import chrToAbs from './utils/chr-to-abs';
7
+ import parseChromsizesRows from './utils/parse-chromsizes-rows';
8
+ import fakePubSub from './utils/fake-pub-sub';
7
9
 
8
10
  function ChromosomeInfo(filepath, success, pubSub = fakePubSub) {
9
11
  const ret = {};
@@ -216,7 +216,10 @@ class HeatmapOptions extends React.Component {
216
216
  <td className="td-track-options">Preview</td>
217
217
  </tr>
218
218
  <tr>
219
- <td className="td-track-options">
219
+ <td
220
+ className="td-track-options"
221
+ aria-label="Track options"
222
+ >
220
223
  <div style={{ width: 200 }}>
221
224
  <HiGlassComponent
222
225
  options={{ bounded: false }}
@@ -5,30 +5,36 @@ import { format } from 'd3-format';
5
5
  import { scaleLinear } from 'd3-scale';
6
6
  import { select } from 'd3-selection';
7
7
  import slugid from 'slugid';
8
- import {
9
- colorToRgba,
10
- absToChr,
11
- colorDomainToRgbaArray,
12
- colorToHex,
13
- download,
14
- ndarrayAssign,
15
- ndarrayFlatten,
16
- objVals,
17
- showMousePosition,
18
- valueToColor,
19
- } from './utils';
8
+
9
+ // Utils
10
+ import colorToRgba from './utils/color-to-rgba';
11
+ import absToChr from './utils/abs-to-chr';
12
+ import colorDomainToRgbaArray from './utils/color-domain-to-rgba-array';
13
+ import colorToHex from './utils/color-to-hex';
14
+ import { download } from './utils/download';
15
+ import ndarrayAssign from './utils/ndarray-assign';
16
+ import ndarrayFlatten from './utils/ndarray-flatten';
17
+ import objVals from './utils/obj-vals';
18
+ import showMousePosition from './utils/show-mouse-position';
19
+ import valueToColor from './utils/value-to-color';
20
20
 
21
21
  import TiledPixiTrack, { getValueScale } from './TiledPixiTrack';
22
22
  import AxisPixi from './AxisPixi';
23
23
 
24
24
  // Services
25
- import { tileProxy } from './services';
26
-
27
25
  import {
28
- GLOBALS,
29
- HEATED_OBJECT_MAP,
30
- NUM_PRECOMP_SUBSETS_PER_2D_TTILE,
31
- } from './configs';
26
+ tileDataToPixData,
27
+ calculateTileWidth,
28
+ calculateTilesFromResolution,
29
+ calculateTiles,
30
+ calculateResolution,
31
+ calculateZoomLevelFromResolutions,
32
+ calculateZoomLevel,
33
+ } from './services/tile-proxy';
34
+
35
+ import GLOBALS from './configs/globals';
36
+ import { NUM_PRECOMP_SUBSETS_PER_2D_TTILE } from './configs/dense-data-extrema-config';
37
+ import { HEATED_OBJECT_MAP } from './configs/colormaps';
32
38
 
33
39
  const COLORBAR_MAX_HEIGHT = 200;
34
40
  const COLORBAR_WIDTH = 10;
@@ -973,7 +979,7 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
973
979
  ? Math.min(this.tilesetInfo.max_zoom, zoomLevel)
974
980
  : zoomLevel;
975
981
 
976
- const calculatedWidth = tileProxy.calculateTileWidth(
982
+ const calculatedWidth = calculateTileWidth(
977
983
  this.tilesetInfo,
978
984
  zoomLevel,
979
985
  this.binsPerTile(),
@@ -1230,7 +1236,7 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1230
1236
  }
1231
1237
  }
1232
1238
 
1233
- tileProxy.tileDataToPixData(
1239
+ tileDataToPixData(
1234
1240
  tile,
1235
1241
  scaleType,
1236
1242
  this.limitedValueScale.domain(),
@@ -1625,20 +1631,20 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1625
1631
  .map((x) => +x)
1626
1632
  .sort((a, b) => b - a);
1627
1633
 
1628
- this.xTiles = tileProxy.calculateTilesFromResolution(
1634
+ this.xTiles = calculateTilesFromResolution(
1629
1635
  sortedResolutions[this.zoomLevel],
1630
1636
  this._xScale,
1631
1637
  this.tilesetInfo.min_pos[0],
1632
1638
  this.tilesetInfo.max_pos[0],
1633
1639
  );
1634
- this.yTiles = tileProxy.calculateTilesFromResolution(
1640
+ this.yTiles = calculateTilesFromResolution(
1635
1641
  sortedResolutions[this.zoomLevel],
1636
1642
  this._yScale,
1637
1643
  this.tilesetInfo.min_pos[0],
1638
1644
  this.tilesetInfo.max_pos[0],
1639
1645
  );
1640
1646
  } else {
1641
- this.xTiles = tileProxy.calculateTiles(
1647
+ this.xTiles = calculateTiles(
1642
1648
  this.zoomLevel,
1643
1649
  this._xScale,
1644
1650
  this.tilesetInfo.min_pos[0],
@@ -1647,7 +1653,7 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1647
1653
  this.tilesetInfo.max_width,
1648
1654
  );
1649
1655
 
1650
- this.yTiles = tileProxy.calculateTiles(
1656
+ this.yTiles = calculateTiles(
1651
1657
  this.zoomLevel,
1652
1658
  this._yScale,
1653
1659
  this.options.reverseYAxis
@@ -1674,6 +1680,28 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1674
1680
  );
1675
1681
  }
1676
1682
 
1683
+ contextMenuItems(trackX, trackY) {
1684
+ /* Get a list of context menu items to display and the actions
1685
+ to take */
1686
+
1687
+ // This should return items like this:
1688
+
1689
+ // return [
1690
+ // {
1691
+ // label: 'Change background color to black',
1692
+ // onClick: (evt, onTrackOptionsChanged) => {
1693
+ // // The onTrackOptionsChanged handler will handle any changes
1694
+ // // to the track's options that are triggered in this event.
1695
+ // // The only thing that needs to be passed is the new option being
1696
+ // // passed
1697
+ // onTrackOptionsChanged({ backgroundColor: 'black' });
1698
+ // },
1699
+ // },
1700
+ // ];
1701
+
1702
+ return [];
1703
+ }
1704
+
1677
1705
  getMouseOverHtml(trackX, trackY) {
1678
1706
  if (!this.options || !this.options.showTooltip) {
1679
1707
  return '';
@@ -1683,7 +1711,7 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1683
1711
  return '';
1684
1712
  }
1685
1713
 
1686
- const currentResolution = tileProxy.calculateResolution(
1714
+ const currentResolution = calculateResolution(
1687
1715
  this.tilesetInfo,
1688
1716
  this.zoomLevel,
1689
1717
  );
@@ -1803,13 +1831,13 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1803
1831
  let zoomLevel = null;
1804
1832
 
1805
1833
  if (this.tilesetInfo.resolutions) {
1806
- const zoomIndexX = tileProxy.calculateZoomLevelFromResolutions(
1834
+ const zoomIndexX = calculateZoomLevelFromResolutions(
1807
1835
  this.tilesetInfo.resolutions,
1808
1836
  this._xScale,
1809
1837
  minX,
1810
1838
  maxX,
1811
1839
  );
1812
- const zoomIndexY = tileProxy.calculateZoomLevelFromResolutions(
1840
+ const zoomIndexY = calculateZoomLevelFromResolutions(
1813
1841
  this.tilesetInfo.resolutions,
1814
1842
  this._yScale,
1815
1843
  minY,
@@ -1818,14 +1846,14 @@ class HeatmapTiledPixiTrack extends TiledPixiTrack {
1818
1846
 
1819
1847
  zoomLevel = Math.min(zoomIndexX, zoomIndexY);
1820
1848
  } else {
1821
- const xZoomLevel = tileProxy.calculateZoomLevel(
1849
+ const xZoomLevel = calculateZoomLevel(
1822
1850
  this._xScale,
1823
1851
  this.tilesetInfo.min_pos[0],
1824
1852
  this.tilesetInfo.max_pos[0],
1825
1853
  this.binsPerTile(),
1826
1854
  );
1827
1855
 
1828
- const yZoomLevel = tileProxy.calculateZoomLevel(
1856
+ const yZoomLevel = calculateZoomLevel(
1829
1857
  this._xScale,
1830
1858
  this.tilesetInfo.min_pos[1],
1831
1859
  this.tilesetInfo.max_pos[1],
@@ -17,9 +17,10 @@ class MapboxTilesTrack extends OSMTilesTrack {
17
17
  this.style = options.style;
18
18
 
19
19
  if (!this.options.accessToken) {
20
- this.errorTextText =
20
+ this.setError(
21
21
  "No access token provided in the viewconf's track options " +
22
- "('accessToken' option).";
22
+ "('accessToken' option).",
23
+ );
23
24
  this.drawError();
24
25
  }
25
26
  }
@@ -3,10 +3,10 @@ import slugid from 'slugid';
3
3
 
4
4
  import Track from './Track';
5
5
 
6
- import { colorToHex } from './utils';
6
+ import colorToHex from './utils/color-to-hex';
7
7
 
8
8
  // Configs
9
- import { GLOBALS } from './configs';
9
+ import GLOBALS from './configs/globals';
10
10
  import {
11
11
  isResolutionsTilesetInfo,
12
12
  isLegacyTilesetInfo,
@@ -208,12 +208,14 @@ class PixiTrack extends Track {
208
208
  this.errorText.anchor.x = 0.5;
209
209
  this.errorText.anchor.y = 0.5;
210
210
  this.pLabel.addChild(this.errorText);
211
- /** @type {string} */
212
- this.errorTextText = '';
211
+
213
212
  /** @type {boolean} */
214
213
  this.flipText = false;
215
214
  /** @type {import('./types').TilesetInfo | undefined} */
216
215
  this.tilesetInfo = undefined;
216
+
217
+ /** @type {{ [key: string]: string }} */
218
+ this.errorTexts = {};
217
219
  }
218
220
 
219
221
  setLabelText() {
@@ -294,15 +296,38 @@ class PixiTrack extends Track {
294
296
  );
295
297
  }
296
298
 
299
+ /** Set an error for this track.
300
+ *
301
+ * The error can be associated with a source so that multiple
302
+ * components within the track can set their own independent errors
303
+ * that will be displayed to the user without overlapping.
304
+ *
305
+ * @param {string} error The error text
306
+ * @param {string} source The source of the error
307
+ */
308
+ setError(error, source) {
309
+ this.errorTexts[source] = error;
310
+
311
+ this.drawError();
312
+ }
313
+
297
314
  drawError() {
298
315
  this.errorText.x = this.position[0] + this.dimensions[0] / 2;
299
316
  this.errorText.y = this.position[1] + this.dimensions[1] / 2;
300
317
 
301
- this.errorText.text = this.errorTextText;
318
+ // Collect all the error texts, filter out the ones that are empty
319
+ // and put the non-empty ones separate lines
320
+ const errorTextText = Object.values(this.errorTexts)
321
+ .filter((x) => x && x.length)
322
+ .reduce((acc, x) => (acc ? `${acc}\n${x}` : x), '');
302
323
 
303
- if (this.errorTextText && this.errorTextText.length) {
324
+ this.errorText.text = errorTextText;
325
+ this.errorText.alpha = 0.8;
326
+
327
+ if (errorTextText && errorTextText.length) {
304
328
  // draw a red border around the track to bring attention to its
305
329
  // error
330
+
306
331
  const graphics = this.pBorder;
307
332
  graphics.clear();
308
333
  graphics.lineStyle(1, colorToHex('red'));
@@ -313,6 +338,8 @@ class PixiTrack extends Track {
313
338
  this.dimensions[0],
314
339
  this.dimensions[1],
315
340
  );
341
+ } else {
342
+ this.pBorder.clear();
316
343
  }
317
344
  }
318
345
 
@@ -16,8 +16,9 @@ class RasterTilesTrack extends OSMTilesTrack {
16
16
  this.style = options.style;
17
17
 
18
18
  if (!this.options.tileSource) {
19
- this.errorTextText =
20
- 'No tile source string provided in the options. It should be in the form of http://a.com/{z}/{x}/{y}';
19
+ this.setError(
20
+ 'No tile source string provided in the options. It should be in the form of http://a.com/{z}/{x}/{y}',
21
+ );
21
22
  this.drawError();
22
23
  }
23
24
  }
@@ -13,6 +13,52 @@ import OPTIONS_INFO from './options-info';
13
13
  // Styles
14
14
  import classes from '../styles/ContextMenu.module.scss';
15
15
 
16
+ /**
17
+ * We're going to get the track object to see if it has a
18
+ * context menu handler that will give use context menu items
19
+ * to display
20
+ *
21
+ * @param (Dict) track The config for the track we're getting context menu
22
+ * items for
23
+ *
24
+ * @param (TrackRenderer) trackRenderer The track renderer for the view
25
+ * containing this track. We'll use it to get the track's object
26
+ *
27
+ * @param (Dict) position The position of the track. Relevant are the canvasLeft,
28
+ * canvasRight positions which mark where the track starts relative to the
29
+ * canvas. This is important because all coordinates within a track are relative
30
+ * to left and top coordinates.
31
+ */
32
+
33
+ function findTrackContextMenuItems(track, trackRenderer, position) {
34
+ let trackObj = trackRenderer.getTrackObject(track.uid);
35
+
36
+ // The track may be a LeftTrackModifier track
37
+ trackObj = trackObj.originalTrack || trackObj;
38
+
39
+ // See if the track will provide us with context menu items
40
+ if (trackObj.contextMenuItems) {
41
+ let trackLeft = position.canvasLeft - trackObj.position[0];
42
+ let trackTop = position.canvasTop - trackObj.position[1];
43
+
44
+ if (trackObj.flipText) {
45
+ // This is a left track modifier track so we need to swap the
46
+ // left and right values
47
+ const temp = trackLeft;
48
+ trackLeft = trackTop;
49
+ trackTop = temp;
50
+ }
51
+
52
+ const items = trackObj.contextMenuItems(trackLeft, trackTop);
53
+
54
+ return items || [];
55
+ }
56
+
57
+ // The track doesn't have a contextMenuItems function so we it's
58
+ // obviously not providing any items.
59
+ return [];
60
+ }
61
+
16
62
  export default class SeriesListMenu extends ContextMenuContainer {
17
63
  getConfigureSeriesMenu(position, bbox, track) {
18
64
  const menuItems = {};
@@ -283,6 +329,12 @@ export default class SeriesListMenu extends ContextMenuContainer {
283
329
  render() {
284
330
  let exportDataMenuItem = null;
285
331
 
332
+ const trackContextMenuItems = findTrackContextMenuItems(
333
+ this.props.track,
334
+ this.props.trackRenderer,
335
+ this.props.position,
336
+ );
337
+
286
338
  if (
287
339
  TRACKS_INFO_BY_TYPE[this.props.series.type] &&
288
340
  TRACKS_INFO_BY_TYPE[this.props.series.type].exportable
@@ -333,6 +385,29 @@ export default class SeriesListMenu extends ContextMenuContainer {
333
385
  top: this.state.top,
334
386
  }}
335
387
  >
388
+ {trackContextMenuItems.map((x) => (
389
+ <ContextMenuItem
390
+ key={x.label}
391
+ onClick={(evt) => {
392
+ x.onClick(evt, (newOptions) => {
393
+ // We're going to pass in a handler to that the track
394
+ // can use to change its options
395
+ this.props.onTrackOptionsChanged(this.props.track.uid, {
396
+ ...this.props.track.options,
397
+ ...newOptions,
398
+ });
399
+ });
400
+ this.props.closeMenu();
401
+ }}
402
+ onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
403
+ className={classes['context-menu-item']}
404
+ >
405
+ <span className={classes['context-menu-span']}>{x.label}</span>
406
+ </ContextMenuItem>
407
+ ))}
408
+ {trackContextMenuItems.length > 0 && (
409
+ <hr className={classes['context-menu-hr']} />
410
+ )}
336
411
  <ContextMenuItem
337
412
  onClick={() => {}}
338
413
  onMouseEnter={(e) =>
@@ -28,6 +28,9 @@ const SeriesListSubmenuMixin = Mixin(
28
28
  };
29
29
  }
30
30
 
31
+ position.canvasLeft = this.props.position.canvasLeft;
32
+ position.canvasTop = this.props.position.canvasTop;
33
+
31
34
  const series = getAllTracksAndSubtracks(this.props.tracks);
32
35
  const selectedTrack = series.filter(
33
36
  (t) => t.uid === this.state.submenuShown.uid,
@@ -65,6 +68,7 @@ const SeriesListSubmenuMixin = Mixin(
65
68
  theme={this.props.theme}
66
69
  track={selectedTrack}
67
70
  trackOrientation={this.props.trackOrientation}
71
+ trackRenderer={this.props.trackRenderer}
68
72
  trackSourceServers={this.props.trackSourceServers}
69
73
  />
70
74
  );
@@ -3,15 +3,17 @@ import { scaleLinear, scaleLog, scaleQuantile } from 'd3-scale';
3
3
  import { median, range, ticks } from 'd3-array';
4
4
  import slugid from 'slugid';
5
5
 
6
- import { DataFetcher } from './data-fetchers';
6
+ import DataFetcher from './data-fetchers/DataFetcher';
7
7
  import PixiTrack from './PixiTrack';
8
8
 
9
9
  // Utils
10
- import { throttleAndDebounce, parseChromsizesRows } from './utils';
10
+ import throttleAndDebounce from './utils/throttle-and-debounce';
11
+ import parseChromsizesRows from './utils/parse-chromsizes-rows';
11
12
  import backgroundTaskScheduler from './utils/background-task-scheduler';
12
13
 
13
14
  // Configs
14
- import { GLOBALS, ZOOM_DEBOUNCE } from './configs';
15
+ import GLOBALS from './configs/globals';
16
+ import { ZOOM_DEBOUNCE } from './configs/primitives';
15
17
 
16
18
  /**
17
19
  * Get a valueScale for a heatmap.
@@ -222,12 +224,6 @@ class TiledPixiTrack extends PixiTrack {
222
224
  });
223
225
  }
224
226
 
225
- setError(error) {
226
- this.errorTextText = error;
227
- this.draw();
228
- this.animate();
229
- }
230
-
231
227
  setFixedValueScaleMin(value) {
232
228
  if (!Number.isNaN(+value)) this.fixedValueScaleMin = +value;
233
229
  else this.fixedValueScaleMin = null;
@@ -623,6 +619,9 @@ class TiledPixiTrack extends PixiTrack {
623
619
  }
624
620
 
625
621
  fetchNewTiles(toFetch) {
622
+ this._checkForErrors();
623
+ this.draw();
624
+
626
625
  if (toFetch.length > 0) {
627
626
  const toFetchList = [...new Set(toFetch.map((x) => x.remoteId))];
628
627
 
@@ -727,6 +726,29 @@ class TiledPixiTrack extends PixiTrack {
727
726
  }
728
727
  }
729
728
 
729
+ _checkForErrors() {
730
+ const errors = Object.values(this.fetchedTiles)
731
+ .map(
732
+ (x) =>
733
+ x.tileData && x.tileData.error && `${x.tileId}: ${x.tileData.error}`,
734
+ )
735
+ .filter((x) => x);
736
+
737
+ if (errors.length) {
738
+ this.errorTexts.TiledPixiTrack = errors.join('\n');
739
+ } else {
740
+ this.errorTexts.TiledPixiTrack = '';
741
+ }
742
+
743
+ if (this.tilesetInfoError) {
744
+ this.errorTexts.TiledPixiTrack = this.tilesetInfoError;
745
+
746
+ errors.push(this.tilesetInfoError);
747
+ }
748
+
749
+ return errors;
750
+ }
751
+
730
752
  draw() {
731
753
  if (this.delayDrawing) return;
732
754
 
@@ -754,18 +776,8 @@ class TiledPixiTrack extends PixiTrack {
754
776
  uuid: this.uuid,
755
777
  });
756
778
  }
757
- const errors = Object.values(this.fetchedTiles)
758
- .map(
759
- (x) =>
760
- x.tileData && x.tileData.error && `${x.tileId}: ${x.tileData.error}`,
761
- )
762
- .filter((x) => x);
763
779
 
764
- if (errors.length) {
765
- this.errorTextText = errors.join('\n');
766
- } else {
767
- this.errorTextText = '';
768
- }
780
+ this._checkForErrors();
769
781
 
770
782
  super.draw();
771
783
 
@@ -1615,6 +1615,7 @@ class TiledPlot extends React.Component {
1615
1615
  position={this.state.contextMenuPosition}
1616
1616
  theme={this.props.theme}
1617
1617
  tracks={relevantTracks}
1618
+ trackRenderer={this.trackRenderer}
1618
1619
  trackSourceServers={this.props.trackSourceServers}
1619
1620
  />
1620
1621
  </PopupMenu>
@@ -2228,6 +2229,7 @@ class TiledPlot extends React.Component {
2228
2229
  this.props.tracks,
2229
2230
  this.state.configTrackMenuId,
2230
2231
  )}
2232
+ trackRenderer={this.trackRenderer}
2231
2233
  tracks={[
2232
2234
  getTrackByUid(this.props.tracks, this.state.configTrackMenuId),
2233
2235
  ]}
@@ -1,8 +1,8 @@
1
1
  import { scaleLinear } from 'd3-scale';
2
- import { fake as fakePubSub } from './hocs/with-pub-sub';
3
2
 
4
3
  // Services
5
- import { isWithin } from './utils';
4
+ import isWithin from './utils/is-within';
5
+ import fakePubSub from './utils/fake-pub-sub';
6
6
 
7
7
  /**
8
8
  * @typedef TrackContext
@@ -7,7 +7,7 @@ class UnknownPixiTrack extends PixiTrack {
7
7
  // so that the tests checking for retrieved tilesetInfo pass
8
8
  this.tilesetInfo = {};
9
9
 
10
- this.errorTextText = `Unknown track type: ${options.type}`;
10
+ this.setError(`Unknown track type: ${options.type}`);
11
11
  }
12
12
 
13
13
  zoomed() {