@genome-spy/core 0.43.3 → 0.45.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 (124) hide show
  1. package/dist/bundle/index.es.js +5231 -4324
  2. package/dist/bundle/index.js +197 -85
  3. package/dist/schema.json +723 -104
  4. package/dist/src/data/collector.d.ts.map +1 -1
  5. package/dist/src/data/collector.js +4 -2
  6. package/dist/src/data/flowOptimizer.test.js +12 -3
  7. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  8. package/dist/src/data/sources/dataUtils.js +3 -1
  9. package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
  10. package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
  11. package/dist/src/data/sources/lazy/axisTickSource.js +2 -2
  12. package/dist/src/data/sources/lazy/bigBedSource.d.ts +1 -1
  13. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  14. package/dist/src/data/sources/lazy/bigBedSource.js +52 -20
  15. package/dist/src/data/sources/lazy/bigWigSource.d.ts +6 -1
  16. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  17. package/dist/src/data/sources/lazy/bigWigSource.js +33 -9
  18. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  19. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  20. package/dist/src/data/sources/lazy/singleAxisLazySource.js +1 -3
  21. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +13 -14
  22. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  23. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +70 -48
  24. package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
  25. package/dist/src/data/sources/sequenceSource.js +14 -5
  26. package/dist/src/data/sources/sequenceSource.test.js +23 -5
  27. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  28. package/dist/src/data/sources/urlSource.js +15 -2
  29. package/dist/src/data/transforms/aggregate.d.ts.map +1 -1
  30. package/dist/src/data/transforms/aggregate.js +5 -2
  31. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  32. package/dist/src/encoder/encoder.d.ts +2 -4
  33. package/dist/src/encoder/encoder.d.ts.map +1 -1
  34. package/dist/src/encoder/encoder.js +20 -10
  35. package/dist/src/encoder/encoder.test.js +3 -0
  36. package/dist/src/genomeSpy.d.ts +8 -5
  37. package/dist/src/genomeSpy.d.ts.map +1 -1
  38. package/dist/src/genomeSpy.js +121 -42
  39. package/dist/src/gl/glslScaleGenerator.d.ts +23 -3
  40. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  41. package/dist/src/gl/glslScaleGenerator.js +137 -42
  42. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  43. package/dist/src/gl/webGLHelper.js +5 -7
  44. package/dist/src/index.d.ts +1 -1
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +1 -1
  47. package/dist/src/marks/link.common.glsl.js +2 -0
  48. package/dist/src/marks/link.d.ts.map +1 -1
  49. package/dist/src/marks/link.js +19 -9
  50. package/dist/src/marks/link.vertex.glsl.js +1 -1
  51. package/dist/src/marks/mark.d.ts +25 -20
  52. package/dist/src/marks/mark.d.ts.map +1 -1
  53. package/dist/src/marks/mark.js +234 -129
  54. package/dist/src/marks/point.common.glsl.js +1 -1
  55. package/dist/src/marks/point.d.ts +1 -4
  56. package/dist/src/marks/point.d.ts.map +1 -1
  57. package/dist/src/marks/point.js +31 -23
  58. package/dist/src/marks/point.vertex.glsl.js +1 -1
  59. package/dist/src/marks/rect.common.glsl.js +2 -0
  60. package/dist/src/marks/rect.d.ts.map +1 -1
  61. package/dist/src/marks/rect.js +12 -12
  62. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  63. package/dist/src/marks/rule.common.glsl.js +1 -1
  64. package/dist/src/marks/rule.js +2 -2
  65. package/dist/src/marks/text.common.glsl.js +1 -1
  66. package/dist/src/marks/text.d.ts.map +1 -1
  67. package/dist/src/marks/text.js +17 -9
  68. package/dist/src/spec/channel.d.ts +4 -3
  69. package/dist/src/spec/data.d.ts +11 -10
  70. package/dist/src/spec/mark.d.ts +28 -46
  71. package/dist/src/spec/parameter.d.ts +127 -0
  72. package/dist/src/spec/root.d.ts +1 -0
  73. package/dist/src/spec/scale.d.ts +2 -1
  74. package/dist/src/spec/title.d.ts +5 -4
  75. package/dist/src/spec/view.d.ts +20 -5
  76. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  77. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  78. package/dist/src/styles/genome-spy.css.js +52 -5
  79. package/dist/src/styles/genome-spy.scss +63 -10
  80. package/dist/src/styles/update.sh +6 -0
  81. package/dist/src/tooltip/dataTooltipHandler.js +1 -1
  82. package/dist/src/tooltip/refseqGeneTooltipHandler.js +1 -1
  83. package/dist/src/tooltip/tooltipHandler.d.ts +1 -1
  84. package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
  85. package/dist/src/tooltip/tooltipHandler.ts +1 -1
  86. package/dist/src/types/embedApi.d.ts +6 -0
  87. package/dist/src/types/scaleResolutionApi.d.ts +7 -3
  88. package/dist/src/types/viewContext.d.ts +2 -3
  89. package/dist/src/utils/debounce.d.ts +2 -2
  90. package/dist/src/utils/debounce.d.ts.map +1 -1
  91. package/dist/src/utils/debounce.js +5 -2
  92. package/dist/src/utils/expression.d.ts +2 -2
  93. package/dist/src/utils/expression.d.ts.map +1 -1
  94. package/dist/src/utils/expression.js +3 -3
  95. package/dist/src/utils/formatObject.d.ts +2 -2
  96. package/dist/src/utils/formatObject.d.ts.map +1 -1
  97. package/dist/src/utils/formatObject.js +2 -2
  98. package/dist/src/utils/inputBinding.d.ts +5 -0
  99. package/dist/src/utils/inputBinding.d.ts.map +1 -0
  100. package/dist/src/utils/inputBinding.js +115 -0
  101. package/dist/src/utils/ui/tooltip.js +1 -1
  102. package/dist/src/view/axisView.js +3 -3
  103. package/dist/src/view/paramMediator.d.ts +108 -0
  104. package/dist/src/view/paramMediator.d.ts.map +1 -0
  105. package/dist/src/view/paramMediator.js +337 -0
  106. package/dist/src/view/paramMediator.test.js +211 -0
  107. package/dist/src/view/scaleResolution.d.ts +8 -18
  108. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  109. package/dist/src/view/scaleResolution.js +225 -126
  110. package/dist/src/view/scaleResolution.test.js +7 -7
  111. package/dist/src/view/unitView.d.ts.map +1 -1
  112. package/dist/src/view/unitView.js +10 -3
  113. package/dist/src/view/view.d.ts +4 -1
  114. package/dist/src/view/view.d.ts.map +1 -1
  115. package/dist/src/view/view.js +21 -7
  116. package/dist/src/view/viewFactory.d.ts.map +1 -1
  117. package/dist/src/view/viewFactory.js +45 -0
  118. package/dist/src/view/viewUtils.d.ts +5 -1
  119. package/dist/src/view/viewUtils.d.ts.map +1 -1
  120. package/dist/src/view/viewUtils.js +9 -4
  121. package/package.json +16 -17
  122. package/dist/src/paramBroker.d.ts +0 -30
  123. package/dist/src/paramBroker.d.ts.map +0 -1
  124. package/dist/src/paramBroker.js +0 -102
@@ -18,15 +18,16 @@ import createEncoders, {
18
18
  isValueDef,
19
19
  } from "../encoder/encoder.js";
20
20
  import {
21
- DOMAIN_PREFIX,
22
- generateValueGlsl,
21
+ generateConstantValueGlsl,
23
22
  generateScaleGlsl,
24
23
  RANGE_TEXTURE_PREFIX,
25
- ATTRIBUTE_PREFIX,
26
24
  isHighPrecisionScale,
27
25
  toHighPrecisionDomainUniform,
28
- splitHighPrecision,
29
26
  dedupeEncodingFields,
27
+ generateDynamicValueGlslAndUniform,
28
+ isLargeGenome,
29
+ splitLargeHighPrecision,
30
+ getRangeForGlsl,
30
31
  } from "../gl/glslScaleGenerator.js";
31
32
  import GLSL_COMMON from "../gl/includes/common.glsl.js";
32
33
  import GLSL_SCALES from "../gl/includes/scales.glsl.js";
@@ -38,9 +39,8 @@ import { createProgram } from "../gl/webGLHelper.js";
38
39
  import coalesceProperties from "../utils/propertyCoalescer.js";
39
40
  import { isScalar } from "../utils/variableTools.js";
40
41
  import { InternMap } from "internmap";
41
- import scaleNull from "../utils/scaleNull.js";
42
42
  import ViewError from "../view/viewError.js";
43
- import { isString } from "vega-util";
43
+ import { isExprRef } from "../view/paramMediator.js";
44
44
 
45
45
  export const SAMPLE_FACET_UNIFORM = "SAMPLE_FACET_UNIFORM";
46
46
  export const SAMPLE_FACET_TEXTURE = "SAMPLE_FACET_TEXTURE";
@@ -60,13 +60,20 @@ export const SAMPLE_FACET_TEXTURE = "SAMPLE_FACET_TEXTURE";
60
60
  */
61
61
  export default class Mark {
62
62
  /**
63
- * @typedef {import("../spec/mark.js").MarkConfig} MarkConfig
63
+ * @typedef {import("../spec/mark.js").MarkProps} MarkProps
64
64
  * @typedef {import("../spec/channel.js").Channel} Channel
65
65
  * @typedef {import("../spec/channel.js").Encoding} Encoding
66
66
  * @typedef {import("../spec/channel.js").ValueDef} ValueDef
67
- * @typedef {import("../spec/mark.js").ExprRef} ExprRef
67
+ * @typedef {import("../spec/parameter.js").ExprRef} ExprRef
68
68
  */
69
69
 
70
+ /**
71
+ * Only needed during initialization;
72
+ *
73
+ * @type {(() => void)[]}
74
+ */
75
+ #callAfterShaderCompilation = [];
76
+
70
77
  /**
71
78
  * @param {import("../view/unitView.js").default} unitView
72
79
  */
@@ -111,7 +118,7 @@ export default class Mark {
111
118
  this.rangeMap = new RangeMap();
112
119
 
113
120
  // TODO: Implement https://vega.github.io/vega-lite/docs/config.html
114
- /** @type {MarkConfig} */
121
+ /** @type {MarkProps} */
115
122
  this.defaultProperties = {
116
123
  get clip() {
117
124
  // TODO: Cache once the scales have been resolved
@@ -141,13 +148,13 @@ export default class Mark {
141
148
  *
142
149
  * TODO: Proper and comprehensive typings for mark properties
143
150
  *
144
- * @type {Partial<MarkConfig>}
151
+ * @type {Partial<MarkProps>}
145
152
  * @readonly
146
153
  */
147
154
  this.properties = coalesceProperties(
148
155
  typeof this.unitView.spec.mark == "object"
149
- ? () => /** @type {MarkConfig} */ (this.unitView.spec.mark)
150
- : () => /** @type {MarkConfig} */ ({}),
156
+ ? () => /** @type {MarkProps} */ (this.unitView.spec.mark)
157
+ : () => /** @type {MarkProps} */ ({}),
151
158
  () => this.defaultProperties
152
159
  );
153
160
  }
@@ -214,6 +221,49 @@ export default class Mark {
214
221
  return encoding;
215
222
  }
216
223
 
224
+ /**
225
+ * Handles dynamic properties that are not bound to uniforms but need
226
+ * to trigger a graphics update, i.e., rebuild the vertex buffer.
227
+ *
228
+ * @param {(keyof MarkProps)[]} props
229
+ * @protected
230
+ */
231
+ setupExprRefsNeedingGraphicsUpdate(props) {
232
+ const channels = this.getSupportedChannels();
233
+ /** @type {Partial<MarkProps>} */
234
+ const exprProps = {};
235
+ for (const key of props) {
236
+ const prop = this.properties[key];
237
+ if (prop && isExprRef(prop)) {
238
+ const fn = this.unitView.paramMediator.createExpression(
239
+ prop.expr
240
+ );
241
+ fn.addListener(() => {
242
+ this.updateGraphicsData();
243
+ this.unitView.context.animator.requestRender();
244
+ });
245
+ // @ts-ignore
246
+ if (!channels.includes(key)) {
247
+ Object.defineProperty(exprProps, key, {
248
+ get() {
249
+ return fn();
250
+ },
251
+ });
252
+ } else {
253
+ // Encoder takes care of evaluating the expression
254
+ // N.B.: There are now two expression instances for the
255
+ // same expression, which is no ideal
256
+ }
257
+ }
258
+ }
259
+ const originalProperties = this.properties;
260
+ // @ts-ignore
261
+ this.properties = coalesceProperties(
262
+ () => exprProps,
263
+ () => originalProperties
264
+ );
265
+ }
266
+
217
267
  /**
218
268
  * Returns the encoding spec supplemented with mark's default encodings
219
269
  *
@@ -227,20 +277,22 @@ export default class Mark {
227
277
  /** @type {(property: string) => ValueDef} */
228
278
  const propToValueDef = (property) => {
229
279
  const value =
230
- this.properties[/** @type {keyof MarkConfig} */ (property)];
231
- return isScalar(value) && { value };
280
+ this.properties[/** @type {keyof MarkProps} */ (property)];
281
+ return isScalar(value) || isExprRef(value)
282
+ ? { value }
283
+ : undefined;
232
284
  };
233
285
 
234
286
  const propertyValues = Object.fromEntries(
235
287
  this.getSupportedChannels()
236
288
  .map(
237
289
  (channel) =>
238
- /** @type {[Channel, ValueDef]} */ ([
290
+ /** @type {[Channel, ValueDef] } */ ([
239
291
  channel,
240
292
  propToValueDef(channel),
241
293
  ])
242
294
  )
243
- .filter((entry) => entry[1].value !== undefined)
295
+ .filter((entry) => isValueDef(entry[1]))
244
296
  );
245
297
 
246
298
  const encoding = this.fixEncoding({
@@ -326,16 +378,13 @@ export default class Mark {
326
378
  * @param {string[]} [extraHeaders]
327
379
  * @protected
328
380
  */
381
+ // eslint-disable-next-line complexity
329
382
  createAndLinkShaders(vertexShader, fragmentShader, extraHeaders = []) {
330
383
  const attributes = this.getAttributes();
331
384
 
332
385
  // For debugging
333
386
  const debugHeader = "// view: " + this.unitView.getPathString();
334
387
 
335
- // TODO: This is a temporary variable, don't store it in the mark object
336
- /** @type {string[]} */
337
- this.domainUniforms = [];
338
-
339
388
  /** @type {string[]} */
340
389
  let scaleCode = [];
341
390
 
@@ -353,6 +402,9 @@ export default class Mark {
353
402
  extraHeaders.push(`#define ${sampleFacetMode}`);
354
403
  }
355
404
 
405
+ /** @type {string[]} */
406
+ const dynamicMarkUniforms = [];
407
+
356
408
  for (const attribute of attributes) {
357
409
  /** @type {Channel} */
358
410
  let channel;
@@ -363,24 +415,40 @@ export default class Mark {
363
415
  }
364
416
 
365
417
  const channelDef = this.encoding[channel];
366
-
367
418
  if (!channelDef) {
368
419
  continue;
369
420
  }
370
421
 
371
422
  if (isValueDef(channelDef)) {
372
- scaleCode.push(generateValueGlsl(channel, channelDef.value));
423
+ if (isExprRef(channelDef.value)) {
424
+ // An expression that evaluates to a value
425
+ const { uniformName, uniformGlsl, scaleGlsl, adjuster } =
426
+ generateDynamicValueGlslAndUniform(channel);
427
+ scaleCode.push(scaleGlsl);
428
+ dynamicMarkUniforms.push(uniformGlsl);
429
+
430
+ this.#callAfterShaderCompilation.push(() => {
431
+ this.registerMarkUniformValue(
432
+ uniformName,
433
+ channelDef.value,
434
+ adjuster
435
+ );
436
+ });
437
+ } else {
438
+ // A constant value
439
+ scaleCode.push(
440
+ generateConstantValueGlsl(channel, channelDef.value)
441
+ );
442
+ }
373
443
  } else {
374
444
  const resolutionChannel =
375
445
  (isChannelDefWithScale(channelDef) &&
376
446
  channelDef.resolutionChannel) ||
377
447
  channel;
378
448
 
379
- const scale = isChannelWithScale(resolutionChannel)
380
- ? this.unitView
381
- .getScaleResolution(resolutionChannel)
382
- .getScale()
383
- : scaleNull();
449
+ const scaleResolution = isChannelWithScale(resolutionChannel)
450
+ ? this.unitView.getScaleResolution(resolutionChannel)
451
+ : null;
384
452
 
385
453
  // Channels that share the same quantitative field
386
454
  // TODO: It should be ok to share a categorical field if the channels
@@ -391,7 +459,7 @@ export default class Mark {
391
459
 
392
460
  const generated = generateScaleGlsl(
393
461
  channel,
394
- scale,
462
+ scaleResolution,
395
463
  channelDef,
396
464
  sharedChannels?.includes(channel)
397
465
  ? sharedChannels
@@ -399,22 +467,103 @@ export default class Mark {
399
467
  );
400
468
 
401
469
  scaleCode.push(generated.glsl);
402
- if (generated.domainUniform) {
403
- this.domainUniforms.push(generated.domainUniform);
470
+ dynamicMarkUniforms.push(generated.domainUniform);
471
+ dynamicMarkUniforms.push(generated.rangeUniform);
472
+ attributeCode.add(generated.attributeGlsl);
473
+
474
+ if (generated.rangeUniform) {
475
+ this.#callAfterShaderCompilation.push(() => {
476
+ const rangeSetter = this.createMarkUniformSetter(
477
+ generated.rangeName
478
+ );
479
+
480
+ const set = () =>
481
+ rangeSetter(
482
+ getRangeForGlsl(scaleResolution.scale, channel)
483
+ );
484
+ scaleResolution.addEventListener("range", set);
485
+
486
+ // Initial value
487
+ set();
488
+ });
404
489
  }
405
- if (generated.attributeGlsl) {
406
- attributeCode.add(generated.attributeGlsl);
490
+
491
+ if (generated.markUniformGlsl) {
492
+ if (!isDatumDef(channelDef)) {
493
+ throw new Error("Bug!");
494
+ }
495
+
496
+ const encoder = this.encoders[channel];
497
+
498
+ const indexer = encoder.indexer;
499
+ const hp = isHighPrecisionScale(encoder.scale.type);
500
+ const largeHp = hp && isLargeGenome(encoder.scale.domain());
501
+
502
+ /**
503
+ * Discrete variables both numeric and strings must be "indexed",
504
+ * 64 bit floats must be converted to vec2.
505
+ * 32 bit continuous variables go to GPU as is.
506
+ *
507
+ * @type {function(import("../spec/channel.js").Scalar):(number | number[])}
508
+ */
509
+ const adjuster = indexer
510
+ ? indexer
511
+ : largeHp
512
+ ? splitLargeHighPrecision
513
+ : (d) => +d;
514
+
515
+ dynamicMarkUniforms.push(generated.markUniformGlsl);
516
+
517
+ this.#callAfterShaderCompilation.push(() => {
518
+ this.registerMarkUniformValue(
519
+ generated.attributeName,
520
+ channelDef.datum,
521
+ adjuster
522
+ );
523
+ });
524
+ }
525
+
526
+ if (generated.domainUniform) {
527
+ this.#callAfterShaderCompilation.push(() => {
528
+ const domainSetter = this.createMarkUniformSetter(
529
+ generated.domainUniformName
530
+ );
531
+ const scale = scaleResolution.scale;
532
+ const set = () => {
533
+ const domain = isDiscrete(scale.type)
534
+ ? [0, scale.domain().length]
535
+ : scale.domain();
536
+
537
+ domainSetter(
538
+ isHighPrecisionScale(scale.type)
539
+ ? toHighPrecisionDomainUniform(domain)
540
+ : domain
541
+ );
542
+ };
543
+
544
+ scaleResolution.addEventListener("domain", set);
545
+
546
+ // Initial value
547
+ set();
548
+ });
407
549
  }
408
550
  }
409
551
  }
410
552
 
411
- const domainUniformBlock = this.domainUniforms.length
412
- ? "layout(std140) uniform Domains {\n" +
413
- this.domainUniforms.map((u) => ` ${u}\n`).join("") +
414
- "};\n\n"
415
- : "";
553
+ const vertexPrecision = "precision highp float;\nprecision highp int;";
416
554
 
417
- const vertexPrecision = "precision highp float;\n";
555
+ /**
556
+ * @param {string} shaderCode
557
+ */
558
+ const addDynamicMarkUniforms = (shaderCode) =>
559
+ shaderCode.replace(
560
+ "#pragma markUniforms",
561
+ dynamicMarkUniforms.join("\n")
562
+ );
563
+
564
+ extraHeaders = extraHeaders.map(addDynamicMarkUniforms);
565
+ vertexShader = addDynamicMarkUniforms(vertexShader);
566
+ fragmentShader = addDynamicMarkUniforms(fragmentShader);
418
567
 
419
568
  const vertexParts = [
420
569
  vertexPrecision,
@@ -422,7 +571,6 @@ export default class Mark {
422
571
  ...extraHeaders,
423
572
  GLSL_COMMON,
424
573
  GLSL_SCALES,
425
- domainUniformBlock,
426
574
  [...attributeCode].join("\n"),
427
575
  ...scaleCode,
428
576
  GLSL_SAMPLE_FACET,
@@ -431,6 +579,7 @@ export default class Mark {
431
579
  ];
432
580
 
433
581
  const fragmentParts = [
582
+ vertexPrecision,
434
583
  debugHeader,
435
584
  ...extraHeaders,
436
585
  GLSL_COMMON,
@@ -453,6 +602,9 @@ export default class Mark {
453
602
  /**
454
603
  * Check WebGL shader/program compilation/linking status and finalize
455
604
  * initialization.
605
+ *
606
+ * This is done as a separate step after all shader compilations have been
607
+ * initiated. The idea is to allow for parallel background compilation.
456
608
  */
457
609
  finalizeGraphicsInitialization() {
458
610
  const error = this.programStatus.getProgramErrors();
@@ -474,14 +626,6 @@ export default class Mark {
474
626
  );
475
627
  delete this.programStatus;
476
628
 
477
- if (this.domainUniforms.length) {
478
- this.domainUniformInfo = createUniformBlockInfo(
479
- this.gl,
480
- this.programInfo,
481
- "Domains"
482
- );
483
- }
484
-
485
629
  this.viewUniformInfo = createUniformBlockInfo(
486
630
  this.gl,
487
631
  this.programInfo,
@@ -496,14 +640,39 @@ export default class Mark {
496
640
 
497
641
  this.gl.useProgram(this.programInfo.program);
498
642
 
499
- this._setDatums();
500
-
501
643
  setUniforms(this.programInfo, {
502
644
  // left pos, left height, right pos, right height
503
645
  uSampleFacet: [0, 1, 0, 1],
504
646
  uTransitionOffset: 0.0,
505
647
  uZero: 0.0,
506
648
  });
649
+
650
+ for (const fn of this.#callAfterShaderCompilation) {
651
+ fn();
652
+ }
653
+ this.#callAfterShaderCompilation = undefined;
654
+ }
655
+
656
+ /**
657
+ * Sets a uniform in the Mark block. Requests a render from the animator.
658
+ *
659
+ * @protected
660
+ * @param {string} uniformName
661
+ * @returns {function(any):void}
662
+ */
663
+ createMarkUniformSetter(uniformName) {
664
+ const uniformSetter = this.markUniformInfo.setters[uniformName];
665
+ if (!uniformSetter) {
666
+ throw new Error(
667
+ `Uniform "${uniformName}" not found int the Mark block!`
668
+ );
669
+ }
670
+
671
+ return (value) => {
672
+ uniformSetter(value);
673
+ this.markUniformsAltered = true;
674
+ this.unitView.context.animator.requestRender();
675
+ };
507
676
  }
508
677
 
509
678
  /**
@@ -517,46 +686,32 @@ export default class Mark {
517
686
  * @param {T} propValue
518
687
  * @param {(x: Exclude<T, ExprRef>) => any} adjuster
519
688
  */
520
- registerMarkUniform(uniformName, propValue, adjuster = (x) => x) {
521
- const uniformSetter = this.markUniformInfo.setters[uniformName];
689
+ registerMarkUniformValue(uniformName, propValue, adjuster = (x) => x) {
690
+ const rawSetter = this.createMarkUniformSetter(uniformName);
691
+ const setter = (/** @type {any} */ value) => {
692
+ if (value == null) {
693
+ throw new Error(
694
+ `Trying to set null/undefined value for uniform: ${uniformName}${
695
+ isExprRef(propValue) ? `Expr: ${propValue.expr}` : ""
696
+ }`
697
+ );
698
+ }
699
+ rawSetter(value);
700
+ };
522
701
 
523
702
  if (isExprRef(propValue)) {
524
- const fn = this.unitView.context.paramBroker.createExpression(
703
+ const fn = this.unitView.paramMediator.createExpression(
525
704
  propValue.expr
526
705
  );
527
706
 
528
- const set = () => {
529
- uniformSetter(adjuster(fn(null)));
530
- this.markUniformsAltered = true;
531
- };
707
+ const set = () => setter(adjuster(fn(null)));
532
708
 
533
709
  // Register a listener ...
534
710
  fn.addListener(set);
535
711
  // ... and set the initial value
536
712
  set();
537
713
  } else {
538
- uniformSetter(
539
- adjuster(/** @type {Exclude<T, ExprRef>} */ (propValue))
540
- );
541
- this.markUniformsAltered = true;
542
- }
543
- }
544
-
545
- _setDatums() {
546
- for (const [channel, channelDef] of Object.entries(this.encoding)) {
547
- if (isDatumDef(channelDef)) {
548
- const encoder = this.encoders[channel];
549
-
550
- const datum = encoder.indexer
551
- ? encoder.indexer(channelDef.datum)
552
- : isHighPrecisionScale(encoder.scale.type)
553
- ? splitHighPrecision(+channelDef.datum)
554
- : +channelDef.datum;
555
-
556
- setUniforms(this.programInfo, {
557
- [ATTRIBUTE_PREFIX + channel]: datum,
558
- });
559
- }
714
+ setter(adjuster(/** @type {Exclude<T, ExprRef>} */ (propValue)));
560
715
  }
561
716
  }
562
717
 
@@ -707,47 +862,6 @@ export default class Mark {
707
862
  gl.useProgram(this.programInfo.program);
708
863
  });
709
864
 
710
- if (this.domainUniformInfo) {
711
- // TODO: Only update the domains that have changed
712
-
713
- for (const [uniform, setter] of Object.entries(
714
- this.domainUniformInfo.setters
715
- )) {
716
- // TODO: isChannel()
717
- const channel = /** @type {Channel} */ (
718
- uniform.substring(DOMAIN_PREFIX.length)
719
- );
720
-
721
- const channelDef = this.encoding[channel];
722
- const resolutionChannel =
723
- (isChannelDefWithScale(channelDef) &&
724
- channelDef.resolutionChannel) ||
725
- channel;
726
-
727
- if (isChannelWithScale(resolutionChannel)) {
728
- const scale = this.unitView
729
- .getScaleResolution(resolutionChannel)
730
- .getScale();
731
-
732
- ops.push(() => {
733
- const domain = isDiscrete(scale.type)
734
- ? [0, scale.domain().length]
735
- : scale.domain();
736
-
737
- setter(
738
- isHighPrecisionScale(scale.type)
739
- ? toHighPrecisionDomainUniform(domain)
740
- : domain
741
- );
742
- });
743
- }
744
- }
745
-
746
- ops.push(() =>
747
- setUniformBlock(gl, this.programInfo, this.domainUniformInfo)
748
- );
749
- }
750
-
751
865
  for (const [channel, channelDef] of Object.entries(this.encoding)) {
752
866
  if (isChannelDefWithScale(channelDef)) {
753
867
  const resolutionChannel =
@@ -888,7 +1002,7 @@ export default class Mark {
888
1002
  /** @type {function(import("../gl/dataToVertices.js").RangeEntry):void} rangeEntry */
889
1003
  let drawWithRangeEntry;
890
1004
 
891
- const scale = this.unitView.getScaleResolution("x")?.getScale();
1005
+ const scale = this.unitView.getScaleResolution("x")?.scale;
892
1006
  const continuous = scale && isContinuous(scale.type);
893
1007
  const domainStartOffset = ["index", "locus"].includes(scale?.type)
894
1008
  ? -1
@@ -1115,12 +1229,3 @@ class RangeMap extends InternMap {
1115
1229
  }
1116
1230
  }
1117
1231
  }
1118
-
1119
- // TODO: Find a better place for this function
1120
- /**
1121
- * @param {any} x
1122
- * @returns {x is import("../spec/mark.js").ExprRef}
1123
- */
1124
- export function isExprRef(x) {
1125
- return typeof x === "object" && "expr" in x && isString(x.expr);
1126
- }
@@ -1,2 +1,2 @@
1
- const shader = "uniform Mark{/***The stroke should only grow inwards,e.g,the diameter/outline is not affected by the stroke width.*Thus,a point that has a zero size has no visible stroke. This allows strokes to be used with*geometric zoom,etc.*/uniform bool uInwardStroke;uniform lowp float uMaxRelativePointDiameter;uniform mediump float uScaleFactor;uniform mediump float uMaxPointSize;uniform mediump float uZoomLevel;uniform highp float uSemanticThreshold;uniform mediump float uGradientStrength;};";
1
+ const shader = "layout(std140)uniform Mark{/***The stroke should only grow inwards,e.g,the diameter/outline is not affected by the stroke width.*Thus,a point that has a zero size has no visible stroke. This allows strokes to be used with*geometric zoom,etc.*/uniform bool uInwardStroke;uniform mediump float uScaleFactor;uniform mediump float uZoomLevel;uniform highp float uSemanticThreshold;uniform mediump float uGradientStrength;\n#pragma markUniforms\n};";
2
2
  export default shader;
@@ -1,11 +1,8 @@
1
1
  export default class PointMark extends Mark {
2
2
  sampledSemanticScores: Float32Array;
3
3
  _getGeometricScaleFactor(): number;
4
- /**
5
- * Returns the maximum size of the points in the data, before any scaling
6
- */
7
- _getMaxPointSize(): import("../spec/channel.js").Scalar;
8
4
  getSemanticThreshold(): number;
5
+ #private;
9
6
  }
10
7
  import Mark from "./mark.js";
11
8
  //# sourceMappingURL=point.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/marks/point.js"],"names":[],"mappings":"AAkBA;IAsGY,oCAMC;IA2CT,mCAQC;IAED;;OAEG;IACH,wDASC;IAED,+BAoBC;CAiDJ;iBA3PgB,WAAW"}
1
+ {"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/marks/point.js"],"names":[],"mappings":"AAmBA;IA0HY,oCAMC;IA+CT,mCAQC;IAED,+BAkBC;;CAgDJ;iBAnQgB,WAAW"}
@@ -10,6 +10,7 @@ import FRAGMENT_SHADER from "./point.fragment.glsl.js";
10
10
  import COMMON_SHADER from "./point.common.glsl.js";
11
11
 
12
12
  import Mark from "./mark.js";
13
+ import { isExprRef } from "../view/paramMediator.js";
13
14
  import { sampleIterable } from "../data/transforms/sample.js";
14
15
  import { fixFill, fixStroke } from "./markUtils.js";
15
16
 
@@ -17,6 +18,8 @@ import { fixFill, fixStroke } from "./markUtils.js";
17
18
  const defaultEncoding = {};
18
19
 
19
20
  export default class PointMark extends Mark {
21
+ #semanticZoomFraction = () => 0;
22
+
20
23
  /**
21
24
  * @param {import("../view/unitView.js").default} unitView
22
25
  */
@@ -45,6 +48,24 @@ export default class PointMark extends Mark {
45
48
  semanticZoomFraction: 0.02,
46
49
  })
47
50
  );
51
+
52
+ // TODO: This mess should be simplified
53
+ // TODO: createExpression should accept constant values or ExprRefs and allow
54
+ // easy registration of requestRender listeners
55
+ const szf = this.properties.semanticZoomFraction;
56
+ if (szf != null) {
57
+ if (isExprRef(szf)) {
58
+ const fn = this.unitView.paramMediator.createExpression(
59
+ szf.expr
60
+ );
61
+ fn.addListener(() =>
62
+ this.getContext().animator.requestRender()
63
+ );
64
+ this.#semanticZoomFraction = fn;
65
+ } else {
66
+ this.#semanticZoomFraction = () => szf;
67
+ }
68
+ }
48
69
  }
49
70
 
50
71
  getAttributes() {
@@ -143,11 +164,15 @@ export default class PointMark extends Mark {
143
164
 
144
165
  const props = this.properties;
145
166
 
146
- setBlockUniforms(this.markUniformInfo, {
147
- uInwardStroke: !!props.inwardStroke,
148
- uGradientStrength: +props.fillGradientStrength,
149
- uMaxRelativePointDiameter: 1 - 2 * props.sampleFacetPadding,
150
- });
167
+ this.registerMarkUniformValue(
168
+ "uInwardStroke",
169
+ props.inwardStroke,
170
+ (x) => !!x
171
+ );
172
+ this.registerMarkUniformValue(
173
+ "uGradientStrength",
174
+ props.fillGradientStrength
175
+ );
151
176
  }
152
177
 
153
178
  updateGraphicsData() {
@@ -177,27 +202,11 @@ export default class PointMark extends Mark {
177
202
  );
178
203
  }
179
204
 
180
- /**
181
- * Returns the maximum size of the points in the data, before any scaling
182
- */
183
- _getMaxPointSize() {
184
- const e = this.encoders.size;
185
- if (e.constant) {
186
- return e(null);
187
- } else {
188
- return /** @type {number[]} */ (e.scale.range()).reduce((a, b) =>
189
- Math.max(a, b)
190
- );
191
- }
192
- }
193
-
194
205
  getSemanticThreshold() {
195
206
  if (this.sampledSemanticScores) {
196
207
  const p = Math.max(
197
208
  0,
198
- 1 -
199
- this.properties.semanticZoomFraction *
200
- this.unitView.getZoomLevel()
209
+ 1 - this.#semanticZoomFraction() * this.unitView.getZoomLevel()
201
210
  );
202
211
  if (p <= 0) {
203
212
  // The sampled scores may be missing the min/max values
@@ -222,7 +231,6 @@ export default class PointMark extends Mark {
222
231
  ops.push(() => {
223
232
  // TODO: Use bindUniformBlock if none of the uniform has changed
224
233
  setBlockUniforms(this.markUniformInfo, {
225
- uMaxPointSize: this._getMaxPointSize(),
226
234
  uScaleFactor: this._getGeometricScaleFactor(),
227
235
  uSemanticThreshold: this.getSemanticThreshold(),
228
236
  });