@genome-spy/core 0.48.0 → 0.48.2

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 (46) hide show
  1. package/dist/bundle/index.es.js +3613 -3504
  2. package/dist/bundle/index.js +85 -85
  3. package/dist/schema.json +9 -2
  4. package/dist/src/data/collector.d.ts +10 -8
  5. package/dist/src/data/collector.d.ts.map +1 -1
  6. package/dist/src/data/collector.js +131 -33
  7. package/dist/src/data/collector.test.js +55 -1
  8. package/dist/src/data/transforms/identifier.d.ts +1 -1
  9. package/dist/src/data/transforms/identifier.d.ts.map +1 -1
  10. package/dist/src/data/transforms/identifier.js +2 -2
  11. package/dist/src/data/transforms/identifier.test.js +23 -14
  12. package/dist/src/genomeSpy.d.ts.map +1 -1
  13. package/dist/src/genomeSpy.js +14 -15
  14. package/dist/src/gl/dataToVertices.d.ts +1 -1
  15. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  16. package/dist/src/gl/dataToVertices.js +8 -5
  17. package/dist/src/gl/glslScaleGenerator.js +1 -1
  18. package/dist/src/gl/includes/picking.vertex.glsl.js +1 -1
  19. package/dist/src/marks/link.common.glsl.js +1 -1
  20. package/dist/src/marks/link.d.ts.map +1 -1
  21. package/dist/src/marks/link.js +14 -22
  22. package/dist/src/marks/link.vertex.glsl.js +1 -1
  23. package/dist/src/marks/mark.d.ts +9 -0
  24. package/dist/src/marks/mark.d.ts.map +1 -1
  25. package/dist/src/marks/mark.js +19 -0
  26. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  27. package/dist/src/spec/mark.d.ts +1 -1
  28. package/dist/src/utils/animator.d.ts +5 -4
  29. package/dist/src/utils/animator.d.ts.map +1 -1
  30. package/dist/src/utils/animator.js +34 -10
  31. package/dist/src/utils/inertia.d.ts +3 -1
  32. package/dist/src/utils/inertia.d.ts.map +1 -1
  33. package/dist/src/utils/inertia.js +10 -5
  34. package/dist/src/utils/iterateNestedMaps.d.ts +4 -3
  35. package/dist/src/utils/iterateNestedMaps.d.ts.map +1 -1
  36. package/dist/src/utils/iterateNestedMaps.js +3 -2
  37. package/dist/src/utils/radixSort.d.ts +9 -0
  38. package/dist/src/utils/radixSort.d.ts.map +1 -0
  39. package/dist/src/utils/radixSort.js +130 -0
  40. package/dist/src/utils/radixSort.test.js +51 -0
  41. package/dist/src/view/gridView.d.ts +3 -1
  42. package/dist/src/view/gridView.d.ts.map +1 -1
  43. package/dist/src/view/gridView.js +7 -6
  44. package/dist/src/view/zoom.d.ts.map +1 -1
  45. package/dist/src/view/zoom.js +23 -15
  46. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/marks/link.js"],"names":[],"mappings":"AAYA;IA+BQ;;;;;OAKG;IACH,yBAEC;IA4GD;;;;;;MAKC;CAoFR;iBAlPgB,WAAW"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/marks/link.js"],"names":[],"mappings":"AAYA;IA+BQ;;;;;OAKG;IACH,yBAEC;IA2GD;;;;;;MAKC;CAwFR;iBArPgB,WAAW"}
@@ -133,6 +133,11 @@ export default class LinkMark extends Mark {
133
133
  (x) => !!x
134
134
  );
135
135
  this.registerMarkUniformValue("uMaxChordLength", props.maxChordLength);
136
+ this.registerMarkUniformValue(
137
+ "uSegmentBreaks",
138
+ props.segments,
139
+ (x) => x + 1
140
+ );
136
141
  }
137
142
 
138
143
  updateGraphicsData() {
@@ -149,12 +154,6 @@ export default class LinkMark extends Mark {
149
154
 
150
155
  const vertexData = builder.toArrays();
151
156
 
152
- // TODO: Use gl_VertexID to calculate the strip in the vertex shader
153
- vertexData.arrays.strip = {
154
- data: createStrip(this.properties.segments),
155
- numComponents: 2,
156
- };
157
-
158
157
  this.rangeMap.migrateEntries(vertexData.rangeMap);
159
158
 
160
159
  this.arrays = Object.fromEntries(
@@ -196,8 +195,6 @@ export default class LinkMark extends Mark {
196
195
  render(options) {
197
196
  const gl = this.gl;
198
197
 
199
- const arcVertexCount = (this.properties.segments + 1) * 2;
200
-
201
198
  return this._baseInstanceExt
202
199
  ? this.createRenderCallback((offset, count) => {
203
200
  // Using the following extension, which, however, is only a draft and
@@ -207,7 +204,9 @@ export default class LinkMark extends Mark {
207
204
  this._baseInstanceExt.drawArraysInstancedBaseInstanceWEBGL(
208
205
  gl.TRIANGLE_STRIP,
209
206
  0,
210
- arcVertexCount,
207
+ /** @type {Float32Array} */ (
208
+ this.markUniformInfo.uniforms.uSegmentBreaks
209
+ )[0] * 2,
211
210
  count,
212
211
  offset
213
212
  );
@@ -229,7 +228,9 @@ export default class LinkMark extends Mark {
229
228
  attribInfo.divisor
230
229
  ) {
231
230
  attribInfo.offset =
232
- offset * this.arrays[attribute].numComponents * 4; // gl.FLOAT in bytes
231
+ offset *
232
+ this.arrays[attribute].numComponents *
233
+ this.bytesPerElement.get(attribute);
233
234
  }
234
235
  }
235
236
  setBuffersAndAttributes(
@@ -241,20 +242,11 @@ export default class LinkMark extends Mark {
241
242
  gl.drawArraysInstanced(
242
243
  gl.TRIANGLE_STRIP,
243
244
  0,
244
- arcVertexCount,
245
+ /** @type {Float32Array} */ (
246
+ this.markUniformInfo.uniforms.uSegmentBreaks
247
+ )[0] * 2,
245
248
  count
246
249
  );
247
250
  }, options);
248
251
  }
249
252
  }
250
-
251
- function createStrip(/** @type number */ segments) {
252
- let i = 0;
253
- const coords = [];
254
-
255
- for (; i <= segments; i++) {
256
- coords.push(i / segments, 0.5);
257
- coords.push(i / segments, -0.5);
258
- }
259
- return new Float32Array(coords);
260
- }
@@ -1,2 +1,2 @@
1
- const shader = "in vec2 strip;out vec4 vColor;out float vSize;out float vNormalLengthInPixels;const int SHAPE_ARC=0;const int SHAPE_DOME=1;const int SHAPE_DIAGONAL=2;const int SHAPE_LINE=3;const int ORIENT_VERTICAL=0;const int ORIENT_HORIZONTAL=1;float distanceFromLine(vec2 pointOnLine1,vec2 pointOnLine2,vec2 point){vec2 a=point-pointOnLine1;vec2 b=pointOnLine2-pointOnLine1;vec2 proj=dot(a,b)/dot(b,b)*b;return length(a-proj);}bool isInsideViewport(vec2 point,float marginFactor){vec2 margin=uViewportSize*vec2(marginFactor);return point.x>=-margin.x&&point.x<=uViewportSize.x+margin.x&&point.y>=-margin.y&&point.y<=uViewportSize.y+margin.y;}void main(void){float pixelSize=1.0/uDevicePixelRatio;float opacity=getScaled_opacity()*uViewOpacity;vec2 p1,p2,p3,p4;vec2 a=applySampleFacet(vec2(getScaled_x(),getScaled_y()))*uViewportSize;vec2 b=applySampleFacet(vec2(getScaled_x2(),getScaled_y2()))*uViewportSize;if(uShape<=SHAPE_DOME){if(uShape==SHAPE_DOME){vec2 height=vec2(0.0);if(uOrient==ORIENT_VERTICAL){p1=vec2(min(a.x,b.x),b.y);p4=vec2(max(a.x,b.x),b.y);height=vec2(0.0,a.y-b.y);if(uClampApex){if(p4.x>0.0){p1.x=max(p1.x,-p4.x);}if(p1.x<uViewportSize.x){p4.x=min(p4.x,2.0*uViewportSize.x-p1.x);}}}else{p1=vec2(b.x,min(a.y,b.y));p4=vec2(b.x,max(a.y,b.y));height=vec2(a.x-b.x,0.0);if(uClampApex){if(p4.y>0.0){p1.y=max(p1.y,-p4.y);}if(p1.y<uViewportSize.y){p4.y=min(p4.y,2.0*uViewportSize.y-p1.y);}}}vec2 controlOffset=height/0.75;p2=p1+controlOffset;p3=p4+controlOffset;}if(uShape==SHAPE_ARC){p1=a;p4=b;vec2 chordVector=p4-p1;vec2 unitChordVector=normalize(chordVector);vec2 chordNormal=vec2(-unitChordVector.y,unitChordVector.x);float chordLength=length(chordVector);if(chordLength>uMaxChordLength){if(isInsideViewport(p1,2.0)){chordLength=uMaxChordLength;p4=p1+unitChordVector*uMaxChordLength;}else if(isInsideViewport(p4,2.0)){chordLength=uMaxChordLength;p1=p4-unitChordVector*uMaxChordLength;}}float height=max(chordLength/2.0*uArcHeightFactor,uMinArcHeight);vec2 controlOffset=chordNormal*height/0.75;p2=p1+controlOffset;p3=p4+controlOffset;}}else if(uShape==SHAPE_DIAGONAL){if(uOrient==ORIENT_VERTICAL){p1=a;p2=vec2(a.x,(a.y+b.y)/2.0);p3=vec2(b.x,(a.y+b.y)/2.0);p4=b;}else{p1=a;p2=vec2((a.x+b.x)/2.0,a.y);p3=vec2((a.x+b.x)/2.0,b.y);p4=b;}}else if(uShape==SHAPE_LINE){p1=a;p2=(a+b)/2.0;p3=p2;p4=b;}float t=smoothstep(0.0,1.0,strip.x);vec2 C1=p4-3.0*p3+3.0*p2-p1;vec2 C2=3.0*p3-6.0*p2+3.0*p1;vec2 C3=3.0*p2-3.0*p1;vec2 C4=p1;vec2 p;if(t==0.0){p=p1;}else if(t==1.0){p=p4;}else{p=C1*t*t*t+C2*t*t+C3*t+C4;}vec2 tangent=normalize(3.0*C1*t*t+2.0*C2*t+C3);vec2 normal=vec2(-tangent.y,tangent.x);float size=getScaled_size();if(size<pixelSize){opacity*=size/pixelSize;size=pixelSize;}float paddedSize=uPickingEnabled? max(size,uMinPickingSize): size+pixelSize;vNormalLengthInPixels=strip.y*paddedSize;if(uShape==SHAPE_ARC&&uArcFadingDistance[0]>0.0&&uArcFadingDistance[1]>0.0){float d=distanceFromLine(p1,p4,p);float distanceOpacity=smoothstep(uArcFadingDistance[1],uArcFadingDistance[0],d);opacity*=distanceOpacity;if(distanceOpacity<=0.0){vNormalLengthInPixels=0.0;}}p+=normal*vNormalLengthInPixels;gl_Position=pixelsToNdc(p);vec3 color=getScaled_color();vColor=vec4(color*opacity,opacity);vSize=paddedSize;setupPicking();}";
1
+ const shader = "out vec4 vColor;out float vSize;out float vNormalLengthInPixels;const int SHAPE_ARC=0;const int SHAPE_DOME=1;const int SHAPE_DIAGONAL=2;const int SHAPE_LINE=3;const int ORIENT_VERTICAL=0;const int ORIENT_HORIZONTAL=1;float distanceFromLine(vec2 pointOnLine1,vec2 pointOnLine2,vec2 point){vec2 a=point-pointOnLine1;vec2 b=pointOnLine2-pointOnLine1;vec2 proj=dot(a,b)/dot(b,b)*b;return length(a-proj);}bool isInsideViewport(vec2 point,float marginFactor){vec2 margin=uViewportSize*vec2(marginFactor);return point.x>=-margin.x&&point.x<=uViewportSize.x+margin.x&&point.y>=-margin.y&&point.y<=uViewportSize.y+margin.y;}void main(void){float pixelSize=1.0/uDevicePixelRatio;float opacity=getScaled_opacity()*uViewOpacity;vec2 p1,p2,p3,p4;vec2 a=applySampleFacet(vec2(getScaled_x(),getScaled_y()))*uViewportSize;vec2 b=applySampleFacet(vec2(getScaled_x2(),getScaled_y2()))*uViewportSize;if(uShape<=SHAPE_DOME){if(uShape==SHAPE_DOME){vec2 height=vec2(0.0);if(uOrient==ORIENT_VERTICAL){p1=vec2(min(a.x,b.x),b.y);p4=vec2(max(a.x,b.x),b.y);height=vec2(0.0,a.y-b.y);if(uClampApex){if(p4.x>0.0){p1.x=max(p1.x,-p4.x);}if(p1.x<uViewportSize.x){p4.x=min(p4.x,2.0*uViewportSize.x-p1.x);}}}else{p1=vec2(b.x,min(a.y,b.y));p4=vec2(b.x,max(a.y,b.y));height=vec2(a.x-b.x,0.0);if(uClampApex){if(p4.y>0.0){p1.y=max(p1.y,-p4.y);}if(p1.y<uViewportSize.y){p4.y=min(p4.y,2.0*uViewportSize.y-p1.y);}}}vec2 controlOffset=height/0.75;p2=p1+controlOffset;p3=p4+controlOffset;}if(uShape==SHAPE_ARC){p1=a;p4=b;vec2 chordVector=p4-p1;vec2 unitChordVector=normalize(chordVector);vec2 chordNormal=vec2(-unitChordVector.y,unitChordVector.x);float chordLength=length(chordVector);if(chordLength>uMaxChordLength){if(isInsideViewport(p1,2.0)){chordLength=uMaxChordLength;p4=p1+unitChordVector*uMaxChordLength;}else if(isInsideViewport(p4,2.0)){chordLength=uMaxChordLength;p1=p4-unitChordVector*uMaxChordLength;}}float height=max(chordLength/2.0*uArcHeightFactor,uMinArcHeight);vec2 controlOffset=chordNormal*height/0.75;p2=p1+controlOffset;p3=p4+controlOffset;}}else if(uShape==SHAPE_DIAGONAL){if(uOrient==ORIENT_VERTICAL){p1=a;p2=vec2(a.x,(a.y+b.y)/2.0);p3=vec2(b.x,(a.y+b.y)/2.0);p4=b;}else{p1=a;p2=vec2((a.x+b.x)/2.0,a.y);p3=vec2((a.x+b.x)/2.0,b.y);p4=b;}}else if(uShape==SHAPE_LINE){p1=a;p2=(a+b)/2.0;p3=p2;p4=b;}vec2 strip=vec2(float(gl_VertexID/2)/float(uSegmentBreaks),float(gl_VertexID % 2)-0.5);float t=smoothstep(0.0,1.0,strip.x);vec2 C1=p4-3.0*p3+3.0*p2-p1;vec2 C2=3.0*p3-6.0*p2+3.0*p1;vec2 C3=3.0*p2-3.0*p1;vec2 C4=p1;vec2 p;if(t==0.0){p=p1;}else if(t==1.0){p=p4;}else{p=C1*t*t*t+C2*t*t+C3*t+C4;}vec2 tangent=normalize(3.0*C1*t*t+2.0*C2*t+C3);vec2 normal=vec2(-tangent.y,tangent.x);float size=getScaled_size();if(size<pixelSize){opacity*=size/pixelSize;size=pixelSize;}float paddedSize=uPickingEnabled? max(size,uMinPickingSize): size+pixelSize;vNormalLengthInPixels=strip.y*paddedSize;if(uShape==SHAPE_ARC&&uArcFadingDistance[0]>0.0&&uArcFadingDistance[1]>0.0){float d=distanceFromLine(p1,p4,p);float distanceOpacity=smoothstep(uArcFadingDistance[1],uArcFadingDistance[0],d);opacity*=distanceOpacity;if(distanceOpacity<=0.0){vNormalLengthInPixels=0.0;}}p+=normal*vNormalLengthInPixels;gl_Position=pixelsToNdc(p);vec3 color=getScaled_color();vColor=vec4(color*opacity,opacity);vSize=paddedSize;setupPicking();}";
2
2
  export default shader;
@@ -29,6 +29,15 @@ export default class Mark {
29
29
  protected bufferInfo: import("twgl.js").BufferInfo & {
30
30
  allocatedVertices?: number;
31
31
  };
32
+ /**
33
+ * TWGL's BufferInfo doesn't contain the number of bytes per element,
34
+ * so we need to keep track of it separately. This is mainly used by
35
+ * the instancing hack in the link mark.
36
+ *
37
+ * @type {Map<string, number>}
38
+ * @protected
39
+ */
40
+ protected bytesPerElement: Map<string, number>;
32
41
  /**
33
42
  * @type {import("twgl.js").ProgramInfo}
34
43
  * @protected
@@ -1 +1 @@
1
- {"version":3,"file":"mark.d.ts","sourceRoot":"","sources":["../../../src/marks/mark.js"],"names":[],"mappings":";AA4CA,0DAA2D;AAC3D,0DAA2D;AAE3D;;;;;;;;;;;;GAYG;AACH;IAgBI;;OAEG;IACH,sBAFW,OAAO,qBAAqB,EAAE,OAAO,EA6F/C;IA1FG,gDAAwB;IAExB,oEAAoE;IACpE,UADW,OAAO,MAAM,EAAE,OAAO,qBAAqB,EAAE,OAAO,CAAC,CACvC;IAIzB;;;OAGG;IACH,sBAHU,OAAO,SAAS,EAAE,UAAU,GAAG;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAG5C;IAE3B;;;OAGG;IACH,uBAHU,OAAO,SAAS,EAAE,WAAW,CAGX;IAE5B;;;OAGG;IACH,2BAHU,OAAO,SAAS,EAAE,eAAe,CAGX;IAEhC;;;OAGG;IACH,2BAHU,OAAO,SAAS,EAAE,gBAAgB,CAGZ;IAEhC;;;;;OAKG;IACH,2BAHU,OAAO,SAAS,EAAE,gBAAgB,CAGZ;IAEhC;;;;;OAKG;IACH,uCAA+B;IAE/B,kFAAkF;IAClF,UADW,SAAS,GAAG,CAAC,CACM;IAG9B,wBAAwB;IACxB,uDAqBC;IAED;;;;;;;;OAQG;IACH,kEAKC;IAGL,sBAEC;IAED;;;;;;;OAOG;IACH,iBAFa,MAAM,EAAE,CAKpB;IAED;;OAEG;IACH,+DAWC;IAED;;OAEG;IACH,oEAcC;IAED;;;;;OAKG;IACH,oHAEC;IAED;;;;;;OAMG;IACH,yGAkCC;IAED;;;;OAIG;IACH,8DAiDC;IAED,wDAEC;IAED,8CAEC;IAED,uBAEC;IAED;;;OAGG;IACH,2BAEC;IAED;;OAEG;IACH,oCAEC;IAED;;OAEG;IACH,2BAEC;IAED,sEAeC;IAED;;;;;;OAMG;IAEH,6CANW,MAAM,kBACN,MAAM,iBACN,MAAM,EAAE,QA8NlB;IALG;;;;;;MAIC;IAGL;;;;;;OAMG;IACH,uCA6CC;IAED;;;;;;OAMG;IACH,+CAHW,MAAM,UACK,GAAG,KAAE,IAAI,CAe9B;IAED;;;;;;;;;;OAUG;IACH,mDAJW,MAAM,sFAEsB,GAAG,QA6BzC;IAED;;OAEG;IACH,2BAwBC;IAED;;;OAGG;IACH,6BAFW,GAAG,QAgCb;IAED,yBAAyB;IACzB,uDAEC;IAED,yBAAyB;IACzB,iCAEC;IAED,gCAEC;IAED,+BAEC;IAED,yCAEC;IAED;;;;;OAKG;IACH,gCAcC;IAED;;OAEG;IACH,4CAOC;IAED;;;;;;;;OAQG;IAEH,uBAJW,OAAO,uBAAuB,EAAE,sBAAsB,GACpD,CAAC,MAAM,IAAI,CAAC,EAAE,CAsF1B;IAED;;;;;;OAMG;IACH,qCAJW,oBAAoB,GAClB,OAAO,CAqCnB;IAED;;;;;;;OAOG;IACH,gBAJW,oBAAoB,SACP,IAAI,CAM3B;IAED;;;OAGG;IACH,2BAHW,YAAY,WACZ,OAAO,WAAW,EAAE,oBAAoB,cAiElD;IAED;;;;;;OAMG;IACH,oBAJW,OAAO,6BAA6B,EAAE,OAAO,aAC7C,OAAO,6BAA6B,EAAE,OAAO,GAC3C,OAAO,CAuHnB;IAED;;;;;;;;;OASG;IACH,qBAJW,MAAM,KACN,OAAO,oBAAoB,EAAE,MAAM,GACjC,GAAG,CAIf;;CACJ;+BAvoCY,OAAO,uBAAuB,EAAE,gBAAgB;;;;;;wBAEnD,OAAO;;mCAEJ,gBAAgB,GAAG,qBAAqB;oCAG1C,MAAM,SACN,MAAM;AAioCjB;;;GAGG;AACH;IACI,cAEC;IAkBD;;;OAGG;IACH,2BAFW,IAAI,CAAC,EAAE,OAAO,yBAAyB,EAAE,UAAU,CAAC,QAiB9D;CACJ;0BA/rCyB,WAAW"}
1
+ {"version":3,"file":"mark.d.ts","sourceRoot":"","sources":["../../../src/marks/mark.js"],"names":[],"mappings":"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"}
@@ -91,6 +91,16 @@ export default class Mark {
91
91
  */
92
92
  this.bufferInfo = undefined;
93
93
 
94
+ /**
95
+ * TWGL's BufferInfo doesn't contain the number of bytes per element,
96
+ * so we need to keep track of it separately. This is mainly used by
97
+ * the instancing hack in the link mark.
98
+ *
99
+ * @type {Map<string, number>}
100
+ * @protected
101
+ */
102
+ this.bytesPerElement = new Map();
103
+
94
104
  /**
95
105
  * @type {import("twgl.js").ProgramInfo}
96
106
  * @protected
@@ -788,6 +798,15 @@ export default class Mark {
788
798
  { numElements: vertexData.vertexCount }
789
799
  );
790
800
  this.bufferInfo.allocatedVertices = vertexData.allocatedVertices;
801
+
802
+ for (const [attribute, attributeData] of Object.entries(
803
+ vertexData.arrays
804
+ )) {
805
+ this.bytesPerElement.set(
806
+ attribute,
807
+ attributeData.data.BYTES_PER_ELEMENT
808
+ );
809
+ }
791
810
  }
792
811
  }
793
812
 
@@ -1,2 +1,2 @@
1
- const shader = "out lowp vec4 vFillColor;out lowp vec4 vStrokeColor;out float vHalfStrokeWidth;out vec4 vCornerRadii;\n#if defined(ROUNDED_CORNERS) || defined(STROKED)\nout vec2 vPosInPixels;\n#endif\nout vec2 vHalfSizeInPixels;/***Clamps the minimumSize and returns an opacity that reflects the amount of clamping.*/float clampMinSize(inout float pos,float frac,float size,float minSize){if(minSize>0.0&&abs(size)<minSize){pos+=(frac-0.5)*(minSize*sign(size)-size);return abs(size)/minSize;}return 1.0;}void sort(inout float a,inout float b){if(a>b){float tmp=b;b=a;a=tmp;}}/***The vertex position wrt the rectangle specified by(x,x2,y,y2).*[0,0]=[x,y],[1,1]=[x2,y2].*The x or y component may contain fractional values if the rectangle*have been tessellated.*/vec2 getVertexPos(){int index=gl_VertexID % 6;return vec2(index==0||index==1||index==3 ? 0.0 : 1.0,index==0||index==1||index==2 ? 0.0 : 1.0);}void main(void){vec2 frac=getVertexPos();vec2 normalizedMinSize=vec2(uMinWidth,uMinHeight)/uViewportSize;vec4 cornerRadii=vec4(uCornerRadiusTopRight,uCornerRadiusBottomRight,uCornerRadiusBottomLeft,uCornerRadiusTopLeft);float x=getScaled_x();float x2=getScaled_x2();float y=getScaled_y();float y2=getScaled_y2();sort(x,x2);sort(y,y2);float clampMargin=1.0;vec2 pos1=vec2(clamp(x,0.0-clampMargin,1.0+clampMargin),y);vec2 pos2=vec2(clamp(x2,0.0-clampMargin,1.0+clampMargin),y2);vec2 size=pos2-pos1;if(size.x<=0.0||size.y<=0.0){gl_Position=vec4(0.0,0.0,0.0,1.0);return;}vec2 pos=pos1+frac*size;size.y*=getSampleFacetHeight(pos);float opaFactor=uViewOpacity*max(uMinOpacity,clampMinSize(pos.x,frac.x,size.x,normalizedMinSize.x)*clampMinSize(pos.y,frac.y,size.y,normalizedMinSize.y));pos=applySampleFacet(pos);\n#if defined(ROUNDED_CORNERS) || defined(STROKED)\nfloat aaPadding=1.0/uDevicePixelRatio;float strokeWidth=getScaled_strokeWidth();float strokeOpacity=getScaled_strokeOpacity()*opaFactor;vec2 centeredFrac=frac-0.5;vec2 expand=centeredFrac*(strokeWidth+aaPadding)/uViewportSize;pos+=expand;vec2 sizeInPixels=size*uViewportSize;vPosInPixels=(centeredFrac+expand/size)*sizeInPixels;vHalfSizeInPixels=sizeInPixels/2.0;vCornerRadii=min(cornerRadii,min(vHalfSizeInPixels.x,vHalfSizeInPixels.y));vHalfStrokeWidth=strokeWidth/2.0;vStrokeColor=vec4(getScaled_stroke()*strokeOpacity,strokeOpacity);\n#endif\ngl_Position=unitToNdc(pos);float fillOpacity=getScaled_fillOpacity()*opaFactor;vFillColor=vec4(getScaled_fill()*fillOpacity,fillOpacity);setupPicking();}";
1
+ const shader = "out lowp vec4 vFillColor;out lowp vec4 vStrokeColor;out float vHalfStrokeWidth;out vec4 vCornerRadii;\n#if defined(ROUNDED_CORNERS) || defined(STROKED)\nout vec2 vPosInPixels;\n#endif\nout vec2 vHalfSizeInPixels;/***Clamps the minimumSize and returns an opacity that reflects the amount of clamping.*/float clampMinSize(inout float pos,float frac,float size,float minSize){if(minSize>0.0&&abs(size)<minSize){pos+=(frac-0.5)*(minSize*sign(size)-size);return abs(size)/minSize;}return 1.0;}void sort(inout float a,inout float b){if(a>b){float tmp=b;b=a;a=tmp;}}/***The vertex position wrt the rectangle specified by(x,x2,y,y2).*[0,0]=[x,y],[1,1]=[x2,y2].*The x or y component may contain fractional values if the rectangle*have been tessellated.*/vec2 getVertexPos(){int index=gl_VertexID % 6;return vec2(index==0||index==1||index==3 ? 0.0 : 1.0,index==0||index==1||index==2 ? 0.0 : 1.0);}void main(void){vec2 frac=getVertexPos();vec2 normalizedMinSize=vec2(uMinWidth,uMinHeight)/uViewportSize;vec4 cornerRadii=vec4(uCornerRadiusTopRight,uCornerRadiusBottomRight,uCornerRadiusTopLeft,uCornerRadiusBottomLeft);float x=getScaled_x();float x2=getScaled_x2();float y=getScaled_y();float y2=getScaled_y2();sort(x,x2);sort(y,y2);float clampMargin=1.0;vec2 pos1=vec2(clamp(x,0.0-clampMargin,1.0+clampMargin),y);vec2 pos2=vec2(clamp(x2,0.0-clampMargin,1.0+clampMargin),y2);vec2 size=pos2-pos1;if(size.x<=0.0||size.y<=0.0){gl_Position=vec4(0.0,0.0,0.0,1.0);return;}vec2 pos=pos1+frac*size;size.y*=getSampleFacetHeight(pos);float opaFactor=uViewOpacity*max(uMinOpacity,clampMinSize(pos.x,frac.x,size.x,normalizedMinSize.x)*clampMinSize(pos.y,frac.y,size.y,normalizedMinSize.y));pos=applySampleFacet(pos);\n#if defined(ROUNDED_CORNERS) || defined(STROKED)\nfloat aaPadding=1.0/uDevicePixelRatio;float strokeWidth=getScaled_strokeWidth();float strokeOpacity=getScaled_strokeOpacity()*opaFactor;vec2 centeredFrac=frac-0.5;vec2 expand=centeredFrac*(strokeWidth+aaPadding)/uViewportSize;pos+=expand;vec2 sizeInPixels=size*uViewportSize;vPosInPixels=(centeredFrac+expand/size)*sizeInPixels;vHalfSizeInPixels=sizeInPixels/2.0;vCornerRadii=min(cornerRadii,min(vHalfSizeInPixels.x,vHalfSizeInPixels.y));vHalfStrokeWidth=strokeWidth/2.0;vStrokeColor=vec4(getScaled_stroke()*strokeOpacity,strokeOpacity);\n#endif\ngl_Position=unitToNdc(pos);float fillOpacity=getScaled_fillOpacity()*opaFactor;vFillColor=vec4(getScaled_fill()*fillOpacity,fillOpacity);setupPicking();}";
2
2
  export default shader;
@@ -349,7 +349,7 @@ export interface LinkProps extends SecondaryPositionProps {
349
349
  *
350
350
  * **Default value:* `101`
351
351
  */
352
- segments?: number;
352
+ segments?: number | ExprRef;
353
353
 
354
354
  /**
355
355
  * Scaling factor for the `"arc`" shape's height. The default value `1.0` produces roughly circular arcs.
@@ -6,13 +6,14 @@
6
6
  * Read more at: https://www.gamedeveloper.com/programming/improved-lerp-smoothing-
7
7
  *
8
8
  * @param {import("../utils/animator.js").default} animator
9
- * @param {(value: number) => void} callback Function to be called with the interpolated value
9
+ * @param {(value: T) => void} callback Function to be called with the interpolated value
10
10
  * @param {number} halfLife Time until half of the value is reached, in milliseconds
11
11
  * @param {number} stopAt Stop animation when the value is within this distance from the target
12
- * @param {number} [initialValue] Initial value
13
- * @returns {((target: number) => void) & { stop: () => void}} Function that activates the transition with a new target value
12
+ * @param {T} initialValue Initial value
13
+ * @returns {((target: T) => void) & { stop: () => void}} Function that activates the transition with a new target value
14
+ * @template {Record<string, number>} T
14
15
  */
15
- export function makeLerpSmoother(animator: import("../utils/animator.js").default, callback: (value: number) => void, halfLife: number, stopAt: number, initialValue?: number): ((target: number) => void) & {
16
+ export function makeLerpSmoother<T extends Record<string, number>>(animator: import("../utils/animator.js").default, callback: (value: T) => void, halfLife: number, stopAt: number, initialValue: T): ((target: T) => void) & {
16
17
  stop: () => void;
17
18
  };
18
19
  export default class Animator {
@@ -1 +1 @@
1
- {"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../../src/utils/animator.js"],"names":[],"mappings":"AAoFA;;;;;;;;;;;;;GAaG;AACH,2CAPW,OAAO,sBAAsB,EAAE,OAAO,oBAC9B,MAAM,KAAK,IAAI,YACvB,MAAM,UACN,MAAM,iBACN,MAAM,aACM,MAAM,KAAK,IAAI;UAAY,MAAM,IAAI;EA0D3D;AAxJD;IACI;;;OAGG;IACH,mCAFoB,MAAM,KAAE,IAAI,EAS/B;IANG,wBAHgB,MAAM,KAAE,IAAI,CAGS;IACrC,0BAA6B;IAC7B,eAAkB;IAElB,wCAAwC;IACxC,aADW,QAAU,MAAM,KAAE,IAAI,CAAC,EAAE,CACf;IAGzB;;;;;;;;;OASG;IACH,mCAFoB,MAAM,KAAE,IAAI,QAM/B;IAED;;OAEG;IACH,kCAFoB,MAAM,KAAE,IAAI,QAO/B;IAED;;;;OAIG;IACH,sBAoBC;IAED;;;;;OAKG;IACH,oBAFW,OAAO,iBAAiB,EAAE,iBAAiB,gBAQrD;CACJ"}
1
+ {"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../../src/utils/animator.js"],"names":[],"mappings":"AAoFA;;;;;;;;;;;;;;GAcG;AACH,6EARW,OAAO,sBAAsB,EAAE,OAAO,0BACxB,IAAI,YAClB,MAAM,UACN,MAAM,oCAEY,IAAI;UAAY,MAAM,IAAI;EAkFtD;AAhLD;IACI;;;OAGG;IACH,mCAFoB,MAAM,KAAE,IAAI,EAS/B;IANG,wBAHgB,MAAM,KAAE,IAAI,CAGS;IACrC,0BAA6B;IAC7B,eAAkB;IAElB,wCAAwC;IACxC,aADW,QAAU,MAAM,KAAE,IAAI,CAAC,EAAE,CACf;IAGzB;;;;;;;;;OASG;IACH,mCAFoB,MAAM,KAAE,IAAI,QAM/B;IAED;;OAEG;IACH,kCAFoB,MAAM,KAAE,IAAI,QAO/B;IAED;;;;OAIG;IACH,sBAoBC;IAED;;;;;OAKG;IACH,oBAFW,OAAO,iBAAiB,EAAE,iBAAiB,gBAQrD;CACJ"}
@@ -90,25 +90,39 @@ export default class Animator {
90
90
  * Read more at: https://www.gamedeveloper.com/programming/improved-lerp-smoothing-
91
91
  *
92
92
  * @param {import("../utils/animator.js").default} animator
93
- * @param {(value: number) => void} callback Function to be called with the interpolated value
93
+ * @param {(value: T) => void} callback Function to be called with the interpolated value
94
94
  * @param {number} halfLife Time until half of the value is reached, in milliseconds
95
95
  * @param {number} stopAt Stop animation when the value is within this distance from the target
96
- * @param {number} [initialValue] Initial value
97
- * @returns {((target: number) => void) & { stop: () => void}} Function that activates the transition with a new target value
96
+ * @param {T} initialValue Initial value
97
+ * @returns {((target: T) => void) & { stop: () => void}} Function that activates the transition with a new target value
98
+ * @template {Record<string, number>} T
98
99
  */
99
100
  export function makeLerpSmoother(
100
101
  animator,
101
102
  callback,
102
103
  halfLife,
103
104
  stopAt,
104
- initialValue = 0
105
+ initialValue
105
106
  ) {
106
107
  let lastTimeStamp = 0;
107
108
  let settled = true;
108
109
 
109
- let current = initialValue;
110
+ let current = structuredClone(initialValue);
110
111
  let target = current;
111
112
 
113
+ /**
114
+ * Smoother for a scalar.
115
+ * Based on: https://twitter.com/FreyaHolmer/status/1757836988495847568
116
+ *
117
+ * @param {number} current
118
+ * @param {number} target
119
+ * @param {number} tD
120
+ * @param {number} halfLife
121
+ */
122
+ function lerpSmooth(current, target, tD, halfLife) {
123
+ return target + (current - target) * Math.pow(2, -tD / halfLife);
124
+ }
125
+
112
126
  /**
113
127
  * @param {number} [timestamp]
114
128
  */
@@ -120,23 +134,33 @@ export function makeLerpSmoother(
120
134
  const tD = timestamp - lastTimeStamp;
121
135
  lastTimeStamp = timestamp;
122
136
 
123
- // Lerp smoothing: https://twitter.com/FreyaHolmer/status/1757836988495847568
124
- current = target + (current - target) * Math.pow(2, -tD / halfLife);
137
+ for (const key of /** @type {(keyof T)[]} */ (Object.keys(target))) {
138
+ current[key] = /** @type {T[keyof T]}*/ (
139
+ lerpSmooth(current[key], target[key], tD, halfLife)
140
+ );
141
+ }
125
142
 
126
143
  callback(current);
127
144
 
128
- if (Math.abs(target - current) < stopAt) {
145
+ let maxDiff = -Infinity;
146
+ for (const key of Object.keys(target)) {
147
+ maxDiff = Math.max(maxDiff, Math.abs(target[key] - current[key]));
148
+ }
149
+
150
+ if (maxDiff < stopAt) {
129
151
  current = target;
130
152
  callback(current);
131
153
  settled = true;
132
- animator.requestRender();
154
+ if (maxDiff != 0) {
155
+ animator.requestRender();
156
+ }
133
157
  } else {
134
158
  animator.requestTransition((t) => smoothUpdate(t));
135
159
  }
136
160
  }
137
161
 
138
162
  /**
139
- * @param {number} value
163
+ * @param {T} value
140
164
  */
141
165
  function setTarget(value) {
142
166
  target = value;
@@ -19,7 +19,9 @@ export default class Inertia {
19
19
  callback: (arg0: number) => void;
20
20
  targetValue: number;
21
21
  lastValue: number;
22
- smoother: ((target: number) => void) & {
22
+ smoother: ((target: {
23
+ x: number;
24
+ }) => void) & {
23
25
  stop: () => void;
24
26
  };
25
27
  cancel(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"inertia.d.ts","sourceRoot":"","sources":["../../../src/utils/inertia.js"],"names":[],"mappings":"AAmEA;;;GAGG;AACH,8EAiBC;AApFD;;GAEG;AACH;IACI;;;OAGG;IACH,sBAHW,OAAO,eAAe,EAAE,OAAO,aAC/B,OAAO,EAyBjB;IAtBG,0CAAwB;IACxB,kBAA0B;IAG1B,oBAAsB;IAEtB,oCAAoC;IACpC,iBADoB,MAAM,KAAE,IAAI,CACZ;IAEpB,oBAAoB;IACpB,kBAAkB;IAElB;;MASC;IAGL,eAIC;IAED;;;;OAIG;IACH,mBAHW,MAAM,mBACG,MAAM,KAAE,IAAI,QAkB/B;CACJ"}
1
+ {"version":3,"file":"inertia.d.ts","sourceRoot":"","sources":["../../../src/utils/inertia.js"],"names":[],"mappings":"AAwEA;;;GAGG;AACH,8EAiBC;AAzFD;;GAEG;AACH;IACI;;;OAGG;IACH,sBAHW,OAAO,eAAe,EAAE,OAAO,aAC/B,OAAO,EA0BjB;IAvBG,0CAAwB;IACxB,kBAA0B;IAG1B,oBAAsB;IAEtB,oCAAoC;IACpC,iBADoB,MAAM,KAAE,IAAI,CACZ;IAEpB,oBAAoB;IACpB,kBAAkB;IAElB;;;;MAUC;IAGL,eAQC;IAED;;;;OAIG;IACH,mBAHW,MAAM,mBACG,MAAM,KAAE,IAAI,QAkB/B;CACJ"}
@@ -26,19 +26,24 @@ export default class Inertia {
26
26
  this.smoother = makeLerpSmoother(
27
27
  animator,
28
28
  (value) => {
29
- const delta = value - this.lastValue;
30
- this.lastValue = value;
29
+ const delta = value.x - this.lastValue;
30
+ this.lastValue = value.x;
31
31
  this.callback?.(delta);
32
32
  },
33
33
  40,
34
- 0.1
34
+ 0.1,
35
+ { x: 0 }
35
36
  );
36
37
  }
37
38
 
38
39
  cancel() {
40
+ if (this.lastValue === this.targetValue) {
41
+ return;
42
+ }
43
+
39
44
  // decelelerate rapidly
40
45
  this.targetValue = lerp([this.lastValue, this.targetValue], 0.3);
41
- this.smoother(this.targetValue);
46
+ this.smoother({ x: this.targetValue });
42
47
  }
43
48
 
44
49
  /**
@@ -61,7 +66,7 @@ export default class Inertia {
61
66
  );
62
67
  this.targetValue = this.lastValue + delta;
63
68
 
64
- this.smoother(this.targetValue);
69
+ this.smoother({ x: this.targetValue });
65
70
  }
66
71
  }
67
72
 
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Yields arrays that contain the compound key and the grouped data items.
5
5
  *
6
- * @param {Map<any, any>} map The root
6
+ * @param {Map<any, T>} map The root
7
7
  * @param {any[]} [path] The path so far.
8
- * @returns {Generator<[any[], any[]]>}
8
+ * @returns {Generator<[any[], T]>}
9
+ * @template T
9
10
  */
10
- export default function iterateNestedMaps(map: Map<any, any>, path?: any[]): Generator<[any[], any[]]>;
11
+ export default function iterateNestedMaps<T>(map: Map<any, T>, path?: any[]): Generator<[any[], T], any, any>;
11
12
  //# sourceMappingURL=iterateNestedMaps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"iterateNestedMaps.d.ts","sourceRoot":"","sources":["../../../src/utils/iterateNestedMaps.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,+CAJW,IAAI,GAAG,EAAE,GAAG,CAAC,SACb,GAAG,EAAE,GACH,UAAU,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAarC"}
1
+ {"version":3,"file":"iterateNestedMaps.d.ts","sourceRoot":"","sources":["../../../src/utils/iterateNestedMaps.js"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,sEAJW,GAAG,EAAE,mCAef"}
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Yields arrays that contain the compound key and the grouped data items.
5
5
  *
6
- * @param {Map<any, any>} map The root
6
+ * @param {Map<any, T>} map The root
7
7
  * @param {any[]} [path] The path so far.
8
- * @returns {Generator<[any[], any[]]>}
8
+ * @returns {Generator<[any[], T]>}
9
+ * @template T
9
10
  */
10
11
  export default function* iterateNestedMaps(map, path = []) {
11
12
  for (const [key, value] of map.entries()) {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @param {number[]} arr An array of unsigned integers
3
+ */
4
+ export default function radixSort(arr: number[]): number[];
5
+ /**
6
+ * @param {number[]} arr An array of unsigned integers
7
+ */
8
+ export function radixSortIntoLookupArray(arr: number[]): number[];
9
+ //# sourceMappingURL=radixSort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radixSort.d.ts","sourceRoot":"","sources":["../../../src/utils/radixSort.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,uCAFW,MAAM,EAAE,YAyDlB;AAaD;;GAEG;AACH,8CAFW,MAAM,EAAE,YAoDlB"}
@@ -0,0 +1,130 @@
1
+ const MAX_INTEGER = 2147483647;
2
+ const MAX_INTEGER_DIGIT = getDigits([MAX_INTEGER]);
3
+
4
+ // TODO: Optimize more! Some ideas: https://travisdowns.github.io/blog/2019/05/22/sorting.html
5
+
6
+ /**
7
+ * @param {number[]} arr An array of unsigned integers
8
+ */
9
+ export default function radixSort(arr) {
10
+ const maxDigits = getDigits(arr);
11
+
12
+ let buffer = new Array(arr.length);
13
+ let bufferPtr = buffer;
14
+ const counts = new Array(16);
15
+
16
+ for (let digitIndex = 0; digitIndex < maxDigits; digitIndex++) {
17
+ counts.fill(0);
18
+
19
+ const shift = digitIndex * 4;
20
+ const pow = Math.pow(16, digitIndex);
21
+
22
+ /**
23
+ * @param {*} i number
24
+ */
25
+ // eslint-disable-next-line no-loop-func
26
+ const getDigit = (/** @type {number} */ i) => {
27
+ const value = arr[i];
28
+
29
+ // Need hacks for large numbers because JS bitwise operators only work
30
+ // with 32-bit integers.
31
+ // TODO: This could be implemented in WASM for better performance as
32
+ // it would be able to use 64-bit integers.
33
+ if (digitIndex >= MAX_INTEGER_DIGIT) {
34
+ if (value > MAX_INTEGER) {
35
+ return Math.floor(value / pow) % 16;
36
+ } else {
37
+ return 0;
38
+ }
39
+ } else {
40
+ return (value >> shift) & 0xf;
41
+ }
42
+ };
43
+
44
+ // Count occurrences of each digit
45
+ for (let i = 0, n = arr.length; i < n; i++) {
46
+ counts[getDigit(i)]++;
47
+ }
48
+
49
+ // Prefix sum to get starting indexes
50
+ for (let i = 1; i < 16; i++) {
51
+ counts[i] += counts[i - 1];
52
+ }
53
+
54
+ // Sort based on current digit
55
+ for (let i = arr.length - 1; i >= 0; i--) {
56
+ bufferPtr[--counts[getDigit(i)]] = arr[i];
57
+ }
58
+
59
+ // Swap buffer and arr for next iteration
60
+ [arr, bufferPtr] = [bufferPtr, arr];
61
+ }
62
+
63
+ return arr;
64
+ }
65
+
66
+ /**
67
+ * @param {number[]} arr An array of unsigned integers
68
+ */
69
+ function getDigits(arr) {
70
+ let max = 0;
71
+ for (let i = 0, n = arr.length; i < n; i++) {
72
+ max = Math.max(max, arr[i]);
73
+ }
74
+ return Math.floor(Math.log2(max) / 4) + 1;
75
+ }
76
+
77
+ /**
78
+ * @param {number[]} arr An array of unsigned integers
79
+ */
80
+ export function radixSortIntoLookupArray(arr) {
81
+ const maxDigits = getDigits(arr);
82
+ let indexes = Array.from({ length: arr.length }, (_, i) => i);
83
+ let buffer = new Array(arr.length);
84
+ const counts = new Array(16);
85
+
86
+ for (let digitIndex = 0; digitIndex < maxDigits; digitIndex++) {
87
+ counts.fill(0);
88
+
89
+ const shift = digitIndex * 4;
90
+ const pow = Math.pow(16, digitIndex);
91
+
92
+ /**
93
+ * @param {*} i number
94
+ */
95
+ // eslint-disable-next-line no-loop-func
96
+ const getDigit = (i) => {
97
+ const value = arr[indexes[i]]; // Use index to access array value
98
+
99
+ if (digitIndex >= MAX_INTEGER_DIGIT) {
100
+ if (value > MAX_INTEGER) {
101
+ return Math.floor(value / pow) % 16;
102
+ } else {
103
+ return 0;
104
+ }
105
+ } else {
106
+ return (value >> shift) & 0xf;
107
+ }
108
+ };
109
+
110
+ // Count occurrences of each digit
111
+ for (let i = 0; i < arr.length; i++) {
112
+ counts[getDigit(i)]++;
113
+ }
114
+
115
+ // Prefix sum to get starting indexes
116
+ for (let i = 1; i < 16; i++) {
117
+ counts[i] += counts[i - 1];
118
+ }
119
+
120
+ // Sort indexes based on current digit
121
+ for (let i = arr.length - 1; i >= 0; i--) {
122
+ buffer[--counts[getDigit(i)]] = indexes[i];
123
+ }
124
+
125
+ // Swap buffer and indexes for next iteration
126
+ [indexes, buffer] = [buffer, indexes];
127
+ }
128
+
129
+ return indexes; // Return the sorted index array
130
+ }
@@ -0,0 +1,51 @@
1
+ import { expect, test } from "vitest";
2
+ import radixSort, { radixSortIntoLookupArray } from "./radixSort.js";
3
+
4
+ /**
5
+ * Checks that numbers in an array are in ascending order.
6
+
7
+ * @param {number[]} arr An array of unsigned integers
8
+ */
9
+ function isSorted(arr) {
10
+ for (let i = 1; i < arr.length; i++) {
11
+ if (arr[i - 1] > arr[i]) {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ return true;
17
+ }
18
+
19
+ /**
20
+ * Generates a random array of unsigned integers.
21
+ *
22
+ * @param {number} length
23
+ */
24
+ function generateArray(length) {
25
+ const arr = new Array(length);
26
+
27
+ for (let i = 0; i < length; i++) {
28
+ arr[i] = Math.floor(Math.random() * 10_000_000_000);
29
+ }
30
+
31
+ return arr;
32
+ }
33
+
34
+ test("Radix Sort correctly sorts numbers", () => {
35
+ expect(isSorted(radixSort([1, 2, 3]))).toBeTruthy();
36
+ expect(isSorted(radixSort([3, 2, 1]))).toBeTruthy();
37
+ expect(isSorted(radixSort([123, 1234567, 12, 1, 1234]))).toBeTruthy();
38
+ expect(isSorted(radixSort(generateArray(1_000_000)))).toBeTruthy();
39
+ });
40
+
41
+ test("Lookup", () => {
42
+ expect(radixSortIntoLookupArray([1, 2, 3])).toEqual([0, 1, 2]);
43
+ expect(radixSortIntoLookupArray([3, 2, 1])).toEqual([2, 1, 0]);
44
+ expect(radixSortIntoLookupArray([10000, 100, 1000, 10, 1])).toEqual([
45
+ 4, 3, 1, 2, 0,
46
+ ]);
47
+
48
+ const arr = generateArray(1_000_000);
49
+ const lookup = radixSortIntoLookupArray(arr);
50
+ expect(isSorted(arr.map((_, i) => arr[lookup[i]]))).toBeTruthy();
51
+ });
@@ -122,7 +122,9 @@ declare class Scrollbar extends UnitView {
122
122
  scrollbarSize: number;
123
123
  scrollbarPadding: number;
124
124
  };
125
- interpolateViewportOffset: ((target: number) => void) & {
125
+ interpolateViewportOffset: ((target: {
126
+ x: number;
127
+ }) => void) & {
126
128
  stop: () => void;
127
129
  };
128
130
  get scrollOffset(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"gridView.d.ts","sourceRoot":"","sources":["../../../src/view/gridView.js"],"names":[],"mappings":"AAswBA;;;GAGG;AACH,iDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CAwB9C;AAED;;;GAGG;AACH,uDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CA6C9C;AA2BD;;GAEG;AACH,8EAUC;AAED;;;;;GAKG;AACH,4CAJW,OAAO,uBAAuB,EAAE,OAAO,UACvC,OAAO,iBAAiB,EAAE,UAAU,YACpC,QAAQ,aAmBlB;AA53BD;;;;;;;;;;;;;;;GAeG;AACH;IA6BI;;;;;;;;;OASG;IACH,kBARW,OAAO,iBAAiB,EAAE,aAAa,WACvC,OAAO,yBAAyB,EAAE,OAAO,gBACzC,aAAa,iDAEb,MAAM,WACN,MAAM,YACN,OAAO,WAAW,EAAE,WAAW,EAoBzC;IARG,8CAAgB;IAOhB,uBAA0B;IAG9B;;OAEG;IACH,qDAIC;IAeD;;OAEG;IACH,wDAKC;IAqBD;;OAEG;IACH,8CAEC;IAED,yBAEC;IAED;;OAEG;IACH,wCAmCC;IA2OD;;;;OAIG;IAEH,gBALW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,uBAAuB,EAAE,gBAAgB,QA6O1D;;CAoGJ;AAgJD;IACI;;;;OAIG;IACH,6DAHW,aAAa,UACb,MAAM,EAoFhB;IAjFG,4BAAgC;IAChC,kCAAgB;IAChB,eAAoB;IAEpB,uBAAuB;IACvB,YADW,QAAQ,CACQ;IAE3B,uBAAuB;IACvB,kBADW,QAAQ,CACc;IAEjC,mFAAmF;IACnF,MADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAC5D;IAEd,4FAA4F;IAC5F,WADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAC3D;IAEnB,0DAA0D;IAC1D,kEAAoB;IAEpB,uBAAuB;IACvB,OADW,QAAQ,CACG;IAEtB,wBAAwB;IACxB,QADW,SAAS,CACQ;IA4DhC,uEAcC;IAED;;OAEG;IACH,4BAkKC;IAED,uBAqBC;IAED,iCAEC;CACJ;qBAlrC0D,eAAe;sBAFpD,uBAAuB;0BAGnB,oBAAoB;qBAGzB,eAAe;yBALX,mBAAmB;AAqrC5C;IAeI;;;OAGG;IACH,uBAHW,SAAS,8CA4FnB;IA/FD,uBAAmB;IAsCf;;;MAAoB;IAIpB;;MAQC;IA+CL,2BAKC;IAWD;;;;OAIG;IACH,gCAHW,SAAS,UACT,SAAS,QA8CnB;;CACJ;oBAv2CmB,qBAAqB"}
1
+ {"version":3,"file":"gridView.d.ts","sourceRoot":"","sources":["../../../src/view/gridView.js"],"names":[],"mappings":"AAswBA;;;GAGG;AACH,iDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CAwB9C;AAED;;;GAGG;AACH,uDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CA6C9C;AA2BD;;GAEG;AACH,8EAUC;AAED;;;;;GAKG;AACH,4CAJW,OAAO,uBAAuB,EAAE,OAAO,UACvC,OAAO,iBAAiB,EAAE,UAAU,YACpC,QAAQ,aAmBlB;AA53BD;;;;;;;;;;;;;;;GAeG;AACH;IA6BI;;;;;;;;;OASG;IACH,kBARW,OAAO,iBAAiB,EAAE,aAAa,WACvC,OAAO,yBAAyB,EAAE,OAAO,gBACzC,aAAa,iDAEb,MAAM,WACN,MAAM,YACN,OAAO,WAAW,EAAE,WAAW,EAoBzC;IARG,8CAAgB;IAOhB,uBAA0B;IAG9B;;OAEG;IACH,qDAIC;IAeD;;OAEG;IACH,wDAKC;IAqBD;;OAEG;IACH,8CAEC;IAED,yBAEC;IAED;;OAEG;IACH,wCAmCC;IA2OD;;;;OAIG;IAEH,gBALW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,uBAAuB,EAAE,gBAAgB,QA6O1D;;CAoGJ;AAgJD;IACI;;;;OAIG;IACH,6DAHW,aAAa,UACb,MAAM,EAoFhB;IAjFG,4BAAgC;IAChC,kCAAgB;IAChB,eAAoB;IAEpB,uBAAuB;IACvB,YADW,QAAQ,CACQ;IAE3B,uBAAuB;IACvB,kBADW,QAAQ,CACc;IAEjC,mFAAmF;IACnF,MADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAC5D;IAEd,4FAA4F;IAC5F,WADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAC3D;IAEnB,0DAA0D;IAC1D,kEAAoB;IAEpB,uBAAuB;IACvB,OADW,QAAQ,CACG;IAEtB,wBAAwB;IACxB,QADW,SAAS,CACQ;IA4DhC,uEAcC;IAED;;OAEG;IACH,4BAkKC;IAED,uBAqBC;IAED,iCAEC;CACJ;qBAlrC0D,eAAe;sBAFpD,uBAAuB;0BAGnB,oBAAoB;qBAGzB,eAAe;yBALX,mBAAmB;AAqrC5C;IAeI;;;OAGG;IACH,uBAHW,SAAS,8CA6FnB;IAhGD,uBAAmB;IAsCf;;;MAAoB;IAIpB;;;;MAQC;IAgDL,2BAKC;IAWD;;;;OAIG;IACH,gCAHW,SAAS,UACT,SAAS,QA8CnB;;CACJ;oBAx2CmB,qBAAqB"}
@@ -1273,11 +1273,11 @@ class Scrollbar extends UnitView {
1273
1273
  this.interpolateViewportOffset = makeLerpSmoother(
1274
1274
  this.context.animator,
1275
1275
  (value) => {
1276
- this.viewportOffset = value;
1276
+ this.viewportOffset = value.x;
1277
1277
  },
1278
1278
  50,
1279
1279
  0.4,
1280
- this.viewportOffset
1280
+ { x: this.viewportOffset }
1281
1281
  );
1282
1282
 
1283
1283
  this.addInteractionEventListener("mousedown", (coords, event) => {
@@ -1309,10 +1309,11 @@ class Scrollbar extends UnitView {
1309
1309
  this.#maxScrollOffset
1310
1310
  );
1311
1311
 
1312
- this.interpolateViewportOffset(
1313
- (scrollOffset / this.#maxScrollOffset) *
1314
- this.#maxViewportOffset
1315
- );
1312
+ this.interpolateViewportOffset({
1313
+ x:
1314
+ (scrollOffset / this.#maxScrollOffset) *
1315
+ this.#maxViewportOffset,
1316
+ });
1316
1317
  };
1317
1318
 
1318
1319
  const onMouseup = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"zoom.d.ts","sourceRoot":"","sources":["../../../src/view/zoom.js"],"names":[],"mappings":"AAkBA,0CAGC;AAgBD;;;;;;GAMG;AACH,yCANW,OAAO,8BAA8B,EAAE,OAAO,UAC9C,OAAO,uBAAuB,EAAE,OAAO,0BAC3B,SAAS,KAAK,IAAI,UAC9B,OAAO,yBAAyB,EAAE,KAAK,aACvC,OAAO,sBAAsB,EAAE,OAAO,QA0IhD;;OAlLS,MAAM;OACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM"}
1
+ {"version":3,"file":"zoom.d.ts","sourceRoot":"","sources":["../../../src/view/zoom.js"],"names":[],"mappings":"AAkBA,0CAGC;AAgBD;;;;;;GAMG;AACH,yCANW,OAAO,8BAA8B,EAAE,OAAO,UAC9C,OAAO,uBAAuB,EAAE,OAAO,0BAC3B,SAAS,KAAK,IAAI,UAC9B,OAAO,yBAAyB,EAAE,KAAK,aACvC,OAAO,sBAAsB,EAAE,OAAO,QAkJhD;;OA1LS,MAAM;OACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM"}