@genome-spy/core 0.74.0 → 0.76.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 (143) hide show
  1. package/dist/bundle/{esm-CgfVIRJ-.js → esm-BimDEpBb.js} +1 -1
  2. package/dist/bundle/{esm-DtE8VqAv.js → esm-Bvlm1uVk.js} +1 -1
  3. package/dist/bundle/{esm-sIoQYZ21.js → esm-CngqBe45.js} +17 -17
  4. package/dist/bundle/{esm-DQiq2Zhd.js → esm-D_euN86T.js} +43 -43
  5. package/dist/bundle/index.es.js +6064 -5756
  6. package/dist/bundle/index.js +104 -103
  7. package/dist/schema.json +572 -12
  8. package/dist/src/config/defaults/markDefaults.d.ts.map +1 -1
  9. package/dist/src/config/defaults/markDefaults.js +1 -12
  10. package/dist/src/config/defaults/scaleDefaults.d.ts.map +1 -1
  11. package/dist/src/config/defaults/scaleDefaults.js +1 -0
  12. package/dist/src/config/markConfig.d.ts.map +1 -1
  13. package/dist/src/config/markConfig.js +16 -8
  14. package/dist/src/config/themes.d.ts.map +1 -1
  15. package/dist/src/config/themes.js +15 -2
  16. package/dist/src/data/sources/dataUtils.d.ts +25 -0
  17. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  18. package/dist/src/data/sources/dataUtils.js +23 -0
  19. package/dist/src/data/sources/inlineSource.js +2 -2
  20. package/dist/src/data/sources/lazy/registerBuiltInLazySources.js +2 -2
  21. package/dist/src/data/sources/lazy/registerCoreLazySources.d.ts +2 -0
  22. package/dist/src/data/sources/lazy/registerCoreLazySources.d.ts.map +1 -0
  23. package/dist/src/data/sources/lazy/registerCoreLazySources.js +2 -0
  24. package/dist/src/data/sources/lazy/tabixSource.d.ts +7 -0
  25. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  26. package/dist/src/data/sources/lazy/tabixSource.js +18 -0
  27. package/dist/src/data/sources/lazy/tabixTsvSource.d.ts +37 -0
  28. package/dist/src/data/sources/lazy/tabixTsvSource.d.ts.map +1 -0
  29. package/dist/src/data/sources/lazy/tabixTsvSource.js +163 -0
  30. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  31. package/dist/src/data/sources/urlSource.js +8 -3
  32. package/dist/src/encoder/encoder.d.ts +2 -2
  33. package/dist/src/encoder/encoder.d.ts.map +1 -1
  34. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  35. package/dist/src/genome/scaleLocus.js +8 -3
  36. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  37. package/dist/src/genomeSpy/interactionController.js +91 -51
  38. package/dist/src/genomeSpyBase.d.ts.map +1 -1
  39. package/dist/src/genomeSpyBase.js +4 -1
  40. package/dist/src/gl/dataToVertices.d.ts +12 -14
  41. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  42. package/dist/src/gl/dataToVertices.js +116 -95
  43. package/dist/src/gl/glslScaleGenerator.d.ts +3 -0
  44. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  45. package/dist/src/gl/glslScaleGenerator.js +10 -8
  46. package/dist/src/gl/vertexRangeIndex.d.ts +23 -0
  47. package/dist/src/gl/vertexRangeIndex.d.ts.map +1 -0
  48. package/dist/src/gl/vertexRangeIndex.js +150 -0
  49. package/dist/src/gl/webGLHelper.d.ts +5 -2
  50. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  51. package/dist/src/gl/webGLHelper.js +20 -3
  52. package/dist/src/marks/__snapshots__/shaderSnapshot.test.js.snap +1082 -0
  53. package/dist/src/marks/link.vertex.glsl.js +1 -1
  54. package/dist/src/marks/mark.d.ts +1 -1
  55. package/dist/src/minimal.d.ts.map +1 -1
  56. package/dist/src/minimal.js +5 -4
  57. package/dist/src/paramRuntime/expressionCompiler.d.ts +2 -1
  58. package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -1
  59. package/dist/src/paramRuntime/expressionCompiler.js +3 -2
  60. package/dist/src/paramRuntime/expressionRef.d.ts +4 -1
  61. package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -1
  62. package/dist/src/paramRuntime/expressionRef.js +10 -3
  63. package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -1
  64. package/dist/src/paramRuntime/graphRuntime.js +15 -6
  65. package/dist/src/paramRuntime/paramRuntime.d.ts +8 -2
  66. package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -1
  67. package/dist/src/paramRuntime/paramRuntime.js +10 -5
  68. package/dist/src/paramRuntime/types.d.ts +1 -0
  69. package/dist/src/paramRuntime/types.d.ts.map +1 -1
  70. package/dist/src/paramRuntime/types.js +1 -0
  71. package/dist/src/paramRuntime/viewParamRuntime.d.ts +5 -4
  72. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -1
  73. package/dist/src/paramRuntime/viewParamRuntime.js +17 -6
  74. package/dist/src/scale/scale.d.ts.map +1 -1
  75. package/dist/src/scale/scale.js +11 -2
  76. package/dist/src/scales/domainPlanner.d.ts +57 -11
  77. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  78. package/dist/src/scales/domainPlanner.js +183 -84
  79. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  80. package/dist/src/scales/scaleInstanceManager.js +7 -2
  81. package/dist/src/scales/scalePropsResolver.d.ts +3 -3
  82. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
  83. package/dist/src/scales/scalePropsResolver.js +28 -5
  84. package/dist/src/scales/scaleResolution.d.ts +12 -1
  85. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  86. package/dist/src/scales/scaleResolution.js +180 -21
  87. package/dist/src/scales/selectionDomainUtils.d.ts +10 -0
  88. package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -1
  89. package/dist/src/scales/selectionDomainUtils.js +32 -3
  90. package/dist/src/screenshotExport.d.ts +23 -0
  91. package/dist/src/screenshotExport.d.ts.map +1 -0
  92. package/dist/src/screenshotExport.js +44 -0
  93. package/dist/src/screenshotHarness.d.ts.map +1 -1
  94. package/dist/src/screenshotHarness.js +26 -24
  95. package/dist/src/spec/axis.d.ts +2 -2
  96. package/dist/src/spec/channel.d.ts +34 -4
  97. package/dist/src/spec/data.d.ts +52 -0
  98. package/dist/src/spec/parameter.d.ts +6 -0
  99. package/dist/src/spec/scale.d.ts +13 -1
  100. package/dist/src/spec/transform.d.ts +6 -0
  101. package/dist/src/utils/expression.d.ts +16 -8
  102. package/dist/src/utils/expression.d.ts.map +1 -1
  103. package/dist/src/utils/expression.js +291 -11
  104. package/dist/src/view/axisGridView.d.ts.map +1 -1
  105. package/dist/src/view/axisGridView.js +2 -1
  106. package/dist/src/view/axisView.d.ts.map +1 -1
  107. package/dist/src/view/axisView.js +2 -1
  108. package/dist/src/view/facetView.d.ts.map +1 -1
  109. package/dist/src/view/facetView.js +2 -1
  110. package/dist/src/view/flowBuilder.d.ts +1 -1
  111. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  112. package/dist/src/view/flowBuilder.js +11 -7
  113. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  114. package/dist/src/view/gridView/gridChild.js +9 -1
  115. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  116. package/dist/src/view/gridView/gridView.js +198 -32
  117. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  118. package/dist/src/view/gridView/scrollbar.js +5 -1
  119. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  120. package/dist/src/view/gridView/selectionRect.js +5 -1
  121. package/dist/src/view/gridView/separatorView.d.ts.map +1 -1
  122. package/dist/src/view/gridView/separatorView.js +5 -1
  123. package/dist/src/view/resolutionPlanner.d.ts +9 -0
  124. package/dist/src/view/resolutionPlanner.d.ts.map +1 -0
  125. package/dist/src/view/resolutionPlanner.js +302 -0
  126. package/dist/src/view/testUtils.d.ts +30 -3
  127. package/dist/src/view/testUtils.d.ts.map +1 -1
  128. package/dist/src/view/testUtils.js +51 -2
  129. package/dist/src/view/unitView.d.ts +1 -1
  130. package/dist/src/view/unitView.d.ts.map +1 -1
  131. package/dist/src/view/unitView.js +5 -152
  132. package/dist/src/view/view.d.ts.map +1 -1
  133. package/dist/src/view/view.js +2 -1
  134. package/dist/src/view/viewSelectors.d.ts +38 -10
  135. package/dist/src/view/viewSelectors.d.ts.map +1 -1
  136. package/dist/src/view/viewSelectors.js +67 -2
  137. package/dist/src/view/viewUtilTypes.d.ts +15 -0
  138. package/dist/src/view/viewUtils.d.ts.map +1 -1
  139. package/dist/src/view/viewUtils.js +10 -0
  140. package/package.json +2 -2
  141. package/LICENSE +0 -21
  142. /package/dist/bundle/{esm-BDFRLEuD.js → esm-C49STiCR.js} +0 -0
  143. /package/dist/bundle/{esm-CGX-qz1d.js → esm-CuVa5T98.js} +0 -0
@@ -99,6 +99,12 @@ export interface TypeMixins<T extends Type> {
99
99
  }
100
100
 
101
101
  export interface FieldDefBase {
102
+ /**
103
+ * A description of the encoded field. Can be used for documentation and
104
+ * to explain the meaning of the channel mapping.
105
+ */
106
+ description?: string;
107
+
102
108
  /**
103
109
  * __Required.__ A string defining the name of the field from which to pull a data value
104
110
  * or an object defining iterated values from the [`repeat`](https://vega.github.io/vega-lite/docs/repeat.html) operator.
@@ -169,12 +175,24 @@ export interface ValueDefBase<V extends Value = Scalar> {
169
175
  * A constant value in visual domain (e.g., `"red"` / `"#0099ff"`, values between `0` to `1` for opacity).
170
176
  */
171
177
  value: V | ExprRef;
178
+
179
+ /**
180
+ * A description of the encoded value. Can be used for documentation and
181
+ * to explain the meaning of the channel mapping.
182
+ */
183
+ description?: string;
172
184
  }
173
185
 
174
186
  export type ValueDef<V extends Value = Scalar> = ValueDefBase<V> & TitleMixins;
175
187
 
176
188
  export interface DatumDef<V extends Scalar | ExprRef = Scalar | ExprRef>
177
189
  extends Partial<TypeMixins<Type>>, BandMixins, ScaleMixins, TitleMixins {
190
+ /**
191
+ * A description of the encoded datum. Can be used for documentation and
192
+ * to explain the meaning of the channel mapping.
193
+ */
194
+ description?: string;
195
+
178
196
  /**
179
197
  * A constant value in data domain.
180
198
  */
@@ -183,6 +201,12 @@ export interface DatumDef<V extends Scalar | ExprRef = Scalar | ExprRef>
183
201
 
184
202
  export interface ExprDef
185
203
  extends Partial<TypeMixins<Type>>, BandMixins, TitleMixins {
204
+ /**
205
+ * A description of the encoded expression. Can be used for documentation
206
+ * and to explain the meaning of the channel mapping.
207
+ */
208
+ description?: string;
209
+
186
210
  /**
187
211
  * An expression. Properties of the data can be accessed through the `datum` object.
188
212
  */
@@ -332,6 +356,12 @@ export interface PositionMixins extends BandMixins {
332
356
  export type PositionFieldDefBase = ScaleFieldDef<Type>;
333
357
 
334
358
  export interface ChromPosDefBase extends BandMixins {
359
+ /**
360
+ * A description of the encoded position. Can be used for documentation and
361
+ * to explain the meaning of the channel mapping.
362
+ */
363
+ description?: string;
364
+
335
365
  /**
336
366
  * The field having the chromosome or contig.
337
367
  */
@@ -434,17 +464,17 @@ export interface Encoding {
434
464
  *
435
465
  * The `value` of this channel can be a number between zero and one.
436
466
  */
437
- x2?: Position2Def;
467
+ x2?: Position2Def | null;
438
468
 
439
469
  /**
440
470
  * Y2 coordinates of the marks.
441
471
  *
442
472
  * The `value` of this channel can be a number between zero and one.
443
473
  */
444
- y2?: Position2Def;
474
+ y2?: Position2Def | null;
445
475
 
446
- dx?: NumericMarkPropDef; // TODO: Not a mark property. Fix types.
447
- dy?: NumericMarkPropDef;
476
+ dx?: NumericMarkPropDef | MarkPropExprDef; // TODO: Not a mark property. Fix types.
477
+ dy?: NumericMarkPropDef | MarkPropExprDef;
448
478
 
449
479
  /**
450
480
  * 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 {
@@ -126,6 +138,12 @@ export type InlineDataset =
126
138
  | object;
127
139
 
128
140
  export interface DataBase {
141
+ /**
142
+ * A description of the data source. Can be used for documentation and to
143
+ * explain the role of the data in the visualization.
144
+ */
145
+ description?: string;
146
+
129
147
  /**
130
148
  * An object that specifies the format for parsing the data.
131
149
  */
@@ -190,6 +208,12 @@ export interface DynamicCallbackData extends DataBase {
190
208
  export type Generator = SequenceGenerator;
191
209
 
192
210
  export interface GeneratorBase {
211
+ /**
212
+ * A description of the data source. Can be used for documentation and to
213
+ * explain the role of the generated data in the visualization.
214
+ */
215
+ description?: string;
216
+
193
217
  /**
194
218
  * Provide a placeholder name and bind data at runtime.
195
219
  */
@@ -228,6 +252,12 @@ export interface SequenceParams {
228
252
  }
229
253
 
230
254
  export interface LazyData {
255
+ /**
256
+ * A description of the data source. Can be used for documentation and to
257
+ * explain the role of the lazy data in the visualization.
258
+ */
259
+ description?: string;
260
+
231
261
  lazy: LazyDataParams;
232
262
  }
233
263
 
@@ -238,6 +268,7 @@ export type LazyDataParams =
238
268
  | BigWigData
239
269
  | BigBedData
240
270
  | BamData
271
+ | TabixTsvData
241
272
  | Gff3Data
242
273
  | VcfData;
243
274
 
@@ -434,6 +465,27 @@ export interface TabixData extends DebouncedData {
434
465
  windowSize?: number;
435
466
  }
436
467
 
468
+ export interface TabixTsvData extends TabixData {
469
+ type: "tabix";
470
+
471
+ /**
472
+ * Ordered list of field names for headerless tabix TSV input.
473
+ * If omitted, the source tries to read a commented header line from the
474
+ * tabix file header or the first row of a plain TSV header.
475
+ */
476
+ columns?: string[];
477
+
478
+ /**
479
+ * Optional type parsing for TSV fields. When omitted, field types are
480
+ * inferred automatically. Set to `null` to disable spec-based type
481
+ * inference and rely on data inference, or provide a field-to-type map to
482
+ * override selected columns.
483
+ *
484
+ * __Default value:__ `"auto"`
485
+ */
486
+ parse?: Parse | null;
487
+ }
488
+
437
489
  export interface Gff3Data extends TabixData {
438
490
  type: "gff3";
439
491
  }
@@ -18,6 +18,12 @@ export interface ParameterBase {
18
18
  */
19
19
  name: string;
20
20
 
21
+ /**
22
+ * A description of the parameter. Can be used for documentation and to
23
+ * explain the meaning of the control or selection.
24
+ */
25
+ description?: string;
26
+
21
27
  push?: "outer";
22
28
  }
23
29
 
@@ -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
  *
@@ -11,6 +11,12 @@ export type Field = string;
11
11
  export interface TransformParamsBase {
12
12
  /** The type of the transform to be applied */
13
13
  type: string;
14
+
15
+ /**
16
+ * A description of the transform step. Can be used for documentation
17
+ * and agent context.
18
+ */
19
+ description?: string;
14
20
  }
15
21
 
16
22
  export interface IdentifierParams extends TransformParamsBase {
@@ -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"}
@@ -8,6 +8,7 @@ import {
8
8
  isString,
9
9
  ascending,
10
10
  lerp,
11
+ span,
11
12
  } from "vega-util";
12
13
  import { format as d3format } from "d3-format";
13
14
  import smoothstep from "./smoothstep.js";
@@ -100,41 +101,314 @@ const functionContext = {
100
101
  sort(/** @type {Array<any>} */ seq) {
101
102
  return array(seq).slice().sort(ascending);
102
103
  },
104
+ /**
105
+ * Returns the midpoint of an ordered extent, such as [min, max].
106
+ * The helper uses the first and last elements and does not sort.
107
+ */
108
+ center(/** @type {Array<any>} */ seq) {
109
+ const values = array(seq);
110
+ return (values[0] + values[values.length - 1]) / 2;
111
+ },
112
+ span(/** @type {Array<any>} */ seq) {
113
+ return span(seq);
114
+ },
103
115
  smoothstep,
104
116
  };
105
117
 
106
118
  /**
107
119
  * @param {typeof codegenExpression} codegen
120
+ * @param {ScaleHelperCompileContext} context
108
121
  */
109
- function buildFunctions(codegen) {
110
- const fn = functions(codegen);
122
+ function buildFunctions(codegen, context) {
123
+ const fn = /** @type {any} */ (functions(codegen));
111
124
  for (const name in functionContext) {
112
125
  fn[name] = `this.${name}`;
113
126
  }
127
+
128
+ // Replace the public helper names with custom codegen hooks so we can bind
129
+ // a scale resolution once during compilation instead of looking it up for
130
+ // every datum.
131
+ for (const kind of /** @type {const} */ ([
132
+ "scale",
133
+ "invert",
134
+ "domain",
135
+ "range",
136
+ ])) {
137
+ fn[kind] = (
138
+ /** @type {any[]} */
139
+ args
140
+ ) => buildScaleHelperCall(codegen, context, kind, args);
141
+ }
142
+
114
143
  return fn;
115
144
  }
116
145
 
117
- const cg = codegenExpression({
118
- forbidden: [],
119
- allowed: ["datum", "undefined"],
120
- globalvar: "globalObject",
121
- fieldvar: "datum",
122
- functions: buildFunctions,
123
- });
124
-
125
146
  /**
126
147
  * @typedef { object } ExpressionProps
127
148
  * @prop { string[] } fields
128
149
  * @prop { string[] } globals
129
150
  * @prop { string } code
151
+ * @prop { import("../paramRuntime/types.js").ParamRef<any>[] } [scaleDependencies]
130
152
  *
131
153
  * @typedef { ((datum?: import("../data/flowNode.js").Datum) => any) & ExpressionProps } ExpressionFunction
132
154
  *
155
+ * @typedef {object} ExpressionCompileContext
156
+ * @prop {(channel: string) => import("../scales/scaleResolution.js").default | undefined} [resolveScaleResolution]
157
+ *
158
+ * @typedef {ExpressionCompileContext & {
159
+ * globalvar: string,
160
+ * globalObject: Record<string, any>,
161
+ * getScaleHelper: (kind: "scale" | "invert" | "domain" | "range", channel: string, resolution: import("../scales/scaleResolution.js").default) => { codeName: string, dependency: import("../paramRuntime/types.js").ParamRef<any> }
162
+ * }} ScaleHelperCompileContext
163
+ */
164
+
165
+ /**
166
+ * @param {typeof codegenExpression} codegen
167
+ * @param {ScaleHelperCompileContext} context
168
+ * @param {"scale" | "invert" | "domain" | "range"} kind
169
+ * @param {any[]} args
170
+ * @returns {string}
171
+ */
172
+ function buildScaleHelperCall(codegen, context, kind, args) {
173
+ if (args.length === 0) {
174
+ throw new Error(
175
+ `Scale helper "${kind}" requires a literal channel name.`
176
+ );
177
+ }
178
+
179
+ if ((kind === "scale" || kind === "invert") && args.length < 2) {
180
+ throw new Error(
181
+ `Scale helper "${kind}" requires a channel name and a value.`
182
+ );
183
+ }
184
+
185
+ const channel = getLiteralString(args[0]);
186
+ if (!channel) {
187
+ throw new Error(
188
+ `Scale helper "${kind}" requires a literal channel name.`
189
+ );
190
+ }
191
+
192
+ const resolution = context.resolveScaleResolution?.(channel);
193
+ if (!resolution) {
194
+ throw new Error(
195
+ `Unknown scale channel "${channel}" in expression helper "${kind}".`
196
+ );
197
+ }
198
+
199
+ const helper = context.getScaleHelper(kind, channel, resolution);
200
+ const remainingArgs = args
201
+ .slice(1)
202
+ .map((arg) => codegen(arg))
203
+ .join(",");
204
+ return `${context.globalvar}["${helper.codeName}"](${remainingArgs})`;
205
+ }
206
+
207
+ /**
208
+ * @param {any} node
209
+ * @returns {string | undefined}
210
+ */
211
+ function getLiteralString(node) {
212
+ return node?.type === "Literal" && typeof node.value === "string"
213
+ ? node.value
214
+ : undefined;
215
+ }
216
+
217
+ /**
218
+ * @param {"scale" | "invert" | "domain" | "range"} kind
219
+ * @param {import("../scales/scaleResolution.js").default} resolution
220
+ * @returns {(...args: any[]) => any}
221
+ */
222
+ function createScaleHelperFunction(kind, resolution) {
223
+ /** @type {(fn: () => any) => any} */
224
+ const run = (fn) => runWithActiveScaleResolution(resolution, kind, fn);
225
+
226
+ if (kind === "domain") {
227
+ // `vega-scale` returns a fresh array here. That is fine for
228
+ // batch-invariant expressions, but callers should avoid using it in a
229
+ // per-datum hot path unless they really need the full extent array.
230
+ return () => run(() => resolution.getDomain());
231
+ }
232
+ if (kind === "range") {
233
+ // Same allocation caveat as `domain()`: the underlying scale getter
234
+ // returns a copied array, so repeated per-row calls will allocate.
235
+ return () => run(() => resolution.getScale().range());
236
+ }
237
+ if (kind === "scale") {
238
+ return (value) => run(() => resolution.getScale()(value));
239
+ }
240
+ if (kind === "invert") {
241
+ return (value) =>
242
+ run(() => /** @type {any} */ (resolution.getScale()).invert(value));
243
+ }
244
+ throw new Error("Unknown scale helper: " + kind);
245
+ }
246
+
247
+ /** @type {WeakSet<object>} */
248
+ const activeScaleHelperResolutions = new WeakSet();
249
+
250
+ /**
251
+ * @template T
252
+ * @param {import("../scales/scaleResolution.js").default} resolution
253
+ * @param {"scale" | "invert" | "domain" | "range"} kind
254
+ * @param {() => T} fn
255
+ * @returns {T}
256
+ */
257
+ function runWithActiveScaleResolution(resolution, kind, fn) {
258
+ if (activeScaleHelperResolutions.has(resolution)) {
259
+ throw new Error(
260
+ `Scale helper cycle detected while evaluating ${kind}("${resolution.channel}").`
261
+ );
262
+ }
263
+
264
+ activeScaleHelperResolutions.add(resolution);
265
+ try {
266
+ return fn();
267
+ } finally {
268
+ activeScaleHelperResolutions.delete(resolution);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * @param {"scale" | "invert" | "domain" | "range"} kind
274
+ * @param {string} channel
275
+ * @param {import("../scales/scaleResolution.js").default} resolution
276
+ * @param {string} codeName
277
+ * @returns {import("../paramRuntime/types.js").ParamRef<any> & { rank: number }}
278
+ */
279
+ function createScaleDependency(kind, channel, resolution, codeName) {
280
+ /** @type {Set<() => void>} */
281
+ const listeners = new Set();
282
+
283
+ const notify = () => {
284
+ for (const listener of listeners) {
285
+ listener();
286
+ }
287
+ };
288
+
289
+ const attach = () => {
290
+ if (kind === "domain") {
291
+ resolution.addEventListener("domain", notify);
292
+ } else if (kind === "range") {
293
+ resolution.addEventListener("range", notify);
294
+ } else {
295
+ resolution.addEventListener("domain", notify);
296
+ resolution.addEventListener("range", notify);
297
+ }
298
+ };
299
+
300
+ const detach = () => {
301
+ if (kind === "domain") {
302
+ resolution.removeEventListener("domain", notify);
303
+ } else if (kind === "range") {
304
+ resolution.removeEventListener("range", notify);
305
+ } else {
306
+ resolution.removeEventListener("domain", notify);
307
+ resolution.removeEventListener("range", notify);
308
+ }
309
+ };
310
+
311
+ return {
312
+ // The dependency is a lightweight invalidation token. It does not need
313
+ // to expose a separate scale value; the bound helper closure already
314
+ // closes over the actual resolution. The ref exists so the expression
315
+ // graph can subscribe to scale changes like any other reactive input.
316
+ id: `scale:${channel}:${codeName}`,
317
+ name: `scale(${channel})`,
318
+ kind: "derived",
319
+ rank: 0,
320
+ get() {
321
+ return resolution.getScale();
322
+ },
323
+ subscribe(listener) {
324
+ const wasEmpty = listeners.size === 0;
325
+ listeners.add(listener);
326
+ if (wasEmpty) {
327
+ attach();
328
+ }
329
+ return () => {
330
+ const removed = listeners.delete(listener);
331
+ if (removed && listeners.size === 0) {
332
+ detach();
333
+ }
334
+ };
335
+ },
336
+ };
337
+ }
338
+
339
+ /**
133
340
  * @param {string} expr
341
+ * @param {Record<string, any>} globalObject
342
+ * @param {ExpressionCompileContext} context
343
+ *
134
344
  * @returns {ExpressionFunction}
135
345
  */
136
- export default function createFunction(expr, globalObject = {}) {
346
+ export default function createFunction(expr, globalObject = {}, context = {}) {
137
347
  try {
348
+ // Each scale helper call is rewritten once at compile time into a
349
+ // cached closure that targets a specific scale resolution.
350
+ /** @type {Map<string, import("../paramRuntime/types.js").ParamRef<any>>} */
351
+ const scaleDependenciesByChannel = new Map();
352
+ // A helper may appear multiple times in one expression. Cache both the
353
+ // generated closure and the synthetic dependency ref so repeated calls
354
+ // share the same reactive identity.
355
+ /** @type {Map<string, { codeName: string, dependency: import("../paramRuntime/types.js").ParamRef<any> }>} */
356
+ const helperEntries = new Map();
357
+ let nextScaleHelperId = 1;
358
+
359
+ /**
360
+ * @type {ExpressionCompileContext & {
361
+ * globalvar: string,
362
+ * globalObject: Record<string, any>,
363
+ * getScaleHelper: (kind: "scale" | "invert" | "domain" | "range", channel: string, resolution: import("../scales/scaleResolution.js").default) => { codeName: string, dependency: import("../paramRuntime/types.js").ParamRef<any> }
364
+ * }}
365
+ */
366
+ const helperContext = {
367
+ ...context,
368
+ globalvar: "globalObject",
369
+ globalObject,
370
+ getScaleHelper(kind, channel, resolution) {
371
+ // Helper instances are keyed by helper kind + channel so
372
+ // `domain("x")` and `scale("x", ...)` share the same
373
+ // resolution binding but keep separate generated closures.
374
+ const key = kind + ":" + channel;
375
+ const cached = helperEntries.get(key);
376
+ if (cached) {
377
+ return cached;
378
+ }
379
+
380
+ let dependency = scaleDependenciesByChannel.get(channel);
381
+ if (!dependency) {
382
+ dependency = createScaleDependency(
383
+ kind,
384
+ channel,
385
+ resolution,
386
+ "__scale_dependency_" + nextScaleHelperId++
387
+ );
388
+ scaleDependenciesByChannel.set(channel, dependency);
389
+ }
390
+
391
+ const codeName = "__scale_helper_" + nextScaleHelperId++;
392
+ const entry = { codeName, dependency };
393
+ helperEntries.set(key, entry);
394
+ // Store the concrete helper implementation on the global object
395
+ // used by the generated expression function.
396
+ globalObject[codeName] = createScaleHelperFunction(
397
+ kind,
398
+ resolution
399
+ );
400
+ return entry;
401
+ },
402
+ };
403
+
404
+ const cg = codegenExpression({
405
+ forbidden: [],
406
+ allowed: ["datum", "undefined"],
407
+ globalvar: "globalObject",
408
+ fieldvar: "datum",
409
+ functions: (visitor) => buildFunctions(visitor, helperContext),
410
+ });
411
+
138
412
  const parsed = parseExpression(expr);
139
413
  const generatedCode = cg(parsed);
140
414
 
@@ -157,6 +431,12 @@ export default function createFunction(expr, globalObject = {}) {
157
431
  exprFunction.fields = generatedCode.fields;
158
432
  exprFunction.globals = generatedCode.globals;
159
433
  exprFunction.code = generatedCode.code;
434
+ // Reactive bookkeeping lives outside the generated expression body.
435
+ // The expression runtime subscribes to these refs and invalidates the
436
+ // compiled expression when the referenced scale changes.
437
+ exprFunction.scaleDependencies = Array.from(
438
+ scaleDependenciesByChannel.values()
439
+ );
160
440
 
161
441
  return exprFunction;
162
442
  } catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"axisGridView.d.ts","sourceRoot":"","sources":["../../../src/view/axisGridView.js"],"names":[],"mappings":"AAIA;;;GAGG;AAEH;;;;;;GAMG;AACH;IACI;;;;;;;OAOG;IACH,uBAPW,IAAI,QAEJ,MAAM,WADN,OAAO,yBAAyB,EAAE,OAAO,gBAEzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,YAC3B,OAAO,WAAW,EAAE,WAAW,EAkBzC;IAHG,0CAA0B;IAK9B,kDAEC;CAKJ;gCA7CY,OAAO,oBAAoB,EAAE,wBAAwB;iCACrD,OAAO,iBAAiB,EAAE,kBAAkB;;;;mBAM5C,OAAO,WAAW,EAAE,OAAO;;;;mBAC3B,OAAO,iBAAiB,EAAE,IAAI;;;;yBAC9B,OAAO,iBAAiB,EAAE,UAAU;sBAd3B,gBAAgB"}
1
+ {"version":3,"file":"axisGridView.d.ts","sourceRoot":"","sources":["../../../src/view/axisGridView.js"],"names":[],"mappings":"AAIA;;;GAGG;AAEH;;;;;;GAMG;AACH;IACI;;;;;;;OAOG;IACH,uBAPW,IAAI,QAEJ,MAAM,WADN,OAAO,yBAAyB,EAAE,OAAO,gBAEzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,YAC3B,OAAO,WAAW,EAAE,WAAW,EAmBzC;IAJG,0CAA0B;IAM9B,kDAEC;CAKJ;gCA9CY,OAAO,oBAAoB,EAAE,wBAAwB;iCACrD,OAAO,iBAAiB,EAAE,kBAAkB;;;;mBAM5C,OAAO,WAAW,EAAE,OAAO;;;;mBAC3B,OAAO,iBAAiB,EAAE,IAAI;;;;yBAC9B,OAAO,iBAAiB,EAAE,UAAU;sBAd3B,gBAAgB"}
@@ -1,6 +1,6 @@
1
1
  import LayerView from "./layerView.js";
2
2
  import { orient2channel } from "./axisView.js";
3
- import { markViewAsNonAddressable } from "./viewSelectors.js";
3
+ import { markViewAsChrome, markViewAsNonAddressable } from "./viewSelectors.js";
4
4
 
5
5
  /**
6
6
  * @typedef {import("../spec/channel.js").PrimaryPositionalChannel} PositionalChannel
@@ -39,6 +39,7 @@ export default class AxisGridView extends LayerView {
39
39
  this.axisProps = axisProps;
40
40
 
41
41
  markViewAsNonAddressable(this, { skipSubtree: true });
42
+ markViewAsChrome(this, { skipSubtree: true });
42
43
  }
43
44
 
44
45
  getOrient() {
@@ -1 +1 @@
1
- {"version":3,"file":"axisView.d.ts","sourceRoot":"","sources":["../../../src/view/axisView.js"],"names":[],"mappings":"AAuCA;;GAEG;AACH,kIAEC;AAqhBD;;;;GAIG;AACH,wFAHW,MAAM,uCAqNhB;AAlwBD;;GAEG;AACH,8BAFU,MAAM,CAAC,OAAO,oBAAoB,EAAE,wBAAwB,EAAE,sCAAY,CAAC,CAKnF;AAEF;;GAEG;AACH,8BAFU,MAAM,uCAAa,OAAO,oBAAoB,EAAE,wBAAwB,CAAC,CAMjF;AASF;;;;;GAKG;AACH;IAeI;;;;;;;;OAQG;IAEH;;;;;;;OAOG;IACH,6DALW,MAAM,WADN,OAAO,yBAAyB,EAAE,OAAO,gBAEzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,YAC3B,OAAO,WAAW,EAAE,WAAW,EAsDzC;IARG,iFAA8B;IA8ClC,+BAEC;;CAuEJ;sBAxPqB,gBAAgB"}
1
+ {"version":3,"file":"axisView.d.ts","sourceRoot":"","sources":["../../../src/view/axisView.js"],"names":[],"mappings":"AAuCA;;GAEG;AACH,kIAEC;AAshBD;;;;GAIG;AACH,wFAHW,MAAM,uCAqNhB;AAnwBD;;GAEG;AACH,8BAFU,MAAM,CAAC,OAAO,oBAAoB,EAAE,wBAAwB,EAAE,sCAAY,CAAC,CAKnF;AAEF;;GAEG;AACH,8BAFU,MAAM,uCAAa,OAAO,oBAAoB,EAAE,wBAAwB,CAAC,CAMjF;AASF;;;;;GAKG;AACH;IAeI;;;;;;;;OAQG;IAEH;;;;;;;OAOG;IACH,6DALW,MAAM,WADN,OAAO,yBAAyB,EAAE,OAAO,gBAEzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,YAC3B,OAAO,WAAW,EAAE,WAAW,EAuDzC;IATG,iFAA8B;IA+ClC,+BAEC;;CAuEJ;sBAzPqB,gBAAgB"}
@@ -1,7 +1,7 @@
1
1
  import LayerView from "./layerView.js";
2
2
  import { FlexDimensions } from "./layout/flexLayout.js";
3
3
  import UnitView from "./unitView.js";
4
- import { markViewAsNonAddressable } from "./viewSelectors.js";
4
+ import { markViewAsChrome, markViewAsNonAddressable } from "./viewSelectors.js";
5
5
  import { getConfiguredAxisDefaults } from "../config/axisConfig.js";
6
6
 
7
7
  const CHROM_LAYER_NAME = "chromosome_ticks_and_labels";
@@ -135,6 +135,7 @@ export default class AxisView extends LayerView {
135
135
  );
136
136
 
137
137
  markViewAsNonAddressable(this, { skipSubtree: true });
138
+ markViewAsChrome(this, { skipSubtree: true });
138
139
  }
139
140
 
140
141
  async initializeChildren() {
@@ -1 +1 @@
1
- {"version":3,"file":"facetView.d.ts","sourceRoot":"","sources":["../../../src/view/facetView.js"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;GAcG;AACH;IACI;;;;;;;OAOG;IACH;;;;;;OAMG;IACH,kBALW,OAAO,gBAAgB,EAAE,SAAS,WAClC,OAAO,gBAAgB,EAAE,WAAW,UACpC,aAAa,QACb,MAAM,EA+BhB;IA1BG,UAAgB;IAEhB,WAEC;IAED;;;;OAIG;IACH,aAFU,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAavC;IAED,oDAAoD;IACpD,kBADY,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,CACa;IAajE,sBAIC;IAED;;;OAGG;IACH,qBAFW,KAAK,GAAG,QAAQ,OAwB1B;IAED,qBAyCC;IAED,qBASC;IA6BD,kDAgBC;IAOD;;;;OAIG;IACH,gBAJW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,WAAW,EAAE,gBAAgB,QA2L9C;IA3KO,wBAA0B;CA4KrC;2BAlbY,QAAQ,GAAG,KAAK;;;;;;aA4CnB,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE;;;0BA3Df,oBAAoB;qBACzB,eAAe"}
1
+ {"version":3,"file":"facetView.d.ts","sourceRoot":"","sources":["../../../src/view/facetView.js"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;GAcG;AACH;IACI;;;;;;;OAOG;IACH;;;;;;OAMG;IACH,kBALW,OAAO,gBAAgB,EAAE,SAAS,WAClC,OAAO,gBAAgB,EAAE,WAAW,UACpC,aAAa,QACb,MAAM,EAgChB;IA3BG,UAAgB;IAEhB,WAEC;IAED;;;;OAIG;IACH,aAFU,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAcvC;IAED,oDAAoD;IACpD,kBADY,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,CACa;IAajE,sBAIC;IAED;;;OAGG;IACH,qBAFW,KAAK,GAAG,QAAQ,OAwB1B;IAED,qBAyCC;IAED,qBASC;IA6BD,kDAgBC;IAOD;;;;OAIG;IACH,gBAJW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,WAAW,EAAE,gBAAgB,QA2L9C;IA3KO,wBAA0B;CA4KrC;2BAnbY,QAAQ,GAAG,KAAK;;;;;;aA4CnB,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE;;;0BA3Df,oBAAoB;qBACzB,eAAe"}