@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
@@ -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,7 +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';
151
+ /** What the mark does with NULL in its categorical channels; see BaseMarkProps. Default 'keep'. */
152
+ nulls?: 'keep' | 'drop';
153
+ /** Whether the chart's entrance waits for this mark's first data; see BaseMarkProps. Default true. */
154
+ holdsEntrance?: boolean;
138
155
  scheme?: ColorScheme;
139
156
  limit?: number;
140
157
  transforms?: TransformSpec[];
@@ -143,7 +160,10 @@ interface BaseMarkSpec {
143
160
  x?: string[];
144
161
  y?: string[];
145
162
  } | null;
146
- tooltip?: boolean;
163
+ /** Hover tooltips, or options; see BaseMarkProps.tooltip. */
164
+ tooltip?: boolean | {
165
+ sortTable?: boolean;
166
+ };
147
167
  tooltipProximity?: 'x' | 'y' | 'xy';
148
168
  tip?: (string | '*' | AggregateFieldSpec | LabeledTipFieldSpec)[] | false;
149
169
  fitDomain?: 'x' | 'y' | boolean;
@@ -156,7 +176,7 @@ export interface DotMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
156
176
  mark: 'dot';
157
177
  x: FieldSpec;
158
178
  y: FieldSpec;
159
- fill?: FieldSpec | null;
179
+ fill?: ColorChannelSpec | null;
160
180
  opacity?: number;
161
181
  r?: number | FieldSpec;
162
182
  radiusRange?: [number, number];
@@ -167,7 +187,7 @@ export interface BarXMarkSpec extends BaseMarkSpec {
167
187
  x1?: FieldSpec;
168
188
  x2?: FieldSpec;
169
189
  y: FieldSpec;
170
- fill?: FieldSpec | null;
190
+ fill?: ColorChannelSpec | null;
171
191
  opacity?: number;
172
192
  padding?: number;
173
193
  paddingInner?: number;
@@ -175,6 +195,11 @@ export interface BarXMarkSpec extends BaseMarkSpec {
175
195
  inset?: number;
176
196
  dx?: number;
177
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;
178
203
  }
179
204
  export interface BarYMarkSpec extends BaseMarkSpec {
180
205
  mark: 'barY';
@@ -182,7 +207,7 @@ export interface BarYMarkSpec extends BaseMarkSpec {
182
207
  y: FieldSpec;
183
208
  y1?: FieldSpec;
184
209
  y2?: FieldSpec;
185
- fill?: FieldSpec | null;
210
+ fill?: ColorChannelSpec | null;
186
211
  opacity?: number;
187
212
  padding?: number;
188
213
  paddingInner?: number;
@@ -190,12 +215,17 @@ export interface BarYMarkSpec extends BaseMarkSpec {
190
215
  inset?: number;
191
216
  dx?: number;
192
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;
193
223
  }
194
224
  export interface LineMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
195
225
  mark: 'line';
196
226
  x: FieldSpec;
197
227
  y: FieldSpec;
198
- stroke?: FieldSpec | null;
228
+ stroke?: ColorChannelSpec | null;
199
229
  z?: FieldSpec | null;
200
230
  strokeWidth?: number;
201
231
  dotRadius?: number;
@@ -220,7 +250,7 @@ export interface AreaYMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
220
250
  y: FieldSpec;
221
251
  y1?: FieldSpec;
222
252
  y2?: FieldSpec;
223
- fill?: FieldSpec | null;
253
+ fill?: ColorChannelSpec | null;
224
254
  z?: FieldSpec | null;
225
255
  fillOpacity?: number;
226
256
  stroke?: string | null;
@@ -235,7 +265,7 @@ export interface AreaXMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
235
265
  x: FieldSpec;
236
266
  x1?: FieldSpec;
237
267
  x2?: FieldSpec;
238
- fill?: FieldSpec | null;
268
+ fill?: ColorChannelSpec | null;
239
269
  z?: FieldSpec | null;
240
270
  fillOpacity?: number;
241
271
  stroke?: string | null;
@@ -252,7 +282,7 @@ export interface RectMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
252
282
  y?: FieldSpec | null;
253
283
  y1?: FieldSpec | null;
254
284
  y2?: FieldSpec | null;
255
- fill?: FieldSpec | null;
285
+ fill?: ColorChannelSpec | null;
256
286
  opacity?: number;
257
287
  padding?: number;
258
288
  paddingInner?: number;
@@ -269,7 +299,7 @@ export interface RectXMarkSpec extends BaseMarkSpec {
269
299
  y?: FieldSpec | null;
270
300
  y1?: FieldSpec | null;
271
301
  y2?: FieldSpec | null;
272
- fill?: FieldSpec | null;
302
+ fill?: ColorChannelSpec | null;
273
303
  opacity?: number;
274
304
  inset?: number;
275
305
  dx?: number;
@@ -285,7 +315,7 @@ export interface RectYMarkSpec extends BaseMarkSpec {
285
315
  y: FieldSpec;
286
316
  y1?: FieldSpec;
287
317
  y2?: FieldSpec;
288
- fill?: FieldSpec | null;
318
+ fill?: ColorChannelSpec | null;
289
319
  opacity?: number;
290
320
  inset?: number;
291
321
  dx?: number;
@@ -298,7 +328,7 @@ export interface TextMarkSpec extends BaseMarkSpec, ContinuousScaleTypeSpec {
298
328
  x: FieldSpec;
299
329
  y: FieldSpec;
300
330
  text: FieldSpec;
301
- fill?: FieldSpec | null;
331
+ fill?: ColorChannelSpec | null;
302
332
  fontSize?: number;
303
333
  fontWeight?: string;
304
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;
package/dist/sql/bin.js CHANGED
@@ -1,5 +1,43 @@
1
- import { binDate, binHistogram, column } from '@uwdata/mosaic-sql';
1
+ import { binDate, binHistogram, binSpec, column, interval, sql, timeInterval } from '@uwdata/mosaic-sql';
2
2
  import { isTemporalScale } from '../scales/scale-categories';
3
+ /**
4
+ * Numeric bins over [min, max]: binHistogram's own derivation (binSpec, with
5
+ * the linear transform it is given), as the first edge and the width.
6
+ */
7
+ function numericBins(min, max, options) {
8
+ const b = binSpec(min, max, options);
9
+ return { min: b.min, alpha: (b.max - b.min) / b.steps || 1 };
10
+ }
11
+ /**
12
+ * The edge of numeric bin `index` (SQL), `min + alpha * (index + offset)`, in
13
+ * DOUBLE arithmetic. The one formula both `bin()` and `dense()`'s padding
14
+ * write, so a bin's edge is the same bits whichever produced it: a padded row
15
+ * then merges with a real one, and a mark's rows are the same bits whether
16
+ * they came from the base table or a preagg table.
17
+ */
18
+ export function numericBinEdgeSQL(min, alpha, offset, index) {
19
+ return `(${sqlDouble(min)} + ${sqlDouble(alpha)} * (${index} + ${offset}))`;
20
+ }
21
+ /** A JS number as a DOUBLE literal: a bare 0.1 would be read as a DECIMAL. */
22
+ function sqlDouble(n) {
23
+ return `CAST(${JSON.stringify(n)} AS DOUBLE)`;
24
+ }
25
+ /** Engine-built SQL text as an expression node. Never user input. */
26
+ function sqlFragment(text) {
27
+ return sql([text]);
28
+ }
29
+ /** A time unit's typical length, for estimating a bucket count. */
30
+ const UNIT_MS = {
31
+ year: 365 * 86_400_000,
32
+ quarter: 91 * 86_400_000,
33
+ month: 30 * 86_400_000,
34
+ day: 86_400_000,
35
+ hour: 3_600_000,
36
+ minute: 60_000,
37
+ second: 1000,
38
+ millisecond: 1,
39
+ microsecond: 0.001
40
+ };
3
41
  export function bin(field, options = {}) {
4
42
  return new AutoBinNode(field, options);
5
43
  }
@@ -19,6 +57,70 @@ export class AutoBinNode {
19
57
  };
20
58
  }
21
59
  resolve(fieldInfo, scaleType, timeOffset = null, currentDomain = null, binStepMultiplier = 1.0) {
60
+ const { col, min, max, isTemporal, binOptions } = this.#plan(fieldInfo, scaleType, currentDomain, binStepMultiplier);
61
+ if (isTemporal)
62
+ return binDate(col, [min, max], binOptions);
63
+ // A single value: binHistogram floors it rather than binning.
64
+ if (max === min)
65
+ return binHistogram(col, [min, max], binOptions);
66
+ // binHistogram's bins, written with the formula dense() pads with, so a
67
+ // real row's edge and a padded row's are the same bits (see
68
+ // numericBinEdgeSQL).
69
+ const { min: lo, alpha } = numericBins(min, max, binOptions);
70
+ const index = `floor((CAST(${col} AS DOUBLE) - ${sqlDouble(lo)}) / ${sqlDouble(alpha)})`;
71
+ return sqlFragment(numericBinEdgeSQL(lo, alpha, binOptions.offset ?? 0, index));
72
+ }
73
+ /**
74
+ * The bins `resolve` produces with the same arguments: every bin a value of
75
+ * the field can fall in. What `dense()` pads a histogram from, so a bin
76
+ * emptied by a filter stays as a zero row and the bars can morph.
77
+ *
78
+ * Numeric bins as numbers, edge k at `min + alpha * (k + offset)`. Time
79
+ * buckets as the interval `time_bucket` steps by, and the field's extent;
80
+ * those need `t0`, the time scale's origin in epoch seconds, because a time
81
+ * channel is drawn as seconds from it, and are null without it (before the
82
+ * scale has one, the channel is not rewritten either).
83
+ *
84
+ * Null for a field with a single value, which binHistogram floors instead
85
+ * of binning.
86
+ */
87
+ padding(fieldInfo, scaleType, currentDomain = null, binStepMultiplier = 1.0, t0 = null) {
88
+ const { col, min, max, isTemporal, binOptions } = this.#plan(fieldInfo, scaleType, currentDomain, binStepMultiplier);
89
+ const offset = binOptions.offset ?? 0;
90
+ const source = String(col);
91
+ if (isTemporal) {
92
+ if (t0 === null)
93
+ return null;
94
+ // binDate's own choice of unit and step.
95
+ const { unit, step = 1 } = binOptions.interval
96
+ ? { unit: binOptions.interval, step: binOptions.step }
97
+ : timeInterval(min, max, binOptions.steps || 40);
98
+ const width = (UNIT_MS[unit] ?? UNIT_MS.day) * step;
99
+ return {
100
+ kind: 'time',
101
+ source,
102
+ interval: String(interval(unit, step)),
103
+ minMs: min,
104
+ maxMs: max,
105
+ t0,
106
+ count: Math.ceil((max - min) / width) + 2,
107
+ offset
108
+ };
109
+ }
110
+ if (max === min)
111
+ return null;
112
+ const { min: lo, alpha } = numericBins(min, max, binOptions);
113
+ return {
114
+ kind: 'number',
115
+ source,
116
+ min: lo,
117
+ alpha,
118
+ count: Math.floor((max - lo) / alpha) + 1,
119
+ offset
120
+ };
121
+ }
122
+ /** The column, extent and options both `resolve` and `padding` bin with. */
123
+ #plan(fieldInfo, scaleType, currentDomain, binStepMultiplier) {
22
124
  const fieldName = typeof this.field === 'string' ? this.field : this.field.toString();
23
125
  const info = fieldInfo[fieldName] || {};
24
126
  const { min, max, type } = info;
@@ -50,12 +152,13 @@ export class AutoBinNode {
50
152
  binOptions.steps = Math.max(1, Math.round(binOptions.steps * binStepMultiplier));
51
153
  }
52
154
  if (binOptions.step) {
53
- binOptions.step = binOptions.step / binStepMultiplier;
155
+ // A time interval counts whole units: `INTERVAL 6.78 day` is not SQL.
156
+ binOptions.step = binOptions.interval
157
+ ? Math.max(1, Math.round(binOptions.step / binStepMultiplier))
158
+ : binOptions.step / binStepMultiplier;
54
159
  }
55
160
  }
56
- return isTemporal
57
- ? binDate(col, [min, max], binOptions)
58
- : binHistogram(col, [min, max], binOptions);
161
+ return { col, min, max, isTemporal, binOptions };
59
162
  }
60
163
  withOffset(offset) {
61
164
  return new AutoBinNode(this.field, {
package/dist/theme.css CHANGED
@@ -7,6 +7,11 @@
7
7
  * Or copy and customize the variables below in your own CSS.
8
8
  * All variables are optional — Fjader components have sensible
9
9
  * inline fallbacks and work without any CSS imports.
10
+ *
11
+ * The tooltip is not set here: it follows the color-scheme it inherits (the
12
+ * one below, or the app's own) by itself, in contrast or follow mode
13
+ * (data-fjader-tooltip on any ancestor, or ChartContainer's tooltipScheme).
14
+ * Set --fjader-tooltip-bg and --fjader-tooltip-text to override both modes.
10
15
  */
11
16
 
12
17
  :where(:root) {
@@ -16,8 +21,6 @@
16
21
  --fjader-bg: #ffffff;
17
22
  --fjader-primary: #3b82f6;
18
23
  --fjader-accent: #93c5fd;
19
- --fjader-tooltip-bg: rgba(0, 0, 0, 0.88);
20
- --fjader-tooltip-text: #ffffff;
21
24
  --fjader-palette-bg: rgba(0, 0, 0, 0.92);
22
25
  --fjader-palette-text: #ffffff;
23
26
  --fjader-palette-text-secondary: rgba(255, 255, 255, 0.5);
@@ -34,8 +37,6 @@
34
37
  --fjader-bg: #111827;
35
38
  --fjader-primary: #60a5fa;
36
39
  --fjader-accent: #3b82f6;
37
- --fjader-tooltip-bg: rgba(255, 255, 255, 0.92);
38
- --fjader-tooltip-text: #111827;
39
40
  --fjader-palette-bg: rgba(255, 255, 255, 0.95);
40
41
  --fjader-palette-text: #111827;
41
42
  --fjader-palette-text-secondary: rgba(17, 24, 39, 0.5);