bo-grid 0.8.0 → 0.21.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 (48) hide show
  1. package/README.md +202 -9
  2. package/dist/bo-grid.FilterMenu-BHI6rILc.js +154 -0
  3. package/dist/bo-grid.ToolPanel-C3u-4YKc.js +34 -0
  4. package/dist/bo-grid.element-DPnHUXMa.js +6623 -0
  5. package/dist/bo-grid.element.js +4 -0
  6. package/dist/charts/BarChart.svelte +50 -0
  7. package/dist/charts/BarChart.svelte.d.ts +16 -0
  8. package/dist/charts/DonutChart.svelte +54 -0
  9. package/dist/charts/DonutChart.svelte.d.ts +18 -0
  10. package/dist/charts/Legend.svelte +47 -0
  11. package/dist/charts/Legend.svelte.d.ts +12 -0
  12. package/dist/charts/LineChart.svelte +59 -0
  13. package/dist/charts/LineChart.svelte.d.ts +14 -0
  14. package/dist/charts/StackedBarChart.svelte +56 -0
  15. package/dist/charts/StackedBarChart.svelte.d.ts +18 -0
  16. package/dist/charts/chart-math.d.ts +57 -0
  17. package/dist/charts/chart-math.js +174 -0
  18. package/dist/charts/index.d.ts +8 -0
  19. package/dist/charts/index.js +11 -0
  20. package/dist/charts/palette.d.ts +4 -0
  21. package/dist/charts/palette.js +14 -0
  22. package/dist/format/format.d.ts +6 -0
  23. package/dist/format/format.js +41 -0
  24. package/dist/grid/Cell.svelte +247 -8
  25. package/dist/grid/Cell.svelte.d.ts +6 -0
  26. package/dist/grid/FilterMenu.svelte +7 -0
  27. package/dist/grid/Grid.svelte +307 -85
  28. package/dist/grid/Grid.svelte.d.ts +19 -0
  29. package/dist/grid/GroupRow.svelte +5 -2
  30. package/dist/grid/Pager.svelte +4 -0
  31. package/dist/grid/RowMenu.svelte +65 -2
  32. package/dist/grid/ToolPanel.svelte +5 -0
  33. package/dist/grid/column.d.ts +133 -0
  34. package/dist/grid/column.js +133 -4
  35. package/dist/grid/colvirt.d.ts +15 -0
  36. package/dist/grid/colvirt.js +43 -0
  37. package/dist/grid/export.js +5 -2
  38. package/dist/grid/filtering.d.ts +5 -2
  39. package/dist/grid/filtering.js +5 -4
  40. package/dist/grid/grouping.d.ts +30 -0
  41. package/dist/grid/grouping.js +33 -0
  42. package/dist/grid/theme.d.ts +15 -0
  43. package/dist/grid/theme.js +78 -0
  44. package/dist/grid/tree.d.ts +19 -7
  45. package/dist/grid/tree.js +16 -11
  46. package/dist/index.d.ts +5 -4
  47. package/dist/index.js +2 -2
  48. package/package.json +12 -2
@@ -0,0 +1,4 @@
1
+ import { E as f } from "./bo-grid.element-DPnHUXMa.js";
2
+ export {
3
+ f as default
4
+ };
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import { barRects } from './chart-math';
3
+ import { CHART_COLOR } from './palette';
4
+
5
+ let {
6
+ data,
7
+ width = 120,
8
+ height = 36,
9
+ gap = 2,
10
+ color = CHART_COLOR,
11
+ radius = 1,
12
+ ariaLabel = 'Bar chart',
13
+ class: klass = '',
14
+ }: {
15
+ /** Series values; negative values draw below the zero axis. */
16
+ data: number[];
17
+ width?: number;
18
+ height?: number;
19
+ /** Gap between bars (px). */
20
+ gap?: number;
21
+ /** Bar colour (CSS colour or var). */
22
+ color?: string;
23
+ radius?: number;
24
+ ariaLabel?: string;
25
+ class?: string;
26
+ } = $props();
27
+
28
+ const bars = $derived(barRects(data, width, height, gap));
29
+ </script>
30
+
31
+ <svg
32
+ class="boc boc-bar {klass}"
33
+ viewBox="0 0 {width} {height}"
34
+ width={width}
35
+ height={height}
36
+ preserveAspectRatio="none"
37
+ role="img"
38
+ aria-label={ariaLabel}
39
+ >
40
+ {#each bars as b, i (i)}
41
+ <rect x={b.x} y={b.y} width={b.w} height={b.h} rx={radius} fill={color}><title>{data[i]}</title></rect>
42
+ {/each}
43
+ </svg>
44
+
45
+ <style>
46
+ .boc {
47
+ display: block;
48
+ max-width: 100%;
49
+ }
50
+ </style>
@@ -0,0 +1,16 @@
1
+ type $$ComponentProps = {
2
+ /** Series values; negative values draw below the zero axis. */
3
+ data: number[];
4
+ width?: number;
5
+ height?: number;
6
+ /** Gap between bars (px). */
7
+ gap?: number;
8
+ /** Bar colour (CSS colour or var). */
9
+ color?: string;
10
+ radius?: number;
11
+ ariaLabel?: string;
12
+ class?: string;
13
+ };
14
+ declare const BarChart: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type BarChart = ReturnType<typeof BarChart>;
16
+ export default BarChart;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { donutArcs } from './chart-math';
3
+ import { CHART_PALETTE } from './palette';
4
+
5
+ type Slice = { value: number; color?: string; label?: string };
6
+
7
+ let {
8
+ data,
9
+ size = 120,
10
+ thickness = 18,
11
+ colors = CHART_PALETTE,
12
+ ariaLabel = 'Donut chart',
13
+ class: klass = '',
14
+ }: {
15
+ /** Plain values, or `{ value, color?, label? }` objects. */
16
+ data: number[] | Slice[];
17
+ size?: number;
18
+ /** Ring thickness (px); `>= size/2` renders a full pie. */
19
+ thickness?: number;
20
+ colors?: readonly string[];
21
+ ariaLabel?: string;
22
+ class?: string;
23
+ } = $props();
24
+
25
+ const values = $derived(data.map((d) => (typeof d === 'number' ? d : d.value)));
26
+ const arcs = $derived(donutArcs(values, size, thickness));
27
+ function colorOf(i: number): string {
28
+ const d = data[i];
29
+ if (typeof d !== 'number' && d.color) return d.color;
30
+ return colors[i % colors.length];
31
+ }
32
+ </script>
33
+
34
+ <svg
35
+ class="boc boc-donut {klass}"
36
+ viewBox="0 0 {size} {size}"
37
+ width={size}
38
+ height={size}
39
+ role="img"
40
+ aria-label={ariaLabel}
41
+ >
42
+ {#each arcs as a (a.index)}
43
+ {@const d = data[a.index]}
44
+ <path d={a.d} fill={colorOf(a.index)} fill-rule="evenodd">
45
+ <title>{typeof d !== 'number' && d.label ? `${d.label}: ` : ''}{a.value}</title>
46
+ </path>
47
+ {/each}
48
+ </svg>
49
+
50
+ <style>
51
+ .boc {
52
+ display: block;
53
+ }
54
+ </style>
@@ -0,0 +1,18 @@
1
+ type Slice = {
2
+ value: number;
3
+ color?: string;
4
+ label?: string;
5
+ };
6
+ type $$ComponentProps = {
7
+ /** Plain values, or `{ value, color?, label? }` objects. */
8
+ data: number[] | Slice[];
9
+ size?: number;
10
+ /** Ring thickness (px); `>= size/2` renders a full pie. */
11
+ thickness?: number;
12
+ colors?: readonly string[];
13
+ ariaLabel?: string;
14
+ class?: string;
15
+ };
16
+ declare const DonutChart: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type DonutChart = ReturnType<typeof DonutChart>;
18
+ export default DonutChart;
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { CHART_PALETTE } from './palette';
3
+
4
+ let {
5
+ items,
6
+ colors = CHART_PALETTE,
7
+ class: klass = '',
8
+ }: {
9
+ /** Legend entries; `color` defaults to the palette by position. */
10
+ items: { label: string; color?: string }[];
11
+ colors?: readonly string[];
12
+ class?: string;
13
+ } = $props();
14
+ </script>
15
+
16
+ <ul class="boc-legend {klass}">
17
+ {#each items as item, i (item.label + i)}
18
+ <li>
19
+ <span class="boc-legend-dot" style="background:{item.color ?? colors[i % colors.length]}"></span>
20
+ {item.label}
21
+ </li>
22
+ {/each}
23
+ </ul>
24
+
25
+ <style>
26
+ .boc-legend {
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ gap: 4px 14px;
30
+ margin: 0;
31
+ padding: 0;
32
+ list-style: none;
33
+ font-size: 11px;
34
+ color: var(--boc-legend-color, inherit);
35
+ }
36
+ .boc-legend li {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 6px;
40
+ }
41
+ .boc-legend-dot {
42
+ flex: none;
43
+ width: 9px;
44
+ height: 9px;
45
+ border-radius: 2px;
46
+ }
47
+ </style>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ /** Legend entries; `color` defaults to the palette by position. */
3
+ items: {
4
+ label: string;
5
+ color?: string;
6
+ }[];
7
+ colors?: readonly string[];
8
+ class?: string;
9
+ };
10
+ declare const Legend: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Legend = ReturnType<typeof Legend>;
12
+ export default Legend;
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import { linePoints, linePath, areaPath } from './chart-math';
3
+ import { CHART_COLOR } from './palette';
4
+
5
+ let {
6
+ data,
7
+ width = 120,
8
+ height = 36,
9
+ color = CHART_COLOR,
10
+ area = false,
11
+ strokeWidth = 1.5,
12
+ ariaLabel = 'Line chart',
13
+ class: klass = '',
14
+ }: {
15
+ data: number[];
16
+ width?: number;
17
+ height?: number;
18
+ color?: string;
19
+ /** Fill the area under the line (a soft tint of `color`). */
20
+ area?: boolean;
21
+ strokeWidth?: number;
22
+ ariaLabel?: string;
23
+ class?: string;
24
+ } = $props();
25
+
26
+ const points = $derived(linePoints(data, width, height, strokeWidth));
27
+ const d = $derived(linePath(points));
28
+ const fillD = $derived(area ? areaPath(points, height - strokeWidth) : '');
29
+ </script>
30
+
31
+ <svg
32
+ class="boc boc-line {klass}"
33
+ viewBox="0 0 {width} {height}"
34
+ width={width}
35
+ height={height}
36
+ preserveAspectRatio="none"
37
+ role="img"
38
+ aria-label={ariaLabel}
39
+ >
40
+ {#if area && fillD}
41
+ <path d={fillD} fill={color} fill-opacity="0.14" stroke="none" />
42
+ {/if}
43
+ <path
44
+ d={d}
45
+ fill="none"
46
+ stroke={color}
47
+ stroke-width={strokeWidth}
48
+ stroke-linejoin="round"
49
+ stroke-linecap="round"
50
+ vector-effect="non-scaling-stroke"
51
+ />
52
+ </svg>
53
+
54
+ <style>
55
+ .boc {
56
+ display: block;
57
+ max-width: 100%;
58
+ }
59
+ </style>
@@ -0,0 +1,14 @@
1
+ type $$ComponentProps = {
2
+ data: number[];
3
+ width?: number;
4
+ height?: number;
5
+ color?: string;
6
+ /** Fill the area under the line (a soft tint of `color`). */
7
+ area?: boolean;
8
+ strokeWidth?: number;
9
+ ariaLabel?: string;
10
+ class?: string;
11
+ };
12
+ declare const LineChart: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type LineChart = ReturnType<typeof LineChart>;
14
+ export default LineChart;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { stackedBars, groupedBars } from './chart-math';
3
+ import { CHART_PALETTE } from './palette';
4
+
5
+ let {
6
+ data,
7
+ width = 160,
8
+ height = 60,
9
+ gap = 3,
10
+ grouped = false,
11
+ colors = CHART_PALETTE,
12
+ seriesLabels = [],
13
+ radius = 1,
14
+ ariaLabel = 'Bar chart',
15
+ class: klass = '',
16
+ }: {
17
+ /** Series of category values: `data[series][category]`. */
18
+ data: number[][];
19
+ width?: number;
20
+ height?: number;
21
+ gap?: number;
22
+ /** Side-by-side bars per category instead of a stack. */
23
+ grouped?: boolean;
24
+ colors?: readonly string[];
25
+ /** Optional series names — shown in each segment's hover tooltip. */
26
+ seriesLabels?: string[];
27
+ radius?: number;
28
+ ariaLabel?: string;
29
+ class?: string;
30
+ } = $props();
31
+
32
+ const segs = $derived(grouped ? groupedBars(data, width, height, gap) : stackedBars(data, width, height, gap));
33
+ </script>
34
+
35
+ <svg
36
+ class="boc boc-stacked {klass}"
37
+ viewBox="0 0 {width} {height}"
38
+ width={width}
39
+ height={height}
40
+ preserveAspectRatio="none"
41
+ role="img"
42
+ aria-label={ariaLabel}
43
+ >
44
+ {#each segs as seg, i (i)}
45
+ <rect x={seg.x} y={seg.y} width={seg.w} height={seg.h} rx={radius} fill={colors[seg.series % colors.length]}>
46
+ <title>{seriesLabels[seg.series] ? `${seriesLabels[seg.series]}: ` : ''}{seg.value}</title>
47
+ </rect>
48
+ {/each}
49
+ </svg>
50
+
51
+ <style>
52
+ .boc {
53
+ display: block;
54
+ max-width: 100%;
55
+ }
56
+ </style>
@@ -0,0 +1,18 @@
1
+ type $$ComponentProps = {
2
+ /** Series of category values: `data[series][category]`. */
3
+ data: number[][];
4
+ width?: number;
5
+ height?: number;
6
+ gap?: number;
7
+ /** Side-by-side bars per category instead of a stack. */
8
+ grouped?: boolean;
9
+ colors?: readonly string[];
10
+ /** Optional series names — shown in each segment's hover tooltip. */
11
+ seriesLabels?: string[];
12
+ radius?: number;
13
+ ariaLabel?: string;
14
+ class?: string;
15
+ };
16
+ declare const StackedBarChart: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type StackedBarChart = ReturnType<typeof StackedBarChart>;
18
+ export default StackedBarChart;
@@ -0,0 +1,57 @@
1
+ export interface Point {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export interface Rect {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ }
11
+ export interface Arc {
12
+ /** SVG path `d` for the slice. */
13
+ d: string;
14
+ value: number;
15
+ index: number;
16
+ /** Share of the (positive) total, 0..1. */
17
+ fraction: number;
18
+ }
19
+ /** Data extent, guarding empty/constant series. `baseline`, when given, is always
20
+ included (e.g. 0 so bars share a zero axis). */
21
+ export declare function extent(values: readonly number[], baseline?: number): {
22
+ min: number;
23
+ max: number;
24
+ };
25
+ /** Map a series to SVG points inside the [pad..w-pad] × [pad..h-pad] box (y down,
26
+ so larger values sit higher). Pass an explicit `range` to share a scale. */
27
+ export declare function linePoints(values: readonly number[], w: number, h: number, pad?: number, range?: {
28
+ min: number;
29
+ max: number;
30
+ }): Point[];
31
+ /** SVG path `d` for a polyline through the points. */
32
+ export declare function linePath(points: readonly Point[]): string;
33
+ /** SVG path `d` for the filled area under the polyline, down to `baselineY`. */
34
+ export declare function areaPath(points: readonly Point[], baselineY: number): string;
35
+ /** Bar rectangles across the width, from a shared zero baseline (signed-aware:
36
+ negatives extend below the axis). */
37
+ export declare function barRects(values: readonly number[], w: number, h: number, gap?: number, pad?: number): Rect[];
38
+ /** One bar segment of a multi-series (stacked/grouped) chart. */
39
+ export interface BarSeg extends Rect {
40
+ series: number;
41
+ category: number;
42
+ value: number;
43
+ }
44
+ /**
45
+ * Stacked bars: `series[s][c]` is series `s`'s value in category `c`. Each
46
+ * category is a vertical stack scaled to the largest category total. Non-positive
47
+ * values are clamped to 0 (stacking).
48
+ */
49
+ export declare function stackedBars(series: readonly (readonly number[])[], w: number, h: number, gap?: number, pad?: number): BarSeg[];
50
+ /**
51
+ * Grouped bars: same data shape as `stackedBars`, but each category's series are
52
+ * drawn side by side, scaled to the largest single value.
53
+ */
54
+ export declare function groupedBars(series: readonly (readonly number[])[], w: number, h: number, gap?: number, innerGap?: number, pad?: number): BarSeg[];
55
+ /** Donut/pie arc paths. `thickness >= size/2` gives a full pie. Slices start at
56
+ 12 o'clock and run clockwise; non-positive values are skipped. */
57
+ export declare function donutArcs(values: readonly number[], size: number, thickness: number): Arc[];
@@ -0,0 +1,174 @@
1
+ // Pure SVG geometry for the bo-grid/charts companion. No dependencies; every
2
+ // chart component is a thin SVG wrapper over these helpers, so the math is
3
+ // unit-tested in isolation and the components stay trivial.
4
+ const round = (n) => Math.round(n * 100) / 100;
5
+ /** Data extent, guarding empty/constant series. `baseline`, when given, is always
6
+ included (e.g. 0 so bars share a zero axis). */
7
+ export function extent(values, baseline) {
8
+ let min = baseline ?? Infinity;
9
+ let max = baseline ?? -Infinity;
10
+ for (const v of values) {
11
+ if (!Number.isFinite(v))
12
+ continue;
13
+ if (v < min)
14
+ min = v;
15
+ if (v > max)
16
+ max = v;
17
+ }
18
+ if (!Number.isFinite(min))
19
+ min = 0;
20
+ if (!Number.isFinite(max))
21
+ max = min;
22
+ if (min === max)
23
+ max = min + 1; // avoid divide-by-zero on a flat series
24
+ return { min, max };
25
+ }
26
+ /** Map a series to SVG points inside the [pad..w-pad] × [pad..h-pad] box (y down,
27
+ so larger values sit higher). Pass an explicit `range` to share a scale. */
28
+ export function linePoints(values, w, h, pad = 1, range) {
29
+ if (values.length === 0)
30
+ return [];
31
+ const { min, max } = range ?? extent(values);
32
+ const span = max - min || 1;
33
+ const iw = w - pad * 2;
34
+ const ih = h - pad * 2;
35
+ const step = values.length > 1 ? iw / (values.length - 1) : 0;
36
+ return values.map((v, i) => ({
37
+ x: round(pad + i * step),
38
+ y: round(pad + ih - ((v - min) / span) * ih),
39
+ }));
40
+ }
41
+ /** SVG path `d` for a polyline through the points. */
42
+ export function linePath(points) {
43
+ return points.map((p, i) => `${i ? 'L' : 'M'}${round(p.x)} ${round(p.y)}`).join(' ');
44
+ }
45
+ /** SVG path `d` for the filled area under the polyline, down to `baselineY`. */
46
+ export function areaPath(points, baselineY) {
47
+ if (points.length === 0)
48
+ return '';
49
+ const a = points[0];
50
+ const b = points[points.length - 1];
51
+ return `${linePath(points)} L${round(b.x)} ${round(baselineY)} L${round(a.x)} ${round(baselineY)} Z`;
52
+ }
53
+ /** Bar rectangles across the width, from a shared zero baseline (signed-aware:
54
+ negatives extend below the axis). */
55
+ export function barRects(values, w, h, gap = 2, pad = 1) {
56
+ if (values.length === 0)
57
+ return [];
58
+ const { min, max } = extent(values, 0);
59
+ const span = max - min || 1;
60
+ const iw = w - pad * 2;
61
+ const ih = h - pad * 2;
62
+ const bw = Math.max(0, (iw - gap * (values.length - 1)) / values.length);
63
+ const yOf = (v) => pad + ih - ((v - min) / span) * ih;
64
+ const zeroY = yOf(0);
65
+ return values.map((v, i) => {
66
+ const vy = yOf(Number.isFinite(v) ? v : 0);
67
+ return { x: round(pad + i * (bw + gap)), y: round(Math.min(vy, zeroY)), w: round(bw), h: round(Math.abs(vy - zeroY)) };
68
+ });
69
+ }
70
+ const polar = (cx, cy, r, a) => [
71
+ round(cx + r * Math.cos(a)),
72
+ round(cy + r * Math.sin(a)),
73
+ ];
74
+ /** One donut slice (outer arc out, radial in, inner arc back). */
75
+ function ringArc(cx, cy, r, ir, a0, a1) {
76
+ const large = a1 - a0 > Math.PI ? 1 : 0;
77
+ const [x0, y0] = polar(cx, cy, r, a0);
78
+ const [x1, y1] = polar(cx, cy, r, a1);
79
+ const [xi1, yi1] = polar(cx, cy, ir, a1);
80
+ const [xi0, yi0] = polar(cx, cy, ir, a0);
81
+ return `M${x0} ${y0} A${round(r)} ${round(r)} 0 ${large} 1 ${x1} ${y1} L${xi1} ${yi1} A${round(ir)} ${round(ir)} 0 ${large} 0 ${xi0} ${yi0} Z`;
82
+ }
83
+ /** A complete ring (used when one slice is 100% — a single 360° arc degenerates).
84
+ Outer circle CW + inner circle CCW; render with `fill-rule="evenodd"`. */
85
+ function fullRing(cx, cy, r, ir) {
86
+ return (`M${round(cx - r)} ${round(cy)} A${round(r)} ${round(r)} 0 1 1 ${round(cx + r)} ${round(cy)} ` +
87
+ `A${round(r)} ${round(r)} 0 1 1 ${round(cx - r)} ${round(cy)} Z ` +
88
+ `M${round(cx - ir)} ${round(cy)} A${round(ir)} ${round(ir)} 0 1 0 ${round(cx + ir)} ${round(cy)} ` +
89
+ `A${round(ir)} ${round(ir)} 0 1 0 ${round(cx - ir)} ${round(cy)} Z`);
90
+ }
91
+ const nCategories = (series) => series.reduce((m, s) => Math.max(m, s.length), 0);
92
+ /**
93
+ * Stacked bars: `series[s][c]` is series `s`'s value in category `c`. Each
94
+ * category is a vertical stack scaled to the largest category total. Non-positive
95
+ * values are clamped to 0 (stacking).
96
+ */
97
+ export function stackedBars(series, w, h, gap = 2, pad = 1) {
98
+ const nCat = nCategories(series);
99
+ if (nCat === 0)
100
+ return [];
101
+ const totals = Array.from({ length: nCat }, (_, c) => series.reduce((a, s) => a + Math.max(0, s[c] ?? 0), 0));
102
+ const max = Math.max(1, ...totals);
103
+ const iw = w - pad * 2;
104
+ const ih = h - pad * 2;
105
+ const bw = Math.max(0, (iw - gap * (nCat - 1)) / nCat);
106
+ const segs = [];
107
+ for (let c = 0; c < nCat; c++) {
108
+ const x = pad + c * (bw + gap);
109
+ let yBottom = pad + ih;
110
+ for (let s = 0; s < series.length; s++) {
111
+ const v = Math.max(0, series[s][c] ?? 0);
112
+ const segH = (v / max) * ih;
113
+ yBottom -= segH;
114
+ segs.push({ x: round(x), y: round(yBottom), w: round(bw), h: round(segH), series: s, category: c, value: series[s][c] ?? 0 });
115
+ }
116
+ }
117
+ return segs;
118
+ }
119
+ /**
120
+ * Grouped bars: same data shape as `stackedBars`, but each category's series are
121
+ * drawn side by side, scaled to the largest single value.
122
+ */
123
+ export function groupedBars(series, w, h, gap = 4, innerGap = 1, pad = 1) {
124
+ const nCat = nCategories(series);
125
+ const nSer = series.length;
126
+ if (nCat === 0)
127
+ return [];
128
+ let max = 1;
129
+ for (const s of series)
130
+ for (const v of s)
131
+ max = Math.max(max, v);
132
+ const iw = w - pad * 2;
133
+ const ih = h - pad * 2;
134
+ const groupW = (iw - gap * (nCat - 1)) / nCat;
135
+ const bw = Math.max(0, (groupW - innerGap * (nSer - 1)) / nSer);
136
+ const segs = [];
137
+ for (let c = 0; c < nCat; c++) {
138
+ const gx = pad + c * (groupW + gap);
139
+ for (let s = 0; s < nSer; s++) {
140
+ const v = Math.max(0, series[s][c] ?? 0);
141
+ const segH = (v / max) * ih;
142
+ segs.push({ x: round(gx + s * (bw + innerGap)), y: round(pad + ih - segH), w: round(bw), h: round(segH), series: s, category: c, value: series[s][c] ?? 0 });
143
+ }
144
+ }
145
+ return segs;
146
+ }
147
+ /** Donut/pie arc paths. `thickness >= size/2` gives a full pie. Slices start at
148
+ 12 o'clock and run clockwise; non-positive values are skipped. */
149
+ export function donutArcs(values, size, thickness) {
150
+ const pos = values.map((v) => (Number.isFinite(v) && v > 0 ? v : 0));
151
+ const total = pos.reduce((a, b) => a + b, 0);
152
+ if (total <= 0)
153
+ return [];
154
+ const r = size / 2;
155
+ const ir = Math.max(0, r - thickness);
156
+ const cx = r;
157
+ const cy = r;
158
+ let a0 = -Math.PI / 2;
159
+ const arcs = [];
160
+ pos.forEach((v, index) => {
161
+ if (v <= 0)
162
+ return;
163
+ const fraction = v / total;
164
+ const a1 = a0 + fraction * Math.PI * 2;
165
+ arcs.push({
166
+ d: fraction >= 1 ? fullRing(cx, cy, r, ir) : ringArc(cx, cy, r, ir, a0, a1),
167
+ value: values[index],
168
+ index,
169
+ fraction,
170
+ });
171
+ a0 = a1;
172
+ });
173
+ return arcs;
174
+ }
@@ -0,0 +1,8 @@
1
+ export { default as BarChart } from './BarChart.svelte';
2
+ export { default as LineChart } from './LineChart.svelte';
3
+ export { default as DonutChart } from './DonutChart.svelte';
4
+ export { default as StackedBarChart } from './StackedBarChart.svelte';
5
+ export { default as Legend } from './Legend.svelte';
6
+ export { CHART_PALETTE, CHART_COLOR } from './palette';
7
+ export { extent, linePoints, linePath, areaPath, barRects, stackedBars, groupedBars, donutArcs } from './chart-math';
8
+ export type { Point, Rect, Arc, BarSeg } from './chart-math';
@@ -0,0 +1,11 @@
1
+ // Public API for the optional `bo-grid/charts` companion — tiny, dependency-free
2
+ // SVG charts for dashboards. Imported via the subpath so the grid core stays
3
+ // untouched: `import { LineChart } from 'bo-grid/charts';`
4
+ export { default as BarChart } from './BarChart.svelte';
5
+ export { default as LineChart } from './LineChart.svelte';
6
+ export { default as DonutChart } from './DonutChart.svelte';
7
+ export { default as StackedBarChart } from './StackedBarChart.svelte';
8
+ export { default as Legend } from './Legend.svelte';
9
+ export { CHART_PALETTE, CHART_COLOR } from './palette';
10
+ // Geometry helpers (for building your own SVG charts).
11
+ export { extent, linePoints, linePath, areaPath, barRects, stackedBars, groupedBars, donutArcs } from './chart-math';
@@ -0,0 +1,4 @@
1
+ export declare const CHART_PALETTE: readonly string[];
2
+ /** A single-series default colour (line/bar). Override via `--boc-color` or the
3
+ component's `color` prop. */
4
+ export declare const CHART_COLOR = "var(--boc-color, #6366f1)";
@@ -0,0 +1,14 @@
1
+ // Default categorical palette for multi-series charts (donut slices, grouped
2
+ // bars). Each entry is a CSS var with a fallback, so a consumer can re-theme the
3
+ // whole set by defining `--boc-1`…`--boc-6` on any ancestor — or pass `colors`.
4
+ export const CHART_PALETTE = [
5
+ 'var(--boc-1, #6366f1)',
6
+ 'var(--boc-2, #10b981)',
7
+ 'var(--boc-3, #f59e0b)',
8
+ 'var(--boc-4, #ef4444)',
9
+ 'var(--boc-5, #06b6d4)',
10
+ 'var(--boc-6, #a855f7)',
11
+ ];
12
+ /** A single-series default colour (line/bar). Override via `--boc-color` or the
13
+ component's `color` prop. */
14
+ export const CHART_COLOR = 'var(--boc-color, #6366f1)';
@@ -3,3 +3,9 @@ export declare function fmtPercent(v: number): string;
3
3
  export declare function fmtVolume(v: number): string;
4
4
  export type DateStyle = 'short' | 'medium';
5
5
  export declare function fmtDate(ms: number, style?: DateStyle): string;
6
+ /** Localized currency (e.g. `$1,234.50`). Falls back to a fixed-decimal number
7
+ if the ISO `currency` code is unsupported. */
8
+ export declare function fmtCurrency(v: number, currency?: string, locale?: string, decimals?: number): string;
9
+ /** Human relative time vs `now` (default: real now) — e.g. `3 hours ago`,
10
+ `in 2 days`. `ms` is an epoch timestamp. Deterministic when `now` is passed. */
11
+ export declare function relativeTime(ms: number, now?: number): string;