@fundar/data-chart-telling 0.0.62 → 0.0.64

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.
package/README.md CHANGED
@@ -482,10 +482,13 @@ Each is a `TextStyle` — `{ font, size, weight, color, leading, spacing }` —
482
482
  | `legend.navButton.fontSize` | `string` | `'1rem'` | `--dct-legend-nav-font-size` |
483
483
  | `legend.navButton.disabledOpacity` | `number` | `0.3` | `--dct-legend-nav-disabled-opacity` |
484
484
 
485
- `legend.continuous` — the gradient bar's draggable handles, ticks, and labels:
485
+ `legend.continuous` — the gradient bar itself, its draggable handles, ticks, and labels:
486
486
 
487
487
  | Field | Type | Default | CSS variable |
488
488
  | ----- | ---- | ------- | ------------- |
489
+ | `legend.continuous.height` | `number` (px) | `20` | — |
490
+ | `legend.continuous.barHeight` | `number` (px) | `12` | — |
491
+ | `legend.continuous.barRadius` | `number` (px) | `2` | — |
489
492
  | `legend.continuous.handle.width` | `string` | `'6px'` | `--dct-continuous-handle-width` |
490
493
  | `legend.continuous.handle.height` | `string` | `'20px'` | `--dct-continuous-handle-height` |
491
494
  | `legend.continuous.handle.radius` | `string` | `'3px'` | `--dct-continuous-handle-radius` |
@@ -522,6 +525,10 @@ Consumed as plain numeric/boolean props by the underlying svelteplot axes — on
522
525
  | `axis.xTickPadding` | `number` | `10` | — |
523
526
  | `axis.yTickPadding` | `number` | `10` | — |
524
527
  | `axis.grid` | `boolean` | `false` | — |
528
+ | `axis.locale` | `string` | `'es-AR'` | — |
529
+ | `axis.compactDisplay` | `'short' \| 'long'` | `'long'` | — (`'1 mil'` vs `'1 K'`) |
530
+
531
+ `axis.locale`/`axis.compactDisplay` feed svelteplot's own automatic tick formatter — the one it falls back to on any axis without its own explicit `scales.x`/`scales.y.tickFormat`. Whether a given axis abbreviates at all (vs. spelling every digit out) stays svelteplot's own per-axis call, based on that axis's value range; these two only change the *wording* once it decides to. `axis.locale` updates a chart already on screen; `axis.compactDisplay` is read once when a chart mounts (it goes through svelteplot's own `setPlotDefaults` context hook, not a per-instance prop) and needs a remount to pick up a later change.
525
532
 
526
533
  #### Tooltip
527
534
 
@@ -14,7 +14,11 @@
14
14
  */
15
15
  // ── Defaults ────────────────────────────────────────────────────────────────
16
16
  // Mirror the values the components shipped with, so adopting the config system
17
- // is visually a no-op until something is overridden.
17
+ // is visually a no-op until something is overridden — except `axis.locale`,
18
+ // which defaults to `es-AR` instead of svelteplot's own `en-US`, and
19
+ // `axis.compactDisplay`, which defaults to `long` instead of svelteplot's
20
+ // own `short` (Spanish's short form is inconsistent about letter case —
21
+ // `'1 K'` vs `'10 k'` for the same unit — while its long form isn't).
18
22
  const text = (over) => ({
19
23
  font: 'inherit',
20
24
  size: '1rem',
@@ -60,6 +64,9 @@ export const DEFAULT_CONFIG = {
60
64
  disabledOpacity: 0.3
61
65
  },
62
66
  continuous: {
67
+ height: 20,
68
+ barHeight: 12,
69
+ barRadius: 2,
63
70
  // A slim vertical bar handle, taller than the 12px gradient track.
64
71
  handle: {
65
72
  width: '6px',
@@ -95,7 +102,9 @@ export const DEFAULT_CONFIG = {
95
102
  yTickSize: 5,
96
103
  xTickPadding: 10,
97
104
  yTickPadding: 10,
98
- grid: false
105
+ grid: false,
106
+ locale: 'es-AR',
107
+ compactDisplay: 'long'
99
108
  },
100
109
  tooltip: {
101
110
  background: '#111827',
@@ -58,6 +58,9 @@ export declare const THEMES: {
58
58
  disabledOpacity?: number | undefined;
59
59
  } | undefined;
60
60
  continuous?: {
61
+ height?: number | undefined;
62
+ barHeight?: number | undefined;
63
+ barRadius?: number | undefined;
61
64
  handle?: {
62
65
  width?: string | undefined;
63
66
  height?: string | undefined;
@@ -93,6 +96,8 @@ export declare const THEMES: {
93
96
  xTickPadding?: number | undefined;
94
97
  yTickPadding?: number | undefined;
95
98
  grid?: boolean | undefined;
99
+ locale?: string | undefined;
100
+ compactDisplay?: "short" | "long" | undefined;
96
101
  } | undefined;
97
102
  tooltip?: {
98
103
  background?: string | undefined;
@@ -294,6 +299,9 @@ export declare const THEMES: {
294
299
  disabledOpacity?: number | undefined;
295
300
  } | undefined;
296
301
  continuous?: {
302
+ height?: number | undefined;
303
+ barHeight?: number | undefined;
304
+ barRadius?: number | undefined;
297
305
  handle?: {
298
306
  width?: string | undefined;
299
307
  height?: string | undefined;
@@ -329,6 +337,8 @@ export declare const THEMES: {
329
337
  xTickPadding?: number | undefined;
330
338
  yTickPadding?: number | undefined;
331
339
  grid?: boolean | undefined;
340
+ locale?: string | undefined;
341
+ compactDisplay?: "short" | "long" | undefined;
332
342
  } | undefined;
333
343
  tooltip?: {
334
344
  background?: string | undefined;
@@ -530,6 +540,9 @@ export declare const THEMES: {
530
540
  disabledOpacity?: number | undefined;
531
541
  } | undefined;
532
542
  continuous?: {
543
+ height?: number | undefined;
544
+ barHeight?: number | undefined;
545
+ barRadius?: number | undefined;
533
546
  handle?: {
534
547
  width?: string | undefined;
535
548
  height?: string | undefined;
@@ -565,6 +578,8 @@ export declare const THEMES: {
565
578
  xTickPadding?: number | undefined;
566
579
  yTickPadding?: number | undefined;
567
580
  grid?: boolean | undefined;
581
+ locale?: string | undefined;
582
+ compactDisplay?: "short" | "long" | undefined;
568
583
  } | undefined;
569
584
  tooltip?: {
570
585
  background?: string | undefined;
@@ -766,6 +781,9 @@ export declare const THEMES: {
766
781
  disabledOpacity?: number | undefined;
767
782
  } | undefined;
768
783
  continuous?: {
784
+ height?: number | undefined;
785
+ barHeight?: number | undefined;
786
+ barRadius?: number | undefined;
769
787
  handle?: {
770
788
  width?: string | undefined;
771
789
  height?: string | undefined;
@@ -801,6 +819,8 @@ export declare const THEMES: {
801
819
  xTickPadding?: number | undefined;
802
820
  yTickPadding?: number | undefined;
803
821
  grid?: boolean | undefined;
822
+ locale?: string | undefined;
823
+ compactDisplay?: "short" | "long" | undefined;
804
824
  } | undefined;
805
825
  tooltip?: {
806
826
  background?: string | undefined;
@@ -30,6 +30,7 @@
30
30
  x,
31
31
  y,
32
32
  coordinates = undefined,
33
+ locale = undefined,
33
34
  data,
34
35
  containerEl,
35
36
  remountKey = undefined,
@@ -41,6 +42,8 @@
41
42
  x: SveltePlotProps['x'];
42
43
  y: SveltePlotProps['y'];
43
44
  coordinates?: CoordinatesConfig;
45
+ /** Forwarded verbatim to svelteplot's own `<Plot locale>`. */
46
+ locale?: SveltePlotProps['locale'];
44
47
  /** Fed to `resolveProjection`'s `domain: 'data'` sentinel and to `coordinates.camera`'s `{features}` target resolution. */
45
48
  data: TData[];
46
49
  containerEl?: HTMLDivElement;
@@ -149,6 +152,7 @@
149
152
  {margin}
150
153
  {x}
151
154
  {y}
155
+ {locale}
152
156
  projection={finalProjection as SveltePlotProps['projection']}
153
157
  >
154
158
  {#if cameraActive && resolvedCameraTarget}
@@ -8,6 +8,8 @@ declare function $$render<TData extends Record<string, unknown>>(): {
8
8
  x: false | import("svelteplot/types/data.js").RawValue[] | Partial<import("svelteplot/types/scale.js").XScaleOptions> | undefined;
9
9
  y: false | import("svelteplot/types/data.js").RawValue[] | Partial<import("svelteplot/types/scale.js").YScaleOptions> | undefined;
10
10
  coordinates?: CoordinatesConfig;
11
+ /** Forwarded verbatim to svelteplot's own `<Plot locale>`. */
12
+ locale?: string | undefined;
11
13
  /** Fed to `resolveProjection`'s `domain: 'data'` sentinel and to `coordinates.camera`'s `{features}` target resolution. */
12
14
  data: TData[];
13
15
  containerEl?: HTMLDivElement;
@@ -14,6 +14,23 @@
14
14
  const fmt = $derived(section.format ?? ((v: number) => String(v)));
15
15
  const isInteractive = $derived(section.interactive?.enabled ?? false);
16
16
 
17
+ // How far the default handle bleeds past its 0/1 track position, so the
18
+ // track can be inset from `barWidth`'s edges by that much.
19
+ let loHandleEl = $state<SVGRectElement>();
20
+ const handleBleed = $derived(
21
+ isInteractive && !section.interactive?.handle && loHandleEl
22
+ ? Math.ceil(loHandleEl.getBoundingClientRect().width / 2)
23
+ : 0,
24
+ );
25
+ const trackWidth = $derived(Math.max(0, barWidth - handleBleed * 2));
26
+ function trackX(t: number): number {
27
+ return handleBleed + t * trackWidth;
28
+ }
29
+
30
+ const svgHeight = $derived(cfg.legend.continuous.height);
31
+ const barHeight = $derived(cfg.legend.continuous.barHeight);
32
+ const barRadius = $derived(cfg.legend.continuous.barRadius);
33
+
17
34
  // Fill/opacity for the excluded-margin rects.
18
35
  const excludedStyle = $derived.by((): { fill: string; opacity: number } => {
19
36
  const d = section.interactive?.disabledStyle;
@@ -133,7 +150,7 @@
133
150
  target.setPointerCapture(event.pointerId);
134
151
  const startX = event.clientX;
135
152
  const startValue = which === 'lo' ? lo : hi;
136
- const valuePerPixel = (section.max - section.min) / barWidth;
153
+ const valuePerPixel = (section.max - section.min) / trackWidth;
137
154
 
138
155
  function onMove(e: PointerEvent): void {
139
156
  const next = startValue + (e.clientX - startX) * valuePerPixel;
@@ -173,12 +190,12 @@
173
190
  const labelsOverlap = $derived(
174
191
  isInteractive &&
175
192
  cfg.legend.continuous.label.overlap.enabled &&
176
- (hiT - loT) * barWidth - loLabelWidth - hiLabelWidth < cfg.legend.continuous.label.overlap.margin,
193
+ (hiT - loT) * trackWidth - loLabelWidth - hiLabelWidth < cfg.legend.continuous.label.overlap.margin,
177
194
  );
178
195
  </script>
179
196
 
180
197
  <div class="dct-continuous" style:width="{barWidth}px">
181
- <svg width={barWidth} height="20" style="overflow: visible">
198
+ <svg width={barWidth} height={svgHeight} style="overflow: visible">
182
199
  <defs>
183
200
  <linearGradient id={gradientId} x1="0" x2="1" y1="0" y2="0">
184
201
  {#each stops as s (s.offset)}
@@ -186,17 +203,17 @@
186
203
  {/each}
187
204
  </linearGradient>
188
205
  </defs>
189
- <rect width={barWidth} height="12" rx="2" fill="url(#{gradientId})" />
206
+ <rect x={handleBleed} width={trackWidth} height={barHeight} rx={barRadius} fill="url(#{gradientId})" />
190
207
  {#each tickPositions as tick (tick.v)}
191
- <rect class="dct-continuous__tick-mark" x={tick.t * barWidth} y="12" />
208
+ <rect class="dct-continuous__tick-mark" x={trackX(tick.t)} y={barHeight} />
192
209
  {/each}
193
210
 
194
211
  {#if isInteractive}
195
212
  {#if loT > 0}
196
- <rect x="0" y="0" width={loT * barWidth} height="12" rx="2" fill={excludedStyle.fill} fill-opacity={excludedStyle.opacity} />
213
+ <rect x={handleBleed} y="0" width={loT * trackWidth} height={barHeight} rx={barRadius} fill={excludedStyle.fill} fill-opacity={excludedStyle.opacity} />
197
214
  {/if}
198
215
  {#if hiT < 1}
199
- <rect x={hiT * barWidth} y="0" width={barWidth - hiT * barWidth} height="12" rx="2" fill={excludedStyle.fill} fill-opacity={excludedStyle.opacity} />
216
+ <rect x={trackX(hiT)} y="0" width={trackWidth - hiT * trackWidth} height={barHeight} rx={barRadius} fill={excludedStyle.fill} fill-opacity={excludedStyle.opacity} />
200
217
  {/if}
201
218
  {#if section.interactive?.handle}
202
219
  {@render section.interactive.handle({
@@ -216,8 +233,9 @@
216
233
  {:else}
217
234
  <rect
218
235
  class="dct-continuous__handle"
219
- x={loT * barWidth}
220
- y="6"
236
+ bind:this={loHandleEl}
237
+ x={trackX(loT)}
238
+ y={barHeight / 2}
221
239
  onpointerdown={startDrag('lo')}
222
240
  ondblclick={resetHandle('lo')}
223
241
  role="slider"
@@ -229,8 +247,8 @@
229
247
  />
230
248
  <rect
231
249
  class="dct-continuous__handle"
232
- x={hiT * barWidth}
233
- y="6"
250
+ x={trackX(hiT)}
251
+ y={barHeight / 2}
234
252
  onpointerdown={startDrag('hi')}
235
253
  ondblclick={resetHandle('hi')}
236
254
  role="slider"
@@ -251,17 +269,17 @@
251
269
  <span
252
270
  class="dct-continuous__tick dct-continuous__tick--handle-lo"
253
271
  class:dct-continuous__tick--vertical={labelsOverlap}
254
- style:left="{loT * barWidth}px"
272
+ style:left="{trackX(loT)}px"
255
273
  >{fmt(lo)}</span>
256
274
  <span
257
275
  class="dct-continuous__tick dct-continuous__tick--handle-hi"
258
276
  class:dct-continuous__tick--vertical={labelsOverlap}
259
- style:left="{hiT * barWidth}px"
277
+ style:left="{trackX(hiT)}px"
260
278
  >{fmt(hi)}</span>
261
279
  {:else}
262
280
  <span class="dct-continuous__edge dct-continuous__edge--start">{fmt(section.min)}</span>
263
281
  {#each tickPositions as tick (tick.v)}
264
- <span class="dct-continuous__tick" style:left="{tick.t * barWidth}px">{tick.label}</span>
282
+ <span class="dct-continuous__tick" style:left="{trackX(tick.t)}px">{tick.label}</span>
265
283
  {/each}
266
284
  <span class="dct-continuous__edge dct-continuous__edge--end">{fmt(section.max)}</span>
267
285
  {/if}
@@ -26,6 +26,7 @@
26
26
  const cfg = $derived(getConfiguration());
27
27
  const layout = $derived(section.layout ?? defaultItemsLayout);
28
28
  const isHorizontal = $derived(layout.type === 'horizontal');
29
+ const isFlex = $derived(layout.type === 'flex');
29
30
  const maxRows = $derived(layout.type === 'horizontal' ? Math.max(1, layout.maxRows ?? 1) : 1);
30
31
  const columns = $derived(section.columns ?? 1);
31
32
  const isInteractive = $derived(section.interactive?.enabled ?? false);
@@ -238,6 +239,12 @@
238
239
  )}
239
240
  {/if}
240
241
  </div>
242
+ {:else if isFlex}
243
+ <div class="dct-discrete dct-discrete--horizontal">
244
+ {#each section.items as item, i (item.name)}
245
+ {@render discreteItem(item, i)}
246
+ {/each}
247
+ </div>
241
248
  {:else}
242
249
  <div class="dct-discrete" style:grid-template-columns="repeat({columns}, max-content)">
243
250
  {#each section.items as item, i (item.name)}
@@ -3,7 +3,7 @@
3
3
  generics="TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData"
4
4
  >
5
5
  import type { ComponentProps, Snippet } from 'svelte';
6
- import { Plot, Pointer } from 'svelteplot';
6
+ import { Plot, Pointer, setPlotDefaults } from 'svelteplot';
7
7
  import { getConfiguration } from '../../configuration/config.svelte';
8
8
  import { AUTO_MARGIN_ESTIMATE, numericMargin } from './margins';
9
9
  import { buildScale } from '../scales/buildScale';
@@ -149,6 +149,9 @@
149
149
  } = $props();
150
150
 
151
151
  const cfg = $derived(getConfiguration());
152
+ setPlotDefaults({
153
+ numberFormat: { useGrouping: 'min2', compactDisplay: cfg.axis.compactDisplay }
154
+ });
152
155
 
153
156
  /**
154
157
  * Margins passed straight to `<Plot margin={…}>`. Each side defaults to
@@ -262,6 +265,7 @@
262
265
  x={resolvedX}
263
266
  y={resolvedY}
264
267
  {coordinates}
268
+ locale={cfg.axis.locale}
265
269
  {data}
266
270
  {containerEl}
267
271
  remountKey={marginRemountKey}
@@ -50,6 +50,12 @@ export type ChartConfig = {
50
50
  };
51
51
  /** Continuous section styling: the draggable lo/hi handles and the tick/edge labels. */
52
52
  continuous: {
53
+ /** Total height (px) of the bar's drawing area. */
54
+ height: number;
55
+ /** Height (px) of the gradient bar itself, centered within `height`. */
56
+ barHeight: number;
57
+ /** Corner radius (px) of the gradient bar and excluded-range overlays. */
58
+ barRadius: number;
53
59
  /** The draggable lo/hi handle. */
54
60
  handle: {
55
61
  width: string;
@@ -94,6 +100,8 @@ export type ChartConfig = {
94
100
  xTickPadding: number;
95
101
  yTickPadding: number;
96
102
  grid: boolean;
103
+ locale: string;
104
+ compactDisplay: 'short' | 'long';
97
105
  };
98
106
  tooltip: {
99
107
  background: string;
@@ -37,13 +37,16 @@ export type LegendItem = {
37
37
  /**
38
38
  * How a discrete section's items flow. `'vertical'` stacks them in a
39
39
  * `columns`-wide grid; `'horizontal'` flows them left to right, paginating
40
- * past `maxRows` (default 1) with prev/next arrows.
40
+ * past `maxRows` (default 1) with prev/next arrows; `'flex'` also flows them
41
+ * left to right, but wraps onto as many rows as it takes instead of paginating.
41
42
  */
42
43
  export type LegendItemsLayout = {
43
44
  type: 'vertical';
44
45
  } | {
45
46
  type: 'horizontal';
46
47
  maxRows?: number;
48
+ } | {
49
+ type: 'flex';
47
50
  };
48
51
  export type LegendNavDirection = 'prev' | 'next';
49
52
  export type LegendNavContext = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fundar/data-chart-telling",
3
- "version": "0.0.62",
3
+ "version": "0.0.64",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"