@genome-spy/core 0.19.1 → 0.22.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.
Files changed (42) hide show
  1. package/dist/index.js +42 -42
  2. package/dist/schema.json +213 -21
  3. package/package.json +3 -3
  4. package/src/genomeSpy.js +16 -11
  5. package/src/gl/dataToVertices.js +43 -46
  6. package/src/gl/includes/common.glsl +12 -12
  7. package/src/gl/includes/picking.fragment.glsl +0 -2
  8. package/src/gl/includes/picking.vertex.glsl +0 -2
  9. package/src/marks/link.js +32 -39
  10. package/src/marks/mark.js +168 -95
  11. package/src/marks/pointMark.js +28 -59
  12. package/src/marks/rectMark.js +38 -33
  13. package/src/marks/rule.js +31 -21
  14. package/src/marks/text.js +18 -14
  15. package/src/spec/mark.d.ts +0 -3
  16. package/src/spec/title.d.ts +102 -0
  17. package/src/spec/view.d.ts +6 -4
  18. package/src/tooltip/dataTooltipHandler.js +3 -2
  19. package/src/utils/binnedIndex.js +147 -0
  20. package/src/utils/binnedIndex.test.js +73 -0
  21. package/src/utils/layout/flexLayout.js +35 -3
  22. package/src/utils/layout/flexLayout.test.js +14 -0
  23. package/src/utils/layout/grid.js +95 -0
  24. package/src/utils/layout/grid.test.js +71 -0
  25. package/src/utils/layout/padding.js +13 -0
  26. package/src/utils/layout/rectangle.js +6 -0
  27. package/src/view/axisView.js +3 -5
  28. package/src/view/concatView.js +24 -275
  29. package/src/view/containerView.js +18 -0
  30. package/src/view/gridView.js +774 -0
  31. package/src/view/implicitRootView.js +14 -0
  32. package/src/view/layerView.js +15 -1
  33. package/src/view/renderingContext/deferredViewRenderingContext.js +3 -1
  34. package/src/view/renderingContext/simpleViewRenderingContext.js +3 -1
  35. package/src/view/title.js +165 -0
  36. package/src/view/unitView.js +9 -5
  37. package/src/view/view.js +35 -14
  38. package/src/view/viewContext.d.ts +6 -1
  39. package/src/view/viewUtils.js +1 -93
  40. package/src/view/zoom.js +89 -0
  41. package/src/utils/binnedRangeIndex.js +0 -83
  42. package/src/view/decoratorView.js +0 -513
package/src/marks/mark.js CHANGED
@@ -4,10 +4,11 @@ import {
4
4
  createUniformBlockInfo,
5
5
  createVertexArrayInfo,
6
6
  setAttribInfoBufferFromArray,
7
+ setBlockUniforms,
7
8
  setUniformBlock,
8
9
  setUniforms,
9
10
  } from "twgl.js";
10
- import { isDiscrete } from "vega-scale";
11
+ import { isContinuous, isDiscrete } from "vega-scale";
11
12
  import createEncoders, {
12
13
  isChannelDefWithScale,
13
14
  isDatumDef,
@@ -32,6 +33,7 @@ import { getCachedOrCall } from "../utils/propertyCacher";
32
33
  import { createProgram } from "../gl/webGLHelper";
33
34
  import coalesceProperties from "../utils/propertyCoalescer";
34
35
  import { isScalar } from "../utils/variableTools";
36
+ import { InternMap } from "internmap";
35
37
 
36
38
  export const SAMPLE_FACET_UNIFORM = "SAMPLE_FACET_UNIFORM";
37
39
  export const SAMPLE_FACET_TEXTURE = "SAMPLE_FACET_TEXTURE";
@@ -77,6 +79,12 @@ export default class Mark {
77
79
  /** @type {import("twgl.js").UniformBlockInfo} WebGL buffers */
78
80
  this.domainUniformInfo = undefined;
79
81
 
82
+ /** @type {import("twgl.js").UniformBlockInfo} WebGL buffers */
83
+ this.viewUniformInfo = undefined;
84
+
85
+ /** @type {RangeMap<any>} keep track of facet locations within the vertex array */
86
+ this.rangeMap = new RangeMap();
87
+
80
88
  // TODO: Implement https://vega.github.io/vega-lite/docs/config.html
81
89
  /** @type {MarkConfig} */
82
90
  this.defaultProperties = {
@@ -399,9 +407,21 @@ export default class Mark {
399
407
  );
400
408
  }
401
409
 
410
+ this.viewUniformInfo = createUniformBlockInfo(
411
+ this.gl,
412
+ this.programInfo,
413
+ "View"
414
+ );
415
+
402
416
  this.gl.useProgram(this.programInfo.program);
403
417
 
404
418
  this._setDatums();
419
+
420
+ setUniforms(this.programInfo, {
421
+ // left pos, left height, right pos, right height
422
+ uSampleFacet: [0, 1, 0, 1],
423
+ uTransitionOffset: 0.0,
424
+ });
405
425
  }
406
426
 
407
427
  _setDatums() {
@@ -535,21 +555,27 @@ export default class Mark {
535
555
  * and scales) and buffers.
536
556
  *
537
557
  * @param {import("../view/rendering").GlobalRenderingOptions} options
558
+ * @returns {(() => void)[]}
538
559
  */
539
560
  // eslint-disable-next-line complexity
540
561
  prepareRender(options) {
541
562
  const glHelper = this.glHelper;
542
563
  const gl = this.gl;
543
564
 
544
- if (!this.vertexArrayInfo) {
545
- this.vertexArrayInfo = createVertexArrayInfo(
546
- this.gl,
547
- this.programInfo,
548
- this.bufferInfo
549
- );
550
- }
565
+ /** @type {(() => void)[]} */
566
+ const ops = [];
551
567
 
552
- gl.useProgram(this.programInfo.program);
568
+ ops.push(() => {
569
+ if (!this.vertexArrayInfo) {
570
+ this.vertexArrayInfo = createVertexArrayInfo(
571
+ this.gl,
572
+ this.programInfo,
573
+ this.bufferInfo
574
+ );
575
+ }
576
+
577
+ gl.useProgram(this.programInfo.program);
578
+ });
553
579
 
554
580
  if (this.domainUniformInfo) {
555
581
  // TODO: Only update the domains that have changed
@@ -572,19 +598,24 @@ export default class Mark {
572
598
 
573
599
  if (resolution) {
574
600
  const scale = resolution.getScale();
575
- const domain = isDiscrete(scale.type)
576
- ? [0, scale.domain().length]
577
- : scale.domain();
578
-
579
- setter(
580
- isHighPrecisionScale(scale.type)
581
- ? toHighPrecisionDomainUniform(domain)
582
- : domain
583
- );
601
+
602
+ ops.push(() => {
603
+ const domain = isDiscrete(scale.type)
604
+ ? [0, scale.domain().length]
605
+ : scale.domain();
606
+
607
+ setter(
608
+ isHighPrecisionScale(scale.type)
609
+ ? toHighPrecisionDomainUniform(domain)
610
+ : domain
611
+ );
612
+ });
584
613
  }
585
614
  }
586
615
 
587
- setUniformBlock(gl, this.programInfo, this.domainUniformInfo);
616
+ ops.push(() =>
617
+ setUniformBlock(gl, this.programInfo, this.domainUniformInfo)
618
+ );
588
619
  }
589
620
 
590
621
  for (const [channel, channelDef] of Object.entries(this.encoding)) {
@@ -599,48 +630,56 @@ export default class Mark {
599
630
 
600
631
  const texture = glHelper.rangeTextures.get(resolution);
601
632
  if (texture) {
602
- setUniforms(this.programInfo, {
603
- [RANGE_TEXTURE_PREFIX + channel]: texture,
604
- });
633
+ ops.push(() =>
634
+ setUniforms(this.programInfo, {
635
+ [RANGE_TEXTURE_PREFIX + channel]: texture,
636
+ })
637
+ );
605
638
  }
606
639
  }
607
640
  }
608
641
 
609
642
  if (this.getSampleFacetMode() == SAMPLE_FACET_TEXTURE) {
610
- /** @type {WebGLTexture} */
611
- let facetTexture;
612
- for (const view of this.unitView.getAncestors()) {
613
- facetTexture = view.getSampleFacetTexture();
614
- if (facetTexture) {
615
- break;
643
+ ops.push(() => {
644
+ /** @type {WebGLTexture} */
645
+ let facetTexture;
646
+ for (const view of this.unitView.getAncestors()) {
647
+ facetTexture = view.getSampleFacetTexture();
648
+ if (facetTexture) {
649
+ break;
650
+ }
616
651
  }
617
- }
618
652
 
619
- setUniforms(this.programInfo, {
620
- uSampleFacetTexture: facetTexture,
653
+ if (!facetTexture) {
654
+ throw new Error("No facet texture available. This is bug.");
655
+ }
656
+
657
+ setUniforms(this.programInfo, {
658
+ uSampleFacetTexture: facetTexture,
659
+ });
621
660
  });
622
661
  }
623
662
 
624
- setUniforms(this.programInfo, {
625
- uDevicePixelRatio: this.glHelper.dpr,
626
- uViewOpacity: this.unitView.getEffectiveOpacity(),
627
- // TODO: Rendering of the mark should be completely skipped if it doesn't
628
- // participate picking
629
- uPickingEnabled:
630
- (options.picking ?? false) && this.isPickingParticipant(),
631
- });
632
-
633
- setUniforms(this.programInfo, {
634
- // left pos, left height, right pos, right height
635
- uSampleFacet: [0, 1, 0, 1],
636
- uTransitionOffset: 0.0,
637
- });
663
+ // TODO: Rendering of the mark should be completely skipped if it doesn't
664
+ // participate picking
665
+ const picking =
666
+ (options.picking ?? false) && this.isPickingParticipant();
667
+
668
+ // Note: the block is sent to GPU in setViewport(), which is repeated for each facet
669
+ ops.push(() =>
670
+ setBlockUniforms(this.viewUniformInfo, {
671
+ uViewOpacity: this.unitView.getEffectiveOpacity(),
672
+ uPickingEnabled: picking,
673
+ })
674
+ );
638
675
 
639
676
  if (this.opaque || options.picking) {
640
- gl.disable(gl.BLEND);
677
+ ops.push(() => gl.disable(gl.BLEND));
641
678
  } else {
642
- gl.enable(gl.BLEND);
679
+ ops.push(() => gl.enable(gl.BLEND));
643
680
  }
681
+
682
+ return ops;
644
683
  }
645
684
 
646
685
  /**
@@ -702,58 +741,56 @@ export default class Mark {
702
741
  /**
703
742
  * @param {DrawFunction} draw A function that draws a range of vertices
704
743
  * @param {import("./Mark").MarkRenderingOptions} options
705
- * @param {function():Map<string, import("../gl/dataToVertices").RangeEntry>} rangeMapSource
706
744
  */
707
- createRenderCallback(draw, options, rangeMapSource) {
745
+ createRenderCallback(draw, options) {
708
746
  // eslint-disable-next-line consistent-this
709
747
  const self = this;
710
748
 
711
749
  /** @type {function(import("../gl/dataToVertices").RangeEntry):void} rangeEntry */
712
750
  let drawWithRangeEntry;
713
751
 
714
- if (this.properties.buildIndex) {
715
- const scale = this.unitView.getScaleResolution("x")?.getScale();
716
-
717
- drawWithRangeEntry = (rangeEntry) => {
718
- if (scale && rangeEntry.xIndex) {
719
- const domain = scale.domain();
720
- const vertexIndices = rangeEntry.xIndex(
721
- domain[0],
722
- domain[1]
723
- );
724
- const offset = vertexIndices[0];
725
- const count = vertexIndices[1] - offset;
726
- if (count > 0) {
727
- draw(offset, count);
728
- }
729
- } else {
730
- draw(rangeEntry.offset, rangeEntry.count);
752
+ const scale = this.unitView.getScaleResolution("x")?.getScale();
753
+ const continuous = scale && isContinuous(scale.type);
754
+ const domainStartOffset = ["index", "locus"].includes(scale?.type)
755
+ ? -1
756
+ : 0;
757
+
758
+ /** @type {[number, number]} Recycle to ease garbage collector's work */
759
+ const arr = [0, 0];
760
+
761
+ drawWithRangeEntry = (rangeEntry) => {
762
+ if (continuous && rangeEntry.xIndex) {
763
+ const domain = scale.domain();
764
+ const vertexIndices = rangeEntry.xIndex(
765
+ domain[0] + domainStartOffset,
766
+ domain[1],
767
+ arr
768
+ );
769
+ const offset = vertexIndices[0];
770
+ const count = vertexIndices[1] - offset;
771
+ if (count > 0) {
772
+ draw(offset, count);
731
773
  }
732
- };
733
- } else {
734
- drawWithRangeEntry = (rangeEntry) =>
774
+ } else {
735
775
  draw(rangeEntry.offset, rangeEntry.count);
736
- }
737
-
738
- if (this.properties.dynamicData) {
739
- return function renderDynamic() {
740
- const rangeEntry = rangeMapSource().get(options.facetId);
741
- if (rangeEntry && rangeEntry.count) {
742
- if (self.prepareSampleFacetRendering(options)) {
743
- drawWithRangeEntry(rangeEntry);
744
- }
745
- }
746
- };
747
- } else {
748
- const rangeEntry = rangeMapSource().get(options.facetId);
749
- if (rangeEntry && rangeEntry.count) {
750
- return function renderStatic() {
751
- if (self.prepareSampleFacetRendering(options)) {
752
- drawWithRangeEntry(rangeEntry);
753
- }
754
- };
755
776
  }
756
- }
777
+ };
778
+
779
+ const rangeEntry = this.rangeMap.get(options.facetId);
780
+
781
+ return options.sampleFacetRenderingOptions
782
+ ? function renderSampleFacetRange() {
783
+ if (rangeEntry.count) {
784
+ if (self.prepareSampleFacetRendering(options)) {
785
+ drawWithRangeEntry(rangeEntry);
786
+ }
787
+ }
788
+ }
789
+ : function renderRange() {
790
+ if (rangeEntry.count) {
791
+ drawWithRangeEntry(rangeEntry);
792
+ }
793
+ };
757
794
  }
758
795
 
759
796
  /**
@@ -835,7 +872,7 @@ export default class Mark {
835
872
  uViewScale,
836
873
  };
837
874
  } else {
838
- // Viewport comprises of the full canvas
875
+ // Viewport comprises the full canvas
839
876
  gl.viewport(
840
877
  0,
841
878
  0,
@@ -858,15 +895,15 @@ export default class Mark {
858
895
  };
859
896
  }
860
897
 
861
- // TODO: Optimization: Use uniform buffer object
862
- setUniforms(this.programInfo, uniforms);
863
-
864
- setUniforms(this.programInfo, {
898
+ setBlockUniforms(this.viewUniformInfo, {
899
+ ...uniforms,
865
900
  uViewportSize: [coords.width, coords.height],
901
+ uDevicePixelRatio: this.glHelper.dpr,
866
902
  });
867
903
 
868
- // TODO: Optimize: don't set viewport and stuff if rect is outside clipRect or screen
904
+ setUniformBlock(this.gl, this.programInfo, this.viewUniformInfo);
869
905
 
906
+ // TODO: Optimize: don't set viewport and stuff if rect is outside clipRect or screen
870
907
  return clippedCoords.height > 0 && clippedCoords.width > 0;
871
908
  }
872
909
 
@@ -884,3 +921,39 @@ export default class Mark {
884
921
  // override
885
922
  }
886
923
  }
924
+
925
+ /**
926
+ * @augments {InternMap<K, import("../gl/dataToVertices").RangeEntry>}
927
+ * @template K
928
+ */
929
+ class RangeMap extends InternMap {
930
+ constructor() {
931
+ super([], JSON.stringify);
932
+ }
933
+
934
+ /**
935
+ * @param {K} key
936
+ */
937
+ get(key) {
938
+ let value = super.get(key);
939
+ if (value === undefined) {
940
+ value = {
941
+ offset: 0,
942
+ count: 0,
943
+ xIndex: undefined,
944
+ };
945
+ super.set(key, value);
946
+ }
947
+ return value;
948
+ }
949
+
950
+ /**
951
+ *
952
+ * @param {Map<K, import("../gl/dataToVertices").RangeEntry>} anotherMap
953
+ */
954
+ migrateEntries(anotherMap) {
955
+ for (const [key, value] of anotherMap.entries()) {
956
+ Object.assign(this.get(key), value);
957
+ }
958
+ }
959
+ }
@@ -1,6 +1,5 @@
1
1
  import { drawBufferInfo, setBuffersAndAttributes, setUniforms } from "twgl.js";
2
- import { bisector, quantileSorted } from "d3-array";
3
- import { zoomLinear } from "vega-util";
2
+ import { quantileSorted } from "d3-array";
4
3
  import { PointVertexBuilder } from "../gl/dataToVertices";
5
4
  import VERTEX_SHADER from "../gl/point.vertex.glsl";
6
5
  import FRAGMENT_SHADER from "../gl/point.fragment.glsl";
@@ -156,7 +155,7 @@ export default class PointMark extends Mark {
156
155
  builder.addBatches(collector.facetBatches);
157
156
 
158
157
  const vertexData = builder.toArrays();
159
- this.rangeMap = vertexData.rangeMap;
158
+ this.rangeMap.migrateEntries(vertexData.rangeMap);
160
159
  this.updateBufferInfo(vertexData);
161
160
  }
162
161
 
@@ -210,44 +209,25 @@ export default class PointMark extends Mark {
210
209
  * @param {import("../view/rendering").GlobalRenderingOptions} options
211
210
  */
212
211
  prepareRender(options) {
213
- super.prepareRender(options);
212
+ const ops = super.prepareRender(options);
214
213
 
215
- setUniforms(this.programInfo, {
216
- uMaxPointSize: this._getMaxPointSize(),
217
- uScaleFactor: this._getGeometricScaleFactor(),
218
- uSemanticThreshold: this.getSemanticThreshold(),
219
- });
214
+ ops.push(() =>
215
+ setUniforms(this.programInfo, {
216
+ uMaxPointSize: this._getMaxPointSize(),
217
+ uScaleFactor: this._getGeometricScaleFactor(),
218
+ uSemanticThreshold: this.getSemanticThreshold(),
219
+ })
220
+ );
220
221
 
221
- setBuffersAndAttributes(
222
- this.gl,
223
- this.programInfo,
224
- this.vertexArrayInfo
222
+ ops.push(() =>
223
+ setBuffersAndAttributes(
224
+ this.gl,
225
+ this.programInfo,
226
+ this.vertexArrayInfo
227
+ )
225
228
  );
226
229
 
227
- // Setup bisector that allows for searching the points that reside within the viewport.
228
- const xEncoder = this.encoders.x;
229
- if (xEncoder && !xEncoder.constant) {
230
- const bisect = bisector(xEncoder.accessor).left;
231
- const visibleDomain = this.unitView
232
- .getScaleResolution("x")
233
- .getScale()
234
- .domain();
235
-
236
- // A hack to include points that are just beyond the borders. TODO: Compute based on maxPointSize
237
- const paddedDomain = zoomLinear(visibleDomain, null, 1.01);
238
-
239
- /** @param {any[]} facetId */
240
- this._findIndices = (facetId) => {
241
- const data = this.unitView
242
- .getCollector()
243
- .facetBatches.get(facetId);
244
-
245
- return [
246
- bisect(data, paddedDomain[0]),
247
- bisect(data, paddedDomain[paddedDomain.length - 1]),
248
- ];
249
- };
250
- }
230
+ return ops;
251
231
  }
252
232
 
253
233
  /**
@@ -256,27 +236,16 @@ export default class PointMark extends Mark {
256
236
  render(options) {
257
237
  const gl = this.gl;
258
238
 
259
- return this.createRenderCallback(
260
- (offset, count) => {
261
- // TODO: findIndices is rather slow. Consider a more coarse-grained, "tiled" solution.
262
- const [lower, upper] = this._findIndices
263
- ? this._findIndices(options.facetId)
264
- : [0, count];
265
-
266
- const length = upper - lower;
267
-
268
- if (length) {
269
- drawBufferInfo(
270
- gl,
271
- this.vertexArrayInfo,
272
- gl.POINTS,
273
- length,
274
- offset + lower
275
- );
276
- }
277
- },
278
- options,
279
- () => this.rangeMap
280
- );
239
+ return this.createRenderCallback((offset, count) => {
240
+ if (count) {
241
+ drawBufferInfo(
242
+ gl,
243
+ this.vertexArrayInfo,
244
+ gl.POINTS,
245
+ count,
246
+ offset
247
+ );
248
+ }
249
+ }, options);
281
250
  }
282
251
  }
@@ -146,6 +146,25 @@ export default class RectMark extends Mark {
146
146
  );
147
147
  }
148
148
 
149
+ finalizeGraphicsInitialization() {
150
+ super.finalizeGraphicsInitialization();
151
+
152
+ this.gl.useProgram(this.programInfo.program);
153
+
154
+ const props = this.properties;
155
+
156
+ setUniforms(this.programInfo, {
157
+ uMinSize: [props.minWidth, props.minHeight], // in pixels
158
+ uMinOpacity: props.minOpacity,
159
+ uCornerRadii: [
160
+ props.cornerRadiusTopRight ?? props.cornerRadius,
161
+ props.cornerRadiusBottomRight ?? props.cornerRadius,
162
+ props.cornerRadiusTopLeft ?? props.cornerRadius,
163
+ props.cornerRadiusBottomLeft ?? props.cornerRadius,
164
+ ],
165
+ });
166
+ }
167
+
149
168
  updateGraphicsData() {
150
169
  const collector = this.unitView.getCollector();
151
170
  const numItems = collector.getItemCount();
@@ -155,13 +174,12 @@ export default class RectMark extends Mark {
155
174
  encoders: this.encoders,
156
175
  attributes: this.getAttributes(),
157
176
  numItems,
158
- buildXIndex: this.properties.buildIndex,
159
177
  });
160
178
 
161
179
  builder.addBatches(collector.facetBatches);
162
180
 
163
181
  const vertexData = builder.toArrays();
164
- this.rangeMap = vertexData.rangeMap;
182
+ this.rangeMap.migrateEntries(vertexData.rangeMap);
165
183
  this.updateBufferInfo(vertexData);
166
184
  }
167
185
 
@@ -169,26 +187,17 @@ export default class RectMark extends Mark {
169
187
  * @param {import("../view/rendering").GlobalRenderingOptions} options
170
188
  */
171
189
  prepareRender(options) {
172
- super.prepareRender(options);
190
+ const ops = super.prepareRender(options);
173
191
 
174
- const props = this.properties;
175
-
176
- setUniforms(this.programInfo, {
177
- uMinSize: [props.minWidth, props.minHeight], // in pixels
178
- uMinOpacity: props.minOpacity,
179
- uCornerRadii: [
180
- props.cornerRadiusTopRight ?? props.cornerRadius,
181
- props.cornerRadiusBottomRight ?? props.cornerRadius,
182
- props.cornerRadiusTopLeft ?? props.cornerRadius,
183
- props.cornerRadiusBottomLeft ?? props.cornerRadius,
184
- ],
185
- });
186
-
187
- setBuffersAndAttributes(
188
- this.gl,
189
- this.programInfo,
190
- this.vertexArrayInfo
192
+ ops.push(() =>
193
+ setBuffersAndAttributes(
194
+ this.gl,
195
+ this.programInfo,
196
+ this.vertexArrayInfo
197
+ )
191
198
  );
199
+
200
+ return ops;
192
201
  }
193
202
 
194
203
  /**
@@ -197,19 +206,15 @@ export default class RectMark extends Mark {
197
206
  render(options) {
198
207
  const gl = this.gl;
199
208
 
200
- return this.createRenderCallback(
201
- (offset, count) => {
202
- drawBufferInfo(
203
- gl,
204
- this.vertexArrayInfo,
205
- gl.TRIANGLE_STRIP,
206
- count,
207
- offset
208
- );
209
- },
210
- options,
211
- () => this.rangeMap
212
- );
209
+ return this.createRenderCallback((offset, count) => {
210
+ drawBufferInfo(
211
+ gl,
212
+ this.vertexArrayInfo,
213
+ gl.TRIANGLE_STRIP,
214
+ count,
215
+ offset
216
+ );
217
+ }, options);
213
218
  }
214
219
 
215
220
  /**
package/src/marks/rule.js CHANGED
@@ -139,6 +139,20 @@ export default class RuleMark extends Mark {
139
139
  this.createAndLinkShaders(VERTEX_SHADER, FRAGMENT_SHADER);
140
140
  }
141
141
 
142
+ finalizeGraphicsInitialization() {
143
+ super.finalizeGraphicsInitialization();
144
+
145
+ this.gl.useProgram(this.programInfo.program);
146
+
147
+ const props = this.properties;
148
+
149
+ setUniforms(this.programInfo, {
150
+ uMinLength: props.minLength,
151
+ uDashTextureSize: this.dashTextureSize,
152
+ uStrokeCap: ["butt", "square", "round"].indexOf(props.strokeCap),
153
+ });
154
+ }
155
+
142
156
  updateGraphicsData() {
143
157
  const collector = this.unitView.getCollector();
144
158
  const itemCount = collector.getItemCount();
@@ -147,13 +161,12 @@ export default class RuleMark extends Mark {
147
161
  encoders: this.encoders,
148
162
  attributes: this.getAttributes(),
149
163
  numItems: Math.max(itemCount, this.properties.minBufferSize || 0),
150
- buildXIndex: this.properties.buildIndex,
151
164
  });
152
165
 
153
166
  builder.addBatches(collector.facetBatches);
154
167
 
155
168
  const vertexData = builder.toArrays();
156
- this.rangeMap = vertexData.rangeMap;
169
+ this.rangeMap.migrateEntries(vertexData.rangeMap);
157
170
 
158
171
  this.updateBufferInfo(vertexData);
159
172
  }
@@ -162,28 +175,26 @@ export default class RuleMark extends Mark {
162
175
  * @param {import("../view/rendering").GlobalRenderingOptions} options
163
176
  */
164
177
  prepareRender(options) {
165
- super.prepareRender(options);
166
-
167
- setUniforms(this.programInfo, {
168
- uMinLength: this.properties.minLength,
169
- uDashTextureSize: this.dashTextureSize,
170
- uStrokeCap: ["butt", "square", "round"].indexOf(
171
- this.properties.strokeCap
172
- ),
173
- });
178
+ const ops = super.prepareRender(options);
174
179
 
175
180
  if (this.dashTexture) {
176
- setUniforms(this.programInfo, {
177
- uDashTexture: this.dashTexture,
178
- uStrokeDashOffset: this.properties.strokeDashOffset,
179
- });
181
+ ops.push(() =>
182
+ setUniforms(this.programInfo, {
183
+ uDashTexture: this.dashTexture,
184
+ uStrokeDashOffset: this.properties.strokeDashOffset,
185
+ })
186
+ );
180
187
  }
181
188
 
182
- setBuffersAndAttributes(
183
- this.gl,
184
- this.programInfo,
185
- this.vertexArrayInfo
189
+ ops.push(() =>
190
+ setBuffersAndAttributes(
191
+ this.gl,
192
+ this.programInfo,
193
+ this.vertexArrayInfo
194
+ )
186
195
  );
196
+
197
+ return ops;
187
198
  }
188
199
 
189
200
  /**
@@ -201,8 +212,7 @@ export default class RuleMark extends Mark {
201
212
  count,
202
213
  offset
203
214
  ),
204
- options,
205
- () => this.rangeMap
215
+ options
206
216
  );
207
217
  }
208
218
  }