@fjader/svelte 0.2.2 → 0.2.4

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 (148) hide show
  1. package/README.md +2 -0
  2. package/dist/components/axes/AxisBottom.svelte +23 -6
  3. package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
  4. package/dist/components/axes/AxisLeft.svelte +77 -8
  5. package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
  6. package/dist/components/axes/BandAxisTicks.svelte +64 -25
  7. package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
  8. package/dist/components/axes/axis-title.d.ts +30 -0
  9. package/dist/components/axes/axis-title.js +77 -0
  10. package/dist/components/charts/ChartCell.svelte +1 -1
  11. package/dist/components/charts/ChartContainer.svelte +12 -3
  12. package/dist/components/charts/ChartContainer.svelte.d.ts +7 -0
  13. package/dist/components/guides/legend/ContinuousColorLegend.svelte +9 -2
  14. package/dist/components/guides/legend/OrdinalColorLegend.svelte +96 -35
  15. package/dist/components/marks/AreaMarkX.svelte +21 -6
  16. package/dist/components/marks/AreaMarkY.svelte +21 -6
  17. package/dist/components/marks/BarMarkX.svelte +16 -1
  18. package/dist/components/marks/BarMarkY.svelte +16 -1
  19. package/dist/components/marks/DotsMark.svelte +5 -0
  20. package/dist/components/marks/LineMark.svelte +8 -1
  21. package/dist/components/marks/ParallelCoordinatesMark.svelte +10 -0
  22. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +5 -0
  23. package/dist/components/marks/RectMark.svelte +5 -0
  24. package/dist/components/marks/RectXMark.svelte +5 -0
  25. package/dist/components/marks/RectYMark.svelte +5 -0
  26. package/dist/components/marks/RuleMarkX.svelte +12 -2
  27. package/dist/components/marks/RuleMarkY.svelte +12 -2
  28. package/dist/components/marks/TextMark.svelte +76 -13
  29. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +21 -2
  30. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +300 -42
  31. package/dist/components/marks/lifecycle/domain-queries.d.ts +3 -1
  32. package/dist/components/marks/lifecycle/domain-queries.js +22 -15
  33. package/dist/components/marks/lifecycle/domain-registration.js +2 -6
  34. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +104 -0
  35. package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
  36. package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
  37. package/dist/components/marks/lifecycle/other-split.js +112 -0
  38. package/dist/components/marks/lifecycle/query-builder.d.ts +26 -1
  39. package/dist/components/marks/lifecycle/query-builder.js +25 -5
  40. package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
  41. package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
  42. package/dist/components/marks/utils/area-outline.d.ts +24 -0
  43. package/dist/components/marks/utils/area-outline.js +10 -0
  44. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  45. package/dist/components/marks/utils/reactive-config.js +9 -0
  46. package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
  47. package/dist/components/marks/utils/tooltip-helpers.js +2 -1
  48. package/dist/components/rendering/Areas.svelte +30 -6
  49. package/dist/components/rendering/Areas.svelte.d.ts +2 -0
  50. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
  51. package/dist/components/rendering/BaseGPURenderer.svelte.js +8 -3
  52. package/dist/components/rendering/Dots.svelte +24 -3
  53. package/dist/components/rendering/Dots.svelte.d.ts +2 -0
  54. package/dist/components/rendering/GridLine.svelte +2 -1
  55. package/dist/components/rendering/Lines.svelte +49 -7
  56. package/dist/components/rendering/Lines.svelte.d.ts +9 -0
  57. package/dist/components/rendering/ParallelLines.svelte +26 -7
  58. package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
  59. package/dist/components/rendering/Rects.svelte +129 -20
  60. package/dist/components/rendering/Rects.svelte.d.ts +9 -0
  61. package/dist/components/rendering/RuleX.svelte +20 -5
  62. package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
  63. package/dist/components/rendering/RuleY.svelte +20 -5
  64. package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
  65. package/dist/components/rendering/activity-reads.d.ts +111 -0
  66. package/dist/components/rendering/activity-reads.js +154 -0
  67. package/dist/components/rendering/uniform-sizes.d.ts +26 -0
  68. package/dist/components/rendering/uniform-sizes.js +28 -0
  69. package/dist/config/ChartConfig.svelte.d.ts +5 -1
  70. package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
  71. package/dist/gpu/ColorScaleShaderLib.js +56 -5
  72. package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
  73. package/dist/gpu/GPUCoordinator.svelte.js +68 -32
  74. package/dist/gpu/MorphedChannels.d.ts +6 -32
  75. package/dist/gpu/MorphedChannels.js +45 -3
  76. package/dist/gpu/ResolveShader.d.ts +2 -2
  77. package/dist/gpu/ResolveShader.js +2 -2
  78. package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
  79. package/dist/gpu/ScaleTemplateSystem.js +111 -11
  80. package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
  81. package/dist/gpu/SizeScaleShaderLib.js +4 -4
  82. package/dist/gpu/identity-slot-packing.d.ts +157 -40
  83. package/dist/gpu/identity-slot-packing.js +262 -62
  84. package/dist/gpu/tracked-scales.d.ts +14 -0
  85. package/dist/gpu/tracked-scales.js +18 -0
  86. package/dist/index.d.ts +2 -0
  87. package/dist/index.js +4 -0
  88. package/dist/interactors/BandLabelToggle.svelte +5 -9
  89. package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
  90. package/dist/interactors/DerivedInterval1D.svelte +26 -54
  91. package/dist/interactors/Interval1D.svelte +8 -2
  92. package/dist/interactors/Interval2D.svelte +8 -2
  93. package/dist/interactors/ParallelAxisSource.svelte +14 -5
  94. package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
  95. package/dist/interactors/ParallelBrushes.svelte +8 -9
  96. package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
  97. package/dist/interactors/Toggle.svelte +48 -38
  98. package/dist/interactors/Toggle.svelte.d.ts +4 -3
  99. package/dist/interactors/Tooltip.svelte +140 -42
  100. package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
  101. package/dist/interactors/TooltipManager.svelte.js +40 -10
  102. package/dist/interactors/other-tooltip.d.ts +16 -0
  103. package/dist/interactors/other-tooltip.js +22 -0
  104. package/dist/interactors/relay-clauses.d.ts +23 -0
  105. package/dist/interactors/relay-clauses.js +60 -0
  106. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
  107. package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
  108. package/dist/query-engine/FieldActivity.svelte.d.ts +128 -0
  109. package/dist/query-engine/FieldActivity.svelte.js +378 -0
  110. package/dist/query-engine/FilterGroup.svelte.d.ts +76 -30
  111. package/dist/query-engine/FilterGroup.svelte.js +223 -124
  112. package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +73 -3
  113. package/dist/query-engine/FilterGroupRegistry.svelte.js +161 -12
  114. package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
  115. package/dist/query-engine/MarkHandle.svelte.js +12 -2
  116. package/dist/query-engine/QueryEngine.d.ts +52 -16
  117. package/dist/query-engine/QueryEngine.js +116 -34
  118. package/dist/query-engine/categorical-identity.d.ts +114 -27
  119. package/dist/query-engine/categorical-identity.js +225 -51
  120. package/dist/query-engine/index.d.ts +2 -1
  121. package/dist/query-engine/index.js +1 -0
  122. package/dist/query-engine/preagg/Preagg.d.ts +1 -1
  123. package/dist/query-engine/preagg/Preagg.js +11 -9
  124. package/dist/query-engine/preagg/plan.d.ts +14 -5
  125. package/dist/query-engine/preagg/plan.js +44 -18
  126. package/dist/query-engine/toggle-predicate.d.ts +21 -0
  127. package/dist/query-engine/toggle-predicate.js +41 -2
  128. package/dist/scales/ActivityTable.d.ts +103 -0
  129. package/dist/scales/ActivityTable.js +220 -0
  130. package/dist/scales/BandScaleState.svelte.d.ts +41 -15
  131. package/dist/scales/BandScaleState.svelte.js +78 -36
  132. package/dist/scales/ChartScales.svelte.d.ts +2 -2
  133. package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
  134. package/dist/scales/ColorScaleState.svelte.js +118 -24
  135. package/dist/scales/DomainResolver.d.ts +2 -0
  136. package/dist/scales/DomainResolver.js +15 -8
  137. package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
  138. package/dist/scales/ScaleRegistry.svelte.js +22 -1
  139. package/dist/scales/TimeScaleState.svelte.js +7 -1
  140. package/dist/spec/index.js +1 -0
  141. package/dist/spec/types.d.ts +4 -0
  142. package/dist/theme.css +5 -4
  143. package/dist/transforms/TransformCompiler.d.ts +37 -2
  144. package/dist/transforms/TransformCompiler.js +44 -1
  145. package/dist/transforms/index.d.ts +27 -0
  146. package/dist/transforms/index.js +127 -19
  147. package/dist/types.d.ts +71 -0
  148. package/package.json +3 -2
@@ -1,9 +1,12 @@
1
1
  <script lang="ts">
2
+ import { UNIFORM_BYTES } from './uniform-sizes';
2
3
  import { getContext, onMount, onDestroy } from 'svelte';
3
4
  import { RendererHost } from './RendererHost.svelte';
4
5
  import { getGPUContext } from '../../gpu';
5
6
  import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
6
7
  import type { ChartContext, WebGPUCanvasContext } from '../../types';
8
+ import type { SplitProp } from '../marks/lifecycle/other-rows.svelte';
9
+ import { activityReads, markActivityWGSL, otherRowActivityWGSL } from './activity-reads';
7
10
 
8
11
  const rectRendererDefinition = {
9
12
  x: { concept: 'extent', channels: ['x0', 'x1'] },
@@ -19,7 +22,18 @@
19
22
  Y1: 3,
20
23
  COLOR: 4,
21
24
  ALPHA: 5,
22
- 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
23
37
  };
24
38
 
25
39
  // Facet uses vertex buffer slot 1 (not a storage binding)
@@ -30,11 +44,12 @@
30
44
  COLOR_MODE: 0,
31
45
  COLOR_SCALE_INDEX: 4,
32
46
  INSET: 8,
33
- _PAD0: 12,
47
+ GROUP_INDEX: 12,
34
48
  CONSTANT_COLOR: 16,
35
49
  DX: 32,
36
- DY: 36
37
- // 40-47: padding for 16-byte alignment
50
+ DY: 36,
51
+ OTHER_COLOR_ON_BAND: 40,
52
+ SPLIT_ALONG_X: 44
38
53
  };
39
54
 
40
55
  interface Props {
@@ -44,11 +59,19 @@
44
59
  y1Data: Float32Array;
45
60
  colorValueData: Float32Array | null;
46
61
  alphaData: Float32Array | null;
62
+ /**
63
+ * A bar's selected share per row (see BINDINGS.SHARE), or null for none: a
64
+ * bar mark with a topN Other bucket passes its lifecycle's `otherRows.split`.
65
+ * The fragment draws the bar above the share dimmed.
66
+ */
67
+ share?: SplitProp | null;
47
68
  facetData?: Float32Array | null;
48
69
  dataVersion: number;
49
70
  zIndex?: number;
50
71
  colorMode?: number;
51
72
  colorScaleIndex?: number;
73
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
74
+ groupIndex?: number;
52
75
  constantColor?: [number, number, number, number];
53
76
  inset?: number;
54
77
  dx?: number;
@@ -73,11 +96,13 @@
73
96
  y1Data,
74
97
  colorValueData,
75
98
  alphaData,
99
+ share = null,
76
100
  facetData = null,
77
101
  dataVersion,
78
102
  zIndex = 0,
79
103
  colorMode = 0,
80
104
  colorScaleIndex = 0,
105
+ groupIndex = 0,
81
106
  constantColor = DEFAULT_FILL_COLOR,
82
107
  inset = 0,
83
108
  dx = 0,
@@ -86,6 +111,11 @@
86
111
  bandAxis = null
87
112
  }: Props = $props();
88
113
 
114
+ const splitData = $derived(share?.data ?? null);
115
+ const splitSlides = $derived(share?.slides ?? true);
116
+ // Bars grouped on y run along x, so their split does too.
117
+ const splitAlongX = $derived(share?.axis === 'y');
118
+ const otherColorOnBand = $derived(share?.colorOnBand ?? false);
89
119
  const morphX = $derived(bandAxis === 'y');
90
120
  const morphY = $derived(bandAxis === 'x');
91
121
 
@@ -119,18 +149,21 @@
119
149
 
120
150
  const rectVertexCount = 6;
121
151
 
152
+ /** The selection activities this shader reads; routing reads the same table. */
153
+ const ACTIVITY_READS = activityReads('rects');
154
+
122
155
  const rectsShaderCode = `
123
156
  // MarkConstants uniform for per-mark configuration
124
157
  struct MarkConstants {
125
158
  colorMode: u32, // 0 = constant, 1 = scale
126
159
  colorScaleIndex: u32, // Index into colorConfigs
127
160
  inset: f32, // Pixel inset on all sides
128
- _pad0: u32,
161
+ groupIndex: u32, // Page-wide filter group index, for activity reads
129
162
  constantColor: vec4<f32>, // Used when colorMode = 0
130
163
  dx: f32, // Horizontal pixel offset
131
164
  dy: f32, // Vertical pixel offset
132
- _pad1: f32,
133
- _pad2: f32,
165
+ otherColorOnBand: u32, // 1: an Other row skips its colour activity too
166
+ splitAlongX: u32, // 1: an Other bar splits along x (horizontal bars)
134
167
  };
135
168
  @group(1) @binding(6) var<uniform> markConstants: MarkConstants;
136
169
 
@@ -138,6 +171,13 @@
138
171
  @builtin(position) position: vec4<f32>,
139
172
  @location(0) colorValue: f32, // Raw value for scale lookup
140
173
  @location(1) alpha: f32,
174
+ // The Other bar's selected share, or -1 on every other row; and where this
175
+ // fragment sits along the bar, 0 at its base, 1 at its end.
176
+ @location(2) @interpolate(flat) share: f32,
177
+ @location(3) along: f32,
178
+ // An Other bar's unselected part: dimmed by the other fields' activity and
179
+ // by the field's free slot, so it fades as the rest of the chart does.
180
+ @location(4) alphaRest: f32,
141
181
  };
142
182
 
143
183
  @group(1) @binding(0) var<storage, read> x0Values: array<f32>;
@@ -146,6 +186,7 @@
146
186
  @group(1) @binding(3) var<storage, read> y1Values: array<f32>;
147
187
  @group(1) @binding(4) var<storage, read> colorValues: array<f32>;
148
188
  @group(1) @binding(5) var<storage, read> alphaValues: array<f32>;
189
+ @group(1) @binding(7) var<storage, read> splitValues: array<f32>;
149
190
 
150
191
  @vertex
151
192
  fn vertexMain(
@@ -182,12 +223,47 @@
182
223
  var output: VertexOutput;
183
224
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
184
225
  output.colorValue = colorValue;
185
- output.alpha = alpha;
226
+ // An Other row (split > 0) is drawn in two parts, mixed in the fragment at
227
+ // its share. Below: as any bar, by its full activity, its own field's
228
+ // included, so it dims and lights with the bucket's lit state (#23). Above:
229
+ // by the other fields' activity and the field's free slot, so it is lit
230
+ // only while nothing is selected. Both fade on the selection clock, and the
231
+ // split only moves when the share moves between two parts that differ.
232
+ let split = splitValues[instanceIdx];
233
+ let isOther = split > 0.5;
234
+ output.share = select(-1.0, split - 1.0, isOther);
235
+ output.along = select(rectCorner.y, rectCorner.x, markConstants.splitAlongX == 1u);
236
+ let activity = ${markActivityWGSL(ACTIVITY_READS, {
237
+ color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
238
+ x: 'x0',
239
+ y: 'y0',
240
+ group: 'markConstants.groupIndex'
241
+ })};
242
+ output.alpha = alpha * selectionDim(activity);
243
+ // The unselected part is drawn only on an Other row: every other bar skips
244
+ // its activity reads.
245
+ output.alphaRest = output.alpha;
246
+ if (isOther) {
247
+ let otherActivity = ${otherRowActivityWGSL(ACTIVITY_READS, {
248
+ color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
249
+ group: 'markConstants.groupIndex',
250
+ skipColor: 'markConstants.otherColorOnBand == 1u'
251
+ })};
252
+ let free = select(
253
+ bandFreeActivityX(markConstants.groupIndex),
254
+ bandFreeActivityY(markConstants.groupIndex),
255
+ markConstants.splitAlongX == 1u
256
+ );
257
+ output.alphaRest = alpha * selectionDim(min(otherActivity, free));
258
+ }
186
259
  return output;
187
260
  }
188
261
 
189
262
  @fragment
190
263
  fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
264
+ // Before any branch: a derivative must be taken in uniform control flow.
265
+ let aa = max(fwidth(input.along), 1e-4);
266
+
191
267
  // Get color based on mode
192
268
  var baseColor: vec4<f32>;
193
269
  if (markConstants.colorMode == 0u) {
@@ -202,7 +278,14 @@
202
278
  );
203
279
  }
204
280
 
205
- let finalAlpha = baseColor.a * input.alpha;
281
+ var finalAlpha = baseColor.a * input.alpha;
282
+ // An Other bar's split: its own part up to the selected share, from its
283
+ // base, its unselected part above, with an anti-aliased edge. At share 1
284
+ // the whole bar is its own part, with no half-dimmed top row.
285
+ if (input.share >= 0.0 && input.share < 1.0) {
286
+ let below = 1.0 - smoothstep(input.share - aa, input.share + aa, input.along);
287
+ finalAlpha = baseColor.a * mix(input.alphaRest, input.alpha, below);
288
+ }
206
289
  return vec4<f32>(baseColor.rgb * finalAlpha, finalAlpha);
207
290
  }
208
291
  `
@@ -233,6 +316,7 @@
233
316
  { binding: BINDINGS.Y1, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
234
317
  { binding: BINDINGS.COLOR, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
235
318
  { binding: BINDINGS.ALPHA, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
319
+ { binding: BINDINGS.SHARE, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
236
320
  {
237
321
  binding: BINDINGS.CONSTANTS,
238
322
  type: 'uniform',
@@ -251,16 +335,40 @@
251
335
  // a fraction that the slot table resolves to a DIFFERENT category.
252
336
  $effect(() => {
253
337
  if (!renderer?.resourcesCreated || !bandAxis) return;
338
+ // The Other share travels with the bar's length, on the same clock, so the
339
+ // split and the bar land together. A share that changes alone (a click)
340
+ // starts a transition of its own on that clock.
341
+ const split = splitData ? ([[BINDINGS.SHARE, splitData]] as const) : [];
342
+ const land = splitData && !splitSlides ? [BINDINGS.SHARE] : undefined;
254
343
  if (bandAxis === 'x') {
255
- renderer.writeMorphed('x', x0Data, [
256
- [BINDINGS.Y0, y0Data],
257
- [BINDINGS.Y1, y1Data]
258
- ]);
344
+ renderer.writeMorphed(
345
+ 'x',
346
+ x0Data,
347
+ [[BINDINGS.Y0, y0Data], [BINDINGS.Y1, y1Data], ...split],
348
+ land
349
+ );
259
350
  } else {
260
- renderer.writeMorphed('y', y0Data, [
261
- [BINDINGS.X0, x0Data],
262
- [BINDINGS.X1, x1Data]
263
- ]);
351
+ renderer.writeMorphed(
352
+ 'y',
353
+ y0Data,
354
+ [[BINDINGS.X0, x0Data], [BINDINGS.X1, x1Data], ...split],
355
+ land
356
+ );
357
+ }
358
+ });
359
+ // Without a band axis the share is written as is. A mark that had shares and
360
+ // has none now writes one array of zeros, which also drops a morphed channel:
361
+ // left alone, the shader would keep reading the last shares. A mark that
362
+ // never had any writes nothing and reads the zero-filled stand-in.
363
+ let splitWritten = false;
364
+ $effect(() => {
365
+ if (!renderer?.resourcesCreated) return;
366
+ if (splitData) {
367
+ if (!bandAxis) renderer.writeAttribute(BINDINGS.SHARE, splitData);
368
+ splitWritten = true;
369
+ } else if (splitWritten) {
370
+ renderer.writeAttribute(BINDINGS.SHARE, new Float32Array(Math.max(1, x0Data.length)));
371
+ splitWritten = false;
264
372
  }
265
373
  });
266
374
  $effect(() => {
@@ -295,21 +403,22 @@
295
403
  // re-fires once data loads.
296
404
  if (colorMode === 1 && !colorValueData) return;
297
405
 
298
- const buffer = new ArrayBuffer(48);
406
+ const buffer = new ArrayBuffer(UNIFORM_BYTES.rectsMarkConstants);
299
407
  const view = new DataView(buffer);
300
408
  const LE = true;
301
409
 
302
410
  view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
303
411
  view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
412
+ view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
304
413
  view.setFloat32(UNIFORM_OFFSETS.INSET, inset, LE);
305
- // padding at 12
306
414
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
307
415
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
308
416
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
309
417
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 12, constantColor[3], LE);
310
418
  view.setFloat32(UNIFORM_OFFSETS.DX, dx, LE);
311
419
  view.setFloat32(UNIFORM_OFFSETS.DY, dy, LE);
312
- // padding at 40-47
420
+ view.setUint32(UNIFORM_OFFSETS.OTHER_COLOR_ON_BAND, otherColorOnBand ? 1 : 0, LE);
421
+ view.setUint32(UNIFORM_OFFSETS.SPLIT_ALONG_X, splitAlongX ? 1 : 0, LE);
313
422
 
314
423
  renderer.writeUniform(BINDINGS.CONSTANTS, new Uint8Array(buffer));
315
424
  }
@@ -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,11 +6,19 @@ 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;
10
17
  zIndex?: number;
11
18
  colorMode?: number;
12
19
  colorScaleIndex?: number;
20
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
21
+ groupIndex?: number;
13
22
  constantColor?: [number, number, number, number];
14
23
  inset?: number;
15
24
  dx?: number;
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
+ import { UNIFORM_BYTES } from './uniform-sizes';
2
3
  import { getContext, onMount, onDestroy } from 'svelte';
3
4
  import { RendererHost } from './RendererHost.svelte';
4
5
  import { getGPUContext } from '../../gpu';
5
6
  import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
6
7
  import type { ChartContext, WebGPUCanvasContext } from '../../types';
8
+ import { activityReads, markActivityWGSL } from './activity-reads';
7
9
 
8
10
  // Named Bindings — stable indices regardless of hasExtent
9
11
  const BINDINGS = {
@@ -23,7 +25,7 @@
23
25
  COLOR_MODE: 0,
24
26
  COLOR_SCALE_INDEX: 4,
25
27
  STROKE_WIDTH: 8,
26
- _PAD0: 12,
28
+ GROUP_INDEX: 12,
27
29
  CONSTANT_COLOR: 16,
28
30
  _PAD1: 32,
29
31
  _PAD2: 36,
@@ -42,6 +44,8 @@
42
44
  zIndex?: number;
43
45
  colorMode?: number;
44
46
  colorScaleIndex?: number;
47
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
48
+ groupIndex?: number;
45
49
  constantColor?: [number, number, number, number];
46
50
  strokeWidth?: number;
47
51
  markId?: string;
@@ -58,6 +62,7 @@
58
62
  zIndex = 0,
59
63
  colorMode = 0,
60
64
  colorScaleIndex = 0,
65
+ groupIndex = 0,
61
66
  constantColor = DEFAULT_FILL_COLOR,
62
67
  strokeWidth = 1,
63
68
  markId = ''
@@ -113,12 +118,15 @@
113
118
  const initializationError = $derived(host.error);
114
119
  let lineGeometryBuffer: GPUBuffer;
115
120
 
121
+ /** The selection activities this shader reads; routing reads the same table. */
122
+ const ACTIVITY_READS = activityReads('rulex', hasExtent);
123
+
116
124
  const ruleXShaderCode = `
117
125
  struct MarkConstants {
118
126
  colorMode: u32,
119
127
  colorScaleIndex: u32,
120
128
  strokeWidth: f32,
121
- _pad0: u32,
129
+ groupIndex: u32,
122
130
  constantColor: vec4<f32>,
123
131
  _pad1: f32,
124
132
  _pad2: f32,
@@ -173,7 +181,14 @@
173
181
  var output: VertexOutput;
174
182
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
175
183
  output.colorValue = colorValue;
176
- output.alpha = alpha;
184
+ output.alpha = alpha * selectionDim(
185
+ ${markActivityWGSL(ACTIVITY_READS, {
186
+ color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
187
+ x: 'x',
188
+ y: 'y0',
189
+ group: 'markConstants.groupIndex'
190
+ })}
191
+ );
177
192
  return output;
178
193
  }
179
194
 
@@ -266,14 +281,14 @@
266
281
  if (renderer?.resourcesCreated) {
267
282
  if (colorMode === 1 && !colorValueData) return;
268
283
 
269
- const buffer = new ArrayBuffer(48);
284
+ const buffer = new ArrayBuffer(UNIFORM_BYTES.ruleXMarkConstants);
270
285
  const view = new DataView(buffer);
271
286
  const LE = true;
272
287
 
273
288
  view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
274
289
  view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
290
+ view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
275
291
  view.setFloat32(UNIFORM_OFFSETS.STROKE_WIDTH, strokeWidth, LE);
276
- // padding at 12
277
292
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
278
293
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
279
294
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
@@ -9,6 +9,8 @@ interface Props {
9
9
  zIndex?: number;
10
10
  colorMode?: number;
11
11
  colorScaleIndex?: number;
12
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
13
+ groupIndex?: number;
12
14
  constantColor?: [number, number, number, number];
13
15
  strokeWidth?: number;
14
16
  markId?: string;
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
+ import { UNIFORM_BYTES } from './uniform-sizes';
2
3
  import { getContext, onMount, onDestroy } from 'svelte';
3
4
  import { RendererHost } from './RendererHost.svelte';
4
5
  import { getGPUContext } from '../../gpu';
5
6
  import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
6
7
  import type { ChartContext, WebGPUCanvasContext } from '../../types';
8
+ import { activityReads, markActivityWGSL } from './activity-reads';
7
9
 
8
10
  // Named Bindings — stable indices regardless of hasExtent
9
11
  const BINDINGS = {
@@ -23,7 +25,7 @@
23
25
  COLOR_MODE: 0,
24
26
  COLOR_SCALE_INDEX: 4,
25
27
  STROKE_WIDTH: 8,
26
- _PAD0: 12,
28
+ GROUP_INDEX: 12,
27
29
  CONSTANT_COLOR: 16,
28
30
  _PAD1: 32,
29
31
  _PAD2: 36,
@@ -42,6 +44,8 @@
42
44
  zIndex?: number;
43
45
  colorMode?: number;
44
46
  colorScaleIndex?: number;
47
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
48
+ groupIndex?: number;
45
49
  constantColor?: [number, number, number, number];
46
50
  strokeWidth?: number;
47
51
  markId?: string;
@@ -58,6 +62,7 @@
58
62
  zIndex = 0,
59
63
  colorMode = 0,
60
64
  colorScaleIndex = 0,
65
+ groupIndex = 0,
61
66
  constantColor = DEFAULT_FILL_COLOR,
62
67
  strokeWidth = 1,
63
68
  markId = ''
@@ -112,12 +117,15 @@
112
117
  const initializationError = $derived(host.error);
113
118
  let lineGeometryBuffer: GPUBuffer;
114
119
 
120
+ /** The selection activities this shader reads; routing reads the same table. */
121
+ const ACTIVITY_READS = activityReads('ruley', hasExtent);
122
+
115
123
  const ruleYShaderCode = `
116
124
  struct MarkConstants {
117
125
  colorMode: u32,
118
126
  colorScaleIndex: u32,
119
127
  strokeWidth: f32,
120
- _pad0: u32,
128
+ groupIndex: u32,
121
129
  constantColor: vec4<f32>,
122
130
  _pad1: f32,
123
131
  _pad2: f32,
@@ -172,7 +180,14 @@
172
180
  var output: VertexOutput;
173
181
  output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
174
182
  output.colorValue = colorValue;
175
- output.alpha = alpha;
183
+ output.alpha = alpha * selectionDim(
184
+ ${markActivityWGSL(ACTIVITY_READS, {
185
+ color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
186
+ x: 'x0',
187
+ y: 'y',
188
+ group: 'markConstants.groupIndex'
189
+ })}
190
+ );
176
191
  return output;
177
192
  }
178
193
 
@@ -265,14 +280,14 @@
265
280
  if (renderer?.resourcesCreated) {
266
281
  if (colorMode === 1 && !colorValueData) return;
267
282
 
268
- const buffer = new ArrayBuffer(48);
283
+ const buffer = new ArrayBuffer(UNIFORM_BYTES.ruleYMarkConstants);
269
284
  const view = new DataView(buffer);
270
285
  const LE = true;
271
286
 
272
287
  view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
273
288
  view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
289
+ view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
274
290
  view.setFloat32(UNIFORM_OFFSETS.STROKE_WIDTH, strokeWidth, LE);
275
- // padding at 12
276
291
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
277
292
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
278
293
  view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
@@ -9,6 +9,8 @@ interface Props {
9
9
  zIndex?: number;
10
10
  colorMode?: number;
11
11
  colorScaleIndex?: number;
12
+ /** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
13
+ groupIndex?: number;
12
14
  constantColor?: [number, number, number, number];
13
15
  strokeWidth?: number;
14
16
  markId?: string;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Which selection activities a mark reads: its ordinal colour, its band x,
3
+ * its band y. One answer per renderer, used twice:
4
+ *
5
+ * - The renderer builds its shader's `markActivity` call from it, so a read
6
+ * the table does not list is passed as 1.0 and cannot happen.
7
+ * - MarkLifecycleV2 turns it into the mark's `reads`, the identity keys of the
8
+ * fields it dims by, which decide whether a categorical selection on a field
9
+ * dims the mark or filters it.
10
+ *
11
+ * The two must agree exactly. A mark routed to dim on a field its shader does
12
+ * not read would be neither dimmed nor filtered; one whose shader reads a field
13
+ * routing missed would be filtered and dimmed at once. Deriving both from this
14
+ * table is what keeps them together. One case they still disagree on: a field
15
+ * whose activity block did not fit in the identity slot buffer (see the known
16
+ * limit in identity-slot-packing.ts).
17
+ *
18
+ * A function of the config, not a constant per renderer, because a rule's
19
+ * answer depends on a prop: a rule without an extent spans the whole cross
20
+ * axis, belongs to no category on it, and must not dim by it.
21
+ */
22
+ import { NO_GROUP } from '../../gpu/identity-slot-packing';
23
+ /** The renderers that read selection activity. Text reads it on the CPU. */
24
+ export type ActivityRenderer = 'dots' | 'rects' | 'rulex' | 'ruley' | 'lines' | 'areas' | 'parallel' | 'text';
25
+ export interface ActivityReads {
26
+ readonly color: boolean;
27
+ readonly x: boolean;
28
+ readonly y: boolean;
29
+ }
30
+ /**
31
+ * 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.
34
+ *
35
+ * Not yet supported: a line or area grouped by `z` that is not its colour
36
+ * field. Its series ids reach the shader, but only to break the line between
37
+ * series; no activity is read through them, and the series field is not among
38
+ * the mark's reads. So a selection on that field filters the mark in other
39
+ * charts and does nothing in its own, instead of dimming the other series.
40
+ */
41
+ export declare function activityReads(renderer: ActivityRenderer, hasExtent?: boolean): ActivityReads;
42
+ /**
43
+ * Whether a rule spans an extent on its cross axis: both ends given, as a
44
+ * field or a constant. A rule mark builds its renderer's extent data from this
45
+ * and hands it to routing as `ruleExtent`, so the two cannot disagree; a
46
+ * constant end (y1={0}, or the y shorthand) counts like a field.
47
+ */
48
+ export declare function ruleHasExtent(lo: unknown, hi: unknown): boolean;
49
+ /** The renderer a mark type draws with, or null for one that reads no activity. */
50
+ export declare function rendererForMark(markType: string): ActivityRenderer | null;
51
+ /**
52
+ * A mark's reads: the identity keys of the fields whose activity it reads,
53
+ * sorted, from what its renderer reads and the identity key of each of its
54
+ * categorical scales (null for a channel with no such scale, or a scale with
55
+ * no identity: a field with no identity has no activity to dim by, so a
56
+ * selection on it filters the mark).
57
+ */
58
+ export declare function readsFor(reads: ActivityReads, keys: {
59
+ color: string | null;
60
+ x: string | null;
61
+ y: string | null;
62
+ }): readonly string[];
63
+ /** A mark that reads no activity. Shared, so its reads never look changed. */
64
+ export declare const NO_READS: readonly string[];
65
+ /**
66
+ * How a mark responds to selections in its filter group (the `selectionResponse`
67
+ * prop). 'auto', the default: a categorical selection on a field the mark draws
68
+ * by (its band axes, its ordinal colour) dims it, and any other selection
69
+ * filters it. 'filter': every selection filters it, even on a field it draws
70
+ * by, so its aggregates are recomputed for the subset (top N within a region).
71
+ */
72
+ export type SelectionResponse = 'auto' | 'filter';
73
+ export { NO_GROUP };
74
+ /** A mark's reads under its selection response: none for 'filter', which never dims. */
75
+ export declare function readsForResponse(response: SelectionResponse | undefined, reads: readonly string[]): readonly string[];
76
+ /** The group index a mark's shader reads activity with: NO_GROUP for 'filter'. */
77
+ export declare function groupIndexForResponse(response: SelectionResponse | undefined, groupIndex: number): number;
78
+ /**
79
+ * The WGSL activity of a renderer that reads colour only (lines, areas,
80
+ * parallel coordinates): `markColorActivity(...)`, or 1.0 if the table says
81
+ * it reads nothing. Throws for a renderer that reads a band axis, whose call
82
+ * is markActivityWGSL.
83
+ */
84
+ export declare function colorActivityWGSL(reads: ActivityReads, args: {
85
+ color: string;
86
+ group: string;
87
+ }): string;
88
+ /**
89
+ * The WGSL `markActivity(...)` call for `reads`: each activity the renderer
90
+ * reads, and 1.0 for each it does not. `color` is markColorActivity's first
91
+ * three arguments; `x` and `y` are the band channel values; `group` the mark's
92
+ * group index.
93
+ */
94
+ /**
95
+ * The activity of an Other row: the same terms as markActivityWGSL without the
96
+ * band axes', and without colour's when colour is the band's own field
97
+ * (`skipColor`, a WGSL bool). Those read the Other bucket's own fade, and the
98
+ * bar's split already shows how much of it is selected (Rects); multiplying
99
+ * them in as well would dim it twice. Activity on any other field stays.
100
+ */
101
+ export declare function otherRowActivityWGSL(reads: ActivityReads, args: {
102
+ color: string;
103
+ group: string;
104
+ skipColor: string;
105
+ }): string;
106
+ export declare function markActivityWGSL(reads: ActivityReads, args: {
107
+ color: string;
108
+ x: string;
109
+ y: string;
110
+ group: string;
111
+ }): string;