@opendata-ai/openchart-core 7.1.4 → 7.2.1
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/index.d.ts +31 -1
- package/dist/index.js +43 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/colors/palettes.ts +11 -8
- package/src/theme/__tests__/resolve.test.ts +20 -0
- package/src/theme/defaults.ts +2 -0
- package/src/theme/resolve.ts +28 -0
- package/src/types/spec.ts +23 -0
- package/src/types/theme.ts +4 -0
package/package.json
CHANGED
package/src/colors/palettes.ts
CHANGED
|
@@ -50,6 +50,9 @@ export const ACHROMATIC_RAMP = {
|
|
|
50
50
|
* -> sRGB pipeline. Documented OKLCH source values are the contract; if
|
|
51
51
|
* the conversion math changes, regenerate from the source rather than
|
|
52
52
|
* editing hex literals directly.
|
|
53
|
+
*
|
|
54
|
+
* Tuned at L≈0.65, C≈0.20 (vs prior L≈0.70, C≈0.15) for more vivid,
|
|
55
|
+
* saturated color on dark backgrounds where the lighter pastels read soft.
|
|
53
56
|
*/
|
|
54
57
|
// Order interleaves hues so adjacent slots sit at least ~70° apart on the
|
|
55
58
|
// OKLCH wheel. Cyan and teal differ by only ~10° and read as the same color
|
|
@@ -57,14 +60,14 @@ export const ACHROMATIC_RAMP = {
|
|
|
57
60
|
// teal slot is pushed deep into the ramp where it won't neighbor cyan.
|
|
58
61
|
export const CATEGORICAL_PALETTE = [
|
|
59
62
|
'#06b6d4', // cyan, primary accent (sRGB literal, ~205°)
|
|
60
|
-
'#
|
|
61
|
-
'#
|
|
62
|
-
'#
|
|
63
|
-
'#
|
|
64
|
-
'#
|
|
65
|
-
'#
|
|
66
|
-
'#
|
|
67
|
-
'#
|
|
63
|
+
'#ee4a73', // rose — oklch(65% 0.20 10)
|
|
64
|
+
'#00b054', // emerald — oklch(65% 0.20 155)
|
|
65
|
+
'#a46bf5', // violet — oklch(65% 0.20 300)
|
|
66
|
+
'#e07d00', // amber — oklch(68% 0.19 70)
|
|
67
|
+
'#0091ff', // sky — oklch(65% 0.20 250)
|
|
68
|
+
'#f36000', // orange — oklch(67% 0.20 45)
|
|
69
|
+
'#6f7dff', // indigo — oklch(65% 0.20 275)
|
|
70
|
+
'#00afbf', // teal — oklch(65% 0.20 200)
|
|
68
71
|
] as const;
|
|
69
72
|
|
|
70
73
|
export type CategoricalPalette = typeof CATEGORICAL_PALETTE;
|
|
@@ -83,6 +83,26 @@ describe('resolveTheme', () => {
|
|
|
83
83
|
expect(resolved.fonts.family).toBe(DEFAULT_THEME.fonts.family);
|
|
84
84
|
expect(resolved.spacing.padding).toBe(DEFAULT_THEME.spacing.padding);
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
it('fonts.sizes overrides propagate to chrome element fontSizes', () => {
|
|
88
|
+
const resolved = resolveTheme({
|
|
89
|
+
fonts: { sizes: { title: 40, subtitle: 20, small: 16, axisTick: 16 } },
|
|
90
|
+
});
|
|
91
|
+
expect(resolved.chrome.title.fontSize).toBe(40);
|
|
92
|
+
expect(resolved.chrome.subtitle.fontSize).toBe(20);
|
|
93
|
+
expect(resolved.chrome.source.fontSize).toBe(16);
|
|
94
|
+
expect(resolved.chrome.byline.fontSize).toBe(16);
|
|
95
|
+
expect(resolved.chrome.footer.fontSize).toBe(16);
|
|
96
|
+
expect(resolved.chrome.eyebrow.fontSize).toBe(DEFAULT_THEME.chrome.eyebrow.fontSize);
|
|
97
|
+
expect(resolved.fonts.sizes.body).toBe(DEFAULT_THEME.fonts.sizes.body);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('fonts.sizes partial override only changes specified sizes', () => {
|
|
101
|
+
const resolved = resolveTheme({ fonts: { sizes: { title: 36 } } });
|
|
102
|
+
expect(resolved.chrome.title.fontSize).toBe(36);
|
|
103
|
+
expect(resolved.chrome.subtitle.fontSize).toBe(DEFAULT_THEME.chrome.subtitle.fontSize);
|
|
104
|
+
expect(resolved.chrome.source.fontSize).toBe(DEFAULT_THEME.chrome.source.fontSize);
|
|
105
|
+
});
|
|
86
106
|
});
|
|
87
107
|
|
|
88
108
|
// ---------------------------------------------------------------------------
|
package/src/theme/defaults.ts
CHANGED
package/src/theme/resolve.ts
CHANGED
|
@@ -72,6 +72,7 @@ function themeConfigToPartial(config: ThemeConfig): Partial<Theme> {
|
|
|
72
72
|
const fonts: Partial<Theme['fonts']> = {};
|
|
73
73
|
if (config.fonts.family) fonts.family = config.fonts.family;
|
|
74
74
|
if (config.fonts.mono) fonts.mono = config.fonts.mono;
|
|
75
|
+
if (config.fonts.sizes) fonts.sizes = config.fonts.sizes as Theme['fonts']['sizes'];
|
|
75
76
|
partial.fonts = fonts as Theme['fonts'];
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -79,6 +80,9 @@ function themeConfigToPartial(config: ThemeConfig): Partial<Theme> {
|
|
|
79
80
|
const spacing: Partial<Theme['spacing']> = {};
|
|
80
81
|
if (config.spacing.padding !== undefined) spacing.padding = config.spacing.padding;
|
|
81
82
|
if (config.spacing.chromeGap !== undefined) spacing.chromeGap = config.spacing.chromeGap;
|
|
83
|
+
if (config.spacing.xAxisHeight !== undefined) spacing.xAxisHeight = config.spacing.xAxisHeight;
|
|
84
|
+
if (config.spacing.xAxisLabelPadding !== undefined)
|
|
85
|
+
spacing.xAxisLabelPadding = config.spacing.xAxisLabelPadding;
|
|
82
86
|
partial.spacing = spacing as Theme['spacing'];
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -198,6 +202,30 @@ function adjustOpacity(hex: string, opacity: number): string {
|
|
|
198
202
|
export function resolveTheme(userTheme?: ThemeConfig, base: Theme = DEFAULT_THEME): ResolvedTheme {
|
|
199
203
|
let merged: Theme = userTheme ? deepMerge(base, themeConfigToPartial(userTheme)) : { ...base };
|
|
200
204
|
|
|
205
|
+
// Propagate fonts.sizes overrides onto the chrome element fontSizes.
|
|
206
|
+
// The chrome defaults hard-code fontSize independently from fonts.sizes,
|
|
207
|
+
// so we sync them here after merging so a single sizes override applies everywhere.
|
|
208
|
+
if (userTheme?.fonts?.sizes) {
|
|
209
|
+
const s = userTheme.fonts.sizes;
|
|
210
|
+
merged = {
|
|
211
|
+
...merged,
|
|
212
|
+
chrome: {
|
|
213
|
+
...merged.chrome,
|
|
214
|
+
...(s.title !== undefined && {
|
|
215
|
+
title: { ...merged.chrome.title, fontSize: s.title },
|
|
216
|
+
}),
|
|
217
|
+
...(s.subtitle !== undefined && {
|
|
218
|
+
subtitle: { ...merged.chrome.subtitle, fontSize: s.subtitle },
|
|
219
|
+
}),
|
|
220
|
+
...(s.small !== undefined && {
|
|
221
|
+
source: { ...merged.chrome.source, fontSize: s.small },
|
|
222
|
+
byline: { ...merged.chrome.byline, fontSize: s.small },
|
|
223
|
+
footer: { ...merged.chrome.footer, fontSize: s.small },
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
201
229
|
// Auto-adapt chrome for dark backgrounds
|
|
202
230
|
const dark = isDarkBackground(merged.colors.background);
|
|
203
231
|
if (dark) {
|
package/src/types/spec.ts
CHANGED
|
@@ -246,6 +246,8 @@ export interface AxisConfig {
|
|
|
246
246
|
labelPadding?: number;
|
|
247
247
|
/** Color override for axis tick labels and title. Useful in dual-axis charts to match axis color to its series. */
|
|
248
248
|
labelColor?: string;
|
|
249
|
+
/** Literal string appended to every formatted tick label. e.g. "B" gives "$4.5B" when format is "$,.1~f". */
|
|
250
|
+
labelSuffix?: string;
|
|
249
251
|
/** Secondary data field to display alongside each tick label. Renders in lighter weight/color. Only effective on categorical y-axis labels (horizontal bar charts). */
|
|
250
252
|
labelField?: string;
|
|
251
253
|
/**
|
|
@@ -810,6 +812,19 @@ export interface ThemeConfig {
|
|
|
810
812
|
family?: string;
|
|
811
813
|
/** Monospace font family (for tabular numbers). */
|
|
812
814
|
mono?: string;
|
|
815
|
+
/** Font size overrides in pixels. Partial — only specified keys are overridden. */
|
|
816
|
+
sizes?: {
|
|
817
|
+
/** Chart title. Default: 26. */
|
|
818
|
+
title?: number;
|
|
819
|
+
/** Subtitle below the title. Default: 14. */
|
|
820
|
+
subtitle?: number;
|
|
821
|
+
/** Body text (tooltips, legend labels). Default: 13. */
|
|
822
|
+
body?: number;
|
|
823
|
+
/** Small text (source line, footer). Default: 11. */
|
|
824
|
+
small?: number;
|
|
825
|
+
/** Axis tick labels. Default: 11. */
|
|
826
|
+
axisTick?: number;
|
|
827
|
+
};
|
|
813
828
|
};
|
|
814
829
|
/** Spacing overrides in pixels. */
|
|
815
830
|
spacing?: {
|
|
@@ -817,6 +832,10 @@ export interface ThemeConfig {
|
|
|
817
832
|
padding?: number;
|
|
818
833
|
/** Gap between chrome elements (title to subtitle, etc.). */
|
|
819
834
|
chromeGap?: number;
|
|
835
|
+
/** Height reserved below chart area for x-axis tick labels. Increase when large axisTick font sizes cause label clipping. */
|
|
836
|
+
xAxisHeight?: number;
|
|
837
|
+
/** Gap in pixels between the x-axis line and tick label text. Increase when larger axisTick fonts sit too close to the axis line. */
|
|
838
|
+
xAxisLabelPadding?: number;
|
|
820
839
|
};
|
|
821
840
|
/** Border radius for chart container and tooltips. */
|
|
822
841
|
borderRadius?: number;
|
|
@@ -863,10 +882,14 @@ export interface LabelConfig {
|
|
|
863
882
|
format?: string;
|
|
864
883
|
/** Literal string prepended to each formatted label value (e.g. "-" or "$"). */
|
|
865
884
|
prefix?: string;
|
|
885
|
+
/** Literal string appended to each formatted label value (e.g. "%" or "x"). */
|
|
886
|
+
suffix?: string;
|
|
866
887
|
/** Fixed CSS color for all labels. Overrides the default fill-derived color. */
|
|
867
888
|
color?: string;
|
|
868
889
|
/** Per-series pixel offsets for fine-tuning label positions, keyed by series name. */
|
|
869
890
|
offsets?: Record<string, AnnotationOffset>;
|
|
891
|
+
/** Font size in pixels for bar/column value labels. */
|
|
892
|
+
fontSize?: number;
|
|
870
893
|
}
|
|
871
894
|
|
|
872
895
|
/** Shorthand: `false` disables all labels, `true` uses defaults, or pass a full config object. */
|
package/src/types/theme.ts
CHANGED
|
@@ -93,6 +93,10 @@ export interface ThemeSpacing {
|
|
|
93
93
|
chartToFooter: number;
|
|
94
94
|
/** Internal padding within the chart area (axes margins). */
|
|
95
95
|
axisMargin: number;
|
|
96
|
+
/** Height reserved below the chart area for x-axis tick labels (and optional axis title). */
|
|
97
|
+
xAxisHeight: number;
|
|
98
|
+
/** Gap in pixels between the x-axis line and the top of non-rotated tick label text. */
|
|
99
|
+
xAxisLabelPadding: number;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
// ---------------------------------------------------------------------------
|