@fjader/svelte 0.2.4 → 0.2.5

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 (139) hide show
  1. package/README.md +1 -1
  2. package/dist/animation/EntranceGate.d.ts +52 -0
  3. package/dist/animation/EntranceGate.js +93 -0
  4. package/dist/animation/TransitionClock.d.ts +61 -30
  5. package/dist/animation/TransitionClock.js +67 -29
  6. package/dist/animation/transition-log.d.ts +41 -0
  7. package/dist/animation/transition-log.js +57 -0
  8. package/dist/components/axes/AxisBottom.svelte +4 -2
  9. package/dist/components/axes/AxisCenter.svelte +4 -2
  10. package/dist/components/axes/AxisLeft.svelte +4 -2
  11. package/dist/components/axes/BandAxisTicks.svelte +3 -2
  12. package/dist/components/charts/ChartCell.svelte +32 -5
  13. package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
  14. package/dist/components/charts/ChartContainer.svelte +45 -3
  15. package/dist/components/charts/ChartContainer.svelte.d.ts +7 -1
  16. package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
  17. package/dist/components/charts/ChartHover.svelte.js +22 -0
  18. package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
  19. package/dist/components/charts/ChartVisibility.svelte.js +67 -0
  20. package/dist/components/guides/Grid.svelte +12 -1
  21. package/dist/components/marks/AreaMarkX.svelte +16 -4
  22. package/dist/components/marks/AreaMarkY.svelte +16 -4
  23. package/dist/components/marks/BarMarkX.svelte +7 -3
  24. package/dist/components/marks/BarMarkY.svelte +7 -3
  25. package/dist/components/marks/DotsMark.svelte +6 -3
  26. package/dist/components/marks/LineMark.svelte +11 -8
  27. package/dist/components/marks/ParallelCoordinatesMark.svelte +7 -2
  28. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +2 -0
  29. package/dist/components/marks/RectMark.svelte +6 -3
  30. package/dist/components/marks/RectXMark.svelte +7 -3
  31. package/dist/components/marks/RectYMark.svelte +7 -3
  32. package/dist/components/marks/RuleMarkX.svelte +30 -26
  33. package/dist/components/marks/RuleMarkY.svelte +30 -26
  34. package/dist/components/marks/TextMark.svelte +44 -5
  35. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +16 -0
  36. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +398 -54
  37. package/dist/components/marks/lifecycle/domain-queries.d.ts +11 -2
  38. package/dist/components/marks/lifecycle/domain-queries.js +33 -6
  39. package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
  40. package/dist/components/marks/lifecycle/domain-registration.js +15 -14
  41. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +9 -1
  42. package/dist/components/marks/lifecycle/other-rows.svelte.js +2 -2
  43. package/dist/components/marks/lifecycle/query-builder.d.ts +27 -2
  44. package/dist/components/marks/lifecycle/query-builder.js +23 -20
  45. package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
  46. package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
  47. package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
  48. package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
  49. package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
  50. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  51. package/dist/components/marks/utils/reactive-config.js +3 -0
  52. package/dist/components/rendering/Areas.svelte +57 -19
  53. package/dist/components/rendering/Areas.svelte.d.ts +12 -0
  54. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +1 -1
  55. package/dist/components/rendering/Dots.svelte +23 -3
  56. package/dist/components/rendering/Dots.svelte.d.ts +6 -0
  57. package/dist/components/rendering/GridLine.svelte +13 -1
  58. package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
  59. package/dist/components/rendering/Lines.svelte +48 -13
  60. package/dist/components/rendering/Lines.svelte.d.ts +12 -0
  61. package/dist/components/rendering/ParallelLines.svelte +5 -5
  62. package/dist/components/rendering/Rects.svelte +51 -11
  63. package/dist/components/rendering/Rects.svelte.d.ts +16 -2
  64. package/dist/components/rendering/RuleX.svelte +1 -1
  65. package/dist/components/rendering/RuleY.svelte +1 -1
  66. package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
  67. package/dist/components/rendering/activity-reads.d.ts +41 -24
  68. package/dist/components/rendering/activity-reads.js +54 -31
  69. package/dist/components/rendering/uniform-sizes.d.ts +1 -1
  70. package/dist/components/rendering/uniform-sizes.js +1 -1
  71. package/dist/config/ChartConfig.svelte.d.ts +7 -0
  72. package/dist/config/ChartConfig.svelte.js +17 -0
  73. package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
  74. package/dist/gpu/GPUCoordinator.svelte.js +164 -7
  75. package/dist/gpu/MorphedChannels.d.ts +11 -2
  76. package/dist/gpu/MorphedChannels.js +33 -11
  77. package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
  78. package/dist/gpu/ScaleTemplateSystem.js +58 -2
  79. package/dist/index.d.ts +17 -4
  80. package/dist/index.js +19 -4
  81. package/dist/interactors/TooltipManager.svelte.js +14 -5
  82. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
  83. package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
  84. package/dist/query-engine/FieldActivity.svelte.d.ts +2 -3
  85. package/dist/query-engine/FieldActivity.svelte.js +1 -2
  86. package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
  87. package/dist/query-engine/MarkHandle.svelte.js +33 -1
  88. package/dist/query-engine/QueryEngine.d.ts +225 -5
  89. package/dist/query-engine/QueryEngine.js +671 -41
  90. package/dist/query-engine/categorical-identity.d.ts +34 -8
  91. package/dist/query-engine/categorical-identity.js +138 -73
  92. package/dist/query-engine/debug/query-log.d.ts +39 -0
  93. package/dist/query-engine/debug/query-log.js +90 -0
  94. package/dist/query-engine/duckdb.d.ts +55 -8
  95. package/dist/query-engine/duckdb.js +82 -30
  96. package/dist/query-engine/index.d.ts +7 -4
  97. package/dist/query-engine/index.js +3 -3
  98. package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
  99. package/dist/query-engine/preagg/BuildQueue.js +51 -15
  100. package/dist/query-engine/preagg/Preagg.d.ts +46 -3
  101. package/dist/query-engine/preagg/Preagg.js +159 -31
  102. package/dist/query-engine/preagg/plan.d.ts +28 -1
  103. package/dist/query-engine/preagg/plan.js +128 -8
  104. package/dist/query-engine/sliced-lane.d.ts +108 -0
  105. package/dist/query-engine/sliced-lane.js +299 -0
  106. package/dist/query-engine/source-routing.d.ts +106 -0
  107. package/dist/query-engine/source-routing.js +189 -0
  108. package/dist/scales/BandScaleState.svelte.d.ts +9 -1
  109. package/dist/scales/BandScaleState.svelte.js +16 -5
  110. package/dist/scales/ChartScales.svelte.d.ts +3 -0
  111. package/dist/scales/ChartScales.svelte.js +26 -8
  112. package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
  113. package/dist/scales/ColorScaleState.svelte.js +138 -26
  114. package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
  115. package/dist/scales/LinearScaleState.svelte.js +21 -39
  116. package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
  117. package/dist/scales/ScaleGeneration.svelte.js +42 -0
  118. package/dist/scales/ScaleRegistry.svelte.js +3 -1
  119. package/dist/scales/SlotTransition.d.ts +15 -1
  120. package/dist/scales/SlotTransition.js +33 -4
  121. package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
  122. package/dist/scales/TimeScaleState.svelte.js +7 -7
  123. package/dist/scales/color-utils.d.ts +0 -3
  124. package/dist/scales/color-utils.js +0 -3
  125. package/dist/scales/entered.d.ts +8 -0
  126. package/dist/scales/entered.js +10 -0
  127. package/dist/spec/LazyQueryEngine.js +2 -19
  128. package/dist/spec/MarkRegistry.d.ts +3 -1
  129. package/dist/spec/MarkRegistry.js +13 -1
  130. package/dist/spec/PlotFromSpec.svelte +1 -0
  131. package/dist/spec/export.js +11 -2
  132. package/dist/spec/types.d.ts +41 -13
  133. package/dist/sql/bin.d.ts +25 -0
  134. package/dist/sql/bin.js +108 -5
  135. package/dist/transforms/TransformCompiler.d.ts +10 -0
  136. package/dist/transforms/TransformCompiler.js +26 -0
  137. package/dist/transforms/index.js +144 -24
  138. package/dist/types.d.ts +136 -2
  139. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { activityReads, colorActivityWGSL } from './activity-reads';
2
+ import { activityReads, connectedActivityWGSL } from './activity-reads';
3
3
  import { UNIFORM_BYTES } from './uniform-sizes';
4
4
  import { getContext, onMount, onDestroy } from 'svelte';
5
5
  import { RendererHost } from './RendererHost.svelte';
@@ -38,7 +38,10 @@
38
38
  DOT_RADIUS: 36, // f32
39
39
  SHOW_BOTTOM_DOTS: 40, // u32 (0 or 1)
40
40
  FULL_DOMAIN_SPAN: 44, // f32
41
- GROUP_INDEX: 48 // u32
41
+ GROUP_INDEX: 48, // u32
42
+ ACTIVITY_SERIES_MODE: 52, // u32
43
+ ACTIVITY_SERIES_SCALE_INDEX: 56, // u32
44
+ ENTRANCE_AXIS: 60 // u32 (0 fades, 1 grows along x, 2 along y)
42
45
  };
43
46
 
44
47
  interface Props {
@@ -60,8 +63,20 @@
60
63
  colorScaleIndex?: number;
61
64
  /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
62
65
  groupIndex?: number;
66
+ /**
67
+ * The series scale selection activity is also read through, from
68
+ * `seriesData`: mode 1 and its index when the mark groups by a `z` of
69
+ * its own, mode 0 (no read) otherwise. MarkSeries decides.
70
+ */
71
+ activitySeriesMode?: number;
72
+ activitySeriesScaleIndex?: number;
63
73
  constantColor?: [number, number, number, number];
64
74
  markId?: string;
75
+ /**
76
+ * Whether the area grows from 0 along its measure axis when its chart
77
+ * enters. False fades it in, for a band with bounds of its own.
78
+ */
79
+ grow?: boolean;
65
80
  }
66
81
 
67
82
  let {
@@ -82,8 +97,11 @@
82
97
  colorMode = 0,
83
98
  colorScaleIndex = 0,
84
99
  groupIndex = 0,
100
+ activitySeriesMode = 0,
101
+ activitySeriesScaleIndex = 0,
85
102
  constantColor = DEFAULT_FILL_COLOR,
86
- markId = ''
103
+ markId = '',
104
+ grow = false
87
105
  }: Props = $props();
88
106
 
89
107
  const chartContext = getContext<ChartContext>('chart');
@@ -152,9 +170,11 @@
152
170
  showBottomDots: u32,
153
171
  fullDomainSpan: f32,
154
172
  groupIndex: u32, // Page-wide filter group index, for activity reads
155
- _pad0: u32,
156
- _pad1: u32,
157
- _pad2: u32
173
+ // The series scale selection activity is also read through, for a z of
174
+ // its own; mode 0 reads nothing.
175
+ activitySeriesMode: u32,
176
+ activitySeriesScaleIndex: u32,
177
+ entranceAxis: u32 // 0 fades in, 1 grows along x, 2 along y
158
178
  };
159
179
  @group(1) @binding(5) var<uniform> markConstants: MarkConstants;
160
180
 
@@ -186,6 +206,12 @@
186
206
  }
187
207
  }
188
208
 
209
+ // A point projected to pixels as it is during the chart's entrance: pulled
210
+ // toward 0 on the measure axis while the area grows.
211
+ fn enterPx(p: vec2<f32>) -> vec2<f32> {
212
+ return entrancePosition(projectToPixels(p), markConstants.entranceAxis);
213
+ }
214
+
189
215
  const DISCARD_POS = vec4<f32>(2.0, 2.0, 2.0, 1.0);
190
216
 
191
217
  @vertex
@@ -220,7 +246,7 @@
220
246
  output.position = DISCARD_POS;
221
247
  return output;
222
248
  }
223
- let center = projectToPixels(makePoint(dim, boundary));
249
+ let center = enterPx(makePoint(dim, boundary));
224
250
 
225
251
  var circleR: f32;
226
252
  if (markConstants.dotRadius < 0.0) {
@@ -254,9 +280,11 @@
254
280
  var output: VertexOutput;
255
281
  output.position = pixelsToClip(pos);
256
282
  output.colorValue = colorValues[instanceIdx];
257
- output.alpha = selectionDim(
258
- ${colorActivityWGSL(ACTIVITY_READS, {
283
+ output.alpha = entranceAlpha(markConstants.entranceAxis) * selectionDim(
284
+ ${connectedActivityWGSL(ACTIVITY_READS, {
259
285
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValues[instanceIdx]',
286
+ series:
287
+ 'markConstants.activitySeriesMode, markConstants.activitySeriesScaleIndex, seriesValues[instanceIdx]',
260
288
  group: 'markConstants.groupIndex'
261
289
  })}
262
290
  );
@@ -304,10 +332,10 @@
304
332
  }
305
333
 
306
334
  // Project segment endpoints for adaptive resolution
307
- let topPxCurr = projectToPixels(makePoint(dimCurr, topCurr));
308
- let topPxNext = projectToPixels(makePoint(dimNext, topNext));
309
- let botPxCurr = projectToPixels(makePoint(dimCurr, botCurr));
310
- let botPxNext = projectToPixels(makePoint(dimNext, botNext));
335
+ let topPxCurr = enterPx(makePoint(dimCurr, topCurr));
336
+ let topPxNext = enterPx(makePoint(dimNext, topNext));
337
+ let botPxCurr = enterPx(makePoint(dimCurr, botCurr));
338
+ let botPxNext = enterPx(makePoint(dimNext, botNext));
311
339
 
312
340
  // Adaptive sub-segment resolution: more segments for longer pixel spans.
313
341
  // Use the longer of top/bottom chord for resolution calculation.
@@ -336,10 +364,10 @@
336
364
  let dimPrev = dimValues[prevIdx];
337
365
  let dimAfter = dimValues[afterIdx];
338
366
 
339
- let topPxPrev = projectToPixels(makePoint(dimPrev, topValues[prevIdx]));
340
- let topPxAfter = projectToPixels(makePoint(dimAfter, topValues[afterIdx]));
341
- let botPxPrev = projectToPixels(makePoint(dimPrev, bottomValues[prevIdx]));
342
- let botPxAfter = projectToPixels(makePoint(dimAfter, bottomValues[afterIdx]));
367
+ let topPxPrev = enterPx(makePoint(dimPrev, topValues[prevIdx]));
368
+ let topPxAfter = enterPx(makePoint(dimAfter, topValues[afterIdx]));
369
+ let botPxPrev = enterPx(makePoint(dimPrev, bottomValues[prevIdx]));
370
+ let botPxAfter = enterPx(makePoint(dimAfter, bottomValues[afterIdx]));
343
371
 
344
372
  // Compute t for this vertex from effective resolution (not geometry buffer)
345
373
  let subSegSize = 1.0 / f32(effectiveRes);
@@ -359,9 +387,11 @@
359
387
  var output: VertexOutput;
360
388
  output.position = pixelsToClip(finalPos);
361
389
  output.colorValue = colorValues[currIdx];
362
- output.alpha = markConstants.fillOpacity * selectionDim(
363
- ${colorActivityWGSL(ACTIVITY_READS, {
390
+ output.alpha = markConstants.fillOpacity * entranceAlpha(markConstants.entranceAxis) * selectionDim(
391
+ ${connectedActivityWGSL(ACTIVITY_READS, {
364
392
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValues[currIdx]',
393
+ series:
394
+ 'markConstants.activitySeriesMode, markConstants.activitySeriesScaleIndex, seriesValues[currIdx]',
365
395
  group: 'markConstants.groupIndex'
366
396
  })}
367
397
  );
@@ -471,7 +501,15 @@
471
501
  view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
472
502
  view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
473
503
  view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
504
+ view.setUint32(UNIFORM_OFFSETS.ACTIVITY_SERIES_MODE, activitySeriesMode, LE);
505
+ view.setUint32(UNIFORM_OFFSETS.ACTIVITY_SERIES_SCALE_INDEX, activitySeriesScaleIndex, LE);
474
506
  view.setUint32(UNIFORM_OFFSETS.ORIENTATION, orientation === 'horizontal' ? 1 : 0, LE);
507
+ // Grows along the boundary axis: y for a vertical area, x for a horizontal one.
508
+ view.setUint32(
509
+ UNIFORM_OFFSETS.ENTRANCE_AXIS,
510
+ grow ? (orientation === 'horizontal' ? 1 : 2) : 0,
511
+ LE
512
+ );
475
513
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
476
514
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
477
515
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
@@ -18,8 +18,20 @@ interface Props {
18
18
  colorScaleIndex?: number;
19
19
  /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
20
20
  groupIndex?: number;
21
+ /**
22
+ * The series scale selection activity is also read through, from
23
+ * `seriesData`: mode 1 and its index when the mark groups by a `z` of
24
+ * its own, mode 0 (no read) otherwise. MarkSeries decides.
25
+ */
26
+ activitySeriesMode?: number;
27
+ activitySeriesScaleIndex?: number;
21
28
  constantColor?: [number, number, number, number];
22
29
  markId?: string;
30
+ /**
31
+ * Whether the area grows from 0 along its measure axis when its chart
32
+ * enters. False fades it in, for a band with bounds of its own.
33
+ */
34
+ grow?: boolean;
23
35
  }
24
36
  declare const Areas: import("svelte").Component<Props, {}, "">;
25
37
  type Areas = ReturnType<typeof Areas>;
@@ -59,7 +59,7 @@ export declare class BaseGPURenderer {
59
59
  */
60
60
  writeMorphed(bandAxis: 'x' | 'y', bandIds: ArrayLike<number>, channels: readonly (readonly [number, AllowSharedBufferSource])[],
61
61
  /** Bindings that land at once rather than travel (see MorphedChannels.write). */
62
- land?: readonly number[]): void;
62
+ land?: readonly number[] | 'all'): void;
63
63
  /**
64
64
  * Write data to a uniform binding.
65
65
  */
@@ -43,6 +43,12 @@
43
43
  alphaData: Float32Array | null;
44
44
  facetData?: Float32Array | null;
45
45
  dataVersion: number;
46
+ /**
47
+ * The data version whose first write lands without morphing, or 0: a
48
+ * catch-up result (MarkLifecycleV2.landsFor). Later writes of that version
49
+ * animate as usual.
50
+ */
51
+ landsFor?: number;
46
52
  zIndex?: number;
47
53
  colorMode?: number;
48
54
  colorScaleIndex?: number;
@@ -74,6 +80,7 @@
74
80
  alphaData,
75
81
  facetData = null,
76
82
  dataVersion,
83
+ landsFor = 0,
77
84
  zIndex = 0,
78
85
  colorMode = 0,
79
86
  colorScaleIndex = 0,
@@ -188,7 +195,7 @@
188
195
  output.colorValue = colorValue;
189
196
  output.uv = quadPosition;
190
197
  output.radius = displayRadius;
191
- output.alpha = alpha * selectionDim(
198
+ output.alpha = alpha * entranceAlpha(0u) * selectionDim(
192
199
  ${markActivityWGSL(ACTIVITY_READS, {
193
200
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
194
201
  x: 'x',
@@ -260,6 +267,18 @@
260
267
 
261
268
  // --- WRITING EFFECTS ---
262
269
 
270
+ // Whether this write lands: the first of the catch-up's data version
271
+ // (landsFor). Plain, not $state: only the write effect reads and sets it.
272
+ // Assumes a catch-up result is ONE morph write: should it ever take two
273
+ // (values, then band ids after the instant layout), the second springs.
274
+ // A morph SPRING right after a morph snap on a return points here.
275
+ let lastMorphedVersion = -1;
276
+ function landsNow(): boolean {
277
+ const first = dataVersion !== lastMorphedVersion;
278
+ lastMorphedVersion = dataVersion;
279
+ return first && landsFor !== 0 && landsFor === dataVersion;
280
+ }
281
+
263
282
  // The measure axis's single channel morphs, as ONE write per data update,
264
283
  // so one update starts one transition. The band's ids go with it: they are
265
284
  // how a row set that is not the one being animated gets noticed and snapped.
@@ -269,8 +288,9 @@
269
288
  // design question rather than a consequence of this one.
270
289
  $effect(() => {
271
290
  if (!renderer?.resourcesCreated || !bandAxis) return;
272
- if (bandAxis === 'x') renderer.writeMorphed('x', xData, [[BINDINGS.Y, yData]]);
273
- else renderer.writeMorphed('y', yData, [[BINDINGS.X, xData]]);
291
+ const land = landsNow() ? 'all' : undefined;
292
+ if (bandAxis === 'x') renderer.writeMorphed('x', xData, [[BINDINGS.Y, yData]], land);
293
+ else renderer.writeMorphed('y', yData, [[BINDINGS.X, xData]], land);
274
294
  });
275
295
  $effect(() => {
276
296
  if (renderer?.resourcesCreated && !morphX) renderer.writeAttribute(BINDINGS.X, xData);
@@ -6,6 +6,12 @@ interface Props {
6
6
  alphaData: Float32Array | null;
7
7
  facetData?: Float32Array | null;
8
8
  dataVersion: number;
9
+ /**
10
+ * The data version whose first write lands without morphing, or 0: a
11
+ * catch-up result (MarkLifecycleV2.landsFor). Later writes of that version
12
+ * animate as usual.
13
+ */
14
+ landsFor?: number;
9
15
  zIndex?: number;
10
16
  colorMode?: number;
11
17
  colorScaleIndex?: number;
@@ -44,6 +44,11 @@
44
44
  * way a bar's height is. Omitted, every line is fully drawn.
45
45
  */
46
46
  alpha?: Float32Array | null;
47
+ /**
48
+ * The band layout whose first opacity write lands without morphing, or 0
49
+ * (a layout that landed at once; see BandScaleState.slotsLandedLayout).
50
+ */
51
+ landsFor?: number;
47
52
  /** The band axis these lines belong to, when `alpha` should morph. */
48
53
  bandAxis?: 'x' | 'y' | null;
49
54
  color?: [number, number, number, number];
@@ -55,6 +60,7 @@
55
60
  axis,
56
61
  positions,
57
62
  alpha = null,
63
+ landsFor = 0,
58
64
  bandAxis = null,
59
65
  color = [0.8, 0.8, 0.8, 0.3],
60
66
  lineWidth = 1,
@@ -175,9 +181,15 @@ ${placement}
175
181
  // the positions are that id list — which is what writeMorphed checks rows
176
182
  // against.
177
183
  const morphs = $derived(!!bandAxis && !!alpha && alpha.length === positions?.length);
184
+ // The landed layout last written; plain, read and set by the write alone.
185
+ let lastLandedLayout = 0;
178
186
  $effect(() => {
179
187
  if (!renderer?.resourcesCreated || !morphs) return;
180
- renderer.writeMorphed(bandAxis!, positions, [[BINDINGS.ALPHA, alpha!]]);
188
+ // Only the first write after that layout lands: the opacity it changed.
189
+ const first = landsFor !== lastLandedLayout;
190
+ lastLandedLayout = landsFor;
191
+ const land = first && landsFor !== 0 ? 'all' : undefined;
192
+ renderer.writeMorphed(bandAxis!, positions, [[BINDINGS.ALPHA, alpha!]], land);
181
193
  });
182
194
 
183
195
  // Otherwise every line is fully drawn. Keyed on the line COUNT alone, because
@@ -9,6 +9,11 @@ interface Props {
9
9
  * way a bar's height is. Omitted, every line is fully drawn.
10
10
  */
11
11
  alpha?: Float32Array | null;
12
+ /**
13
+ * The band layout whose first opacity write lands without morphing, or 0
14
+ * (a layout that landed at once; see BandScaleState.slotsLandedLayout).
15
+ */
16
+ landsFor?: number;
12
17
  /** The band axis these lines belong to, when `alpha` should morph. */
13
18
  bandAxis?: 'x' | 'y' | null;
14
19
  color?: [number, number, number, number];
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { activityReads, colorActivityWGSL } from './activity-reads';
2
+ import { activityReads, connectedActivityWGSL } from './activity-reads';
3
3
  import { UNIFORM_BYTES } from './uniform-sizes';
4
4
  import { getContext, onMount, onDestroy } from 'svelte';
5
5
  import { RendererHost } from './RendererHost.svelte';
@@ -38,7 +38,10 @@
38
38
  GLOBAL_ALPHA: 36, // f32
39
39
  GROUP_INDEX: 40, // u32
40
40
  ACTIVITY_COLOR_MODE: 44, // u32
41
- ACTIVITY_COLOR_SCALE_INDEX: 48 // u32
41
+ ACTIVITY_COLOR_SCALE_INDEX: 48, // u32
42
+ ACTIVITY_SERIES_MODE: 52, // u32
43
+ ACTIVITY_SERIES_SCALE_INDEX: 56, // u32
44
+ ENTRANCE_AXIS: 60 // u32 (0 fades, 1 grows along x, 2 along y)
42
45
  };
43
46
 
44
47
  interface Props {
@@ -63,11 +66,23 @@
63
66
  */
64
67
  activityColorMode?: number | null;
65
68
  activityColorScaleIndex?: number | null;
69
+ /**
70
+ * The series scale selection activity is also read through, from
71
+ * `seriesData`: mode 1 and its index when the mark groups by a `z` of
72
+ * its own, mode 0 (no read) otherwise. MarkSeries decides.
73
+ */
74
+ activitySeriesMode?: number;
75
+ activitySeriesScaleIndex?: number;
66
76
  constantColor?: [number, number, number, number];
67
77
  m4Decimator?: M4Decimator | null;
68
78
  dotsRung?: import('../../lod/DotsRung.svelte').DotsRung | null;
69
79
  globalAlpha?: number;
70
80
  markId?: string;
81
+ /**
82
+ * The axis this line grows along when its chart enters, or null to fade
83
+ * in. A line mark fades; an area's outline grows with its fill.
84
+ */
85
+ grow?: 'x' | 'y' | null;
71
86
  }
72
87
 
73
88
  let {
@@ -86,11 +101,14 @@
86
101
  groupIndex = 0,
87
102
  activityColorMode = null,
88
103
  activityColorScaleIndex = null,
104
+ activitySeriesMode = 0,
105
+ activitySeriesScaleIndex = 0,
89
106
  constantColor = DEFAULT_STROKE_COLOR,
90
107
  m4Decimator = null,
91
108
  dotsRung = null,
92
109
  globalAlpha = 1.0,
93
- markId = ''
110
+ markId = '',
111
+ grow = null
94
112
  }: Props = $props();
95
113
 
96
114
  const chartContext = getContext<ChartContext>('chart');
@@ -164,10 +182,20 @@
164
182
  // own colour by default; an area's outline, drawn in a constant stroke,
165
183
  // reads its fill's so it dims with the fill.
166
184
  activityColorMode: u32,
167
- activityColorScaleIndex: u32
185
+ activityColorScaleIndex: u32,
186
+ // The series scale it is also read through, for a z of its own; mode 0
187
+ // reads nothing.
188
+ activitySeriesMode: u32,
189
+ activitySeriesScaleIndex: u32,
190
+ entranceAxis: u32 // 0 fades in, 1 grows along x, 2 along y
168
191
  };
169
192
  @group(1) @binding(4) var<uniform> markConstants: MarkConstants;
170
193
 
194
+ // A point projected to pixels as it is during the chart's entrance.
195
+ fn enterPx(p: vec2<f32>) -> vec2<f32> {
196
+ return entrancePosition(projectToPixels(p), markConstants.entranceAxis);
197
+ }
198
+
171
199
  struct VertexOutput {
172
200
  @builtin(position) position: vec4<f32>,
173
201
  @location(0) colorValue: f32,
@@ -211,7 +239,7 @@
211
239
  output.position = DISCARD_POS;
212
240
  return output;
213
241
  }
214
- let center = projectToPixels(vec2<f32>(x, y));
242
+ let center = enterPx(vec2<f32>(x, y));
215
243
 
216
244
  // JS computes dotRadius via DotsRung. 0 = no dots. >0 = show at this size.
217
245
  let circleR = select(halfWidth, markConstants.dotRadius * frameUniforms.devicePixelRatio,
@@ -221,10 +249,12 @@
221
249
  var output: VertexOutput;
222
250
  output.position = pixelsToClip(pos);
223
251
  output.colorValue = colorValues[instanceIdx];
224
- output.alpha = markConstants.globalAlpha * selectionDim(
225
- ${colorActivityWGSL(ACTIVITY_READS, {
252
+ output.alpha = markConstants.globalAlpha * entranceAlpha(markConstants.entranceAxis) * selectionDim(
253
+ ${connectedActivityWGSL(ACTIVITY_READS, {
226
254
  color:
227
255
  'markConstants.activityColorMode, markConstants.activityColorScaleIndex, colorValues[instanceIdx]',
256
+ series:
257
+ 'markConstants.activitySeriesMode, markConstants.activitySeriesScaleIndex, seriesValues[instanceIdx]',
228
258
  group: 'markConstants.groupIndex'
229
259
  })}
230
260
  );
@@ -258,8 +288,8 @@
258
288
  }
259
289
 
260
290
  // Project segment endpoints to pixel space
261
- let pCurr = projectToPixels(vec2<f32>(xCurr, yCurr));
262
- let pNext = projectToPixels(vec2<f32>(xNext, yNext));
291
+ let pCurr = enterPx(vec2<f32>(xCurr, yCurr));
292
+ let pNext = enterPx(vec2<f32>(xNext, yNext));
263
293
 
264
294
  // Adaptive sub-segment resolution: more segments for longer pixel spans.
265
295
  // Fixed-resolution curves (linear, step) have CURVE_RESOLUTION <= 3;
@@ -286,8 +316,8 @@
286
316
  nextIdx + 1u >= pointCount || seriesValues[nextIdx + 1u] != seriesNext);
287
317
 
288
318
  // Project neighbor control points
289
- let pPrev = projectToPixels(vec2<f32>(xValues[prevIdx], yValues[prevIdx]));
290
- let pAfter = projectToPixels(vec2<f32>(xValues[afterIdx], yValues[afterIdx]));
319
+ let pPrev = enterPx(vec2<f32>(xValues[prevIdx], yValues[prevIdx]));
320
+ let pAfter = enterPx(vec2<f32>(xValues[afterIdx], yValues[afterIdx]));
291
321
 
292
322
  // Compute t values from effective resolution (not geometry buffer)
293
323
  let subSegSize = 1.0 / f32(effectiveRes);
@@ -322,10 +352,12 @@
322
352
  var output: VertexOutput;
323
353
  output.position = pixelsToClip(finalPos);
324
354
  output.colorValue = colorValues[currIdx];
325
- output.alpha = markConstants.globalAlpha * selectionDim(
326
- ${colorActivityWGSL(ACTIVITY_READS, {
355
+ output.alpha = markConstants.globalAlpha * entranceAlpha(markConstants.entranceAxis) * selectionDim(
356
+ ${connectedActivityWGSL(ACTIVITY_READS, {
327
357
  color:
328
358
  'markConstants.activityColorMode, markConstants.activityColorScaleIndex, colorValues[currIdx]',
359
+ series:
360
+ 'markConstants.activitySeriesMode, markConstants.activitySeriesScaleIndex, seriesValues[currIdx]',
329
361
  group: 'markConstants.groupIndex'
330
362
  })}
331
363
  );
@@ -443,6 +475,9 @@
443
475
  view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
444
476
  view.setUint32(UNIFORM_OFFSETS.ACTIVITY_COLOR_MODE, activityMode, LE);
445
477
  view.setUint32(UNIFORM_OFFSETS.ACTIVITY_COLOR_SCALE_INDEX, activityScale, LE);
478
+ view.setUint32(UNIFORM_OFFSETS.ACTIVITY_SERIES_MODE, activitySeriesMode, LE);
479
+ view.setUint32(UNIFORM_OFFSETS.ACTIVITY_SERIES_SCALE_INDEX, activitySeriesScaleIndex, LE);
480
+ view.setUint32(UNIFORM_OFFSETS.ENTRANCE_AXIS, grow === 'x' ? 1 : grow === 'y' ? 2 : 0, LE);
446
481
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
447
482
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
448
483
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
@@ -22,11 +22,23 @@ interface Props {
22
22
  */
23
23
  activityColorMode?: number | null;
24
24
  activityColorScaleIndex?: number | null;
25
+ /**
26
+ * The series scale selection activity is also read through, from
27
+ * `seriesData`: mode 1 and its index when the mark groups by a `z` of
28
+ * its own, mode 0 (no read) otherwise. MarkSeries decides.
29
+ */
30
+ activitySeriesMode?: number;
31
+ activitySeriesScaleIndex?: number;
25
32
  constantColor?: [number, number, number, number];
26
33
  m4Decimator?: M4Decimator | null;
27
34
  dotsRung?: import('../../lod/DotsRung.svelte').DotsRung | null;
28
35
  globalAlpha?: number;
29
36
  markId?: string;
37
+ /**
38
+ * The axis this line grows along when its chart enters, or null to fade
39
+ * in. A line mark fades; an area's outline grows with its fill.
40
+ */
41
+ grow?: 'x' | 'y' | null;
30
42
  }
31
43
  declare const Lines: import("svelte").Component<Props, {}, "">;
32
44
  type Lines = ReturnType<typeof Lines>;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { activityReads, colorActivityWGSL } from './activity-reads';
2
+ import { activityReads, connectedActivityWGSL } from './activity-reads';
3
3
  import { PARALLEL_MAX_AXES, UNIFORM_BYTES } from './uniform-sizes';
4
4
  import { getContext, onMount, onDestroy } from 'svelte';
5
5
  import { RendererHost } from './RendererHost.svelte';
@@ -199,8 +199,8 @@
199
199
  var output: VertexOutput;
200
200
  output.position = pixelsToClip(pos);
201
201
  output.colorValue = colorValues[rowIdx];
202
- output.alpha = config.opacity * selectionDim(
203
- ${colorActivityWGSL(ACTIVITY_READS, {
202
+ output.alpha = config.opacity * entranceAlpha(0u) * selectionDim(
203
+ ${connectedActivityWGSL(ACTIVITY_READS, {
204
204
  color: 'config.colorMode, config.colorScaleIndex, colorValues[rowIdx]',
205
205
  group: 'config.groupIndex'
206
206
  })}
@@ -283,8 +283,8 @@
283
283
  var output: VertexOutput;
284
284
  output.position = pixelsToClip(finalPos);
285
285
  output.colorValue = colorValues[rowIdx];
286
- output.alpha = config.opacity * selectionDim(
287
- ${colorActivityWGSL(ACTIVITY_READS, {
286
+ output.alpha = config.opacity * entranceAlpha(0u) * selectionDim(
287
+ ${connectedActivityWGSL(ACTIVITY_READS, {
288
288
  color: 'config.colorMode, config.colorScaleIndex, colorValues[rowIdx]',
289
289
  group: 'config.groupIndex'
290
290
  })}
@@ -49,7 +49,8 @@
49
49
  DX: 32,
50
50
  DY: 36,
51
51
  OTHER_COLOR_ON_BAND: 40,
52
- SPLIT_ALONG_X: 44
52
+ SPLIT_ALONG_X: 44,
53
+ ENTRANCE_AXIS: 48
53
54
  };
54
55
 
55
56
  interface Props {
@@ -67,6 +68,12 @@
67
68
  share?: SplitProp | null;
68
69
  facetData?: Float32Array | null;
69
70
  dataVersion: number;
71
+ /**
72
+ * The data version whose first write lands without morphing, or 0: a
73
+ * catch-up result (MarkLifecycleV2.landsFor). Later writes of that version
74
+ * animate as usual.
75
+ */
76
+ landsFor?: number;
70
77
  zIndex?: number;
71
78
  colorMode?: number;
72
79
  colorScaleIndex?: number;
@@ -79,14 +86,22 @@
79
86
  markId?: string;
80
87
  /**
81
88
  * The band axis, when this mark's rows are one per category in category
82
- * order and may therefore morph. null to morph nothing.
89
+ * order and may therefore morph; or a histogram's binned axis, one row per
90
+ * bin in bin order. null to morph nothing.
83
91
  *
84
92
  * The OTHER axis's channels morph. The band's never do: they carry a
85
93
  * category id that the shader resolves through the slot table, and
86
94
  * interpolating an identity produces a fraction that resolves to some
87
- * other category. Position already animates, through the slot table.
95
+ * other category. Position already animates, through the slot table. A
96
+ * bin's edges do not change under a filter, and a re-bin snaps.
88
97
  */
89
98
  bandAxis?: 'x' | 'y' | null;
99
+ /**
100
+ * The axis this mark grows along when its chart enters: its measure axis,
101
+ * for bars and histograms anchored at 0. null fades it in instead, for
102
+ * rects with bounds of their own (a Gantt bar has no 0 to grow from).
103
+ */
104
+ grow?: 'x' | 'y' | null;
90
105
  }
91
106
 
92
107
  let {
@@ -99,6 +114,7 @@
99
114
  share = null,
100
115
  facetData = null,
101
116
  dataVersion,
117
+ landsFor = 0,
102
118
  zIndex = 0,
103
119
  colorMode = 0,
104
120
  colorScaleIndex = 0,
@@ -108,7 +124,8 @@
108
124
  dx = 0,
109
125
  dy = 0,
110
126
  markId = '',
111
- bandAxis = null
127
+ bandAxis = null,
128
+ grow = null
112
129
  }: Props = $props();
113
130
 
114
131
  const splitData = $derived(share?.data ?? null);
@@ -164,6 +181,10 @@
164
181
  dy: f32, // Vertical pixel offset
165
182
  otherColorOnBand: u32, // 1: an Other row skips its colour activity too
166
183
  splitAlongX: u32, // 1: an Other bar splits along x (horizontal bars)
184
+ entranceAxis: u32, // 0: fades in, 1: grows along x, 2: along y
185
+ _pad0: u32,
186
+ _pad1: u32,
187
+ _pad2: u32,
167
188
  };
168
189
  @group(1) @binding(6) var<uniform> markConstants: MarkConstants;
169
190
 
@@ -208,13 +229,19 @@
208
229
  // Y axis scaling (generated based on scale type)
209
230
  {{Y_AXIS_LOGIC}}
210
231
 
232
+ // The chart's entrance: both edges pulled toward 0 on the measure axis,
233
+ // so a stack grows as one block rather than its segments floating up.
234
+ let p0 = entrancePosition(vec2<f32>(scaledX0, scaledY0), markConstants.entranceAxis);
235
+ let p1 = entrancePosition(vec2<f32>(scaledX1, scaledY1), markConstants.entranceAxis);
236
+
211
237
  // Apply inset (shrink rect inward on all sides)
212
- let ix = markConstants.inset * sign(scaledX1 - scaledX0);
213
- let iy = markConstants.inset * sign(scaledY1 - scaledY0);
238
+ let ix = markConstants.inset * sign(p1.x - p0.x);
239
+ let iy = markConstants.inset * sign(p1.y - p0.y);
214
240
 
215
241
  // Interpolate rectangle corners and apply pixel offsets
216
- let finalX = mix(scaledX0 + ix, scaledX1 - ix, rectCorner.x) + markConstants.dx;
217
- let finalY = mix(scaledY0 + iy, scaledY1 - iy, rectCorner.y) + markConstants.dy;
242
+ let finalX = mix(p0.x + ix, p1.x - ix, rectCorner.x) + markConstants.dx;
243
+ let finalY = mix(p0.y + iy, p1.y - iy, rectCorner.y) + markConstants.dy;
244
+ let enterAlpha = entranceAlpha(markConstants.entranceAxis);
218
245
 
219
246
  // Convert to normalized device coordinates
220
247
  let worldPos = vec2<f32>(finalX, finalY);
@@ -239,7 +266,7 @@
239
266
  y: 'y0',
240
267
  group: 'markConstants.groupIndex'
241
268
  })};
242
- output.alpha = alpha * selectionDim(activity);
269
+ output.alpha = alpha * selectionDim(activity) * enterAlpha;
243
270
  // The unselected part is drawn only on an Other row: every other bar skips
244
271
  // its activity reads.
245
272
  output.alphaRest = output.alpha;
@@ -254,7 +281,7 @@
254
281
  bandFreeActivityY(markConstants.groupIndex),
255
282
  markConstants.splitAlongX == 1u
256
283
  );
257
- output.alphaRest = alpha * selectionDim(min(otherActivity, free));
284
+ output.alphaRest = alpha * selectionDim(min(otherActivity, free)) * enterAlpha;
258
285
  }
259
286
  return output;
260
287
  }
@@ -326,6 +353,18 @@
326
353
 
327
354
  // --- DATA WRITING EFFECTS (Independent & Fine-Grained) ---
328
355
 
356
+ // Whether this write lands: the first of the catch-up's data version
357
+ // (landsFor). Plain, not $state: only the write effect reads and sets it.
358
+ // Assumes a catch-up result is ONE morph write: should it ever take two
359
+ // (values, then band ids after the instant layout), the second springs.
360
+ // A morph SPRING right after a morph snap on a return points here.
361
+ let lastMorphedVersion = -1;
362
+ function landsNow(): boolean {
363
+ const first = dataVersion !== lastMorphedVersion;
364
+ lastMorphedVersion = dataVersion;
365
+ return first && landsFor !== 0 && landsFor === dataVersion;
366
+ }
367
+
329
368
  // The measure axis's two edges morph, as ONE write per data update, so one
330
369
  // update starts one transition. The band's ids go with them: they are how a
331
370
  // row set that is not the one being animated gets noticed and snapped.
@@ -339,7 +378,7 @@
339
378
  // split and the bar land together. A share that changes alone (a click)
340
379
  // starts a transition of its own on that clock.
341
380
  const split = splitData ? ([[BINDINGS.SHARE, splitData]] as const) : [];
342
- const land = splitData && !splitSlides ? [BINDINGS.SHARE] : undefined;
381
+ const land = landsNow() ? 'all' : splitData && !splitSlides ? [BINDINGS.SHARE] : undefined;
343
382
  if (bandAxis === 'x') {
344
383
  renderer.writeMorphed(
345
384
  'x',
@@ -419,6 +458,7 @@
419
458
  view.setFloat32(UNIFORM_OFFSETS.DY, dy, LE);
420
459
  view.setUint32(UNIFORM_OFFSETS.OTHER_COLOR_ON_BAND, otherColorOnBand ? 1 : 0, LE);
421
460
  view.setUint32(UNIFORM_OFFSETS.SPLIT_ALONG_X, splitAlongX ? 1 : 0, LE);
461
+ view.setUint32(UNIFORM_OFFSETS.ENTRANCE_AXIS, grow === 'x' ? 1 : grow === 'y' ? 2 : 0, LE);
422
462
 
423
463
  renderer.writeUniform(BINDINGS.CONSTANTS, new Uint8Array(buffer));
424
464
  }