@opendata-ai/openchart-core 6.28.6 → 7.0.2
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 +673 -111
- package/dist/index.js +163 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/colors/__tests__/contrast.test.ts +2 -2
- package/src/colors/__tests__/palettes.test.ts +22 -2
- package/src/colors/index.ts +1 -0
- package/src/colors/palettes.ts +52 -20
- package/src/helpers/spec-builders.ts +3 -1
- package/src/layout/chrome.ts +91 -10
- package/src/styles/base.css +11 -1
- package/src/styles/chrome.css +127 -2
- package/src/styles/dark.css +27 -10
- package/src/styles/tokens.css +57 -14
- package/src/styles/tooltip.css +66 -22
- package/src/theme/__tests__/dark-mode.test.ts +53 -8
- package/src/theme/__tests__/defaults.test.ts +43 -17
- package/src/theme/dark-mode.ts +76 -16
- package/src/theme/defaults.ts +44 -30
- package/src/theme/index.ts +1 -1
- package/src/theme/resolve.ts +2 -0
- package/src/types/__tests__/spec.test.ts +85 -18
- package/src/types/index.ts +16 -0
- package/src/types/layout.ts +151 -5
- package/src/types/spec.ts +519 -85
- package/src/types/theme.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -699,27 +699,43 @@ function findAccessibleColor(baseColor, bg, targetRatio = 4.5) {
|
|
|
699
699
|
}
|
|
700
700
|
|
|
701
701
|
// src/colors/palettes.ts
|
|
702
|
+
var ACHROMATIC_RAMP = {
|
|
703
|
+
fg: "#f7f8f8",
|
|
704
|
+
// primary text
|
|
705
|
+
fg2: "#d0d6e0",
|
|
706
|
+
// body text
|
|
707
|
+
fgMuted: "#a1a1aa",
|
|
708
|
+
// secondary series (zinc-400)
|
|
709
|
+
fgSubtle: "#71717a",
|
|
710
|
+
// tertiary series (zinc-500)
|
|
711
|
+
fgFaint: "#52525b",
|
|
712
|
+
// quaternary series (zinc-600)
|
|
713
|
+
secondary: "#27272a",
|
|
714
|
+
// hover / raised surface
|
|
715
|
+
card: "#111113",
|
|
716
|
+
// card surface
|
|
717
|
+
bg: "#09090b"
|
|
718
|
+
// canvas
|
|
719
|
+
};
|
|
702
720
|
var CATEGORICAL_PALETTE = [
|
|
703
|
-
"#
|
|
704
|
-
//
|
|
705
|
-
"#
|
|
706
|
-
//
|
|
707
|
-
"#
|
|
708
|
-
//
|
|
709
|
-
"#
|
|
710
|
-
//
|
|
711
|
-
"#
|
|
712
|
-
//
|
|
713
|
-
"#
|
|
714
|
-
//
|
|
715
|
-
"#
|
|
716
|
-
//
|
|
717
|
-
"#
|
|
718
|
-
//
|
|
719
|
-
"#
|
|
720
|
-
//
|
|
721
|
-
"#858078"
|
|
722
|
-
// warm gray
|
|
721
|
+
"#06b6d4",
|
|
722
|
+
// cyan, primary accent (sRGB literal, ~205°)
|
|
723
|
+
"#eb7289",
|
|
724
|
+
// rose — oklch(70% 0.15 10)
|
|
725
|
+
"#3bb974",
|
|
726
|
+
// emerald — oklch(70% 0.15 155)
|
|
727
|
+
"#ad87ed",
|
|
728
|
+
// violet — oklch(70% 0.15 300)
|
|
729
|
+
"#e69c3a",
|
|
730
|
+
// amber — oklch(75% 0.14 70)
|
|
731
|
+
"#4ba3f7",
|
|
732
|
+
// sky — oklch(70% 0.15 250)
|
|
733
|
+
"#eb8656",
|
|
734
|
+
// orange — oklch(72% 0.14 45)
|
|
735
|
+
"#8494fa",
|
|
736
|
+
// indigo — oklch(70% 0.15 275)
|
|
737
|
+
"#00b9c3"
|
|
738
|
+
// teal — oklch(70% 0.15 200)
|
|
723
739
|
];
|
|
724
740
|
var SEQUENTIAL_BLUE = {
|
|
725
741
|
name: "blue",
|
|
@@ -792,9 +808,17 @@ var DIVERGING_PALETTES = {
|
|
|
792
808
|
};
|
|
793
809
|
|
|
794
810
|
// src/theme/dark-mode.ts
|
|
795
|
-
var DARK_BG =
|
|
796
|
-
var DARK_TEXT =
|
|
811
|
+
var DARK_BG = ACHROMATIC_RAMP.bg;
|
|
812
|
+
var DARK_TEXT = ACHROMATIC_RAMP.fg;
|
|
797
813
|
function adaptColorForDarkMode(color2, lightBg, darkBg) {
|
|
814
|
+
if (rgb(color2) == null) {
|
|
815
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
816
|
+
console.warn(
|
|
817
|
+
`[openchart] adaptColorForDarkMode: unparseable color "${color2}", returning unchanged. Use precomputed sRGB hex.`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
return color2;
|
|
821
|
+
}
|
|
798
822
|
const originalRatio = contrastRatio(color2, lightBg);
|
|
799
823
|
const c = hsl(color2);
|
|
800
824
|
if (c == null || Number.isNaN(c.h)) {
|
|
@@ -840,9 +864,10 @@ function adaptTheme(theme) {
|
|
|
840
864
|
const alreadyDark = inputBg === "transparent" || _luminanceFromHex(inputBg) < 0.2;
|
|
841
865
|
const darkBg = alreadyDark ? inputBg : DARK_BG;
|
|
842
866
|
const darkText = alreadyDark ? theme.colors.text : DARK_TEXT;
|
|
843
|
-
const darkGridline = alreadyDark ? theme.colors.gridline : "
|
|
844
|
-
const darkAxis = alreadyDark ? theme.colors.axis : "#
|
|
845
|
-
const
|
|
867
|
+
const darkGridline = alreadyDark ? theme.colors.gridline : "rgba(255,255,255,0.05)";
|
|
868
|
+
const darkAxis = alreadyDark ? theme.colors.axis : "#a1a1aa";
|
|
869
|
+
const darkMuted = ACHROMATIC_RAMP.fgMuted;
|
|
870
|
+
const categorical = theme.colors.categorical;
|
|
846
871
|
return {
|
|
847
872
|
...theme,
|
|
848
873
|
isDark: true,
|
|
@@ -852,16 +877,24 @@ function adaptTheme(theme) {
|
|
|
852
877
|
text: darkText,
|
|
853
878
|
gridline: darkGridline,
|
|
854
879
|
axis: darkAxis,
|
|
855
|
-
annotationFill: "rgba(255,255,255,0.
|
|
856
|
-
annotationText:
|
|
857
|
-
categorical
|
|
880
|
+
annotationFill: "rgba(255,255,255,0.06)",
|
|
881
|
+
annotationText: darkMuted,
|
|
882
|
+
categorical,
|
|
883
|
+
// Sparkline trend colors tuned for dark surfaces: teal-leaning green
|
|
884
|
+
// and coral red read better than the saturated light-mode tokens.
|
|
885
|
+
// Any non-default value is treated as a user override and preserved.
|
|
886
|
+
positive: theme.colors.positive !== "#16a34a" ? theme.colors.positive : "#34d399",
|
|
887
|
+
negative: theme.colors.negative !== "#dc2626" ? theme.colors.negative : "#f87171"
|
|
858
888
|
},
|
|
859
889
|
chrome: {
|
|
890
|
+
// Eyebrow keeps its accent tint (cyan in both modes); the other
|
|
891
|
+
// chrome elements desaturate to a muted gray on the dark canvas.
|
|
892
|
+
eyebrow: theme.chrome.eyebrow,
|
|
860
893
|
title: { ...theme.chrome.title, color: darkText },
|
|
861
|
-
subtitle: { ...theme.chrome.subtitle, color:
|
|
862
|
-
source: { ...theme.chrome.source, color:
|
|
863
|
-
byline: { ...theme.chrome.byline, color:
|
|
864
|
-
footer: { ...theme.chrome.footer, color:
|
|
894
|
+
subtitle: { ...theme.chrome.subtitle, color: darkMuted },
|
|
895
|
+
source: { ...theme.chrome.source, color: darkMuted },
|
|
896
|
+
byline: { ...theme.chrome.byline, color: darkMuted },
|
|
897
|
+
footer: { ...theme.chrome.footer, color: darkMuted }
|
|
865
898
|
}
|
|
866
899
|
};
|
|
867
900
|
}
|
|
@@ -873,26 +906,31 @@ var DEFAULT_THEME = {
|
|
|
873
906
|
sequential: SEQUENTIAL_PALETTES,
|
|
874
907
|
diverging: DIVERGING_PALETTES,
|
|
875
908
|
background: "#ffffff",
|
|
876
|
-
text: "#
|
|
877
|
-
gridline: "
|
|
878
|
-
axis
|
|
909
|
+
text: "#09090b",
|
|
910
|
+
gridline: "rgba(0,0,0,0.06)",
|
|
911
|
+
// Used for axis lines/ticks AND axis tick label fill. Must clear WCAG AA
|
|
912
|
+
// contrast (4.5:1) on white because tick labels are rendered with this
|
|
913
|
+
// color. Zinc-500 hits ~5.7:1.
|
|
914
|
+
axis: "#71717a",
|
|
879
915
|
annotationFill: "rgba(0,0,0,0.04)",
|
|
880
|
-
annotationText: "#
|
|
916
|
+
annotationText: "#71717a",
|
|
917
|
+
positive: "#16a34a",
|
|
918
|
+
negative: "#dc2626"
|
|
881
919
|
},
|
|
882
920
|
fonts: {
|
|
883
|
-
family: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
921
|
+
family: '"Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
884
922
|
mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
|
|
885
923
|
sizes: {
|
|
886
|
-
title:
|
|
887
|
-
subtitle:
|
|
924
|
+
title: 26,
|
|
925
|
+
subtitle: 14,
|
|
888
926
|
body: 13,
|
|
889
927
|
small: 11,
|
|
890
928
|
axisTick: 11
|
|
891
929
|
},
|
|
892
930
|
weights: {
|
|
893
931
|
normal: 400,
|
|
894
|
-
medium:
|
|
895
|
-
semibold:
|
|
932
|
+
medium: 510,
|
|
933
|
+
semibold: 590,
|
|
896
934
|
bold: 700
|
|
897
935
|
}
|
|
898
936
|
},
|
|
@@ -903,37 +941,43 @@ var DEFAULT_THEME = {
|
|
|
903
941
|
chartToFooter: 8,
|
|
904
942
|
axisMargin: 6
|
|
905
943
|
},
|
|
906
|
-
borderRadius:
|
|
944
|
+
borderRadius: 2,
|
|
907
945
|
chrome: {
|
|
946
|
+
eyebrow: {
|
|
947
|
+
fontSize: 11,
|
|
948
|
+
fontWeight: 510,
|
|
949
|
+
color: "#06b6d4",
|
|
950
|
+
lineHeight: 1.4
|
|
951
|
+
},
|
|
908
952
|
title: {
|
|
909
|
-
fontSize:
|
|
910
|
-
fontWeight:
|
|
911
|
-
color: "#
|
|
912
|
-
lineHeight: 1.
|
|
953
|
+
fontSize: 26,
|
|
954
|
+
fontWeight: 590,
|
|
955
|
+
color: "#09090b",
|
|
956
|
+
lineHeight: 1.15
|
|
913
957
|
},
|
|
914
958
|
subtitle: {
|
|
915
|
-
fontSize:
|
|
959
|
+
fontSize: 14,
|
|
916
960
|
fontWeight: 400,
|
|
917
|
-
color: "#
|
|
918
|
-
lineHeight: 1.
|
|
961
|
+
color: "#71717a",
|
|
962
|
+
lineHeight: 1.45
|
|
919
963
|
},
|
|
920
964
|
source: {
|
|
921
|
-
fontSize:
|
|
965
|
+
fontSize: 11,
|
|
922
966
|
fontWeight: 400,
|
|
923
|
-
color: "#
|
|
924
|
-
lineHeight: 1.
|
|
967
|
+
color: "#71717a",
|
|
968
|
+
lineHeight: 1.4
|
|
925
969
|
},
|
|
926
970
|
byline: {
|
|
927
|
-
fontSize:
|
|
971
|
+
fontSize: 11,
|
|
928
972
|
fontWeight: 400,
|
|
929
|
-
color: "#
|
|
930
|
-
lineHeight: 1.
|
|
973
|
+
color: "#71717a",
|
|
974
|
+
lineHeight: 1.4
|
|
931
975
|
},
|
|
932
976
|
footer: {
|
|
933
|
-
fontSize:
|
|
977
|
+
fontSize: 11,
|
|
934
978
|
fontWeight: 400,
|
|
935
|
-
color: "#
|
|
936
|
-
lineHeight: 1.
|
|
979
|
+
color: "#71717a",
|
|
980
|
+
lineHeight: 1.4
|
|
937
981
|
}
|
|
938
982
|
}
|
|
939
983
|
};
|
|
@@ -1003,6 +1047,8 @@ function adaptChromeForDarkBg(theme, textColor) {
|
|
|
1003
1047
|
return {
|
|
1004
1048
|
...theme,
|
|
1005
1049
|
chrome: {
|
|
1050
|
+
// Eyebrow keeps its accent tint regardless of surface mode.
|
|
1051
|
+
eyebrow: theme.chrome.eyebrow,
|
|
1006
1052
|
title: {
|
|
1007
1053
|
...theme.chrome.title,
|
|
1008
1054
|
color: theme.chrome.title.color === light.title.color ? textColor : theme.chrome.title.color
|
|
@@ -1153,7 +1199,7 @@ function estimateLineCount(text, style, maxWidth, measureText) {
|
|
|
1153
1199
|
}
|
|
1154
1200
|
return lines;
|
|
1155
1201
|
}
|
|
1156
|
-
function computeChrome(chrome, theme, width, measureText, chromeMode = "full", padding, watermark = true) {
|
|
1202
|
+
function computeChrome(chrome, theme, width, measureText, chromeMode = "full", padding, watermark = true, bottomLegendHeight = 0) {
|
|
1157
1203
|
if (!chrome || chromeMode === "hidden") {
|
|
1158
1204
|
return { topHeight: 0, bottomHeight: 0 };
|
|
1159
1205
|
}
|
|
@@ -1164,6 +1210,26 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1164
1210
|
const fontFamily = theme.fonts.family;
|
|
1165
1211
|
let topY = pad2;
|
|
1166
1212
|
const topElements = {};
|
|
1213
|
+
const eyebrowNorm = chromeMode === "compact" ? null : normalizeChromeText(chrome.eyebrow);
|
|
1214
|
+
if (eyebrowNorm) {
|
|
1215
|
+
const style = buildTextStyle(
|
|
1216
|
+
theme.chrome.eyebrow,
|
|
1217
|
+
fontFamily,
|
|
1218
|
+
theme.chrome.eyebrow.color,
|
|
1219
|
+
width,
|
|
1220
|
+
eyebrowNorm.style
|
|
1221
|
+
);
|
|
1222
|
+
const lineCount = estimateLineCount(eyebrowNorm.text, style, maxWidth, measureText);
|
|
1223
|
+
const element = {
|
|
1224
|
+
text: eyebrowNorm.text,
|
|
1225
|
+
x: pad2 + (eyebrowNorm.offset?.dx ?? 0),
|
|
1226
|
+
y: topY + (eyebrowNorm.offset?.dy ?? 0),
|
|
1227
|
+
maxWidth,
|
|
1228
|
+
style
|
|
1229
|
+
};
|
|
1230
|
+
topElements.eyebrow = element;
|
|
1231
|
+
topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
|
|
1232
|
+
}
|
|
1167
1233
|
const titleNorm = normalizeChromeText(chrome.title);
|
|
1168
1234
|
if (titleNorm) {
|
|
1169
1235
|
const style = buildTextStyle(
|
|
@@ -1204,14 +1270,16 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1204
1270
|
topElements.subtitle = element;
|
|
1205
1271
|
topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
|
|
1206
1272
|
}
|
|
1207
|
-
const hasTopChrome = titleNorm || subtitleNorm;
|
|
1273
|
+
const hasTopChrome = eyebrowNorm || titleNorm || subtitleNorm;
|
|
1208
1274
|
const chromeToChart = width < COMPACT_WIDTH ? Math.min(theme.spacing.chromeToChart, 2) : theme.spacing.chromeToChart;
|
|
1209
1275
|
const topHeight = hasTopChrome ? topY - pad2 + chromeToChart - chromeGap : 0;
|
|
1210
1276
|
if (chromeMode === "compact") {
|
|
1211
1277
|
let compactBottom = 0;
|
|
1212
1278
|
if (watermark && width >= BRAND_MIN_WIDTH) {
|
|
1213
1279
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
1214
|
-
compactBottom = theme.spacing.chartToFooter + brandHeight + pad2;
|
|
1280
|
+
compactBottom = theme.spacing.chartToFooter + brandHeight + pad2 + bottomLegendHeight;
|
|
1281
|
+
} else if (bottomLegendHeight > 0) {
|
|
1282
|
+
compactBottom = bottomLegendHeight;
|
|
1215
1283
|
}
|
|
1216
1284
|
return {
|
|
1217
1285
|
topHeight,
|
|
@@ -1219,7 +1287,9 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1219
1287
|
...topElements
|
|
1220
1288
|
};
|
|
1221
1289
|
}
|
|
1222
|
-
const
|
|
1290
|
+
const brandNorm = normalizeChromeText(chrome.brand);
|
|
1291
|
+
const showWatermark = watermark && !brandNorm;
|
|
1292
|
+
const shouldReserveBrandWidth = (showWatermark || !!brandNorm) && width >= BRAND_MIN_WIDTH;
|
|
1223
1293
|
const bottomMaxWidth = maxWidth - (shouldReserveBrandWidth ? BRAND_RESERVE_WIDTH : 0);
|
|
1224
1294
|
const bottomElements = {};
|
|
1225
1295
|
let bottomHeight = 0;
|
|
@@ -1250,6 +1320,7 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1250
1320
|
}
|
|
1251
1321
|
if (bottomItems.length > 0) {
|
|
1252
1322
|
bottomHeight += theme.spacing.chartToFooter;
|
|
1323
|
+
bottomHeight += bottomLegendHeight;
|
|
1253
1324
|
for (const item of bottomItems) {
|
|
1254
1325
|
const style = buildTextStyle(
|
|
1255
1326
|
item.defaults,
|
|
@@ -1271,7 +1342,7 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1271
1342
|
bottomHeight += height + chromeGap;
|
|
1272
1343
|
}
|
|
1273
1344
|
bottomHeight -= chromeGap;
|
|
1274
|
-
if (
|
|
1345
|
+
if (showWatermark && width >= BRAND_MIN_WIDTH) {
|
|
1275
1346
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
1276
1347
|
const contentBelowFirstItem = bottomHeight - theme.spacing.chartToFooter;
|
|
1277
1348
|
if (brandHeight > contentBelowFirstItem) {
|
|
@@ -1279,15 +1350,41 @@ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", p
|
|
|
1279
1350
|
}
|
|
1280
1351
|
}
|
|
1281
1352
|
bottomHeight += pad2;
|
|
1282
|
-
} else if (
|
|
1353
|
+
} else if (showWatermark && width >= BRAND_MIN_WIDTH) {
|
|
1283
1354
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
1284
|
-
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad2;
|
|
1355
|
+
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad2 + bottomLegendHeight;
|
|
1356
|
+
} else if (bottomLegendHeight > 0) {
|
|
1357
|
+
bottomHeight = bottomLegendHeight;
|
|
1358
|
+
}
|
|
1359
|
+
let brandElement;
|
|
1360
|
+
if (brandNorm && width >= BRAND_MIN_WIDTH) {
|
|
1361
|
+
const brandStyle = buildTextStyle(
|
|
1362
|
+
theme.chrome.footer,
|
|
1363
|
+
fontFamily,
|
|
1364
|
+
theme.chrome.footer.color,
|
|
1365
|
+
width,
|
|
1366
|
+
brandNorm.style
|
|
1367
|
+
);
|
|
1368
|
+
brandStyle.textAnchor = "end";
|
|
1369
|
+
const brandY = bottomItems.length > 0 ? theme.spacing.chartToFooter : theme.spacing.chartToFooter;
|
|
1370
|
+
brandElement = {
|
|
1371
|
+
text: brandNorm.text,
|
|
1372
|
+
x: width - pad2 + (brandNorm.offset?.dx ?? 0),
|
|
1373
|
+
y: brandY + (brandNorm.offset?.dy ?? 0),
|
|
1374
|
+
maxWidth: BRAND_RESERVE_WIDTH,
|
|
1375
|
+
style: brandStyle
|
|
1376
|
+
};
|
|
1377
|
+
if (bottomItems.length === 0) {
|
|
1378
|
+
const brandHeight = estimateTextHeight(brandStyle.fontSize, 1, brandStyle.lineHeight);
|
|
1379
|
+
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad2;
|
|
1380
|
+
}
|
|
1285
1381
|
}
|
|
1286
1382
|
return {
|
|
1287
1383
|
topHeight,
|
|
1288
1384
|
bottomHeight,
|
|
1289
1385
|
...topElements,
|
|
1290
|
-
...bottomElements
|
|
1386
|
+
...bottomElements,
|
|
1387
|
+
...brandElement ? { brand: brandElement } : {}
|
|
1291
1388
|
};
|
|
1292
1389
|
}
|
|
1293
1390
|
|