@genome-spy/core 0.43.3 → 0.44.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 (64) hide show
  1. package/dist/bundle/index.es.js +5525 -5259
  2. package/dist/bundle/index.js +153 -104
  3. package/dist/schema.json +412 -43
  4. package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
  5. package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
  6. package/dist/src/data/sources/lazy/axisTickSource.js +2 -2
  7. package/dist/src/data/sources/lazy/bigWigSource.d.ts +6 -0
  8. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  9. package/dist/src/data/sources/lazy/bigWigSource.js +3 -5
  10. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  11. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  12. package/dist/src/data/sources/lazy/singleAxisLazySource.js +1 -1
  13. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +7 -12
  14. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  15. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +33 -29
  16. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  17. package/dist/src/encoder/encoder.d.ts.map +1 -1
  18. package/dist/src/encoder/encoder.js +16 -6
  19. package/dist/src/genomeSpy.d.ts +1 -0
  20. package/dist/src/genomeSpy.d.ts.map +1 -1
  21. package/dist/src/genomeSpy.js +108 -6
  22. package/dist/src/gl/glslScaleGenerator.d.ts +23 -3
  23. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  24. package/dist/src/gl/glslScaleGenerator.js +137 -42
  25. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  26. package/dist/src/gl/webGLHelper.js +5 -7
  27. package/dist/src/marks/link.common.glsl.js +2 -0
  28. package/dist/src/marks/link.d.ts.map +1 -1
  29. package/dist/src/marks/link.js +19 -9
  30. package/dist/src/marks/link.vertex.glsl.js +1 -1
  31. package/dist/src/marks/mark.d.ts +19 -17
  32. package/dist/src/marks/mark.d.ts.map +1 -1
  33. package/dist/src/marks/mark.js +181 -120
  34. package/dist/src/marks/point.common.glsl.js +1 -1
  35. package/dist/src/marks/rect.common.glsl.js +2 -0
  36. package/dist/src/marks/rect.d.ts.map +1 -1
  37. package/dist/src/marks/rect.js +12 -12
  38. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  39. package/dist/src/marks/rule.common.glsl.js +1 -1
  40. package/dist/src/marks/rule.js +2 -2
  41. package/dist/src/marks/text.common.glsl.js +1 -1
  42. package/dist/src/marks/text.js +2 -2
  43. package/dist/src/paramBroker.d.ts +19 -3
  44. package/dist/src/paramBroker.d.ts.map +1 -1
  45. package/dist/src/paramBroker.js +18 -2
  46. package/dist/src/spec/channel.d.ts +4 -3
  47. package/dist/src/spec/mark.d.ts +17 -25
  48. package/dist/src/spec/parameter.d.ts +123 -0
  49. package/dist/src/spec/root.d.ts +9 -0
  50. package/dist/src/spec/scale.d.ts +2 -1
  51. package/dist/src/spec/view.d.ts +1 -1
  52. package/dist/src/types/scaleResolutionApi.d.ts +7 -3
  53. package/dist/src/utils/expression.d.ts +2 -2
  54. package/dist/src/utils/expression.d.ts.map +1 -1
  55. package/dist/src/utils/expression.js +3 -3
  56. package/dist/src/view/axisView.js +3 -3
  57. package/dist/src/view/scaleResolution.d.ts +8 -18
  58. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  59. package/dist/src/view/scaleResolution.js +220 -126
  60. package/dist/src/view/scaleResolution.test.js +7 -7
  61. package/dist/src/view/unitView.d.ts.map +1 -1
  62. package/dist/src/view/unitView.js +10 -3
  63. package/dist/src/view/view.js +2 -2
  64. package/package.json +2 -2
@@ -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,7 +39,6 @@ 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
43
  import { isString } from "vega-util";
44
44
 
@@ -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
  }
@@ -227,20 +234,22 @@ export default class Mark {
227
234
  /** @type {(property: string) => ValueDef} */
228
235
  const propToValueDef = (property) => {
229
236
  const value =
230
- this.properties[/** @type {keyof MarkConfig} */ (property)];
231
- return isScalar(value) && { value };
237
+ this.properties[/** @type {keyof MarkProps} */ (property)];
238
+ return isScalar(value) || isExprRef(value)
239
+ ? { value }
240
+ : undefined;
232
241
  };
233
242
 
234
243
  const propertyValues = Object.fromEntries(
235
244
  this.getSupportedChannels()
236
245
  .map(
237
246
  (channel) =>
238
- /** @type {[Channel, ValueDef]} */ ([
247
+ /** @type {[Channel, ValueDef] } */ ([
239
248
  channel,
240
249
  propToValueDef(channel),
241
250
  ])
242
251
  )
243
- .filter((entry) => entry[1].value !== undefined)
252
+ .filter((entry) => isValueDef(entry[1]))
244
253
  );
245
254
 
246
255
  const encoding = this.fixEncoding({
@@ -326,16 +335,13 @@ export default class Mark {
326
335
  * @param {string[]} [extraHeaders]
327
336
  * @protected
328
337
  */
338
+ // eslint-disable-next-line complexity
329
339
  createAndLinkShaders(vertexShader, fragmentShader, extraHeaders = []) {
330
340
  const attributes = this.getAttributes();
331
341
 
332
342
  // For debugging
333
343
  const debugHeader = "// view: " + this.unitView.getPathString();
334
344
 
335
- // TODO: This is a temporary variable, don't store it in the mark object
336
- /** @type {string[]} */
337
- this.domainUniforms = [];
338
-
339
345
  /** @type {string[]} */
340
346
  let scaleCode = [];
341
347
 
@@ -353,6 +359,9 @@ export default class Mark {
353
359
  extraHeaders.push(`#define ${sampleFacetMode}`);
354
360
  }
355
361
 
362
+ /** @type {string[]} */
363
+ const dynamicMarkUniforms = [];
364
+
356
365
  for (const attribute of attributes) {
357
366
  /** @type {Channel} */
358
367
  let channel;
@@ -363,24 +372,40 @@ export default class Mark {
363
372
  }
364
373
 
365
374
  const channelDef = this.encoding[channel];
366
-
367
375
  if (!channelDef) {
368
376
  continue;
369
377
  }
370
378
 
371
379
  if (isValueDef(channelDef)) {
372
- scaleCode.push(generateValueGlsl(channel, channelDef.value));
380
+ if (isExprRef(channelDef.value)) {
381
+ // An expression that evaluates to a value
382
+ const { uniformName, uniformGlsl, scaleGlsl, adjuster } =
383
+ generateDynamicValueGlslAndUniform(channel);
384
+ scaleCode.push(scaleGlsl);
385
+ dynamicMarkUniforms.push(uniformGlsl);
386
+
387
+ this.#callAfterShaderCompilation.push(() => {
388
+ this.registerMarkUniformValue(
389
+ uniformName,
390
+ channelDef.value,
391
+ adjuster
392
+ );
393
+ });
394
+ } else {
395
+ // A constant value
396
+ scaleCode.push(
397
+ generateConstantValueGlsl(channel, channelDef.value)
398
+ );
399
+ }
373
400
  } else {
374
401
  const resolutionChannel =
375
402
  (isChannelDefWithScale(channelDef) &&
376
403
  channelDef.resolutionChannel) ||
377
404
  channel;
378
405
 
379
- const scale = isChannelWithScale(resolutionChannel)
380
- ? this.unitView
381
- .getScaleResolution(resolutionChannel)
382
- .getScale()
383
- : scaleNull();
406
+ const scaleResolution = isChannelWithScale(resolutionChannel)
407
+ ? this.unitView.getScaleResolution(resolutionChannel)
408
+ : null;
384
409
 
385
410
  // Channels that share the same quantitative field
386
411
  // TODO: It should be ok to share a categorical field if the channels
@@ -391,7 +416,7 @@ export default class Mark {
391
416
 
392
417
  const generated = generateScaleGlsl(
393
418
  channel,
394
- scale,
419
+ scaleResolution,
395
420
  channelDef,
396
421
  sharedChannels?.includes(channel)
397
422
  ? sharedChannels
@@ -399,22 +424,103 @@ export default class Mark {
399
424
  );
400
425
 
401
426
  scaleCode.push(generated.glsl);
402
- if (generated.domainUniform) {
403
- this.domainUniforms.push(generated.domainUniform);
427
+ dynamicMarkUniforms.push(generated.domainUniform);
428
+ dynamicMarkUniforms.push(generated.rangeUniform);
429
+ attributeCode.add(generated.attributeGlsl);
430
+
431
+ if (generated.rangeUniform) {
432
+ this.#callAfterShaderCompilation.push(() => {
433
+ const rangeSetter = this.createMarkUniformSetter(
434
+ generated.rangeName
435
+ );
436
+
437
+ const set = () =>
438
+ rangeSetter(
439
+ getRangeForGlsl(scaleResolution.scale, channel)
440
+ );
441
+ scaleResolution.addEventListener("range", set);
442
+
443
+ // Initial value
444
+ set();
445
+ });
404
446
  }
405
- if (generated.attributeGlsl) {
406
- attributeCode.add(generated.attributeGlsl);
447
+
448
+ if (generated.markUniformGlsl) {
449
+ if (!isDatumDef(channelDef)) {
450
+ throw new Error("Bug!");
451
+ }
452
+
453
+ const encoder = this.encoders[channel];
454
+
455
+ const indexer = encoder.indexer;
456
+ const hp = isHighPrecisionScale(encoder.scale.type);
457
+ const largeHp = hp && isLargeGenome(encoder.scale.domain());
458
+
459
+ /**
460
+ * Discrete variables both numeric and strings must be "indexed",
461
+ * 64 bit floats must be converted to vec2.
462
+ * 32 bit continuous variables go to GPU as is.
463
+ *
464
+ * @type {function(import("../spec/channel.js").Scalar):(number | number[])}
465
+ */
466
+ const adjuster = indexer
467
+ ? indexer
468
+ : largeHp
469
+ ? splitLargeHighPrecision
470
+ : (d) => +d;
471
+
472
+ dynamicMarkUniforms.push(generated.markUniformGlsl);
473
+
474
+ this.#callAfterShaderCompilation.push(() => {
475
+ this.registerMarkUniformValue(
476
+ generated.attributeName,
477
+ channelDef.datum,
478
+ adjuster
479
+ );
480
+ });
481
+ }
482
+
483
+ if (generated.domainUniform) {
484
+ this.#callAfterShaderCompilation.push(() => {
485
+ const domainSetter = this.createMarkUniformSetter(
486
+ generated.domainUniformName
487
+ );
488
+ const scale = scaleResolution.scale;
489
+ const set = () => {
490
+ const domain = isDiscrete(scale.type)
491
+ ? [0, scale.domain().length]
492
+ : scale.domain();
493
+
494
+ domainSetter(
495
+ isHighPrecisionScale(scale.type)
496
+ ? toHighPrecisionDomainUniform(domain)
497
+ : domain
498
+ );
499
+ };
500
+
501
+ scaleResolution.addEventListener("domain", set);
502
+
503
+ // Initial value
504
+ set();
505
+ });
407
506
  }
408
507
  }
409
508
  }
410
509
 
411
- const domainUniformBlock = this.domainUniforms.length
412
- ? "layout(std140) uniform Domains {\n" +
413
- this.domainUniforms.map((u) => ` ${u}\n`).join("") +
414
- "};\n\n"
415
- : "";
510
+ const vertexPrecision = "precision highp float;\nprecision highp int;";
511
+
512
+ /**
513
+ * @param {string} shaderCode
514
+ */
515
+ const addDynamicMarkUniforms = (shaderCode) =>
516
+ shaderCode.replace(
517
+ "#pragma markUniforms",
518
+ dynamicMarkUniforms.join("\n")
519
+ );
416
520
 
417
- const vertexPrecision = "precision highp float;\n";
521
+ extraHeaders = extraHeaders.map(addDynamicMarkUniforms);
522
+ vertexShader = addDynamicMarkUniforms(vertexShader);
523
+ fragmentShader = addDynamicMarkUniforms(fragmentShader);
418
524
 
419
525
  const vertexParts = [
420
526
  vertexPrecision,
@@ -422,7 +528,6 @@ export default class Mark {
422
528
  ...extraHeaders,
423
529
  GLSL_COMMON,
424
530
  GLSL_SCALES,
425
- domainUniformBlock,
426
531
  [...attributeCode].join("\n"),
427
532
  ...scaleCode,
428
533
  GLSL_SAMPLE_FACET,
@@ -431,6 +536,7 @@ export default class Mark {
431
536
  ];
432
537
 
433
538
  const fragmentParts = [
539
+ vertexPrecision,
434
540
  debugHeader,
435
541
  ...extraHeaders,
436
542
  GLSL_COMMON,
@@ -453,6 +559,9 @@ export default class Mark {
453
559
  /**
454
560
  * Check WebGL shader/program compilation/linking status and finalize
455
561
  * initialization.
562
+ *
563
+ * This is done as a separate step after all shader compilations have been
564
+ * initiated. The idea is to allow for parallel background compilation.
456
565
  */
457
566
  finalizeGraphicsInitialization() {
458
567
  const error = this.programStatus.getProgramErrors();
@@ -474,14 +583,6 @@ export default class Mark {
474
583
  );
475
584
  delete this.programStatus;
476
585
 
477
- if (this.domainUniforms.length) {
478
- this.domainUniformInfo = createUniformBlockInfo(
479
- this.gl,
480
- this.programInfo,
481
- "Domains"
482
- );
483
- }
484
-
485
586
  this.viewUniformInfo = createUniformBlockInfo(
486
587
  this.gl,
487
588
  this.programInfo,
@@ -496,14 +597,39 @@ export default class Mark {
496
597
 
497
598
  this.gl.useProgram(this.programInfo.program);
498
599
 
499
- this._setDatums();
500
-
501
600
  setUniforms(this.programInfo, {
502
601
  // left pos, left height, right pos, right height
503
602
  uSampleFacet: [0, 1, 0, 1],
504
603
  uTransitionOffset: 0.0,
505
604
  uZero: 0.0,
506
605
  });
606
+
607
+ for (const fn of this.#callAfterShaderCompilation) {
608
+ fn();
609
+ }
610
+ this.#callAfterShaderCompilation = undefined;
611
+ }
612
+
613
+ /**
614
+ * Sets a uniform in the Mark block. Requests a render from the animator.
615
+ *
616
+ * @protected
617
+ * @param {string} uniformName
618
+ * @returns {function(any):void}
619
+ */
620
+ createMarkUniformSetter(uniformName) {
621
+ const uniformSetter = this.markUniformInfo.setters[uniformName];
622
+ if (!uniformSetter) {
623
+ throw new Error(
624
+ `Uniform "${uniformName}" not found int the Mark block!`
625
+ );
626
+ }
627
+
628
+ return (value) => {
629
+ uniformSetter(value);
630
+ this.markUniformsAltered = true;
631
+ this.unitView.context.animator.requestRender();
632
+ };
507
633
  }
508
634
 
509
635
  /**
@@ -517,46 +643,22 @@ export default class Mark {
517
643
  * @param {T} propValue
518
644
  * @param {(x: Exclude<T, ExprRef>) => any} adjuster
519
645
  */
520
- registerMarkUniform(uniformName, propValue, adjuster = (x) => x) {
521
- const uniformSetter = this.markUniformInfo.setters[uniformName];
646
+ registerMarkUniformValue(uniformName, propValue, adjuster = (x) => x) {
647
+ const setter = this.createMarkUniformSetter(uniformName);
522
648
 
523
649
  if (isExprRef(propValue)) {
524
650
  const fn = this.unitView.context.paramBroker.createExpression(
525
651
  propValue.expr
526
652
  );
527
653
 
528
- const set = () => {
529
- uniformSetter(adjuster(fn(null)));
530
- this.markUniformsAltered = true;
531
- };
654
+ const set = () => setter(adjuster(fn(null)));
532
655
 
533
656
  // Register a listener ...
534
657
  fn.addListener(set);
535
658
  // ... and set the initial value
536
659
  set();
537
660
  } 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
- }
661
+ setter(adjuster(/** @type {Exclude<T, ExprRef>} */ (propValue)));
560
662
  }
561
663
  }
562
664
 
@@ -707,47 +809,6 @@ export default class Mark {
707
809
  gl.useProgram(this.programInfo.program);
708
810
  });
709
811
 
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
812
  for (const [channel, channelDef] of Object.entries(this.encoding)) {
752
813
  if (isChannelDefWithScale(channelDef)) {
753
814
  const resolutionChannel =
@@ -888,7 +949,7 @@ export default class Mark {
888
949
  /** @type {function(import("../gl/dataToVertices.js").RangeEntry):void} rangeEntry */
889
950
  let drawWithRangeEntry;
890
951
 
891
- const scale = this.unitView.getScaleResolution("x")?.getScale();
952
+ const scale = this.unitView.getScaleResolution("x")?.scale;
892
953
  const continuous = scale && isContinuous(scale.type);
893
954
  const domainStartOffset = ["index", "locus"].includes(scale?.type)
894
955
  ? -1
@@ -1119,8 +1180,8 @@ class RangeMap extends InternMap {
1119
1180
  // TODO: Find a better place for this function
1120
1181
  /**
1121
1182
  * @param {any} x
1122
- * @returns {x is import("../spec/mark.js").ExprRef}
1183
+ * @returns {x is import("../spec/parameter.js").ExprRef}
1123
1184
  */
1124
1185
  export function isExprRef(x) {
1125
- return typeof x === "object" && "expr" in x && isString(x.expr);
1186
+ return typeof x == "object" && x != null && "expr" in x && isString(x.expr);
1126
1187
  }
@@ -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 lowp float uMaxRelativePointDiameter;uniform mediump float uScaleFactor;uniform mediump float uMaxPointSize;uniform mediump float uZoomLevel;uniform highp float uSemanticThreshold;uniform mediump float uGradientStrength;\n#pragma markUniforms\n};";
2
2
  export default shader;
@@ -0,0 +1,2 @@
1
+ const shader = "layout(std140)uniform Mark{uniform float uMinWidth;uniform float uMinHeight;uniform float uMinOpacity;uniform float uCornerRadiusTopRight;uniform float uCornerRadiusBottomRight;uniform float uCornerRadiusTopLeft;uniform float uCornerRadiusBottomLeft;\n#pragma markUniforms\n};";
2
+ export default shader;
@@ -1 +1 @@
1
- {"version":3,"file":"rect.d.ts","sourceRoot":"","sources":["../../../src/marks/rect.js"],"names":[],"mappings":"AAYA;IA0NI;;;;;;;;;;OAUG;IACH,8BALW,GAAG,KACH,OAAO,oBAAoB,EAAE,MAAM,GACjC,GAAG,CAyBf;;CACJ;iBAnQgB,WAAW"}
1
+ {"version":3,"file":"rect.d.ts","sourceRoot":"","sources":["../../../src/marks/rect.js"],"names":[],"mappings":"AAaA;IAyNI;;;;;;;;;;OAUG;IACH,8BALW,GAAG,KACH,OAAO,oBAAoB,EAAE,MAAM,GACjC,GAAG,CAyBf;;CACJ;iBAlQgB,WAAW"}
@@ -1,6 +1,7 @@
1
1
  import { drawBufferInfo, setBuffersAndAttributes } from "twgl.js";
2
2
  import VERTEX_SHADER from "./rect.vertex.glsl.js";
3
3
  import FRAGMENT_SHADER from "./rect.fragment.glsl.js";
4
+ import COMMON_SHADER from "./rect.common.glsl.js";
4
5
  import { RectVertexBuilder } from "../gl/dataToVertices.js";
5
6
 
6
7
  import Mark from "./mark.js";
@@ -140,11 +141,10 @@ export default class RectMark extends Mark {
140
141
  defines.push("STROKED");
141
142
  }
142
143
 
143
- this.createAndLinkShaders(
144
- VERTEX_SHADER,
145
- FRAGMENT_SHADER,
146
- defines.map((d) => "#define " + d)
147
- );
144
+ this.createAndLinkShaders(VERTEX_SHADER, FRAGMENT_SHADER, [
145
+ COMMON_SHADER,
146
+ ...defines.map((d) => "#define " + d),
147
+ ]);
148
148
  }
149
149
 
150
150
  finalizeGraphicsInitialization() {
@@ -154,22 +154,22 @@ export default class RectMark extends Mark {
154
154
 
155
155
  const props = this.properties;
156
156
 
157
- this.registerMarkUniform("uMinWidth", props.minWidth);
158
- this.registerMarkUniform("uMinHeight", props.minHeight);
159
- this.registerMarkUniform("uMinOpacity", props.minOpacity);
160
- this.registerMarkUniform(
157
+ this.registerMarkUniformValue("uMinWidth", props.minWidth);
158
+ this.registerMarkUniformValue("uMinHeight", props.minHeight);
159
+ this.registerMarkUniformValue("uMinOpacity", props.minOpacity);
160
+ this.registerMarkUniformValue(
161
161
  "uCornerRadiusTopRight",
162
162
  props.cornerRadiusTopRight ?? props.cornerRadius ?? 0
163
163
  );
164
- this.registerMarkUniform(
164
+ this.registerMarkUniformValue(
165
165
  "uCornerRadiusBottomRight",
166
166
  props.cornerRadiusBottomRight ?? props.cornerRadius ?? 0
167
167
  );
168
- this.registerMarkUniform(
168
+ this.registerMarkUniformValue(
169
169
  "uCornerRadiusTopLeft",
170
170
  props.cornerRadiusTopLeft ?? props.cornerRadius ?? 0
171
171
  );
172
- this.registerMarkUniform(
172
+ this.registerMarkUniformValue(
173
173
  "uCornerRadiusBottomLeft",
174
174
  props.cornerRadiusBottomLeft ?? props.cornerRadius ?? 0
175
175
  );
@@ -1,2 +1,2 @@
1
- const shader = "uniform Mark{uniform float uMinWidth;uniform float uMinHeight;uniform float uMinOpacity;uniform float uCornerRadiusTopRight;uniform float uCornerRadiusBottomRight;uniform float uCornerRadiusTopLeft;uniform float uCornerRadiusBottomLeft;};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,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();}";
2
2
  export default shader;
@@ -1,2 +1,2 @@
1
- const shader = "uniform Mark{uniform mediump float uMinLength;uniform mediump float uDashTextureSize;uniform lowp int uStrokeCap;uniform mediump float uStrokeDashOffset;};";
1
+ const shader = "layout(std140)uniform Mark{uniform mediump float uMinLength;uniform mediump float uDashTextureSize;uniform lowp int uStrokeCap;uniform mediump float uStrokeDashOffset;\n#pragma markUniforms\n};";
2
2
  export default shader;
@@ -150,8 +150,8 @@ export default class RuleMark extends Mark {
150
150
 
151
151
  const props = this.properties;
152
152
 
153
- this.registerMarkUniform("uMinLength", props.minLength);
154
- this.registerMarkUniform(
153
+ this.registerMarkUniformValue("uMinLength", props.minLength);
154
+ this.registerMarkUniformValue(
155
155
  "uStrokeCap",
156
156
  props.strokeCap ?? "butt",
157
157
  (cap) => ["butt", "square", "round"].indexOf(cap)
@@ -1,2 +1,2 @@
1
- const shader = "uniform Mark{uniform mediump float uSdfNumerator;uniform mediump vec2 uD;uniform mediump vec4 uViewportEdgeFadeWidth;uniform mediump vec4 uViewportEdgeFadeDistance;uniform bool uSqueeze;uniform bool uLogoLetter;uniform lowp ivec2 uAlign;uniform mediump float uPaddingX;uniform bool uFlushX;uniform mediump float uPaddingY;uniform bool uFlushY;};";
1
+ const shader = "layout(std140)uniform Mark{uniform mediump float uSdfNumerator;uniform mediump vec2 uD;uniform mediump vec4 uViewportEdgeFadeWidth;uniform mediump vec4 uViewportEdgeFadeDistance;uniform bool uSqueeze;uniform bool uLogoLetter;uniform lowp ivec2 uAlign;uniform mediump float uPaddingX;uniform bool uFlushX;uniform mediump float uPaddingY;uniform bool uFlushY;\n#pragma markUniforms\n};";
2
2
  export default shader;
@@ -160,9 +160,9 @@ export default class TextMark extends Mark {
160
160
 
161
161
  const props = this.properties;
162
162
 
163
- this.registerMarkUniform(
163
+ this.registerMarkUniformValue(
164
164
  "uSdfNumerator",
165
- /** @type {import("../spec/mark.js").ExprRef | number} */
165
+ /** @type {import("../spec/parameter.js").ExprRef | number} */
166
166
  ({ expr: "devicePixelRatio" }),
167
167
  (dpr) => {
168
168
  let q = 0.35; // TODO: Ensure that this makes sense. Now chosen by trial & error
@@ -9,6 +9,8 @@
9
9
  * - Calling observers when a parameter changes
10
10
  * - Somehow saving parameter "state" (in bookmarks)
11
11
  * - Maybe something else
12
+ *
13
+ * @typedef {import("./utils/expression.js").ExpressionFunction & { addListener: (listener: () => void) => void, invalidate: () => void}} ExprRefFunction
12
14
  */
13
15
  export default class ParamBroker {
14
16
  /**
@@ -22,9 +24,23 @@ export default class ParamBroker {
22
24
  *
23
25
  * @param {string} expr
24
26
  */
25
- createExpression(expr: string): ((x: object) => any) & import("./utils/expression.js").ExpressionProps & {
26
- addListener: (listener: () => void) => void;
27
- };
27
+ createExpression(expr: string): ExprRefFunction;
28
28
  #private;
29
29
  }
30
+ /**
31
+ * A class that manages parameters and expressions. Still a work in progress.
32
+ *
33
+ * TODO: Write tests for this class.
34
+ *
35
+ * This should eventually handle the following:
36
+ * - Parameter registration
37
+ * - Dependency tracking
38
+ * - Calling observers when a parameter changes
39
+ * - Somehow saving parameter "state" (in bookmarks)
40
+ * - Maybe something else
41
+ */
42
+ export type ExprRefFunction = ((datum: object) => any) & import("./utils/expression.js").ExpressionProps & {
43
+ addListener: (listener: () => void) => void;
44
+ invalidate: () => void;
45
+ };
30
46
  //# sourceMappingURL=paramBroker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paramBroker.d.ts","sourceRoot":"","sources":["../../src/paramBroker.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH;IAyBI;;;;OAIG;IACH,0BAHW,MAAM,WACI,GAAG,KAAK,IAAI,CAqBhC;IAID;;;;OAIG;IACH,uBAFW,MAAM;gCAG6E,MAAM,IAAI,KAAK,IAAI;MA0BhH;;CACJ"}
1
+ {"version":3,"file":"paramBroker.d.ts","sourceRoot":"","sources":["../../src/paramBroker.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH;IAyBI;;;;OAIG;IACH,0BAHW,MAAM,WACI,GAAG,KAAK,IAAI,CAqBhC;IAID;;;;OAIG;IACH,uBAFW,MAAM,mBA2ChB;;CACJ;;;;;;;;;;;;;;4BAtG2F,MAAM,IAAI,KAAK,IAAI;gBAAc,MAAM,IAAI"}