@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 +8 -1
- package/dist/configuration/config.svelte.js +11 -2
- package/dist/configuration/themes/index.d.ts +20 -0
- package/dist/layout/coordinates/CoordinatesLayout.svelte +4 -0
- package/dist/layout/coordinates/CoordinatesLayout.svelte.d.ts +2 -0
- package/dist/layout/legend/ContinuousSection.svelte +32 -14
- package/dist/layout/legend/DiscreteSection.svelte +7 -0
- package/dist/layout/plot/BasePlotLayout.svelte +5 -1
- package/dist/types/configuration/styling.d.ts +8 -0
- package/dist/types/layout/legend.d.ts +4 -1
- package/package.json +1 -1
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
|
|
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) /
|
|
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) *
|
|
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=
|
|
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={
|
|
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
|
|
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=
|
|
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
|
|
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
|
-
|
|
220
|
-
|
|
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
|
|
233
|
-
y=
|
|
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
|
|
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
|
|
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
|
|
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 = {
|