@fjader/svelte 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +1 -1
  2. package/dist/animation/EntranceGate.d.ts +52 -0
  3. package/dist/animation/EntranceGate.js +93 -0
  4. package/dist/animation/TransitionClock.d.ts +61 -30
  5. package/dist/animation/TransitionClock.js +67 -29
  6. package/dist/animation/transition-log.d.ts +41 -0
  7. package/dist/animation/transition-log.js +57 -0
  8. package/dist/components/axes/AxisBottom.svelte +4 -2
  9. package/dist/components/axes/AxisCenter.svelte +4 -2
  10. package/dist/components/axes/AxisLeft.svelte +4 -2
  11. package/dist/components/axes/BandAxisTicks.svelte +3 -2
  12. package/dist/components/charts/ChartCell.svelte +32 -5
  13. package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
  14. package/dist/components/charts/ChartContainer.svelte +45 -3
  15. package/dist/components/charts/ChartContainer.svelte.d.ts +7 -1
  16. package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
  17. package/dist/components/charts/ChartHover.svelte.js +22 -0
  18. package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
  19. package/dist/components/charts/ChartVisibility.svelte.js +67 -0
  20. package/dist/components/guides/Grid.svelte +12 -1
  21. package/dist/components/marks/AreaMarkX.svelte +16 -4
  22. package/dist/components/marks/AreaMarkY.svelte +16 -4
  23. package/dist/components/marks/BarMarkX.svelte +7 -3
  24. package/dist/components/marks/BarMarkY.svelte +7 -3
  25. package/dist/components/marks/DotsMark.svelte +6 -3
  26. package/dist/components/marks/LineMark.svelte +11 -8
  27. package/dist/components/marks/ParallelCoordinatesMark.svelte +7 -2
  28. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +2 -0
  29. package/dist/components/marks/RectMark.svelte +6 -3
  30. package/dist/components/marks/RectXMark.svelte +7 -3
  31. package/dist/components/marks/RectYMark.svelte +7 -3
  32. package/dist/components/marks/RuleMarkX.svelte +30 -26
  33. package/dist/components/marks/RuleMarkY.svelte +30 -26
  34. package/dist/components/marks/TextMark.svelte +44 -5
  35. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +16 -0
  36. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +398 -54
  37. package/dist/components/marks/lifecycle/domain-queries.d.ts +11 -2
  38. package/dist/components/marks/lifecycle/domain-queries.js +33 -6
  39. package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
  40. package/dist/components/marks/lifecycle/domain-registration.js +15 -14
  41. package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +9 -1
  42. package/dist/components/marks/lifecycle/other-rows.svelte.js +2 -2
  43. package/dist/components/marks/lifecycle/query-builder.d.ts +27 -2
  44. package/dist/components/marks/lifecycle/query-builder.js +23 -20
  45. package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
  46. package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
  47. package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
  48. package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
  49. package/dist/components/marks/utils/MarkSize.svelte.js +4 -0
  50. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  51. package/dist/components/marks/utils/reactive-config.js +3 -0
  52. package/dist/components/rendering/Areas.svelte +57 -19
  53. package/dist/components/rendering/Areas.svelte.d.ts +12 -0
  54. package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +1 -1
  55. package/dist/components/rendering/Dots.svelte +23 -3
  56. package/dist/components/rendering/Dots.svelte.d.ts +6 -0
  57. package/dist/components/rendering/GridLine.svelte +13 -1
  58. package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
  59. package/dist/components/rendering/Lines.svelte +48 -13
  60. package/dist/components/rendering/Lines.svelte.d.ts +12 -0
  61. package/dist/components/rendering/ParallelLines.svelte +5 -5
  62. package/dist/components/rendering/Rects.svelte +51 -11
  63. package/dist/components/rendering/Rects.svelte.d.ts +16 -2
  64. package/dist/components/rendering/RuleX.svelte +1 -1
  65. package/dist/components/rendering/RuleY.svelte +1 -1
  66. package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
  67. package/dist/components/rendering/activity-reads.d.ts +41 -24
  68. package/dist/components/rendering/activity-reads.js +54 -31
  69. package/dist/components/rendering/uniform-sizes.d.ts +1 -1
  70. package/dist/components/rendering/uniform-sizes.js +1 -1
  71. package/dist/config/ChartConfig.svelte.d.ts +7 -0
  72. package/dist/config/ChartConfig.svelte.js +17 -0
  73. package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
  74. package/dist/gpu/GPUCoordinator.svelte.js +164 -7
  75. package/dist/gpu/MorphedChannels.d.ts +11 -2
  76. package/dist/gpu/MorphedChannels.js +33 -11
  77. package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
  78. package/dist/gpu/ScaleTemplateSystem.js +58 -2
  79. package/dist/index.d.ts +17 -4
  80. package/dist/index.js +19 -4
  81. package/dist/interactors/TooltipManager.svelte.js +14 -5
  82. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
  83. package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
  84. package/dist/query-engine/FieldActivity.svelte.d.ts +2 -3
  85. package/dist/query-engine/FieldActivity.svelte.js +1 -2
  86. package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
  87. package/dist/query-engine/MarkHandle.svelte.js +33 -1
  88. package/dist/query-engine/QueryEngine.d.ts +225 -5
  89. package/dist/query-engine/QueryEngine.js +671 -41
  90. package/dist/query-engine/categorical-identity.d.ts +34 -8
  91. package/dist/query-engine/categorical-identity.js +138 -73
  92. package/dist/query-engine/debug/query-log.d.ts +39 -0
  93. package/dist/query-engine/debug/query-log.js +90 -0
  94. package/dist/query-engine/duckdb.d.ts +55 -8
  95. package/dist/query-engine/duckdb.js +82 -30
  96. package/dist/query-engine/index.d.ts +7 -4
  97. package/dist/query-engine/index.js +3 -3
  98. package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
  99. package/dist/query-engine/preagg/BuildQueue.js +51 -15
  100. package/dist/query-engine/preagg/Preagg.d.ts +46 -3
  101. package/dist/query-engine/preagg/Preagg.js +159 -31
  102. package/dist/query-engine/preagg/plan.d.ts +28 -1
  103. package/dist/query-engine/preagg/plan.js +128 -8
  104. package/dist/query-engine/sliced-lane.d.ts +108 -0
  105. package/dist/query-engine/sliced-lane.js +299 -0
  106. package/dist/query-engine/source-routing.d.ts +106 -0
  107. package/dist/query-engine/source-routing.js +189 -0
  108. package/dist/scales/BandScaleState.svelte.d.ts +9 -1
  109. package/dist/scales/BandScaleState.svelte.js +16 -5
  110. package/dist/scales/ChartScales.svelte.d.ts +3 -0
  111. package/dist/scales/ChartScales.svelte.js +26 -8
  112. package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
  113. package/dist/scales/ColorScaleState.svelte.js +138 -26
  114. package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
  115. package/dist/scales/LinearScaleState.svelte.js +21 -39
  116. package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
  117. package/dist/scales/ScaleGeneration.svelte.js +42 -0
  118. package/dist/scales/ScaleRegistry.svelte.js +3 -1
  119. package/dist/scales/SlotTransition.d.ts +15 -1
  120. package/dist/scales/SlotTransition.js +33 -4
  121. package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
  122. package/dist/scales/TimeScaleState.svelte.js +7 -7
  123. package/dist/scales/color-utils.d.ts +0 -3
  124. package/dist/scales/color-utils.js +0 -3
  125. package/dist/scales/entered.d.ts +8 -0
  126. package/dist/scales/entered.js +10 -0
  127. package/dist/spec/LazyQueryEngine.js +2 -19
  128. package/dist/spec/MarkRegistry.d.ts +3 -1
  129. package/dist/spec/MarkRegistry.js +13 -1
  130. package/dist/spec/PlotFromSpec.svelte +1 -0
  131. package/dist/spec/export.js +11 -2
  132. package/dist/spec/types.d.ts +41 -13
  133. package/dist/sql/bin.d.ts +25 -0
  134. package/dist/sql/bin.js +108 -5
  135. package/dist/transforms/TransformCompiler.d.ts +10 -0
  136. package/dist/transforms/TransformCompiler.js +26 -0
  137. package/dist/transforms/index.js +144 -24
  138. package/dist/types.d.ts +136 -2
  139. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { noteTransition, transitionsObserved } from '../animation/transition-log';
1
2
  import { TransitionClock } from '../animation';
2
3
  export class SlotTransition {
3
4
  #target = new Float32Array(0);
@@ -197,7 +198,14 @@ export class SlotTransition {
197
198
  * one). The space is unchanged, existing ids keep their paths, and the copy
198
199
  * on resize is exactly right.
199
200
  */
200
- layout(space, order, idCount) {
201
+ layout(space, order, idCount,
202
+ /**
203
+ * Land at once rather than travel: every id takes its new slot, leavers
204
+ * go and entrants appear. For a catch-up result, a change nobody just
205
+ * made. It settles the clock, which the marks on this axis share: their
206
+ * values land with it, which is what the caller wants for its chart.
207
+ */
208
+ instant = false) {
201
209
  if (space !== this.#space) {
202
210
  this.#space = space;
203
211
  this.#discard();
@@ -211,8 +219,21 @@ export class SlotTransition {
211
219
  if (id >= 0 && id < target.length)
212
220
  target[id] = slot;
213
221
  }
214
- this.#retarget(shown);
222
+ this.#layouts++;
223
+ this.#landedLayout = instant ? this.#layouts : 0;
224
+ this.#retarget(shown, instant);
215
225
  }
226
+ /**
227
+ * The latest layout's number if it landed at once (an instant layout), 0
228
+ * otherwise. What follows from a layout, like a band grid's line opacity,
229
+ * lands with it, once: a number, so a consumer lands only its first write
230
+ * after that layout.
231
+ */
232
+ get landedLayout() {
233
+ return this.#landedLayout;
234
+ }
235
+ #layouts = 0;
236
+ #landedLayout = 0;
216
237
  /**
217
238
  * Start a transition without changing what is displayed.
218
239
  *
@@ -310,7 +331,7 @@ export class SlotTransition {
310
331
  * Fold, then point every id at its new slot, and wake the loop if anything
311
332
  * has somewhere to go. `shown` is how many slots are displayed.
312
333
  */
313
- #retarget(shown) {
334
+ #retarget(shown, instant = false) {
314
335
  const target = this.#target;
315
336
  const from = this.#from;
316
337
  const to = this.#to;
@@ -383,9 +404,17 @@ export class SlotTransition {
383
404
  this.#hasLaidOut = true;
384
405
  // With no loop to drive it, the clock settles inside the restart, through
385
406
  // #onClockRest — the same landing the loop would reach.
386
- if (moving) {
407
+ if (moving && transitionsObserved()) {
408
+ noteTransition('slots', instant || settled, () => this.clock._affectedChartsArray, 'band slots');
409
+ }
410
+ if (moving && !instant) {
387
411
  this.clock.restart();
388
412
  }
413
+ else if (moving) {
414
+ // Landed at once: the end the loop would reach, leavers retired now
415
+ // (finish calls #onClockRest).
416
+ this.clock.finish();
417
+ }
389
418
  else {
390
419
  this.#settle();
391
420
  }
@@ -25,13 +25,17 @@ export declare class TimeScaleState extends LinearScaleState {
25
25
  * Register a mark's datetime extent.
26
26
  * Computes t0, converts all extents to offsets, updates parent.
27
27
  */
28
- addDatetimeExtent(datetimeRange: [Date | number | bigint | string, Date | number | bigint | string], markId: string): void;
28
+ addDatetimeExtent(datetimeRange: [Date | number | bigint | string, Date | number | bigint | string], markId: string, options?: {
29
+ instant?: boolean;
30
+ }): void;
29
31
  /**
30
32
  * Register a mark's extent using offset values (seconds from t0).
31
33
  * Useful when data is already in offset form (e.g., from binned queries).
32
34
  * Converts offsets back to datetimes and delegates to addDatetimeExtent.
33
35
  */
34
- addOffsetExtent(offsetRange: [number, number], markId: string): void;
36
+ addOffsetExtent(offsetRange: [number, number], markId: string, options?: {
37
+ instant?: boolean;
38
+ }): void;
35
39
  /**
36
40
  * Remove a mark's datetime extent.
37
41
  */
@@ -40,7 +44,7 @@ export declare class TimeScaleState extends LinearScaleState {
40
44
  * Override: apply time-aware nice instead of linear nice.
41
45
  * Called by super.addMark via polymorphism.
42
46
  */
43
- updateExtentFromExtents(nice?: boolean): void;
47
+ updateExtentFromExtents(nice?: boolean, instant?: boolean): void;
44
48
  /** The actual database column name (may differ from scale registry key). */
45
49
  get sourceField(): string;
46
50
  /** The derived column name for this time scale's offsets. */
@@ -62,7 +62,7 @@ export class TimeScaleState extends LinearScaleState {
62
62
  * Register a mark's datetime extent.
63
63
  * Computes t0, converts all extents to offsets, updates parent.
64
64
  */
65
- addDatetimeExtent(datetimeRange, markId) {
65
+ addDatetimeExtent(datetimeRange, markId, options = {}) {
66
66
  const minDate = toDate(datetimeRange[0]);
67
67
  const maxDate = toDate(datetimeRange[1]);
68
68
  this.#datetimeExtents.set(markId, [minDate, maxDate]);
@@ -77,21 +77,21 @@ export class TimeScaleState extends LinearScaleState {
77
77
  }
78
78
  this.#suppressUpdate = false;
79
79
  // Single update with time-aware nice
80
- this.updateExtentFromExtents();
80
+ this.updateExtentFromExtents(true, options.instant);
81
81
  }
82
82
  /**
83
83
  * Register a mark's extent using offset values (seconds from t0).
84
84
  * Useful when data is already in offset form (e.g., from binned queries).
85
85
  * Converts offsets back to datetimes and delegates to addDatetimeExtent.
86
86
  */
87
- addOffsetExtent(offsetRange, markId) {
87
+ addOffsetExtent(offsetRange, markId, options = {}) {
88
88
  if (!this.#t0) {
89
89
  console.warn(`[TimeScaleState] Cannot add offset extent: t0 not set yet`);
90
90
  return;
91
91
  }
92
92
  const minDate = this.offsetToDate(offsetRange[0]);
93
93
  const maxDate = this.offsetToDate(offsetRange[1]);
94
- this.addDatetimeExtent([minDate, maxDate], markId);
94
+ this.addDatetimeExtent([minDate, maxDate], markId, options);
95
95
  }
96
96
  /**
97
97
  * Remove a mark's datetime extent.
@@ -110,12 +110,12 @@ export class TimeScaleState extends LinearScaleState {
110
110
  * Override: apply time-aware nice instead of linear nice.
111
111
  * Called by super.addMark via polymorphism.
112
112
  */
113
- updateExtentFromExtents(nice = true) {
113
+ updateExtentFromExtents(nice = true, instant = false) {
114
114
  if (this.#suppressUpdate)
115
115
  return;
116
116
  // If no datetime extents, fall through to linear behavior
117
117
  if (this.#datetimeExtents.size === 0) {
118
- super.updateExtentFromExtents(nice);
118
+ super.updateExtentFromExtents(nice, instant);
119
119
  return;
120
120
  }
121
121
  // Compute global datetime range
@@ -141,7 +141,7 @@ export class TimeScaleState extends LinearScaleState {
141
141
  this._setExtent(minOffset, maxOffset);
142
142
  }
143
143
  this.resetDomainLimits();
144
- this.resetZoom();
144
+ this.resetZoom(instant);
145
145
  }
146
146
  // ===========================================================================
147
147
  // VIEWMANAGER INTEGRATION
@@ -26,9 +26,6 @@ export declare function getInterpolator(scheme: string): Interpolator | null;
26
26
  * Get the color array for an ordinal scheme.
27
27
  */
28
28
  export declare function getOrdinalColors(scheme: string): readonly string[] | null;
29
- /**
30
- * Determine if a scheme is ordinal (discrete) or continuous.
31
- */
32
29
  /**
33
30
  * All available scheme names, grouped by type.
34
31
  */
@@ -327,9 +327,6 @@ export function getOrdinalColors(scheme) {
327
327
  const key = scheme.toLowerCase();
328
328
  return ORDINAL_SCHEMES[key] || null;
329
329
  }
330
- /**
331
- * Determine if a scheme is ordinal (discrete) or continuous.
332
- */
333
330
  /**
334
331
  * All available scheme names, grouped by type.
335
332
  */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whether a scale's domain is ready to show: a continuous scale only once a
3
+ * chart drawing it has its marks' first data in (LinearScaleState.entered).
4
+ * Before that its domain is a placeholder, and an axis or grid drawn from it
5
+ * shows [0, 100] for a moment. Scales without the notion (band, point) are
6
+ * always shown.
7
+ */
8
+ export declare function hasEntered(scale: unknown): boolean;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Whether a scale's domain is ready to show: a continuous scale only once a
3
+ * chart drawing it has its marks' first data in (LinearScaleState.entered).
4
+ * Before that its domain is a placeholder, and an axis or grid drawn from it
5
+ * shows [0, 100] for a moment. Scales without the notion (band, point) are
6
+ * always shown.
7
+ */
8
+ export function hasEntered(scale) {
9
+ return scale?.entered !== false;
10
+ }
@@ -8,10 +8,7 @@
8
8
  * DuckDB runs in the browser via @duckdb/duckdb-wasm, loaded from jsDelivr.
9
9
  */
10
10
  import { QueryEngine } from '../query-engine/QueryEngine';
11
- // ---------------------------------------------------------------------------
12
- // Configuration (must be set before first getOrCreateQueryEngine call)
13
- // ---------------------------------------------------------------------------
14
- /** Inline WASM bytes + worker code (for environments where separate assets can't be served). */
11
+ import { initDuckDB } from '../query-engine/duckdb';
15
12
  // ---------------------------------------------------------------------------
16
13
  // Singleton QueryEngine
17
14
  // ---------------------------------------------------------------------------
@@ -35,24 +32,10 @@ export async function getOrCreateQueryEngine(userEngine) {
35
32
  return _enginePromise;
36
33
  }
37
34
  async function initEngine() {
38
- _engine = new QueryEngine(await initFromWasm());
35
+ _engine = new QueryEngine(await initDuckDB());
39
36
  return _engine;
40
37
  }
41
38
  // ---------------------------------------------------------------------------
42
- // WASM init (standard CDN bundles)
43
- // ---------------------------------------------------------------------------
44
- async function initFromWasm() {
45
- const duckdb = await import('@duckdb/duckdb-wasm');
46
- const bundles = duckdb.getJsDelivrBundles();
47
- const bundle = await duckdb.selectBundle(bundles);
48
- const workerUrl = URL.createObjectURL(new Blob([`importScripts("${bundle.mainWorker}");`], { type: 'text/javascript' }));
49
- const worker = new Worker(workerUrl);
50
- const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), worker);
51
- await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
52
- URL.revokeObjectURL(workerUrl);
53
- return (await db.connect());
54
- }
55
- // ---------------------------------------------------------------------------
56
39
  // Data loading (shared across all render calls)
57
40
  // ---------------------------------------------------------------------------
58
41
  const _loadedTables = new Set();
@@ -3,7 +3,7 @@
3
3
  * and resolves mark spec objects into component props.
4
4
  */
5
5
  import type { Component } from 'svelte';
6
- import type { MarkSpec } from './types';
6
+ import type { MarkSpec, ColorFieldSpec } from './types';
7
7
  export declare const MARK_REGISTRY: Record<string, Component<any>>;
8
8
  /** Channel prop names that hold FieldSpec values and need resolution. */
9
9
  export declare const FIELD_CHANNELS: Set<string>;
@@ -11,6 +11,8 @@ export declare const FIELD_CHANNELS: Set<string>;
11
11
  export declare const STROKE_AS_FIELD_MARKS: Set<string>;
12
12
  /** Internal mark type → spec mark type. Used by both resolveMarkEntry and exportSpec. */
13
13
  export declare const SPEC_MARK_TYPES: Record<string, string>;
14
+ /** A colour channel with explicit `domain`/`range`, which is not a FieldSpec. */
15
+ export declare function isColorFieldSpec(value: unknown): value is ColorFieldSpec;
14
16
  /**
15
17
  * Convert a MarkSpec into { component, props } for svelte:component rendering.
16
18
  * Resolves FieldSpecs, transforms, and selection references.
@@ -44,6 +44,13 @@ export const SPEC_MARK_TYPES = {
44
44
  rectx: 'rectX',
45
45
  text: 'text'
46
46
  };
47
+ /** A colour channel with explicit `domain`/`range`, which is not a FieldSpec. */
48
+ export function isColorFieldSpec(value) {
49
+ return (typeof value === 'object' &&
50
+ value !== null &&
51
+ typeof value.field === 'string' &&
52
+ ('domain' in value || 'range' in value));
53
+ }
47
54
  /**
48
55
  * Convert a MarkSpec into { component, props } for svelte:component rendering.
49
56
  * Resolves FieldSpecs, transforms, and selection references.
@@ -62,11 +69,16 @@ export function resolveMarkEntry(markSpec) {
62
69
  // Numbers are constants (e.g. r=3), not field references — skip resolution
63
70
  if (typeof props[key] === 'number')
64
71
  continue;
72
+ // Explicit colours pass through as the colour prop's object form
73
+ if (isColorFieldSpec(props[key]))
74
+ continue;
65
75
  props[key] = resolveFieldSpec(props[key]);
66
76
  }
67
77
  }
68
78
  // stroke is data-driven (FieldSpec) on lines, but a CSS constant on areas
69
- if (STROKE_AS_FIELD_MARKS.has(markSpec.mark) && props.stroke != null) {
79
+ if (STROKE_AS_FIELD_MARKS.has(markSpec.mark) &&
80
+ props.stroke != null &&
81
+ !isColorFieldSpec(props.stroke)) {
70
82
  props.stroke = resolveFieldSpec(props.stroke);
71
83
  }
72
84
  // Resolve transforms
@@ -74,6 +74,7 @@
74
74
  yPanLimit={spec.yPanLimit}
75
75
  xDisableZoom={spec.xDisableZoom}
76
76
  yDisableZoom={spec.yDisableZoom}
77
+ entrance={spec.entrance}
77
78
  {legendPosition}
78
79
  >
79
80
  {#snippet background()}
@@ -5,7 +5,7 @@
5
5
  * back to spec format using the same types as the spec renderer.
6
6
  */
7
7
  import { fieldRefToSpec } from './ExpressionParser';
8
- import { SPEC_MARK_TYPES, FIELD_CHANNELS, STROKE_AS_FIELD_MARKS } from './MarkRegistry';
8
+ import { SPEC_MARK_TYPES, FIELD_CHANNELS, STROKE_AS_FIELD_MARKS, isColorFieldSpec } from './MarkRegistry';
9
9
  /** Props handled specially (not passthrough) or internal-only (skip entirely). */
10
10
  const SKIP_PROPS = new Set([
11
11
  // Internal — not part of the spec
@@ -67,13 +67,22 @@ function entryToMarkSpec(entry) {
67
67
  const val = entry[ch];
68
68
  if (val == null)
69
69
  continue;
70
+ // Explicit colours: the colour prop's object form is already spec-shaped
71
+ if (isColorFieldSpec(val)) {
72
+ spec[ch] = { ...val };
73
+ continue;
74
+ }
70
75
  const converted = fieldRefToSpec(val);
71
76
  if (converted != null)
72
77
  spec[ch] = converted;
73
78
  }
74
79
  // stroke: data-driven on lines, CSS constant on areas
75
80
  if (entry.stroke != null) {
76
- if (STROKE_AS_FIELD_MARKS.has(entry.markType)) {
81
+ const stroke = entry.stroke;
82
+ if (STROKE_AS_FIELD_MARKS.has(entry.markType) && isColorFieldSpec(stroke)) {
83
+ spec.stroke = { ...stroke };
84
+ }
85
+ else if (STROKE_AS_FIELD_MARKS.has(entry.markType)) {
77
86
  const converted = fieldRefToSpec(entry.stroke);
78
87
  if (converted != null)
79
88
  spec.stroke = converted;
@@ -2,7 +2,7 @@
2
2
  * Fjader Spec Schema — TypeScript interfaces defining the JSON contract
3
3
  * between spec producers (Python wrapper, JS API) and the spec renderer.
4
4
  */
5
- import type { ColorScheme, CurveType, ScaleType, SortConfig } from '../types';
5
+ import type { ColorDomainValue, ColorScheme, CurveType, EntranceMode, ScaleType, SortConfig } from '../types';
6
6
  import type { QueryEngine } from '../query-engine/QueryEngine';
7
7
  /** A layout item: either a single plot or a nested layout. */
8
8
  export type LayoutItem = PlotSpec | {
@@ -64,6 +64,8 @@ export interface PlotSpec {
64
64
  xDisableZoom?: boolean;
65
65
  yDisableZoom?: boolean;
66
66
  legend?: LegendSpec | boolean;
67
+ /** How the marks enter when data first draws; see ChartContainer. Default 'auto'. */
68
+ entrance?: EntranceMode;
67
69
  }
68
70
  export type InteractorSpec = IntervalXSpec | IntervalYSpec | Interval2DSpec | ToggleSpec | DerivedIntervalSpec;
69
71
  export interface IntervalXSpec {
@@ -97,6 +99,17 @@ export interface DerivedIntervalSpec {
97
99
  }
98
100
  /** A column name, aggregate shorthand ("count()"), or structured object. */
99
101
  export type FieldSpec = string | AggregateFieldSpec | BinFieldSpec | WindowFieldSpec | RawSqlFieldSpec;
102
+ /**
103
+ * A colour channel with explicit ordinal colours, as on the Svelte colour prop:
104
+ * `{ "field": "won", "domain": [0, 1], "range": ["#d62728", "#2ca02c"] }`.
105
+ */
106
+ export interface ColorFieldSpec {
107
+ field: string;
108
+ domain?: ColorDomainValue[];
109
+ range?: string[];
110
+ }
111
+ /** What a colour channel (fill, a line's stroke) accepts. */
112
+ export type ColorChannelSpec = FieldSpec | ColorFieldSpec;
100
113
  export interface AggregateFieldSpec {
101
114
  aggregate: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'median';
102
115
  field?: string;
@@ -134,9 +147,11 @@ interface BaseMarkSpec {
134
147
  table: string;
135
148
  selection?: string;
136
149
  /** How the mark responds to selections in its group; see BaseMarkProps. Default 'auto'. */
137
- selectionResponse?: 'auto' | 'filter';
150
+ selectionResponse?: 'auto' | 'filter' | 'ignore';
138
151
  /** What the mark does with NULL in its categorical channels; see BaseMarkProps. Default 'keep'. */
139
152
  nulls?: 'keep' | 'drop';
153
+ /** Whether the chart's entrance waits for this mark's first data; see BaseMarkProps. Default true. */
154
+ holdsEntrance?: boolean;
140
155
  scheme?: ColorScheme;
141
156
  limit?: number;
142
157
  transforms?: TransformSpec[];
@@ -145,7 +160,10 @@ interface BaseMarkSpec {
145
160
  x?: string[];
146
161
  y?: string[];
147
162
  } | null;
148
- tooltip?: boolean;
163
+ /** Hover tooltips, or options; see BaseMarkProps.tooltip. */
164
+ tooltip?: boolean | {
165
+ sortTable?: boolean;
166
+ };
149
167
  tooltipProximity?: 'x' | 'y' | 'xy';
150
168
  tip?: (string | '*' | AggregateFieldSpec | LabeledTipFieldSpec)[] | false;
151
169
  fitDomain?: 'x' | 'y' | boolean;
@@ -158,7 +176,7 @@ export interface DotMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
158
176
  mark: 'dot';
159
177
  x: FieldSpec;
160
178
  y: FieldSpec;
161
- fill?: FieldSpec | null;
179
+ fill?: ColorChannelSpec | null;
162
180
  opacity?: number;
163
181
  r?: number | FieldSpec;
164
182
  radiusRange?: [number, number];
@@ -169,7 +187,7 @@ export interface BarXMarkSpec extends BaseMarkSpec {
169
187
  x1?: FieldSpec;
170
188
  x2?: FieldSpec;
171
189
  y: FieldSpec;
172
- fill?: FieldSpec | null;
190
+ fill?: ColorChannelSpec | null;
173
191
  opacity?: number;
174
192
  padding?: number;
175
193
  paddingInner?: number;
@@ -177,6 +195,11 @@ export interface BarXMarkSpec extends BaseMarkSpec {
177
195
  inset?: number;
178
196
  dx?: number;
179
197
  dy?: number;
198
+ /**
199
+ * `false`: follow the band axis the other marks on it decide, adding no
200
+ * categories to it; see BaseMarkProps. Default `true`.
201
+ */
202
+ contributeDomain?: boolean;
180
203
  }
181
204
  export interface BarYMarkSpec extends BaseMarkSpec {
182
205
  mark: 'barY';
@@ -184,7 +207,7 @@ export interface BarYMarkSpec extends BaseMarkSpec {
184
207
  y: FieldSpec;
185
208
  y1?: FieldSpec;
186
209
  y2?: FieldSpec;
187
- fill?: FieldSpec | null;
210
+ fill?: ColorChannelSpec | null;
188
211
  opacity?: number;
189
212
  padding?: number;
190
213
  paddingInner?: number;
@@ -192,12 +215,17 @@ export interface BarYMarkSpec extends BaseMarkSpec {
192
215
  inset?: number;
193
216
  dx?: number;
194
217
  dy?: number;
218
+ /**
219
+ * `false`: follow the band axis the other marks on it decide, adding no
220
+ * categories to it; see BaseMarkProps. Default `true`.
221
+ */
222
+ contributeDomain?: boolean;
195
223
  }
196
224
  export interface LineMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
197
225
  mark: 'line';
198
226
  x: FieldSpec;
199
227
  y: FieldSpec;
200
- stroke?: FieldSpec | null;
228
+ stroke?: ColorChannelSpec | null;
201
229
  z?: FieldSpec | null;
202
230
  strokeWidth?: number;
203
231
  dotRadius?: number;
@@ -222,7 +250,7 @@ export interface AreaYMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
222
250
  y: FieldSpec;
223
251
  y1?: FieldSpec;
224
252
  y2?: FieldSpec;
225
- fill?: FieldSpec | null;
253
+ fill?: ColorChannelSpec | null;
226
254
  z?: FieldSpec | null;
227
255
  fillOpacity?: number;
228
256
  stroke?: string | null;
@@ -237,7 +265,7 @@ export interface AreaXMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
237
265
  x: FieldSpec;
238
266
  x1?: FieldSpec;
239
267
  x2?: FieldSpec;
240
- fill?: FieldSpec | null;
268
+ fill?: ColorChannelSpec | null;
241
269
  z?: FieldSpec | null;
242
270
  fillOpacity?: number;
243
271
  stroke?: string | null;
@@ -254,7 +282,7 @@ export interface RectMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
254
282
  y?: FieldSpec | null;
255
283
  y1?: FieldSpec | null;
256
284
  y2?: FieldSpec | null;
257
- fill?: FieldSpec | null;
285
+ fill?: ColorChannelSpec | null;
258
286
  opacity?: number;
259
287
  padding?: number;
260
288
  paddingInner?: number;
@@ -271,7 +299,7 @@ export interface RectXMarkSpec extends BaseMarkSpec {
271
299
  y?: FieldSpec | null;
272
300
  y1?: FieldSpec | null;
273
301
  y2?: FieldSpec | null;
274
- fill?: FieldSpec | null;
302
+ fill?: ColorChannelSpec | null;
275
303
  opacity?: number;
276
304
  inset?: number;
277
305
  dx?: number;
@@ -287,7 +315,7 @@ export interface RectYMarkSpec extends BaseMarkSpec {
287
315
  y: FieldSpec;
288
316
  y1?: FieldSpec;
289
317
  y2?: FieldSpec;
290
- fill?: FieldSpec | null;
318
+ fill?: ColorChannelSpec | null;
291
319
  opacity?: number;
292
320
  inset?: number;
293
321
  dx?: number;
@@ -300,7 +328,7 @@ export interface TextMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
300
328
  x: FieldSpec;
301
329
  y: FieldSpec;
302
330
  text: FieldSpec;
303
- fill?: FieldSpec | null;
331
+ fill?: ColorChannelSpec | null;
304
332
  fontSize?: number;
305
333
  fontWeight?: string;
306
334
  fontFamily?: string | null;
package/dist/sql/bin.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ExprNode, BinHistogramOptions, BinDateOptions } from '@uwdata/mosaic-sql';
2
+ import type { BinPadding } from '../types';
2
3
  type BinOptions = BinHistogramOptions & BinDateOptions;
3
4
  interface FieldStats {
4
5
  column: string;
@@ -9,14 +10,38 @@ interface FieldInfo {
9
10
  max?: number;
10
11
  type?: string;
11
12
  }
13
+ /**
14
+ * The edge of numeric bin `index` (SQL), `min + alpha * (index + offset)`, in
15
+ * DOUBLE arithmetic. The one formula both `bin()` and `dense()`'s padding
16
+ * write, so a bin's edge is the same bits whichever produced it: a padded row
17
+ * then merges with a real one, and a mark's rows are the same bits whether
18
+ * they came from the base table or a preagg table.
19
+ */
20
+ export declare function numericBinEdgeSQL(min: number, alpha: number, offset: number, index: string): string;
12
21
  export declare function bin(field: string | ExprNode, options?: BinOptions): AutoBinNode;
13
22
  export declare class AutoBinNode {
23
+ #private;
14
24
  field: string | ExprNode;
15
25
  options: BinOptions;
16
26
  isAutoBinNode: true;
17
27
  constructor(field: string | ExprNode, options?: BinOptions);
18
28
  get stats(): FieldStats;
19
29
  resolve(fieldInfo: Record<string, FieldInfo>, scaleType: string, timeOffset?: number | null, currentDomain?: [number, number] | null, binStepMultiplier?: number): ExprNode;
30
+ /**
31
+ * The bins `resolve` produces with the same arguments: every bin a value of
32
+ * the field can fall in. What `dense()` pads a histogram from, so a bin
33
+ * emptied by a filter stays as a zero row and the bars can morph.
34
+ *
35
+ * Numeric bins as numbers, edge k at `min + alpha * (k + offset)`. Time
36
+ * buckets as the interval `time_bucket` steps by, and the field's extent;
37
+ * those need `t0`, the time scale's origin in epoch seconds, because a time
38
+ * channel is drawn as seconds from it, and are null without it (before the
39
+ * scale has one, the channel is not rewritten either).
40
+ *
41
+ * Null for a field with a single value, which binHistogram floors instead
42
+ * of binning.
43
+ */
44
+ padding(fieldInfo: Record<string, FieldInfo>, scaleType: string, currentDomain?: [number, number] | null, binStepMultiplier?: number, t0?: number | null): BinPadding | null;
20
45
  withOffset(offset: number): AutoBinNode;
21
46
  }
22
47
  export declare function isBinExpression(expr: unknown): expr is AutoBinNode;