@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
@@ -0,0 +1,67 @@
1
+ import { untrack } from 'svelte';
2
+ import { SvelteMap } from 'svelte/reactivity';
3
+ /**
4
+ * Whether any of a chart's cells is on screen, for its marks to stop
5
+ * re-querying while it is not (see MarkHandle.active).
6
+ *
7
+ * Owned by the chart container, which in facet mode holds the marks for many
8
+ * cells: the marks stay active while any cell is visible. Each cell reports
9
+ * what the GPU coordinator's IntersectionObserver and page visibility say
10
+ * about it; the observer has a 200 px margin, so a chart counts as visible
11
+ * just before it scrolls in. A cell that has not reported counts as visible,
12
+ * as it does for the coordinator, so nothing is held back before the first
13
+ * report.
14
+ */
15
+ export class ChartVisibility {
16
+ #cells = new SvelteMap();
17
+ #peers;
18
+ constructor(peers = null) {
19
+ this.#peers = peers;
20
+ }
21
+ /** A cell's visibility changed. */
22
+ set(cellId, visible) {
23
+ this.#cells.set(cellId, visible);
24
+ }
25
+ /** A cell unmounted. */
26
+ remove(cellId) {
27
+ this.#cells.delete(cellId);
28
+ }
29
+ /**
30
+ * True while any cell is visible, or none has reported. Reactive: it reads
31
+ * the SvelteMap. A getter, not a derived, since a chart has few cells.
32
+ */
33
+ get visible() {
34
+ if (this.#cells.size === 0)
35
+ return true;
36
+ for (const v of this.#cells.values())
37
+ if (v)
38
+ return true;
39
+ return false;
40
+ }
41
+ /**
42
+ * Whether a chart other than this one is visible and draws `scale`, so
43
+ * that changing the scale moves something in view. A chart catching up on
44
+ * its return snaps its own parts but springs a scale like this: the chart
45
+ * being watched moves through the scale, as it would for any change.
46
+ *
47
+ * Asked when a result lands, not when it was sent: visibility changes
48
+ * during a scroll. "Visible" is the coordinator's: it includes the 200 px
49
+ * margin, so a chart in the margin, which nobody sees yet, still counts and
50
+ * keeps the spring. Untracked: a point-in-time answer, asked from inside
51
+ * effects that must not re-run when a cell scrolls.
52
+ */
53
+ shownElsewhere(scale) {
54
+ const peers = this.#peers;
55
+ if (!peers)
56
+ return false;
57
+ return untrack(() => {
58
+ for (const chartId of peers.chartsUsing(scale)) {
59
+ if (this.#cells.has(chartId))
60
+ continue;
61
+ if (peers.isChartVisible(chartId))
62
+ return true;
63
+ }
64
+ return false;
65
+ });
66
+ }
67
+ }
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { hasEntered } from '../../scales/entered';
2
3
  import { getContext } from 'svelte';
3
4
  import { GridLine } from '../rendering';
4
5
  import type { ChartContext, ScaleStateInstance } from '../../types';
@@ -38,7 +39,9 @@
38
39
  let yBuffer = new Float32Array(64);
39
40
 
40
41
  function ticksOf(scale: ScaleStateInstance | null, which: 'x' | 'y'): Float32Array | null {
41
- const ticks = scale?.getTicks ? (scale.getTicks() as ArrayLike<number>) : null;
42
+ // None until the scale has entered: its domain is a placeholder before that.
43
+ const ticks =
44
+ scale?.getTicks && hasEntered(scale) ? (scale.getTicks() as ArrayLike<number>) : null;
42
45
  if (!ticks || ticks.length === 0) return null;
43
46
  let buffer = which === 'x' ? xBuffer : yBuffer;
44
47
  if (buffer.length < ticks.length) {
@@ -58,6 +61,12 @@
58
61
  const yTickValues = $derived(ticksOf(yScale, 'y'));
59
62
  const xAlpha = $derived(visibilityOf(xScale, xBand));
60
63
  const yAlpha = $derived(visibilityOf(yScale, yBand));
64
+ // A band axis's line opacity follows its layout: when that landed at once
65
+ // (a catch-up result), the opacity lands with it.
66
+ const landedOf = (scale: ScaleStateInstance | null, band: boolean) =>
67
+ band && scale ? (scale as BandScaleState).slotsLandedLayout : 0;
68
+ const xLands = $derived(landedOf(xScale, xBand));
69
+ const yLands = $derived(landedOf(yScale, yBand));
61
70
  </script>
62
71
 
63
72
  {#if xTickValues}
@@ -65,6 +74,7 @@
65
74
  axis="x"
66
75
  positions={xTickValues}
67
76
  alpha={xAlpha}
77
+ landsFor={xLands}
68
78
  bandAxis={xBand ? 'x' : null}
69
79
  {color}
70
80
  {lineWidth}
@@ -76,6 +86,7 @@
76
86
  axis="y"
77
87
  positions={yTickValues}
78
88
  alpha={yAlpha}
89
+ landsFor={yLands}
79
90
  bandAxis={yBand ? 'y' : null}
80
91
  {color}
81
92
  {lineWidth}
@@ -194,7 +194,13 @@
194
194
  column: scaleState.field,
195
195
  label: scaleState.field,
196
196
  values: value ? [(value as number[])[0], (value as number[])[1]] : []
197
- })
197
+ }),
198
+ // The clause was removed from outside (clearAll, a filter pill): take the
199
+ // brush down too, which only this legend holds.
200
+ onCleared: () => {
201
+ brushT0 = null;
202
+ brushT1 = null;
203
+ }
198
204
  })
199
205
  : null;
200
206
 
@@ -65,7 +65,7 @@
65
65
  id: number | null;
66
66
  value: DimValue;
67
67
  color: string;
68
- /** An Other bucket: shown, not clickable (it names no value). */
68
+ /** An Other bucket: a click selects what it holds (see toggleOther). */
69
69
  bucket: boolean;
70
70
  }
71
71
 
@@ -128,8 +128,11 @@
128
128
  onDestroy(() => toggle?.destroy());
129
129
 
130
130
  // Shift/meta-click adds or removes; selecting every value clears instead.
131
- function handleClick(value: DimValue, event: MouseEvent) {
132
- toggle?.toggle([value], event.shiftKey || event.metaKey, scaleState.domain.length);
131
+ function handleClick(item: Item, event: MouseEvent) {
132
+ const multi = event.shiftKey || event.metaKey;
133
+ // An Other bucket names no value: the click selects what it holds.
134
+ if (item.bucket && item.id !== null) toggle?.toggleOther(item.id, null, multi);
135
+ else toggle?.toggle([item.value], multi, scaleState.domain.length);
133
136
  }
134
137
  </script>
135
138
 
@@ -149,11 +152,11 @@
149
152
  >
150
153
  {#each items as item (item.id ?? item.value)}
151
154
  {@const { value, color } = item}
152
- {#if isInteractive && !item.bucket}
155
+ {#if isInteractive}
153
156
  <button
154
157
  class="legend-item legend-item-interactive"
155
158
  style:opacity={opacityOf(item)}
156
- onclick={(e) => handleClick(value, e)}
159
+ onclick={(e) => handleClick(item, e)}
157
160
  >
158
161
  <span
159
162
  class="legend-swatch"
@@ -48,6 +48,8 @@
48
48
  table,
49
49
  selection = undefined,
50
50
  selectionResponse = undefined,
51
+ nulls = undefined,
52
+ holdsEntrance = undefined,
51
53
  y,
52
54
  x,
53
55
  x1: x1Prop,
@@ -101,6 +103,8 @@
101
103
  transforms: () => transforms,
102
104
  selection: () => selection,
103
105
  selectionResponse: () => selectionResponse,
106
+ nulls: () => nulls,
107
+ holdsEntrance: () => holdsEntrance,
104
108
  limit: () => limit,
105
109
  preferredXScaleType: () => preferredXScaleType,
106
110
  preferredYScaleType: () => preferredYScaleType,
@@ -146,7 +150,8 @@
146
150
  seriesChannelKey,
147
151
  () => setup,
148
152
  scaleRegistry,
149
- chartContext
153
+ chartContext,
154
+ () => markColor.scaleState
150
155
  );
151
156
 
152
157
  // Color: fill determines area color — reads from entry
@@ -237,8 +242,8 @@
237
242
  resolveFitDomain: () => resolvedFitAxis
238
243
  });
239
244
 
240
- let xScaleId = $derived(chartContext.xScaleState?.id);
241
- let yScaleId = $derived(chartContext.yScaleState?.id);
245
+ // Rebuild only when a scale is replaced, not on its first resolution.
246
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
242
247
 
243
248
  let instanceCount = $derived(computeInstanceCount(setup?.data, entry.y, x2));
244
249
 
@@ -305,7 +310,7 @@
305
310
  // --- SETUP LIFECYCLE ---
306
311
 
307
312
  $effect(() => {
308
- (void config, xScaleId, yScaleId);
313
+ (void config, scaleGeneration);
309
314
  untrack(() => {
310
315
  if (setup) {
311
316
  gpuCoordinator?.markForRetain(markId);
@@ -373,6 +378,8 @@
373
378
  colorMode={markColor.colorMode}
374
379
  colorScaleIndex={markColor.colorScaleIndex}
375
380
  groupIndex={setup?.groupIndex ?? 0}
381
+ activitySeriesMode={markSeries.activityMode}
382
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
376
383
  constantColor={markColor.constantColor}
377
384
  fillOpacity={entry.fillOpacity ?? 0.5}
378
385
  dotRadius={effectiveDotRadius}
@@ -381,6 +388,7 @@
381
388
  orientation="horizontal"
382
389
  facetData={setup?.facetData}
383
390
  dataVersion={setup?.dataVersion || 0}
391
+ grow={!hasExplicitBounds}
384
392
  />
385
393
  {#if (entry.strokeWidth ?? 0) > 0}
386
394
  <Lines
@@ -394,11 +402,14 @@
394
402
  activityColorMode={outline.activityColorMode}
395
403
  activityColorScaleIndex={outline.activityColorScaleIndex}
396
404
  groupIndex={setup?.groupIndex ?? 0}
405
+ activitySeriesMode={markSeries.activityMode}
406
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
397
407
  constantColor={strokeConstantColor}
398
408
  strokeWidth={entry.strokeWidth ?? 0}
399
409
  zIndex={1}
400
410
  facetData={setup?.facetData}
401
411
  dataVersion={setup?.dataVersion || 0}
412
+ grow={hasExplicitBounds ? null : 'x'}
402
413
  />
403
414
  {#if showBothBoundaries}
404
415
  <Lines
@@ -412,11 +423,14 @@
412
423
  activityColorMode={outline.activityColorMode}
413
424
  activityColorScaleIndex={outline.activityColorScaleIndex}
414
425
  groupIndex={setup?.groupIndex ?? 0}
426
+ activitySeriesMode={markSeries.activityMode}
427
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
415
428
  constantColor={strokeConstantColor}
416
429
  strokeWidth={entry.strokeWidth ?? 0}
417
430
  zIndex={1}
418
431
  facetData={setup?.facetData}
419
432
  dataVersion={setup?.dataVersion || 0}
433
+ grow={hasExplicitBounds ? null : 'x'}
420
434
  />
421
435
  {/if}
422
436
  {/if}
@@ -48,6 +48,8 @@
48
48
  table,
49
49
  selection = undefined,
50
50
  selectionResponse = undefined,
51
+ nulls = undefined,
52
+ holdsEntrance = undefined,
51
53
  x,
52
54
  y,
53
55
  y1: y1Prop,
@@ -101,6 +103,8 @@
101
103
  transforms: () => transforms,
102
104
  selection: () => selection,
103
105
  selectionResponse: () => selectionResponse,
106
+ nulls: () => nulls,
107
+ holdsEntrance: () => holdsEntrance,
104
108
  limit: () => limit,
105
109
  preferredXScaleType: () => preferredXScaleType,
106
110
  preferredYScaleType: () => preferredYScaleType,
@@ -146,7 +150,8 @@
146
150
  seriesChannelKey,
147
151
  () => setup,
148
152
  scaleRegistry,
149
- chartContext
153
+ chartContext,
154
+ () => markColor.scaleState
150
155
  );
151
156
 
152
157
  // Color: fill determines area color — reads from entry
@@ -237,8 +242,8 @@
237
242
  resolveFitDomain: () => resolvedFitAxis
238
243
  });
239
244
 
240
- let xScaleId = $derived(chartContext.xScaleState?.id);
241
- let yScaleId = $derived(chartContext.yScaleState?.id);
245
+ // Rebuild only when a scale is replaced, not on its first resolution.
246
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
242
247
 
243
248
  let instanceCount = $derived(computeInstanceCount(setup?.data, entry.x, y2));
244
249
 
@@ -304,7 +309,7 @@
304
309
  // --- SETUP LIFECYCLE ---
305
310
 
306
311
  $effect(() => {
307
- (void config, xScaleId, yScaleId);
312
+ (void config, scaleGeneration);
308
313
  untrack(() => {
309
314
  if (setup) {
310
315
  gpuCoordinator?.markForRetain(markId);
@@ -373,6 +378,8 @@
373
378
  colorMode={markColor.colorMode}
374
379
  colorScaleIndex={markColor.colorScaleIndex}
375
380
  groupIndex={setup?.groupIndex ?? 0}
381
+ activitySeriesMode={markSeries.activityMode}
382
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
376
383
  constantColor={markColor.constantColor}
377
384
  fillOpacity={entry.fillOpacity ?? 0.5}
378
385
  dotRadius={effectiveDotRadius}
@@ -381,6 +388,7 @@
381
388
  orientation="vertical"
382
389
  facetData={setup?.facetData}
383
390
  dataVersion={setup?.dataVersion || 0}
391
+ grow={!hasExplicitBounds}
384
392
  />
385
393
  {#if (entry.strokeWidth ?? 0) > 0}
386
394
  <Lines
@@ -394,11 +402,14 @@
394
402
  activityColorMode={outline.activityColorMode}
395
403
  activityColorScaleIndex={outline.activityColorScaleIndex}
396
404
  groupIndex={setup?.groupIndex ?? 0}
405
+ activitySeriesMode={markSeries.activityMode}
406
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
397
407
  constantColor={strokeConstantColor}
398
408
  strokeWidth={entry.strokeWidth ?? 0}
399
409
  zIndex={1}
400
410
  facetData={setup?.facetData}
401
411
  dataVersion={setup?.dataVersion || 0}
412
+ grow={hasExplicitBounds ? null : 'y'}
402
413
  />
403
414
  {#if showBothBoundaries}
404
415
  <Lines
@@ -412,11 +423,14 @@
412
423
  activityColorMode={outline.activityColorMode}
413
424
  activityColorScaleIndex={outline.activityColorScaleIndex}
414
425
  groupIndex={setup?.groupIndex ?? 0}
426
+ activitySeriesMode={markSeries.activityMode}
427
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
415
428
  constantColor={strokeConstantColor}
416
429
  strokeWidth={entry.strokeWidth ?? 0}
417
430
  zIndex={1}
418
431
  facetData={setup?.facetData}
419
432
  dataVersion={setup?.dataVersion || 0}
433
+ grow={hasExplicitBounds ? null : 'y'}
420
434
  />
421
435
  {/if}
422
436
  {/if}
@@ -41,6 +41,9 @@
41
41
  table,
42
42
  selection = undefined,
43
43
  selectionResponse = undefined,
44
+ nulls = undefined,
45
+ holdsEntrance = undefined,
46
+ contributeDomain = undefined,
44
47
  x,
45
48
  x1: x1Prop,
46
49
  x2: x2Prop,
@@ -83,6 +86,9 @@
83
86
  transforms: () => transforms,
84
87
  selection: () => selection,
85
88
  selectionResponse: () => selectionResponse,
89
+ nulls: () => nulls,
90
+ holdsEntrance: () => holdsEntrance,
91
+ contributeDomain: () => contributeDomain,
86
92
  limit: () => limit,
87
93
  padding: () => padding,
88
94
  paddingInner: () => paddingInner,
@@ -174,8 +180,8 @@
174
180
 
175
181
  const reactiveConfig = buildReactiveConfig(entry);
176
182
 
177
- let xScaleId = $derived(chartContext.xScaleState?.id);
178
- let yScaleId = $derived(chartContext.yScaleState?.id);
183
+ // Rebuild only when a scale is replaced, not on its first resolution.
184
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
179
185
 
180
186
  let instanceCount = $derived(computeInstanceCount(setup?.data, x2, entry.y));
181
187
 
@@ -238,7 +244,7 @@
238
244
  // --- SETUP LIFECYCLE ---
239
245
 
240
246
  $effect(() => {
241
- (void config, xScaleId, yScaleId);
247
+ (void config, scaleGeneration);
242
248
  untrack(() => {
243
249
  if (setup) {
244
250
  gpuCoordinator?.markForRetain(markId);
@@ -280,7 +286,14 @@
280
286
  colorColumn: 'fill',
281
287
  bandAxes: { y: true },
282
288
  markColor,
283
- displayFields: resolvedTip.displayFields
289
+ displayFields: resolvedTip.displayFields,
290
+ otherStats: (point) => {
291
+ const id = point.__bandIdY;
292
+ const facet = point.__facetId;
293
+ return typeof id === 'number'
294
+ ? setup!.otherRows.statsAt(id, typeof facet === 'number' ? facet : null)
295
+ : null;
296
+ }
284
297
  })
285
298
  );
286
299
  });
@@ -303,13 +316,16 @@
303
316
  {y1Data}
304
317
  colorValueData={markColor.data}
305
318
  alphaData={opacityData}
319
+ share={setup?.otherRows.split ?? null}
306
320
  colorMode={markColor.colorMode}
307
321
  colorScaleIndex={markColor.colorScaleIndex}
308
322
  groupIndex={setup?.groupIndex ?? 0}
309
323
  constantColor={markColor.constantColor}
310
324
  facetData={setup?.facetData}
311
325
  dataVersion={setup?.dataVersion || 0}
326
+ landsFor={setup?.landsFor ?? 0}
312
327
  bandAxis={setup?.alignedBand ?? null}
328
+ grow={hasExplicitBounds ? null : 'x'}
313
329
  inset={entry.inset ?? 0}
314
330
  dx={entry.dx ?? 0}
315
331
  dy={entry.dy ?? 0}
@@ -40,6 +40,9 @@
40
40
  table,
41
41
  selection = undefined,
42
42
  selectionResponse = undefined,
43
+ nulls = undefined,
44
+ holdsEntrance = undefined,
45
+ contributeDomain = undefined,
43
46
  x,
44
47
  y,
45
48
  y1: y1Prop,
@@ -82,6 +85,9 @@
82
85
  transforms: () => transforms,
83
86
  selection: () => selection,
84
87
  selectionResponse: () => selectionResponse,
88
+ nulls: () => nulls,
89
+ holdsEntrance: () => holdsEntrance,
90
+ contributeDomain: () => contributeDomain,
85
91
  limit: () => limit,
86
92
  padding: () => padding,
87
93
  paddingInner: () => paddingInner,
@@ -175,8 +181,8 @@
175
181
 
176
182
  const reactiveConfig = buildReactiveConfig(entry);
177
183
 
178
- let xScaleId = $derived(chartContext.xScaleState?.id);
179
- let yScaleId = $derived(chartContext.yScaleState?.id);
184
+ // Rebuild only when a scale is replaced, not on its first resolution.
185
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
180
186
 
181
187
  let instanceCount = $derived(computeInstanceCount(setup?.data, y2, entry.x));
182
188
 
@@ -239,7 +245,7 @@
239
245
  // --- SETUP LIFECYCLE ---
240
246
 
241
247
  $effect(() => {
242
- (void config, xScaleId, yScaleId);
248
+ (void config, scaleGeneration);
243
249
  untrack(() => {
244
250
  if (setup) {
245
251
  gpuCoordinator?.markForRetain(markId);
@@ -281,7 +287,14 @@
281
287
  colorColumn: 'fill',
282
288
  bandAxes: { x: true },
283
289
  markColor,
284
- displayFields: resolvedTip.displayFields
290
+ displayFields: resolvedTip.displayFields,
291
+ otherStats: (point) => {
292
+ const id = point.__bandIdX;
293
+ const facet = point.__facetId;
294
+ return typeof id === 'number'
295
+ ? setup!.otherRows.statsAt(id, typeof facet === 'number' ? facet : null)
296
+ : null;
297
+ }
285
298
  })
286
299
  );
287
300
  });
@@ -304,13 +317,16 @@
304
317
  {y1Data}
305
318
  colorValueData={markColor.data}
306
319
  alphaData={opacityData}
320
+ share={setup?.otherRows.split ?? null}
307
321
  colorMode={markColor.colorMode}
308
322
  colorScaleIndex={markColor.colorScaleIndex}
309
323
  groupIndex={setup?.groupIndex ?? 0}
310
324
  constantColor={markColor.constantColor}
311
325
  facetData={setup?.facetData}
312
326
  dataVersion={setup?.dataVersion || 0}
327
+ landsFor={setup?.landsFor ?? 0}
313
328
  bandAxis={setup?.alignedBand ?? null}
329
+ grow={hasExplicitBounds ? null : 'y'}
314
330
  inset={entry.inset ?? 0}
315
331
  dx={entry.dx ?? 0}
316
332
  dy={entry.dy ?? 0}
@@ -43,6 +43,8 @@
43
43
  table,
44
44
  selection = undefined,
45
45
  selectionResponse = undefined,
46
+ nulls = undefined,
47
+ holdsEntrance = undefined,
46
48
  x,
47
49
  y,
48
50
  fill = null,
@@ -82,6 +84,8 @@
82
84
  transforms: () => transforms,
83
85
  selection: () => selection,
84
86
  selectionResponse: () => selectionResponse,
87
+ nulls: () => nulls,
88
+ holdsEntrance: () => holdsEntrance,
85
89
  limit: () => limit,
86
90
  preferredXScaleType: () => preferredXScaleType,
87
91
  preferredYScaleType: () => preferredYScaleType,
@@ -175,8 +179,8 @@
175
179
  resolveFitDomain: () => resolvedFitAxis
176
180
  });
177
181
 
178
- let xScaleId = $derived(chartContext.xScaleState?.id);
179
- let yScaleId = $derived(chartContext.yScaleState?.id);
182
+ // Rebuild only when a scale is replaced, not on its first resolution.
183
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
180
184
 
181
185
  let instanceCount = $derived(computeInstanceCount(setup?.data, entry.x, entry.y));
182
186
 
@@ -244,7 +248,7 @@
244
248
  // --- SETUP LIFECYCLE ---
245
249
 
246
250
  $effect(() => {
247
- (void config, xScaleId, yScaleId);
251
+ (void config, scaleGeneration);
248
252
  untrack(() => {
249
253
  if (setup) {
250
254
  gpuCoordinator?.markForRetain(markId);
@@ -314,6 +318,7 @@
314
318
  sizeScaleIndex={markSize.sizeScaleIndex}
315
319
  facetData={setup?.facetData}
316
320
  dataVersion={setup?.dataVersion || 0}
321
+ landsFor={setup?.landsFor ?? 0}
317
322
  bandAxis={setup?.alignedBand ?? null}
318
323
  />
319
324
  {/if}
@@ -71,6 +71,8 @@
71
71
  table,
72
72
  selection = undefined,
73
73
  selectionResponse = undefined,
74
+ nulls = undefined,
75
+ holdsEntrance = undefined,
74
76
  x,
75
77
  y,
76
78
  stroke = null,
@@ -124,6 +126,8 @@
124
126
  transforms: () => transforms,
125
127
  selection: () => selection,
126
128
  selectionResponse: () => selectionResponse,
129
+ nulls: () => nulls,
130
+ holdsEntrance: () => holdsEntrance,
127
131
  limit: () => limit,
128
132
  preferredXScaleType: () => preferredXScaleType,
129
133
  preferredYScaleType: () => preferredYScaleType,
@@ -146,14 +150,16 @@
146
150
  inferMarkScaleType(entry.y, entry.table, entry.preferredYScaleType)
147
151
  );
148
152
 
149
- // Series grouping: z overrides stroke for grouping
153
+ // Series grouping: z overrides stroke for grouping. A selection on either
154
+ // field dims the line (see activityReads in activity-reads.ts).
150
155
  const seriesChannelKey = entry.z != null ? 'z' : 'stroke';
151
156
  const markSeries = new MarkSeries(
152
157
  () => entry.z ?? entry.stroke,
153
158
  seriesChannelKey,
154
159
  () => setup,
155
160
  scaleRegistry,
156
- chartContext
161
+ chartContext,
162
+ () => markColor.scaleState
157
163
  );
158
164
 
159
165
  // Color: stroke determines line color — reads from entry
@@ -228,8 +234,8 @@
228
234
  resolveFitDomain: () => (_isOverlay ? undefined : resolvedFitAxis)
229
235
  });
230
236
 
231
- let xScaleId = $derived(chartContext.xScaleState?.id);
232
- let yScaleId = $derived(chartContext.yScaleState?.id);
237
+ // Rebuild only when a scale is replaced, not on its first resolution.
238
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
233
239
 
234
240
  let instanceCount = $derived(computeInstanceCount(setup?.data, entry.x, entry.y));
235
241
 
@@ -264,8 +270,7 @@
264
270
  // changes via setRungParam (LOD window size adjustments should re-query,
265
271
  // not recreate the lifecycle).
266
272
  void channelSelect;
267
- void xScaleId;
268
- void yScaleId;
273
+ void scaleGeneration;
269
274
  void effectiveXScaleType;
270
275
  void effectiveYScaleType;
271
276
  untrack(() => {
@@ -573,6 +578,8 @@
573
578
  colorMode={markColor.colorMode}
574
579
  colorScaleIndex={markColor.colorScaleIndex}
575
580
  groupIndex={setup?.groupIndex ?? 0}
581
+ activitySeriesMode={markSeries.activityMode}
582
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
576
583
  constantColor={markColor.constantColor}
577
584
  strokeWidth={entry.strokeWidth ?? 2}
578
585
  dotRadius={effectiveDotRadius}
@@ -12,6 +12,7 @@
12
12
 
13
13
  <script lang="ts">
14
14
  import type { SelectionResponse } from '../rendering/activity-reads';
15
+ import type { NullsResponse } from '../../types';
15
16
  import { getContext, onDestroy, untrack } from 'svelte';
16
17
  import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
17
18
  import { ParallelLines } from '../rendering';
@@ -34,6 +35,9 @@
34
35
  selection?: string;
35
36
  /** How this mark responds to selections in its group (see BaseMarkProps). */
36
37
  selectionResponse?: SelectionResponse;
38
+ nulls?: NullsResponse;
39
+ /** Whether the chart's entrance waits for this mark (see BaseMarkProps). */
40
+ holdsEntrance?: boolean;
37
41
  transforms?: Transform[];
38
42
  }
39
43
 
@@ -47,6 +51,8 @@
47
51
  curve = 'linear',
48
52
  selection = undefined,
49
53
  selectionResponse = undefined,
54
+ nulls = undefined,
55
+ holdsEntrance = undefined,
50
56
  transforms = []
51
57
  }: Props = $props();
52
58
 
@@ -64,6 +70,8 @@
64
70
  scheme: () => scheme,
65
71
  selection: () => selection,
66
72
  selectionResponse: () => selectionResponse,
73
+ nulls: () => nulls,
74
+ holdsEntrance: () => holdsEntrance,
67
75
  transforms: () => transforms
68
76
  });
69
77
 
@@ -116,12 +124,13 @@
116
124
 
117
125
  const reactiveConfig = buildReactiveConfig(entry);
118
126
 
119
- let xScaleId = $derived(chartContext.xScaleState?.id);
127
+ // Rebuild only when a scale is replaced, not on its first resolution.
128
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
120
129
 
121
130
  // --- SETUP LIFECYCLE ---
122
131
  $effect(() => {
123
132
  void config;
124
- void xScaleId;
133
+ void scaleGeneration;
125
134
  untrack(() => {
126
135
  setup?.destroy();
127
136
  setup = null;
@@ -10,6 +10,7 @@ export declare const parallelParity: {
10
10
  reverseScales: boolean;
11
11
  };
12
12
  import type { SelectionResponse } from '../rendering/activity-reads';
13
+ import type { NullsResponse } from '../../types';
13
14
  import type { FieldRef, ColorScheme, CurveType, Transform } from '../../types';
14
15
  interface Props {
15
16
  table: string;
@@ -22,6 +23,9 @@ interface Props {
22
23
  selection?: string;
23
24
  /** How this mark responds to selections in its group (see BaseMarkProps). */
24
25
  selectionResponse?: SelectionResponse;
26
+ nulls?: NullsResponse;
27
+ /** Whether the chart's entrance waits for this mark (see BaseMarkProps). */
28
+ holdsEntrance?: boolean;
25
29
  transforms?: Transform[];
26
30
  }
27
31
  declare const ParallelCoordinatesMark: import("svelte").Component<Props, {}, "">;