@juspay/svelte-ui-components 4.21.0 → 4.23.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.
- package/dist/AreaChart/AreaChart.svelte +10 -1
- package/dist/AreaChart/properties.d.ts +15 -0
- package/dist/BarChart/BarChart.svelte +15 -2
- package/dist/BarChart/properties.d.ts +16 -0
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +12 -1
- package/dist/DualAxisBarChart/properties.d.ts +15 -0
- package/dist/LineChart/LineChart.svelte +7 -1
- package/dist/LineChart/properties.d.ts +14 -0
- package/dist/Table/Table.svelte +8 -1
- package/dist/_chart/Legend.svelte +41 -13
- package/dist/_chart/aggregate.d.ts +35 -0
- package/dist/_chart/aggregate.js +68 -0
- package/dist/_chart/types.d.ts +8 -0
- package/dist-wc/index.js +104 -89
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { measureText } from '../_chart/measure';
|
|
14
14
|
import { resolvePointLabels } from '../_chart/labels';
|
|
15
15
|
import { pointerPositionIn, dismissOnOutsidePointerDown } from '../_chart/interactions';
|
|
16
|
+
import { formatSeriesAggregate } from '../_chart/aggregate';
|
|
16
17
|
import type { LegendItem, Point } from '../_chart/types';
|
|
17
18
|
import { DEFAULT_CHART_MAX_HEIGHT } from '../_chart/types';
|
|
18
19
|
|
|
@@ -171,7 +172,15 @@
|
|
|
171
172
|
});
|
|
172
173
|
|
|
173
174
|
let legendItems = $derived<LegendItem[]>(
|
|
174
|
-
series.map((s, i) => ({
|
|
175
|
+
series.map((s, i) => ({
|
|
176
|
+
label: s.name,
|
|
177
|
+
color: s.color ?? getColor(i),
|
|
178
|
+
aggregateLabel: formatSeriesAggregate(
|
|
179
|
+
s.data.map((d) => d.y),
|
|
180
|
+
s.aggregate ?? 'none',
|
|
181
|
+
s.aggregateFormat ?? yTickFormat ?? formatNumber
|
|
182
|
+
)
|
|
183
|
+
}))
|
|
175
184
|
);
|
|
176
185
|
|
|
177
186
|
// ── Tooltip ────────────────────────────────────────────────────
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { CurveType } from '../_chart/types';
|
|
3
|
+
import type { SeriesAggregate } from '../_chart/aggregate';
|
|
3
4
|
export type AreaChartDataPoint = {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -9,6 +10,20 @@ export type AreaChartSeries = {
|
|
|
9
10
|
name: string;
|
|
10
11
|
data: AreaChartDataPoint[];
|
|
11
12
|
color?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Shows an aggregate of this series' `y` values beside its legend label
|
|
15
|
+
* (e.g. "Revenue $128K"). Aggregates that series' own values only -- when
|
|
16
|
+
* `stacked` is true this is never the per-category stack total, which is a
|
|
17
|
+
* separate, existing computation. Omit (or `'none'`, the default) to show
|
|
18
|
+
* no aggregate; every chart renders unchanged without it.
|
|
19
|
+
*/
|
|
20
|
+
aggregate?: SeriesAggregate;
|
|
21
|
+
/**
|
|
22
|
+
* Formatter for this series' legend aggregate. Defaults to the chart's own
|
|
23
|
+
* `yTickFormat` (or the built-in number formatter) -- set this only when
|
|
24
|
+
* the aggregate needs a different unit than the axis.
|
|
25
|
+
*/
|
|
26
|
+
aggregateFormat?: (value: number) => string;
|
|
12
27
|
};
|
|
13
28
|
export type AreaChartTooltipContext = {
|
|
14
29
|
x: number;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
placedLabelRect,
|
|
30
30
|
dropOverlapping
|
|
31
31
|
} from '../_chart/labels';
|
|
32
|
+
import { formatSeriesAggregate } from '../_chart/aggregate';
|
|
32
33
|
import type { LegendItem, BarRect } from '../_chart/types';
|
|
33
34
|
import { DEFAULT_CHART_CORNER_RADIUS, DEFAULT_CHART_MAX_HEIGHT } from '../_chart/types';
|
|
34
35
|
import type { TooltipAnchor } from '../_chart/types';
|
|
@@ -583,10 +584,22 @@
|
|
|
583
584
|
|
|
584
585
|
let legendItems = $derived<LegendItem[]>(
|
|
585
586
|
isMulti
|
|
586
|
-
?
|
|
587
|
+
? rawSeries.map((s, i) => ({
|
|
587
588
|
label: s.name,
|
|
588
589
|
color: fallbackColor(s.color ?? getColor(i), i),
|
|
589
|
-
hidden: hiddenSeries.has(i)
|
|
590
|
+
hidden: hiddenSeries.has(i),
|
|
591
|
+
// Aggregates this series' own raw `value`s, from `rawSeries` rather
|
|
592
|
+
// than `resolvedSeries`: the latter may hold normaliseToFirstPoint
|
|
593
|
+
// percentages or topN-clipped/overflow values, neither of which is
|
|
594
|
+
// the series' real data (never the cross-series stack total either,
|
|
595
|
+
// which is an unrelated existing computation). Defaults to the
|
|
596
|
+
// chart's own `valueFormat`/number formatter, matching the "reuse
|
|
597
|
+
// the axis/tooltip formatting mechanism" rule.
|
|
598
|
+
aggregateLabel: formatSeriesAggregate(
|
|
599
|
+
s.data.map((d) => d.value),
|
|
600
|
+
s.aggregate ?? 'none',
|
|
601
|
+
s.aggregateFormat ?? format
|
|
602
|
+
)
|
|
590
603
|
}))
|
|
591
604
|
: []
|
|
592
605
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { ChartHighlightAPI } from '../_chart/highlight';
|
|
3
|
+
import type { SeriesAggregate } from '../_chart/aggregate';
|
|
3
4
|
export type BarFillPattern = {
|
|
4
5
|
pattern: {
|
|
5
6
|
/** SVG pattern element type: 'lines' | 'dots' | 'crosshatch' */
|
|
@@ -52,6 +53,21 @@ export type BarChartSeries = {
|
|
|
52
53
|
data: BarChartDataPoint[];
|
|
53
54
|
/** Series-level fill: plain color, pattern, or gradient. */
|
|
54
55
|
color?: BarFill;
|
|
56
|
+
/**
|
|
57
|
+
* Shows an aggregate of this series' `value`s beside its legend label (e.g.
|
|
58
|
+
* "Revenue $128K"). Aggregates that series' own values only -- in
|
|
59
|
+
* `groupMode="stacked"` this is never the per-category stack total, which
|
|
60
|
+
* is a separate, existing computation. Omit (or `'none'`, the default) to
|
|
61
|
+
* show no aggregate; every chart renders unchanged without it.
|
|
62
|
+
*/
|
|
63
|
+
aggregate?: SeriesAggregate;
|
|
64
|
+
/**
|
|
65
|
+
* Formatter for this series' legend aggregate. Defaults to the chart's own
|
|
66
|
+
* `valueFormat` (or the built-in number formatter) -- set this only when
|
|
67
|
+
* the aggregate needs a different unit than the axis, e.g. a percentage
|
|
68
|
+
* average alongside a currency-formatted axis.
|
|
69
|
+
*/
|
|
70
|
+
aggregateFormat?: (value: number) => string;
|
|
55
71
|
};
|
|
56
72
|
/**
|
|
57
73
|
* Geometry for a single rendered bar, in inner (post-margin) coordinates — the
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { getColor } from '../_chart/colors';
|
|
19
19
|
import { formatNumber } from '../_chart/format';
|
|
20
20
|
import { roundedRectPath, linePath } from '../_chart/paths';
|
|
21
|
+
import { formatSeriesAggregate } from '../_chart/aggregate';
|
|
21
22
|
import type {
|
|
22
23
|
LegendItem,
|
|
23
24
|
TooltipData,
|
|
@@ -280,7 +281,17 @@
|
|
|
280
281
|
series.map((s, si) => ({
|
|
281
282
|
label: s.name,
|
|
282
283
|
color: resolvedColor(s, si),
|
|
283
|
-
hidden: hiddenSeries.has(si)
|
|
284
|
+
hidden: hiddenSeries.has(si),
|
|
285
|
+
// Defaults to the same per-axis formatter (leftFormat/rightFormat, used
|
|
286
|
+
// for ticks/tooltip above) as whichever axis this series is plotted
|
|
287
|
+
// against, so a left-axis currency series and a right-axis percentage
|
|
288
|
+
// series each get a sensible default without either specifying
|
|
289
|
+
// `aggregateFormat`.
|
|
290
|
+
aggregateLabel: formatSeriesAggregate(
|
|
291
|
+
s.data,
|
|
292
|
+
s.aggregate ?? 'none',
|
|
293
|
+
s.aggregateFormat ?? (s.yAxisIndex === 0 ? leftFormat : rightFormat)
|
|
294
|
+
)
|
|
284
295
|
}))
|
|
285
296
|
);
|
|
286
297
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { SeriesAggregate } from '../_chart/aggregate';
|
|
2
3
|
/**
|
|
3
4
|
* Configuration for one of the two independent Y-axes.
|
|
4
5
|
*/
|
|
@@ -37,6 +38,20 @@ export type DualAxisSeries = {
|
|
|
37
38
|
* `'line'` — line with optional dots drawn over the column layer.
|
|
38
39
|
*/
|
|
39
40
|
type?: 'column' | 'line';
|
|
41
|
+
/**
|
|
42
|
+
* Shows an aggregate of this series' values beside its legend label (e.g.
|
|
43
|
+
* "Revenue $128K"). Omit (or `'none'`, the default) to show no aggregate;
|
|
44
|
+
* every chart renders unchanged without it.
|
|
45
|
+
*/
|
|
46
|
+
aggregate?: SeriesAggregate;
|
|
47
|
+
/**
|
|
48
|
+
* Formatter for this series' legend aggregate. Defaults to the `valueFormat`
|
|
49
|
+
* of whichever axis (`leftAxis`/`rightAxis`) this series' `yAxisIndex`
|
|
50
|
+
* points at -- reusing the same per-axis formatting the chart already has,
|
|
51
|
+
* rather than a parallel mechanism. Set this only when the aggregate needs
|
|
52
|
+
* a different unit than its axis.
|
|
53
|
+
*/
|
|
54
|
+
aggregateFormat?: (value: number) => string;
|
|
40
55
|
};
|
|
41
56
|
/**
|
|
42
57
|
* Data passed to `tooltipSnippet` when the user hovers a category.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { formatNumber, defaultTickFormat } from '../_chart/format';
|
|
15
15
|
import { measureText } from '../_chart/measure';
|
|
16
16
|
import { resolvePointLabels } from '../_chart/labels';
|
|
17
|
+
import { formatSeriesAggregate } from '../_chart/aggregate';
|
|
17
18
|
import type { LegendItem, Point, TooltipAnchor } from '../_chart/types';
|
|
18
19
|
import { pointerPositionIn, dismissOnOutsidePointerDown } from '../_chart/interactions';
|
|
19
20
|
import { SvelteSet } from 'svelte/reactivity';
|
|
@@ -236,7 +237,12 @@
|
|
|
236
237
|
series.map((s, i) => ({
|
|
237
238
|
label: s.name,
|
|
238
239
|
color: s.color ?? getColor(i),
|
|
239
|
-
hidden: hiddenSeries.has(i)
|
|
240
|
+
hidden: hiddenSeries.has(i),
|
|
241
|
+
aggregateLabel: formatSeriesAggregate(
|
|
242
|
+
s.data.map((d) => d.y),
|
|
243
|
+
s.aggregate ?? 'none',
|
|
244
|
+
s.aggregateFormat ?? yTickFormat ?? formatNumber
|
|
245
|
+
)
|
|
240
246
|
}))
|
|
241
247
|
);
|
|
242
248
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { CurveType } from '../_chart/types';
|
|
3
3
|
import type { ChartHighlightAPI } from '../_chart/highlight';
|
|
4
|
+
import type { SeriesAggregate } from '../_chart/aggregate';
|
|
4
5
|
export type LineChartDataPoint = {
|
|
5
6
|
x: number;
|
|
6
7
|
y: number;
|
|
@@ -16,6 +17,19 @@ export type LineChartSeries = {
|
|
|
16
17
|
* custom SVG `stroke-dasharray`. Omit for a solid line.
|
|
17
18
|
*/
|
|
18
19
|
dash?: boolean | string;
|
|
20
|
+
/**
|
|
21
|
+
* Shows an aggregate of this series' `y` values beside its legend label
|
|
22
|
+
* (e.g. "Revenue $128K"). Omit (or `'none'`, the default) to show no
|
|
23
|
+
* aggregate; every chart renders unchanged without it.
|
|
24
|
+
*/
|
|
25
|
+
aggregate?: SeriesAggregate;
|
|
26
|
+
/**
|
|
27
|
+
* Formatter for this series' legend aggregate. Defaults to the chart's own
|
|
28
|
+
* `yTickFormat` (or the built-in number formatter) -- set this only when
|
|
29
|
+
* the aggregate needs a different unit than the axis, e.g. a percentage
|
|
30
|
+
* average alongside a count-formatted axis.
|
|
31
|
+
*/
|
|
32
|
+
aggregateFormat?: (value: number) => string;
|
|
19
33
|
};
|
|
20
34
|
export type LineChartTooltipContext = {
|
|
21
35
|
x: number;
|
package/dist/Table/Table.svelte
CHANGED
|
@@ -1188,11 +1188,18 @@
|
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
1190
|
/* ── Container ──────────────────────────────────────────────────────────── */
|
|
1191
|
+
/* `hidden` is the default because it is what clips the table's corners to
|
|
1192
|
+
this element's border-radius; without it the first and last cells square
|
|
1193
|
+
off over the rounded border. A consumer who needs the container itself to
|
|
1194
|
+
scroll sideways sets this to `auto` and accepts that trade. Note the
|
|
1195
|
+
scrollable variant below sets `overflow-y`, and per the CSS overflow spec
|
|
1196
|
+
a `visible` on one axis computes to `auto` when the other is not visible,
|
|
1197
|
+
so `visible` here does not survive on a scrollable table. */
|
|
1191
1198
|
.table-container {
|
|
1192
1199
|
border: var(--table-border, 1px solid #e5e7eb);
|
|
1193
1200
|
border-radius: var(--table-border-radius, var(--radius, 4px));
|
|
1194
1201
|
width: var(--table-container-width, 100%);
|
|
1195
|
-
overflow: hidden;
|
|
1202
|
+
overflow: var(--table-container-overflow, hidden);
|
|
1196
1203
|
}
|
|
1197
1204
|
|
|
1198
1205
|
.scrollable-table {
|
|
@@ -11,22 +11,38 @@
|
|
|
11
11
|
{:else}
|
|
12
12
|
{#each items as item, i (i)}
|
|
13
13
|
{#if typeof onToggle === 'function'}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
14
|
+
<!-- The aggregate sits OUTSIDE the button deliberately. Every bit of
|
|
15
|
+
text inside a button becomes part of its accessible name, so an
|
|
16
|
+
aggregate inside it would make the control's name change whenever
|
|
17
|
+
the data does -- "Revenue $6,500" today, something else tomorrow.
|
|
18
|
+
A name that moves breaks voice-control targeting and any consumer
|
|
19
|
+
test matching it exactly, and announces a figure as the control's
|
|
20
|
+
identity rather than as information about the series. It also
|
|
21
|
+
stops a number being a click target for toggling a series, which
|
|
22
|
+
it never should have been. -->
|
|
23
|
+
<div class="legend-item" class:legend-hidden={item.hidden}>
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
class="legend-toggle"
|
|
27
|
+
aria-pressed={!item.hidden}
|
|
28
|
+
onclick={() => onToggle(i)}
|
|
29
|
+
data-pw={`legend-toggle-${i}`}
|
|
30
|
+
testID={`legend-toggle-${i}`}
|
|
31
|
+
>
|
|
32
|
+
<span class="legend-swatch" style="background: {item.color}"></span>
|
|
33
|
+
<span class="legend-label">{item.label}</span>
|
|
34
|
+
</button>
|
|
35
|
+
{#if item.aggregateLabel}
|
|
36
|
+
<span class="legend-aggregate">{item.aggregateLabel}</span>
|
|
37
|
+
{/if}
|
|
38
|
+
</div>
|
|
26
39
|
{:else}
|
|
27
40
|
<div class="legend-item">
|
|
28
41
|
<span class="legend-swatch" style="background: {item.color}"></span>
|
|
29
42
|
<span class="legend-label">{item.label}</span>
|
|
43
|
+
{#if item.aggregateLabel}
|
|
44
|
+
<span class="legend-aggregate">{item.aggregateLabel}</span>
|
|
45
|
+
{/if}
|
|
30
46
|
</div>
|
|
31
47
|
{/if}
|
|
32
48
|
{/each}
|
|
@@ -72,7 +88,18 @@
|
|
|
72
88
|
color: var(--chart-legend-color, light-dark(#333, #e5e7eb));
|
|
73
89
|
}
|
|
74
90
|
|
|
91
|
+
.legend-aggregate {
|
|
92
|
+
font-size: var(--chart-legend-font-size, 12px);
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
color: var(--chart-legend-aggregate-color, light-dark(#111, #f5f5f5));
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
.legend-toggle {
|
|
98
|
+
/* The button no longer IS the row -- it holds the swatch and label, and the
|
|
99
|
+
aggregate is its sibling -- so it carries its own flex layout. */
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 6px;
|
|
76
103
|
background: none;
|
|
77
104
|
border: none;
|
|
78
105
|
padding: 0;
|
|
@@ -85,7 +112,8 @@
|
|
|
85
112
|
opacity: 0.25;
|
|
86
113
|
}
|
|
87
114
|
|
|
88
|
-
.legend-hidden .legend-label
|
|
115
|
+
.legend-hidden .legend-label,
|
|
116
|
+
.legend-hidden .legend-aggregate {
|
|
89
117
|
color: var(--chart-legend-hidden-color, light-dark(#bbb, #555));
|
|
90
118
|
}
|
|
91
119
|
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a chart legend summarises a series' values into one number shown
|
|
3
|
+
* beside its label. `'none'` (the default for every series that doesn't set
|
|
4
|
+
* this) shows nothing -- the feature is purely additive.
|
|
5
|
+
*/
|
|
6
|
+
export type SeriesAggregate = 'sum' | 'average' | 'min' | 'max' | 'none';
|
|
7
|
+
/**
|
|
8
|
+
* Reduces a series' values to a single number for its legend entry.
|
|
9
|
+
*
|
|
10
|
+
* Null and non-finite entries (a missing point, or `NaN`/`Infinity` slipping
|
|
11
|
+
* through past the `number` type at runtime) are treated as ABSENT rather
|
|
12
|
+
* than as zero -- they are skipped entirely, not summed/counted as 0. This
|
|
13
|
+
* only changes the result for `average` (skipping shrinks the denominator)
|
|
14
|
+
* and `min`/`max` (an absent point can never set an extreme); `sum` is
|
|
15
|
+
* identical either way since 0 is the additive identity.
|
|
16
|
+
*
|
|
17
|
+
* Zero remaining values after skipping -- an empty series, or one whose
|
|
18
|
+
* points are all absent -- resolves to `null` for EVERY kind, `sum`
|
|
19
|
+
* included: a "total" of 0 for a series with no real data would be a
|
|
20
|
+
* fabricated number, not an observed one, so the legend renders nothing
|
|
21
|
+
* rather than a misleading 0.
|
|
22
|
+
*
|
|
23
|
+
* Aggregation is always over one series' own values, never across a stack --
|
|
24
|
+
* a stacked bar/area chart's per-category stack total is an unrelated,
|
|
25
|
+
* existing computation (the y-domain sum) that this function does not touch.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeSeriesAggregate(values: ReadonlyArray<number | null>, aggregate: SeriesAggregate): number | null;
|
|
28
|
+
/**
|
|
29
|
+
* Computes and formats a series aggregate for legend display in one step,
|
|
30
|
+
* reusing the chart's own value formatter (the same one driving its
|
|
31
|
+
* axis/tooltip) rather than a parallel formatting path. Returns `null` --
|
|
32
|
+
* render nothing -- whenever there is no aggregate to show, so the formatter
|
|
33
|
+
* is never handed `NaN` and a caller never needs its own null guard.
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatSeriesAggregate(values: ReadonlyArray<number | null>, aggregate: SeriesAggregate, format: (value: number) => string): string | null;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduces a series' values to a single number for its legend entry.
|
|
3
|
+
*
|
|
4
|
+
* Null and non-finite entries (a missing point, or `NaN`/`Infinity` slipping
|
|
5
|
+
* through past the `number` type at runtime) are treated as ABSENT rather
|
|
6
|
+
* than as zero -- they are skipped entirely, not summed/counted as 0. This
|
|
7
|
+
* only changes the result for `average` (skipping shrinks the denominator)
|
|
8
|
+
* and `min`/`max` (an absent point can never set an extreme); `sum` is
|
|
9
|
+
* identical either way since 0 is the additive identity.
|
|
10
|
+
*
|
|
11
|
+
* Zero remaining values after skipping -- an empty series, or one whose
|
|
12
|
+
* points are all absent -- resolves to `null` for EVERY kind, `sum`
|
|
13
|
+
* included: a "total" of 0 for a series with no real data would be a
|
|
14
|
+
* fabricated number, not an observed one, so the legend renders nothing
|
|
15
|
+
* rather than a misleading 0.
|
|
16
|
+
*
|
|
17
|
+
* Aggregation is always over one series' own values, never across a stack --
|
|
18
|
+
* a stacked bar/area chart's per-category stack total is an unrelated,
|
|
19
|
+
* existing computation (the y-domain sum) that this function does not touch.
|
|
20
|
+
*/
|
|
21
|
+
export function computeSeriesAggregate(values, aggregate) {
|
|
22
|
+
if (aggregate === 'none') {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
let sum = 0;
|
|
26
|
+
let count = 0;
|
|
27
|
+
let min = Infinity;
|
|
28
|
+
let max = -Infinity;
|
|
29
|
+
for (const value of values) {
|
|
30
|
+
if (value === null || !Number.isFinite(value)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
sum += value;
|
|
34
|
+
count += 1;
|
|
35
|
+
if (value < min) {
|
|
36
|
+
min = value;
|
|
37
|
+
}
|
|
38
|
+
if (value > max) {
|
|
39
|
+
max = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (count === 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
switch (aggregate) {
|
|
46
|
+
case 'sum':
|
|
47
|
+
return sum;
|
|
48
|
+
case 'average':
|
|
49
|
+
return sum / count;
|
|
50
|
+
case 'min':
|
|
51
|
+
return min;
|
|
52
|
+
case 'max':
|
|
53
|
+
return max;
|
|
54
|
+
default:
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Computes and formats a series aggregate for legend display in one step,
|
|
60
|
+
* reusing the chart's own value formatter (the same one driving its
|
|
61
|
+
* axis/tooltip) rather than a parallel formatting path. Returns `null` --
|
|
62
|
+
* render nothing -- whenever there is no aggregate to show, so the formatter
|
|
63
|
+
* is never handed `NaN` and a caller never needs its own null guard.
|
|
64
|
+
*/
|
|
65
|
+
export function formatSeriesAggregate(values, aggregate, format) {
|
|
66
|
+
const value = computeSeriesAggregate(values, aggregate);
|
|
67
|
+
return value === null ? null : format(value);
|
|
68
|
+
}
|
package/dist/_chart/types.d.ts
CHANGED
|
@@ -126,6 +126,14 @@ export type LegendItem = {
|
|
|
126
126
|
color: string;
|
|
127
127
|
/** True when the series is toggled off via an interactive legend. */
|
|
128
128
|
hidden?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Formatted per-series aggregate (e.g. `"$1.2K"`, `"45%"`) shown beside the
|
|
131
|
+
* label. `null`/omitted renders nothing -- both the "no `aggregate` was
|
|
132
|
+
* configured for this series" and "the series had no data to aggregate"
|
|
133
|
+
* cases resolve here, via `computeSeriesAggregate`/`formatSeriesAggregate`
|
|
134
|
+
* in `_chart/aggregate.ts`.
|
|
135
|
+
*/
|
|
136
|
+
aggregateLabel?: string | null;
|
|
129
137
|
};
|
|
130
138
|
/** Data-space anchor for point/category-anchored tooltips (coords are container px). */
|
|
131
139
|
export type TooltipAnchor = {
|