@fundar/data-chart-telling 1.0.0 → 1.1.0

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 (155) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +57 -18
  3. package/dist/configuration/config.svelte.js +13 -1
  4. package/dist/configuration/themes/index.d.ts +124 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +1 -0
  7. package/dist/layout/base/BasePlotLayout.svelte +18 -5
  8. package/dist/layout/hover/HoverSettings.d.ts +20 -6
  9. package/dist/layout/hover/HoverSettings.js +29 -4
  10. package/dist/layout/legend/LegendState.svelte.d.ts +5 -0
  11. package/dist/layout/legend/LegendState.svelte.js +4 -0
  12. package/dist/layout/plot/Plot.svelte +6 -3
  13. package/dist/layout/plot/PublicPlotContext.d.ts +5 -4
  14. package/dist/layout/plot/PublicPlotContext.js +7 -3
  15. package/dist/layout/plot/Slice.js +4 -2
  16. package/dist/layout/scales/EdgeTicks.d.ts +39 -0
  17. package/dist/layout/scales/EdgeTicks.js +82 -0
  18. package/dist/layout/scales/NiceTicks.d.ts +40 -0
  19. package/dist/layout/scales/NiceTicks.js +72 -0
  20. package/dist/layout/scales/ScalesLayout.svelte +64 -30
  21. package/dist/layout/scales/ScalesLayout.svelte.d.ts +5 -0
  22. package/dist/layout/scales/SveltePlotScale.d.ts +15 -0
  23. package/dist/layout/scales/SveltePlotScale.js +79 -0
  24. package/dist/layout/styles/ValueLabels.js +3 -2
  25. package/dist/layout/timeline/Timeline.svelte +5 -2
  26. package/dist/layout/timeline/Timeline.svelte.d.ts +2 -0
  27. package/dist/layout/timeline/TimelineLayout.svelte +38 -12
  28. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +7 -5
  29. package/dist/layout/timeline/TimelineRule.svelte +1 -0
  30. package/dist/layout/timeline/TimelineState.svelte.d.ts +13 -5
  31. package/dist/layout/timeline/TimelineState.svelte.js +42 -8
  32. package/dist/layout/timeline/TimelineTime.d.ts +18 -0
  33. package/dist/layout/timeline/TimelineTime.js +117 -0
  34. package/dist/layout/tooltip/Tooltip.svelte +6 -3
  35. package/dist/layout/tooltip/Tooltip.svelte.d.ts +2 -0
  36. package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -2
  37. package/dist/layout/tooltip/TooltipState.svelte.js +22 -3
  38. package/dist/markers/delta/DeltaMarker.svelte +9 -7
  39. package/dist/phases/Pipeline.js +3 -2
  40. package/dist/phases/data/DataPhase.d.ts +5 -11
  41. package/dist/phases/data/DataPhase.js +11 -8
  42. package/dist/phases/facet/FacetPanels.d.ts +54 -0
  43. package/dist/phases/facet/FacetPanels.js +95 -0
  44. package/dist/phases/facet/FacetPhase.d.ts +1 -42
  45. package/dist/phases/facet/FacetPhase.js +1 -87
  46. package/dist/phases/hover/HoverPhase.d.ts +6 -43
  47. package/dist/phases/hover/HoverPhase.js +19 -54
  48. package/dist/phases/mapping/MappingPhase.d.ts +1 -1
  49. package/dist/phases/mapping/MappingPhase.js +2 -4
  50. package/dist/phases/scales/ScalesPhase.d.ts +1 -3
  51. package/dist/phases/scales/ScalesPhase.js +17 -21
  52. package/dist/phases/sharedScales/SharedScalesPhase.d.ts +4 -1
  53. package/dist/phases/sharedScales/SharedScalesPhase.js +10 -7
  54. package/dist/phases/statistics/StatisticsPhase.js +3 -4
  55. package/dist/phases/tooltip/TooltipPhase.d.ts +4 -2
  56. package/dist/phases/tooltip/TooltipPhase.js +10 -3
  57. package/dist/phases/transforms/ProcessedPositions.d.ts +30 -0
  58. package/dist/phases/transforms/ProcessedPositions.js +74 -0
  59. package/dist/phases/transforms/TransformsPhase.d.ts +5 -42
  60. package/dist/phases/transforms/TransformsPhase.js +20 -57
  61. package/dist/plots/area/markers.js +1 -2
  62. package/dist/plots/bar/defaults.js +6 -10
  63. package/dist/plots/bar/markers.js +5 -4
  64. package/dist/plots/box/BoxSummaries.d.ts +19 -0
  65. package/dist/plots/box/BoxSummaries.js +101 -0
  66. package/dist/plots/box/Plot.svelte +42 -0
  67. package/dist/plots/box/Plot.svelte.d.ts +26 -0
  68. package/dist/plots/box/defaults.d.ts +12 -0
  69. package/dist/plots/box/defaults.js +34 -0
  70. package/dist/plots/box/markers.d.ts +12 -0
  71. package/dist/plots/box/markers.js +34 -0
  72. package/dist/plots/geo/phases/hover.js +5 -5
  73. package/dist/plots/geo/phases/markers.js +3 -4
  74. package/dist/plots/geo/slices.js +6 -3
  75. package/dist/plots/heatmap/markers.js +2 -1
  76. package/dist/plots/histogram/defaults.js +1 -1
  77. package/dist/plots/histogram/markers.js +4 -4
  78. package/dist/plots/line/markers.js +1 -2
  79. package/dist/plots/pyramid/defaults.js +3 -2
  80. package/dist/plots/pyramid/markers.js +3 -6
  81. package/dist/plots/scatter/defaults.js +3 -2
  82. package/dist/plots/scatter/markers.js +4 -3
  83. package/dist/types/configuration/styling.d.ts +24 -2
  84. package/dist/types/layout/composer.d.ts +5 -2
  85. package/dist/types/layout/scales.d.ts +16 -0
  86. package/dist/types/layout/timeline.d.ts +14 -5
  87. package/dist/types/phases/hover/phase.d.ts +6 -0
  88. package/dist/types/phases/state.d.ts +5 -2
  89. package/dist/types/phases/statistics/phase.d.ts +3 -1
  90. package/dist/types/phases/transforms/phase.d.ts +10 -0
  91. package/dist/types/plots/box/props.d.ts +10 -0
  92. package/dist/types/plots/box/props.js +1 -0
  93. package/dist/types/plots/box/state.d.ts +23 -0
  94. package/dist/types/plots/box/state.js +1 -0
  95. package/dist/types/plots/props.d.ts +2 -0
  96. package/docs/chrome.md +39 -8
  97. package/docs/composer.md +19 -3
  98. package/docs/concepts.md +1 -1
  99. package/docs/development.md +14 -5
  100. package/docs/facets.md +62 -8
  101. package/docs/getting-started.md +8 -9
  102. package/docs/legend.md +62 -15
  103. package/docs/markers.md +17 -6
  104. package/docs/plots.md +168 -42
  105. package/docs/scales.md +36 -19
  106. package/docs/state.md +1 -1
  107. package/docs/styles-and-segments.md +85 -31
  108. package/docs/theming.md +30 -19
  109. package/docs/timeline.md +62 -28
  110. package/docs/tooltip-and-hover.md +72 -24
  111. package/docs/transforms-and-statistics.md +47 -17
  112. package/examples/BarValues.svelte +2 -2
  113. package/examples/Box.svelte +7 -0
  114. package/examples/Chrome.svelte +17 -0
  115. package/examples/ChromeOrder.svelte +18 -0
  116. package/examples/Composer.svelte +23 -0
  117. package/examples/CustomTooltip.svelte +21 -14
  118. package/examples/Facets.svelte +3 -3
  119. package/examples/FacetsCarousel.svelte +14 -0
  120. package/examples/FacetsRowsCols.svelte +23 -0
  121. package/examples/FirstPlot.svelte +7 -0
  122. package/examples/Gaps.svelte +21 -0
  123. package/examples/GroupedBars.svelte +15 -0
  124. package/examples/Heatmap.svelte +7 -0
  125. package/examples/HeatmapSegments.svelte +26 -0
  126. package/examples/Histogram.svelte +14 -0
  127. package/examples/HorizontalBars.svelte +15 -0
  128. package/examples/LegendSections.svelte +27 -0
  129. package/examples/LegendToggle.svelte +3 -3
  130. package/examples/LineByRegion.svelte +18 -0
  131. package/examples/LineDates.svelte +15 -0
  132. package/examples/Markers.svelte +27 -0
  133. package/examples/MovingAverage.svelte +15 -0
  134. package/examples/Pyramid.svelte +7 -0
  135. package/examples/Regression.svelte +14 -0
  136. package/examples/ResponsiveLine.svelte +7 -7
  137. package/examples/Scales.svelte +26 -0
  138. package/examples/Scatter.svelte +16 -0
  139. package/examples/SegmentHighlight.svelte +22 -0
  140. package/examples/Segments.svelte +11 -3
  141. package/examples/SegmentsBySeries.svelte +25 -0
  142. package/examples/StackedArea.svelte +16 -0
  143. package/examples/StackedBars.svelte +15 -0
  144. package/examples/TimelinePlayer.svelte +8 -7
  145. package/examples/TimelineRule.svelte +19 -0
  146. package/examples/Tooltip.svelte +3 -3
  147. package/examples/ValueLabels.svelte +21 -0
  148. package/examples/data.ts +320 -8
  149. package/llms-full.txt +33 -5
  150. package/llms.txt +30 -2
  151. package/package.json +3 -2
  152. package/dist/layout/scales/buildScale.d.ts +0 -8
  153. package/dist/layout/scales/buildScale.js +0 -67
  154. package/dist/layout/scales/niceDomain.d.ts +0 -23
  155. package/dist/layout/scales/niceDomain.js +0 -59
@@ -2,7 +2,12 @@ import type { Snippet } from 'svelte';
2
2
  import type { AxisValue } from './mapping';
3
3
  import type { Accessor } from '../plots/data/common';
4
4
  import type { EasingName, EasingFn } from '../misc/easing';
5
+ /** A step as the timeline works with it: a number (a date as its timestamp). */
5
6
  export type TimeValue = number;
7
+ /** A step as the rows hold it: a number, a `Date` or a date string. */
8
+ export type TimeInput = TimeValue | Date | string;
9
+ /** A step as authors see it: a `Date` on a timeline of dates, else the number. */
10
+ export type TimelineStep = number | Date;
6
11
  export type Orientation = 'horizontal' | 'vertical';
7
12
  export type TimelineChildContext<TRow extends Record<string, unknown>> = {
8
13
  selectedValue: TimeValue;
@@ -42,8 +47,8 @@ export type PlayerConfig = {
42
47
  speeds?: number[];
43
48
  /** Milliseconds each step lasts at speed `1`. Defaults to the configuration's `timeline.player.stepDuration`. */
44
49
  stepDuration?: number;
45
- /** How the current step reads in the `'value'` control. Defaults to the step itself. */
46
- format?: (value: TimeValue) => string;
50
+ /** How a step reads, in the player and along a rule. Defaults to the step, or a date label for dates. */
51
+ format?: (value: TimelineStep) => string;
47
52
  onplay?: (playing: boolean) => void;
48
53
  onspeed?: (speed: number) => void;
49
54
  onloop?: (loop: boolean) => void;
@@ -53,10 +58,13 @@ export type TimelineCallbacks = {
53
58
  /** Called with the new position, `0`-`1`, whenever the player or the reader moves it. */
54
59
  onprogress?: (progress: number) => void;
55
60
  /** Called with the step the position lands on, when that step changes. */
56
- onvalue?: (value: TimeValue) => void;
61
+ onvalue?: (value: TimelineStep) => void;
57
62
  };
58
63
  export type TimelineConfig<TRow extends Record<string, unknown>> = TimelineCallbacks & {
59
- accessor: Accessor<TRow, TimeValue>;
64
+ /** The field (or function) giving each row's step: a number, a `Date`, or an ISO or `dateFormat` date string. */
65
+ accessor: Accessor<TRow, TimeInput>;
66
+ /** How a non-ISO date string reads, with `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss`, e.g. `'DD/MM/YYYY'`. */
67
+ dateFormat?: string;
60
68
  /** How it draws. Defaults to `'player'`. */
61
69
  type?: TimelineType;
62
70
  /** The player bar's own options, when `type` is `'player'`. */
@@ -67,7 +75,8 @@ export type TimelineConfig<TRow extends Record<string, unknown>> = TimelineCallb
67
75
  * with its own `timeline`, the composer's wins and nothing draws here.
68
76
  */
69
77
  progress?: number;
70
- values?: TimeValue[];
78
+ /** The steps, when not every distinct value of `accessor`. */
79
+ values?: TimeInput[];
71
80
  /** Which way a `'rule'` runs. A `'player'` is always horizontal. */
72
81
  orientation?: Orientation;
73
82
  show?: boolean;
@@ -17,6 +17,12 @@ export type HoverStageOptions<TState, TCtx = unknown> = {
17
17
  * that isn't measured, or `'punctual'` with both axes holding categories.
18
18
  */
19
19
  strategy?: HoverStrategy;
20
+ /** The rows the pointer hovers over, when they aren't the ones drawn (a box plot's one summary per box). Defaults to the processed rows. */
21
+ rows?: (state: TState) => Record<string | symbol, unknown>[];
22
+ /** How a hovered row reads in the tooltip unless the author sets `format`. Defaults to the shared tooltip text. */
23
+ format?: (state: TState) => (row: never) => string;
24
+ /** The order a tooltip's rows read in. Defaults to the shared order: highest value first. */
25
+ sort?: (state: TState) => (a: never, b: never) => number;
20
26
  /** Where a row sits. Defaults to its processed position: a stacked row's top, a bin's centre. */
21
27
  position?: (state: TState) => HoverPosition;
22
28
  /** The short label drawn next to a hovered row. Defaults to its value. */
@@ -1,4 +1,4 @@
1
- import type { Series } from '../plots/data/common';
1
+ import type { Accessor, Series } from '../plots/data/common';
2
2
  import type { AxisBasedScalesConfig } from '../layout/scales';
3
3
  import type { SegmentsConfig, MarginConfig, HoverConfig, TooltipConfig } from '../plots/props';
4
4
  import type { HoverPosition } from './hover/phase';
@@ -9,7 +9,7 @@ import type { PlotStylesConfig } from '../configuration/styling';
9
9
  import type { AxisBasedMarkersConfig } from '../markers/common';
10
10
  import type { StatisticConfig } from '../layout/statistics';
11
11
  import type { MarkerTransform } from '../transforms';
12
- import type { ProcessedData } from './transforms/phase';
12
+ import type { ProcessedData, ProcessedRow } from './transforms/phase';
13
13
  import type { FacetStageState } from './facet/phase';
14
14
  import type { AxisBasedStylesConfig, ValuesStyle } from '../layout/styles';
15
15
  import type { ValueLabels } from '../../layout/styles/ValueLabels';
@@ -48,6 +48,7 @@ export type AxisPlotState<TData extends Record<string, unknown>, TStyle extends
48
48
  /** What each position channel is, which one holds the categories, and which way the plot runs. */
49
49
  axes?: PlotAxes;
50
50
  resolvedSeries?: TSeries[];
51
+ seriesGrouping?: Accessor<any, unknown>;
51
52
  /** Every series name, before legend-omitted ones are dropped, so colors stay put. */
52
53
  allSeriesNames?: string[];
53
54
  groupedSeries?: VisualGroup<TSeries, TStyle>[];
@@ -58,9 +59,11 @@ export type AxisPlotState<TData extends Record<string, unknown>, TStyle extends
58
59
  gapFillMaps?: Map<string, Map<TData, DiscreteGapFill<TStyle>>>;
59
60
  channels?: ResolvedChannels<TData>;
60
61
  processedData?: ProcessedData;
62
+ processedGroups?: VisualGroup<Series<ProcessedRow>, TStyle>[];
61
63
  valueLabels?: ValueLabels;
62
64
  markers?: AxisBasedMarkersConfig[];
63
65
  tooltipFormat?: (row: Record<string, unknown>) => string;
66
+ tooltipSort?: (a: Record<string, unknown>, b: Record<string, unknown>) => number;
64
67
  hover?: HoverConfig<any>;
65
68
  hoverPosition?: HoverPosition;
66
69
  hoverRows?: unknown[];
@@ -3,7 +3,7 @@ import type { Accessor, Series } from '../../plots/data/common';
3
3
  import type { AxisValue } from '../../layout/mapping';
4
4
  import type { StatisticConfig } from '../../layout/statistics';
5
5
  import type { AxisBasedMarkersConfig } from '../../markers/common';
6
- import type { ProcessedData } from '../transforms/phase';
6
+ import type { ProcessedData, TransformsStageState } from '../transforms/phase';
7
7
  import type { FacetStageState } from '../facet/phase';
8
8
  import type { PlotAxes } from '../../../phases/data/PlotAxes';
9
9
  /**
@@ -25,6 +25,8 @@ export type StatisticsStageState = Pick<FacetStageState, 'panels'> & {
25
25
  scopedGroups?: ScopedMarkerGroup<any>[];
26
26
  /** The plot's rows after its own transforms, which an author marker without rows of its own draws. */
27
27
  processedData?: ProcessedData;
28
+ /** Those rows split back into the plot's visual groups, which a series without a value of its own (a histogram's) is measured by. */
29
+ processedGroups?: TransformsStageState['processedGroups'];
28
30
  markers?: AxisBasedMarkersConfig[];
29
31
  };
30
32
  /** What differs between plots in the shared "statistics" stage. */
@@ -1,6 +1,7 @@
1
1
  import type { Series } from '../../plots/data/common';
2
2
  import type { MarkerTransform } from '../../transforms';
3
3
  import type { PlotAxes } from '../../../phases/data/PlotAxes';
4
+ import type { VisualGroup } from '../../plots/segments/config';
4
5
  /** A row the plot-level transforms produced: the source row's fields, plus whatever channels they add (often symbol-keyed). */
5
6
  export type ProcessedRow = Record<string | symbol, unknown>;
6
7
  /** A channel as svelteplot reads it off a processed row: a field key, a symbol key, or a function. */
@@ -32,9 +33,18 @@ export type TransformsStageState = {
32
33
  }>>;
33
34
  transforms?: MarkerTransform[];
34
35
  processedData?: ProcessedData;
36
+ /** The processed rows split back into the visual groups the plot's own marks draw. */
37
+ processedGroups?: VisualGroup<Series<ProcessedRow>, any>[];
35
38
  };
36
39
  /** What differs between plots in the shared "transforms" stage. */
37
40
  export type TransformsStageOptions<TState extends TransformsStageState, TCtx> = {
38
41
  /** The transforms to run. Defaults to the plot's own `transforms`. */
39
42
  transforms?: (state: TState, ctx: TCtx) => MarkerTransform[];
43
+ /**
44
+ * How the processed rows split into `processedGroups`: one group per series
45
+ * (the default), or `'runs'` of consecutive rows from the same series, in
46
+ * the rows' own order, so a reordering transform (`sort`, `reverse`,
47
+ * `shuffle`) decides which rows paint on top.
48
+ */
49
+ groups?: 'series' | 'runs';
40
50
  };
@@ -0,0 +1,10 @@
1
+ import type { BasePlotProps, PlotCoreProps } from '../props';
2
+ import type { AxisBasedScalesConfig } from '../../layout/scales';
3
+ import type { AxisBasedMarkersConfig } from '../../markers/common';
4
+ import type { SeriesProps, DataProps } from '../data/common';
5
+ import type { AxisBasedStylesConfig } from '../../layout/styles';
6
+ import type { AreaStyle } from '../styling';
7
+ /** `BoxPlot`'s public props. */
8
+ export type BoxPlotProps<TData extends Record<string, unknown>> = BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, AxisBasedStylesConfig, AreaStyle, AxisBasedMarkersConfig, AxisBasedScalesConfig<TData>>;
9
+ /** What `BoxPlot`'s core draws from. */
10
+ export type BoxPlotCoreProps<TData extends Record<string, unknown>> = PlotCoreProps<SeriesProps<TData> | DataProps<TData>, TData, AxisBasedStylesConfig, AreaStyle, AxisBasedMarkersConfig, AxisBasedScalesConfig<TData>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { AreaStyle } from '../styling';
2
+ import type { AxisPlotState, AxisPlotCtx, ProcessedRow } from '../../phases';
3
+ import type { AxisValue } from '../../layout/mapping';
4
+ /** BoxPlot's pipeline state: the shared axis-based state, with no extras of its own. */
5
+ export type BoxPlotState<TData extends Record<string, unknown>> = AxisPlotState<TData, AreaStyle>;
6
+ /** The environment every BoxPlot phase reads from, alongside the growing state. */
7
+ export type BoxPlotCtx = AxisPlotCtx;
8
+ /**
9
+ * One box's summary - what a hovered box reads as: its category, its
10
+ * quartiles, the whisker ends (the farthest values within 1.5 IQR of the
11
+ * box) and the values past them. It also keeps the series tags and, when the
12
+ * category channel is a field key, the category under that field.
13
+ */
14
+ export type BoxSummaryRow = ProcessedRow & {
15
+ category: AxisValue;
16
+ min: number;
17
+ p25: number;
18
+ median: number;
19
+ p75: number;
20
+ max: number;
21
+ count: number;
22
+ outliers: number[];
23
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -48,8 +48,10 @@ export type HoverConfig<TRow extends Record<string, unknown>> = {
48
48
  strategy?: HoverStrategy;
49
49
  /** Resolved sync flag for the *local* hover store (ignored once a shared store is in scope). */
50
50
  sync?: boolean;
51
+ /** The resolved tooltip: its `format`, and the order its text reads the rows in (a `content` snippet gets them as matched). */
51
52
  tooltip?: TooltipConfig<TRow> & {
52
53
  format: (row: TRow) => string;
54
+ sort?: (a: TRow, b: TRow) => number;
53
55
  };
54
56
  /** When it returns `true`, that pointer event is treated as a leave instead of a move - lets a nested layout opt part of the plot out of hover matching. */
55
57
  exclude?: (e: PointerEvent) => boolean;
package/docs/chrome.md CHANGED
@@ -2,20 +2,30 @@
2
2
 
3
3
  The chrome is everything a figure draws around its plot: title, subtitle, legend, timeline player, caption. Every plot and every [composer](composer.md) takes the same chrome props.
4
4
 
5
+ <!-- example: examples/Chrome.svelte -->
6
+
5
7
  ```svelte
8
+ <script lang="ts">
9
+ import { LinePlot } from '@fundar/data-chart-telling';
10
+ import { PRICES } from './data';
11
+ </script>
12
+
13
+ <!-- A plot carries its own chrome: title, subtitle, legend and caption. -->
6
14
  <LinePlot
7
- {width}
8
- {height}
9
- title="Consumer prices"
10
- subtitle="Index by division, December 2016 = 100"
11
- caption="Source: national statistics office"
12
- data={rows}
15
+ width={720}
16
+ height={440}
17
+ title="Commodity prices"
18
+ subtitle="Price index, January 2021 = 100"
19
+ caption="Source: IMF, Primary Commodity Prices"
20
+ data={PRICES}
13
21
  x="date"
14
22
  y="index"
15
- color="division"
23
+ color="commodity"
16
24
  />
17
25
  ```
18
26
 
27
+ <!-- /example -->
28
+
19
29
  ![A plot with title, subtitle and caption](images/chrome.png)
20
30
 
21
31
  | Prop | Type | Meaning |
@@ -55,10 +65,31 @@ A `header` takes the place of `title` and `subtitle`, and a `footer` the place o
55
65
  - `'divider'` draws a line between the two blocks around it, and only when both are drawn. Several dividers may appear.
56
66
  - A row puts two or three blocks side by side: `'(plot, timeline)'`, `'(legend, plot)'`. The `plot` takes the width the others leave; a row without `plot` shares its width among the blocks.
57
67
 
68
+ <!-- example: examples/ChromeOrder.svelte -->
69
+
58
70
  ```svelte
59
- <LinePlot ... order={['title', 'subtitle', 'divider', 'legend', 'plot', 'divider', 'caption']} />
71
+ <script lang="ts">
72
+ import { LinePlot } from '@fundar/data-chart-telling';
73
+ import { PRICES } from './data';
74
+ </script>
75
+
76
+ <!-- `order` places the chrome blocks; `divider` draws a rule between them. -->
77
+ <LinePlot
78
+ width={720}
79
+ height={460}
80
+ title="Commodity prices"
81
+ subtitle="Price index"
82
+ caption="Source: IMF, Primary Commodity Prices"
83
+ data={PRICES}
84
+ x="date"
85
+ y="index"
86
+ color="commodity"
87
+ order={['title', 'subtitle', 'divider', 'legend', 'plot', 'divider', 'caption']}
88
+ />
60
89
  ```
61
90
 
91
+ <!-- /example -->
92
+
62
93
  ![Dividers placed between chrome blocks](images/chrome-order.png)
63
94
 
64
95
  The default order, from the configuration's `chrome.order`: `header`, `title`, `subtitle`, `divider`, `legend`, `plot`, `timeline`, `footer`, `caption`. It can be changed globally with `setConfiguration({ chrome: { order: [...] } })`.
package/docs/composer.md CHANGED
@@ -2,20 +2,36 @@
2
2
 
3
3
  `PlotComposer` puts several plots in one figure. It is a figure like any plot: it takes the same [chrome](chrome.md) (`title`, `subtitle`, `caption`, `legend`, `order`) and a `width` and `height`, and lays its children out on a grid.
4
4
 
5
+ <!-- example: examples/Composer.svelte -->
6
+
5
7
  ```svelte
8
+ <script lang="ts">
9
+ import { PlotComposer, LinePlot, BarPlot, ScatterPlot } from '@fundar/data-chart-telling';
10
+ import { REGION_VALUES, PROVINCE_VALUES, COUNTRIES } from './data';
11
+ </script>
12
+
13
+ <!-- Several plots in one figure, placed on named areas of a grid. -->
6
14
  <PlotComposer
7
15
  width={900}
8
16
  height={560}
9
17
  title="Three views, one figure"
18
+ legend={false}
10
19
  tags="A"
11
20
  layout={{ areas: ['a a b', 'a a c'] }}
12
21
  >
13
- <LinePlot area="a" data={series} x="year" y="value" color="metric" tooltip />
14
- <BarPlot area="b" data={bars} x="region" y="value" />
15
- <ScatterPlot area="c" data={points} x="gdp" y="life" />
22
+ <LinePlot area="a" data={REGION_VALUES} x="date" y="value" color="region" tooltip />
23
+ <BarPlot
24
+ area="b"
25
+ data={PROVINCE_VALUES.filter((row) => row.sex === 'female').slice(0, 3)}
26
+ x="province"
27
+ y="value"
28
+ />
29
+ <ScatterPlot area="c" data={COUNTRIES} x="gdp" y="life" />
16
30
  </PlotComposer>
17
31
  ```
18
32
 
33
+ <!-- /example -->
34
+
19
35
  ![Three plots in one figure on a named-area layout](images/composer.png)
20
36
 
21
37
  Plots drawn straight inside the composer take a cell of the grid and draw at the cell's size - they need no `width` or `height`.
package/docs/concepts.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Plots and figures
4
4
 
5
- A **plot** is one graph: axes and marks. Every plot is a component named after what it draws - `LinePlot`, `AreaPlot`, `BarPlot`, `HistogramPlot`, `HeatmapPlot`, `PyramidPlot`, `ScatterPlot`, `GeoPlot` (see [Plots](plots.md)).
5
+ A **plot** is one graph: axes and marks. Every plot is a component named after what it draws - `LinePlot`, `AreaPlot`, `BarPlot`, `HistogramPlot`, `BoxPlot`, `HeatmapPlot`, `PyramidPlot`, `ScatterPlot`, `GeoPlot` (see [Plots](plots.md)).
6
6
 
7
7
  A plot is drawn inside a **figure**: the box that holds the plot together with its [chrome](chrome.md) (title, subtitle, legend, timeline, caption). Every plot component is a figure, so a plot with a `title` and a `legend` is still one component. A [`PlotComposer`](composer.md) is a figure that holds several plots.
8
8
 
@@ -35,7 +35,7 @@ pnpm test:stories # every story as a test (boots Storybook
35
35
 
36
36
  - Component tests must end in `.svelte.test.ts`; that suffix is what routes a file to the browser project.
37
37
  - Browser tests render with `vitest-browser-svelte`. A dot renders as a `<path>`, not a `<circle>`; styles live in the `style` attribute.
38
- - `tests/unit/docs` and `tests/unit/examples.svelte.test.ts` guard the LLM-facing docs: every example mounts, and the links and exports named in `llms.txt` and `llms-full.txt` exist.
38
+ - `tests/unit/docs` and `tests/unit/examples.svelte.test.ts` guard the LLM-facing docs: every example mounts, the links and exports named in `llms.txt` and `llms-full.txt` exist, and the example snippets in the Markdown match their files.
39
39
 
40
40
  ## Storybook
41
41
 
@@ -53,14 +53,23 @@ Every option has a story under `stories/`, grouped as Plots, Markers, Transforms
53
53
 
54
54
  ## Documentation figures
55
55
 
56
- Every screenshot in the docs is a real component in `scripts/docs-figures/figures/`, type-checked by `pnpm check` and rendered by Playwright:
56
+ Every screenshot in the docs is an example, rendered by Playwright to `docs/images/<name>.png` in kebab-case (`examples/LineByRegion.svelte` is `line-by-region.png`). The examples and the screenshots share one file of small real datasets, `examples/data.ts`, which names the source of each, and axes are set to English. `docs/images` is left out of the npm package.
57
57
 
58
58
  ```bash
59
- pnpm docs:images # regenerate docs/images/*.png
60
- pnpm docs:images tooltip scales # only these figures
59
+ pnpm docs:images # regenerate docs/images/*.png
60
+ pnpm docs:images tooltip scales # only these
61
61
  ```
62
62
 
63
- A figure can set `export const meta = { hover: { x, y } }` in a `<script module>` block to rest the pointer before the shot. The data is synthetic and deterministic (`scripts/docs-figures/data.ts`), and axes are set to English. `docs/images` is left out of the npm package.
63
+ `scripts/docs-figures/figures.json` lists the examples that get no screenshot (`skip`) and where the pointer rests before a shot (`meta`, e.g. `{ "hover": { "x": 330, "y": 160 } }`). The maps are the exception: their GeoJSON is too large to ship in `examples/`, so they are components of their own in `scripts/docs-figures/figures/`, importing it from `stories/`, and their snippets in `geo.md` and `styles-and-segments.md` are written by hand.
64
+
65
+ The Markdown shows each example's source between `<!-- example: examples/Name.svelte -->` and `<!-- /example -->`:
66
+
67
+ ```bash
68
+ pnpm docs:examples # copy each example's source into README.md and docs/*.md
69
+ pnpm docs:examples --check # fail when a block is out of date
70
+ ```
71
+
72
+ `tests/unit/docs/examples-sync.test.ts` runs the check, so a snippet never drifts from its example or its screenshot.
64
73
 
65
74
  ## Continuous integration
66
75
 
package/docs/facets.md CHANGED
@@ -2,18 +2,29 @@
2
2
 
3
3
  `facet` splits a plot's rows into small multiples: one panel per value of a field, each drawn with the same plot configuration.
4
4
 
5
+ <!-- example: examples/Facets.svelte -->
6
+
5
7
  ```svelte
8
+ <script lang="ts">
9
+ import { LinePlot } from '@fundar/data-chart-telling';
10
+ import { REGION_VALUES } from './data';
11
+ </script>
12
+
13
+ <!-- One panel per region, three columns, sharing scales. A tooltip with `sync` shows in every panel on purpose. -->
6
14
  <LinePlot
7
- {width}
8
- {height}
9
- data={rows}
10
- x="year"
15
+ width={900}
16
+ height={360}
17
+ data={REGION_VALUES}
18
+ x="date"
11
19
  y="value"
12
- facet={{ by: 'region', columns: 3, gap: 12 }}
20
+ facet={{ by: 'region', columns: 3, gap: 12, scales: 'shared' }}
21
+ tooltip={{ sync: true }}
13
22
  />
14
23
  ```
15
24
 
16
- ![One panel per region](images/facets-grid.png)
25
+ <!-- /example -->
26
+
27
+ ![One panel per region](images/facets.png)
17
28
 
18
29
  The data phases (grouping, transforms, statistics, shared scales) run once for all panels; each panel then lays itself out in its own box. Panels share legend toggles and hover, so one legend controls them all.
19
30
 
@@ -36,20 +47,63 @@ The data phases (grouping, transforms, statistics, shared scales) run once for a
36
47
  | `scales` | `'shared'` (default), `'free'`, `'free-x'`, `'free-y'` |
37
48
  | `axes` | `'all'` (default) draws every panel's axes; `'outer'` draws only the left column's y and the bottom row's x, with panels resized to equal boxes |
38
49
 
50
+ <!-- example: examples/FacetsCarousel.svelte -->
51
+
39
52
  ```svelte
40
- <BarPlot ... facet={{ by: 'group', columns: 3, mode: 'carousel', visible: 2 }} />
53
+ <script lang="ts">
54
+ import { LinePlot } from '@fundar/data-chart-telling';
55
+ import { REGION_VALUES } from './data';
56
+ </script>
57
+
58
+ <!-- A carousel shows `visible` panels at a time, with navigation below. -->
59
+ <LinePlot
60
+ width={720}
61
+ height={340}
62
+ data={REGION_VALUES}
63
+ x="date"
64
+ y="value"
65
+ facet={{ by: 'region', columns: 3, mode: 'carousel', visible: 2, navLayout: 'below' }}
66
+ />
41
67
  ```
42
68
 
69
+ <!-- /example -->
70
+
43
71
  ![A carousel showing two panels at a time](images/facets-carousel.png)
44
72
 
45
73
  ## Rows by columns
46
74
 
47
75
  `{ rows, cols }` makes one panel per combination of a row field and a column field, with each value labelled. Combinations without data stay empty. `rowValues` and `colValues` choose and order the values. A free `x` scale is shared down each column and a free `y` along each row.
48
76
 
77
+ <!-- example: examples/FacetsRowsCols.svelte -->
78
+
49
79
  ```svelte
50
- <LinePlot ... facet={{ rows: 'region', cols: 'sector', scales: 'free' }} />
80
+ <script lang="ts">
81
+ import { LinePlot } from '@fundar/data-chart-telling';
82
+ import { REGION_VALUES } from './data';
83
+
84
+ // Each region's value split into two sectors.
85
+ const SECTORS = [
86
+ { sector: 'Goods', share: 0.8 },
87
+ { sector: 'Services', share: 1.4 }
88
+ ];
89
+ const rows = REGION_VALUES.flatMap((row) =>
90
+ SECTORS.map(({ sector, share }) => ({ ...row, sector, value: row.value * share }))
91
+ );
92
+ </script>
93
+
94
+ <!-- One panel per region and sector; `scales: 'free'` fits each panel to its own data. -->
95
+ <LinePlot
96
+ width={720}
97
+ height={420}
98
+ data={rows}
99
+ x="date"
100
+ y="value"
101
+ facet={{ rows: 'region', cols: 'sector', scales: 'free' }}
102
+ />
51
103
  ```
52
104
 
105
+ <!-- /example -->
106
+
53
107
  ![Panels for each combination of region and sector](images/facets-rows-cols.png)
54
108
 
55
109
  ## Alignment
@@ -13,24 +13,23 @@ pnpm add @fundar/data-chart-telling svelte svelteplot
13
13
 
14
14
  ## A first plot
15
15
 
16
+ <!-- example: examples/FirstPlot.svelte -->
17
+
16
18
  ```svelte
17
19
  <script lang="ts">
18
20
  import { LinePlot } from '@fundar/data-chart-telling';
19
-
20
- const rows = [
21
- { year: 2020, region: 'North', value: 3.1 },
22
- { year: 2021, region: 'North', value: 3.8 },
23
- { year: 2020, region: 'South', value: 2.2 },
24
- { year: 2021, region: 'South', value: 2.9 }
25
- ];
21
+ import { REGION_VALUES } from './data';
26
22
  </script>
27
23
 
28
- <LinePlot width={720} height={400} data={rows} x="year" y="value" color="region" />
24
+ <!-- The smallest useful plot: rows, the fields to map and a pixel size. -->
25
+ <LinePlot width={720} height={400} data={REGION_VALUES} x="date" y="value" color="region" />
29
26
  ```
30
27
 
28
+ <!-- /example -->
29
+
31
30
  ![A line plot with one line per region](images/first-plot.png)
32
31
 
33
- - `data` is an array of flat rows; `x` and `y` name the fields that position each row.
32
+ - `data` is an array of flat rows, here `{ date, region, value }` from [examples/data.ts](../examples/data.ts); `x` and `y` name the fields that position each row.
34
33
  - `color="region"` splits the rows into one series per region and gives each its own color.
35
34
  - `width` and `height` are pixels.
36
35
 
package/docs/legend.md CHANGED
@@ -37,11 +37,39 @@ Segments and markers join the series section when they carry a `legend` label:
37
37
 
38
38
  Items become clickable with `interactive` on the series section. `disabled` says what a hidden series becomes:
39
39
 
40
+ <!-- example: examples/LegendToggle.svelte -->
41
+
40
42
  ```svelte
41
- <LinePlot ... legend={{ series: { interactive: { enabled: true, disabled: { mode: 'omit' } } } }} />
43
+ <script lang="ts">
44
+ import { LinePlot } from '@fundar/data-chart-telling';
45
+ import { REGION_VALUES } from './data';
46
+ import LegendReader from './LegendReader.svelte';
47
+ </script>
48
+
49
+ <!--
50
+ The automatic legend can toggle series. `mode: 'omit'` drops a hidden series
51
+ and refits the axes to what is left; 'dim' and 'hide' keep the axes.
52
+ Anything inside the figure (here, the footer) reads the toggles through
53
+ `useFigure().legend` - see LegendReader.
54
+ -->
55
+ <LinePlot
56
+ width={720}
57
+ height={400}
58
+ data={REGION_VALUES}
59
+ x="date"
60
+ y="value"
61
+ color="region"
62
+ legend={{ series: { interactive: { enabled: true, disabled: { mode: 'omit' } } } }}
63
+ >
64
+ {#snippet footer()}
65
+ <LegendReader />
66
+ {/snippet}
67
+ </LinePlot>
42
68
  ```
43
69
 
44
- ![Legend items as toggles](images/legend-interactive.png)
70
+ <!-- /example -->
71
+
72
+ ![Legend items as toggles](images/legend-toggle.png)
45
73
 
46
74
  | `disabled.mode` | A hidden series... |
47
75
  | ----------------- | ------------------------------------------------------------------------------------- |
@@ -77,21 +105,40 @@ Each channel key adjusts the section that channel draws: any section field excep
77
105
 
78
106
  A list replaces the automatic legend with exactly those sections:
79
107
 
80
- ```ts
81
- legend={[
82
- {
83
- type: 'discrete',
84
- title: 'Indicator',
85
- interactive: { enabled: true },
86
- items: [
87
- { name: 'GDP', color: '#4f46e5', symbol: 'line' },
88
- { name: 'Employment', color: '#e6580d', symbol: 'line' }
89
- ]
90
- },
91
- { type: 'continuous', title: 'Share', min: 0, max: 100, scheme: 'blues' }
92
- ]}
108
+ <!-- example: examples/LegendSections.svelte -->
109
+
110
+ ```svelte
111
+ <script lang="ts">
112
+ import { LinePlot } from '@fundar/data-chart-telling';
113
+ import { REGION_VALUES } from './data';
114
+ </script>
115
+
116
+ <!-- An explicit legend: a discrete section of toggles and a continuous ramp. -->
117
+ <LinePlot
118
+ width={720}
119
+ height={400}
120
+ data={REGION_VALUES}
121
+ x="date"
122
+ y="value"
123
+ color="region"
124
+ legend={[
125
+ {
126
+ type: 'discrete',
127
+ title: 'Region',
128
+ interactive: { enabled: true },
129
+ items: [
130
+ { name: 'GBA', color: '#4f46e5', symbol: 'line' },
131
+ { name: 'Cuyo', color: '#e6580d', symbol: 'line' },
132
+ { name: 'Northwest', color: '#16a34a', symbol: 'line' }
133
+ ]
134
+ },
135
+ { type: 'continuous', title: 'Share', min: 0, max: 100, scheme: 'blues' }
136
+ ]}
137
+ />
93
138
  ```
94
139
 
140
+ <!-- /example -->
141
+
95
142
  ![A legend of explicit sections: discrete items and a continuous ramp](images/legend-sections.png)
96
143
 
97
144
  Section types:
package/docs/markers.md CHANGED
@@ -2,29 +2,40 @@
2
2
 
3
3
  `markers` adds marks to a plot beyond the ones its kind draws: annotations, regions, derived shapes, hover highlights, or any Svelte component. It is an array of objects; `type` picks the marker and the other fields configure it.
4
4
 
5
+ <!-- example: examples/Markers.svelte -->
6
+
5
7
  ```svelte
8
+ <script lang="ts">
9
+ import { LinePlot } from '@fundar/data-chart-telling';
10
+ import { LIFE } from './data';
11
+ </script>
12
+
13
+ <!-- Markers add marks to a plot: a hover highlight, a reference rule and a delta between two series. -->
6
14
  <LinePlot
7
- {width}
8
- {height}
9
- data={lifeExpectancy}
15
+ width={720}
16
+ height={400}
17
+ data={LIFE}
10
18
  x="year"
11
19
  y="value"
12
20
  color="sex"
21
+ scales={{ x: { edgeTicks: true } }}
13
22
  markers={[
14
23
  { type: 'hover' },
15
- { type: 'rule', axis: 'x', value: 2020, styles: { stroke: '#999', strokeDasharray: '4 4' } },
24
+ { type: 'rule', axis: 'x', value: 2001, styles: { stroke: '#999', strokeDasharray: '4 4' } },
16
25
  {
17
26
  type: 'delta',
18
27
  axis: 'x',
19
- at: 2018,
28
+ at: 1980,
20
29
  from: { type: 'series', series: 'female' },
21
30
  to: { type: 'series', series: 'male' },
22
- formatDiff: (d) => d.toFixed(1)
31
+ formatDiff: (difference) => difference.toFixed(1)
23
32
  }
24
33
  ]}
25
34
  />
26
35
  ```
27
36
 
37
+ <!-- /example -->
38
+
28
39
  ![A line plot with a reference rule and a delta between two series](images/markers.png)
29
40
 
30
41
  Markers draw in array order. Type a markers array with `satisfies AxisBasedMarkersConfig[]` (or `GeoMarkersConfig[]`) to get completion.