@fjader/svelte 0.2.3 → 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 (167) hide show
  1. package/README.md +3 -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 +27 -8
  9. package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
  10. package/dist/components/axes/AxisCenter.svelte +4 -2
  11. package/dist/components/axes/AxisLeft.svelte +81 -10
  12. package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
  13. package/dist/components/axes/BandAxisTicks.svelte +4 -3
  14. package/dist/components/axes/axis-title.d.ts +30 -0
  15. package/dist/components/axes/axis-title.js +77 -0
  16. package/dist/components/charts/ChartCell.svelte +33 -6
  17. package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
  18. package/dist/components/charts/ChartContainer.svelte +55 -5
  19. package/dist/components/charts/ChartContainer.svelte.d.ts +14 -1
  20. package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
  21. package/dist/components/charts/ChartHover.svelte.js +22 -0
  22. package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
  23. package/dist/components/charts/ChartVisibility.svelte.js +67 -0
  24. package/dist/components/guides/Grid.svelte +12 -1
  25. package/dist/components/guides/legend/ContinuousColorLegend.svelte +7 -1
  26. package/dist/components/guides/legend/OrdinalColorLegend.svelte +8 -5
  27. package/dist/components/marks/AreaMarkX.svelte +18 -4
  28. package/dist/components/marks/AreaMarkY.svelte +18 -4
  29. package/dist/components/marks/BarMarkX.svelte +20 -4
  30. package/dist/components/marks/BarMarkY.svelte +20 -4
  31. package/dist/components/marks/DotsMark.svelte +8 -3
  32. package/dist/components/marks/LineMark.svelte +13 -6
  33. package/dist/components/marks/ParallelCoordinatesMark.svelte +11 -2
  34. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +4 -0
  35. package/dist/components/marks/RectMark.svelte +8 -3
  36. package/dist/components/marks/RectXMark.svelte +9 -3
  37. package/dist/components/marks/RectYMark.svelte +9 -3
  38. package/dist/components/marks/RuleMarkX.svelte +31 -25
  39. package/dist/components/marks/RuleMarkY.svelte +31 -25
  40. package/dist/components/marks/TextMark.svelte +52 -14
  41. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +22 -1
  42. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +579 -66
  43. package/dist/components/marks/lifecycle/domain-queries.d.ts +13 -2
  44. package/dist/components/marks/lifecycle/domain-queries.js +43 -11
  45. package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
  46. package/dist/components/marks/lifecycle/domain-registration.js +17 -20
  47. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +112 -0
  48. package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
  49. package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
  50. package/dist/components/marks/lifecycle/other-split.js +112 -0
  51. package/dist/components/marks/lifecycle/query-builder.d.ts +53 -3
  52. package/dist/components/marks/lifecycle/query-builder.js +46 -23
  53. package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
  54. package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
  55. package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
  56. package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
  57. package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
  58. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  59. package/dist/components/marks/utils/reactive-config.js +9 -0
  60. package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
  61. package/dist/components/marks/utils/tooltip-helpers.js +2 -1
  62. package/dist/components/rendering/Areas.svelte +57 -19
  63. package/dist/components/rendering/Areas.svelte.d.ts +12 -0
  64. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
  65. package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -2
  66. package/dist/components/rendering/Dots.svelte +23 -3
  67. package/dist/components/rendering/Dots.svelte.d.ts +6 -0
  68. package/dist/components/rendering/GridLine.svelte +13 -1
  69. package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
  70. package/dist/components/rendering/Lines.svelte +48 -13
  71. package/dist/components/rendering/Lines.svelte.d.ts +12 -0
  72. package/dist/components/rendering/ParallelLines.svelte +5 -5
  73. package/dist/components/rendering/Rects.svelte +164 -30
  74. package/dist/components/rendering/Rects.svelte.d.ts +23 -2
  75. package/dist/components/rendering/RuleX.svelte +1 -1
  76. package/dist/components/rendering/RuleY.svelte +1 -1
  77. package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
  78. package/dist/components/rendering/activity-reads.d.ts +47 -12
  79. package/dist/components/rendering/activity-reads.js +61 -19
  80. package/dist/components/rendering/uniform-sizes.d.ts +1 -1
  81. package/dist/components/rendering/uniform-sizes.js +1 -1
  82. package/dist/config/ChartConfig.svelte.d.ts +10 -1
  83. package/dist/config/ChartConfig.svelte.js +17 -0
  84. package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
  85. package/dist/gpu/GPUCoordinator.svelte.js +164 -7
  86. package/dist/gpu/MorphedChannels.d.ts +11 -28
  87. package/dist/gpu/MorphedChannels.js +72 -8
  88. package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
  89. package/dist/gpu/ScaleTemplateSystem.js +83 -6
  90. package/dist/index.d.ts +19 -4
  91. package/dist/index.js +22 -4
  92. package/dist/interactors/Interval1D.svelte +6 -1
  93. package/dist/interactors/Interval2D.svelte +6 -1
  94. package/dist/interactors/ParallelAxisSource.svelte +10 -1
  95. package/dist/interactors/Toggle.svelte +32 -3
  96. package/dist/interactors/Tooltip.svelte +138 -41
  97. package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
  98. package/dist/interactors/TooltipManager.svelte.js +46 -12
  99. package/dist/interactors/other-tooltip.d.ts +16 -0
  100. package/dist/interactors/other-tooltip.js +22 -0
  101. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
  102. package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
  103. package/dist/query-engine/FieldActivity.svelte.d.ts +49 -1
  104. package/dist/query-engine/FieldActivity.svelte.js +175 -5
  105. package/dist/query-engine/FilterGroup.svelte.d.ts +23 -1
  106. package/dist/query-engine/FilterGroup.svelte.js +43 -3
  107. package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +31 -3
  108. package/dist/query-engine/FilterGroupRegistry.svelte.js +62 -15
  109. package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
  110. package/dist/query-engine/MarkHandle.svelte.js +33 -1
  111. package/dist/query-engine/QueryEngine.d.ts +225 -5
  112. package/dist/query-engine/QueryEngine.js +697 -47
  113. package/dist/query-engine/categorical-identity.d.ts +41 -0
  114. package/dist/query-engine/categorical-identity.js +145 -65
  115. package/dist/query-engine/debug/query-log.d.ts +39 -0
  116. package/dist/query-engine/debug/query-log.js +90 -0
  117. package/dist/query-engine/duckdb.d.ts +55 -8
  118. package/dist/query-engine/duckdb.js +82 -30
  119. package/dist/query-engine/index.d.ts +7 -4
  120. package/dist/query-engine/index.js +3 -3
  121. package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
  122. package/dist/query-engine/preagg/BuildQueue.js +51 -15
  123. package/dist/query-engine/preagg/Preagg.d.ts +46 -3
  124. package/dist/query-engine/preagg/Preagg.js +159 -31
  125. package/dist/query-engine/preagg/plan.d.ts +33 -1
  126. package/dist/query-engine/preagg/plan.js +158 -14
  127. package/dist/query-engine/sliced-lane.d.ts +108 -0
  128. package/dist/query-engine/sliced-lane.js +299 -0
  129. package/dist/query-engine/source-routing.d.ts +106 -0
  130. package/dist/query-engine/source-routing.js +189 -0
  131. package/dist/query-engine/toggle-predicate.d.ts +21 -0
  132. package/dist/query-engine/toggle-predicate.js +29 -1
  133. package/dist/scales/ActivityTable.d.ts +5 -1
  134. package/dist/scales/ActivityTable.js +12 -3
  135. package/dist/scales/BandScaleState.svelte.d.ts +22 -9
  136. package/dist/scales/BandScaleState.svelte.js +39 -21
  137. package/dist/scales/ChartScales.svelte.d.ts +3 -0
  138. package/dist/scales/ChartScales.svelte.js +26 -8
  139. package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
  140. package/dist/scales/ColorScaleState.svelte.js +138 -26
  141. package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
  142. package/dist/scales/LinearScaleState.svelte.js +21 -39
  143. package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
  144. package/dist/scales/ScaleGeneration.svelte.js +42 -0
  145. package/dist/scales/ScaleRegistry.svelte.js +3 -1
  146. package/dist/scales/SlotTransition.d.ts +15 -1
  147. package/dist/scales/SlotTransition.js +33 -4
  148. package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
  149. package/dist/scales/TimeScaleState.svelte.js +7 -7
  150. package/dist/scales/color-utils.d.ts +0 -3
  151. package/dist/scales/color-utils.js +0 -3
  152. package/dist/scales/entered.d.ts +8 -0
  153. package/dist/scales/entered.js +10 -0
  154. package/dist/spec/LazyQueryEngine.js +2 -19
  155. package/dist/spec/MarkRegistry.d.ts +3 -1
  156. package/dist/spec/MarkRegistry.js +13 -1
  157. package/dist/spec/PlotFromSpec.svelte +1 -0
  158. package/dist/spec/export.js +11 -2
  159. package/dist/spec/types.d.ts +43 -13
  160. package/dist/sql/bin.d.ts +25 -0
  161. package/dist/sql/bin.js +108 -5
  162. package/dist/theme.css +5 -4
  163. package/dist/transforms/TransformCompiler.d.ts +47 -2
  164. package/dist/transforms/TransformCompiler.js +70 -1
  165. package/dist/transforms/index.js +176 -42
  166. package/dist/types.d.ts +177 -2
  167. package/package.json +4 -3
@@ -5,7 +5,8 @@
5
5
  import { getGPUContext } from '../../gpu';
6
6
  import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
7
7
  import type { ChartContext, WebGPUCanvasContext } from '../../types';
8
- import { activityReads, markActivityWGSL } from './activity-reads';
8
+ import type { SplitProp } from '../marks/lifecycle/other-rows.svelte';
9
+ import { activityReads, markActivityWGSL, otherRowActivityWGSL } from './activity-reads';
9
10
 
10
11
  const rectRendererDefinition = {
11
12
  x: { concept: 'extent', channels: ['x0', 'x1'] },
@@ -21,7 +22,18 @@
21
22
  Y1: 3,
22
23
  COLOR: 4,
23
24
  ALPHA: 5,
24
- CONSTANTS: 6
25
+ CONSTANTS: 6,
26
+ /**
27
+ * An Other bar's selected share, per row: 0 on rows that are not an Other
28
+ * (so the zero-filled stand-in buffer means "no split anywhere"), `1 +
29
+ * share` on one. Morphed with the bar's length.
30
+ *
31
+ * The vertex stage's 8th storage buffer: six channels, the identity slot
32
+ * table and this fill WebGPU's default maxStorageBuffersPerShaderStage. A
33
+ * further per-row channel needs another container (an instance vertex
34
+ * attribute) or a raised device limit.
35
+ */
36
+ SHARE: 7
25
37
  };
26
38
 
27
39
  // Facet uses vertex buffer slot 1 (not a storage binding)
@@ -35,8 +47,10 @@
35
47
  GROUP_INDEX: 12,
36
48
  CONSTANT_COLOR: 16,
37
49
  DX: 32,
38
- DY: 36
39
- // 40-47: padding for 16-byte alignment
50
+ DY: 36,
51
+ OTHER_COLOR_ON_BAND: 40,
52
+ SPLIT_ALONG_X: 44,
53
+ ENTRANCE_AXIS: 48
40
54
  };
41
55
 
42
56
  interface Props {
@@ -46,8 +60,20 @@
46
60
  y1Data: Float32Array;
47
61
  colorValueData: Float32Array | null;
48
62
  alphaData: Float32Array | null;
63
+ /**
64
+ * A bar's selected share per row (see BINDINGS.SHARE), or null for none: a
65
+ * bar mark with a topN Other bucket passes its lifecycle's `otherRows.split`.
66
+ * The fragment draws the bar above the share dimmed.
67
+ */
68
+ share?: SplitProp | null;
49
69
  facetData?: Float32Array | null;
50
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;
51
77
  zIndex?: number;
52
78
  colorMode?: number;
53
79
  colorScaleIndex?: number;
@@ -60,14 +86,22 @@
60
86
  markId?: string;
61
87
  /**
62
88
  * The band axis, when this mark's rows are one per category in category
63
- * 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.
64
91
  *
65
92
  * The OTHER axis's channels morph. The band's never do: they carry a
66
93
  * category id that the shader resolves through the slot table, and
67
94
  * interpolating an identity produces a fraction that resolves to some
68
- * 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.
69
97
  */
70
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;
71
105
  }
72
106
 
73
107
  let {
@@ -77,8 +111,10 @@
77
111
  y1Data,
78
112
  colorValueData,
79
113
  alphaData,
114
+ share = null,
80
115
  facetData = null,
81
116
  dataVersion,
117
+ landsFor = 0,
82
118
  zIndex = 0,
83
119
  colorMode = 0,
84
120
  colorScaleIndex = 0,
@@ -88,9 +124,15 @@
88
124
  dx = 0,
89
125
  dy = 0,
90
126
  markId = '',
91
- bandAxis = null
127
+ bandAxis = null,
128
+ grow = null
92
129
  }: Props = $props();
93
130
 
131
+ const splitData = $derived(share?.data ?? null);
132
+ const splitSlides = $derived(share?.slides ?? true);
133
+ // Bars grouped on y run along x, so their split does too.
134
+ const splitAlongX = $derived(share?.axis === 'y');
135
+ const otherColorOnBand = $derived(share?.colorOnBand ?? false);
94
136
  const morphX = $derived(bandAxis === 'y');
95
137
  const morphY = $derived(bandAxis === 'x');
96
138
 
@@ -137,8 +179,12 @@
137
179
  constantColor: vec4<f32>, // Used when colorMode = 0
138
180
  dx: f32, // Horizontal pixel offset
139
181
  dy: f32, // Vertical pixel offset
140
- _pad1: f32,
141
- _pad2: f32,
182
+ otherColorOnBand: u32, // 1: an Other row skips its colour activity too
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,
142
188
  };
143
189
  @group(1) @binding(6) var<uniform> markConstants: MarkConstants;
144
190
 
@@ -146,6 +192,13 @@
146
192
  @builtin(position) position: vec4<f32>,
147
193
  @location(0) colorValue: f32, // Raw value for scale lookup
148
194
  @location(1) alpha: f32,
195
+ // The Other bar's selected share, or -1 on every other row; and where this
196
+ // fragment sits along the bar, 0 at its base, 1 at its end.
197
+ @location(2) @interpolate(flat) share: f32,
198
+ @location(3) along: f32,
199
+ // An Other bar's unselected part: dimmed by the other fields' activity and
200
+ // by the field's free slot, so it fades as the rest of the chart does.
201
+ @location(4) alphaRest: f32,
149
202
  };
150
203
 
151
204
  @group(1) @binding(0) var<storage, read> x0Values: array<f32>;
@@ -154,6 +207,7 @@
154
207
  @group(1) @binding(3) var<storage, read> y1Values: array<f32>;
155
208
  @group(1) @binding(4) var<storage, read> colorValues: array<f32>;
156
209
  @group(1) @binding(5) var<storage, read> alphaValues: array<f32>;
210
+ @group(1) @binding(7) var<storage, read> splitValues: array<f32>;
157
211
 
158
212
  @vertex
159
213
  fn vertexMain(
@@ -175,13 +229,19 @@
175
229
  // Y axis scaling (generated based on scale type)
176
230
  {{Y_AXIS_LOGIC}}
177
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
+
178
237
  // Apply inset (shrink rect inward on all sides)
179
- let ix = markConstants.inset * sign(scaledX1 - scaledX0);
180
- 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);
181
240
 
182
241
  // Interpolate rectangle corners and apply pixel offsets
183
- let finalX = mix(scaledX0 + ix, scaledX1 - ix, rectCorner.x) + markConstants.dx;
184
- 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);
185
245
 
186
246
  // Convert to normalized device coordinates
187
247
  let worldPos = vec2<f32>(finalX, finalY);
@@ -190,19 +250,47 @@
190
250
  var output: VertexOutput;
191
251
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
192
252
  output.colorValue = colorValue;
193
- output.alpha = alpha * selectionDim(
194
- ${markActivityWGSL(ACTIVITY_READS, {
253
+ // An Other row (split > 0) is drawn in two parts, mixed in the fragment at
254
+ // its share. Below: as any bar, by its full activity, its own field's
255
+ // included, so it dims and lights with the bucket's lit state (#23). Above:
256
+ // by the other fields' activity and the field's free slot, so it is lit
257
+ // only while nothing is selected. Both fade on the selection clock, and the
258
+ // split only moves when the share moves between two parts that differ.
259
+ let split = splitValues[instanceIdx];
260
+ let isOther = split > 0.5;
261
+ output.share = select(-1.0, split - 1.0, isOther);
262
+ output.along = select(rectCorner.y, rectCorner.x, markConstants.splitAlongX == 1u);
263
+ let activity = ${markActivityWGSL(ACTIVITY_READS, {
264
+ color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
265
+ x: 'x0',
266
+ y: 'y0',
267
+ group: 'markConstants.groupIndex'
268
+ })};
269
+ output.alpha = alpha * selectionDim(activity) * enterAlpha;
270
+ // The unselected part is drawn only on an Other row: every other bar skips
271
+ // its activity reads.
272
+ output.alphaRest = output.alpha;
273
+ if (isOther) {
274
+ let otherActivity = ${otherRowActivityWGSL(ACTIVITY_READS, {
195
275
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
196
- x: 'x0',
197
- y: 'y0',
198
- group: 'markConstants.groupIndex'
199
- })}
200
- );
276
+ group: 'markConstants.groupIndex',
277
+ skipColor: 'markConstants.otherColorOnBand == 1u'
278
+ })};
279
+ let free = select(
280
+ bandFreeActivityX(markConstants.groupIndex),
281
+ bandFreeActivityY(markConstants.groupIndex),
282
+ markConstants.splitAlongX == 1u
283
+ );
284
+ output.alphaRest = alpha * selectionDim(min(otherActivity, free)) * enterAlpha;
285
+ }
201
286
  return output;
202
287
  }
203
288
 
204
289
  @fragment
205
290
  fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
291
+ // Before any branch: a derivative must be taken in uniform control flow.
292
+ let aa = max(fwidth(input.along), 1e-4);
293
+
206
294
  // Get color based on mode
207
295
  var baseColor: vec4<f32>;
208
296
  if (markConstants.colorMode == 0u) {
@@ -217,7 +305,14 @@
217
305
  );
218
306
  }
219
307
 
220
- let finalAlpha = baseColor.a * input.alpha;
308
+ var finalAlpha = baseColor.a * input.alpha;
309
+ // An Other bar's split: its own part up to the selected share, from its
310
+ // base, its unselected part above, with an anti-aliased edge. At share 1
311
+ // the whole bar is its own part, with no half-dimmed top row.
312
+ if (input.share >= 0.0 && input.share < 1.0) {
313
+ let below = 1.0 - smoothstep(input.share - aa, input.share + aa, input.along);
314
+ finalAlpha = baseColor.a * mix(input.alphaRest, input.alpha, below);
315
+ }
221
316
  return vec4<f32>(baseColor.rgb * finalAlpha, finalAlpha);
222
317
  }
223
318
  `
@@ -248,6 +343,7 @@
248
343
  { binding: BINDINGS.Y1, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
249
344
  { binding: BINDINGS.COLOR, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
250
345
  { binding: BINDINGS.ALPHA, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
346
+ { binding: BINDINGS.SHARE, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
251
347
  {
252
348
  binding: BINDINGS.CONSTANTS,
253
349
  type: 'uniform',
@@ -257,6 +353,18 @@
257
353
 
258
354
  // --- DATA WRITING EFFECTS (Independent & Fine-Grained) ---
259
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
+
260
368
  // The measure axis's two edges morph, as ONE write per data update, so one
261
369
  // update starts one transition. The band's ids go with them: they are how a
262
370
  // row set that is not the one being animated gets noticed and snapped.
@@ -266,16 +374,40 @@
266
374
  // a fraction that the slot table resolves to a DIFFERENT category.
267
375
  $effect(() => {
268
376
  if (!renderer?.resourcesCreated || !bandAxis) return;
377
+ // The Other share travels with the bar's length, on the same clock, so the
378
+ // split and the bar land together. A share that changes alone (a click)
379
+ // starts a transition of its own on that clock.
380
+ const split = splitData ? ([[BINDINGS.SHARE, splitData]] as const) : [];
381
+ const land = landsNow() ? 'all' : splitData && !splitSlides ? [BINDINGS.SHARE] : undefined;
269
382
  if (bandAxis === 'x') {
270
- renderer.writeMorphed('x', x0Data, [
271
- [BINDINGS.Y0, y0Data],
272
- [BINDINGS.Y1, y1Data]
273
- ]);
383
+ renderer.writeMorphed(
384
+ 'x',
385
+ x0Data,
386
+ [[BINDINGS.Y0, y0Data], [BINDINGS.Y1, y1Data], ...split],
387
+ land
388
+ );
274
389
  } else {
275
- renderer.writeMorphed('y', y0Data, [
276
- [BINDINGS.X0, x0Data],
277
- [BINDINGS.X1, x1Data]
278
- ]);
390
+ renderer.writeMorphed(
391
+ 'y',
392
+ y0Data,
393
+ [[BINDINGS.X0, x0Data], [BINDINGS.X1, x1Data], ...split],
394
+ land
395
+ );
396
+ }
397
+ });
398
+ // Without a band axis the share is written as is. A mark that had shares and
399
+ // has none now writes one array of zeros, which also drops a morphed channel:
400
+ // left alone, the shader would keep reading the last shares. A mark that
401
+ // never had any writes nothing and reads the zero-filled stand-in.
402
+ let splitWritten = false;
403
+ $effect(() => {
404
+ if (!renderer?.resourcesCreated) return;
405
+ if (splitData) {
406
+ if (!bandAxis) renderer.writeAttribute(BINDINGS.SHARE, splitData);
407
+ splitWritten = true;
408
+ } else if (splitWritten) {
409
+ renderer.writeAttribute(BINDINGS.SHARE, new Float32Array(Math.max(1, x0Data.length)));
410
+ splitWritten = false;
279
411
  }
280
412
  });
281
413
  $effect(() => {
@@ -324,7 +456,9 @@
324
456
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 12, constantColor[3], LE);
325
457
  view.setFloat32(UNIFORM_OFFSETS.DX, dx, LE);
326
458
  view.setFloat32(UNIFORM_OFFSETS.DY, dy, LE);
327
- // padding at 40-47
459
+ view.setUint32(UNIFORM_OFFSETS.OTHER_COLOR_ON_BAND, otherColorOnBand ? 1 : 0, LE);
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);
328
462
 
329
463
  renderer.writeUniform(BINDINGS.CONSTANTS, new Uint8Array(buffer));
330
464
  }
@@ -1,3 +1,4 @@
1
+ import type { SplitProp } from '../marks/lifecycle/other-rows.svelte';
1
2
  interface Props {
2
3
  x0Data: Float32Array;
3
4
  x1Data: Float32Array;
@@ -5,8 +6,20 @@ interface Props {
5
6
  y1Data: Float32Array;
6
7
  colorValueData: Float32Array | null;
7
8
  alphaData: Float32Array | null;
9
+ /**
10
+ * A bar's selected share per row (see BINDINGS.SHARE), or null for none: a
11
+ * bar mark with a topN Other bucket passes its lifecycle's `otherRows.split`.
12
+ * The fragment draws the bar above the share dimmed.
13
+ */
14
+ share?: SplitProp | null;
8
15
  facetData?: Float32Array | null;
9
16
  dataVersion: number;
17
+ /**
18
+ * The data version whose first write lands without morphing, or 0: a
19
+ * catch-up result (MarkLifecycleV2.landsFor). Later writes of that version
20
+ * animate as usual.
21
+ */
22
+ landsFor?: number;
10
23
  zIndex?: number;
11
24
  colorMode?: number;
12
25
  colorScaleIndex?: number;
@@ -19,14 +32,22 @@ interface Props {
19
32
  markId?: string;
20
33
  /**
21
34
  * The band axis, when this mark's rows are one per category in category
22
- * order and may therefore morph. null to morph nothing.
35
+ * order and may therefore morph; or a histogram's binned axis, one row per
36
+ * bin in bin order. null to morph nothing.
23
37
  *
24
38
  * The OTHER axis's channels morph. The band's never do: they carry a
25
39
  * category id that the shader resolves through the slot table, and
26
40
  * interpolating an identity produces a fraction that resolves to some
27
- * other category. Position already animates, through the slot table.
41
+ * other category. Position already animates, through the slot table. A
42
+ * bin's edges do not change under a filter, and a re-bin snaps.
28
43
  */
29
44
  bandAxis?: 'x' | 'y' | null;
45
+ /**
46
+ * The axis this mark grows along when its chart enters: its measure axis,
47
+ * for bars and histograms anchored at 0. null fades it in instead, for
48
+ * rects with bounds of their own (a Gantt bar has no 0 to grow from).
49
+ */
50
+ grow?: 'x' | 'y' | null;
30
51
  }
31
52
  declare const Rects: import("svelte").Component<Props, {}, "">;
32
53
  type Rects = ReturnType<typeof Rects>;
@@ -181,7 +181,7 @@
181
181
  var output: VertexOutput;
182
182
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
183
183
  output.colorValue = colorValue;
184
- output.alpha = alpha * selectionDim(
184
+ output.alpha = alpha * entranceAlpha(0u) * selectionDim(
185
185
  ${markActivityWGSL(ACTIVITY_READS, {
186
186
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
187
187
  x: 'x',
@@ -180,7 +180,7 @@
180
180
  var output: VertexOutput;
181
181
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
182
182
  output.colorValue = colorValue;
183
- output.alpha = alpha * selectionDim(
183
+ output.alpha = alpha * entranceAlpha(0u) * selectionDim(
184
184
  ${markActivityWGSL(ACTIVITY_READS, {
185
185
  color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
186
186
  x: 'x0',
@@ -2,7 +2,7 @@
2
2
  import { onMount, onDestroy, setContext, getContext } from 'svelte';
3
3
  import { getGPUContext } from '../../gpu';
4
4
  import type { Snippet } from 'svelte';
5
- import type { ChartContext, WebGPUCanvasContext } from '../../types';
5
+ import type { ChartContext, EntranceMode, WebGPUCanvasContext } from '../../types';
6
6
  import type { GPUCoordinator, ChartRegistrationResult } from '../../gpu/GPUCoordinator.svelte';
7
7
  import type { FacetScaleRef } from '../../gpu/GPUCoordinator.svelte';
8
8
 
@@ -120,7 +120,9 @@
120
120
  chartControl = gpuCoordinator.registerChart(chartId, canvas, {
121
121
  zIndex: 0,
122
122
  facetIndex: facetIndex ?? 0,
123
- facetScale
123
+ facetScale,
124
+ entrance: c.config?.chartProps?.entrance as EntranceMode | undefined,
125
+ entranceGate: c.config?.entrance ?? null
124
126
  });
125
127
 
126
128
  // Notify coordinator that context is ready
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Which selection activities a mark reads: its ordinal colour, its band x,
3
- * its band y. One answer per renderer, used twice:
3
+ * its band y, and a line's or area's series. One answer per renderer, used
4
+ * twice:
4
5
  *
5
6
  * - The renderer builds its shader's `markActivity` call from it, so a read
6
7
  * the table does not list is passed as 1.0 and cannot happen.
@@ -26,11 +27,14 @@ export interface ActivityReads {
26
27
  readonly color: boolean;
27
28
  readonly x: boolean;
28
29
  readonly y: boolean;
30
+ /** A line's or area's `z`: the series field when it is not the colour field. */
31
+ readonly series: boolean;
29
32
  }
30
33
  /**
31
34
  * What a renderer reads. `hasExtent` is a rule's: RuleX with y1/y2, RuleY with
32
- * x1/x2. Lines, areas and parallel coordinates dim by colour only: a line over
33
- * a band axis spans its categories.
35
+ * x1/x2. Lines and areas dim by colour and by series (`z`), never by a band
36
+ * axis: a line over a band axis spans its categories. Parallel coordinates
37
+ * have no `z` and dim by colour only.
34
38
  */
35
39
  export declare function activityReads(renderer: ActivityRenderer, hasExtent?: boolean): ActivityReads;
36
40
  /**
@@ -47,12 +51,14 @@ export declare function rendererForMark(markType: string): ActivityRenderer | nu
47
51
  * sorted, from what its renderer reads and the identity key of each of its
48
52
  * categorical scales (null for a channel with no such scale, or a scale with
49
53
  * no identity: a field with no identity has no activity to dim by, so a
50
- * selection on it filters the mark).
54
+ * selection on it filters the mark). `series` is null unless the mark has a
55
+ * `z` of its own; a series that is the colour field is already in `color`.
51
56
  */
52
57
  export declare function readsFor(reads: ActivityReads, keys: {
53
58
  color: string | null;
54
59
  x: string | null;
55
60
  y: string | null;
61
+ series?: string | null;
56
62
  }): readonly string[];
57
63
  /** A mark that reads no activity. Shared, so its reads never look changed. */
58
64
  export declare const NO_READS: readonly string[];
@@ -62,22 +68,51 @@ export declare const NO_READS: readonly string[];
62
68
  * by (its band axes, its ordinal colour) dims it, and any other selection
63
69
  * filters it. 'filter': every selection filters it, even on a field it draws
64
70
  * by, so its aggregates are recomputed for the subset (top N within a region).
71
+ * 'ignore': no selection filters or dims it; context, such as the whole
72
+ * drawn behind a filtered part.
65
73
  */
66
- export type SelectionResponse = 'auto' | 'filter';
74
+ export type SelectionResponse = 'auto' | 'filter' | 'ignore';
67
75
  export { NO_GROUP };
68
- /** A mark's reads under its selection response: none for 'filter', which never dims. */
76
+ /**
77
+ * Whether a selection can dim the mark: only under 'auto'. A 'filter' mark is
78
+ * filtered instead, and an 'ignore' mark neither.
79
+ */
80
+ export declare function dimsBySelection(response: SelectionResponse | undefined): boolean;
81
+ /** A mark's reads under its selection response: none for a mark that never dims. */
69
82
  export declare function readsForResponse(response: SelectionResponse | undefined, reads: readonly string[]): readonly string[];
70
- /** The group index a mark's shader reads activity with: NO_GROUP for 'filter'. */
83
+ /** The group index a mark's shader reads activity with: NO_GROUP for a mark that never dims. */
71
84
  export declare function groupIndexForResponse(response: SelectionResponse | undefined, groupIndex: number): number;
72
85
  /**
73
- * The WGSL activity of a renderer that reads colour only (lines, areas,
74
- * parallel coordinates): `markColorActivity(...)`, or 1.0 if the table says
75
- * it reads nothing. Throws for a renderer that reads a band axis, whose call
76
- * is markActivityWGSL.
86
+ * The WGSL activity of a renderer that reads no band axis (lines, areas,
87
+ * parallel coordinates): its colour activity and, for lines and areas, its
88
+ * series activity, 1.0 for each the table says it does not read.
89
+ *
90
+ * `series` is markColorActivity's first three arguments for the series scale,
91
+ * the same lookup as colour on another scale and buffer. The mark passes mode
92
+ * 0 when it has no `z` of its own, so the read returns 1.0 before loading
93
+ * anything. The two combine by `min`, the least active wins, as in
94
+ * markActivity: a series on the colour field cannot dim twice.
95
+ *
96
+ * Throws for a renderer that reads a band axis, whose call is
97
+ * markActivityWGSL, and for one that reads a series it was given no
98
+ * arguments for.
99
+ */
100
+ export declare function connectedActivityWGSL(reads: ActivityReads, args: {
101
+ color: string;
102
+ series?: string;
103
+ group: string;
104
+ }): string;
105
+ /**
106
+ * The activity of an Other row: the same terms as markActivityWGSL without the
107
+ * band axes', and without colour's when colour is the band's own field
108
+ * (`skipColor`, a WGSL bool). Those read the Other bucket's own fade, and the
109
+ * bar's split already shows how much of it is selected (Rects); multiplying
110
+ * them in as well would dim it twice. Activity on any other field stays.
77
111
  */
78
- export declare function colorActivityWGSL(reads: ActivityReads, args: {
112
+ export declare function otherRowActivityWGSL(reads: ActivityReads, args: {
79
113
  color: string;
80
114
  group: string;
115
+ skipColor: string;
81
116
  }): string;
82
117
  /**
83
118
  * The WGSL `markActivity(...)` call for `reads`: each activity the renderer
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Which selection activities a mark reads: its ordinal colour, its band x,
3
- * its band y. One answer per renderer, used twice:
3
+ * its band y, and a line's or area's series. One answer per renderer, used
4
+ * twice:
4
5
  *
5
6
  * - The renderer builds its shader's `markActivity` call from it, so a read
6
7
  * the table does not list is passed as 1.0 and cannot happen.
@@ -20,12 +21,14 @@
20
21
  * axis, belongs to no category on it, and must not dim by it.
21
22
  */
22
23
  import { NO_GROUP } from '../../gpu/identity-slot-packing';
23
- const ALL = { color: true, x: true, y: true };
24
- const COLOR_ONLY = { color: true, x: false, y: false };
24
+ const ALL = { color: true, x: true, y: true, series: false };
25
+ const CONNECTED = { color: true, x: false, y: false, series: true };
26
+ const COLOR_ONLY = { color: true, x: false, y: false, series: false };
25
27
  /**
26
28
  * What a renderer reads. `hasExtent` is a rule's: RuleX with y1/y2, RuleY with
27
- * x1/x2. Lines, areas and parallel coordinates dim by colour only: a line over
28
- * a band axis spans its categories.
29
+ * x1/x2. Lines and areas dim by colour and by series (`z`), never by a band
30
+ * axis: a line over a band axis spans its categories. Parallel coordinates
31
+ * have no `z` and dim by colour only.
29
32
  */
30
33
  export function activityReads(renderer, hasExtent = false) {
31
34
  switch (renderer) {
@@ -34,11 +37,12 @@ export function activityReads(renderer, hasExtent = false) {
34
37
  case 'text':
35
38
  return ALL;
36
39
  case 'rulex':
37
- return { color: true, x: true, y: hasExtent };
40
+ return { color: true, x: true, y: hasExtent, series: false };
38
41
  case 'ruley':
39
- return { color: true, x: hasExtent, y: true };
42
+ return { color: true, x: hasExtent, y: true, series: false };
40
43
  case 'lines':
41
44
  case 'areas':
45
+ return CONNECTED;
42
46
  case 'parallel':
43
47
  return COLOR_ONLY;
44
48
  }
@@ -85,7 +89,8 @@ export function rendererForMark(markType) {
85
89
  * sorted, from what its renderer reads and the identity key of each of its
86
90
  * categorical scales (null for a channel with no such scale, or a scale with
87
91
  * no identity: a field with no identity has no activity to dim by, so a
88
- * selection on it filters the mark).
92
+ * selection on it filters the mark). `series` is null unless the mark has a
93
+ * `z` of its own; a series that is the colour field is already in `color`.
89
94
  */
90
95
  export function readsFor(reads, keys) {
91
96
  const out = [];
@@ -95,6 +100,8 @@ export function readsFor(reads, keys) {
95
100
  out.push(keys.x);
96
101
  if (reads.y && keys.y)
97
102
  out.push(keys.y);
103
+ if (reads.series && keys.series)
104
+ out.push(keys.series);
98
105
  if (out.length === 0)
99
106
  return NO_READS;
100
107
  return [...new Set(out)].sort();
@@ -102,24 +109,59 @@ export function readsFor(reads, keys) {
102
109
  /** A mark that reads no activity. Shared, so its reads never look changed. */
103
110
  export const NO_READS = [];
104
111
  export { NO_GROUP };
105
- /** A mark's reads under its selection response: none for 'filter', which never dims. */
112
+ /**
113
+ * Whether a selection can dim the mark: only under 'auto'. A 'filter' mark is
114
+ * filtered instead, and an 'ignore' mark neither.
115
+ */
116
+ export function dimsBySelection(response) {
117
+ return response !== 'filter' && response !== 'ignore';
118
+ }
119
+ /** A mark's reads under its selection response: none for a mark that never dims. */
106
120
  export function readsForResponse(response, reads) {
107
- return response === 'filter' ? NO_READS : reads;
121
+ return dimsBySelection(response) ? reads : NO_READS;
108
122
  }
109
- /** The group index a mark's shader reads activity with: NO_GROUP for 'filter'. */
123
+ /** The group index a mark's shader reads activity with: NO_GROUP for a mark that never dims. */
110
124
  export function groupIndexForResponse(response, groupIndex) {
111
- return response === 'filter' ? NO_GROUP : groupIndex;
125
+ return dimsBySelection(response) ? groupIndex : NO_GROUP;
112
126
  }
113
127
  /**
114
- * The WGSL activity of a renderer that reads colour only (lines, areas,
115
- * parallel coordinates): `markColorActivity(...)`, or 1.0 if the table says
116
- * it reads nothing. Throws for a renderer that reads a band axis, whose call
117
- * is markActivityWGSL.
128
+ * The WGSL activity of a renderer that reads no band axis (lines, areas,
129
+ * parallel coordinates): its colour activity and, for lines and areas, its
130
+ * series activity, 1.0 for each the table says it does not read.
131
+ *
132
+ * `series` is markColorActivity's first three arguments for the series scale,
133
+ * the same lookup as colour on another scale and buffer. The mark passes mode
134
+ * 0 when it has no `z` of its own, so the read returns 1.0 before loading
135
+ * anything. The two combine by `min`, the least active wins, as in
136
+ * markActivity: a series on the colour field cannot dim twice.
137
+ *
138
+ * Throws for a renderer that reads a band axis, whose call is
139
+ * markActivityWGSL, and for one that reads a series it was given no
140
+ * arguments for.
118
141
  */
119
- export function colorActivityWGSL(reads, args) {
142
+ export function connectedActivityWGSL(reads, args) {
120
143
  if (reads.x || reads.y)
121
- throw new Error('colorActivityWGSL: this renderer reads a band axis');
122
- return reads.color ? `markColorActivity(${args.color}, ${args.group})` : '1.0';
144
+ throw new Error('connectedActivityWGSL: this renderer reads a band axis');
145
+ if (reads.series && !args.series) {
146
+ throw new Error('connectedActivityWGSL: this renderer reads a series; pass its arguments');
147
+ }
148
+ const color = reads.color ? `markColorActivity(${args.color}, ${args.group})` : '1.0';
149
+ if (!reads.series)
150
+ return color;
151
+ return `min(${color}, markColorActivity(${args.series}, ${args.group}))`;
152
+ }
153
+ /**
154
+ * The activity of an Other row: the same terms as markActivityWGSL without the
155
+ * band axes', and without colour's when colour is the band's own field
156
+ * (`skipColor`, a WGSL bool). Those read the Other bucket's own fade, and the
157
+ * bar's split already shows how much of it is selected (Rects); multiplying
158
+ * them in as well would dim it twice. Activity on any other field stays.
159
+ */
160
+ export function otherRowActivityWGSL(reads, args) {
161
+ const color = reads.color
162
+ ? `select(markColorActivity(${args.color}, ${args.group}), 1.0, ${args.skipColor})`
163
+ : '1.0';
164
+ return `markActivity(${color}, 1.0, 1.0)`;
123
165
  }
124
166
  /**
125
167
  * The WGSL `markActivity(...)` call for `reads`: each activity the renderer
@@ -13,7 +13,7 @@
13
13
  export declare const PARALLEL_MAX_AXES = 16;
14
14
  export declare const UNIFORM_BYTES: {
15
15
  readonly dotsMarkConstants: 48;
16
- readonly rectsMarkConstants: 48;
16
+ readonly rectsMarkConstants: 64;
17
17
  readonly ruleXMarkConstants: 48;
18
18
  readonly ruleYMarkConstants: 48;
19
19
  readonly linesMarkConstants: 64;