@genome-spy/core 0.74.0 → 0.75.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 (79) hide show
  1. package/dist/bundle/index.es.js +4660 -4468
  2. package/dist/bundle/index.js +81 -81
  3. package/dist/schema.json +220 -12
  4. package/dist/src/data/sources/dataUtils.d.ts +25 -0
  5. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  6. package/dist/src/data/sources/dataUtils.js +23 -0
  7. package/dist/src/data/sources/inlineSource.js +2 -2
  8. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  9. package/dist/src/data/sources/urlSource.js +8 -3
  10. package/dist/src/encoder/encoder.d.ts +2 -2
  11. package/dist/src/encoder/encoder.d.ts.map +1 -1
  12. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  13. package/dist/src/genome/scaleLocus.js +8 -3
  14. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  15. package/dist/src/genomeSpy/interactionController.js +91 -51
  16. package/dist/src/gl/dataToVertices.d.ts +12 -14
  17. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  18. package/dist/src/gl/dataToVertices.js +116 -95
  19. package/dist/src/gl/glslScaleGenerator.d.ts +3 -0
  20. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  21. package/dist/src/gl/glslScaleGenerator.js +10 -8
  22. package/dist/src/gl/vertexRangeIndex.d.ts +23 -0
  23. package/dist/src/gl/vertexRangeIndex.d.ts.map +1 -0
  24. package/dist/src/gl/vertexRangeIndex.js +150 -0
  25. package/dist/src/marks/mark.d.ts +1 -1
  26. package/dist/src/paramRuntime/expressionCompiler.d.ts +2 -1
  27. package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -1
  28. package/dist/src/paramRuntime/expressionCompiler.js +3 -2
  29. package/dist/src/paramRuntime/expressionRef.d.ts +4 -1
  30. package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -1
  31. package/dist/src/paramRuntime/expressionRef.js +10 -3
  32. package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -1
  33. package/dist/src/paramRuntime/graphRuntime.js +15 -6
  34. package/dist/src/paramRuntime/paramRuntime.d.ts +8 -2
  35. package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -1
  36. package/dist/src/paramRuntime/paramRuntime.js +10 -5
  37. package/dist/src/paramRuntime/types.d.ts +1 -0
  38. package/dist/src/paramRuntime/types.d.ts.map +1 -1
  39. package/dist/src/paramRuntime/types.js +1 -0
  40. package/dist/src/paramRuntime/viewParamRuntime.d.ts +5 -4
  41. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -1
  42. package/dist/src/paramRuntime/viewParamRuntime.js +17 -6
  43. package/dist/src/scale/scale.d.ts.map +1 -1
  44. package/dist/src/scale/scale.js +1 -0
  45. package/dist/src/scales/domainPlanner.d.ts +57 -11
  46. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  47. package/dist/src/scales/domainPlanner.js +182 -83
  48. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  49. package/dist/src/scales/scaleInstanceManager.js +7 -2
  50. package/dist/src/scales/scalePropsResolver.d.ts +3 -3
  51. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
  52. package/dist/src/scales/scalePropsResolver.js +28 -5
  53. package/dist/src/scales/scaleResolution.d.ts +12 -1
  54. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  55. package/dist/src/scales/scaleResolution.js +171 -18
  56. package/dist/src/screenshotExport.d.ts +23 -0
  57. package/dist/src/screenshotExport.d.ts.map +1 -0
  58. package/dist/src/screenshotExport.js +44 -0
  59. package/dist/src/screenshotHarness.d.ts.map +1 -1
  60. package/dist/src/screenshotHarness.js +26 -24
  61. package/dist/src/spec/axis.d.ts +2 -2
  62. package/dist/src/spec/channel.d.ts +4 -4
  63. package/dist/src/spec/data.d.ts +12 -0
  64. package/dist/src/spec/scale.d.ts +13 -1
  65. package/dist/src/utils/expression.d.ts +16 -8
  66. package/dist/src/utils/expression.d.ts.map +1 -1
  67. package/dist/src/utils/expression.js +291 -11
  68. package/dist/src/view/flowBuilder.d.ts +1 -1
  69. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  70. package/dist/src/view/flowBuilder.js +11 -7
  71. package/dist/src/view/resolutionPlanner.d.ts +9 -0
  72. package/dist/src/view/resolutionPlanner.d.ts.map +1 -0
  73. package/dist/src/view/resolutionPlanner.js +302 -0
  74. package/dist/src/view/unitView.d.ts +1 -1
  75. package/dist/src/view/unitView.d.ts.map +1 -1
  76. package/dist/src/view/unitView.js +5 -152
  77. package/dist/src/view/view.d.ts.map +1 -1
  78. package/dist/src/view/view.js +2 -1
  79. package/package.json +2 -2
@@ -28,11 +28,13 @@ import {
28
28
  isPrimaryPositionalChannel,
29
29
  isSecondaryChannel,
30
30
  } from "../encoder/encoder.js";
31
+ import { isExprRef } from "../paramRuntime/paramUtils.js";
31
32
  import { NominalDomain } from "../utils/domainArray.js";
32
33
  import { shallowArrayEquals } from "../utils/arrayUtils.js";
33
34
  import createIndexer from "../utils/indexer.js";
34
35
  import { getCachedOrCall, invalidate } from "../utils/propertyCacher.js";
35
36
  import { resolveUrl } from "../utils/url.js";
37
+ import { orderResolutionMembers } from "./resolutionMemberOrder.js";
36
38
  import {
37
39
  findIntervalSelectionBindingOwners,
38
40
  normalizeIntervalForSelection,
@@ -100,6 +102,9 @@ export default class ScaleResolution {
100
102
  /** @type {Set<ScaleResolutionMember>} */
101
103
  #dataDomainMembers = new Set();
102
104
 
105
+ /** @type {ScaleResolutionMember[] | undefined} */
106
+ #orderedMembers;
107
+
103
108
  /**
104
109
  * @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
105
110
  */
@@ -127,15 +132,28 @@ export default class ScaleResolution {
127
132
 
128
133
  #selectionReverseSyncSuppressionDepth = 0;
129
134
 
135
+ /** @type {(() => void)[]} */
136
+ #configuredDomainExprUnsubscribers = [];
137
+
130
138
  #ignoreSelectionInitial = false;
131
139
 
132
140
  /** @type {[number, number] | null | undefined} */
133
141
  #lastLinkedSelectionInterval = undefined;
134
142
 
143
+ /** @type {import("../view/view.js").default | undefined} */
144
+ #hostView;
145
+
146
+ #resolvingScaleProps = 0;
147
+
148
+ #memberRegistrationBatchDepth = 0;
149
+
150
+ #membersDirty = false;
151
+
135
152
  /**
136
153
  * @param {Channel} channel
154
+ * @param {import("../view/view.js").default} [hostView]
137
155
  */
138
- constructor(channel) {
156
+ constructor(channel, hostView) {
139
157
  this.channel = channel;
140
158
  /** @type {import("../spec/channel.js").Type} Data type (quantitative, nominal, etc...) */
141
159
  this.type = null;
@@ -143,8 +161,10 @@ export default class ScaleResolution {
143
161
  /** @type {string} An optional unique identifier for the scale */
144
162
  this.name = undefined;
145
163
 
164
+ this.#hostView = hostView;
165
+
146
166
  this.#domainAggregator = new DomainPlanner({
147
- getMembers: () => this.#getActiveMembers(),
167
+ getActiveMembers: () => this.#getActiveMembers(),
148
168
  getAllMembers: () => this.#members,
149
169
  getDataMembers: () =>
150
170
  this.#getActiveMembers(this.#dataDomainMembers),
@@ -154,7 +174,7 @@ export default class ScaleResolution {
154
174
  });
155
175
 
156
176
  this.#scaleManager = new ScaleInstanceManager({
157
- getParamRuntime: () => this.#firstMemberView.paramRuntime,
177
+ getParamRuntime: () => this.#resolutionView.paramRuntime,
158
178
  onRangeChange: () => this.#notifyListeners("range"),
159
179
  onDomainChange: () => this.#notifyListeners("domain"),
160
180
  getGenomeStore: () => this.#viewContext.genomeStore,
@@ -182,6 +202,10 @@ export default class ScaleResolution {
182
202
  return first.view;
183
203
  }
184
204
 
205
+ get #resolutionView() {
206
+ return this.#hostView ?? this.#firstMemberView;
207
+ }
208
+
185
209
  /**
186
210
  * @param {Set<ScaleResolutionMember>} [members]
187
211
  */
@@ -206,7 +230,7 @@ export default class ScaleResolution {
206
230
  }
207
231
 
208
232
  get #viewContext() {
209
- return this.#firstMemberView.context;
233
+ return this.#resolutionView.context;
210
234
  }
211
235
 
212
236
  get zoomExtent() {
@@ -372,9 +396,15 @@ export default class ScaleResolution {
372
396
  * @returns {boolean}
373
397
  */
374
398
  #hasConfiguredDomain() {
375
- return this.#domainAggregator.hasConfiguredDomain({
376
- includeSelectionInitial: this.#shouldIncludeSelectionInitial(),
377
- });
399
+ for (const member of this.#members) {
400
+ if (
401
+ member.contributesToDomain &&
402
+ member.channelDef.scale?.domain !== undefined
403
+ ) {
404
+ return true;
405
+ }
406
+ }
407
+ return false;
378
408
  }
379
409
 
380
410
  /**
@@ -484,9 +514,53 @@ export default class ScaleResolution {
484
514
  if (member.contributesToDomain) {
485
515
  this.#dataDomainMembers.add(member);
486
516
  }
517
+ return member;
518
+ }
519
+
520
+ #syncMembers() {
521
+ this.#membersDirty = false;
522
+ this.#invalidateOrderedMembers();
487
523
  this.#invalidateConfiguredDomain();
488
524
  this.#refreshSelectionDomainParamSubscriptions();
489
- return member;
525
+ this.#refreshConfiguredDomainExprSubscriptions();
526
+ }
527
+
528
+ #markMembersDirty() {
529
+ if (this.#memberRegistrationBatchDepth > 0) {
530
+ this.#membersDirty = true;
531
+ } else {
532
+ this.#syncMembers();
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Executes a group of member registrations without refreshing derived
538
+ * membership state until the callback completes.
539
+ *
540
+ * @template T
541
+ * @param {Iterable<ScaleResolution>} resolutions
542
+ * @param {() => T} callback
543
+ * @returns {T}
544
+ */
545
+ static registerInBatch(resolutions, callback) {
546
+ const batchedResolutions = Array.from(resolutions);
547
+ for (const resolution of batchedResolutions) {
548
+ resolution.#memberRegistrationBatchDepth++;
549
+ }
550
+
551
+ try {
552
+ return callback();
553
+ } finally {
554
+ for (const resolution of batchedResolutions) {
555
+ resolution.#memberRegistrationBatchDepth--;
556
+ if (
557
+ resolution.#memberRegistrationBatchDepth === 0 &&
558
+ resolution.#membersDirty
559
+ ) {
560
+ resolution.#syncMembers();
561
+ }
562
+ }
563
+ }
490
564
  }
491
565
 
492
566
  /**
@@ -495,12 +569,12 @@ export default class ScaleResolution {
495
569
  */
496
570
  registerMember(member) {
497
571
  const registeredMember = this.#addMember(member);
572
+ this.#markMembersDirty();
498
573
  return () => {
499
574
  const removed = this.#members.delete(registeredMember);
500
575
  if (removed) {
501
576
  this.#dataDomainMembers.delete(registeredMember);
502
- this.#invalidateConfiguredDomain();
503
- this.#refreshSelectionDomainParamSubscriptions();
577
+ this.#markMembersDirty();
504
578
  }
505
579
  return removed && this.#members.size === 0;
506
580
  };
@@ -508,6 +582,7 @@ export default class ScaleResolution {
508
582
 
509
583
  dispose() {
510
584
  this.#clearSelectionDomainParamSubscriptions();
585
+ this.#clearConfiguredDomainExprSubscriptions();
511
586
  this.#listeners.domain.clear();
512
587
  this.#listeners.range.clear();
513
588
  this.#scaleManager.dispose();
@@ -521,6 +596,13 @@ export default class ScaleResolution {
521
596
  this.#lastLinkedSelectionInterval = undefined;
522
597
  }
523
598
 
599
+ #clearConfiguredDomainExprSubscriptions() {
600
+ for (const unsubscribe of this.#configuredDomainExprUnsubscribers) {
601
+ unsubscribe();
602
+ }
603
+ this.#configuredDomainExprUnsubscribers = [];
604
+ }
605
+
524
606
  #refreshSelectionDomainParamSubscriptions() {
525
607
  this.#clearSelectionDomainParamSubscriptions();
526
608
 
@@ -552,6 +634,33 @@ export default class ScaleResolution {
552
634
  );
553
635
  }
554
636
 
637
+ #refreshConfiguredDomainExprSubscriptions() {
638
+ this.#clearConfiguredDomainExprSubscriptions();
639
+
640
+ if (this.#members.size === 0) {
641
+ return;
642
+ }
643
+
644
+ const listener = () => {
645
+ this.#invalidateConfiguredDomain();
646
+ this.reconfigureDomain();
647
+ };
648
+
649
+ for (const member of this.#members) {
650
+ if (!member.contributesToDomain) {
651
+ continue;
652
+ }
653
+ const domain = member.channelDef.scale?.domain;
654
+ if (!isExprRef(domain)) {
655
+ continue;
656
+ }
657
+
658
+ const expr = member.view.paramRuntime.createExpression(domain.expr);
659
+ const unsubscribe = expr.subscribe(listener);
660
+ this.#configuredDomainExprUnsubscribers.push(unsubscribe);
661
+ }
662
+ }
663
+
555
664
  #hasRenderedMember() {
556
665
  for (const member of this.#members) {
557
666
  if (member.view.hasRendered()) {
@@ -637,9 +746,9 @@ export default class ScaleResolution {
637
746
  const props = resolveScalePropsBase({
638
747
  channel: this.channel,
639
748
  dataType: this.type,
640
- members: this.#members,
749
+ orderedMembers: this.#getOrderedMembers(),
641
750
  isExplicitDomain: this.isDomainDefinedExplicitly(),
642
- configScopes: this.#firstMemberView.getConfigScopes(),
751
+ configScopes: this.#resolutionView.getConfigScopes(),
643
752
  });
644
753
  this.#validateLinkedSelectionConfiguration(props);
645
754
  return props;
@@ -650,6 +759,26 @@ export default class ScaleResolution {
650
759
  invalidate(this, "mergedScaleProps");
651
760
  }
652
761
 
762
+ #invalidateOrderedMembers() {
763
+ this.#orderedMembers = undefined;
764
+ }
765
+
766
+ /**
767
+ * Returns the participating members in a stable order.
768
+ *
769
+ * The membership set changes rarely, so cache the sorted order separately
770
+ * from merged scale props. That keeps parameter-driven domain updates from
771
+ * re-running the same path-based sort work.
772
+ *
773
+ * @returns {ScaleResolutionMember[]}
774
+ */
775
+ #getOrderedMembers() {
776
+ if (!this.#orderedMembers) {
777
+ this.#orderedMembers = orderResolutionMembers(this.#members);
778
+ }
779
+ return this.#orderedMembers;
780
+ }
781
+
653
782
  #invalidateConfiguredDomain() {
654
783
  this.#domainAggregator.invalidateConfiguredDomain();
655
784
  this.#invalidateMergedScaleProps();
@@ -736,10 +865,18 @@ export default class ScaleResolution {
736
865
 
737
866
  const resolvedProps = { ...props };
738
867
 
739
- const domain = this.#getConfiguredOrDefaultDomain(
740
- extractDataDomain,
741
- resolvedProps.type === LOCUS ? resolvedProps.assembly : undefined
742
- );
868
+ this.#resolvingScaleProps += 1;
869
+ let domain;
870
+ try {
871
+ domain = this.#getConfiguredOrDefaultDomain(
872
+ extractDataDomain,
873
+ resolvedProps.type === LOCUS
874
+ ? resolvedProps.assembly
875
+ : undefined
876
+ );
877
+ } finally {
878
+ this.#resolvingScaleProps -= 1;
879
+ }
743
880
 
744
881
  if (isDiscrete(resolvedProps.type)) {
745
882
  const isExplicit = this.isDomainDefinedExplicitly();
@@ -833,7 +970,6 @@ export default class ScaleResolution {
833
970
  */
834
971
  reconfigureDomain() {
835
972
  this.#withSelectionReverseSyncSuppressed(() => {
836
- this.#invalidateMergedScaleProps();
837
973
  const state = this.#computeScaleState(true, true);
838
974
  if (!state) {
839
975
  return;
@@ -946,6 +1082,11 @@ export default class ScaleResolution {
946
1082
  return;
947
1083
  }
948
1084
 
1085
+ if (scale.props.domainTransition === false) {
1086
+ this.#notifyListeners("domain");
1087
+ return;
1088
+ }
1089
+
949
1090
  const newDomain = scale.domain();
950
1091
  const action = this.#interactionController.getDomainChangeAction(
951
1092
  previousDomain,
@@ -1005,6 +1146,11 @@ export default class ScaleResolution {
1005
1146
  * @returns {ScaleWithProps}
1006
1147
  */
1007
1148
  getScale() {
1149
+ if (this.#resolvingScaleProps > 0) {
1150
+ throw new Error(
1151
+ `Scale resolution for channel "${this.channel}" cannot read its own scale while its domain is being resolved.`
1152
+ );
1153
+ }
1008
1154
  return this.#scaleManager.scale ?? this.initializeScale();
1009
1155
  }
1010
1156
 
@@ -1025,6 +1171,13 @@ export default class ScaleResolution {
1025
1171
  }
1026
1172
 
1027
1173
  getDomain() {
1174
+ if (this.#resolvingScaleProps > 0) {
1175
+ throw new Error(
1176
+ `Scale resolution for channel "${this.channel}" cannot read its own domain while its domain is being resolved.`
1177
+ );
1178
+ }
1179
+ // The underlying scale getter returns a fresh array. Treat this as a
1180
+ // read-only snapshot rather than a mutable backing store.
1028
1181
  return this.getScale().domain();
1029
1182
  }
1030
1183
 
@@ -1058,7 +1211,7 @@ export default class ScaleResolution {
1058
1211
  return;
1059
1212
  }
1060
1213
 
1061
- const root = this.#firstMemberView.getLayoutAncestors().at(-1);
1214
+ const root = this.#resolutionView.getLayoutAncestors().at(-1);
1062
1215
  const persist = root
1063
1216
  ? findIntervalSelectionBindingOwners(
1064
1217
  root,
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @param {{ width: number | undefined, height: number | undefined }} renderedBounds
3
+ * @param {{ width: number, height: number }} logicalSize
4
+ */
5
+ export function resolveExportSize(renderedBounds: {
6
+ width: number | undefined;
7
+ height: number | undefined;
8
+ }, logicalSize: {
9
+ width: number;
10
+ height: number;
11
+ }): {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ /**
16
+ * Returns the smallest DPR that yields at least 400 physical pixels vertically.
17
+ *
18
+ * Fractional DPRs are rounded upward to the nearest 0.5.
19
+ *
20
+ * @param {number} logicalHeight
21
+ */
22
+ export function resolveCaptureDevicePixelRatio(logicalHeight: number): number;
23
+ //# sourceMappingURL=screenshotExport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshotExport.d.ts","sourceRoot":"","sources":["../../src/screenshotExport.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,kDAHW;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,eACzD;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;;EAiB3C;AAED;;;;;;GAMG;AACH,8DAFW,MAAM,UAehB"}
@@ -0,0 +1,44 @@
1
+ const MIN_VERTICAL_RESOLUTION = 400;
2
+
3
+ /**
4
+ * @param {{ width: number | undefined, height: number | undefined }} renderedBounds
5
+ * @param {{ width: number, height: number }} logicalSize
6
+ */
7
+ export function resolveExportSize(renderedBounds, logicalSize) {
8
+ return {
9
+ width:
10
+ Number.isFinite(renderedBounds.width) && renderedBounds.width > 0
11
+ ? Math.ceil(renderedBounds.width)
12
+ : Number.isFinite(logicalSize.width) && logicalSize.width > 0
13
+ ? logicalSize.width
14
+ : 500,
15
+ height:
16
+ Number.isFinite(renderedBounds.height) && renderedBounds.height > 0
17
+ ? Math.ceil(renderedBounds.height)
18
+ : Number.isFinite(logicalSize.height) && logicalSize.height > 0
19
+ ? logicalSize.height
20
+ : 280,
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Returns the smallest DPR that yields at least 400 physical pixels vertically.
26
+ *
27
+ * Fractional DPRs are rounded upward to the nearest 0.5.
28
+ *
29
+ * @param {number} logicalHeight
30
+ */
31
+ export function resolveCaptureDevicePixelRatio(logicalHeight) {
32
+ if (!Number.isFinite(logicalHeight) || logicalHeight <= 0) {
33
+ throw new Error(
34
+ `Expected a positive logical height, got ${logicalHeight}.`
35
+ );
36
+ }
37
+
38
+ if (logicalHeight >= MIN_VERTICAL_RESOLUTION) {
39
+ return 1;
40
+ }
41
+
42
+ const requiredDevicePixelRatio = MIN_VERTICAL_RESOLUTION / logicalHeight;
43
+ return Math.ceil(requiredDevicePixelRatio * 2) / 2;
44
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"screenshotHarness.d.ts","sourceRoot":"","sources":["../../src/screenshotHarness.js"],"names":[],"mappings":"8BAQa;IACR,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,WAAW,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC5F;+BAIS,MAAM,GAAG,OAAO,UAAU,GAAG;IAAE,qBAAqB,EAAE,eAAe,CAAA;CAAE"}
1
+ {"version":3,"file":"screenshotHarness.d.ts","sourceRoot":"","sources":["../../src/screenshotHarness.js"],"names":[],"mappings":"8BAYa;IACR,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,WAAW,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC5F;+BAIS,MAAM,GAAG,OAAO,UAAU,GAAG;IAAE,qBAAqB,EAAE,eAAe,CAAA;CAAE"}
@@ -1,8 +1,12 @@
1
1
  import { embed, loadSpec } from "./index.js";
2
-
3
- const DEFAULT_CONTAINER_WIDTH = 600;
4
- const DEFAULT_CONTAINER_HEIGHT = 320;
5
- const DEFAULT_LAZY_READY_TIMEOUT_MS = 30_000;
2
+ import {
3
+ resolveCaptureDevicePixelRatio,
4
+ resolveExportSize,
5
+ } from "./screenshotExport.js";
6
+
7
+ const DEFAULT_CONTAINER_WIDTH = 500;
8
+ const DEFAULT_CONTAINER_HEIGHT = 300;
9
+ const DEFAULT_LAZY_READY_TIMEOUT_MS = 10_000;
6
10
  const READY_DELAY_MS = 100;
7
11
 
8
12
  /**
@@ -89,29 +93,41 @@ async function initializeHarness(url) {
89
93
  api.getRenderedBounds(),
90
94
  api.getLogicalCanvasSize()
91
95
  );
96
+ const devicePixelRatio = resolveCaptureDevicePixelRatio(
97
+ logicalSize.height
98
+ );
92
99
 
93
100
  screenshotWindow.__genomeSpyScreenshot = {
94
101
  status: "ready",
95
- detail: `Ready (${logicalSize.width}x${logicalSize.height}, DPR 1)`,
102
+ detail: `Ready (${logicalSize.width}x${logicalSize.height}, DPR ${formatDevicePixelRatio(
103
+ devicePixelRatio
104
+ )})`,
96
105
  error: "",
97
106
  async capture() {
98
107
  const currentSize = resolveExportSize(
99
108
  api.getRenderedBounds(),
100
109
  api.getLogicalCanvasSize()
101
110
  );
111
+ const currentDevicePixelRatio = resolveCaptureDevicePixelRatio(
112
+ currentSize.height
113
+ );
102
114
  return {
103
115
  logicalSize: currentSize,
104
116
  dataUrl: api.exportCanvas(
105
117
  currentSize.width,
106
118
  currentSize.height,
107
- 1,
119
+ currentDevicePixelRatio,
108
120
  "white"
109
121
  ),
110
122
  };
111
123
  },
112
124
  };
113
125
 
114
- setStatus(`Ready (${logicalSize.width}x${logicalSize.height}, DPR 1)`);
126
+ setStatus(
127
+ `Ready (${logicalSize.width}x${logicalSize.height}, DPR ${formatDevicePixelRatio(
128
+ devicePixelRatio
129
+ )})`
130
+ );
115
131
  } catch (error) {
116
132
  setFailure(error instanceof Error ? error.message : String(error));
117
133
  }
@@ -184,24 +200,10 @@ function parseTimeoutMs(value, fallback) {
184
200
  }
185
201
 
186
202
  /**
187
- * @param {{ width: number | undefined, height: number | undefined }} renderedBounds
188
- * @param {{ width: number, height: number }} logicalSize
203
+ * @param {number} value
189
204
  */
190
- function resolveExportSize(renderedBounds, logicalSize) {
191
- return {
192
- width:
193
- Number.isFinite(renderedBounds.width) && renderedBounds.width > 0
194
- ? Math.ceil(renderedBounds.width)
195
- : Number.isFinite(logicalSize.width) && logicalSize.width > 0
196
- ? logicalSize.width
197
- : DEFAULT_CONTAINER_WIDTH,
198
- height:
199
- Number.isFinite(renderedBounds.height) && renderedBounds.height > 0
200
- ? Math.ceil(renderedBounds.height)
201
- : Number.isFinite(logicalSize.height) && logicalSize.height > 0
202
- ? logicalSize.height
203
- : DEFAULT_CONTAINER_HEIGHT,
204
- };
205
+ function formatDevicePixelRatio(value) {
206
+ return value.toFixed(3).replace(/\.?0+$/, "");
205
207
  }
206
208
 
207
209
  /**
@@ -113,9 +113,9 @@ export interface Axis extends BaseAxis, ZIndexProps {
113
113
  format?: string;
114
114
 
115
115
  /**
116
- * A title for the axis (none by default).
116
+ * A title for the axis (none by default). Set to `null` to remove it.
117
117
  */
118
- title?: string;
118
+ title?: string | null;
119
119
 
120
120
  /**
121
121
  * The orthogonal offset in pixels by which to displace the axis from its position along the edge of the chart.
@@ -434,17 +434,17 @@ export interface Encoding {
434
434
  *
435
435
  * The `value` of this channel can be a number between zero and one.
436
436
  */
437
- x2?: Position2Def;
437
+ x2?: Position2Def | null;
438
438
 
439
439
  /**
440
440
  * Y2 coordinates of the marks.
441
441
  *
442
442
  * The `value` of this channel can be a number between zero and one.
443
443
  */
444
- y2?: Position2Def;
444
+ y2?: Position2Def | null;
445
445
 
446
- dx?: NumericMarkPropDef; // TODO: Not a mark property. Fix types.
447
- dy?: NumericMarkPropDef;
446
+ dx?: NumericMarkPropDef | MarkPropExprDef; // TODO: Not a mark property. Fix types.
447
+ dy?: NumericMarkPropDef | MarkPropExprDef;
448
448
 
449
449
  /**
450
450
  * Color of the marks – either fill or stroke color based on the `filled` property of mark definition.
@@ -50,6 +50,12 @@ export interface DataFormatBase {
50
50
 
51
51
  export interface CsvDataFormat extends DataFormatBase {
52
52
  type?: "csv" | "tsv";
53
+
54
+ /**
55
+ * Optional ordered list of field names for headerless CSV or TSV input.
56
+ * When provided, the first row is interpreted as data rather than a header row.
57
+ */
58
+ columns?: string[];
53
59
  }
54
60
 
55
61
  export interface DsvDataFormat extends DataFormatBase {
@@ -62,6 +68,12 @@ export interface DsvDataFormat extends DataFormatBase {
62
68
  * @maxLength 1
63
69
  */
64
70
  delimiter: string;
71
+
72
+ /**
73
+ * Optional ordered list of field names for headerless delimiter-separated input.
74
+ * When provided, the first row is interpreted as data rather than a header row.
75
+ */
76
+ columns?: string[];
65
77
  }
66
78
 
67
79
  export interface JsonDataFormat extends DataFormatBase {
@@ -68,7 +68,7 @@ export interface Scale {
68
68
  *
69
69
  * For _ordinal_ and _nominal_ fields, `domain` can be an array that lists valid input values.
70
70
  */
71
- domain?: ScalarDomain | ComplexDomain | SelectionDomainRef;
71
+ domain?: ScalarDomain | ComplexDomain | SelectionDomainRef | ExprRef;
72
72
 
73
73
  /**
74
74
  * Inserts a single mid-point value into a two-element domain. The mid-point value must lie between the domain minimum and maximum values. This property can be useful for setting a midpoint for [diverging color scales](https://vega.github.io/vega-lite/docs/scale.html#piecewise). The domainMid property is only intended for use with scales supporting continuous, piecewise domains.
@@ -92,6 +92,18 @@ export interface Scale {
92
92
  */
93
93
  reverse?: boolean;
94
94
 
95
+ /**
96
+ * Controls whether domain updates are applied immediately or with a smooth
97
+ * transition.
98
+ *
99
+ * Set this to `false` to apply domain updates immediately. The default is
100
+ * `true`, except for domains defined by an `ExprRef`, which default to
101
+ * `false` unless overridden.
102
+ *
103
+ * __Default value:__ `true`, except `false` for ExprRef-driven domains.
104
+ */
105
+ domainTransition?: boolean | Record<string, unknown>;
106
+
95
107
  /**
96
108
  * The range of the scale. One of:
97
109
  *
@@ -1,15 +1,11 @@
1
1
  /**
2
- * @typedef { object } ExpressionProps
3
- * @prop { string[] } fields
4
- * @prop { string[] } globals
5
- * @prop { string } code
6
- *
7
- * @typedef { ((datum?: import("../data/flowNode.js").Datum) => any) & ExpressionProps } ExpressionFunction
8
- *
9
2
  * @param {string} expr
3
+ * @param {Record<string, any>} globalObject
4
+ * @param {ExpressionCompileContext} context
5
+ *
10
6
  * @returns {ExpressionFunction}
11
7
  */
12
- export default function createFunction(expr: string, globalObject?: {}): ExpressionFunction;
8
+ export default function createFunction(expr: string, globalObject?: Record<string, any>, context?: ExpressionCompileContext): ExpressionFunction;
13
9
  /**
14
10
  * @param {string} expr
15
11
  * @returns {(event: UIEvent | import("./interactionEvent.js").WheelLikeEvent) => boolean}
@@ -19,6 +15,18 @@ export type ExpressionProps = {
19
15
  fields: string[];
20
16
  globals: string[];
21
17
  code: string;
18
+ scaleDependencies?: import("../paramRuntime/types.js").ParamRef<any>[];
22
19
  };
23
20
  export type ExpressionFunction = ((datum?: import("../data/flowNode.js").Datum) => any) & ExpressionProps;
21
+ export type ExpressionCompileContext = {
22
+ resolveScaleResolution?: (channel: string) => import("../scales/scaleResolution.js").default | undefined;
23
+ };
24
+ export type ScaleHelperCompileContext = ExpressionCompileContext & {
25
+ globalvar: string;
26
+ globalObject: Record<string, any>;
27
+ getScaleHelper: (kind: "scale" | "invert" | "domain" | "range", channel: string, resolution: import("../scales/scaleResolution.js").default) => {
28
+ codeName: string;
29
+ dependency: import("../paramRuntime/types.js").ParamRef<any>;
30
+ };
31
+ };
24
32
  //# sourceMappingURL=expression.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../../../src/utils/expression.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;GAUG;AACH,6CAHW,MAAM,sBACJ,kBAAkB,CAiC9B;AAQD;;;GAGG;AACH,gDAHW,MAAM,GACJ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,uBAAuB,EAAE,cAAc,KAAK,OAAO,CA4BxF;;YA9EU,MAAM,EAAE;aACR,MAAM,EAAE;UACR,MAAM;;iCAEH,CAAC,CAAC,KAAK,CAAC,EAAE,OAAO,qBAAqB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,eAAe"}
1
+ {"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../../../src/utils/expression.js"],"names":[],"mappings":"AAkVA;;;;;;GAMG;AACH,6CANW,MAAM,iBACN,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YACnB,wBAAwB,GAEtB,kBAAkB,CAuG9B;AAQD;;;GAGG;AACH,gDAHW,MAAM,GACJ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,uBAAuB,EAAE,cAAc,KAAK,OAAO,CA4BxF;;YAjVU,MAAM,EAAE;aACR,MAAM,EAAE;UACR,MAAM;wBACN,OAAO,0BAA0B,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;;iCAE/C,CAAC,CAAC,KAAK,CAAC,EAAE,OAAO,qBAAqB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,eAAe;;6BAG5E,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,8BAA8B,EAAE,OAAO,GAAG,SAAS;;wCAE5E,wBAAwB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,cAAc,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,8BAA8B,EAAE,OAAO,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,0BAA0B,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;CACnO"}