@fjader/svelte 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +1 -1
  2. package/dist/animation/EntranceGate.d.ts +52 -0
  3. package/dist/animation/EntranceGate.js +93 -0
  4. package/dist/animation/TransitionClock.d.ts +61 -30
  5. package/dist/animation/TransitionClock.js +67 -29
  6. package/dist/animation/transition-log.d.ts +41 -0
  7. package/dist/animation/transition-log.js +57 -0
  8. package/dist/components/axes/AxisBottom.svelte +4 -2
  9. package/dist/components/axes/AxisCenter.svelte +4 -2
  10. package/dist/components/axes/AxisLeft.svelte +4 -2
  11. package/dist/components/axes/BandAxisTicks.svelte +3 -2
  12. package/dist/components/charts/ChartCell.svelte +32 -5
  13. package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
  14. package/dist/components/charts/ChartContainer.svelte +45 -3
  15. package/dist/components/charts/ChartContainer.svelte.d.ts +7 -1
  16. package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
  17. package/dist/components/charts/ChartHover.svelte.js +22 -0
  18. package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
  19. package/dist/components/charts/ChartVisibility.svelte.js +67 -0
  20. package/dist/components/guides/Grid.svelte +12 -1
  21. package/dist/components/marks/AreaMarkX.svelte +16 -4
  22. package/dist/components/marks/AreaMarkY.svelte +16 -4
  23. package/dist/components/marks/BarMarkX.svelte +7 -3
  24. package/dist/components/marks/BarMarkY.svelte +7 -3
  25. package/dist/components/marks/DotsMark.svelte +6 -3
  26. package/dist/components/marks/LineMark.svelte +11 -8
  27. package/dist/components/marks/ParallelCoordinatesMark.svelte +7 -2
  28. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +2 -0
  29. package/dist/components/marks/RectMark.svelte +6 -3
  30. package/dist/components/marks/RectXMark.svelte +7 -3
  31. package/dist/components/marks/RectYMark.svelte +7 -3
  32. package/dist/components/marks/RuleMarkX.svelte +30 -26
  33. package/dist/components/marks/RuleMarkY.svelte +30 -26
  34. package/dist/components/marks/TextMark.svelte +44 -5
  35. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +16 -0
  36. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +398 -54
  37. package/dist/components/marks/lifecycle/domain-queries.d.ts +11 -2
  38. package/dist/components/marks/lifecycle/domain-queries.js +33 -6
  39. package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
  40. package/dist/components/marks/lifecycle/domain-registration.js +15 -14
  41. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +9 -1
  42. package/dist/components/marks/lifecycle/other-rows.svelte.js +2 -2
  43. package/dist/components/marks/lifecycle/query-builder.d.ts +27 -2
  44. package/dist/components/marks/lifecycle/query-builder.js +23 -20
  45. package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
  46. package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
  47. package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
  48. package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
  49. package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
  50. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  51. package/dist/components/marks/utils/reactive-config.js +3 -0
  52. package/dist/components/rendering/Areas.svelte +57 -19
  53. package/dist/components/rendering/Areas.svelte.d.ts +12 -0
  54. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +1 -1
  55. package/dist/components/rendering/Dots.svelte +23 -3
  56. package/dist/components/rendering/Dots.svelte.d.ts +6 -0
  57. package/dist/components/rendering/GridLine.svelte +13 -1
  58. package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
  59. package/dist/components/rendering/Lines.svelte +48 -13
  60. package/dist/components/rendering/Lines.svelte.d.ts +12 -0
  61. package/dist/components/rendering/ParallelLines.svelte +5 -5
  62. package/dist/components/rendering/Rects.svelte +51 -11
  63. package/dist/components/rendering/Rects.svelte.d.ts +16 -2
  64. package/dist/components/rendering/RuleX.svelte +1 -1
  65. package/dist/components/rendering/RuleY.svelte +1 -1
  66. package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
  67. package/dist/components/rendering/activity-reads.d.ts +41 -24
  68. package/dist/components/rendering/activity-reads.js +54 -31
  69. package/dist/components/rendering/uniform-sizes.d.ts +1 -1
  70. package/dist/components/rendering/uniform-sizes.js +1 -1
  71. package/dist/config/ChartConfig.svelte.d.ts +7 -0
  72. package/dist/config/ChartConfig.svelte.js +17 -0
  73. package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
  74. package/dist/gpu/GPUCoordinator.svelte.js +164 -7
  75. package/dist/gpu/MorphedChannels.d.ts +11 -2
  76. package/dist/gpu/MorphedChannels.js +33 -11
  77. package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
  78. package/dist/gpu/ScaleTemplateSystem.js +58 -2
  79. package/dist/index.d.ts +17 -4
  80. package/dist/index.js +19 -4
  81. package/dist/interactors/TooltipManager.svelte.js +14 -5
  82. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
  83. package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
  84. package/dist/query-engine/FieldActivity.svelte.d.ts +2 -3
  85. package/dist/query-engine/FieldActivity.svelte.js +1 -2
  86. package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
  87. package/dist/query-engine/MarkHandle.svelte.js +33 -1
  88. package/dist/query-engine/QueryEngine.d.ts +225 -5
  89. package/dist/query-engine/QueryEngine.js +671 -41
  90. package/dist/query-engine/categorical-identity.d.ts +34 -8
  91. package/dist/query-engine/categorical-identity.js +138 -73
  92. package/dist/query-engine/debug/query-log.d.ts +39 -0
  93. package/dist/query-engine/debug/query-log.js +90 -0
  94. package/dist/query-engine/duckdb.d.ts +55 -8
  95. package/dist/query-engine/duckdb.js +82 -30
  96. package/dist/query-engine/index.d.ts +7 -4
  97. package/dist/query-engine/index.js +3 -3
  98. package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
  99. package/dist/query-engine/preagg/BuildQueue.js +51 -15
  100. package/dist/query-engine/preagg/Preagg.d.ts +46 -3
  101. package/dist/query-engine/preagg/Preagg.js +159 -31
  102. package/dist/query-engine/preagg/plan.d.ts +28 -1
  103. package/dist/query-engine/preagg/plan.js +128 -8
  104. package/dist/query-engine/sliced-lane.d.ts +108 -0
  105. package/dist/query-engine/sliced-lane.js +299 -0
  106. package/dist/query-engine/source-routing.d.ts +106 -0
  107. package/dist/query-engine/source-routing.js +189 -0
  108. package/dist/scales/BandScaleState.svelte.d.ts +9 -1
  109. package/dist/scales/BandScaleState.svelte.js +16 -5
  110. package/dist/scales/ChartScales.svelte.d.ts +3 -0
  111. package/dist/scales/ChartScales.svelte.js +26 -8
  112. package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
  113. package/dist/scales/ColorScaleState.svelte.js +138 -26
  114. package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
  115. package/dist/scales/LinearScaleState.svelte.js +21 -39
  116. package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
  117. package/dist/scales/ScaleGeneration.svelte.js +42 -0
  118. package/dist/scales/ScaleRegistry.svelte.js +3 -1
  119. package/dist/scales/SlotTransition.d.ts +15 -1
  120. package/dist/scales/SlotTransition.js +33 -4
  121. package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
  122. package/dist/scales/TimeScaleState.svelte.js +7 -7
  123. package/dist/scales/color-utils.d.ts +0 -3
  124. package/dist/scales/color-utils.js +0 -3
  125. package/dist/scales/entered.d.ts +8 -0
  126. package/dist/scales/entered.js +10 -0
  127. package/dist/spec/LazyQueryEngine.js +2 -19
  128. package/dist/spec/MarkRegistry.d.ts +3 -1
  129. package/dist/spec/MarkRegistry.js +13 -1
  130. package/dist/spec/PlotFromSpec.svelte +1 -0
  131. package/dist/spec/export.js +11 -2
  132. package/dist/spec/types.d.ts +41 -13
  133. package/dist/sql/bin.d.ts +25 -0
  134. package/dist/sql/bin.js +108 -5
  135. package/dist/transforms/TransformCompiler.d.ts +10 -0
  136. package/dist/transforms/TransformCompiler.js +26 -0
  137. package/dist/transforms/index.js +144 -24
  138. package/dist/types.d.ts +136 -2
  139. package/package.json +2 -2
@@ -72,6 +72,7 @@
72
72
  selection = undefined,
73
73
  selectionResponse = undefined,
74
74
  nulls = undefined,
75
+ holdsEntrance = undefined,
75
76
  x,
76
77
  y,
77
78
  stroke = null,
@@ -126,6 +127,7 @@
126
127
  selection: () => selection,
127
128
  selectionResponse: () => selectionResponse,
128
129
  nulls: () => nulls,
130
+ holdsEntrance: () => holdsEntrance,
129
131
  limit: () => limit,
130
132
  preferredXScaleType: () => preferredXScaleType,
131
133
  preferredYScaleType: () => preferredYScaleType,
@@ -148,16 +150,16 @@
148
150
  inferMarkScaleType(entry.y, entry.table, entry.preferredYScaleType)
149
151
  );
150
152
 
151
- // Series grouping: z overrides stroke for grouping. A selection dims a line
152
- // by its stroke colour only; dimming by a separate z field is not supported
153
- // yet (see activityReads in activity-reads.ts).
153
+ // Series grouping: z overrides stroke for grouping. A selection on either
154
+ // field dims the line (see activityReads in activity-reads.ts).
154
155
  const seriesChannelKey = entry.z != null ? 'z' : 'stroke';
155
156
  const markSeries = new MarkSeries(
156
157
  () => entry.z ?? entry.stroke,
157
158
  seriesChannelKey,
158
159
  () => setup,
159
160
  scaleRegistry,
160
- chartContext
161
+ chartContext,
162
+ () => markColor.scaleState
161
163
  );
162
164
 
163
165
  // Color: stroke determines line color — reads from entry
@@ -232,8 +234,8 @@
232
234
  resolveFitDomain: () => (_isOverlay ? undefined : resolvedFitAxis)
233
235
  });
234
236
 
235
- let xScaleId = $derived(chartContext.xScaleState?.id);
236
- 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);
237
239
 
238
240
  let instanceCount = $derived(computeInstanceCount(setup?.data, entry.x, entry.y));
239
241
 
@@ -268,8 +270,7 @@
268
270
  // changes via setRungParam (LOD window size adjustments should re-query,
269
271
  // not recreate the lifecycle).
270
272
  void channelSelect;
271
- void xScaleId;
272
- void yScaleId;
273
+ void scaleGeneration;
273
274
  void effectiveXScaleType;
274
275
  void effectiveYScaleType;
275
276
  untrack(() => {
@@ -577,6 +578,8 @@
577
578
  colorMode={markColor.colorMode}
578
579
  colorScaleIndex={markColor.colorScaleIndex}
579
580
  groupIndex={setup?.groupIndex ?? 0}
581
+ activitySeriesMode={markSeries.activityMode}
582
+ activitySeriesScaleIndex={markSeries.activityScaleIndex}
580
583
  constantColor={markColor.constantColor}
581
584
  strokeWidth={entry.strokeWidth ?? 2}
582
585
  dotRadius={effectiveDotRadius}
@@ -36,6 +36,8 @@
36
36
  /** How this mark responds to selections in its group (see BaseMarkProps). */
37
37
  selectionResponse?: SelectionResponse;
38
38
  nulls?: NullsResponse;
39
+ /** Whether the chart's entrance waits for this mark (see BaseMarkProps). */
40
+ holdsEntrance?: boolean;
39
41
  transforms?: Transform[];
40
42
  }
41
43
 
@@ -50,6 +52,7 @@
50
52
  selection = undefined,
51
53
  selectionResponse = undefined,
52
54
  nulls = undefined,
55
+ holdsEntrance = undefined,
53
56
  transforms = []
54
57
  }: Props = $props();
55
58
 
@@ -68,6 +71,7 @@
68
71
  selection: () => selection,
69
72
  selectionResponse: () => selectionResponse,
70
73
  nulls: () => nulls,
74
+ holdsEntrance: () => holdsEntrance,
71
75
  transforms: () => transforms
72
76
  });
73
77
 
@@ -120,12 +124,13 @@
120
124
 
121
125
  const reactiveConfig = buildReactiveConfig(entry);
122
126
 
123
- 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);
124
129
 
125
130
  // --- SETUP LIFECYCLE ---
126
131
  $effect(() => {
127
132
  void config;
128
- void xScaleId;
133
+ void scaleGeneration;
129
134
  untrack(() => {
130
135
  setup?.destroy();
131
136
  setup = null;
@@ -24,6 +24,8 @@ interface Props {
24
24
  /** How this mark responds to selections in its group (see BaseMarkProps). */
25
25
  selectionResponse?: SelectionResponse;
26
26
  nulls?: NullsResponse;
27
+ /** Whether the chart's entrance waits for this mark (see BaseMarkProps). */
28
+ holdsEntrance?: boolean;
27
29
  transforms?: Transform[];
28
30
  }
29
31
  declare const ParallelCoordinatesMark: import("svelte").Component<Props, {}, "">;
@@ -49,6 +49,7 @@
49
49
  selection = undefined,
50
50
  selectionResponse = undefined,
51
51
  nulls = undefined,
52
+ holdsEntrance = undefined,
52
53
  x = null,
53
54
  x1: x1Prop = null,
54
55
  x2: x2Prop = null,
@@ -99,6 +100,7 @@
99
100
  selection: () => selection,
100
101
  selectionResponse: () => selectionResponse,
101
102
  nulls: () => nulls,
103
+ holdsEntrance: () => holdsEntrance,
102
104
  limit: () => limit,
103
105
  preferredXScaleType: () => preferredXScaleType,
104
106
  preferredYScaleType: () => preferredYScaleType,
@@ -213,8 +215,8 @@
213
215
  resolveFitDomain: () => resolvedFitAxis
214
216
  });
215
217
 
216
- let xScaleId = $derived(chartContext.xScaleState?.id);
217
- let yScaleId = $derived(chartContext.yScaleState?.id);
218
+ // Rebuild only when a scale is replaced, not on its first resolution.
219
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
218
220
 
219
221
  let instanceCount = $derived(computeInstanceCount(setup?.data));
220
222
 
@@ -299,7 +301,7 @@
299
301
  // --- SETUP LIFECYCLE ---
300
302
 
301
303
  $effect(() => {
302
- (void config, xScaleId, yScaleId);
304
+ (void config, scaleGeneration);
303
305
  untrack(() => {
304
306
  if (setup) {
305
307
  gpuCoordinator?.markForRetain(markId);
@@ -368,6 +370,7 @@
368
370
  constantColor={markColor.constantColor}
369
371
  facetData={setup?.facetData}
370
372
  dataVersion={setup?.dataVersion || 0}
373
+ landsFor={setup?.landsFor ?? 0}
371
374
  bandAxis={null}
372
375
  inset={entry.inset ?? 0.5}
373
376
  dx={entry.dx ?? 0}
@@ -49,6 +49,7 @@
49
49
  selection = undefined,
50
50
  selectionResponse = undefined,
51
51
  nulls = undefined,
52
+ holdsEntrance = undefined,
52
53
  x,
53
54
  x1: x1Prop,
54
55
  x2: x2Prop,
@@ -94,6 +95,7 @@
94
95
  selection: () => selection,
95
96
  selectionResponse: () => selectionResponse,
96
97
  nulls: () => nulls,
98
+ holdsEntrance: () => holdsEntrance,
97
99
  limit: () => limit,
98
100
  preferredYScaleType: () => preferredYScaleType,
99
101
  fitDomain: () => fitDomain,
@@ -205,8 +207,8 @@
205
207
  resolveFitDomain: () => resolvedFitAxis
206
208
  });
207
209
 
208
- let xScaleId = $derived(chartContext.xScaleState?.id);
209
- let yScaleId = $derived(chartContext.yScaleState?.id);
210
+ // Rebuild only when a scale is replaced, not on its first resolution.
211
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
210
212
 
211
213
  let instanceCount = $derived(computeInstanceCount(setup?.data, x2));
212
214
 
@@ -283,7 +285,7 @@
283
285
  // --- SETUP LIFECYCLE ---
284
286
 
285
287
  $effect(() => {
286
- (void config, xScaleId, yScaleId);
288
+ (void config, scaleGeneration);
287
289
  untrack(() => {
288
290
  if (setup) {
289
291
  gpuCoordinator?.markForRetain(markId);
@@ -359,7 +361,9 @@
359
361
  constantColor={markColor.constantColor}
360
362
  facetData={setup?.facetData}
361
363
  dataVersion={setup?.dataVersion || 0}
364
+ landsFor={setup?.landsFor ?? 0}
362
365
  bandAxis={setup?.alignedBand ?? null}
366
+ grow={hasExplicitBounds ? null : 'x'}
363
367
  inset={entry.inset ?? 0.5}
364
368
  dx={entry.dx ?? 0}
365
369
  dy={entry.dy ?? 0}
@@ -50,6 +50,7 @@
50
50
  selection = undefined,
51
51
  selectionResponse = undefined,
52
52
  nulls = undefined,
53
+ holdsEntrance = undefined,
53
54
  x = null,
54
55
  x1: x1Prop = null,
55
56
  x2: x2Prop = null,
@@ -96,6 +97,7 @@
96
97
  selection: () => selection,
97
98
  selectionResponse: () => selectionResponse,
98
99
  nulls: () => nulls,
100
+ holdsEntrance: () => holdsEntrance,
99
101
  limit: () => limit,
100
102
  preferredXScaleType: () => preferredXScaleType,
101
103
  fitDomain: () => fitDomain,
@@ -211,8 +213,8 @@
211
213
  resolveFitDomain: () => resolvedFitAxis
212
214
  });
213
215
 
214
- let xScaleId = $derived(chartContext.xScaleState?.id);
215
- let yScaleId = $derived(chartContext.yScaleState?.id);
216
+ // Rebuild only when a scale is replaced, not on its first resolution.
217
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
216
218
 
217
219
  let instanceCount = $derived(computeInstanceCount(setup?.data, y2));
218
220
 
@@ -289,7 +291,7 @@
289
291
  // --- SETUP LIFECYCLE ---
290
292
 
291
293
  $effect(() => {
292
- (void config, xScaleId, yScaleId);
294
+ (void config, scaleGeneration);
293
295
  untrack(() => {
294
296
  if (setup) {
295
297
  gpuCoordinator?.markForRetain(markId);
@@ -366,7 +368,9 @@
366
368
  constantColor={markColor.constantColor}
367
369
  facetData={setup?.facetData}
368
370
  dataVersion={setup?.dataVersion || 0}
371
+ landsFor={setup?.landsFor ?? 0}
369
372
  bandAxis={setup?.alignedBand ?? null}
373
+ grow={hasExplicitBounds ? null : 'y'}
370
374
  inset={entry.inset ?? 0.5}
371
375
  dx={entry.dx ?? 0}
372
376
  dy={entry.dy ?? 0}
@@ -37,6 +37,7 @@
37
37
  selection = undefined,
38
38
  selectionResponse = undefined,
39
39
  nulls = undefined,
40
+ holdsEntrance = undefined,
40
41
  x,
41
42
  y: yShorthand = undefined,
42
43
  y1: y1Prop = undefined,
@@ -112,28 +113,31 @@
112
113
  );
113
114
 
114
115
  // --- ChartConfig registration (data-driven only) ---
115
- const entry = $derived.by(() => {
116
- if (!table) return null;
117
- return chartContext.config.registerMark(markId, 'rulex', {
118
- table: () => table,
119
- x: () => x,
120
- y1: () => y1,
121
- y2: () => y2,
122
- stroke: () => stroke,
123
- scheme: () => scheme,
124
- opacity: () => opacity,
125
- transforms: () => transforms,
126
- selection: () => selection,
127
- selectionResponse: () => selectionResponse,
128
- nulls: () => nulls,
129
- limit: () => limit,
130
- sort: () => sort,
131
- domain: () => domain,
132
- tooltip: () => tooltip,
133
- tip: () => tip,
134
- fitDomain: () => fitDomain
135
- });
136
- });
116
+ // Registered once, at mount, like every other mark. Not in a $derived:
117
+ // registering writes the config's map, and a derived must not write state.
118
+ // A rule without a table draws constants and has no config entry.
119
+ const entry = untrack(() => table)
120
+ ? chartContext.config.registerMark(markId, 'rulex', {
121
+ table: () => table,
122
+ x: () => x,
123
+ y1: () => y1,
124
+ y2: () => y2,
125
+ stroke: () => stroke,
126
+ scheme: () => scheme,
127
+ opacity: () => opacity,
128
+ transforms: () => transforms,
129
+ selection: () => selection,
130
+ selectionResponse: () => selectionResponse,
131
+ nulls: () => nulls,
132
+ holdsEntrance: () => holdsEntrance,
133
+ limit: () => limit,
134
+ sort: () => sort,
135
+ domain: () => domain,
136
+ tooltip: () => tooltip,
137
+ tip: () => tip,
138
+ fitDomain: () => fitDomain
139
+ })
140
+ : null;
137
141
 
138
142
  // Config for MarkLifecycle
139
143
  const channelSelect = $derived.by(() => {
@@ -191,8 +195,8 @@
191
195
  resolveFitDomain: () => resolvedFitAxis
192
196
  });
193
197
 
194
- let xScaleId = $derived(chartContext.xScaleState?.id);
195
- let yScaleId = $derived(chartContext.yScaleState?.id);
198
+ // Rebuild only when a scale is replaced, not on its first resolution.
199
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
196
200
 
197
201
  let instanceCount = $derived(computeInstanceCount(setup?.data, x));
198
202
 
@@ -242,7 +246,7 @@
242
246
 
243
247
  $effect(() => {
244
248
  if (!config) return;
245
- (void config, xScaleId, yScaleId);
249
+ (void config, scaleGeneration);
246
250
  untrack(() => {
247
251
  if (setup) {
248
252
  gpuCoordinator?.markForRetain(markId);
@@ -291,7 +295,7 @@
291
295
 
292
296
  onDestroy(() => {
293
297
  setup?.destroy();
294
- if (table) chartContext.config.unregisterMark(markId);
298
+ if (entry) chartContext.config.unregisterMark(markId);
295
299
  gpuCoordinator?.releaseRetained(markId);
296
300
  });
297
301
  </script>
@@ -37,6 +37,7 @@
37
37
  selection = undefined,
38
38
  selectionResponse = undefined,
39
39
  nulls = undefined,
40
+ holdsEntrance = undefined,
40
41
  y,
41
42
  x: xShorthand = undefined,
42
43
  x1: x1Prop = undefined,
@@ -110,28 +111,31 @@
110
111
  );
111
112
 
112
113
  // --- ChartConfig registration (data-driven only) ---
113
- const entry = $derived.by(() => {
114
- if (!table) return null;
115
- return chartContext.config.registerMark(markId, 'ruley', {
116
- table: () => table,
117
- y: () => y,
118
- x1: () => x1,
119
- x2: () => x2,
120
- stroke: () => stroke,
121
- scheme: () => scheme,
122
- opacity: () => opacity,
123
- transforms: () => transforms,
124
- selection: () => selection,
125
- selectionResponse: () => selectionResponse,
126
- nulls: () => nulls,
127
- limit: () => limit,
128
- sort: () => sort,
129
- domain: () => domain,
130
- tooltip: () => tooltip,
131
- tip: () => tip,
132
- fitDomain: () => fitDomain
133
- });
134
- });
114
+ // Registered once, at mount, like every other mark. Not in a $derived:
115
+ // registering writes the config's map, and a derived must not write state.
116
+ // A rule without a table draws constants and has no config entry.
117
+ const entry = untrack(() => table)
118
+ ? chartContext.config.registerMark(markId, 'ruley', {
119
+ table: () => table,
120
+ y: () => y,
121
+ x1: () => x1,
122
+ x2: () => x2,
123
+ stroke: () => stroke,
124
+ scheme: () => scheme,
125
+ opacity: () => opacity,
126
+ transforms: () => transforms,
127
+ selection: () => selection,
128
+ selectionResponse: () => selectionResponse,
129
+ nulls: () => nulls,
130
+ holdsEntrance: () => holdsEntrance,
131
+ limit: () => limit,
132
+ sort: () => sort,
133
+ domain: () => domain,
134
+ tooltip: () => tooltip,
135
+ tip: () => tip,
136
+ fitDomain: () => fitDomain
137
+ })
138
+ : null;
135
139
 
136
140
  const channelSelect = $derived.by(() => {
137
141
  if (!entry) return {};
@@ -187,8 +191,8 @@
187
191
  resolveFitDomain: () => resolvedFitAxis
188
192
  });
189
193
 
190
- let xScaleId = $derived(chartContext.xScaleState?.id);
191
- let yScaleId = $derived(chartContext.yScaleState?.id);
194
+ // Rebuild only when a scale is replaced, not on its first resolution.
195
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
192
196
 
193
197
  let instanceCount = $derived(computeInstanceCount(setup?.data, y));
194
198
 
@@ -238,7 +242,7 @@
238
242
 
239
243
  $effect(() => {
240
244
  if (!config) return;
241
- (void config, xScaleId, yScaleId);
245
+ (void config, scaleGeneration);
242
246
  untrack(() => {
243
247
  if (setup) {
244
248
  gpuCoordinator?.markForRetain(markId);
@@ -287,7 +291,7 @@
287
291
 
288
292
  onDestroy(() => {
289
293
  setup?.destroy();
290
- if (table) chartContext.config.unregisterMark(markId);
294
+ if (entry) chartContext.config.unregisterMark(markId);
291
295
  gpuCoordinator?.releaseRetained(markId);
292
296
  });
293
297
  </script>
@@ -15,7 +15,13 @@
15
15
  resolveTip
16
16
  } from './utils';
17
17
  import { buildReactiveConfig } from './utils/reactive-config';
18
- import type { FieldRef, BaseMarkProps, ContinuousScaleTypeProps, ChartContext } from '../../types';
18
+ import type {
19
+ FieldRef,
20
+ BaseMarkProps,
21
+ ContinuousScaleTypeProps,
22
+ ChartContext,
23
+ EntranceMode
24
+ } from '../../types';
19
25
  import type { ScaleRegistry } from '../../scales/ScaleRegistry.svelte';
20
26
  import type { QueryEngine } from '../../query-engine/QueryEngine';
21
27
  import type { FilterGroupRegistry } from '../../query-engine/FilterGroupRegistry.svelte';
@@ -44,6 +50,7 @@
44
50
  selection = undefined,
45
51
  selectionResponse = undefined,
46
52
  nulls = undefined,
53
+ holdsEntrance = undefined,
47
54
  x,
48
55
  y,
49
56
  text,
@@ -70,8 +77,15 @@
70
77
  }: Props = $props();
71
78
 
72
79
  const markId = generateMarkId('text');
80
+ // Held from mount, as other marks do in ChartConfig.registerMark: TextMark
81
+ // has no config entry, and its lifecycle is built later.
82
+ if (untrack(() => table) && untrack(() => holdsEntrance) !== false) {
83
+ getContext<ChartContext>('chart').config?.entrance.hold(markId);
84
+ }
73
85
  const queryEngine = getContext<QueryEngine>('query-engine');
74
86
  const chartContext = getContext<ChartContext>('chart');
87
+ // Read once: the entrance happens when the labels first mount.
88
+ const entrance = chartContext.config?.chartProps?.entrance as EntranceMode | undefined;
75
89
  const filterGroups = getContext<FilterGroupRegistry>('filter-groups');
76
90
  const scaleRegistry = getContext<ScaleRegistry>('scale-registry');
77
91
 
@@ -135,6 +149,9 @@
135
149
  get nulls() {
136
150
  return nulls;
137
151
  },
152
+ get holdsEntrance() {
153
+ return holdsEntrance;
154
+ },
138
155
  get limit() {
139
156
  return limit;
140
157
  },
@@ -149,8 +166,8 @@
149
166
  }
150
167
  });
151
168
 
152
- let xScaleId = $derived(chartContext.xScaleState?.id);
153
- let yScaleId = $derived(chartContext.yScaleState?.id);
169
+ // Rebuild only when a scale is replaced, not on its first resolution.
170
+ let scaleGeneration = $derived(chartContext.scaleGeneration);
154
171
 
155
172
  let instanceCount = $derived(computeInstanceCount(setup?.data, x, y));
156
173
 
@@ -429,7 +446,7 @@
429
446
  // --- Setup Lifecycle ---
430
447
 
431
448
  $effect(() => {
432
- (void config, xScaleId, yScaleId);
449
+ (void config, scaleGeneration);
433
450
  untrack(() => {
434
451
  setup?.destroy();
435
452
  setup = null;
@@ -473,11 +490,17 @@
473
490
 
474
491
  onDestroy(() => {
475
492
  setup?.destroy();
493
+ // No config entry to unregister, which is where other marks release it.
494
+ chartContext.config?.entrance.release(markId);
476
495
  });
477
496
  </script>
478
497
 
479
498
  {#if (setup?.gpuReady || !table) && markColor.ready && labelItems.length > 0}
480
- <g class="text-mark" class:text-mark-hidden={useFadeMode && scalesAnimating}>
499
+ <g
500
+ class="text-mark"
501
+ class:text-mark-hidden={useFadeMode && scalesAnimating}
502
+ class:text-mark-enter={entrance !== 'none'}
503
+ >
481
504
  {#each labelItems as item, i (item.idx)}
482
505
  {@const pos = positions[i]}
483
506
  <!--
@@ -513,4 +536,20 @@
513
536
  .text-mark-hidden {
514
537
  opacity: 0;
515
538
  }
539
+
540
+ /*
541
+ * The chart's entrance, for SVG labels: a fade, never motion, so it is the
542
+ * same under reduced motion. Labels sit at their final positions, so on
543
+ * growing bars they arrive as the bars reach them rather than ahead.
544
+ */
545
+ .text-mark-enter {
546
+ /* The chart's fade: a soft S-curve over about 0.45s (ENTRANCE_FADE_STIFFNESS). */
547
+ animation: text-mark-enter 450ms ease-in-out;
548
+ }
549
+
550
+ @keyframes text-mark-enter {
551
+ from {
552
+ opacity: 0;
553
+ }
554
+ }
516
555
  </style>
@@ -96,6 +96,7 @@ export interface ReactiveConfig {
96
96
  readonly selectionResponse: SelectionResponse | undefined;
97
97
  readonly nulls: NullsResponse | undefined;
98
98
  readonly contributeDomain: boolean | undefined;
99
+ readonly holdsEntrance: boolean | undefined;
99
100
  }
100
101
  export declare class MarkLifecycleV2 {
101
102
  #private;
@@ -120,6 +121,11 @@ export declare class MarkLifecycleV2 {
120
121
  * never mixed, the other axis's channels morph, and the band scale owns the
121
122
  * clock both run on.
122
123
  *
124
+ * A histogram's binned axis aligns too: one row per bin, in bin order. Its
125
+ * channels are bin edges rather than ids, still never mixed, and with no
126
+ * slot clock on a linear scale the mark's channels run on a clock of their
127
+ * own.
128
+ *
123
129
  * The first channel of the transforms' alignment — `dense()`'s plan, over the
124
130
  * same select and facts its `apply()` sees — so the conditions are written
125
131
  * once. Derived rather than recorded at apply time: apply runs for every
@@ -144,6 +150,16 @@ export declare class MarkLifecycleV2 {
144
150
  * MarkConstants to read its group's run of each field's activity block.
145
151
  */
146
152
  get groupIndex(): number;
153
+ /**
154
+ * The data version that lands without animating, or 0: a catch-up result
155
+ * (the chart came back into view stale), a change nobody just made. A
156
+ * version, not a flag: the cause stays set until the next result, and a
157
+ * renderer lands only its FIRST write of this version. Later writes of the
158
+ * same data (an Other split on a click, a JS transform's parameter) are
159
+ * changes someone just made, and animate. Reactive through `dataVersion`,
160
+ * which the cause is set with.
161
+ */
162
+ get landsFor(): number;
147
163
  get dataVersion(): number;
148
164
  get hasCategoryReducingTransforms(): boolean;
149
165
  data: DataAccessor | null;