@opendata-ai/openchart-core 8.8.0 → 8.9.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 +374 -121
- package/dist/index.js +346 -7
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
- package/schema/chart.schema.json +189 -13
- package/schema/table.schema.json +158 -13
- package/schema/vizspec.schema.json +365 -41
package/dist/index.js
CHANGED
|
@@ -1,3 +1,121 @@
|
|
|
1
|
+
// src/helpers/edit-transactions.ts
|
|
2
|
+
function isContainer(value) {
|
|
3
|
+
return Array.isArray(value) || value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
4
|
+
}
|
|
5
|
+
function clone(value) {
|
|
6
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
7
|
+
if (!isContainer(value)) return value;
|
|
8
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, clone(child)]));
|
|
9
|
+
}
|
|
10
|
+
function tokens(path) {
|
|
11
|
+
if (!path.startsWith("/")) throw new Error("Expected a non-root JSON Pointer beginning with /.");
|
|
12
|
+
return path.slice(1).split("/").map((token) => {
|
|
13
|
+
if (/~(?:[^01]|$)/.test(token)) throw new Error("Invalid JSON Pointer escape.");
|
|
14
|
+
const decoded = token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
15
|
+
if (["__proto__", "prototype", "constructor"].includes(decoded)) {
|
|
16
|
+
throw new Error("Unsafe property in edit path.");
|
|
17
|
+
}
|
|
18
|
+
return decoded;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function pointer(parts) {
|
|
22
|
+
return `/${parts.map((part) => part.replace(/~/g, "~0").replace(/\//g, "~1")).join("/")}`;
|
|
23
|
+
}
|
|
24
|
+
function keyAt(parent, token, insert = false) {
|
|
25
|
+
if (!Array.isArray(parent)) return token;
|
|
26
|
+
if (insert && token === "-") return parent.length;
|
|
27
|
+
if (!/^(0|[1-9]\d*)$/.test(token)) throw new Error("Expected an array index.");
|
|
28
|
+
const index = Number(token);
|
|
29
|
+
if (!Number.isSafeInteger(index) || index > parent.length || !insert && index === parent.length) {
|
|
30
|
+
throw new Error("Array index is out of bounds.");
|
|
31
|
+
}
|
|
32
|
+
return index;
|
|
33
|
+
}
|
|
34
|
+
function read(parent, key) {
|
|
35
|
+
return parent[key];
|
|
36
|
+
}
|
|
37
|
+
function checkExpectation(root, parts, expected) {
|
|
38
|
+
if (!expected) return;
|
|
39
|
+
let end = -1;
|
|
40
|
+
for (let i = 0; i + 1 < parts.length; i++) {
|
|
41
|
+
if (parts[i] === "annotations" && /^(0|[1-9]\d*)$/.test(parts[i + 1])) end = i + 2;
|
|
42
|
+
}
|
|
43
|
+
if (end === -1) throw new Error("Annotation precondition requires an annotation path.");
|
|
44
|
+
let target = root;
|
|
45
|
+
for (const part of parts.slice(0, end)) {
|
|
46
|
+
if (!isContainer(target)) throw new Error("Annotation target no longer exists.");
|
|
47
|
+
const key = keyAt(target, part);
|
|
48
|
+
if (!Object.hasOwn(target, key)) throw new Error("Annotation target no longer exists.");
|
|
49
|
+
target = read(target, key);
|
|
50
|
+
}
|
|
51
|
+
if (!isContainer(target) || Array.isArray(target))
|
|
52
|
+
throw new Error("Annotation target is invalid.");
|
|
53
|
+
for (const field of ["id", "type", "text"]) {
|
|
54
|
+
if (Object.hasOwn(expected, field) && target[field] !== expected[field]) {
|
|
55
|
+
throw new Error(`Annotation ${field} changed; discard the stale edit.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function applyEditTransaction(spec, tx) {
|
|
60
|
+
const result = clone(spec);
|
|
61
|
+
const inverse = [];
|
|
62
|
+
for (const [opIndex, op] of tx.ops.entries()) {
|
|
63
|
+
try {
|
|
64
|
+
if (!["set", "add", "remove"].includes(op.op)) throw new Error("Unknown edit operation.");
|
|
65
|
+
if (op.op !== "remove" && !Object.hasOwn(op, "value"))
|
|
66
|
+
throw new Error("Edit value is missing.");
|
|
67
|
+
const parts = tokens(op.path);
|
|
68
|
+
checkExpectation(result, parts, op.expect);
|
|
69
|
+
let parent = result;
|
|
70
|
+
const created = [];
|
|
71
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
72
|
+
if (!isContainer(parent)) throw new Error("Cannot traverse a primitive or non-JSON value.");
|
|
73
|
+
const key2 = keyAt(parent, parts[i]);
|
|
74
|
+
if (!Object.hasOwn(parent, key2)) {
|
|
75
|
+
if (op.op !== "add" || Array.isArray(parent))
|
|
76
|
+
throw new Error("Intermediate container is missing.");
|
|
77
|
+
parent[key2] = /^(0|[1-9]\d*|-)$/.test(parts[i + 1]) ? [] : {};
|
|
78
|
+
created.push(pointer(parts.slice(0, i + 1)));
|
|
79
|
+
}
|
|
80
|
+
parent = read(parent, key2);
|
|
81
|
+
}
|
|
82
|
+
if (!isContainer(parent)) throw new Error("Edit parent is not a JSON container.");
|
|
83
|
+
const key = keyAt(parent, parts.at(-1), op.op === "add");
|
|
84
|
+
const exists = Object.hasOwn(parent, key);
|
|
85
|
+
const old = read(parent, key);
|
|
86
|
+
const path = pointer([...parts.slice(0, -1), String(key)]);
|
|
87
|
+
let undo;
|
|
88
|
+
if (op.op === "remove") {
|
|
89
|
+
if (!exists) throw new Error("Edit target no longer exists.");
|
|
90
|
+
if (Array.isArray(parent)) parent.splice(Number(key), 1);
|
|
91
|
+
else delete parent[key];
|
|
92
|
+
undo = { op: "add", path, value: old };
|
|
93
|
+
} else if (op.op === "add" && Array.isArray(parent)) {
|
|
94
|
+
parent.splice(Number(key), 0, clone(op.value));
|
|
95
|
+
undo = { op: "remove", path };
|
|
96
|
+
} else {
|
|
97
|
+
parent[key] = clone(op.value);
|
|
98
|
+
undo = exists ? { op: "set", path, value: old } : { op: "remove", path };
|
|
99
|
+
}
|
|
100
|
+
inverse.unshift(undo, ...created.reverse().map((path2) => ({ op: "remove", path: path2 })));
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
error: {
|
|
105
|
+
opIndex,
|
|
106
|
+
path: op.path,
|
|
107
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
spec: result,
|
|
115
|
+
inverse: { ...tx.revision !== void 0 ? { revision: tx.revision } : {}, ops: inverse }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
1
119
|
// src/types/encoding.ts
|
|
2
120
|
function required(...types) {
|
|
3
121
|
return { required: true, allowedTypes: types };
|
|
@@ -521,6 +639,13 @@ var CSS_TOKENS = [
|
|
|
521
639
|
{ name: "--oc-source-size", light: "11px" },
|
|
522
640
|
{ name: "--oc-source-weight", light: "400" },
|
|
523
641
|
{ name: "--oc-body-size", light: "13px" },
|
|
642
|
+
{ name: "--oc-annotation-label", light: "#333333", dark: "#d1d5db" },
|
|
643
|
+
{ name: "--oc-annotation-muted", light: "#6b7280", dark: "#9ca3af" },
|
|
644
|
+
{ name: "--oc-annotation-leader", light: "#8f8f8f", dark: "#9aa0a6" },
|
|
645
|
+
{ name: "--oc-annotation-rule", light: "#666666", dark: "#666666" },
|
|
646
|
+
{ name: "--oc-annotation-range-fill", light: "#000000", dark: "#000000" },
|
|
647
|
+
{ name: "--oc-annotation-marker-fill", light: "#ffffff", dark: "#0a0a0a" },
|
|
648
|
+
{ name: "--oc-annotation-plate", light: "#ffffff", dark: "#0a0a0a" },
|
|
524
649
|
{ name: "--oc-eyebrow-size", light: "11px" },
|
|
525
650
|
{ name: "--oc-eyebrow-weight", light: "500" },
|
|
526
651
|
{ name: "--oc-eyebrow-tracking", light: "0.08em" },
|
|
@@ -1439,6 +1564,13 @@ var DEFAULT_THEME = {
|
|
|
1439
1564
|
axis: "#71717a",
|
|
1440
1565
|
annotationFill: "rgba(0,0,0,0.04)",
|
|
1441
1566
|
annotationText: "#71717a",
|
|
1567
|
+
annotationLabel: "#333333",
|
|
1568
|
+
annotationMuted: "#6b7280",
|
|
1569
|
+
annotationLeader: "#8f8f8f",
|
|
1570
|
+
annotationRule: "#666666",
|
|
1571
|
+
annotationRangeFill: "#000000",
|
|
1572
|
+
annotationMarkerFill: "#ffffff",
|
|
1573
|
+
annotationPlate: "#ffffff",
|
|
1442
1574
|
positive: "#15803d",
|
|
1443
1575
|
negative: "#dc2626"
|
|
1444
1576
|
},
|
|
@@ -1605,6 +1737,13 @@ function adaptTheme(theme) {
|
|
|
1605
1737
|
axis: darkAxis,
|
|
1606
1738
|
annotationFill: pairs?.["colors.annotationFill"] ? pairs["colors.annotationFill"].dark : overridden(theme.colors.annotationFill, light.annotationFill, "rgba(255,255,255,0.06)"),
|
|
1607
1739
|
annotationText: pairs?.["colors.annotationText"] ? pairs["colors.annotationText"].dark : overridden(theme.colors.annotationText, light.annotationText, darkMuted),
|
|
1740
|
+
annotationLabel: pairs?.["colors.annotationLabel"]?.dark ?? overridden(theme.colors.annotationLabel, light.annotationLabel, "#d1d5db"),
|
|
1741
|
+
annotationMuted: pairs?.["colors.annotationMuted"]?.dark ?? overridden(theme.colors.annotationMuted, light.annotationMuted, "#9ca3af"),
|
|
1742
|
+
annotationLeader: pairs?.["colors.annotationLeader"]?.dark ?? overridden(theme.colors.annotationLeader, light.annotationLeader, "#9aa0a6"),
|
|
1743
|
+
annotationRule: pairs?.["colors.annotationRule"]?.dark ?? overridden(theme.colors.annotationRule, light.annotationRule, "#666666"),
|
|
1744
|
+
annotationRangeFill: pairs?.["colors.annotationRangeFill"]?.dark ?? overridden(theme.colors.annotationRangeFill, light.annotationRangeFill, "#000000"),
|
|
1745
|
+
annotationMarkerFill: pairs?.["colors.annotationMarkerFill"]?.dark ?? overridden(theme.colors.annotationMarkerFill, light.annotationMarkerFill, "#0a0a0a"),
|
|
1746
|
+
annotationPlate: pairs?.["colors.annotationPlate"]?.dark ?? overridden(theme.colors.annotationPlate, light.annotationPlate, "#0a0a0a"),
|
|
1608
1747
|
categorical,
|
|
1609
1748
|
categoricalFill,
|
|
1610
1749
|
hairline: darkHairline,
|
|
@@ -1656,6 +1795,11 @@ function adaptForLightLineStroke(color) {
|
|
|
1656
1795
|
return c.formatHex();
|
|
1657
1796
|
}
|
|
1658
1797
|
|
|
1798
|
+
// src/theme/merge.ts
|
|
1799
|
+
function mergeThemeConfig(base, override) {
|
|
1800
|
+
return override ? { ...base, ...override } : base;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1659
1803
|
// src/theme/presets.ts
|
|
1660
1804
|
var editorial = {};
|
|
1661
1805
|
var essay = {
|
|
@@ -1771,6 +1915,23 @@ var terminal = {
|
|
|
1771
1915
|
borderRadius: 4,
|
|
1772
1916
|
seriesStrategy: "accent-neutral"
|
|
1773
1917
|
};
|
|
1918
|
+
var clarity = {
|
|
1919
|
+
colors: {
|
|
1920
|
+
background: { light: "#ffffff", dark: "#09090b" },
|
|
1921
|
+
axis: { light: "#52525b", dark: "#a1a1aa" }
|
|
1922
|
+
},
|
|
1923
|
+
fonts: { sizes: { title: 26, subtitle: 14, body: 13, small: 12, axisTick: 12 } },
|
|
1924
|
+
chrome: {
|
|
1925
|
+
eyebrow: { color: { light: "#0f766e", dark: "#5eead4" } },
|
|
1926
|
+
source: { fontSize: 12 },
|
|
1927
|
+
footer: { fontSize: 12 }
|
|
1928
|
+
},
|
|
1929
|
+
spacing: { chromeToChart: 16, chartToFooter: 12 }
|
|
1930
|
+
};
|
|
1931
|
+
var legacyEditorial = {
|
|
1932
|
+
fonts: { sizes: { title: 26, subtitle: 14, body: 13, small: 11, axisTick: 11 } },
|
|
1933
|
+
spacing: { padding: 20, chromeGap: 6, chromeToChart: 8, chartToFooter: 8 }
|
|
1934
|
+
};
|
|
1774
1935
|
|
|
1775
1936
|
// src/theme/resolve.ts
|
|
1776
1937
|
function deepMerge(target, source) {
|
|
@@ -1809,6 +1970,20 @@ function themeConfigToPartial(config) {
|
|
|
1809
1970
|
colors.annotationFill = resolveTokenLight(config.colors.annotationFill);
|
|
1810
1971
|
if (config.colors.annotationText)
|
|
1811
1972
|
colors.annotationText = resolveTokenLight(config.colors.annotationText);
|
|
1973
|
+
if (config.colors.annotationLabel)
|
|
1974
|
+
colors.annotationLabel = resolveTokenLight(config.colors.annotationLabel);
|
|
1975
|
+
if (config.colors.annotationMuted)
|
|
1976
|
+
colors.annotationMuted = resolveTokenLight(config.colors.annotationMuted);
|
|
1977
|
+
if (config.colors.annotationLeader)
|
|
1978
|
+
colors.annotationLeader = resolveTokenLight(config.colors.annotationLeader);
|
|
1979
|
+
if (config.colors.annotationRule)
|
|
1980
|
+
colors.annotationRule = resolveTokenLight(config.colors.annotationRule);
|
|
1981
|
+
if (config.colors.annotationRangeFill)
|
|
1982
|
+
colors.annotationRangeFill = resolveTokenLight(config.colors.annotationRangeFill);
|
|
1983
|
+
if (config.colors.annotationMarkerFill)
|
|
1984
|
+
colors.annotationMarkerFill = resolveTokenLight(config.colors.annotationMarkerFill);
|
|
1985
|
+
if (config.colors.annotationPlate)
|
|
1986
|
+
colors.annotationPlate = resolveTokenLight(config.colors.annotationPlate);
|
|
1812
1987
|
if (config.colors.positive) colors.positive = resolveTokenLight(config.colors.positive);
|
|
1813
1988
|
if (config.colors.negative) colors.negative = resolveTokenLight(config.colors.negative);
|
|
1814
1989
|
}
|
|
@@ -1883,6 +2058,13 @@ function collectTokenPairs(config) {
|
|
|
1883
2058
|
"axis",
|
|
1884
2059
|
"annotationFill",
|
|
1885
2060
|
"annotationText",
|
|
2061
|
+
"annotationLabel",
|
|
2062
|
+
"annotationMuted",
|
|
2063
|
+
"annotationLeader",
|
|
2064
|
+
"annotationRule",
|
|
2065
|
+
"annotationRangeFill",
|
|
2066
|
+
"annotationMarkerFill",
|
|
2067
|
+
"annotationPlate",
|
|
1886
2068
|
"positive",
|
|
1887
2069
|
"negative"
|
|
1888
2070
|
];
|
|
@@ -1983,6 +2165,22 @@ function resolveTheme(userTheme, base = DEFAULT_THEME) {
|
|
|
1983
2165
|
const dark = isDarkBackground(merged.colors.background);
|
|
1984
2166
|
if (dark) {
|
|
1985
2167
|
merged = adaptChromeForDarkBg(merged, merged.colors.text);
|
|
2168
|
+
const authoredColors = userTheme?.colors && !Array.isArray(userTheme.colors) ? userTheme.colors : {};
|
|
2169
|
+
for (const key of [
|
|
2170
|
+
"annotationLabel",
|
|
2171
|
+
"annotationMuted",
|
|
2172
|
+
"annotationLeader",
|
|
2173
|
+
"annotationRule",
|
|
2174
|
+
"annotationRangeFill",
|
|
2175
|
+
"annotationMarkerFill",
|
|
2176
|
+
"annotationPlate"
|
|
2177
|
+
]) {
|
|
2178
|
+
if (authoredColors[key] === void 0)
|
|
2179
|
+
merged.colors[key] = cssTokenDefault(
|
|
2180
|
+
`--oc-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`,
|
|
2181
|
+
"dark"
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
1986
2184
|
}
|
|
1987
2185
|
return {
|
|
1988
2186
|
...merged,
|
|
@@ -2067,9 +2265,20 @@ function estimateLineCount(text, style, maxWidth, measureText, maxLines) {
|
|
|
2067
2265
|
}
|
|
2068
2266
|
var FOOTNOTE_LINE_HEIGHT = 1.3;
|
|
2069
2267
|
var FOOTNOTE_BAND_GAP = 4;
|
|
2070
|
-
function
|
|
2071
|
-
if (
|
|
2072
|
-
|
|
2268
|
+
function computeNoteBandHeight(input, theme) {
|
|
2269
|
+
if (typeof input !== "number" && input.noteBandHeight !== void 0) return input.noteBandHeight;
|
|
2270
|
+
const count = typeof input === "number" ? input : (input.footnotes?.reduce((sum, note) => sum + (note.lines?.length ?? 1), 0) ?? 0) + (input.seriesSummary?.reduce((sum, entry) => sum + entry.lines.length, 0) ?? 0);
|
|
2271
|
+
return count <= 0 ? 0 : count * theme.fonts.sizes.small * FOOTNOTE_LINE_HEIGHT + FOOTNOTE_BAND_GAP;
|
|
2272
|
+
}
|
|
2273
|
+
var warnedFootnoteBandHeight = false;
|
|
2274
|
+
function footnoteBandHeight(input, theme) {
|
|
2275
|
+
if (!warnedFootnoteBandHeight) {
|
|
2276
|
+
warnedFootnoteBandHeight = true;
|
|
2277
|
+
console.warn(
|
|
2278
|
+
"[openchart] footnoteBandHeight is deprecated; read layout.chrome.noteBandHeight."
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
return computeNoteBandHeight(input, theme);
|
|
2073
2282
|
}
|
|
2074
2283
|
function computeChrome(chrome, theme, width, measureText, chromeMode = "full", padding, watermark = true, bottomLegendHeight = 0, crampedTitle = false) {
|
|
2075
2284
|
if (!chrome || chromeMode === "hidden") {
|
|
@@ -2583,6 +2792,10 @@ function buildD3Formatter(formatStr) {
|
|
|
2583
2792
|
}
|
|
2584
2793
|
function formatPercent(value, options) {
|
|
2585
2794
|
const fraction = options?.fraction ?? true;
|
|
2795
|
+
const percent = fraction ? value * 100 : value;
|
|
2796
|
+
if (Number.isFinite(percent) && percent !== 0 && Math.abs(percent) < 0.1) {
|
|
2797
|
+
return percent < 0 ? `>${FORMAT_PERCENT_RAW(-0.1)}%` : `<${FORMAT_PERCENT_RAW(0.1)}%`;
|
|
2798
|
+
}
|
|
2586
2799
|
if (fraction) {
|
|
2587
2800
|
return FORMAT_PERCENT_FRACTION(value);
|
|
2588
2801
|
}
|
|
@@ -2918,6 +3131,19 @@ function formatValue(value) {
|
|
|
2918
3131
|
}
|
|
2919
3132
|
|
|
2920
3133
|
// src/helpers/spec-builders.ts
|
|
3134
|
+
var warnedBuilderOptions = /* @__PURE__ */ new Set();
|
|
3135
|
+
function warnBuilderOption(key, message) {
|
|
3136
|
+
if (warnedBuilderOptions.has(key)) return;
|
|
3137
|
+
warnedBuilderOptions.add(key);
|
|
3138
|
+
console.warn(`[openchart] ${message}`);
|
|
3139
|
+
}
|
|
3140
|
+
function warnDarkMode(options) {
|
|
3141
|
+
if (options?.darkMode !== void 0)
|
|
3142
|
+
warnBuilderOption(
|
|
3143
|
+
"darkMode",
|
|
3144
|
+
"Builder darkMode is deprecated and has no effect; use the mount option."
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
2921
3147
|
var ISO_DATE_RE = /^\d{4}(-\d{2}(-\d{2}(T\d{2}(:\d{2}(:\d{2})?)?)?)?)?$/;
|
|
2922
3148
|
function inferFieldType(data, field) {
|
|
2923
3149
|
const sampleSize = Math.min(data.length, 20);
|
|
@@ -2962,12 +3188,12 @@ function buildEncoding(channels, options, data) {
|
|
|
2962
3188
|
return encoding;
|
|
2963
3189
|
}
|
|
2964
3190
|
function buildChartSpec(mark, data, encoding, options) {
|
|
3191
|
+
warnDarkMode(options);
|
|
2965
3192
|
const spec = { mark, data, encoding };
|
|
2966
3193
|
if (options?.chrome) spec.chrome = options.chrome;
|
|
2967
3194
|
if (options?.annotations) spec.annotations = options.annotations;
|
|
2968
3195
|
if (options?.responsive !== void 0) spec.responsive = options.responsive;
|
|
2969
3196
|
if (options?.theme) spec.theme = options.theme;
|
|
2970
|
-
if (options?.darkMode) spec.darkMode = options.darkMode;
|
|
2971
3197
|
return spec;
|
|
2972
3198
|
}
|
|
2973
3199
|
function lineChart(data, x, y, options) {
|
|
@@ -3031,6 +3257,9 @@ function scatterChart(data, x, y, options) {
|
|
|
3031
3257
|
return buildChartSpec("point", data, encoding, options);
|
|
3032
3258
|
}
|
|
3033
3259
|
function dataTable(data, options) {
|
|
3260
|
+
warnDarkMode(options);
|
|
3261
|
+
if (options?.compact !== void 0)
|
|
3262
|
+
warnBuilderOption("compact", "dataTable compact is deprecated; use density.");
|
|
3034
3263
|
let columns = options?.columns;
|
|
3035
3264
|
if (!columns && data.length > 0) {
|
|
3036
3265
|
columns = Object.keys(data[0]).map((key) => {
|
|
@@ -3051,15 +3280,16 @@ function dataTable(data, options) {
|
|
|
3051
3280
|
if (options?.rowKey) spec.rowKey = options.rowKey;
|
|
3052
3281
|
if (options?.chrome) spec.chrome = options.chrome;
|
|
3053
3282
|
if (options?.theme) spec.theme = options.theme;
|
|
3054
|
-
if (options?.darkMode) spec.darkMode = options.darkMode;
|
|
3055
3283
|
if (options?.search !== void 0) spec.search = options.search;
|
|
3056
3284
|
if (options?.pagination !== void 0) spec.pagination = options.pagination;
|
|
3057
3285
|
if (options?.stickyFirstColumn !== void 0) spec.stickyFirstColumn = options.stickyFirstColumn;
|
|
3058
|
-
if (options?.
|
|
3286
|
+
if (options?.density !== void 0) spec.density = options.density;
|
|
3287
|
+
else if (options?.compact !== void 0) spec.density = options.compact ? "condensed" : "regular";
|
|
3059
3288
|
if (options?.responsive !== void 0) spec.responsive = options.responsive;
|
|
3060
3289
|
return spec;
|
|
3061
3290
|
}
|
|
3062
3291
|
function tileMap(data, options) {
|
|
3292
|
+
warnDarkMode(options);
|
|
3063
3293
|
return {
|
|
3064
3294
|
type: "tilemap",
|
|
3065
3295
|
data,
|
|
@@ -3069,7 +3299,6 @@ function tileMap(data, options) {
|
|
|
3069
3299
|
...options?.chrome && { chrome: options.chrome },
|
|
3070
3300
|
...options?.legend && { legend: options.legend },
|
|
3071
3301
|
...options?.theme && { theme: options.theme },
|
|
3072
|
-
...options?.darkMode && { darkMode: options.darkMode },
|
|
3073
3302
|
...options?.watermark !== void 0 && { watermark: options.watermark },
|
|
3074
3303
|
...options?.animation && { animation: options.animation },
|
|
3075
3304
|
...options?.valueFormat && { valueFormat: options.valueFormat }
|
|
@@ -3089,6 +3318,104 @@ function deepMergeSpec(target, source) {
|
|
|
3089
3318
|
}
|
|
3090
3319
|
return result;
|
|
3091
3320
|
}
|
|
3321
|
+
|
|
3322
|
+
// src/helpers/annotation-source.ts
|
|
3323
|
+
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3324
|
+
function annotationSource(spec) {
|
|
3325
|
+
const root = object(spec);
|
|
3326
|
+
if (Array.isArray(root?.annotations))
|
|
3327
|
+
return { annotations: root.annotations, path: "/annotations" };
|
|
3328
|
+
function firstLeaf(node, path) {
|
|
3329
|
+
if (Array.isArray(node?.layer)) {
|
|
3330
|
+
for (const [index, child] of node.layer.entries()) {
|
|
3331
|
+
const result = firstLeaf(object(child), `${path}/layer/${index}`);
|
|
3332
|
+
if (result) return result;
|
|
3333
|
+
}
|
|
3334
|
+
} else if (node && "mark" in node) {
|
|
3335
|
+
return {
|
|
3336
|
+
annotations: Array.isArray(node.annotations) ? node.annotations : [],
|
|
3337
|
+
path: `${path}/annotations`
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
return firstLeaf(root, "") ?? { annotations: [], path: "/annotations" };
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
// src/motion/sample.ts
|
|
3345
|
+
var MOTION_REST_THRESHOLD = 1e-3;
|
|
3346
|
+
var MAX_SPRING_DURATION = 1200;
|
|
3347
|
+
var FLUID_SPRING = {
|
|
3348
|
+
stiffness: 320,
|
|
3349
|
+
damping: 36,
|
|
3350
|
+
mass: 1
|
|
3351
|
+
};
|
|
3352
|
+
function springState(timeMs, config) {
|
|
3353
|
+
const stiffness = config.stiffness ?? FLUID_SPRING.stiffness;
|
|
3354
|
+
const mass = config.mass ?? FLUID_SPRING.mass;
|
|
3355
|
+
const rootStiffness = Math.sqrt(stiffness);
|
|
3356
|
+
const rootMass = Math.sqrt(mass);
|
|
3357
|
+
const omega = rootStiffness / rootMass;
|
|
3358
|
+
const damping = config.damping ?? FLUID_SPRING.damping;
|
|
3359
|
+
const zeta = Math.max(1, damping / rootStiffness / rootMass / 2);
|
|
3360
|
+
const t = Math.max(0, timeMs) / 1e3;
|
|
3361
|
+
let remaining;
|
|
3362
|
+
let velocity;
|
|
3363
|
+
if (zeta - 1 < 1e-4) {
|
|
3364
|
+
const scaledTime = omega * t;
|
|
3365
|
+
const decay = Math.exp(-scaledTime);
|
|
3366
|
+
remaining = decay === 0 ? 0 : (1 + scaledTime) * decay;
|
|
3367
|
+
velocity = decay === 0 ? 0 : omega * (scaledTime * decay);
|
|
3368
|
+
} else {
|
|
3369
|
+
const inverseZeta = 1 / zeta;
|
|
3370
|
+
const q = damping / 2 * (1 + Math.sqrt(1 - inverseZeta * inverseZeta));
|
|
3371
|
+
const slow = -stiffness / q;
|
|
3372
|
+
const fast = -q / mass;
|
|
3373
|
+
const ratio = slow / fast;
|
|
3374
|
+
const slowDecay = Math.exp(slow * t);
|
|
3375
|
+
const fastDecay = Math.exp(fast * t);
|
|
3376
|
+
if (slowDecay === 0) {
|
|
3377
|
+
remaining = 0;
|
|
3378
|
+
velocity = 0;
|
|
3379
|
+
} else {
|
|
3380
|
+
remaining = (slowDecay - ratio * fastDecay) / (1 - ratio);
|
|
3381
|
+
velocity = -slow * ((slowDecay - fastDecay) / (1 - ratio));
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
return {
|
|
3385
|
+
position: Math.min(1, Math.max(0, 1 - remaining)),
|
|
3386
|
+
velocity,
|
|
3387
|
+
settled: remaining <= MOTION_REST_THRESHOLD && Math.abs(velocity) <= MOTION_REST_THRESHOLD
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
function springDuration(config) {
|
|
3391
|
+
for (const name of ["stiffness", "damping", "mass"]) {
|
|
3392
|
+
const value = config[name];
|
|
3393
|
+
if (value === void 0) continue;
|
|
3394
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
3395
|
+
throw new Error(`spring.${name} must be finite and positive`);
|
|
3396
|
+
}
|
|
3397
|
+
for (let ms = 1; ms < MAX_SPRING_DURATION; ms++) {
|
|
3398
|
+
if (springState(ms, config).settled) return ms;
|
|
3399
|
+
}
|
|
3400
|
+
return MAX_SPRING_DURATION;
|
|
3401
|
+
}
|
|
3402
|
+
function sampleMotion(phase, elapsedMs) {
|
|
3403
|
+
if (phase.duration <= 0 || elapsedMs >= phase.duration) {
|
|
3404
|
+
return { position: 1, velocity: 0, settled: true };
|
|
3405
|
+
}
|
|
3406
|
+
if (elapsedMs <= 0) return { position: 0, velocity: 0, settled: false };
|
|
3407
|
+
if (phase.spring) return springState(elapsedMs, phase.spring);
|
|
3408
|
+
const t = elapsedMs / phase.duration;
|
|
3409
|
+
const power = phase.ease === "snappy" ? 5 : 3;
|
|
3410
|
+
return {
|
|
3411
|
+
position: 1 - (1 - t) ** power,
|
|
3412
|
+
velocity: power * (1 - t) ** (power - 1) * 1e3 / phase.duration,
|
|
3413
|
+
settled: false
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
function motionCSS(phase) {
|
|
3417
|
+
return `linear(${Array.from({ length: 101 }, (_, i) => sampleMotion(phase, i * phase.duration / 100).position.toFixed(6)).join(",")})`;
|
|
3418
|
+
}
|
|
3092
3419
|
export {
|
|
3093
3420
|
ACHROMATIC_RAMP,
|
|
3094
3421
|
AXIS_TITLE_GAP,
|
|
@@ -3112,6 +3439,7 @@ export {
|
|
|
3112
3439
|
DEFAULT_THEME,
|
|
3113
3440
|
DIVERGING_PALETTES,
|
|
3114
3441
|
EXTENDED_OFFSET_STRATEGIES,
|
|
3442
|
+
FLUID_SPRING,
|
|
3115
3443
|
FOOTNOTE_LINE_HEIGHT,
|
|
3116
3444
|
GRAPH_ENCODING_RULES,
|
|
3117
3445
|
HEIGHT_CRAMPED_MAX,
|
|
@@ -3130,7 +3458,9 @@ export {
|
|
|
3130
3458
|
MAX_LEFT_LABEL_FRACTION_DEFAULT,
|
|
3131
3459
|
MAX_LEFT_LABEL_FRACTION_MEDIUM,
|
|
3132
3460
|
MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX,
|
|
3461
|
+
MAX_SPRING_DURATION,
|
|
3133
3462
|
MAX_SR_TABLE_ROWS,
|
|
3463
|
+
MOTION_REST_THRESHOLD,
|
|
3134
3464
|
NARROW_VIEWPORT_MAX,
|
|
3135
3465
|
OFFSET_STRATEGIES,
|
|
3136
3466
|
SEQUENTIAL_PALETTES,
|
|
@@ -3147,6 +3477,8 @@ export {
|
|
|
3147
3477
|
adaptColorForDarkMode,
|
|
3148
3478
|
adaptForLightLineStroke,
|
|
3149
3479
|
adaptTheme,
|
|
3480
|
+
annotationSource,
|
|
3481
|
+
applyEditTransaction,
|
|
3150
3482
|
areaChart,
|
|
3151
3483
|
axisTitleOffset,
|
|
3152
3484
|
barChart,
|
|
@@ -3155,10 +3487,12 @@ export {
|
|
|
3155
3487
|
buildD3Formatter,
|
|
3156
3488
|
buildTemporalFormatter,
|
|
3157
3489
|
checkPaletteDistinguishability,
|
|
3490
|
+
clarity,
|
|
3158
3491
|
columnChart,
|
|
3159
3492
|
computeChrome,
|
|
3160
3493
|
computeFieldFormatContext,
|
|
3161
3494
|
computeLabelBounds,
|
|
3495
|
+
computeNoteBandHeight,
|
|
3162
3496
|
computeXAxisExtentFromLabels,
|
|
3163
3497
|
contrastRatio,
|
|
3164
3498
|
cssTokenDefault,
|
|
@@ -3208,9 +3542,12 @@ export {
|
|
|
3208
3542
|
isTileMapSpec,
|
|
3209
3543
|
isValueDef,
|
|
3210
3544
|
isYearContext,
|
|
3545
|
+
legacyEditorial,
|
|
3211
3546
|
lineChart,
|
|
3212
3547
|
maxRotatedLabelWidth,
|
|
3213
3548
|
meetsAA,
|
|
3549
|
+
mergeThemeConfig,
|
|
3550
|
+
motionCSS,
|
|
3214
3551
|
overlapArea,
|
|
3215
3552
|
pickLabelColor,
|
|
3216
3553
|
pieChart,
|
|
@@ -3222,8 +3559,10 @@ export {
|
|
|
3222
3559
|
resolveNumberFormatter,
|
|
3223
3560
|
resolveSchemeName,
|
|
3224
3561
|
resolveTheme,
|
|
3562
|
+
sampleMotion,
|
|
3225
3563
|
scatterChart,
|
|
3226
3564
|
simulateColorBlindness,
|
|
3565
|
+
springDuration,
|
|
3227
3566
|
terminal,
|
|
3228
3567
|
textAscent,
|
|
3229
3568
|
tileMap,
|