@classic-homes/charts-svelte 0.5.0 → 0.7.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/lib/components/core/AreaChart.svelte +31 -15
- package/dist/lib/components/core/BarChart.svelte +59 -32
- package/dist/lib/components/core/DonutChart.svelte +16 -7
- package/dist/lib/components/core/LineChart.svelte +27 -13
- package/dist/lib/components/core/PieChart.svelte +16 -7
- package/dist/lib/components/index.d.ts +1 -1
- package/dist/lib/components/index.js +1 -1
- package/dist/lib/components/report/ComboChart.svelte +12 -6
- package/dist/lib/components/report/DeltaText.svelte +72 -0
- package/dist/lib/components/report/DeltaText.svelte.d.ts +4 -0
- package/dist/lib/components/report/StatCard.svelte +14 -54
- package/dist/lib/components/report/index.d.ts +1 -0
- package/dist/lib/components/report/index.js +1 -0
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +1 -1
- package/package.json +1 -1
|
@@ -29,8 +29,12 @@
|
|
|
29
29
|
seriesLabelLookup,
|
|
30
30
|
toPercentStack,
|
|
31
31
|
withPointLabels,
|
|
32
|
+
withGhostSeries,
|
|
32
33
|
withReferenceMarks,
|
|
33
34
|
withZoomInset,
|
|
35
|
+
selectionShowsSymbols,
|
|
36
|
+
withSelectedPoints,
|
|
37
|
+
withSelectionDescription,
|
|
34
38
|
} from '@classic-homes/charts-core';
|
|
35
39
|
|
|
36
40
|
import { cn } from '../../utils.js';
|
|
@@ -89,6 +93,8 @@
|
|
|
89
93
|
showDataTable,
|
|
90
94
|
exportable,
|
|
91
95
|
patterns,
|
|
96
|
+
showDataPoints = false,
|
|
97
|
+
selected,
|
|
92
98
|
onClick,
|
|
93
99
|
onHover,
|
|
94
100
|
class: className,
|
|
@@ -119,7 +125,8 @@
|
|
|
119
125
|
const plotted = isPercent ? toPercentStack(data.series) : data.series;
|
|
120
126
|
const getLabel = seriesLabelLookup(data.series);
|
|
121
127
|
// ECharts hides line labels along with the symbol
|
|
122
|
-
const showSymbol = showLabels || hasPointLabels(data.series);
|
|
128
|
+
const showSymbol = showDataPoints || showLabels || hasPointLabels(data.series);
|
|
129
|
+
const pointSize = showDataPoints ? 6 : showSymbol ? 4 : 0;
|
|
123
130
|
const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
|
|
124
131
|
|
|
125
132
|
const series = data.series.map((source, index) => {
|
|
@@ -128,14 +135,20 @@
|
|
|
128
135
|
return {
|
|
129
136
|
name: source.name,
|
|
130
137
|
type: 'line',
|
|
131
|
-
data:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
data: withSelectedPoints(
|
|
139
|
+
withForecastPoints(
|
|
140
|
+
withPointLabels(
|
|
141
|
+
pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
|
|
142
|
+
source.labels,
|
|
143
|
+
formatters.label,
|
|
144
|
+
{ position: 'top' }
|
|
145
|
+
),
|
|
146
|
+
source,
|
|
147
|
+
pointSize
|
|
137
148
|
),
|
|
138
|
-
|
|
149
|
+
data.categories,
|
|
150
|
+
selected,
|
|
151
|
+
{ pointSize, ringColor: chartColors.surface }
|
|
139
152
|
),
|
|
140
153
|
smooth,
|
|
141
154
|
connectNulls,
|
|
@@ -158,10 +171,10 @@
|
|
|
158
171
|
emphasis: {
|
|
159
172
|
focus: 'series',
|
|
160
173
|
},
|
|
161
|
-
// Forecast points show even when the others are hidden (size 0)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
// Forecast and selected points show even when the others are hidden (size 0)
|
|
175
|
+
showSymbol:
|
|
176
|
+
showSymbol || forecastStart(source) !== undefined || selectionShowsSymbols(selected),
|
|
177
|
+
symbolSize: pointSize,
|
|
165
178
|
};
|
|
166
179
|
});
|
|
167
180
|
|
|
@@ -221,7 +234,7 @@
|
|
|
221
234
|
formatters.axis(yAxisConfig)
|
|
222
235
|
),
|
|
223
236
|
series: withForecastLines(
|
|
224
|
-
withReferenceMarks(series, referenceLines, referenceBands, {
|
|
237
|
+
withReferenceMarks(withGhostSeries(series, data.series), referenceLines, referenceBands, {
|
|
225
238
|
valueAxis: 'y',
|
|
226
239
|
format: formatters.format,
|
|
227
240
|
color: chartColors.reference,
|
|
@@ -240,8 +253,11 @@
|
|
|
240
253
|
});
|
|
241
254
|
|
|
242
255
|
const accessibilityDescription = $derived(
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
withSelectionDescription(
|
|
257
|
+
description ||
|
|
258
|
+
(!isEmpty ? generateAreaChartDescription(title, data, formatters.format) : undefined),
|
|
259
|
+
selected
|
|
260
|
+
)
|
|
245
261
|
);
|
|
246
262
|
|
|
247
263
|
const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
forecastTagLookup,
|
|
20
20
|
withForecastBars,
|
|
21
21
|
formatValue,
|
|
22
|
+
ghostStackId,
|
|
22
23
|
generateBarChartDescription,
|
|
23
24
|
getChartColors,
|
|
24
25
|
gridInset,
|
|
@@ -31,8 +32,14 @@
|
|
|
31
32
|
toPercentStack,
|
|
32
33
|
withPointLabels,
|
|
33
34
|
withEndLabelInset,
|
|
35
|
+
withGhostSeries,
|
|
36
|
+
withoutGhostSeries,
|
|
34
37
|
withReferenceMarks,
|
|
35
38
|
withZoomInset,
|
|
39
|
+
GHOST_BAR_OPACITY,
|
|
40
|
+
isGhostSeries,
|
|
41
|
+
withSelectedBars,
|
|
42
|
+
withSelectionDescription,
|
|
36
43
|
} from '@classic-homes/charts-core';
|
|
37
44
|
|
|
38
45
|
import { cn } from '../../utils.js';
|
|
@@ -91,6 +98,7 @@
|
|
|
91
98
|
showDataTable,
|
|
92
99
|
exportable,
|
|
93
100
|
patterns,
|
|
101
|
+
selected,
|
|
94
102
|
onClick,
|
|
95
103
|
onHover,
|
|
96
104
|
class: className,
|
|
@@ -146,39 +154,53 @@
|
|
|
146
154
|
const zoomAxis = isHorizontal ? 'y' : 'x';
|
|
147
155
|
const legendBottom = builtInLegend && (legendConfig?.position ?? 'bottom') === 'bottom';
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// Ghost series stack in a parallel group, beside the real stacks
|
|
158
|
+
const stackIds = data.series.map((s) =>
|
|
159
|
+
ghostStackId(s.stack ?? (stacked ? 'Total' : undefined), s)
|
|
160
|
+
);
|
|
161
|
+
const series = withGhostSeries(
|
|
162
|
+
data.series.map((source, index) => ({
|
|
163
|
+
name: source.name,
|
|
164
|
+
type: 'bar',
|
|
165
|
+
data: withSelectedBars(
|
|
166
|
+
withForecastBars(
|
|
167
|
+
withPointLabels(
|
|
168
|
+
pairWithCategories(data.categories, plotted[index].data, categoryAxisType),
|
|
169
|
+
source.labels,
|
|
170
|
+
formatters.label,
|
|
171
|
+
{ position: labelPosition }
|
|
172
|
+
),
|
|
173
|
+
source,
|
|
174
|
+
source.color || chartColors.series[index % chartColors.series.length]
|
|
175
|
+
),
|
|
176
|
+
data.categories,
|
|
177
|
+
selected,
|
|
178
|
+
isGhostSeries(source) ? GHOST_BAR_OPACITY : 1
|
|
159
179
|
),
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
stack: stackIds[index],
|
|
181
|
+
itemStyle: {
|
|
182
|
+
...(source.color && { color: source.color }),
|
|
183
|
+
borderRadius: barCornerRadius(isHorizontal, stackIds, index),
|
|
184
|
+
},
|
|
185
|
+
label: buildDataLabel({ show: true, position: labelPosition }, undefined, {
|
|
186
|
+
show: showValues,
|
|
187
|
+
formatter: formatters.label,
|
|
188
|
+
getLabel,
|
|
189
|
+
}),
|
|
190
|
+
emphasis: {
|
|
191
|
+
focus: 'series',
|
|
192
|
+
},
|
|
193
|
+
})),
|
|
194
|
+
data.series
|
|
195
|
+
);
|
|
196
|
+
// Ghost series count toward neither totals nor end labels
|
|
197
|
+
const solid = withoutGhostSeries(data.series);
|
|
198
|
+
const solidPlotted = plotted.filter((_, index) => data.series[index].variant !== 'ghost');
|
|
177
199
|
|
|
178
200
|
// Totals use the value format, not the percent one: they show the raw sum
|
|
179
201
|
const format = resolveValueFormatter(valueFormat, locale);
|
|
180
202
|
const totalSeries = showTotal
|
|
181
|
-
? buildStackTotalSeries(data.categories,
|
|
203
|
+
? buildStackTotalSeries(data.categories, solid, {
|
|
182
204
|
stacked,
|
|
183
205
|
position: labelPosition,
|
|
184
206
|
format,
|
|
@@ -190,9 +212,11 @@
|
|
|
190
212
|
const endLabels = !isHorizontal
|
|
191
213
|
? []
|
|
192
214
|
: totalSeries.length
|
|
193
|
-
? [...stackTotals(
|
|
215
|
+
? [...stackTotals(solid, stacked).values()].flat().map((t) => formatValue(t, format))
|
|
194
216
|
: showValues
|
|
195
|
-
?
|
|
217
|
+
? solidPlotted.flatMap((s) =>
|
|
218
|
+
s.data.map((value) => formatValue(value, formatters.format))
|
|
219
|
+
)
|
|
196
220
|
: [];
|
|
197
221
|
|
|
198
222
|
return mergeChartOption(
|
|
@@ -253,8 +277,11 @@
|
|
|
253
277
|
});
|
|
254
278
|
|
|
255
279
|
const accessibilityDescription = $derived(
|
|
256
|
-
|
|
257
|
-
|
|
280
|
+
withSelectionDescription(
|
|
281
|
+
description ||
|
|
282
|
+
(!isEmpty ? generateBarChartDescription(title, data, formatters.format) : undefined),
|
|
283
|
+
selected
|
|
284
|
+
)
|
|
258
285
|
);
|
|
259
286
|
|
|
260
287
|
const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
pointLabel,
|
|
21
21
|
resolveValueFormatter,
|
|
22
22
|
sliceTable,
|
|
23
|
+
withSelectedSlices,
|
|
24
|
+
withSelectionDescription,
|
|
23
25
|
} from '@classic-homes/charts-core';
|
|
24
26
|
|
|
25
27
|
import { cn } from '../../utils.js';
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
showDataTable,
|
|
73
75
|
exportable,
|
|
74
76
|
patterns,
|
|
77
|
+
selected,
|
|
75
78
|
onClick,
|
|
76
79
|
class: className,
|
|
77
80
|
}: Props = $props();
|
|
@@ -181,12 +184,15 @@
|
|
|
181
184
|
radius: [innerRadius, '70%'],
|
|
182
185
|
center: pieCenter,
|
|
183
186
|
avoidLabelOverlap: true,
|
|
184
|
-
data:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
data: withSelectedSlices(
|
|
188
|
+
data.map((item) => ({
|
|
189
|
+
name: item.name,
|
|
190
|
+
value: item.value,
|
|
191
|
+
itemStyle: item.color ? { color: item.color } : undefined,
|
|
192
|
+
label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
|
|
193
|
+
})),
|
|
194
|
+
selected
|
|
195
|
+
),
|
|
190
196
|
label: buildDataLabel(
|
|
191
197
|
{
|
|
192
198
|
show: true,
|
|
@@ -214,7 +220,10 @@
|
|
|
214
220
|
});
|
|
215
221
|
|
|
216
222
|
const accessibilityDescription = $derived(
|
|
217
|
-
|
|
223
|
+
withSelectionDescription(
|
|
224
|
+
description || (!isEmpty ? generatePieChartDescription(title, data, true) : undefined),
|
|
225
|
+
selected
|
|
226
|
+
)
|
|
218
227
|
);
|
|
219
228
|
|
|
220
229
|
const dataTable = $derived(isEmpty ? undefined : sliceTable(data));
|
|
@@ -28,8 +28,12 @@
|
|
|
28
28
|
seriesLabelLookup,
|
|
29
29
|
toPercentStack,
|
|
30
30
|
withPointLabels,
|
|
31
|
+
withGhostSeries,
|
|
31
32
|
withReferenceMarks,
|
|
32
33
|
withZoomInset,
|
|
34
|
+
selectionShowsSymbols,
|
|
35
|
+
withSelectedPoints,
|
|
36
|
+
withSelectionDescription,
|
|
33
37
|
} from '@classic-homes/charts-core';
|
|
34
38
|
|
|
35
39
|
import { cn } from '../../utils.js';
|
|
@@ -89,6 +93,7 @@
|
|
|
89
93
|
showDataTable,
|
|
90
94
|
exportable,
|
|
91
95
|
patterns,
|
|
96
|
+
selected,
|
|
92
97
|
onClick,
|
|
93
98
|
onHover,
|
|
94
99
|
class: className,
|
|
@@ -125,19 +130,25 @@
|
|
|
125
130
|
const series = data.series.map((source, index) => ({
|
|
126
131
|
name: source.name,
|
|
127
132
|
type: 'line',
|
|
128
|
-
data:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
data: withSelectedPoints(
|
|
134
|
+
withForecastPoints(
|
|
135
|
+
withPointLabels(
|
|
136
|
+
pairWithCategories(data.categories, plotted[index].data, xAxisConfig?.type),
|
|
137
|
+
source.labels,
|
|
138
|
+
formatters.label,
|
|
139
|
+
{ position: 'top' }
|
|
140
|
+
),
|
|
141
|
+
source,
|
|
142
|
+
showDataPoints ? 6 : 0
|
|
134
143
|
),
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
data.categories,
|
|
145
|
+
selected,
|
|
146
|
+
{ pointSize: showDataPoints ? 6 : 0, ringColor: chartColors.surface }
|
|
137
147
|
),
|
|
138
148
|
smooth,
|
|
139
|
-
// Forecast points show even when the others are hidden (size 0)
|
|
140
|
-
showSymbol:
|
|
149
|
+
// Forecast and selected points show even when the others are hidden (size 0)
|
|
150
|
+
showSymbol:
|
|
151
|
+
showSymbol || forecastStart(source) !== undefined || selectionShowsSymbols(selected),
|
|
141
152
|
symbolSize: showDataPoints ? 6 : 0,
|
|
142
153
|
connectNulls,
|
|
143
154
|
stack: source.stack ?? (stacked ? 'Total' : undefined),
|
|
@@ -212,7 +223,7 @@
|
|
|
212
223
|
formatters.axis(yAxisConfig)
|
|
213
224
|
),
|
|
214
225
|
series: withForecastLines(
|
|
215
|
-
withReferenceMarks(series, referenceLines, referenceBands, {
|
|
226
|
+
withReferenceMarks(withGhostSeries(series, data.series), referenceLines, referenceBands, {
|
|
216
227
|
valueAxis: 'y',
|
|
217
228
|
format: formatters.format,
|
|
218
229
|
color: chartColors.reference,
|
|
@@ -231,8 +242,11 @@
|
|
|
231
242
|
});
|
|
232
243
|
|
|
233
244
|
const accessibilityDescription = $derived(
|
|
234
|
-
|
|
235
|
-
|
|
245
|
+
withSelectionDescription(
|
|
246
|
+
description ||
|
|
247
|
+
(!isEmpty ? generateLineChartDescription(title, data, formatters.format) : undefined),
|
|
248
|
+
selected
|
|
249
|
+
)
|
|
236
250
|
);
|
|
237
251
|
|
|
238
252
|
const dataTable = $derived(isEmpty ? undefined : categoryTable(data));
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
pointLabel,
|
|
20
20
|
resolveValueFormatter,
|
|
21
21
|
sliceTable,
|
|
22
|
+
withSelectedSlices,
|
|
23
|
+
withSelectionDescription,
|
|
22
24
|
} from '@classic-homes/charts-core';
|
|
23
25
|
|
|
24
26
|
import { cn } from '../../utils.js';
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
showDataTable,
|
|
70
72
|
exportable,
|
|
71
73
|
patterns,
|
|
74
|
+
selected,
|
|
72
75
|
onClick,
|
|
73
76
|
class: className,
|
|
74
77
|
}: Props = $props();
|
|
@@ -122,12 +125,15 @@
|
|
|
122
125
|
type: 'pie',
|
|
123
126
|
radius: innerRadius ? [innerRadius, '70%'] : '70%',
|
|
124
127
|
center: legendCenter(legendConfig, showLegend && !renderLegend, ['50%', '45%']),
|
|
125
|
-
data:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
data: withSelectedSlices(
|
|
129
|
+
data.map((item) => ({
|
|
130
|
+
name: item.name,
|
|
131
|
+
value: item.value,
|
|
132
|
+
itemStyle: item.color ? { color: item.color } : undefined,
|
|
133
|
+
label: pointLabel(item.label, labelFormatter, { position: labelPosition }),
|
|
134
|
+
})),
|
|
135
|
+
selected
|
|
136
|
+
),
|
|
131
137
|
label: buildDataLabel(
|
|
132
138
|
{
|
|
133
139
|
show: true,
|
|
@@ -152,7 +158,10 @@
|
|
|
152
158
|
});
|
|
153
159
|
|
|
154
160
|
const accessibilityDescription = $derived(
|
|
155
|
-
|
|
161
|
+
withSelectionDescription(
|
|
162
|
+
description || (!isEmpty ? generatePieChartDescription(title, data, false) : undefined),
|
|
163
|
+
selected
|
|
164
|
+
)
|
|
156
165
|
);
|
|
157
166
|
|
|
158
167
|
const dataTable = $derived(isEmpty ? undefined : sliceTable(data));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, } from './base/index.js';
|
|
2
2
|
export { LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, } from './core/index.js';
|
|
3
3
|
export { RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, } from './extended/index.js';
|
|
4
|
-
export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, type StatGroupProps, } from './report/index.js';
|
|
4
|
+
export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, type StatGroupProps, } from './report/index.js';
|
|
@@ -5,4 +5,4 @@ export { LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, } f
|
|
|
5
5
|
// Extended charts
|
|
6
6
|
export { RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, } from './extended/index.js';
|
|
7
7
|
// Report charts and stats
|
|
8
|
-
export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, } from './report/index.js';
|
|
8
|
+
export { ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, } from './report/index.js';
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
valueAxisConfig,
|
|
27
27
|
valueLabelFormatter,
|
|
28
28
|
withPointLabels,
|
|
29
|
+
withGhostSeries,
|
|
29
30
|
withReferenceMarks,
|
|
30
31
|
withZoomInset,
|
|
31
32
|
} from '@classic-homes/charts-core';
|
|
@@ -243,12 +244,17 @@
|
|
|
243
244
|
]
|
|
244
245
|
: []),
|
|
245
246
|
],
|
|
246
|
-
series: withReferenceMarks(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
series: withReferenceMarks(
|
|
248
|
+
withGhostSeries(series, data.series),
|
|
249
|
+
referenceLines,
|
|
250
|
+
referenceBands,
|
|
251
|
+
{
|
|
252
|
+
valueAxis: 'y',
|
|
253
|
+
format: formatters.format,
|
|
254
|
+
color: chartColors.reference,
|
|
255
|
+
bandColor: chartColors.referenceBand,
|
|
256
|
+
}
|
|
257
|
+
),
|
|
252
258
|
} as EChartsOption,
|
|
253
259
|
optionOverride
|
|
254
260
|
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DeltaTextProps } from '@classic-homes/charts-core';
|
|
3
|
+
import { computeDelta, formatValue, resolveValueFormatter } from '@classic-homes/charts-core';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
current,
|
|
8
|
+
previous,
|
|
9
|
+
display = 'percent',
|
|
10
|
+
goodWhen = 'up',
|
|
11
|
+
valueFormat,
|
|
12
|
+
locale = 'en-US',
|
|
13
|
+
label,
|
|
14
|
+
class: className,
|
|
15
|
+
}: DeltaTextProps = $props();
|
|
16
|
+
|
|
17
|
+
const format = $derived(resolveValueFormatter(valueFormat, locale));
|
|
18
|
+
|
|
19
|
+
const change = $derived(computeDelta(current, previous));
|
|
20
|
+
|
|
21
|
+
const percentFormat = $derived(
|
|
22
|
+
new Intl.NumberFormat(locale, {
|
|
23
|
+
style: 'percent',
|
|
24
|
+
maximumFractionDigits: 1,
|
|
25
|
+
signDisplay: 'exceptZero',
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const changeText = $derived.by(() => {
|
|
30
|
+
if (!change) return '';
|
|
31
|
+
const sign = change.change > 0 ? '+' : change.change < 0 ? '−' : '';
|
|
32
|
+
const absolute = `${sign}${formatValue(Math.abs(change.change), format)}`;
|
|
33
|
+
const percent = change.ratio === null ? null : percentFormat.format(change.ratio);
|
|
34
|
+
switch (display) {
|
|
35
|
+
case 'absolute':
|
|
36
|
+
return absolute;
|
|
37
|
+
case 'both':
|
|
38
|
+
return percent ? `${absolute} (${percent})` : absolute;
|
|
39
|
+
default:
|
|
40
|
+
// A change from zero has no percentage
|
|
41
|
+
return percent ?? absolute;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const changeTone = $derived.by(() => {
|
|
46
|
+
if (!change || change.direction === 'flat' || goodWhen === 'none') {
|
|
47
|
+
return 'text-muted-foreground';
|
|
48
|
+
}
|
|
49
|
+
return goodWhen === change.direction ? 'text-success' : 'text-destructive';
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const labelText = $derived(
|
|
53
|
+
label ?? (typeof previous === 'number' ? `vs ${formatValue(previous, format)}` : '')
|
|
54
|
+
);
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
{#if change}
|
|
58
|
+
<span
|
|
59
|
+
data-testid="delta-text"
|
|
60
|
+
class={cn('inline-flex flex-wrap items-baseline gap-x-1 text-xs', className)}
|
|
61
|
+
>
|
|
62
|
+
<span class={cn('font-medium tabular-nums', changeTone)}>
|
|
63
|
+
<span aria-hidden="true">
|
|
64
|
+
{change.direction === 'up' ? '▲' : change.direction === 'down' ? '▼' : ''}
|
|
65
|
+
</span>
|
|
66
|
+
{changeText}
|
|
67
|
+
</span>
|
|
68
|
+
{#if labelText}
|
|
69
|
+
<span class="text-muted-foreground">{labelText}</span>
|
|
70
|
+
{/if}
|
|
71
|
+
</span>
|
|
72
|
+
{/if}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { StatCardProps } from '@classic-homes/charts-core';
|
|
3
|
-
import {
|
|
3
|
+
import { formatValue, resolveValueFormatter } from '@classic-homes/charts-core';
|
|
4
4
|
import { cn } from '../../utils.js';
|
|
5
|
+
import DeltaText from './DeltaText.svelte';
|
|
5
6
|
import Sparkline from './Sparkline.svelte';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -27,47 +28,7 @@
|
|
|
27
28
|
typeof value === 'string' ? value : formatValue(value ?? null, format, emptyValue)
|
|
28
29
|
);
|
|
29
30
|
|
|
30
|
-
const
|
|
31
|
-
delta
|
|
32
|
-
? computeDelta(delta.current ?? (typeof value === 'number' ? value : null), delta.previous)
|
|
33
|
-
: null
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const percentFormat = $derived(
|
|
37
|
-
new Intl.NumberFormat(locale, {
|
|
38
|
-
style: 'percent',
|
|
39
|
-
maximumFractionDigits: 1,
|
|
40
|
-
signDisplay: 'exceptZero',
|
|
41
|
-
})
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const changeText = $derived.by(() => {
|
|
45
|
-
if (!change || !delta) return '';
|
|
46
|
-
const sign = change.change > 0 ? '+' : change.change < 0 ? '−' : '';
|
|
47
|
-
const absolute = `${sign}${formatValue(Math.abs(change.change), format)}`;
|
|
48
|
-
const percent = change.ratio === null ? null : percentFormat.format(change.ratio);
|
|
49
|
-
switch (delta.display ?? 'percent') {
|
|
50
|
-
case 'absolute':
|
|
51
|
-
return absolute;
|
|
52
|
-
case 'both':
|
|
53
|
-
return percent ? `${absolute} (${percent})` : absolute;
|
|
54
|
-
default:
|
|
55
|
-
// A change from zero has no percentage
|
|
56
|
-
return percent ?? absolute;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const changeTone = $derived.by(() => {
|
|
61
|
-
if (!change || change.direction === 'flat' || delta?.goodWhen === 'none') {
|
|
62
|
-
return 'text-muted-foreground';
|
|
63
|
-
}
|
|
64
|
-
const good = (delta?.goodWhen ?? 'up') === change.direction;
|
|
65
|
-
return good ? 'text-success' : 'text-destructive';
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const previousText = $derived(
|
|
69
|
-
delta && typeof delta.previous === 'number' ? formatValue(delta.previous, format) : ''
|
|
70
|
-
);
|
|
31
|
+
const deltaCurrent = $derived(delta?.current ?? (typeof value === 'number' ? value : null));
|
|
71
32
|
|
|
72
33
|
const valueTone = $derived(
|
|
73
34
|
{
|
|
@@ -114,18 +75,17 @@
|
|
|
114
75
|
<div class="mt-1 flex items-end justify-between gap-3">
|
|
115
76
|
<div class="min-w-0">
|
|
116
77
|
<div class={cn('text-2xl font-semibold tabular-nums', valueTone)}>{display}</div>
|
|
117
|
-
{#if
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</div>
|
|
78
|
+
{#if delta}
|
|
79
|
+
<DeltaText
|
|
80
|
+
class="mt-1 flex"
|
|
81
|
+
current={deltaCurrent}
|
|
82
|
+
previous={delta.previous}
|
|
83
|
+
display={delta.display}
|
|
84
|
+
goodWhen={delta.goodWhen}
|
|
85
|
+
{valueFormat}
|
|
86
|
+
{locale}
|
|
87
|
+
label={delta.label}
|
|
88
|
+
/>
|
|
129
89
|
{/if}
|
|
130
90
|
{#if hint}
|
|
131
91
|
<p class="mt-1 text-xs text-muted-foreground">{hint}</p>
|
|
@@ -6,6 +6,7 @@ export { default as BoxPlotChart } from './BoxPlotChart.svelte';
|
|
|
6
6
|
export { default as WaterfallChart } from './WaterfallChart.svelte';
|
|
7
7
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
8
8
|
export { default as StatCard } from './StatCard.svelte';
|
|
9
|
+
export { default as DeltaText } from './DeltaText.svelte';
|
|
9
10
|
export { default as StatGroup } from './StatGroup.svelte';
|
|
10
11
|
import type { ComponentProps } from 'svelte';
|
|
11
12
|
import type StatGroup from './StatGroup.svelte';
|
|
@@ -6,4 +6,5 @@ export { default as BoxPlotChart } from './BoxPlotChart.svelte';
|
|
|
6
6
|
export { default as WaterfallChart } from './WaterfallChart.svelte';
|
|
7
7
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
8
8
|
export { default as StatCard } from './StatCard.svelte';
|
|
9
|
+
export { default as DeltaText } from './DeltaText.svelte';
|
|
9
10
|
export { default as StatGroup } from './StatGroup.svelte';
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Svelte ECharts components for the Classic Theme design system
|
|
5
5
|
*/
|
|
6
|
-
export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, type StatGroupProps, } from './components/index.js';
|
|
6
|
+
export { ChartContainer, ChartSkeleton, ChartError, ChartEmpty, ChartLegend, type ChartContainerProps, type ChartLegendProps, type ChartSkeletonProps, type ChartErrorProps, type ChartEmptyProps, ChartDataTable, type ChartDataTableProps, LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart, RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart, ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, type StatGroupProps, } from './components/index.js';
|
|
7
7
|
export { useChart, exportChartImage, registerEchartsModules, type ExportImageOptions, useChartTheme, useChartResize, registerClassicThemes, useReducedMotion, useChartLegend, type CustomLegendProps, type UseChartLegendOptions, type UseChartLegendReturn, type UseChartOptions, type UseChartReturn, } from './composables/index.js';
|
|
8
8
|
export { cn } from './utils.js';
|
|
9
9
|
export { fromRecords, toSliceData, resolveValueFormatter, formatValue, computeDelta, binValues, boxplotStats, waterfallLayout, toPercentStack, tableToCsv, downloadCsv, chartFilename, type FromRecordsOptions, type ToSliceDataOptions, type Delta, } from '@classic-homes/charts-core';
|
|
10
|
-
export type { BaseChartProps, LegendChartProps, LabelChartProps, AxisChartProps, AxisConfig, ChartFormatter, ChartLegendConfig, ChartLegendPosition, LabelFormatterParams, TooltipFormatterParams, LegendItem, LegendRenderProps, LineChartProps, BarChartProps, PieChartProps, DonutChartProps, AreaChartProps, ScatterChartProps, RadarChartProps, FunnelChartProps, GaugeChartProps, HeatmapChartProps, TreemapChartProps, CandlestickChartProps, SankeyChartProps, LineChartData, LineSeriesData, BarChartData, BarSeriesData, PieSliceData, AreaChartData, AreaSeriesData, ScatterChartData, ScatterSeriesData, RadarChartData, RadarSeriesData, RadarIndicator, FunnelSliceData, HeatmapData, TreemapNode, TreemapLevel, CandlestickData, CandlestickItem, SankeyData, SankeyNode, SankeyLink, BaseEventParams, DataPointEventParams, SliceEventParams, HeatmapEventParams, TreemapEventParams, CandlestickEventParams, SankeyEventParams, ChartEventHandlers, ChartExportConfig, ReferenceChartProps, ZoomChartProps, RowHeightProps, ComboChartProps, DotPlotChartProps, BulletChartProps, HistogramChartProps, BoxPlotChartProps, WaterfallChartProps, SparklineProps, StatCardProps, StatDelta, CategoryChartData, CategorySeriesData, ComboChartData, ComboSeriesData, DotPlotChartData, DotPlotSeriesData, BulletItem, HistogramBin, HistogramBucket, BoxPlotGroup, BoxPlotStats, WaterfallStep, ValueFormat, ValueFormatPreset, ReferenceLine, ReferenceBand, ChartDataTable as ChartDataTableData, } from '@classic-homes/charts-core';
|
|
10
|
+
export type { BaseChartProps, LegendChartProps, LabelChartProps, AxisChartProps, AxisConfig, ChartFormatter, ChartLegendConfig, ChartLegendPosition, LabelFormatterParams, TooltipFormatterParams, LegendItem, LegendRenderProps, LineChartProps, BarChartProps, PieChartProps, DonutChartProps, AreaChartProps, ScatterChartProps, RadarChartProps, FunnelChartProps, GaugeChartProps, HeatmapChartProps, TreemapChartProps, CandlestickChartProps, SankeyChartProps, LineChartData, LineSeriesData, BarChartData, BarSeriesData, PieSliceData, AreaChartData, AreaSeriesData, ScatterChartData, ScatterSeriesData, RadarChartData, RadarSeriesData, RadarIndicator, FunnelSliceData, HeatmapData, TreemapNode, TreemapLevel, CandlestickData, CandlestickItem, SankeyData, SankeyNode, SankeyLink, BaseEventParams, DataPointEventParams, SliceEventParams, HeatmapEventParams, TreemapEventParams, CandlestickEventParams, SankeyEventParams, ChartEventHandlers, ChartExportConfig, ReferenceChartProps, ZoomChartProps, RowHeightProps, ComboChartProps, DotPlotChartProps, BulletChartProps, HistogramChartProps, BoxPlotChartProps, WaterfallChartProps, SparklineProps, StatCardProps, StatDelta, DeltaTextProps, CategoryChartData, CategorySeriesData, ComboChartData, ComboSeriesData, DotPlotChartData, DotPlotSeriesData, BulletItem, HistogramBin, HistogramBucket, BoxPlotGroup, BoxPlotStats, WaterfallStep, ValueFormat, ValueFormatPreset, ReferenceLine, ReferenceBand, ChartDataTable as ChartDataTableData, } from '@classic-homes/charts-core';
|
package/dist/lib/index.js
CHANGED
|
@@ -12,7 +12,7 @@ LineChart, BarChart, PieChart, AreaChart, ScatterChart, DonutChart,
|
|
|
12
12
|
// Extended charts
|
|
13
13
|
RadarChart, FunnelChart, GaugeChart, HeatmapChart, TreemapChart, CandlestickChart, SankeyChart,
|
|
14
14
|
// Report charts and stats
|
|
15
|
-
ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, StatGroup, } from './components/index.js';
|
|
15
|
+
ComboChart, DotPlotChart, BulletChart, HistogramChart, BoxPlotChart, WaterfallChart, Sparkline, StatCard, DeltaText, StatGroup, } from './components/index.js';
|
|
16
16
|
// Composables
|
|
17
17
|
export { useChart, exportChartImage, registerEchartsModules, useChartTheme, useChartResize, registerClassicThemes, useReducedMotion, useChartLegend, } from './composables/index.js';
|
|
18
18
|
// Utils
|