@jbrowse/core 1.5.1 → 1.5.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.
Files changed (111) hide show
  1. package/BaseFeatureWidget/BaseFeatureDetail.d.ts +4 -2
  2. package/BaseFeatureWidget/BaseFeatureDetail.js +43 -16
  3. package/BaseFeatureWidget/SequenceFeatureDetails.js +5 -4
  4. package/BaseFeatureWidget/index.js +1 -1
  5. package/BaseFeatureWidget/util.js +5 -5
  6. package/PluginLoader.d.ts +25 -2
  7. package/PluginLoader.js +334 -47
  8. package/PluginManager.d.ts +6 -5
  9. package/PluginManager.js +1 -5
  10. package/ReExports/list.js +1 -1
  11. package/ReExports/material-ui-colors.js +38 -38
  12. package/ReExports/modules.d.ts +2 -0
  13. package/ReExports/modules.js +3 -0
  14. package/TextSearch/BaseResults.js +1 -1
  15. package/TextSearch/TextSearchManager.d.ts +1 -1
  16. package/TextSearch/TextSearchManager.js +0 -2
  17. package/assemblyManager/assembly.js +5 -3
  18. package/assemblyManager/index.js +4 -4
  19. package/configuration/configurationSchema.js +6 -4
  20. package/configuration/configurationSlot.js +3 -1
  21. package/configuration/index.js +4 -4
  22. package/configuration/util.js +2 -2
  23. package/data_adapters/BaseAdapter.d.ts +5 -5
  24. package/data_adapters/BaseAdapter.js +9 -18
  25. package/data_adapters/CytobandAdapter.js +4 -2
  26. package/data_adapters/dataAdapterCache.js +2 -2
  27. package/package.json +9 -8
  28. package/pluggableElementTypes/AdapterType.js +3 -1
  29. package/pluggableElementTypes/ConnectionType.js +3 -1
  30. package/pluggableElementTypes/DisplayType.js +3 -1
  31. package/pluggableElementTypes/InternetAccountType.js +3 -1
  32. package/pluggableElementTypes/PluggableElementBase.js +4 -3
  33. package/pluggableElementTypes/RpcMethodType.js +2 -4
  34. package/pluggableElementTypes/TextSearchAdapterType.js +3 -1
  35. package/pluggableElementTypes/WidgetType.js +3 -1
  36. package/pluggableElementTypes/index.d.ts +2 -1
  37. package/pluggableElementTypes/index.js +37 -14
  38. package/pluggableElementTypes/models/BaseDisplayModel.js +2 -2
  39. package/pluggableElementTypes/models/BaseTrackModel.js +4 -3
  40. package/pluggableElementTypes/models/BaseViewModel.js +6 -2
  41. package/pluggableElementTypes/models/InternetAccountModel.d.ts +2 -2
  42. package/pluggableElementTypes/models/baseTrackConfig.js +2 -2
  43. package/pluggableElementTypes/models/index.js +14 -14
  44. package/pluggableElementTypes/renderers/BoxRendererType.js +3 -3
  45. package/pluggableElementTypes/renderers/CircularChordRendererType.js +3 -1
  46. package/pluggableElementTypes/renderers/ComparativeServerSideRendererType.js +2 -2
  47. package/pluggableElementTypes/renderers/FeatureRendererType.d.ts +1 -1
  48. package/pluggableElementTypes/renderers/FeatureRendererType.js +4 -4
  49. package/pluggableElementTypes/renderers/RendererType.js +3 -1
  50. package/pluggableElementTypes/renderers/ServerSideRendererType.js +2 -2
  51. package/pluggableElementTypes/renderers/index.d.ts +2 -1
  52. package/rpc/BaseRpcDriver.d.ts +1 -1
  53. package/rpc/BaseRpcDriver.js +3 -5
  54. package/rpc/BaseRpcDriver.test.js +1 -1
  55. package/rpc/RpcManager.d.ts +3 -3
  56. package/rpc/RpcManager.js +44 -22
  57. package/rpc/WebWorkerRpcDriver.d.ts +2 -2
  58. package/rpc/WebWorkerRpcDriver.js +5 -7
  59. package/rpc/coreRpcMethods.js +3 -3
  60. package/rpc/remoteAbortSignals.js +2 -2
  61. package/ui/App.js +2 -0
  62. package/ui/DrawerWidget.js +1 -0
  63. package/ui/EditableTypography.js +2 -2
  64. package/ui/FileSelector/FileSelector.js +2 -2
  65. package/ui/Icons.js +4 -4
  66. package/ui/Logo.js +1 -1
  67. package/ui/Menu.js +2 -2
  68. package/ui/NewSessionCards.js +2 -2
  69. package/ui/PrerenderedCanvas.d.ts +1 -0
  70. package/ui/PrerenderedCanvas.js +6 -3
  71. package/ui/SanitizedHTML.js +1 -1
  72. package/ui/SnackbarModel.d.ts +16 -0
  73. package/ui/SnackbarModel.js +56 -0
  74. package/ui/ViewContainer.js +4 -3
  75. package/ui/index.js +24 -24
  76. package/ui/theme.js +7 -5
  77. package/util/Base1DViewModel.js +2 -2
  78. package/util/aborting.js +7 -4
  79. package/util/blockTypes.js +8 -11
  80. package/util/calculateDynamicBlocks.test.js +2 -2
  81. package/util/color/cssColorsLevel4.js +1 -1
  82. package/util/color/index.js +5 -5
  83. package/util/compositeMap.js +3 -11
  84. package/util/index.d.ts +2 -1
  85. package/util/index.js +63 -36
  86. package/util/index.test.js +2 -2
  87. package/util/io/RemoteFileWithRangeCache.js +3 -3
  88. package/util/io/index.js +1 -1
  89. package/util/jexl.js +4 -1
  90. package/util/layouts/BaseLayout.d.ts +3 -0
  91. package/util/layouts/GranularRectLayout.d.ts +19 -10
  92. package/util/layouts/GranularRectLayout.js +459 -100
  93. package/util/layouts/GranularRectLayout.test.js +57 -10
  94. package/util/layouts/MultiLayout.d.ts +1 -1
  95. package/util/layouts/MultiLayout.js +0 -2
  96. package/util/layouts/PrecomputedLayout.js +2 -1
  97. package/util/layouts/PrecomputedMultiLayout.js +5 -3
  98. package/util/layouts/SceneGraph.d.ts +3 -3
  99. package/util/layouts/SceneGraph.js +0 -14
  100. package/util/layouts/index.d.ts +7 -0
  101. package/util/layouts/index.js +68 -0
  102. package/util/mst-reflection.d.ts +3 -3
  103. package/util/mst-reflection.js +3 -3
  104. package/util/offscreenCanvasPonyfill.js +1 -1
  105. package/util/range.js +1 -1
  106. package/util/simpleFeature.js +3 -3
  107. package/util/stats.js +4 -4
  108. package/util/tracks.js +10 -10
  109. package/util/types/index.d.ts +9 -1
  110. package/util/types/index.js +14 -19
  111. package/util/types/mst.js +3 -3
@@ -7,8 +7,6 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports.default = void 0;
9
9
 
10
- var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
-
12
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
11
 
14
12
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -17,8 +15,6 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
17
15
 
18
16
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
19
17
 
20
- var _rbush = _interopRequireDefault(require("rbush"));
21
-
22
18
  var _index = require("../index");
23
19
 
24
20
  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
@@ -27,37 +23,315 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
27
23
 
28
24
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
29
25
 
26
+ /**
27
+ * Rectangle-layout manager that lays out rectangles using bitmaps at
28
+ * resolution that, for efficiency, may be somewhat lower than that of
29
+ * the coordinate system for the rectangles being laid out. `pitchX`
30
+ * and `pitchY` are the ratios of input scale resolution to internal
31
+ * bitmap resolution.
32
+ */
33
+ // minimum excess size of the array at which we garbage collect
34
+ var minSizeToBotherWith = 10000;
35
+ var maxFeaturePitchWidth = 20000;
36
+
30
37
  function segmentsIntersect(x1, x2, y1, y2) {
31
38
  return x2 >= y1 && y2 >= x1;
32
39
  }
33
40
 
41
+ // a single row in the layout
42
+ var LayoutRow = /*#__PURE__*/function () {
43
+ function LayoutRow() {
44
+ (0, _classCallCheck2.default)(this, LayoutRow);
45
+ (0, _defineProperty2.default)(this, "padding", void 0);
46
+ (0, _defineProperty2.default)(this, "allFilled", void 0);
47
+ (0, _defineProperty2.default)(this, "widthLimit", void 0);
48
+ (0, _defineProperty2.default)(this, "rowState", void 0);
49
+ this.padding = 1;
50
+ this.widthLimit = 1000000; // this.rowState.offset is the offset of the bits array relative to the genomic coordinates
51
+ // (modified by pitchX, but we don't know that in this class)
52
+ // this.rowState.bits is the array of items in the layout row, indexed by (x - this.offset)
53
+ // this.rowState.min is the leftmost edge of all the rectangles we have in the layout
54
+ // this.rowState.max is the rightmost edge of all the rectangles we have in the layout
55
+ } // log(msg: string): void {
56
+ // // if (this.rowNumber === 0)
57
+ // // eslint-disable-next-line no-console
58
+ // console.log(`r${this.rowNumber}: ${msg}`)
59
+ // }
60
+
61
+
62
+ (0, _createClass2.default)(LayoutRow, [{
63
+ key: "setAllFilled",
64
+ value: function setAllFilled(data) {
65
+ this.allFilled = data;
66
+ }
67
+ }, {
68
+ key: "getItemAt",
69
+ value: function getItemAt(x) {
70
+ if (this.allFilled) {
71
+ return this.allFilled;
72
+ }
73
+
74
+ if (!this.rowState) {
75
+ return undefined;
76
+ }
77
+
78
+ if (this.rowState.min === undefined) {
79
+ return undefined;
80
+ }
81
+
82
+ if (x < this.rowState.min) {
83
+ return undefined;
84
+ }
85
+
86
+ if (x >= this.rowState.max) {
87
+ return undefined;
88
+ }
89
+
90
+ var offset = x - this.rowState.offset; // if (offset < 0)
91
+ // debugger
92
+ // if (offset >= this.rowState.bits.length)
93
+ // debugger
94
+
95
+ return this.rowState.bits[offset];
96
+ }
97
+ }, {
98
+ key: "isRangeClear",
99
+ value: function isRangeClear(left, right) {
100
+ if (this.allFilled) {
101
+ return false;
102
+ }
103
+
104
+ if (!this.rowState) {
105
+ return true;
106
+ }
107
+
108
+ var _this$rowState = this.rowState,
109
+ min = _this$rowState.min,
110
+ max = _this$rowState.max;
111
+
112
+ if (right <= min || left >= max) {
113
+ return true;
114
+ } // TODO: check right and middle before looping
115
+
116
+
117
+ var maxX = Math.min(max, right);
118
+ var x = Math.max(min, left);
119
+
120
+ for (; x < right && x < maxX; x += 1) {
121
+ if (this.getItemAt(x)) {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ return true;
127
+ }
128
+ }, {
129
+ key: "initialize",
130
+ value: function initialize(left, right) {
131
+ // NOTE: this.rowState.min, this.rowState.max, and this.rowState.offset are interbase coordinates
132
+ var rectWidth = right - left;
133
+ return {
134
+ offset: left - rectWidth,
135
+ min: left,
136
+ max: right,
137
+ bits: new Array(3 * rectWidth)
138
+ }; // this.log(`initialize ${this.rowState.min} - ${this.rowState.max} (${this.rowState.bits.length})`)
139
+ }
140
+ }, {
141
+ key: "addRect",
142
+ value: function addRect(rect, data) {
143
+ var left = rect.l;
144
+ var right = rect.r + this.padding; // only padding on the right
145
+
146
+ if (!this.rowState) {
147
+ this.rowState = this.initialize(left, right);
148
+ } // or check if we need to expand to the left and/or to the right
149
+
150
+
151
+ var oLeft = left - this.rowState.offset;
152
+ var oRight = right - this.rowState.offset;
153
+ var currLength = this.rowState.bits.length; // console.log(oRight, this.rowState.bits.length)
154
+ // expand rightward if necessary
155
+
156
+ if (oRight >= this.rowState.bits.length) {
157
+ var additionalLength = oRight + 1;
158
+
159
+ if (this.rowState.bits.length + additionalLength > this.widthLimit) {
160
+ console.warn('Layout width limit exceeded, discarding old layout. Please be more careful about discarding unused blocks.');
161
+ this.rowState = this.initialize(left, right);
162
+ } else if (additionalLength > 0) {
163
+ this.rowState.bits = this.rowState.bits.concat(new Array(additionalLength));
164
+ }
165
+ } // expand leftward if necessary
166
+
167
+
168
+ if (left < this.rowState.offset) {
169
+ // use math.min to avoid negative lengths
170
+ var _additionalLength = Math.min(currLength - oLeft, this.rowState.offset);
171
+
172
+ if (this.rowState.bits.length + _additionalLength > this.widthLimit) {
173
+ console.warn('Layout width limit exceeded, discarding old layout. Please be more careful about discarding unused blocks.');
174
+ this.rowState = this.initialize(left, right);
175
+ } else {
176
+ this.rowState.bits = new Array(_additionalLength).concat(this.rowState.bits);
177
+ this.rowState.offset -= _additionalLength;
178
+ }
179
+ }
180
+
181
+ oRight = right - this.rowState.offset;
182
+ oLeft = left - this.rowState.offset; // set the bits in the bitmask
183
+ // if (oLeft < 0) debugger
184
+ // if (oRight < 0) debugger
185
+ // if (oRight <= oLeft) debugger
186
+ // if (oRight > this.rowState.bits.length) debugger
187
+
188
+ if (oRight - oLeft > maxFeaturePitchWidth) {
189
+ console.warn("Layout X pitch set too low, feature spans ".concat(oRight - oLeft, " bits in a single row."), rect, data);
190
+ }
191
+
192
+ for (var x = oLeft; x < oRight; x += 1) {
193
+ // if (this.rowState.bits[x] && this.rowState.bits[x].get('name') !== data.get('name')) debugger
194
+ this.rowState.bits[x] = data;
195
+ }
196
+
197
+ if (left < this.rowState.min) {
198
+ this.rowState.min = left;
199
+ }
200
+
201
+ if (right > this.rowState.max) {
202
+ this.rowState.max = right;
203
+ } // // this.log(`added ${leftX} - ${rightX}`)
204
+
205
+ }
206
+ /**
207
+ * Given a range of interbase coordinates, deletes all data dealing with that range
208
+ */
209
+
210
+ }, {
211
+ key: "discardRange",
212
+ value: function discardRange(left, right) {
213
+ if (this.allFilled) {
214
+ return;
215
+ } // allFilled is irrevocable currently
216
+ // if we have no data, do nothing
217
+
218
+
219
+ if (!this.rowState) {
220
+ return;
221
+ } // if doesn't overlap at all, do nothing
222
+
223
+
224
+ if (right <= this.rowState.min || left >= this.rowState.max) {
225
+ return;
226
+ } // if completely encloses range, discard everything
227
+
228
+
229
+ if (left <= this.rowState.min && right >= this.rowState.max) {
230
+ this.rowState = undefined;
231
+ return;
232
+ } // if overlaps left edge, adjust the min
233
+
234
+
235
+ if (right > this.rowState.min && left <= this.rowState.min) {
236
+ this.rowState.min = right;
237
+ } // if overlaps right edge, adjust the max
238
+
239
+
240
+ if (left < this.rowState.max && right >= this.rowState.max) {
241
+ this.rowState.max = left;
242
+ } // now trim the left, right, or both sides of the array
243
+
244
+
245
+ if (this.rowState.offset < this.rowState.min - minSizeToBotherWith && this.rowState.bits.length > this.rowState.max + minSizeToBotherWith - this.rowState.offset) {
246
+ // trim both sides
247
+ var leftTrimAmount = this.rowState.min - this.rowState.offset;
248
+ var rightTrimAmount = this.rowState.bits.length - 1 - (this.rowState.max - this.rowState.offset); // if (rightTrimAmount <= 0) debugger
249
+ // if (leftTrimAmount <= 0) debugger
250
+ // this.log(`trim both sides, ${leftTrimAmount} from left, ${rightTrimAmount} from right`)
251
+
252
+ this.rowState.bits = this.rowState.bits.slice(leftTrimAmount, this.rowState.bits.length - rightTrimAmount);
253
+ this.rowState.offset += leftTrimAmount; // if (this.rowState.offset > this.rowState.min) debugger
254
+ // if (this.rowState.bits.length <= this.rowState.max - this.rowState.offset) debugger
255
+ } else if (this.rowState.offset < this.rowState.min - minSizeToBotherWith) {
256
+ // trim left side
257
+ var desiredOffset = this.rowState.min - Math.floor(minSizeToBotherWith / 2);
258
+ var trimAmount = desiredOffset - this.rowState.offset; // this.log(`trim left side by ${trimAmount}`)
259
+
260
+ this.rowState.bits.splice(0, trimAmount);
261
+ this.rowState.offset += trimAmount; // if (this.rowState.offset > this.rowState.min) debugger
262
+ // if (this.rowState.bits.length <= this.rowState.max - this.rowState.offset) debugger
263
+ } else if (this.rowState.bits.length > this.rowState.max - this.rowState.offset + minSizeToBotherWith) {
264
+ // trim right side
265
+ var desiredLength = this.rowState.max - this.rowState.offset + 1 + Math.floor(minSizeToBotherWith / 2); // this.log(`trim right side by ${this.rowState.bits.length-desiredLength}`)
266
+ // if (desiredLength > this.rowState.bits.length) debugger
267
+
268
+ this.rowState.bits.length = desiredLength; // if (this.rowState.offset > this.rowState.min) debugger
269
+ // if (this.rowState.bits.length <= this.rowState.max - this.rowState.offset) debugger
270
+ } // if (this.rowState.offset > this.rowState.min) debugger
271
+ // if (this.rowState.bits.length <= this.rowState.max - this.rowState.offset) debugger
272
+ // if range now enclosed in the new bounds, loop through and clear the bits
273
+
274
+
275
+ var oLeft = Math.max(this.rowState.min, left) - this.rowState.offset; // if (oLeft < 0) debugger
276
+ // if (oLeft >= this.rowState.bits.length) debugger
277
+ // if (oRight < 0) debugger
278
+ // if (oRight >= this.rowState.bits.length) debugger
279
+
280
+ var oRight = Math.min(right, this.rowState.max) - this.rowState.offset;
281
+
282
+ for (var x = oLeft; x >= 0 && x < oRight; x += 1) {
283
+ this.rowState.bits[x] = undefined;
284
+ }
285
+ }
286
+ }]);
287
+ return LayoutRow;
288
+ }();
289
+
34
290
  var GranularRectLayout = /*#__PURE__*/function () {
291
+ /*
292
+ *
293
+ * pitchX - layout grid pitch in the X direction
294
+ * pitchY - layout grid pitch in the Y direction
295
+ * maxHeight - maximum layout height, default Infinity (no max)
296
+ */
35
297
  function GranularRectLayout() {
36
298
  var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
299
+ _ref$pitchX = _ref.pitchX,
300
+ pitchX = _ref$pitchX === void 0 ? 10 : _ref$pitchX,
301
+ _ref$pitchY = _ref.pitchY,
302
+ pitchY = _ref$pitchY === void 0 ? 10 : _ref$pitchY,
37
303
  _ref$maxHeight = _ref.maxHeight,
38
304
  maxHeight = _ref$maxHeight === void 0 ? 10000 : _ref$maxHeight,
39
- _ref$spacing = _ref.spacing,
40
- spacing = _ref$spacing === void 0 ? 2 : _ref$spacing,
41
- _ref$pitchX = _ref.pitchX,
42
- pitchX = _ref$pitchX === void 0 ? 1 : _ref$pitchX;
305
+ _ref$hardRowLimit = _ref.hardRowLimit,
306
+ hardRowLimit = _ref$hardRowLimit === void 0 ? 10000 : _ref$hardRowLimit,
307
+ _ref$displayMode = _ref.displayMode,
308
+ displayMode = _ref$displayMode === void 0 ? 'normal' : _ref$displayMode;
43
309
 
44
310
  (0, _classCallCheck2.default)(this, GranularRectLayout);
311
+ (0, _defineProperty2.default)(this, "pitchX", void 0);
312
+ (0, _defineProperty2.default)(this, "pitchY", void 0);
313
+ (0, _defineProperty2.default)(this, "hardRowLimit", void 0);
314
+ (0, _defineProperty2.default)(this, "bitmap", void 0);
45
315
  (0, _defineProperty2.default)(this, "rectangles", void 0);
46
316
  (0, _defineProperty2.default)(this, "maxHeightReached", void 0);
47
317
  (0, _defineProperty2.default)(this, "maxHeight", void 0);
48
- (0, _defineProperty2.default)(this, "rbush", void 0);
49
- (0, _defineProperty2.default)(this, "spacing", void 0);
318
+ (0, _defineProperty2.default)(this, "displayMode", void 0);
50
319
  (0, _defineProperty2.default)(this, "pTotalHeight", void 0);
51
- (0, _defineProperty2.default)(this, "pitchX", void 0);
52
- // we keep holding onto the concept of pitchX so that when we zoom out
53
- // layout is redone
54
320
  this.pitchX = pitchX;
321
+ this.pitchY = pitchY;
322
+ this.hardRowLimit = hardRowLimit;
55
323
  this.maxHeightReached = false;
56
- this.rbush = new _rbush.default();
57
- this.spacing = spacing;
324
+ this.displayMode = displayMode; // reduce the pitchY to try and pack the features tighter
325
+
326
+ if (this.displayMode === 'compact') {
327
+ this.pitchY = Math.round(this.pitchY / 4) || 1;
328
+ this.pitchX = Math.round(this.pitchX / 4) || 1;
329
+ }
330
+
331
+ this.bitmap = [];
58
332
  this.rectangles = new Map();
59
- this.maxHeight = Math.ceil(maxHeight);
60
- this.pTotalHeight = 0;
333
+ this.maxHeight = Math.ceil(maxHeight / this.pitchY);
334
+ this.pTotalHeight = 0; // total height, in units of bitmap squares (px/pitchY)
61
335
  }
62
336
  /**
63
337
  * @returns top position for the rect, or Null if laying
@@ -68,59 +342,117 @@ var GranularRectLayout = /*#__PURE__*/function () {
68
342
  (0, _createClass2.default)(GranularRectLayout, [{
69
343
  key: "addRect",
70
344
  value: function addRect(id, left, right, height, data) {
71
- // add to rbush
72
- var existingRecord = this.rectangles.get(id);
345
+ // if we have already laid it out, return its layout
346
+ var storedRec = this.rectangles.get(id);
347
+
348
+ if (storedRec) {
349
+ if (storedRec.top === null) {
350
+ return null;
351
+ } // add it to the bitmap again, since that bitmap range may have been
352
+ // discarded
353
+
73
354
 
74
- if (existingRecord) {
75
- return existingRecord.minY;
355
+ this.addRectToBitmap(storedRec);
356
+ return storedRec.top * this.pitchY;
76
357
  }
77
358
 
78
- var currHeight = 0;
79
- var maxHeightReached = false;
80
- var found = false;
81
-
82
- while ( // 0.01 fudge factor to avoid edge-exact collision detection returning
83
- // true
84
- this.rbush.collides({
85
- minX: left,
86
- minY: currHeight + 0.01,
87
- maxX: right,
88
- maxY: currHeight + height - 0.01
89
- }) && currHeight <= this.maxHeight) {
90
- found = true;
91
- currHeight += 1;
92
-
93
- if (currHeight + height >= this.maxHeight) {
94
- maxHeightReached = true;
95
- break;
359
+ var pLeft = Math.floor(left / this.pitchX);
360
+ var pRight = Math.floor(right / this.pitchX);
361
+ var pHeight = Math.ceil(height / this.pitchY);
362
+ var rectangle = {
363
+ id: id,
364
+ l: pLeft,
365
+ r: pRight,
366
+ top: null,
367
+ h: pHeight,
368
+ originalHeight: height,
369
+ data: data
370
+ };
371
+ var maxTop = this.maxHeight - pHeight;
372
+ var top = 0;
373
+
374
+ if (this.displayMode !== 'collapse') {
375
+ for (; top <= maxTop; top += 1) {
376
+ if (!this.collides(rectangle, top)) {
377
+ break;
378
+ }
379
+ }
380
+
381
+ if (top > maxTop) {
382
+ rectangle.top = null;
383
+ this.rectangles.set(id, rectangle);
384
+ this.maxHeightReached = true;
385
+ return null;
96
386
  }
97
387
  }
98
388
 
99
- if (found) {
100
- currHeight += this.spacing;
389
+ rectangle.top = top;
390
+ this.addRectToBitmap(rectangle);
391
+ this.rectangles.set(id, rectangle);
392
+ this.pTotalHeight = Math.max(this.pTotalHeight || 0, top + pHeight);
393
+ return top * this.pitchY;
394
+ }
395
+ }, {
396
+ key: "collides",
397
+ value: function collides(rect, top) {
398
+ var bitmap = this.bitmap;
399
+ var maxY = top + rect.h;
400
+
401
+ for (var y = top; y < maxY; y += 1) {
402
+ var row = bitmap[y];
403
+
404
+ if (row && !row.isRangeClear(rect.l, rect.r)) {
405
+ return true;
406
+ }
101
407
  }
102
408
 
103
- if (!maxHeightReached) {
104
- var record = {
105
- minX: left,
106
- minY: currHeight,
107
- maxX: right,
108
- maxY: currHeight + height,
109
- id: id,
110
- data: data
111
- };
112
- this.rbush.insert(record);
113
- this.rectangles.set(id, record);
114
- this.pTotalHeight = Math.min(this.maxHeight, Math.max(this.pTotalHeight, currHeight + height));
409
+ return false;
410
+ }
411
+ /**
412
+ * make a subarray if it does not exist
413
+ */
414
+
415
+ }, {
416
+ key: "autovivifyRow",
417
+ value: function autovivifyRow(bitmap, y) {
418
+ var row = bitmap[y];
419
+
420
+ if (!row) {
421
+ if (y > this.hardRowLimit) {
422
+ throw new Error("layout hard limit (".concat(this.hardRowLimit * this.pitchY, "px) exceeded, aborting layout"));
423
+ }
424
+
425
+ row = new LayoutRow();
426
+ bitmap[y] = row;
115
427
  }
116
428
 
117
- this.maxHeightReached = this.maxHeightReached || maxHeightReached;
118
- return maxHeightReached ? null : currHeight;
429
+ return row;
119
430
  }
120
431
  }, {
121
- key: "collides",
122
- value: function collides() {
123
- return false;
432
+ key: "addRectToBitmap",
433
+ value: function addRectToBitmap(rect) {
434
+ if (rect.top === null) {
435
+ return;
436
+ }
437
+
438
+ var data = rect.data || rect.id;
439
+ var bitmap = this.bitmap;
440
+ var yEnd = rect.top + rect.h;
441
+
442
+ if (rect.r - rect.l > maxFeaturePitchWidth) {
443
+ // the rect is very big in relation to the view size, just pretend, for
444
+ // the purposes of layout, that it extends infinitely. this will cause
445
+ // weird layout if a user scrolls manually for a very, very long time
446
+ // along the genome at the same zoom level. but most users will not do
447
+ // that. hopefully.
448
+ for (var y = rect.top; y < yEnd; y += 1) {
449
+ this.autovivifyRow(bitmap, y).setAllFilled(data);
450
+ }
451
+ } else {
452
+ for (var _y = rect.top; _y < yEnd; _y += 1) {
453
+ this.autovivifyRow(bitmap, _y).addRect(rect, data);
454
+ }
455
+ }
124
456
  }
125
457
  /**
126
458
  * Given a range of X coordinates, deletes all data dealing with
@@ -129,7 +461,20 @@ var GranularRectLayout = /*#__PURE__*/function () {
129
461
 
130
462
  }, {
131
463
  key: "discardRange",
132
- value: function discardRange() {}
464
+ value: function discardRange(left, right) {
465
+ // console.log( 'discard', left, right );
466
+ var pLeft = Math.floor(left / this.pitchX);
467
+ var pRight = Math.floor(right / this.pitchX);
468
+ var bitmap = this.bitmap;
469
+
470
+ for (var y = 0; y < bitmap.length; y += 1) {
471
+ var row = bitmap[y];
472
+
473
+ if (row) {
474
+ row.discardRange(pLeft, pRight);
475
+ }
476
+ }
477
+ }
133
478
  }, {
134
479
  key: "hasSeen",
135
480
  value: function hasSeen(id) {
@@ -138,25 +483,24 @@ var GranularRectLayout = /*#__PURE__*/function () {
138
483
  }, {
139
484
  key: "getByCoord",
140
485
  value: function getByCoord(x, y) {
141
- var rect = {
142
- minX: x,
143
- minY: y,
144
- maxX: x + 1,
145
- maxY: y + 1
146
- };
147
- return this.rbush.collides(rect) ? this.rbush.search(rect)[0].id : undefined;
486
+ var pY = Math.floor(y / this.pitchY);
487
+ var row = this.bitmap[pY];
488
+
489
+ if (!row) {
490
+ return undefined;
491
+ }
492
+
493
+ var pX = Math.floor(x / this.pitchX);
494
+ return row.getItemAt(pX);
148
495
  }
149
496
  }, {
150
497
  key: "getByID",
151
498
  value: function getByID(id) {
152
- var rect = this.rectangles.get(id);
499
+ var r = this.rectangles.get(id);
153
500
 
154
- if (rect) {
155
- var minX = rect.minX,
156
- maxX = rect.maxX,
157
- minY = rect.minY,
158
- maxY = rect.maxY;
159
- return [minX, minY, maxX, maxY];
501
+ if (r) {
502
+ var t = r.top * this.pitchY;
503
+ return [r.l * this.pitchX, t, r.r * this.pitchX, t + r.originalHeight];
160
504
  }
161
505
 
162
506
  return undefined;
@@ -167,17 +511,37 @@ var GranularRectLayout = /*#__PURE__*/function () {
167
511
  }, {
168
512
  key: "getTotalHeight",
169
513
  value: function getTotalHeight() {
170
- return this.pTotalHeight;
514
+ return this.pTotalHeight * this.pitchY;
171
515
  }
172
516
  }, {
173
517
  key: "totalHeight",
174
518
  get: function get() {
175
519
  return this.getTotalHeight();
176
520
  }
521
+ }, {
522
+ key: "getRectangles",
523
+ value: function getRectangles() {
524
+ var _this = this;
525
+
526
+ return new Map(Array.from(this.rectangles.entries()).map(function (_ref2) {
527
+ var _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
528
+ id = _ref3[0],
529
+ rect = _ref3[1];
530
+
531
+ var l = rect.l,
532
+ r = rect.r,
533
+ originalHeight = rect.originalHeight,
534
+ top = rect.top;
535
+ var t = (top || 0) * _this.pitchY;
536
+ var b = t + originalHeight;
537
+ return [id, [l * _this.pitchX, t, r * _this.pitchX, b]]; // left, top, right, bottom
538
+ }));
539
+ }
177
540
  }, {
178
541
  key: "serializeRegion",
179
542
  value: function serializeRegion(region) {
180
543
  var regionRectangles = {};
544
+ var maxHeightReached = false;
181
545
 
182
546
  var _iterator = _createForOfIteratorHelper(this.rectangles.entries()),
183
547
  _step;
@@ -188,15 +552,24 @@ var GranularRectLayout = /*#__PURE__*/function () {
188
552
  id = _step$value[0],
189
553
  rect = _step$value[1];
190
554
 
191
- var minX = rect.minX,
192
- maxX = rect.maxX,
193
- minY = rect.minY,
194
- maxY = rect.maxY;
195
- var start = region.start,
196
- end = region.end;
197
-
198
- if (segmentsIntersect(start, end, minX, maxX)) {
199
- regionRectangles[id] = [minX, minY, maxX, maxY];
555
+ var l = rect.l,
556
+ r = rect.r,
557
+ originalHeight = rect.originalHeight,
558
+ top = rect.top;
559
+
560
+ if (rect.top === null) {
561
+ maxHeightReached = true;
562
+ } else {
563
+ var t = (top || 0) * this.pitchY;
564
+ var b = t + originalHeight;
565
+ var y1 = l * this.pitchX;
566
+ var y2 = r * this.pitchX;
567
+ var x1 = region.start;
568
+ var x2 = region.end; // add +/- pitchX to avoid resolution causing errors
569
+
570
+ if (segmentsIntersect(x1, x2, y1 - this.pitchX, y2 + this.pitchX)) {
571
+ regionRectangles[id] = [y1, t, y2, b];
572
+ }
200
573
  }
201
574
  }
202
575
  } catch (err) {
@@ -208,29 +581,15 @@ var GranularRectLayout = /*#__PURE__*/function () {
208
581
  return {
209
582
  rectangles: regionRectangles,
210
583
  totalHeight: this.getTotalHeight(),
211
- maxHeightReached: this.maxHeightReached
584
+ maxHeightReached: maxHeightReached
212
585
  };
213
586
  }
214
- }, {
215
- key: "getRectangles",
216
- value: function getRectangles() {
217
- return new Map((0, _toConsumableArray2.default)(this.rectangles.entries()).map(function (_ref2) {
218
- var _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
219
- id = _ref3[0],
220
- _ref3$ = _ref3[1],
221
- minX = _ref3$.minX,
222
- minY = _ref3$.minY,
223
- maxX = _ref3$.maxX,
224
- maxY = _ref3$.maxY;
225
-
226
- return [id, [minX, minY, maxX, maxY]];
227
- }));
228
- }
229
587
  }, {
230
588
  key: "toJSON",
231
589
  value: function toJSON() {
590
+ var rectangles = (0, _index.objectFromEntries)(this.getRectangles());
232
591
  return {
233
- rectangles: (0, _index.objectFromEntries)(this.getRectangles()),
592
+ rectangles: rectangles,
234
593
  totalHeight: this.getTotalHeight(),
235
594
  maxHeightReached: this.maxHeightReached
236
595
  };