@genome-spy/core 0.71.0 → 0.72.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.
@@ -0,0 +1,164 @@
1
+ /** Sentinel key that marks an empty hash slot. */
2
+ export const HASH_EMPTY_KEY = 0xffff_ffff;
3
+
4
+ /** Default maximum load factor for table sizing. */
5
+ export const DEFAULT_MAX_LOAD_FACTOR = 0.6;
6
+
7
+ const MAX_U32 = 0xffff_ffff;
8
+
9
+ /**
10
+ * Build-time options for sizing the hash table.
11
+ *
12
+ * @typedef {object} HashTableBuildOptions
13
+ * @property {number} [capacity] Power-of-two table size override.
14
+ * @property {number} [maxLoadFactor] Maximum load factor before resizing.
15
+ */
16
+
17
+ /**
18
+ * Packed table payload ready for WebGL texture upload.
19
+ *
20
+ * @typedef {object} HashTableBuildResult
21
+ * @property {Uint32Array} table Hash table slots that contain keys.
22
+ * @property {number} capacity Table capacity (number of slots).
23
+ * @property {number} size Number of stored keys.
24
+ */
25
+
26
+ /**
27
+ * 32-bit integer hash for u32 keys. Keep in sync with GLSL hash32.
28
+ *
29
+ * @param {number} value
30
+ * @returns {number}
31
+ */
32
+ export function hash32(value) {
33
+ let v = value >>> 0;
34
+ v ^= v >>> 16;
35
+ v = Math.imul(v, 0x7feb352d);
36
+ v ^= v >>> 15;
37
+ v = Math.imul(v, 0x846ca68b);
38
+ v ^= v >>> 16;
39
+ return v >>> 0;
40
+ }
41
+
42
+ /**
43
+ * Build a hash table for set membership checks.
44
+ *
45
+ * @param {Iterable<number>} keys
46
+ * @param {HashTableBuildOptions} [options]
47
+ * @returns {HashTableBuildResult}
48
+ */
49
+ export function buildHashTableSet(keys, options = {}) {
50
+ const normalized = Array.from(keys, (key) => normalizeU32(key, "key"));
51
+ const size = normalized.length;
52
+ const maxLoadFactor = options.maxLoadFactor ?? DEFAULT_MAX_LOAD_FACTOR;
53
+ if (!(maxLoadFactor > 0 && maxLoadFactor < 1)) {
54
+ throw new Error("maxLoadFactor must be between 0 and 1.");
55
+ }
56
+
57
+ const capacity =
58
+ options.capacity ?? nextPow2(Math.ceil(size / maxLoadFactor));
59
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
60
+ throw new Error("capacity must be a positive power of two.");
61
+ }
62
+ if ((capacity & (capacity - 1)) !== 0) {
63
+ throw new Error("capacity must be a power of two.");
64
+ }
65
+
66
+ const table = new Uint32Array(capacity);
67
+ table.fill(HASH_EMPTY_KEY);
68
+
69
+ const mask = capacity - 1;
70
+ for (const key of normalized) {
71
+ if (key === HASH_EMPTY_KEY) {
72
+ throw new Error(
73
+ "Hash table keys must not equal the empty sentinel (0xffffffff)."
74
+ );
75
+ }
76
+ let index = hash32(key) & mask;
77
+ let inserted = false;
78
+
79
+ for (let probe = 0; probe < capacity; probe += 1) {
80
+ const existingKey = table[index];
81
+ if (existingKey === HASH_EMPTY_KEY || existingKey === key) {
82
+ table[index] = key;
83
+ inserted = true;
84
+ break;
85
+ }
86
+ index = (index + 1) & mask;
87
+ }
88
+
89
+ if (!inserted) {
90
+ throw new Error(
91
+ "Hash table insertion failed. Increase capacity or lower load factor."
92
+ );
93
+ }
94
+ }
95
+
96
+ return { table, capacity, size };
97
+ }
98
+
99
+ /**
100
+ * Computes a 2D texture layout for a power-of-two hash table capacity.
101
+ *
102
+ * The returned dimensions satisfy `width * height === capacity`.
103
+ *
104
+ * @param {number} capacity
105
+ * @param {number} maxTextureSize
106
+ * @returns {{ width: number, height: number }}
107
+ */
108
+ export function computeHashTextureDimensions(capacity, maxTextureSize) {
109
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
110
+ throw new Error("capacity must be a positive integer.");
111
+ }
112
+ if ((capacity & (capacity - 1)) !== 0) {
113
+ throw new Error("capacity must be a power of two.");
114
+ }
115
+ if (!Number.isSafeInteger(maxTextureSize) || maxTextureSize < 1) {
116
+ throw new Error("maxTextureSize must be a positive integer.");
117
+ }
118
+
119
+ const maxSlots = maxTextureSize * maxTextureSize;
120
+ if (capacity > maxSlots) {
121
+ throw new Error(
122
+ "Selection hash table exceeds maximum texture capacity."
123
+ );
124
+ }
125
+
126
+ const maxPow2Width = 1 << Math.floor(Math.log2(maxTextureSize));
127
+ const width = Math.min(capacity, maxPow2Width);
128
+ const height = capacity / width;
129
+
130
+ if (height > maxTextureSize) {
131
+ throw new Error(
132
+ "Selection hash table dimensions exceed maximum texture size."
133
+ );
134
+ }
135
+
136
+ return { width, height };
137
+ }
138
+
139
+ /**
140
+ * @param {number} value
141
+ * @param {string} label
142
+ * @returns {number}
143
+ */
144
+ function normalizeU32(value, label) {
145
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_U32) {
146
+ throw new Error(label + " must be a non-negative u32.");
147
+ }
148
+ return value >>> 0;
149
+ }
150
+
151
+ /**
152
+ * @param {number} value
153
+ * @returns {number}
154
+ */
155
+ function nextPow2(value) {
156
+ let v = Math.max(1, value);
157
+ v -= 1;
158
+ v |= v >>> 1;
159
+ v |= v >>> 2;
160
+ v |= v >>> 4;
161
+ v |= v >>> 8;
162
+ v |= v >>> 16;
163
+ return v + 1;
164
+ }
@@ -1,2 +1,2 @@
1
- const shader = "#define PI 3.141593\nuniform View{mediump vec2 uViewOffset;mediump vec2 uViewScale;mediump vec2 uViewportSize;lowp float uDevicePixelRatio;lowp float uViewOpacity;bool uPickingEnabled;};/***Maps a coordinate on the unit scale to a normalized device coordinate.*(0,0)is at the bottom left corner.*/vec4 unitToNdc(vec2 coord){return vec4((coord*uViewScale+uViewOffset)*2.0-1.0,0.0,1.0);}vec4 unitToNdc(float x,float y){return unitToNdc(vec2(x,y));}vec4 pixelsToNdc(vec2 coord){return unitToNdc(coord/uViewportSize);}vec4 pixelsToNdc(float x,float y){return pixelsToNdc(vec2(x,y));}float linearstep(float edge0,float edge1,float x){return clamp((x-edge0)/(edge1-edge0),0.0,1.0);}bool isEmptyBinarySearchTexture(highp usampler2D s){return textureSize(s,0).x==1&&texelFetch(s,ivec2(0,0),0).r==0u;}bool binarySearchTexture(highp usampler2D s,uint value){int texSize=textureSize(s,0).x;if(texSize==1&&texelFetch(s,ivec2(0,0),0).r==0u){return false;}int left=0;int right=texSize-1;while(left<=right){int mid=left+(right-left)/2;uint midValue=texelFetch(s,ivec2(mid,0),0).r;if(midValue==value){return true;}if(midValue<value){left=mid+1;}else{right=mid-1;}}return false;}/***Calculates a gamma for antialiasing opacity based on the color.*/float getGammaForColor(vec3 rgb){return mix(1.25,0.75,smoothstep(0.0,1.0,dot(rgb,vec3(0.299,0.587,0.114))));}/***Specialized linearstep for doing antialiasing*/float distanceToRatio(float d){return clamp(d*uDevicePixelRatio+0.5,0.0,1.0);}vec4 distanceToColor(float d,vec4 fill,vec4 stroke,vec4 background,float halfStrokeWidth){if(halfStrokeWidth>0.0){float sd=abs(d)-halfStrokeWidth;return mix(stroke,d<=0.0 ? fill : background,distanceToRatio(sd));}else{return mix(background,fill,distanceToRatio(-d));}}";
1
+ const shader = "#define PI 3.141593\nuniform View{mediump vec2 uViewOffset;mediump vec2 uViewScale;mediump vec2 uViewportSize;lowp float uDevicePixelRatio;lowp float uViewOpacity;bool uPickingEnabled;};/***Maps a coordinate on the unit scale to a normalized device coordinate.*(0,0)is at the bottom left corner.*/vec4 unitToNdc(vec2 coord){return vec4((coord*uViewScale+uViewOffset)*2.0-1.0,0.0,1.0);}vec4 unitToNdc(float x,float y){return unitToNdc(vec2(x,y));}vec4 pixelsToNdc(vec2 coord){return unitToNdc(coord/uViewportSize);}vec4 pixelsToNdc(float x,float y){return pixelsToNdc(vec2(x,y));}float linearstep(float edge0,float edge1,float x){return clamp((x-edge0)/(edge1-edge0),0.0,1.0);}const highp uint HASH_EMPTY_KEY=0xffffffffu;highp uint hash32(highp uint key){highp uint v=key;v ^=v>>16u;v*=0x7feb352du;v ^=v>>15u;v*=0x846ca68bu;v ^=v>>16u;return v;}bool isEmptyHashTexture(highp usampler2D s){ivec2 texSize=textureSize(s,0);return texSize.x==1&&texSize.y==1&&texelFetch(s,ivec2(0,0),0).r==HASH_EMPTY_KEY;}bool hashContainsTexture(highp usampler2D s,highp uint value){ivec2 texSize=textureSize(s,0);highp uint width=uint(texSize.x);highp uint size=width*uint(texSize.y);highp uint mask=size-1u;highp uint index=hash32(value)&mask;for(highp uint probe=0u;probe<size;probe+=1u){ivec2 coord=ivec2(int(index % width),int(index/width));highp uint entry=texelFetch(s,coord,0).r;if(entry==value){return true;}if(entry==HASH_EMPTY_KEY){return false;}index=(index+1u)&mask;}return false;}/***Calculates a gamma for antialiasing opacity based on the color.*/float getGammaForColor(vec3 rgb){return mix(1.25,0.75,smoothstep(0.0,1.0,dot(rgb,vec3(0.299,0.587,0.114))));}/***Specialized linearstep for doing antialiasing*/float distanceToRatio(float d){return clamp(d*uDevicePixelRatio+0.5,0.0,1.0);}vec4 distanceToColor(float d,vec4 fill,vec4 stroke,vec4 background,float halfStrokeWidth){if(halfStrokeWidth>0.0){float sd=abs(d)-halfStrokeWidth;return mix(stroke,d<=0.0 ? fill : background,distanceToRatio(sd));}else{return mix(background,fill,distanceToRatio(-d));}}";
2
2
  export default shader;
@@ -1 +1 @@
1
- {"version":3,"file":"webGLHelper.d.ts","sourceRoot":"","sources":["../../../src/gl/webGLHelper.js"],"names":[],"mappings":"AAyZA;;;;GAIG;AACH,kCAJW,sBAAsB,gBACtB,WAAW,kBACX,WAAW;;;;;;EA8CrB;AAED;;;;;GAKG;AACH,0CALW,qBAAqB,WACrB,IAAI,CAAC,OAAO,SAAS,EAAE,cAAc,EAAE,KAAK,CAAC,OAC7C,MAAM,EAAE,GAAG,eAAe,YAC1B,YAAY,gBAYtB;AAED;;;;;;GAMG;AACH,qCALW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,KACjC,MAAM,KACN,MAAM,2BAWhB;AAED;;;;;GAKG;AACH,yCAJW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,SACjC,MAAM,UA4BhB;AAlfD;IACI;;;;;;;OAOG;IACH,uBANW,WAAW,eACX,MAAM;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,2BAGrC,sBAAsB,EAsFhC;IAnFG,wBAA2B;IAC3B;;;MAKO;IAEP,uCAAuC;IACvC,cADW,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CACN;IAE7B,6EAA6E;IAC7E,eADW,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,EAAE,YAAY,CAAC,CACvC;IAElC;;OAEG;IACH,mBAFU,OAAO,CAAC,OAAO,4BAA4B,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAEnD;IA8CtC,0BAAoB;IACpB,2BAAY;IAGZ,oDAAoD;IACpD,2BADW,OAAO,SAAS,EAAE,iBAAiB,EAAE,CAQ/C;IACD,sDAGC;IAML,uBAGC;IAFG;;;MAAmC;IAIvC;;;;;OAKG;IACH,oBAHW,MAAM,QACN,MAAM,GAAG,MAAM,EAAE,eA2B3B;IAED,iBAcC;IAED,iBAEC;IAED;;;;OAIG;IACH,oCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;;;MAS3C;IAED;;;OAGG;IACH;;;MAuBC;IAED;;;;;;;;;OASG;IACH,+BAHW,OAAO,8BAA8B,EAAE,OAAO,WAC9C,OAAO,QA2GjB;IAED;;OAEG;IACH,kCAFW,OAAO,4BAA4B,EAAE,mBAAmB,0BA+BlE;CACJ"}
1
+ {"version":3,"file":"webGLHelper.d.ts","sourceRoot":"","sources":["../../../src/gl/webGLHelper.js"],"names":[],"mappings":"AA+ZA;;;;GAIG;AACH,kCAJW,sBAAsB,gBACtB,WAAW,kBACX,WAAW;;;;;;EA8CrB;AAED;;;;;GAKG;AACH,0CALW,qBAAqB,WACrB,IAAI,CAAC,OAAO,SAAS,EAAE,cAAc,EAAE,KAAK,CAAC,OAC7C,MAAM,EAAE,GAAG,eAAe,YAC1B,YAAY,gBAYtB;AAED;;;;;;GAMG;AACH,qCALW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,KACjC,MAAM,KACN,MAAM,2BAWhB;AAED;;;;;GAKG;AACH,yCAJW,sBAAsB,mBACtB,OAAO,SAAS,EAAE,eAAe,SACjC,MAAM,UA4BhB;AApfD;IACI;;;;;;;OAOG;IACH,uBANW,WAAW,eACX,MAAM;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,2BAGrC,sBAAsB,EAsFhC;IAnFG,wBAA2B;IAC3B;;;MAKO;IAEP,uCAAuC;IACvC,cADW,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CACN;IAE7B,6EAA6E;IAC7E,eADW,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,EAAE,YAAY,CAAC,CACvC;IAElC;;OAEG;IACH,mBAFU,OAAO,CAAC,OAAO,4BAA4B,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAEnD;IA8CtC,0BAAoB;IACpB,2BAAY;IAGZ,oDAAoD;IACpD,2BADW,OAAO,SAAS,EAAE,iBAAiB,EAAE,CAQ/C;IACD,sDAGC;IAML,uBAGC;IAFG;;;MAAmC;IAIvC;;;;;OAKG;IACH,oBAHW,MAAM,QACN,MAAM,GAAG,MAAM,EAAE,eA2B3B;IAED,iBAcC;IAED,iBAEC;IAED;;;;OAIG;IACH,oCAFW;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;;;MAS3C;IAED;;;OAGG;IACH;;;MAuBC;IAED;;;;;;;;;OASG;IACH,+BAHW,OAAO,8BAA8B,EAAE,OAAO,WAC9C,OAAO,QA2GjB;IAED;;OAEG;IACH,kCAFW,OAAO,4BAA4B,EAAE,mBAAmB,0BAiClE;CACJ"}
@@ -27,6 +27,10 @@ import {
27
27
  isDiscreteChannel,
28
28
  } from "../encoder/encoder.js";
29
29
  import { isMultiPointSelection } from "../selection/selection.js";
30
+ import {
31
+ buildHashTableSet,
32
+ computeHashTextureDimensions,
33
+ } from "./hashTable.js";
30
34
 
31
35
  export default class WebGLHelper {
32
36
  /**
@@ -351,9 +355,11 @@ export default class WebGLHelper {
351
355
  );
352
356
  }
353
357
 
354
- const keys = Array.from(selection.data.keys());
355
- // Zero is a special value for no selection. The minimum texture size is 1.
356
- const uniqueIds = keys.length > 0 ? keys.sort((a, b) => a - b) : [0];
358
+ const { table, capacity } = buildHashTableSet(selection.data.keys());
359
+ const { width, height } = computeHashTextureDimensions(
360
+ capacity,
361
+ this.gl.getParameter(this.gl.MAX_TEXTURE_SIZE)
362
+ );
357
363
 
358
364
  const existingTexture = this.selectionTextures.get(selection);
359
365
 
@@ -365,10 +371,10 @@ export default class WebGLHelper {
365
371
  minMag: gl.NEAREST,
366
372
  format: gl.RED_INTEGER,
367
373
  internalFormat: gl.R32UI,
368
- height: 1,
369
- width: uniqueIds.length,
374
+ width,
375
+ height,
370
376
  },
371
- new Uint32Array(uniqueIds),
377
+ table,
372
378
  update ? existingTexture : false
373
379
  );
374
380
 
@@ -137,7 +137,7 @@ export default class Mark<P extends import("../spec/mark.js").MarkProps = import
137
137
  */
138
138
  get encoding(): import("../spec/channel.js").Encoding;
139
139
  getContext(): import("../types/viewContext.js").default;
140
- getType(): "link" | "rect" | "text" | "point" | "rule";
140
+ getType(): "link" | "point" | "text" | "rect" | "rule";
141
141
  initializeData(): void;
142
142
  /**
143
143
  * Initialize encoders that encode fields of the data (or constants) to
@@ -1 +1 @@
1
- {"version":3,"file":"mark.d.ts","sourceRoot":"","sources":["../../../src/marks/mark.js"],"names":[],"mappings":"AA6DA,mCAAoC,sBAAsB,CAAC;AAC3D,mCAAoC,sBAAsB,CAAC;AAE3D,uCAAwC,oBAAoB,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,0BAF0B,CAAC,SAAd,mCAAW;IAkBpB;;OAEG;IACH,sBAFW,OAAO,qBAAqB,EAAE,OAAO,EA4G/C;IAzGG,oFAAwB;IAExB,8EAA8E;IAC9E,UADW,OAAO,CAAC,MAAM,uCAAU,OAAO,qBAAqB,EAAE,OAAO,CAAC,CAAC,CACjD;IAIzB;;;OAGG;IACH,sBAHU,OAAO,SAAS,EAAE,UAAU,GAAG;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAG5C;IAE3B;;;;;;;OAOG;IACH,2BAHU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAGG;IAEhC;;;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;;;;;OAKG;IACH,4BAA6B;IAE7B,kFAAkF;IAClF,UADW,QAAQ,CAAC,GAAG,CAAC,CACM;IAG9B,qBAqBE;IAEF;;;;;;OAMG;IACH,qBAHU,CAAC,CAQV;IAGL;;;OAGG;IACH,0CAHW,OAAO,CAAC,CAAC,CAAC,QAQpB;IAED,sBAEC;IAED;;;OAGG;IACH,0BAFa,WAAW,CAIvB;IAED;;;;;OAKG;IACH,2BAHa,OAAO,oBAAoB,EAAE,OAAO,EAAE,CAMlD;IAED;;OAEG;IACH,wBAFa,sCAAS,CAarB;IAED;;OAEG;IACH,4DAcC;IAED;;;;;OAKG;IACH,oGAEC;IAED;;;;;;OAMG;IACH,oDAHW,CAAC,MAAM,CAAC,CAAC,EAAE,QAqCrB;IAED;;;;OAIG;IACH,sDAiDC;IAED,wDAEC;IAED,uDAEC;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,QAqelB;IALG;;;;;;MAIC;IAGL;;;;;;OAMG;IACH,uCAwDC;IAED;;;;;;OAMG;IACH,+CAHW,MAAM,GACJ,CAAS,IAAG,EAAH,GAAG,KAAE,IAAI,CAe9B;IAED;;;;;;;;;;OAUG;IACH,mCALa,CAAC,eACH,MAAM,aACN,CAAC,aACD,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,yCAAU,KAAK,GAAG,QA6BzC;IAED;;OAEG;IACH,2BA+BC;IAED,gBAEC;IAED;;;OAGG;IACH,6BAFW,GAAG,QAyCb;IAED,yBAAyB;IACzB,uDAEC;IAED,yBAAyB;IACzB,iCAEC;IAED,yCAEC;IAED;;OAEG;IACH,gCAgBC;IAED;;OAEG;IACH,4CAOC;IAED;;;;;;;;OAQG;IAEH,uBAJW,OAAO,uBAAuB,EAAE,sBAAsB,GACpD,CAAC,MAAM,IAAI,CAAC,EAAE,CA4E1B;IAED;;;;;;OAMG;IACH,qCAJW,oBAAoB,GAClB,OAAO,CAiCnB;IAED;;;;;;;OAOG;IACH,gBAJW,oBAAoB,GAClB,MAAW,IAAI,CAM3B;IAED;;;;OAIG;IACH,2BAJW,YAAY,WACZ,OAAO,WAAW,EAAE,oBAAoB,GACtC,MAAW,IAAI,CAmE3B;IAED;;;;;;;;OAQG;IACH,wBANW;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,OAC/B,MAAM,UACN,OAAO,6BAA6B,EAAE,OAAO,aAC7C,OAAO,6BAA6B,EAAE,OAAO,GAC3C,OAAO,CA+GnB;IAED;;;;;;;;;OASG;IACH,qBAJW,MAAM,KACN,OAAO,oBAAoB,EAAE,MAAM,GACjC,GAAG,CAIf;;CACJ;+BAx7CY,OAAO,uBAAuB,EAAE,gBAAgB;;;;;;wBAEnD,OAAO;;mCAEJ,gBAAgB,GAAG,qBAAqB;oCAG1C,MAAM,SACN,MAAM;0BAEJ,YAAY,GAAG,UAAU,GAAG,WAAW;AAg7CpD;;;GAGG;AACH,uBAFa,CAAC;IAGV,cAEC;IAkBD;;;OAGG;IACH,2BAFW,GAAG,CAAC,CAAC,EAAE,OAAO,yBAAyB,EAAE,UAAU,CAAC,QAiB9D;CACJ;0BA3/CyB,WAAW"}
1
+ {"version":3,"file":"mark.d.ts","sourceRoot":"","sources":["../../../src/marks/mark.js"],"names":[],"mappings":"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"}
@@ -528,8 +528,7 @@ export default class Mark {
528
528
  }
529
529
  } else if (isMultiPointSelection(selection)) {
530
530
  // We need a texture for each multi-selection parameter.
531
- // The texture contains the uniqueIds of the selected data objects sorted
532
- // in ascending order, which allows for binary search.
531
+ // The texture stores an open-addressing hash table of selected uniqueIds.
533
532
  if (!selectionParameterUniforms.has(param)) {
534
533
  selectionParameterUniforms.set(param, "multi");
535
534
 
@@ -560,7 +559,7 @@ export default class Mark {
560
559
  const texName = SELECTION_TEXTURE_PREFIX + param;
561
560
  scaleCode.push(
562
561
  `bool ${SELECTION_CHECKER_PREFIX}${param}(bool empty) {\n` +
563
- ` return binarySearchTexture(${texName}, ${uniqueIdAttr}) || (empty && isEmptyBinarySearchTexture(${texName}));\n` +
562
+ ` return hashContainsTexture(${texName}, ${uniqueIdAttr}) || (empty && isEmptyHashTexture(${texName}));\n` +
564
563
  `}`
565
564
  );
566
565
 
@@ -1 +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"}
1
+ {"version":3,"file":"radixSort.d.ts","sourceRoot":"","sources":["../../../src/utils/radixSort.js"],"names":[],"mappings":"AAiBA;;GAEG;AACH,uCAFW,MAAM,EAAE,YA8DlB;AAeD;;GAEG;AACH,8CAFW,MAAM,EAAE,YA0DlB"}
@@ -3,12 +3,29 @@ const MAX_INTEGER_DIGIT = getDigits([MAX_INTEGER]);
3
3
 
4
4
  // TODO: Optimize more! Some ideas: https://travisdowns.github.io/blog/2019/05/22/sorting.html
5
5
 
6
+ /**
7
+ * @param {number[]} arr An array of unsigned integers
8
+ */
9
+ function checkIfSorted(arr) {
10
+ for (let i = 1; i < arr.length; i++) {
11
+ if (arr[i] < arr[i - 1]) {
12
+ return false;
13
+ }
14
+ }
15
+ return true;
16
+ }
17
+
6
18
  /**
7
19
  * @param {number[]} arr An array of unsigned integers
8
20
  */
9
21
  export default function radixSort(arr) {
10
22
  const maxDigits = getDigits(arr);
11
23
 
24
+ // Early exit if already sorted
25
+ if (checkIfSorted(arr)) {
26
+ return arr;
27
+ }
28
+
12
29
  let buffer = new Array(arr.length);
13
30
  let bufferPtr = buffer;
14
31
  const counts = new Array(16);
@@ -69,7 +86,9 @@ export default function radixSort(arr) {
69
86
  function getDigits(arr) {
70
87
  let max = 0;
71
88
  for (let i = 0, n = arr.length; i < n; i++) {
72
- max = Math.max(max, arr[i]);
89
+ if (arr[i] > max) {
90
+ max = arr[i];
91
+ }
73
92
  }
74
93
  return Math.floor(Math.log2(max) / 4) + 1;
75
94
  }
@@ -80,6 +99,12 @@ function getDigits(arr) {
80
99
  export function radixSortIntoLookupArray(arr) {
81
100
  const maxDigits = getDigits(arr);
82
101
  let indexes = Array.from({ length: arr.length }, (_, i) => i);
102
+
103
+ // Early exit if already sorted
104
+ if (checkIfSorted(arr)) {
105
+ return indexes;
106
+ }
107
+
83
108
  let buffer = new Array(arr.length);
84
109
  const counts = new Array(16);
85
110
 
@@ -48,7 +48,7 @@ export default class FacetView extends ContainerView<import("../spec/view.js").C
48
48
  getAccessor(channel: "row" | "column"): any;
49
49
  updateFacets(): void;
50
50
  updateLabels(): void;
51
- getFacetGroups(): string[] | number[] | boolean[];
51
+ getFacetGroups(): number[] | boolean[] | string[];
52
52
  /**
53
53
  * @param {import("./renderingContext/viewRenderingContext.js").default} context
54
54
  * @param {import("./layout/rectangle.js").default} coords
@@ -21,7 +21,7 @@ export default class UnitView<TSpec extends import("../spec/view.js").UnitSpec =
21
21
  constructor(spec: TSpec, context: import("../types/viewContext.js").default, layoutParent: import("./containerView.js").default, dataParent: import("./view.js").default, name: string, options?: import("./view.js").ViewOptions);
22
22
  /** @type {import("../marks/mark.js").default} */
23
23
  mark: import("../marks/mark.js").default;
24
- getMarkType(): "link" | "rect" | "text" | "point" | "rule";
24
+ getMarkType(): "link" | "point" | "text" | "rect" | "rule";
25
25
  /**
26
26
  * Pulls scales and axes up in the view hierarcy according to the resolution rules, using dataParents.
27
27
  * TODO: legends
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "contributors": [],
9
9
  "license": "MIT",
10
- "version": "0.71.0",
10
+ "version": "0.72.0",
11
11
  "jsdelivr": "dist/bundle/index.js",
12
12
  "unpkg": "dist/bundle/index.js",
13
13
  "browser": "dist/bundle/index.js",
@@ -56,6 +56,7 @@
56
56
  "flatqueue": "^3.0.0",
57
57
  "generic-filehandle2": "^2.0.14",
58
58
  "gff-nostream": "^2.0.0",
59
+ "hyparquet": "^1.25.0",
59
60
  "internmap": "^2.0.3",
60
61
  "lit": "^3.3.0",
61
62
  "twgl.js": "^4.19.1",
@@ -67,5 +68,5 @@
67
68
  "devDependencies": {
68
69
  "@types/long": "^4.0.1"
69
70
  },
70
- "gitHead": "ee14df5ba7218882d48a1674916304ac6f0a4549"
71
+ "gitHead": "504e1d0f7c45c2324bb6b0ff7b9230829d60518e"
71
72
  }