@classic-homes/charts-core 0.1.36 → 0.1.37
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/{chunk-ZRG25JNX.js → chunk-JM72YHZD.js} +38 -3
- package/dist/index.cjs +82 -6
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +42 -7
- package/dist/theme/index.cjs +40 -1
- package/dist/theme/index.d.cts +33 -2
- package/dist/theme/index.d.ts +33 -2
- package/dist/theme/index.js +1 -1
- package/package.json +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { semanticColors, colors, borderRadius, fontFamily } from '@classic-homes/theme-tokens';
|
|
2
2
|
|
|
3
3
|
// src/theme/generator.ts
|
|
4
4
|
function generateLightTheme() {
|
|
@@ -270,8 +270,43 @@ function generateDarkTheme() {
|
|
|
270
270
|
var lightTheme = generateLightTheme();
|
|
271
271
|
var darkTheme = generateDarkTheme();
|
|
272
272
|
|
|
273
|
-
// src/theme/
|
|
273
|
+
// src/theme/constants.ts
|
|
274
274
|
var THEME_LIGHT = "classic-light";
|
|
275
275
|
var THEME_DARK = "classic-dark";
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
// src/theme/semantic.ts
|
|
278
|
+
var lightChartColors = {
|
|
279
|
+
series: lightTheme.color,
|
|
280
|
+
foreground: semanticColors.light.foreground,
|
|
281
|
+
mutedForeground: semanticColors.light.mutedForeground,
|
|
282
|
+
axisPointerLabel: semanticColors.light.mutedForeground,
|
|
283
|
+
divider: colors.gray[600],
|
|
284
|
+
success: semanticColors.light.success,
|
|
285
|
+
destructive: semanticColors.light.destructive
|
|
286
|
+
};
|
|
287
|
+
var darkChartColors = {
|
|
288
|
+
series: darkTheme.color,
|
|
289
|
+
foreground: semanticColors.dark.foreground,
|
|
290
|
+
mutedForeground: semanticColors.dark.mutedForeground,
|
|
291
|
+
// mutedForeground (gray[400]) is too light under the white label text
|
|
292
|
+
axisPointerLabel: colors.gray[600],
|
|
293
|
+
divider: colors.gray[700],
|
|
294
|
+
success: semanticColors.dark.success,
|
|
295
|
+
destructive: semanticColors.dark.destructive
|
|
296
|
+
};
|
|
297
|
+
function getChartColors(themeName) {
|
|
298
|
+
return themeName === THEME_DARK ? darkChartColors : lightChartColors;
|
|
299
|
+
}
|
|
300
|
+
function colorWithAlpha(color, alpha) {
|
|
301
|
+
let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
|
|
302
|
+
if (!hex) return color;
|
|
303
|
+
if (hex.length === 3) {
|
|
304
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
305
|
+
}
|
|
306
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
307
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
308
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
309
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
|
package/dist/index.cjs
CHANGED
|
@@ -272,10 +272,45 @@ function generateDarkTheme() {
|
|
|
272
272
|
var lightTheme = generateLightTheme();
|
|
273
273
|
var darkTheme = generateDarkTheme();
|
|
274
274
|
|
|
275
|
-
// src/theme/
|
|
275
|
+
// src/theme/constants.ts
|
|
276
276
|
var THEME_LIGHT = "classic-light";
|
|
277
277
|
var THEME_DARK = "classic-dark";
|
|
278
278
|
|
|
279
|
+
// src/theme/semantic.ts
|
|
280
|
+
var lightChartColors = {
|
|
281
|
+
series: lightTheme.color,
|
|
282
|
+
foreground: themeTokens.semanticColors.light.foreground,
|
|
283
|
+
mutedForeground: themeTokens.semanticColors.light.mutedForeground,
|
|
284
|
+
axisPointerLabel: themeTokens.semanticColors.light.mutedForeground,
|
|
285
|
+
divider: themeTokens.colors.gray[600],
|
|
286
|
+
success: themeTokens.semanticColors.light.success,
|
|
287
|
+
destructive: themeTokens.semanticColors.light.destructive
|
|
288
|
+
};
|
|
289
|
+
var darkChartColors = {
|
|
290
|
+
series: darkTheme.color,
|
|
291
|
+
foreground: themeTokens.semanticColors.dark.foreground,
|
|
292
|
+
mutedForeground: themeTokens.semanticColors.dark.mutedForeground,
|
|
293
|
+
// mutedForeground (gray[400]) is too light under the white label text
|
|
294
|
+
axisPointerLabel: themeTokens.colors.gray[600],
|
|
295
|
+
divider: themeTokens.colors.gray[700],
|
|
296
|
+
success: themeTokens.semanticColors.dark.success,
|
|
297
|
+
destructive: themeTokens.semanticColors.dark.destructive
|
|
298
|
+
};
|
|
299
|
+
function getChartColors(themeName) {
|
|
300
|
+
return themeName === THEME_DARK ? darkChartColors : lightChartColors;
|
|
301
|
+
}
|
|
302
|
+
function colorWithAlpha(color, alpha) {
|
|
303
|
+
let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
|
|
304
|
+
if (!hex) return color;
|
|
305
|
+
if (hex.length === 3) {
|
|
306
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
307
|
+
}
|
|
308
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
309
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
310
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
311
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
312
|
+
}
|
|
313
|
+
|
|
279
314
|
// src/utils/format.ts
|
|
280
315
|
function formatNumber(value, options) {
|
|
281
316
|
return new Intl.NumberFormat("en-US", options).format(value);
|
|
@@ -305,7 +340,18 @@ function abbreviateNumber(value, decimals = 1) {
|
|
|
305
340
|
abbreviated /= 1e3;
|
|
306
341
|
suffixIndex++;
|
|
307
342
|
}
|
|
308
|
-
|
|
343
|
+
if (Math.abs(Number(abbreviated.toFixed(decimals))) >= 1e3 && suffixIndex < suffixes.length - 1) {
|
|
344
|
+
abbreviated /= 1e3;
|
|
345
|
+
suffixIndex++;
|
|
346
|
+
}
|
|
347
|
+
let fixed = abbreviated.toFixed(decimals);
|
|
348
|
+
if (fixed.includes(".")) {
|
|
349
|
+
fixed = fixed.replace(/\.?0+$/, "");
|
|
350
|
+
}
|
|
351
|
+
return fixed + suffixes[suffixIndex];
|
|
352
|
+
}
|
|
353
|
+
function escapeHtml(value) {
|
|
354
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
309
355
|
}
|
|
310
356
|
function createTooltipFormatter(options = {}) {
|
|
311
357
|
const { showSeriesName = true, valueFormatter = formatNumber } = options;
|
|
@@ -320,9 +366,9 @@ function createTooltipFormatter(options = {}) {
|
|
|
320
366
|
const formatted = valueFormatter(value);
|
|
321
367
|
const percent = param.percent ? ` (${param.percent.toFixed(1)}%)` : "";
|
|
322
368
|
if (showSeriesName && param.seriesName) {
|
|
323
|
-
return `${param.seriesName}: ${formatted}${percent}`;
|
|
369
|
+
return `${escapeHtml(param.seriesName)}: ${escapeHtml(formatted)}${percent}`;
|
|
324
370
|
}
|
|
325
|
-
return `${param.name}: ${formatted}${percent}`;
|
|
371
|
+
return `${escapeHtml(param.name)}: ${escapeHtml(formatted)}${percent}`;
|
|
326
372
|
}).join("<br/>");
|
|
327
373
|
};
|
|
328
374
|
}
|
|
@@ -376,6 +422,9 @@ function generatePieChartDescription(title, data, isDonut = false) {
|
|
|
376
422
|
const total = data.reduce((sum, slice) => sum + slice.value, 0);
|
|
377
423
|
const chartType = isDonut ? "Donut" : "Pie";
|
|
378
424
|
let description = `${title}. ${chartType} chart with ${data.length} segments`;
|
|
425
|
+
if (total === 0) {
|
|
426
|
+
return `${description}. All segments have a value of 0`;
|
|
427
|
+
}
|
|
379
428
|
const sorted = [...data].sort((a, b) => b.value - a.value);
|
|
380
429
|
const topSegments = sorted.slice(0, 3);
|
|
381
430
|
const percentages = topSegments.map((segment) => {
|
|
@@ -396,8 +445,8 @@ function generateScatterChartDescription(title, data) {
|
|
|
396
445
|
return description;
|
|
397
446
|
}
|
|
398
447
|
function generateDataTable(headers, rows) {
|
|
399
|
-
const headerRow = headers.map((h) => `<th scope="col">${h}</th>`).join("");
|
|
400
|
-
const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
|
|
448
|
+
const headerRow = headers.map((h) => `<th scope="col">${escapeHtml(h)}</th>`).join("");
|
|
449
|
+
const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("");
|
|
401
450
|
return `
|
|
402
451
|
<table class="sr-only">
|
|
403
452
|
<thead><tr>${headerRow}</tr></thead>
|
|
@@ -406,11 +455,35 @@ function generateDataTable(headers, rows) {
|
|
|
406
455
|
`;
|
|
407
456
|
}
|
|
408
457
|
|
|
458
|
+
// src/utils/merge.ts
|
|
459
|
+
var ALWAYS_REPLACE = ["series", "graphic"];
|
|
460
|
+
function countComponents(value) {
|
|
461
|
+
if (value == null || value === false) return 0;
|
|
462
|
+
if (Array.isArray(value)) return value.filter((item) => item != null).length;
|
|
463
|
+
return 1;
|
|
464
|
+
}
|
|
465
|
+
function getReplaceMerge(prev, next, isComponentType) {
|
|
466
|
+
const types = new Set(ALWAYS_REPLACE);
|
|
467
|
+
if (prev) {
|
|
468
|
+
const before = prev;
|
|
469
|
+
const after = next;
|
|
470
|
+
for (const key of Object.keys(before)) {
|
|
471
|
+
if (countComponents(before[key]) > countComponents(after[key])) {
|
|
472
|
+
types.add(key);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return [...types].filter((type) => isComponentType(type));
|
|
477
|
+
}
|
|
478
|
+
|
|
409
479
|
exports.THEME_DARK = THEME_DARK;
|
|
410
480
|
exports.THEME_LIGHT = THEME_LIGHT;
|
|
411
481
|
exports.abbreviateNumber = abbreviateNumber;
|
|
482
|
+
exports.colorWithAlpha = colorWithAlpha;
|
|
412
483
|
exports.createTooltipFormatter = createTooltipFormatter;
|
|
484
|
+
exports.darkChartColors = darkChartColors;
|
|
413
485
|
exports.darkTheme = darkTheme;
|
|
486
|
+
exports.escapeHtml = escapeHtml;
|
|
414
487
|
exports.formatCurrency = formatCurrency;
|
|
415
488
|
exports.formatDate = formatDate;
|
|
416
489
|
exports.formatNumber = formatNumber;
|
|
@@ -422,4 +495,7 @@ exports.generateLightTheme = generateLightTheme;
|
|
|
422
495
|
exports.generateLineChartDescription = generateLineChartDescription;
|
|
423
496
|
exports.generatePieChartDescription = generatePieChartDescription;
|
|
424
497
|
exports.generateScatterChartDescription = generateScatterChartDescription;
|
|
498
|
+
exports.getChartColors = getChartColors;
|
|
499
|
+
exports.getReplaceMerge = getReplaceMerge;
|
|
500
|
+
exports.lightChartColors = lightChartColors;
|
|
425
501
|
exports.lightTheme = lightTheme;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './theme/index.cjs';
|
|
1
|
+
export { ChartSemanticColors, EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './theme/index.cjs';
|
|
2
2
|
import { BarChartData, LineChartData, PieSliceData, ScatterChartData } from './types/index.cjs';
|
|
3
3
|
export { AreaChartData, AreaChartProps, AreaSeriesData, BarChartProps, BarSeriesData, BaseChartProps, BaseEventParams, CandlestickChartProps, CandlestickData, CandlestickEventParams, CandlestickItem, DataPointEventParams, DonutChartProps, FunnelChartProps, FunnelSliceData, GaugeChartProps, HeatmapChartProps, HeatmapData, HeatmapEventParams, LineChartProps, LineSeriesData, PieChartProps, RadarChartData, RadarChartProps, RadarIndicator, RadarSeriesData, SankeyChartProps, SankeyData, SankeyEventParams, SankeyLink, SankeyNode, ScatterChartProps, ScatterSeriesData, SliceEventParams, TreemapChartProps, TreemapEventParams, TreemapLevel, TreemapNode } from './types/index.cjs';
|
|
4
4
|
|
|
@@ -25,6 +25,11 @@ declare function formatDate(date: Date | string, options?: Intl.DateTimeFormatOp
|
|
|
25
25
|
* Abbreviate large numbers (e.g., 1000 -> 1K, 1000000 -> 1M)
|
|
26
26
|
*/
|
|
27
27
|
declare function abbreviateNumber(value: number, decimals?: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Escape a value for interpolation into HTML. ECharts renders the string a
|
|
30
|
+
* function tooltip formatter returns as innerHTML without encoding it.
|
|
31
|
+
*/
|
|
32
|
+
declare function escapeHtml(value: unknown): string;
|
|
28
33
|
/**
|
|
29
34
|
* Create a tooltip formatter for ECharts
|
|
30
35
|
*/
|
|
@@ -64,4 +69,26 @@ declare function generateScatterChartDescription(title: string, data: ScatterCha
|
|
|
64
69
|
*/
|
|
65
70
|
declare function generateDataTable(headers: string[], rows: (string | number)[][]): string;
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Helpers for applying a complete ECharts option on top of a previous one.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Work out the `replaceMerge` list for `chart.setOption(next, { replaceMerge })`
|
|
77
|
+
* when `next` is a complete option that supersedes `prev`.
|
|
78
|
+
*
|
|
79
|
+
* `notMerge: true` would also drop removed series and components, but it
|
|
80
|
+
* rebuilds every component, so legend selection, dataZoom ranges and other
|
|
81
|
+
* interaction state reset on each data update. Instead this replaces only:
|
|
82
|
+
*
|
|
83
|
+
* - `series` and `graphic`, so removed series, mark lines and centre labels
|
|
84
|
+
* disappear;
|
|
85
|
+
* - any component type that `next` has fewer of than `prev` (a hidden legend
|
|
86
|
+
* or tooltip, a dropped volume grid and its axes).
|
|
87
|
+
*
|
|
88
|
+
* Everything else is merged, so its state survives. ECharts rejects types it
|
|
89
|
+
* has no component class for, so only types for which `isComponentType`
|
|
90
|
+
* returns true are listed; pass `echarts.ComponentModel.hasClass`.
|
|
91
|
+
*/
|
|
92
|
+
declare function getReplaceMerge(prev: object | null | undefined, next: object, isComponentType: (mainType: string) => boolean): string[];
|
|
93
|
+
|
|
94
|
+
export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './theme/index.js';
|
|
1
|
+
export { ChartSemanticColors, EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './theme/index.js';
|
|
2
2
|
import { BarChartData, LineChartData, PieSliceData, ScatterChartData } from './types/index.js';
|
|
3
3
|
export { AreaChartData, AreaChartProps, AreaSeriesData, BarChartProps, BarSeriesData, BaseChartProps, BaseEventParams, CandlestickChartProps, CandlestickData, CandlestickEventParams, CandlestickItem, DataPointEventParams, DonutChartProps, FunnelChartProps, FunnelSliceData, GaugeChartProps, HeatmapChartProps, HeatmapData, HeatmapEventParams, LineChartProps, LineSeriesData, PieChartProps, RadarChartData, RadarChartProps, RadarIndicator, RadarSeriesData, SankeyChartProps, SankeyData, SankeyEventParams, SankeyLink, SankeyNode, ScatterChartProps, ScatterSeriesData, SliceEventParams, TreemapChartProps, TreemapEventParams, TreemapLevel, TreemapNode } from './types/index.js';
|
|
4
4
|
|
|
@@ -25,6 +25,11 @@ declare function formatDate(date: Date | string, options?: Intl.DateTimeFormatOp
|
|
|
25
25
|
* Abbreviate large numbers (e.g., 1000 -> 1K, 1000000 -> 1M)
|
|
26
26
|
*/
|
|
27
27
|
declare function abbreviateNumber(value: number, decimals?: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* Escape a value for interpolation into HTML. ECharts renders the string a
|
|
30
|
+
* function tooltip formatter returns as innerHTML without encoding it.
|
|
31
|
+
*/
|
|
32
|
+
declare function escapeHtml(value: unknown): string;
|
|
28
33
|
/**
|
|
29
34
|
* Create a tooltip formatter for ECharts
|
|
30
35
|
*/
|
|
@@ -64,4 +69,26 @@ declare function generateScatterChartDescription(title: string, data: ScatterCha
|
|
|
64
69
|
*/
|
|
65
70
|
declare function generateDataTable(headers: string[], rows: (string | number)[][]): string;
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Helpers for applying a complete ECharts option on top of a previous one.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Work out the `replaceMerge` list for `chart.setOption(next, { replaceMerge })`
|
|
77
|
+
* when `next` is a complete option that supersedes `prev`.
|
|
78
|
+
*
|
|
79
|
+
* `notMerge: true` would also drop removed series and components, but it
|
|
80
|
+
* rebuilds every component, so legend selection, dataZoom ranges and other
|
|
81
|
+
* interaction state reset on each data update. Instead this replaces only:
|
|
82
|
+
*
|
|
83
|
+
* - `series` and `graphic`, so removed series, mark lines and centre labels
|
|
84
|
+
* disappear;
|
|
85
|
+
* - any component type that `next` has fewer of than `prev` (a hidden legend
|
|
86
|
+
* or tooltip, a dropped volume grid and its axes).
|
|
87
|
+
*
|
|
88
|
+
* Everything else is merged, so its state survives. ECharts rejects types it
|
|
89
|
+
* has no component class for, so only types for which `isComponentType`
|
|
90
|
+
* returns true are listed; pass `echarts.ComponentModel.hasClass`.
|
|
91
|
+
*/
|
|
92
|
+
declare function getReplaceMerge(prev: object | null | undefined, next: object, isComponentType: (mainType: string) => boolean): string[];
|
|
93
|
+
|
|
94
|
+
export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './chunk-
|
|
1
|
+
export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './chunk-JM72YHZD.js';
|
|
2
2
|
|
|
3
3
|
// src/utils/format.ts
|
|
4
4
|
function formatNumber(value, options) {
|
|
@@ -29,7 +29,18 @@ function abbreviateNumber(value, decimals = 1) {
|
|
|
29
29
|
abbreviated /= 1e3;
|
|
30
30
|
suffixIndex++;
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
if (Math.abs(Number(abbreviated.toFixed(decimals))) >= 1e3 && suffixIndex < suffixes.length - 1) {
|
|
33
|
+
abbreviated /= 1e3;
|
|
34
|
+
suffixIndex++;
|
|
35
|
+
}
|
|
36
|
+
let fixed = abbreviated.toFixed(decimals);
|
|
37
|
+
if (fixed.includes(".")) {
|
|
38
|
+
fixed = fixed.replace(/\.?0+$/, "");
|
|
39
|
+
}
|
|
40
|
+
return fixed + suffixes[suffixIndex];
|
|
41
|
+
}
|
|
42
|
+
function escapeHtml(value) {
|
|
43
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
33
44
|
}
|
|
34
45
|
function createTooltipFormatter(options = {}) {
|
|
35
46
|
const { showSeriesName = true, valueFormatter = formatNumber } = options;
|
|
@@ -44,9 +55,9 @@ function createTooltipFormatter(options = {}) {
|
|
|
44
55
|
const formatted = valueFormatter(value);
|
|
45
56
|
const percent = param.percent ? ` (${param.percent.toFixed(1)}%)` : "";
|
|
46
57
|
if (showSeriesName && param.seriesName) {
|
|
47
|
-
return `${param.seriesName}: ${formatted}${percent}`;
|
|
58
|
+
return `${escapeHtml(param.seriesName)}: ${escapeHtml(formatted)}${percent}`;
|
|
48
59
|
}
|
|
49
|
-
return `${param.name}: ${formatted}${percent}`;
|
|
60
|
+
return `${escapeHtml(param.name)}: ${escapeHtml(formatted)}${percent}`;
|
|
50
61
|
}).join("<br/>");
|
|
51
62
|
};
|
|
52
63
|
}
|
|
@@ -100,6 +111,9 @@ function generatePieChartDescription(title, data, isDonut = false) {
|
|
|
100
111
|
const total = data.reduce((sum, slice) => sum + slice.value, 0);
|
|
101
112
|
const chartType = isDonut ? "Donut" : "Pie";
|
|
102
113
|
let description = `${title}. ${chartType} chart with ${data.length} segments`;
|
|
114
|
+
if (total === 0) {
|
|
115
|
+
return `${description}. All segments have a value of 0`;
|
|
116
|
+
}
|
|
103
117
|
const sorted = [...data].sort((a, b) => b.value - a.value);
|
|
104
118
|
const topSegments = sorted.slice(0, 3);
|
|
105
119
|
const percentages = topSegments.map((segment) => {
|
|
@@ -120,8 +134,8 @@ function generateScatterChartDescription(title, data) {
|
|
|
120
134
|
return description;
|
|
121
135
|
}
|
|
122
136
|
function generateDataTable(headers, rows) {
|
|
123
|
-
const headerRow = headers.map((h) => `<th scope="col">${h}</th>`).join("");
|
|
124
|
-
const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
|
|
137
|
+
const headerRow = headers.map((h) => `<th scope="col">${escapeHtml(h)}</th>`).join("");
|
|
138
|
+
const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("");
|
|
125
139
|
return `
|
|
126
140
|
<table class="sr-only">
|
|
127
141
|
<thead><tr>${headerRow}</tr></thead>
|
|
@@ -130,4 +144,25 @@ function generateDataTable(headers, rows) {
|
|
|
130
144
|
`;
|
|
131
145
|
}
|
|
132
146
|
|
|
133
|
-
|
|
147
|
+
// src/utils/merge.ts
|
|
148
|
+
var ALWAYS_REPLACE = ["series", "graphic"];
|
|
149
|
+
function countComponents(value) {
|
|
150
|
+
if (value == null || value === false) return 0;
|
|
151
|
+
if (Array.isArray(value)) return value.filter((item) => item != null).length;
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
function getReplaceMerge(prev, next, isComponentType) {
|
|
155
|
+
const types = new Set(ALWAYS_REPLACE);
|
|
156
|
+
if (prev) {
|
|
157
|
+
const before = prev;
|
|
158
|
+
const after = next;
|
|
159
|
+
for (const key of Object.keys(before)) {
|
|
160
|
+
if (countComponents(before[key]) > countComponents(after[key])) {
|
|
161
|
+
types.add(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return [...types].filter((type) => isComponentType(type));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
|
package/dist/theme/index.cjs
CHANGED
|
@@ -272,13 +272,52 @@ function generateDarkTheme() {
|
|
|
272
272
|
var lightTheme = generateLightTheme();
|
|
273
273
|
var darkTheme = generateDarkTheme();
|
|
274
274
|
|
|
275
|
-
// src/theme/
|
|
275
|
+
// src/theme/constants.ts
|
|
276
276
|
var THEME_LIGHT = "classic-light";
|
|
277
277
|
var THEME_DARK = "classic-dark";
|
|
278
278
|
|
|
279
|
+
// src/theme/semantic.ts
|
|
280
|
+
var lightChartColors = {
|
|
281
|
+
series: lightTheme.color,
|
|
282
|
+
foreground: themeTokens.semanticColors.light.foreground,
|
|
283
|
+
mutedForeground: themeTokens.semanticColors.light.mutedForeground,
|
|
284
|
+
axisPointerLabel: themeTokens.semanticColors.light.mutedForeground,
|
|
285
|
+
divider: themeTokens.colors.gray[600],
|
|
286
|
+
success: themeTokens.semanticColors.light.success,
|
|
287
|
+
destructive: themeTokens.semanticColors.light.destructive
|
|
288
|
+
};
|
|
289
|
+
var darkChartColors = {
|
|
290
|
+
series: darkTheme.color,
|
|
291
|
+
foreground: themeTokens.semanticColors.dark.foreground,
|
|
292
|
+
mutedForeground: themeTokens.semanticColors.dark.mutedForeground,
|
|
293
|
+
// mutedForeground (gray[400]) is too light under the white label text
|
|
294
|
+
axisPointerLabel: themeTokens.colors.gray[600],
|
|
295
|
+
divider: themeTokens.colors.gray[700],
|
|
296
|
+
success: themeTokens.semanticColors.dark.success,
|
|
297
|
+
destructive: themeTokens.semanticColors.dark.destructive
|
|
298
|
+
};
|
|
299
|
+
function getChartColors(themeName) {
|
|
300
|
+
return themeName === THEME_DARK ? darkChartColors : lightChartColors;
|
|
301
|
+
}
|
|
302
|
+
function colorWithAlpha(color, alpha) {
|
|
303
|
+
let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
|
|
304
|
+
if (!hex) return color;
|
|
305
|
+
if (hex.length === 3) {
|
|
306
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
307
|
+
}
|
|
308
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
309
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
310
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
311
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
312
|
+
}
|
|
313
|
+
|
|
279
314
|
exports.THEME_DARK = THEME_DARK;
|
|
280
315
|
exports.THEME_LIGHT = THEME_LIGHT;
|
|
316
|
+
exports.colorWithAlpha = colorWithAlpha;
|
|
317
|
+
exports.darkChartColors = darkChartColors;
|
|
281
318
|
exports.darkTheme = darkTheme;
|
|
282
319
|
exports.generateDarkTheme = generateDarkTheme;
|
|
283
320
|
exports.generateLightTheme = generateLightTheme;
|
|
321
|
+
exports.getChartColors = getChartColors;
|
|
322
|
+
exports.lightChartColors = lightChartColors;
|
|
284
323
|
exports.lightTheme = lightTheme;
|
package/dist/theme/index.d.cts
CHANGED
|
@@ -133,11 +133,42 @@ declare const lightTheme: EChartsTheme;
|
|
|
133
133
|
declare const darkTheme: EChartsTheme;
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* Semantic chart colours
|
|
137
|
+
*
|
|
138
|
+
* Colours that chart components set per series or per element (and so cannot
|
|
139
|
+
* come from the registered ECharts theme), derived from the design tokens.
|
|
140
|
+
*/
|
|
141
|
+
interface ChartSemanticColors {
|
|
142
|
+
/** Series palette, in the same order as the ECharts theme `color` array */
|
|
143
|
+
series: string[];
|
|
144
|
+
/** Primary text, e.g. a donut's centre value */
|
|
145
|
+
foreground: string;
|
|
146
|
+
/** Secondary text and marks: labels, gauge ticks, volume bars */
|
|
147
|
+
mutedForeground: string;
|
|
148
|
+
/** Background of axis-pointer labels (rendered with white text) */
|
|
149
|
+
axisPointerLabel: string;
|
|
150
|
+
/** Borders between top-level blocks, e.g. treemap groups */
|
|
151
|
+
divider: string;
|
|
152
|
+
/** Positive values */
|
|
153
|
+
success: string;
|
|
154
|
+
/** Negative values */
|
|
155
|
+
destructive: string;
|
|
156
|
+
}
|
|
157
|
+
declare const lightChartColors: ChartSemanticColors;
|
|
158
|
+
declare const darkChartColors: ChartSemanticColors;
|
|
159
|
+
/**
|
|
160
|
+
* Get the semantic chart colours for a registered theme name
|
|
161
|
+
* (`THEME_LIGHT` / `THEME_DARK`). Unknown names fall back to light.
|
|
162
|
+
*/
|
|
163
|
+
declare function getChartColors(themeName: string): ChartSemanticColors;
|
|
164
|
+
/**
|
|
165
|
+
* Apply an alpha channel to a hex colour (`#rgb` or `#rrggbb`), returning
|
|
166
|
+
* `rgba(...)`. Any other colour string is returned unchanged.
|
|
137
167
|
*/
|
|
168
|
+
declare function colorWithAlpha(color: string, alpha: number): string;
|
|
138
169
|
|
|
139
170
|
/** Theme name constants */
|
|
140
171
|
declare const THEME_LIGHT = "classic-light";
|
|
141
172
|
declare const THEME_DARK = "classic-dark";
|
|
142
173
|
|
|
143
|
-
export { type EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme };
|
|
174
|
+
export { type ChartSemanticColors, type EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -133,11 +133,42 @@ declare const lightTheme: EChartsTheme;
|
|
|
133
133
|
declare const darkTheme: EChartsTheme;
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* Semantic chart colours
|
|
137
|
+
*
|
|
138
|
+
* Colours that chart components set per series or per element (and so cannot
|
|
139
|
+
* come from the registered ECharts theme), derived from the design tokens.
|
|
140
|
+
*/
|
|
141
|
+
interface ChartSemanticColors {
|
|
142
|
+
/** Series palette, in the same order as the ECharts theme `color` array */
|
|
143
|
+
series: string[];
|
|
144
|
+
/** Primary text, e.g. a donut's centre value */
|
|
145
|
+
foreground: string;
|
|
146
|
+
/** Secondary text and marks: labels, gauge ticks, volume bars */
|
|
147
|
+
mutedForeground: string;
|
|
148
|
+
/** Background of axis-pointer labels (rendered with white text) */
|
|
149
|
+
axisPointerLabel: string;
|
|
150
|
+
/** Borders between top-level blocks, e.g. treemap groups */
|
|
151
|
+
divider: string;
|
|
152
|
+
/** Positive values */
|
|
153
|
+
success: string;
|
|
154
|
+
/** Negative values */
|
|
155
|
+
destructive: string;
|
|
156
|
+
}
|
|
157
|
+
declare const lightChartColors: ChartSemanticColors;
|
|
158
|
+
declare const darkChartColors: ChartSemanticColors;
|
|
159
|
+
/**
|
|
160
|
+
* Get the semantic chart colours for a registered theme name
|
|
161
|
+
* (`THEME_LIGHT` / `THEME_DARK`). Unknown names fall back to light.
|
|
162
|
+
*/
|
|
163
|
+
declare function getChartColors(themeName: string): ChartSemanticColors;
|
|
164
|
+
/**
|
|
165
|
+
* Apply an alpha channel to a hex colour (`#rgb` or `#rrggbb`), returning
|
|
166
|
+
* `rgba(...)`. Any other colour string is returned unchanged.
|
|
137
167
|
*/
|
|
168
|
+
declare function colorWithAlpha(color: string, alpha: number): string;
|
|
138
169
|
|
|
139
170
|
/** Theme name constants */
|
|
140
171
|
declare const THEME_LIGHT = "classic-light";
|
|
141
172
|
declare const THEME_DARK = "classic-dark";
|
|
142
173
|
|
|
143
|
-
export { type EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme };
|
|
174
|
+
export { type ChartSemanticColors, type EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
|
package/dist/theme/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from '../chunk-
|
|
1
|
+
export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from '../chunk-JM72YHZD.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classic-homes/charts-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Core utilities and theme for Classic Theme charts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"build": "tsup",
|
|
32
32
|
"dev": "tsup --watch",
|
|
33
33
|
"clean": "node ../../scripts/rm.mjs dist",
|
|
34
|
-
"typecheck": "tsc --noEmit"
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@classic-homes/theme-tokens": "*"
|