@vanduo-oss/vd3-cbun 1.0.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/CHANGELOG.md +50 -0
- package/LICENSE +22 -0
- package/README.md +160 -0
- package/SKILL.md +119 -0
- package/dist/charts/core.d.ts +273 -0
- package/dist/charts/index.cjs +1828 -0
- package/dist/charts/index.cjs.map +7 -0
- package/dist/charts/index.d.ts +65 -0
- package/dist/charts/index.js +1805 -0
- package/dist/charts/index.js.map +7 -0
- package/dist/charts/vd3-charts.css +51 -0
- package/dist/charts/vue.d.ts +86 -0
- package/dist/flowchart/core.d.ts +288 -0
- package/dist/flowchart/index.cjs +3447 -0
- package/dist/flowchart/index.cjs.map +7 -0
- package/dist/flowchart/index.d.ts +54 -0
- package/dist/flowchart/index.js +3424 -0
- package/dist/flowchart/index.js.map +7 -0
- package/dist/flowchart/vd3-flowchart.css +600 -0
- package/dist/flowchart/vue.d.ts +66 -0
- package/dist/hex-grid/core.d.ts +200 -0
- package/dist/hex-grid/hex-math.cjs +162 -0
- package/dist/hex-grid/hex-math.cjs.map +7 -0
- package/dist/hex-grid/hex-math.d.ts +119 -0
- package/dist/hex-grid/hex-math.js +141 -0
- package/dist/hex-grid/hex-math.js.map +7 -0
- package/dist/hex-grid/index.cjs +915 -0
- package/dist/hex-grid/index.cjs.map +7 -0
- package/dist/hex-grid/index.d.ts +15 -0
- package/dist/hex-grid/index.js +894 -0
- package/dist/hex-grid/index.js.map +7 -0
- package/dist/hex-grid/vue.d.ts +14 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +7 -0
- package/dist/meta.json +551 -0
- package/dist/music-player/core.d.ts +88 -0
- package/dist/music-player/index.cjs +1227 -0
- package/dist/music-player/index.cjs.map +7 -0
- package/dist/music-player/index.d.ts +12 -0
- package/dist/music-player/index.js +1204 -0
- package/dist/music-player/index.js.map +7 -0
- package/dist/music-player/vd3-music-player.css +829 -0
- package/dist/music-player/vue.d.ts +32 -0
- package/package.json +105 -0
|
@@ -0,0 +1,1828 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/charts/index.js
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AreaChart: () => AreaChart,
|
|
24
|
+
BarChart: () => BarChart,
|
|
25
|
+
DonutChart: () => DonutChart,
|
|
26
|
+
LineChart: () => LineChart,
|
|
27
|
+
PieChart: () => PieChart,
|
|
28
|
+
ScatterChart: () => ScatterChart,
|
|
29
|
+
VD_CHARTS_VERSION: () => VD_CHARTS_VERSION,
|
|
30
|
+
VdAreaChart: () => VdAreaChart,
|
|
31
|
+
VdBarChart: () => VdBarChart,
|
|
32
|
+
VdChart: () => VdChart,
|
|
33
|
+
VdDonutChart: () => VdDonutChart,
|
|
34
|
+
VdLineChart: () => VdLineChart,
|
|
35
|
+
VdPieChart: () => VdPieChart,
|
|
36
|
+
VdScatterChart: () => VdScatterChart,
|
|
37
|
+
arcPath: () => arcPath,
|
|
38
|
+
areaPath: () => areaPath,
|
|
39
|
+
createAccessor: () => createAccessor,
|
|
40
|
+
linePath: () => linePath,
|
|
41
|
+
niceDomain: () => niceDomain,
|
|
42
|
+
resolveTheme: () => resolveTheme,
|
|
43
|
+
scaleBand: () => scaleBand,
|
|
44
|
+
scaleLinear: () => scaleLinear,
|
|
45
|
+
scaleOrdinal: () => scaleOrdinal,
|
|
46
|
+
scalePoint: () => scalePoint,
|
|
47
|
+
scaleTime: () => scaleTime,
|
|
48
|
+
ticks: () => ticks
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(index_exports);
|
|
51
|
+
|
|
52
|
+
// src/charts/vue.js
|
|
53
|
+
var import_vue = require("vue");
|
|
54
|
+
|
|
55
|
+
// src/charts/core.js
|
|
56
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
57
|
+
var TAU = Math.PI * 2;
|
|
58
|
+
var ARC_EPSILON = 1e-4;
|
|
59
|
+
var DEFAULT_WIDTH = 640;
|
|
60
|
+
var DEFAULT_HEIGHT = 360;
|
|
61
|
+
var DEFAULT_MARGIN = { top: 28, right: 24, bottom: 46, left: 56 };
|
|
62
|
+
var POLAR_MARGIN = { top: 32, right: 24, bottom: 28, left: 24 };
|
|
63
|
+
var DEFAULT_COLORS = [
|
|
64
|
+
"#5c7cfa",
|
|
65
|
+
"#228be6",
|
|
66
|
+
"#40c057",
|
|
67
|
+
"#fab005",
|
|
68
|
+
"#fa5252",
|
|
69
|
+
"#12b886",
|
|
70
|
+
"#be4bdb",
|
|
71
|
+
"#fd7e14"
|
|
72
|
+
];
|
|
73
|
+
var VD_CHARTS_VERSION = "1.0.0";
|
|
74
|
+
var chartId = 0;
|
|
75
|
+
function nextId(prefix) {
|
|
76
|
+
chartId += 1;
|
|
77
|
+
return `${prefix}-${chartId}`;
|
|
78
|
+
}
|
|
79
|
+
function hasWindow() {
|
|
80
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
81
|
+
}
|
|
82
|
+
function isElement(value) {
|
|
83
|
+
return hasWindow() && value instanceof Element;
|
|
84
|
+
}
|
|
85
|
+
function resolveTarget(target) {
|
|
86
|
+
if (!hasWindow()) {
|
|
87
|
+
throw new Error("Vanduo Charts requires a browser DOM target.");
|
|
88
|
+
}
|
|
89
|
+
if (typeof target === "string") {
|
|
90
|
+
const el = document.querySelector(target);
|
|
91
|
+
if (!el) throw new Error(`Chart target not found: ${target}`);
|
|
92
|
+
return el;
|
|
93
|
+
}
|
|
94
|
+
if (isElement(target)) return target;
|
|
95
|
+
throw new Error("Chart target must be an Element or selector string.");
|
|
96
|
+
}
|
|
97
|
+
function svgEl(name, attrs = {}) {
|
|
98
|
+
const el = document.createElementNS(SVG_NS, name);
|
|
99
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
100
|
+
if (value !== null && typeof value !== "undefined") {
|
|
101
|
+
el.setAttribute(key, String(value));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return el;
|
|
105
|
+
}
|
|
106
|
+
function append(parent, child) {
|
|
107
|
+
parent.appendChild(child);
|
|
108
|
+
return child;
|
|
109
|
+
}
|
|
110
|
+
function setText(parent, text) {
|
|
111
|
+
parent.textContent = text == null ? "" : String(text);
|
|
112
|
+
return parent;
|
|
113
|
+
}
|
|
114
|
+
function toArray(value) {
|
|
115
|
+
return Array.isArray(value) ? value : [];
|
|
116
|
+
}
|
|
117
|
+
function unique(values) {
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
119
|
+
const result = [];
|
|
120
|
+
values.forEach((value) => {
|
|
121
|
+
const key = String(value);
|
|
122
|
+
if (seen.has(key)) return;
|
|
123
|
+
seen.add(key);
|
|
124
|
+
result.push(value);
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
function isFiniteNumber(value) {
|
|
129
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
130
|
+
}
|
|
131
|
+
function toNumber(value) {
|
|
132
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
|
|
133
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
134
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
135
|
+
const parsed = Number(value);
|
|
136
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function toTime(value) {
|
|
141
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
|
|
142
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
143
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
144
|
+
const parsed = Date.parse(value);
|
|
145
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function isDateLike(value) {
|
|
150
|
+
if (value instanceof Date) return Number.isFinite(value.getTime());
|
|
151
|
+
if (typeof value !== "string") return false;
|
|
152
|
+
const trimmed = value.trim();
|
|
153
|
+
if (!trimmed || /^-?\d+(\.\d+)?$/.test(trimmed)) return false;
|
|
154
|
+
return Number.isFinite(Date.parse(trimmed));
|
|
155
|
+
}
|
|
156
|
+
function readPathValue(source, path) {
|
|
157
|
+
if (source == null) return void 0;
|
|
158
|
+
if (!String(path).includes(".")) return source[path];
|
|
159
|
+
return String(path).split(".").reduce((value, key) => {
|
|
160
|
+
if (value == null) return void 0;
|
|
161
|
+
return value[key];
|
|
162
|
+
}, source);
|
|
163
|
+
}
|
|
164
|
+
function createAccessor(accessor, fallback) {
|
|
165
|
+
const resolved = accessor == null ? fallback : accessor;
|
|
166
|
+
if (typeof resolved === "function") return resolved;
|
|
167
|
+
if (typeof resolved === "string" && resolved.length) {
|
|
168
|
+
return (datum) => readPathValue(datum, resolved);
|
|
169
|
+
}
|
|
170
|
+
return (datum) => datum;
|
|
171
|
+
}
|
|
172
|
+
function normalizeMargin(value, fallback = DEFAULT_MARGIN) {
|
|
173
|
+
if (typeof value === "number") {
|
|
174
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
top: Number(value?.top ?? fallback.top),
|
|
178
|
+
right: Number(value?.right ?? fallback.right),
|
|
179
|
+
bottom: Number(value?.bottom ?? fallback.bottom),
|
|
180
|
+
left: Number(value?.left ?? fallback.left)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function measureTarget(target, options) {
|
|
184
|
+
const width = Number(options.width) || Math.round(target.clientWidth) || DEFAULT_WIDTH;
|
|
185
|
+
const height = Number(options.height) || Math.round(target.clientHeight) || DEFAULT_HEIGHT;
|
|
186
|
+
return {
|
|
187
|
+
width: Math.max(160, width),
|
|
188
|
+
height: Math.max(140, height)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function getPlotBox(width, height, margin) {
|
|
192
|
+
return {
|
|
193
|
+
left: margin.left,
|
|
194
|
+
top: margin.top,
|
|
195
|
+
right: Math.max(margin.left + 1, width - margin.right),
|
|
196
|
+
bottom: Math.max(margin.top + 1, height - margin.bottom)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function readToken(style, names, fallback) {
|
|
200
|
+
for (const name of names) {
|
|
201
|
+
const value = style.getPropertyValue(name).trim();
|
|
202
|
+
if (value) return value;
|
|
203
|
+
}
|
|
204
|
+
return fallback;
|
|
205
|
+
}
|
|
206
|
+
function resolveTheme(target, overrides = {}) {
|
|
207
|
+
if (!hasWindow()) {
|
|
208
|
+
return {
|
|
209
|
+
fontFamily: "inherit",
|
|
210
|
+
textColor: "#1a1d20",
|
|
211
|
+
mutedTextColor: "#868e96",
|
|
212
|
+
gridColor: "#e9ecef",
|
|
213
|
+
axisColor: "#ced4da",
|
|
214
|
+
backgroundColor: "#ffffff",
|
|
215
|
+
colors: DEFAULT_COLORS.slice(),
|
|
216
|
+
...overrides
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const styleTarget = isElement(target) ? target : document.documentElement;
|
|
220
|
+
const style = getComputedStyle(styleTarget);
|
|
221
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
222
|
+
const tokenStyle = {
|
|
223
|
+
getPropertyValue(name) {
|
|
224
|
+
return style.getPropertyValue(name) || rootStyle.getPropertyValue(name);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const colors = DEFAULT_COLORS.map(
|
|
228
|
+
(fallback, index) => readToken(
|
|
229
|
+
tokenStyle,
|
|
230
|
+
[
|
|
231
|
+
`--vd-chart-${index + 1}`,
|
|
232
|
+
index === 0 ? "--vd-color-primary" : "",
|
|
233
|
+
index === 1 ? "--vd-color-info" : "",
|
|
234
|
+
index === 2 ? "--vd-color-success" : "",
|
|
235
|
+
index === 3 ? "--vd-color-warning" : "",
|
|
236
|
+
index === 4 ? "--vd-color-error" : "",
|
|
237
|
+
index === 0 ? "--color-primary" : "",
|
|
238
|
+
index === 1 ? "--color-info" : "",
|
|
239
|
+
index === 2 ? "--color-success" : ""
|
|
240
|
+
].filter(Boolean),
|
|
241
|
+
fallback
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
return {
|
|
245
|
+
fontFamily: readToken(tokenStyle, ["--vd-font-family-base"], "inherit"),
|
|
246
|
+
textColor: readToken(tokenStyle, ["--vd-text-primary", "--text-primary"], "#1a1d20"),
|
|
247
|
+
mutedTextColor: readToken(tokenStyle, ["--vd-text-muted", "--text-muted"], "#868e96"),
|
|
248
|
+
gridColor: readToken(
|
|
249
|
+
tokenStyle,
|
|
250
|
+
["--vd-border-color-light", "--border-color-light", "--vd-border-color"],
|
|
251
|
+
"#e9ecef"
|
|
252
|
+
),
|
|
253
|
+
axisColor: readToken(tokenStyle, ["--vd-border-color", "--border-color"], "#ced4da"),
|
|
254
|
+
backgroundColor: readToken(tokenStyle, ["--vd-bg-primary", "--bg-primary"], "#ffffff"),
|
|
255
|
+
...overrides,
|
|
256
|
+
colors: overrides.colors || colors
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function tickStep(min, max, count) {
|
|
260
|
+
const span = Math.abs(max - min);
|
|
261
|
+
if (!span || !Number.isFinite(span)) return 1;
|
|
262
|
+
const raw = span / Math.max(1, count);
|
|
263
|
+
const power = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
264
|
+
const error = raw / power;
|
|
265
|
+
const factor = error >= 7.5 ? 10 : error >= 3.5 ? 5 : error >= 1.5 ? 2 : 1;
|
|
266
|
+
return factor * power;
|
|
267
|
+
}
|
|
268
|
+
function ticks(min, max, count = 5) {
|
|
269
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return [];
|
|
270
|
+
if (min === max) return [min];
|
|
271
|
+
const reverse = max < min;
|
|
272
|
+
const start = reverse ? max : min;
|
|
273
|
+
const stop = reverse ? min : max;
|
|
274
|
+
const step = tickStep(start, stop, count);
|
|
275
|
+
const first = Math.ceil(start / step) * step;
|
|
276
|
+
const values = [];
|
|
277
|
+
for (let value = first; value <= stop + step / 2; value += step) {
|
|
278
|
+
values.push(Number(value.toFixed(12)));
|
|
279
|
+
}
|
|
280
|
+
return reverse ? values.reverse() : values;
|
|
281
|
+
}
|
|
282
|
+
function niceDomain(values, options = false) {
|
|
283
|
+
const opts = typeof options === "boolean" ? { includeZero: options } : options || {};
|
|
284
|
+
const nums = values.map(toNumber).filter(isFiniteNumber);
|
|
285
|
+
if (!nums.length) {
|
|
286
|
+
return [isFiniteNumber(opts.min) ? opts.min : 0, isFiniteNumber(opts.max) ? opts.max : 1];
|
|
287
|
+
}
|
|
288
|
+
let min = Math.min(...nums);
|
|
289
|
+
let max = Math.max(...nums);
|
|
290
|
+
if (opts.includeZero) {
|
|
291
|
+
min = Math.min(0, min);
|
|
292
|
+
max = Math.max(0, max);
|
|
293
|
+
}
|
|
294
|
+
if (isFiniteNumber(opts.min)) min = opts.min;
|
|
295
|
+
if (isFiniteNumber(opts.max)) max = opts.max;
|
|
296
|
+
if (min === max) {
|
|
297
|
+
const pad = Math.abs(min || 1) * 0.1;
|
|
298
|
+
min -= pad;
|
|
299
|
+
max += pad;
|
|
300
|
+
}
|
|
301
|
+
const step = tickStep(min, max, opts.tickCount || 5);
|
|
302
|
+
return [
|
|
303
|
+
isFiniteNumber(opts.min) ? opts.min : Math.floor(min / step) * step,
|
|
304
|
+
isFiniteNumber(opts.max) ? opts.max : Math.ceil(max / step) * step
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
function scaleLinear(config = {}) {
|
|
308
|
+
const domain = config.domain || [0, 1];
|
|
309
|
+
const range = config.range || [0, 1];
|
|
310
|
+
let d0 = Number(domain[0]);
|
|
311
|
+
let d1 = Number(domain[1]);
|
|
312
|
+
const r0 = Number(range[0]);
|
|
313
|
+
const r1 = Number(range[1]);
|
|
314
|
+
if (!Number.isFinite(d0)) d0 = 0;
|
|
315
|
+
if (!Number.isFinite(d1)) d1 = 1;
|
|
316
|
+
if (d0 === d1) {
|
|
317
|
+
d0 -= 0.5;
|
|
318
|
+
d1 += 0.5;
|
|
319
|
+
}
|
|
320
|
+
const scale = (value) => {
|
|
321
|
+
const n = Number(value);
|
|
322
|
+
if (!Number.isFinite(n)) return null;
|
|
323
|
+
return r0 + (n - d0) / (d1 - d0) * (r1 - r0);
|
|
324
|
+
};
|
|
325
|
+
scale.domain = () => [d0, d1];
|
|
326
|
+
scale.range = () => [r0, r1];
|
|
327
|
+
scale.ticks = (count = 5) => ticks(d0, d1, count);
|
|
328
|
+
return scale;
|
|
329
|
+
}
|
|
330
|
+
function scaleTime(config = {}) {
|
|
331
|
+
const domain = (config.domain || [/* @__PURE__ */ new Date(0), /* @__PURE__ */ new Date(1)]).map(toTime);
|
|
332
|
+
const linear = scaleLinear({
|
|
333
|
+
domain: [domain[0] ?? 0, domain[1] ?? 1],
|
|
334
|
+
range: config.range || [0, 1]
|
|
335
|
+
});
|
|
336
|
+
const scale = (value) => linear(toTime(value));
|
|
337
|
+
scale.domain = () => linear.domain().map((value) => new Date(value));
|
|
338
|
+
scale.range = linear.range;
|
|
339
|
+
scale.ticks = (count = 5) => linear.ticks(count).map((value) => new Date(value));
|
|
340
|
+
return scale;
|
|
341
|
+
}
|
|
342
|
+
function scaleBand(config = {}) {
|
|
343
|
+
const domain = unique(config.domain || []).map(String);
|
|
344
|
+
const range = config.range || [0, 1];
|
|
345
|
+
const paddingInner = Number(config.paddingInner ?? config.padding ?? 0.16);
|
|
346
|
+
const paddingOuter = Number(config.paddingOuter ?? config.padding ?? 0.16);
|
|
347
|
+
const r0 = Number(range[0]);
|
|
348
|
+
const r1 = Number(range[1]);
|
|
349
|
+
const span = r1 - r0;
|
|
350
|
+
const denominator = Math.max(1, domain.length - paddingInner + paddingOuter * 2);
|
|
351
|
+
const step = span / denominator;
|
|
352
|
+
const bandwidth = Math.abs(step * Math.max(0, 1 - paddingInner));
|
|
353
|
+
const scale = (value) => {
|
|
354
|
+
const index = domain.indexOf(String(value));
|
|
355
|
+
if (index < 0) return null;
|
|
356
|
+
return r0 + (paddingOuter + index) * step;
|
|
357
|
+
};
|
|
358
|
+
scale.domain = () => domain.slice();
|
|
359
|
+
scale.range = () => [r0, r1];
|
|
360
|
+
scale.bandwidth = () => bandwidth;
|
|
361
|
+
scale.step = () => Math.abs(step);
|
|
362
|
+
return scale;
|
|
363
|
+
}
|
|
364
|
+
function scalePoint(config = {}) {
|
|
365
|
+
const domain = unique(config.domain || []).map(String);
|
|
366
|
+
const range = config.range || [0, 1];
|
|
367
|
+
const padding = Number(config.padding ?? 0.5);
|
|
368
|
+
const r0 = Number(range[0]);
|
|
369
|
+
const r1 = Number(range[1]);
|
|
370
|
+
const span = r1 - r0;
|
|
371
|
+
const step = domain.length <= 1 ? 0 : span / Math.max(1, domain.length - 1 + padding * 2);
|
|
372
|
+
const start = domain.length <= 1 ? r0 + span / 2 : r0 + padding * step;
|
|
373
|
+
const scale = (value) => {
|
|
374
|
+
const index = domain.indexOf(String(value));
|
|
375
|
+
if (index < 0) return null;
|
|
376
|
+
return start + index * step;
|
|
377
|
+
};
|
|
378
|
+
scale.domain = () => domain.slice();
|
|
379
|
+
scale.range = () => [r0, r1];
|
|
380
|
+
scale.step = () => Math.abs(step);
|
|
381
|
+
scale.bandwidth = () => 0;
|
|
382
|
+
return scale;
|
|
383
|
+
}
|
|
384
|
+
function scaleOrdinal(config = {}) {
|
|
385
|
+
const domain = unique(config.domain || []).map(String);
|
|
386
|
+
const range = config.range || DEFAULT_COLORS;
|
|
387
|
+
const scale = (value) => {
|
|
388
|
+
const key = String(value);
|
|
389
|
+
let index = domain.indexOf(key);
|
|
390
|
+
if (index < 0) {
|
|
391
|
+
domain.push(key);
|
|
392
|
+
index = domain.length - 1;
|
|
393
|
+
}
|
|
394
|
+
return range[index % range.length];
|
|
395
|
+
};
|
|
396
|
+
scale.domain = () => domain.slice();
|
|
397
|
+
scale.range = () => range.slice();
|
|
398
|
+
return scale;
|
|
399
|
+
}
|
|
400
|
+
function linePath(points) {
|
|
401
|
+
const clean = points.filter((point) => isFiniteNumber(point.x) && isFiniteNumber(point.y));
|
|
402
|
+
if (!clean.length) return "";
|
|
403
|
+
return clean.map((point, index) => `${index === 0 ? "M" : "L"}${point.x},${point.y}`).join(" ");
|
|
404
|
+
}
|
|
405
|
+
function areaPath(points, baselineY) {
|
|
406
|
+
const clean = points.filter((point) => isFiniteNumber(point.x) && isFiniteNumber(point.y));
|
|
407
|
+
if (!clean.length || !isFiniteNumber(baselineY)) return "";
|
|
408
|
+
const line = linePath(clean);
|
|
409
|
+
const last = clean[clean.length - 1];
|
|
410
|
+
const first = clean[0];
|
|
411
|
+
return `${line} L${last.x},${baselineY} L${first.x},${baselineY} Z`;
|
|
412
|
+
}
|
|
413
|
+
function polarPoint(cx, cy, radius, angle) {
|
|
414
|
+
return {
|
|
415
|
+
x: cx + radius * Math.cos(angle),
|
|
416
|
+
y: cy + radius * Math.sin(angle)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function arcPath(cx, cy, outerRadius, innerRadius, startAngle, endAngle) {
|
|
420
|
+
if (!isFiniteNumber(outerRadius) || outerRadius <= 0 || endAngle <= startAngle) return "";
|
|
421
|
+
const safeEnd = endAngle - startAngle >= TAU ? startAngle + TAU - ARC_EPSILON : endAngle;
|
|
422
|
+
const largeArc = safeEnd - startAngle > Math.PI ? 1 : 0;
|
|
423
|
+
const outerStart = polarPoint(cx, cy, outerRadius, startAngle);
|
|
424
|
+
const outerEnd = polarPoint(cx, cy, outerRadius, safeEnd);
|
|
425
|
+
const inner = Math.max(0, Number(innerRadius) || 0);
|
|
426
|
+
if (inner <= 0) {
|
|
427
|
+
return [
|
|
428
|
+
`M${cx},${cy}`,
|
|
429
|
+
`L${outerStart.x},${outerStart.y}`,
|
|
430
|
+
`A${outerRadius},${outerRadius} 0 ${largeArc} 1 ${outerEnd.x},${outerEnd.y}`,
|
|
431
|
+
"Z"
|
|
432
|
+
].join(" ");
|
|
433
|
+
}
|
|
434
|
+
const innerEnd = polarPoint(cx, cy, inner, safeEnd);
|
|
435
|
+
const innerStart = polarPoint(cx, cy, inner, startAngle);
|
|
436
|
+
return [
|
|
437
|
+
`M${outerStart.x},${outerStart.y}`,
|
|
438
|
+
`A${outerRadius},${outerRadius} 0 ${largeArc} 1 ${outerEnd.x},${outerEnd.y}`,
|
|
439
|
+
`L${innerEnd.x},${innerEnd.y}`,
|
|
440
|
+
`A${inner},${inner} 0 ${largeArc} 0 ${innerStart.x},${innerStart.y}`,
|
|
441
|
+
"Z"
|
|
442
|
+
].join(" ");
|
|
443
|
+
}
|
|
444
|
+
function formatNumber(value) {
|
|
445
|
+
if (!Number.isFinite(value)) return "";
|
|
446
|
+
return Math.abs(value) >= 1e3 ? value.toLocaleString() : String(Number(value.toFixed(3)));
|
|
447
|
+
}
|
|
448
|
+
function formatTick(value, formatter) {
|
|
449
|
+
if (typeof formatter === "function") return formatter(value);
|
|
450
|
+
if (value instanceof Date) {
|
|
451
|
+
return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(value);
|
|
452
|
+
}
|
|
453
|
+
return formatNumber(Number(value));
|
|
454
|
+
}
|
|
455
|
+
function formatCategory(value) {
|
|
456
|
+
return value == null ? "" : String(value);
|
|
457
|
+
}
|
|
458
|
+
function attachTooltip(instance, mark, options, context, fallback) {
|
|
459
|
+
const tooltip = options.tooltip;
|
|
460
|
+
if (tooltip === false) return;
|
|
461
|
+
const getContent = () => {
|
|
462
|
+
if (typeof tooltip === "function") {
|
|
463
|
+
const result = tooltip(context.datum, context);
|
|
464
|
+
return result === false ? null : result;
|
|
465
|
+
}
|
|
466
|
+
if (typeof tooltip === "string") return tooltip;
|
|
467
|
+
return fallback;
|
|
468
|
+
};
|
|
469
|
+
const show = (event) => {
|
|
470
|
+
const content = getContent();
|
|
471
|
+
if (content == null || content === "") return;
|
|
472
|
+
instance.showTooltip(content, event);
|
|
473
|
+
};
|
|
474
|
+
const hide = () => instance.hideTooltip();
|
|
475
|
+
mark.addEventListener("pointerenter", show);
|
|
476
|
+
mark.addEventListener("pointermove", show);
|
|
477
|
+
mark.addEventListener("pointerleave", hide);
|
|
478
|
+
mark.addEventListener("focus", show);
|
|
479
|
+
mark.addEventListener("blur", hide);
|
|
480
|
+
}
|
|
481
|
+
function attachClick(mark, callback, datum, index) {
|
|
482
|
+
if (typeof callback !== "function") return;
|
|
483
|
+
const fire = (event) => callback({ event, datum, index });
|
|
484
|
+
mark.addEventListener("click", fire);
|
|
485
|
+
mark.addEventListener("keydown", (event) => {
|
|
486
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
fire(event);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function makeInteractive(mark, hasClick, hasTooltip) {
|
|
493
|
+
if (hasClick || hasTooltip) {
|
|
494
|
+
mark.setAttribute("tabindex", "0");
|
|
495
|
+
mark.setAttribute("focusable", "true");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function createSvgShell(instance) {
|
|
499
|
+
const { target, options } = instance;
|
|
500
|
+
const size = measureTarget(target, options);
|
|
501
|
+
const theme = resolveTheme(target, options.theme);
|
|
502
|
+
const margin = normalizeMargin(options.margin, options.polar ? POLAR_MARGIN : DEFAULT_MARGIN);
|
|
503
|
+
const plot = getPlotBox(size.width, size.height, margin);
|
|
504
|
+
target.innerHTML = "";
|
|
505
|
+
target.classList.add("vd-chart-root", `vd-chart-${instance.kind}`);
|
|
506
|
+
if (getComputedStyle(target).position === "static") {
|
|
507
|
+
target.style.position = "relative";
|
|
508
|
+
}
|
|
509
|
+
const svg = svgEl("svg", {
|
|
510
|
+
class: "vd-chart-svg",
|
|
511
|
+
width: size.width,
|
|
512
|
+
height: size.height,
|
|
513
|
+
viewBox: `0 0 ${size.width} ${size.height}`,
|
|
514
|
+
role: "img"
|
|
515
|
+
});
|
|
516
|
+
const titleId = nextId("vd-chart-title");
|
|
517
|
+
const descId = nextId("vd-chart-desc");
|
|
518
|
+
const labelledBy = [];
|
|
519
|
+
if (options.title) {
|
|
520
|
+
labelledBy.push(titleId);
|
|
521
|
+
append(svg, setText(svgEl("title", { id: titleId }), options.title));
|
|
522
|
+
}
|
|
523
|
+
if (options.description) {
|
|
524
|
+
labelledBy.push(descId);
|
|
525
|
+
append(svg, setText(svgEl("desc", { id: descId }), options.description));
|
|
526
|
+
}
|
|
527
|
+
if (labelledBy.length) {
|
|
528
|
+
svg.setAttribute("aria-labelledby", labelledBy.join(" "));
|
|
529
|
+
} else {
|
|
530
|
+
svg.setAttribute("aria-label", options.ariaLabel || `${instance.kind} chart`);
|
|
531
|
+
}
|
|
532
|
+
if (options.title) {
|
|
533
|
+
append(
|
|
534
|
+
svg,
|
|
535
|
+
setText(
|
|
536
|
+
svgEl("text", {
|
|
537
|
+
x: margin.left,
|
|
538
|
+
y: 18,
|
|
539
|
+
fill: theme.textColor,
|
|
540
|
+
"font-size": 14,
|
|
541
|
+
"font-weight": 600
|
|
542
|
+
}),
|
|
543
|
+
options.title
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
target.appendChild(svg);
|
|
548
|
+
instance.theme = theme;
|
|
549
|
+
instance.size = size;
|
|
550
|
+
instance.plot = plot;
|
|
551
|
+
instance.tooltipEl = null;
|
|
552
|
+
return { svg, size, theme, plot, margin };
|
|
553
|
+
}
|
|
554
|
+
function renderEmpty(svg, size, theme, message = "No data") {
|
|
555
|
+
append(
|
|
556
|
+
svg,
|
|
557
|
+
setText(
|
|
558
|
+
svgEl("text", {
|
|
559
|
+
class: "vd-chart-empty",
|
|
560
|
+
x: size.width / 2,
|
|
561
|
+
y: size.height / 2,
|
|
562
|
+
fill: theme.mutedTextColor,
|
|
563
|
+
"text-anchor": "middle"
|
|
564
|
+
}),
|
|
565
|
+
message
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
function drawAxisLine(svg, x1, y1, x2, y2, theme) {
|
|
570
|
+
append(
|
|
571
|
+
svg,
|
|
572
|
+
svgEl("line", {
|
|
573
|
+
x1,
|
|
574
|
+
y1,
|
|
575
|
+
x2,
|
|
576
|
+
y2,
|
|
577
|
+
stroke: theme.axisColor,
|
|
578
|
+
"stroke-width": 1,
|
|
579
|
+
"shape-rendering": "crispEdges"
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
function drawCartesianAxes(svg, config) {
|
|
584
|
+
const { plot, xScale, yScale, xTicks, yTicks, theme, options, categoricalX } = config;
|
|
585
|
+
const axisGroup = append(svg, svgEl("g", { class: "vd-chart-axes" }));
|
|
586
|
+
yTicks.forEach((tick) => {
|
|
587
|
+
const y = yScale(tick);
|
|
588
|
+
if (!isFiniteNumber(y)) return;
|
|
589
|
+
append(
|
|
590
|
+
axisGroup,
|
|
591
|
+
svgEl("line", {
|
|
592
|
+
x1: plot.left,
|
|
593
|
+
y1: y,
|
|
594
|
+
x2: plot.right,
|
|
595
|
+
y2: y,
|
|
596
|
+
stroke: theme.gridColor,
|
|
597
|
+
"stroke-width": 1,
|
|
598
|
+
"shape-rendering": "crispEdges"
|
|
599
|
+
})
|
|
600
|
+
);
|
|
601
|
+
append(
|
|
602
|
+
axisGroup,
|
|
603
|
+
setText(
|
|
604
|
+
svgEl("text", {
|
|
605
|
+
x: plot.left - 10,
|
|
606
|
+
y: y + 4,
|
|
607
|
+
fill: theme.mutedTextColor,
|
|
608
|
+
"font-size": 11,
|
|
609
|
+
"text-anchor": "end"
|
|
610
|
+
}),
|
|
611
|
+
formatTick(tick, options.yFormat)
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
});
|
|
615
|
+
drawAxisLine(axisGroup, plot.left, plot.bottom, plot.right, plot.bottom, theme);
|
|
616
|
+
drawAxisLine(axisGroup, plot.left, plot.top, plot.left, plot.bottom, theme);
|
|
617
|
+
xTicks.forEach((tick) => {
|
|
618
|
+
let x = xScale(tick);
|
|
619
|
+
if (categoricalX && typeof xScale.bandwidth === "function") {
|
|
620
|
+
x += xScale.bandwidth() / 2;
|
|
621
|
+
}
|
|
622
|
+
if (!isFiniteNumber(x)) return;
|
|
623
|
+
append(
|
|
624
|
+
axisGroup,
|
|
625
|
+
svgEl("line", {
|
|
626
|
+
x1: x,
|
|
627
|
+
y1: plot.bottom,
|
|
628
|
+
x2: x,
|
|
629
|
+
y2: plot.bottom + 5,
|
|
630
|
+
stroke: theme.axisColor,
|
|
631
|
+
"stroke-width": 1
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
append(
|
|
635
|
+
axisGroup,
|
|
636
|
+
setText(
|
|
637
|
+
svgEl("text", {
|
|
638
|
+
x,
|
|
639
|
+
y: plot.bottom + 20,
|
|
640
|
+
fill: theme.mutedTextColor,
|
|
641
|
+
"font-size": 11,
|
|
642
|
+
"text-anchor": "middle"
|
|
643
|
+
}),
|
|
644
|
+
categoricalX ? formatCategory(tick) : formatTick(tick, options.xFormat)
|
|
645
|
+
)
|
|
646
|
+
);
|
|
647
|
+
});
|
|
648
|
+
if (options.xAxis?.label) {
|
|
649
|
+
append(
|
|
650
|
+
axisGroup,
|
|
651
|
+
setText(
|
|
652
|
+
svgEl("text", {
|
|
653
|
+
x: (plot.left + plot.right) / 2,
|
|
654
|
+
y: plot.bottom + 40,
|
|
655
|
+
fill: theme.mutedTextColor,
|
|
656
|
+
"font-size": 12,
|
|
657
|
+
"text-anchor": "middle"
|
|
658
|
+
}),
|
|
659
|
+
options.xAxis.label
|
|
660
|
+
)
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (options.yAxis?.label) {
|
|
664
|
+
append(
|
|
665
|
+
axisGroup,
|
|
666
|
+
setText(
|
|
667
|
+
svgEl("text", {
|
|
668
|
+
x: -((plot.top + plot.bottom) / 2),
|
|
669
|
+
y: 15,
|
|
670
|
+
fill: theme.mutedTextColor,
|
|
671
|
+
"font-size": 12,
|
|
672
|
+
"text-anchor": "middle",
|
|
673
|
+
transform: "rotate(-90)"
|
|
674
|
+
}),
|
|
675
|
+
options.yAxis.label
|
|
676
|
+
)
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function inferXScale(rows, plot, options) {
|
|
681
|
+
const values = rows.map((row) => row.x);
|
|
682
|
+
const explicitType = options.xScale;
|
|
683
|
+
const allNumeric = values.every((value) => toNumber(value) !== null);
|
|
684
|
+
const allDates = values.every(isDateLike);
|
|
685
|
+
if (explicitType === "time" || !explicitType && allDates) {
|
|
686
|
+
const times = values.map(toTime).filter(isFiniteNumber);
|
|
687
|
+
return {
|
|
688
|
+
scale: scaleTime({ domain: niceDomain(times), range: [plot.left, plot.right] }),
|
|
689
|
+
values: times,
|
|
690
|
+
ticks: scaleLinear({ domain: niceDomain(times), range: [plot.left, plot.right] }).ticks(5).map((value) => new Date(value)),
|
|
691
|
+
type: "time",
|
|
692
|
+
mapValue: toTime
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (explicitType === "linear" || !explicitType && allNumeric) {
|
|
696
|
+
const nums = values.map(toNumber).filter(isFiniteNumber);
|
|
697
|
+
const domain2 = niceDomain(nums, { min: options.xMin, max: options.xMax });
|
|
698
|
+
const scale = scaleLinear({ domain: domain2, range: [plot.left, plot.right] });
|
|
699
|
+
return {
|
|
700
|
+
scale,
|
|
701
|
+
values: nums,
|
|
702
|
+
ticks: scale.ticks(5),
|
|
703
|
+
type: "linear",
|
|
704
|
+
mapValue: toNumber
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const domain = unique(values).map(String);
|
|
708
|
+
return {
|
|
709
|
+
scale: scalePoint({ domain, range: [plot.left, plot.right], padding: 0.5 }),
|
|
710
|
+
values: domain,
|
|
711
|
+
ticks: domain,
|
|
712
|
+
type: "point",
|
|
713
|
+
mapValue: (value) => String(value)
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function getColorScale(rows, colorOption, theme) {
|
|
717
|
+
if (colorOption == null) return null;
|
|
718
|
+
if (typeof colorOption === "function") {
|
|
719
|
+
return { direct: colorOption };
|
|
720
|
+
}
|
|
721
|
+
const colorAccessor = createAccessor(colorOption);
|
|
722
|
+
const domain = unique(rows.map((row) => colorAccessor(row.raw))).map(String);
|
|
723
|
+
return {
|
|
724
|
+
accessor: colorAccessor,
|
|
725
|
+
scale: scaleOrdinal({ domain, range: theme.colors })
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function colorForRow(color, row, theme) {
|
|
729
|
+
if (color) {
|
|
730
|
+
if (typeof color.direct === "function") return color.direct(row.raw);
|
|
731
|
+
if (color.scale) return color.scale(color.accessor(row.raw));
|
|
732
|
+
}
|
|
733
|
+
return theme.colors[row.index % theme.colors.length];
|
|
734
|
+
}
|
|
735
|
+
function buildSeriesList(options) {
|
|
736
|
+
const xAccessor = createAccessor(options.x, "x");
|
|
737
|
+
const sharedData = toArray(options.data);
|
|
738
|
+
const buildRows = (data, yAccessor) => data.map((datum, index) => ({
|
|
739
|
+
raw: datum,
|
|
740
|
+
index,
|
|
741
|
+
x: xAccessor(datum),
|
|
742
|
+
y: toNumber(yAccessor(datum))
|
|
743
|
+
})).filter((row) => row.x != null && isFiniteNumber(row.y));
|
|
744
|
+
if (Array.isArray(options.series) && options.series.length) {
|
|
745
|
+
return options.series.map((series, seriesIndex) => {
|
|
746
|
+
const data = Array.isArray(series.data) && series.data.length ? series.data : sharedData;
|
|
747
|
+
const yAccessor = createAccessor(series.y ?? options.y, "y");
|
|
748
|
+
return {
|
|
749
|
+
name: series.name ?? `Series ${seriesIndex + 1}`,
|
|
750
|
+
color: series.color,
|
|
751
|
+
seriesIndex,
|
|
752
|
+
rows: buildRows(data, yAccessor)
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return [
|
|
757
|
+
{
|
|
758
|
+
name: options.name ?? null,
|
|
759
|
+
color: options.stroke,
|
|
760
|
+
seriesIndex: 0,
|
|
761
|
+
rows: buildRows(sharedData, createAccessor(options.y, "y"))
|
|
762
|
+
}
|
|
763
|
+
];
|
|
764
|
+
}
|
|
765
|
+
function seriesColor(series, theme) {
|
|
766
|
+
return series.color || theme.colors[series.seriesIndex % theme.colors.length];
|
|
767
|
+
}
|
|
768
|
+
function renderTopLegend(svg, items, theme, plot) {
|
|
769
|
+
if (!items.length) return;
|
|
770
|
+
const shown = items.slice(0, 8);
|
|
771
|
+
const widths = shown.map((item) => 14 + String(item.label).length * 6.5 + 14);
|
|
772
|
+
const total = widths.reduce((sum, w) => sum + w, 0);
|
|
773
|
+
let x = Math.max(plot.left, plot.right - total);
|
|
774
|
+
const y = 14;
|
|
775
|
+
const legend = append(svg, svgEl("g", { class: "vd-chart-legend" }));
|
|
776
|
+
shown.forEach((item, index) => {
|
|
777
|
+
append(
|
|
778
|
+
legend,
|
|
779
|
+
svgEl("rect", {
|
|
780
|
+
x,
|
|
781
|
+
y: y - 8,
|
|
782
|
+
width: 10,
|
|
783
|
+
height: 10,
|
|
784
|
+
rx: 2,
|
|
785
|
+
fill: item.color
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
append(
|
|
789
|
+
legend,
|
|
790
|
+
setText(
|
|
791
|
+
svgEl("text", {
|
|
792
|
+
x: x + 14,
|
|
793
|
+
y,
|
|
794
|
+
fill: theme.mutedTextColor,
|
|
795
|
+
"font-size": 11
|
|
796
|
+
}),
|
|
797
|
+
formatCategory(item.label)
|
|
798
|
+
)
|
|
799
|
+
);
|
|
800
|
+
x += widths[index];
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
function dataLabelConfig(options) {
|
|
804
|
+
const dl = options.dataLabels;
|
|
805
|
+
if (!dl) return null;
|
|
806
|
+
return typeof dl === "object" ? dl : {};
|
|
807
|
+
}
|
|
808
|
+
function drawDataLabel(svg, x, y, value, cfg, theme, anchor = "middle") {
|
|
809
|
+
if (!isFiniteNumber(x) || !isFiniteNumber(y)) return;
|
|
810
|
+
append(
|
|
811
|
+
svg,
|
|
812
|
+
setText(
|
|
813
|
+
svgEl("text", {
|
|
814
|
+
class: "vd-chart-data-label",
|
|
815
|
+
x,
|
|
816
|
+
y,
|
|
817
|
+
fill: cfg.color || theme.textColor,
|
|
818
|
+
"font-size": 10,
|
|
819
|
+
"text-anchor": anchor
|
|
820
|
+
}),
|
|
821
|
+
typeof cfg.format === "function" ? cfg.format(value) : formatNumber(value)
|
|
822
|
+
)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
function drawAnnotations(svg, options, plot, xScale, yScale, theme) {
|
|
826
|
+
const annotations = toArray(options.annotations);
|
|
827
|
+
if (!annotations.length) return;
|
|
828
|
+
const group = append(svg, svgEl("g", { class: "vd-chart-annotations" }));
|
|
829
|
+
annotations.forEach((ann) => {
|
|
830
|
+
const color = ann.color || theme.mutedTextColor;
|
|
831
|
+
const dash = ann.dash === false ? null : "4 3";
|
|
832
|
+
if (isFiniteNumber(ann.y) && yScale) {
|
|
833
|
+
const y = yScale(ann.y);
|
|
834
|
+
if (!isFiniteNumber(y)) return;
|
|
835
|
+
append(
|
|
836
|
+
group,
|
|
837
|
+
svgEl("line", {
|
|
838
|
+
class: "vd-chart-annotation-line",
|
|
839
|
+
x1: plot.left,
|
|
840
|
+
y1: y,
|
|
841
|
+
x2: plot.right,
|
|
842
|
+
y2: y,
|
|
843
|
+
stroke: color,
|
|
844
|
+
"stroke-width": 1,
|
|
845
|
+
"stroke-dasharray": dash
|
|
846
|
+
})
|
|
847
|
+
);
|
|
848
|
+
if (ann.label) {
|
|
849
|
+
append(
|
|
850
|
+
group,
|
|
851
|
+
setText(
|
|
852
|
+
svgEl("text", {
|
|
853
|
+
x: plot.right - 4,
|
|
854
|
+
y: y - 4,
|
|
855
|
+
fill: color,
|
|
856
|
+
"font-size": 10,
|
|
857
|
+
"text-anchor": "end"
|
|
858
|
+
}),
|
|
859
|
+
ann.label
|
|
860
|
+
)
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (ann.x != null && xScale) {
|
|
865
|
+
let x = xScale(ann.x);
|
|
866
|
+
if (typeof xScale.bandwidth === "function" && isFiniteNumber(x)) {
|
|
867
|
+
x += xScale.bandwidth() / 2;
|
|
868
|
+
}
|
|
869
|
+
if (!isFiniteNumber(x)) return;
|
|
870
|
+
append(
|
|
871
|
+
group,
|
|
872
|
+
svgEl("line", {
|
|
873
|
+
class: "vd-chart-annotation-line",
|
|
874
|
+
x1: x,
|
|
875
|
+
y1: plot.top,
|
|
876
|
+
x2: x,
|
|
877
|
+
y2: plot.bottom,
|
|
878
|
+
stroke: color,
|
|
879
|
+
"stroke-width": 1,
|
|
880
|
+
"stroke-dasharray": dash
|
|
881
|
+
})
|
|
882
|
+
);
|
|
883
|
+
if (ann.label) {
|
|
884
|
+
append(
|
|
885
|
+
group,
|
|
886
|
+
setText(
|
|
887
|
+
svgEl("text", {
|
|
888
|
+
x: x + 4,
|
|
889
|
+
y: plot.top + 10,
|
|
890
|
+
fill: color,
|
|
891
|
+
"font-size": 10
|
|
892
|
+
}),
|
|
893
|
+
ann.label
|
|
894
|
+
)
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function renderBarChart(instance) {
|
|
901
|
+
if (instance.options.series?.length) return renderMultiBarChart(instance);
|
|
902
|
+
const shell = createSvgShell(instance);
|
|
903
|
+
const { svg, size, theme, plot } = shell;
|
|
904
|
+
const options = instance.options;
|
|
905
|
+
const data = toArray(options.data);
|
|
906
|
+
const xAccessor = createAccessor(options.x, "x");
|
|
907
|
+
const yAccessor = createAccessor(options.y, "y");
|
|
908
|
+
const rows = data.map((datum, index) => ({
|
|
909
|
+
raw: datum,
|
|
910
|
+
index,
|
|
911
|
+
x: xAccessor(datum),
|
|
912
|
+
y: toNumber(yAccessor(datum))
|
|
913
|
+
})).filter((row) => row.x != null && isFiniteNumber(row.y));
|
|
914
|
+
if (!rows.length) {
|
|
915
|
+
renderEmpty(svg, size, theme);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const categories = unique(rows.map((row) => row.x)).map(String);
|
|
919
|
+
const xScale = scaleBand({
|
|
920
|
+
domain: categories,
|
|
921
|
+
range: [plot.left, plot.right],
|
|
922
|
+
padding: options.barPadding ?? 0.18
|
|
923
|
+
});
|
|
924
|
+
const yDomain = niceDomain(
|
|
925
|
+
rows.map((row) => row.y),
|
|
926
|
+
{
|
|
927
|
+
includeZero: true,
|
|
928
|
+
min: options.yMin,
|
|
929
|
+
max: options.yMax,
|
|
930
|
+
tickCount: options.yTickCount
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
const yScale = scaleLinear({ domain: yDomain, range: [plot.bottom, plot.top] });
|
|
934
|
+
const yTicks = yScale.ticks(options.yTickCount ?? 5);
|
|
935
|
+
const color = getColorScale(rows, options.color, theme);
|
|
936
|
+
drawCartesianAxes(svg, {
|
|
937
|
+
plot,
|
|
938
|
+
xScale,
|
|
939
|
+
yScale,
|
|
940
|
+
xTicks: categories,
|
|
941
|
+
yTicks,
|
|
942
|
+
theme,
|
|
943
|
+
options,
|
|
944
|
+
categoricalX: true
|
|
945
|
+
});
|
|
946
|
+
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
947
|
+
const labels = dataLabelConfig(options);
|
|
948
|
+
const markGroup = append(svg, svgEl("g", { class: "vd-chart-marks vd-chart-bars" }));
|
|
949
|
+
const baseline = yScale(0);
|
|
950
|
+
rows.forEach((row) => {
|
|
951
|
+
const x = xScale(row.x);
|
|
952
|
+
const y = yScale(row.y);
|
|
953
|
+
if (!isFiniteNumber(x) || !isFiniteNumber(y) || !isFiniteNumber(baseline)) return;
|
|
954
|
+
const rectY = Math.min(y, baseline);
|
|
955
|
+
const rectHeight = Math.max(1, Math.abs(baseline - y));
|
|
956
|
+
const fill = colorForRow(color, row, theme);
|
|
957
|
+
const rect = svgEl("rect", {
|
|
958
|
+
class: "vd-chart-bar",
|
|
959
|
+
x,
|
|
960
|
+
y: rectY,
|
|
961
|
+
width: xScale.bandwidth(),
|
|
962
|
+
height: rectHeight,
|
|
963
|
+
rx: 3,
|
|
964
|
+
fill,
|
|
965
|
+
role: "graphics-symbol",
|
|
966
|
+
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
967
|
+
});
|
|
968
|
+
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
969
|
+
attachTooltip(
|
|
970
|
+
instance,
|
|
971
|
+
rect,
|
|
972
|
+
options,
|
|
973
|
+
{
|
|
974
|
+
datum: row.raw,
|
|
975
|
+
x: row.x,
|
|
976
|
+
y: row.y,
|
|
977
|
+
value: row.y,
|
|
978
|
+
label: row.x,
|
|
979
|
+
index: row.index
|
|
980
|
+
},
|
|
981
|
+
`${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
982
|
+
);
|
|
983
|
+
attachClick(rect, options.onBarClick, row.raw, row.index);
|
|
984
|
+
if (labels) drawDataLabel(svg, x + xScale.bandwidth() / 2, rectY - 4, row.y, labels, theme);
|
|
985
|
+
append(markGroup, rect);
|
|
986
|
+
});
|
|
987
|
+
if (options.legend && color && color.scale) {
|
|
988
|
+
renderTopLegend(
|
|
989
|
+
svg,
|
|
990
|
+
color.scale.domain().map((cat) => ({ label: cat, color: color.scale(cat) })),
|
|
991
|
+
theme,
|
|
992
|
+
plot
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function renderMultiBarChart(instance) {
|
|
997
|
+
const shell = createSvgShell(instance);
|
|
998
|
+
const { svg, size, theme, plot } = shell;
|
|
999
|
+
const options = instance.options;
|
|
1000
|
+
const seriesList = buildSeriesList(options);
|
|
1001
|
+
const allRows = seriesList.flatMap((series) => series.rows);
|
|
1002
|
+
if (!allRows.length) {
|
|
1003
|
+
renderEmpty(svg, size, theme);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const categories = unique(allRows.map((row) => String(row.x)));
|
|
1007
|
+
const xScale = scaleBand({
|
|
1008
|
+
domain: categories,
|
|
1009
|
+
range: [plot.left, plot.right],
|
|
1010
|
+
padding: options.barPadding ?? 0.18
|
|
1011
|
+
});
|
|
1012
|
+
const innerScale = scaleBand({
|
|
1013
|
+
domain: seriesList.map((series) => series.name),
|
|
1014
|
+
range: [0, xScale.bandwidth()],
|
|
1015
|
+
padding: 0.08
|
|
1016
|
+
});
|
|
1017
|
+
const yDomain = niceDomain(
|
|
1018
|
+
allRows.map((row) => row.y),
|
|
1019
|
+
{
|
|
1020
|
+
includeZero: true,
|
|
1021
|
+
min: options.yMin,
|
|
1022
|
+
max: options.yMax,
|
|
1023
|
+
tickCount: options.yTickCount
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
const yScale = scaleLinear({ domain: yDomain, range: [plot.bottom, plot.top] });
|
|
1027
|
+
const yTicks = yScale.ticks(options.yTickCount ?? 5);
|
|
1028
|
+
drawCartesianAxes(svg, {
|
|
1029
|
+
plot,
|
|
1030
|
+
xScale,
|
|
1031
|
+
yScale,
|
|
1032
|
+
xTicks: categories,
|
|
1033
|
+
yTicks,
|
|
1034
|
+
theme,
|
|
1035
|
+
options,
|
|
1036
|
+
categoricalX: true
|
|
1037
|
+
});
|
|
1038
|
+
drawAnnotations(svg, options, plot, xScale, yScale, theme);
|
|
1039
|
+
const labels = dataLabelConfig(options);
|
|
1040
|
+
const markGroup = append(svg, svgEl("g", { class: "vd-chart-marks vd-chart-bars" }));
|
|
1041
|
+
const baseline = yScale(0);
|
|
1042
|
+
seriesList.forEach((series) => {
|
|
1043
|
+
const fill = seriesColor(series, theme);
|
|
1044
|
+
series.rows.forEach((row) => {
|
|
1045
|
+
const groupX = xScale(String(row.x));
|
|
1046
|
+
const offset = innerScale(series.name);
|
|
1047
|
+
const y = yScale(row.y);
|
|
1048
|
+
if (![groupX, offset, y, baseline].every(isFiniteNumber)) return;
|
|
1049
|
+
const rectY = Math.min(y, baseline);
|
|
1050
|
+
const rectHeight = Math.max(1, Math.abs(baseline - y));
|
|
1051
|
+
const rect = svgEl("rect", {
|
|
1052
|
+
class: "vd-chart-bar",
|
|
1053
|
+
x: groupX + offset,
|
|
1054
|
+
y: rectY,
|
|
1055
|
+
width: innerScale.bandwidth(),
|
|
1056
|
+
height: rectHeight,
|
|
1057
|
+
rx: 3,
|
|
1058
|
+
fill,
|
|
1059
|
+
role: "graphics-symbol",
|
|
1060
|
+
"aria-label": `${series.name} \u2014 ${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1061
|
+
});
|
|
1062
|
+
makeInteractive(rect, typeof options.onBarClick === "function", options.tooltip !== false);
|
|
1063
|
+
attachTooltip(
|
|
1064
|
+
instance,
|
|
1065
|
+
rect,
|
|
1066
|
+
options,
|
|
1067
|
+
{
|
|
1068
|
+
datum: row.raw,
|
|
1069
|
+
x: row.x,
|
|
1070
|
+
y: row.y,
|
|
1071
|
+
value: row.y,
|
|
1072
|
+
label: row.x,
|
|
1073
|
+
index: row.index,
|
|
1074
|
+
seriesIndex: series.seriesIndex,
|
|
1075
|
+
seriesName: series.name
|
|
1076
|
+
},
|
|
1077
|
+
`${series.name} \u2014 ${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1078
|
+
);
|
|
1079
|
+
attachClick(rect, options.onBarClick, row.raw, row.index);
|
|
1080
|
+
if (labels)
|
|
1081
|
+
drawDataLabel(
|
|
1082
|
+
svg,
|
|
1083
|
+
groupX + offset + innerScale.bandwidth() / 2,
|
|
1084
|
+
rectY - 4,
|
|
1085
|
+
row.y,
|
|
1086
|
+
labels,
|
|
1087
|
+
theme
|
|
1088
|
+
);
|
|
1089
|
+
append(markGroup, rect);
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
if (options.legend !== false) {
|
|
1093
|
+
renderTopLegend(
|
|
1094
|
+
svg,
|
|
1095
|
+
seriesList.map((series) => ({ label: series.name, color: seriesColor(series, theme) })),
|
|
1096
|
+
theme,
|
|
1097
|
+
plot
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
function renderLineLikeChart(instance, mode) {
|
|
1102
|
+
if (instance.options.series?.length) return renderMultiLineChart(instance, mode);
|
|
1103
|
+
const shell = createSvgShell(instance);
|
|
1104
|
+
const { svg, size, theme, plot } = shell;
|
|
1105
|
+
const options = instance.options;
|
|
1106
|
+
const data = toArray(options.data);
|
|
1107
|
+
const xAccessor = createAccessor(options.x, "x");
|
|
1108
|
+
const yAccessor = createAccessor(options.y, "y");
|
|
1109
|
+
const rows = data.map((datum, index) => ({
|
|
1110
|
+
raw: datum,
|
|
1111
|
+
index,
|
|
1112
|
+
x: xAccessor(datum),
|
|
1113
|
+
y: toNumber(yAccessor(datum))
|
|
1114
|
+
})).filter((row) => row.x != null && isFiniteNumber(row.y));
|
|
1115
|
+
if (!rows.length) {
|
|
1116
|
+
renderEmpty(svg, size, theme);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const xInfo = inferXScale(rows, plot, options);
|
|
1120
|
+
const yDomain = niceDomain(
|
|
1121
|
+
rows.map((row) => row.y),
|
|
1122
|
+
{
|
|
1123
|
+
includeZero: mode === "area" || options.yIncludeZero === true,
|
|
1124
|
+
min: options.yMin,
|
|
1125
|
+
max: options.yMax,
|
|
1126
|
+
tickCount: options.yTickCount
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1129
|
+
const yScale = scaleLinear({ domain: yDomain, range: [plot.bottom, plot.top] });
|
|
1130
|
+
const yTicks = yScale.ticks(options.yTickCount ?? 5);
|
|
1131
|
+
const color = options.stroke || theme.colors[0];
|
|
1132
|
+
const points = rows.map((row) => ({
|
|
1133
|
+
raw: row.raw,
|
|
1134
|
+
index: row.index,
|
|
1135
|
+
xValue: row.x,
|
|
1136
|
+
yValue: row.y,
|
|
1137
|
+
x: xInfo.scale(xInfo.mapValue(row.x)),
|
|
1138
|
+
y: yScale(row.y)
|
|
1139
|
+
})).filter((point) => isFiniteNumber(point.x) && isFiniteNumber(point.y));
|
|
1140
|
+
drawCartesianAxes(svg, {
|
|
1141
|
+
plot,
|
|
1142
|
+
xScale: xInfo.scale,
|
|
1143
|
+
yScale,
|
|
1144
|
+
xTicks: xInfo.ticks,
|
|
1145
|
+
yTicks,
|
|
1146
|
+
theme,
|
|
1147
|
+
options,
|
|
1148
|
+
categoricalX: xInfo.type === "point"
|
|
1149
|
+
});
|
|
1150
|
+
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1151
|
+
const labels = dataLabelConfig(options);
|
|
1152
|
+
const markGroup = append(svg, svgEl("g", { class: `vd-chart-marks vd-chart-${mode}` }));
|
|
1153
|
+
if (mode === "area") {
|
|
1154
|
+
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1155
|
+
append(
|
|
1156
|
+
markGroup,
|
|
1157
|
+
svgEl("path", {
|
|
1158
|
+
class: "vd-chart-area-path",
|
|
1159
|
+
d: areaPath(points, baseline),
|
|
1160
|
+
fill: options.fill || color,
|
|
1161
|
+
opacity: options.fillOpacity ?? 0.18,
|
|
1162
|
+
stroke: "none"
|
|
1163
|
+
})
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
append(
|
|
1167
|
+
markGroup,
|
|
1168
|
+
svgEl("path", {
|
|
1169
|
+
class: "vd-chart-line-path",
|
|
1170
|
+
d: linePath(points),
|
|
1171
|
+
fill: "none",
|
|
1172
|
+
stroke: color,
|
|
1173
|
+
"stroke-width": options.strokeWidth || 2,
|
|
1174
|
+
"stroke-linecap": "round",
|
|
1175
|
+
"stroke-linejoin": "round"
|
|
1176
|
+
})
|
|
1177
|
+
);
|
|
1178
|
+
if (options.points !== false) {
|
|
1179
|
+
points.forEach((point) => {
|
|
1180
|
+
const circle = svgEl("circle", {
|
|
1181
|
+
class: "vd-chart-point",
|
|
1182
|
+
cx: point.x,
|
|
1183
|
+
cy: point.y,
|
|
1184
|
+
r: options.pointRadius || 3.5,
|
|
1185
|
+
fill: options.pointFill || theme.backgroundColor,
|
|
1186
|
+
stroke: color,
|
|
1187
|
+
"stroke-width": 2,
|
|
1188
|
+
role: "graphics-symbol",
|
|
1189
|
+
"aria-label": `${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1190
|
+
});
|
|
1191
|
+
makeInteractive(
|
|
1192
|
+
circle,
|
|
1193
|
+
typeof options.onPointClick === "function",
|
|
1194
|
+
options.tooltip !== false
|
|
1195
|
+
);
|
|
1196
|
+
attachTooltip(
|
|
1197
|
+
instance,
|
|
1198
|
+
circle,
|
|
1199
|
+
options,
|
|
1200
|
+
{
|
|
1201
|
+
datum: point.raw,
|
|
1202
|
+
x: point.xValue,
|
|
1203
|
+
y: point.yValue,
|
|
1204
|
+
value: point.yValue,
|
|
1205
|
+
index: point.index
|
|
1206
|
+
},
|
|
1207
|
+
`${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1208
|
+
);
|
|
1209
|
+
attachClick(circle, options.onPointClick, point.raw, point.index);
|
|
1210
|
+
append(markGroup, circle);
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
if (labels) {
|
|
1214
|
+
points.forEach(
|
|
1215
|
+
(point) => drawDataLabel(svg, point.x, point.y - 8, point.yValue, labels, theme)
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function renderMultiLineChart(instance, mode) {
|
|
1220
|
+
const shell = createSvgShell(instance);
|
|
1221
|
+
const { svg, size, theme, plot } = shell;
|
|
1222
|
+
const options = instance.options;
|
|
1223
|
+
const seriesList = buildSeriesList(options);
|
|
1224
|
+
const allRows = seriesList.flatMap((series) => series.rows);
|
|
1225
|
+
if (!allRows.length) {
|
|
1226
|
+
renderEmpty(svg, size, theme);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const xInfo = inferXScale(allRows, plot, options);
|
|
1230
|
+
const yDomain = niceDomain(
|
|
1231
|
+
allRows.map((row) => row.y),
|
|
1232
|
+
{
|
|
1233
|
+
includeZero: mode === "area" || options.yIncludeZero === true,
|
|
1234
|
+
min: options.yMin,
|
|
1235
|
+
max: options.yMax,
|
|
1236
|
+
tickCount: options.yTickCount
|
|
1237
|
+
}
|
|
1238
|
+
);
|
|
1239
|
+
const yScale = scaleLinear({ domain: yDomain, range: [plot.bottom, plot.top] });
|
|
1240
|
+
const yTicks = yScale.ticks(options.yTickCount ?? 5);
|
|
1241
|
+
drawCartesianAxes(svg, {
|
|
1242
|
+
plot,
|
|
1243
|
+
xScale: xInfo.scale,
|
|
1244
|
+
yScale,
|
|
1245
|
+
xTicks: xInfo.ticks,
|
|
1246
|
+
yTicks,
|
|
1247
|
+
theme,
|
|
1248
|
+
options,
|
|
1249
|
+
categoricalX: xInfo.type === "point"
|
|
1250
|
+
});
|
|
1251
|
+
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1252
|
+
const labels = dataLabelConfig(options);
|
|
1253
|
+
const markGroup = append(svg, svgEl("g", { class: `vd-chart-marks vd-chart-${mode}` }));
|
|
1254
|
+
const baseline = yScale(Math.max(0, yDomain[0]));
|
|
1255
|
+
seriesList.forEach((series) => {
|
|
1256
|
+
const stroke = seriesColor(series, theme);
|
|
1257
|
+
const points = series.rows.map((row) => ({
|
|
1258
|
+
raw: row.raw,
|
|
1259
|
+
index: row.index,
|
|
1260
|
+
xValue: row.x,
|
|
1261
|
+
yValue: row.y,
|
|
1262
|
+
x: xInfo.scale(xInfo.mapValue(row.x)),
|
|
1263
|
+
y: yScale(row.y)
|
|
1264
|
+
})).filter((point) => isFiniteNumber(point.x) && isFiniteNumber(point.y));
|
|
1265
|
+
if (mode === "area") {
|
|
1266
|
+
append(
|
|
1267
|
+
markGroup,
|
|
1268
|
+
svgEl("path", {
|
|
1269
|
+
class: "vd-chart-area-path",
|
|
1270
|
+
d: areaPath(points, baseline),
|
|
1271
|
+
fill: series.color || stroke,
|
|
1272
|
+
opacity: options.fillOpacity ?? 0.18,
|
|
1273
|
+
stroke: "none"
|
|
1274
|
+
})
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
append(
|
|
1278
|
+
markGroup,
|
|
1279
|
+
svgEl("path", {
|
|
1280
|
+
class: "vd-chart-line-path",
|
|
1281
|
+
d: linePath(points),
|
|
1282
|
+
fill: "none",
|
|
1283
|
+
stroke,
|
|
1284
|
+
"stroke-width": options.strokeWidth || 2,
|
|
1285
|
+
"stroke-linecap": "round",
|
|
1286
|
+
"stroke-linejoin": "round"
|
|
1287
|
+
})
|
|
1288
|
+
);
|
|
1289
|
+
if (options.points !== false) {
|
|
1290
|
+
points.forEach((point) => {
|
|
1291
|
+
const circle = svgEl("circle", {
|
|
1292
|
+
class: "vd-chart-point",
|
|
1293
|
+
cx: point.x,
|
|
1294
|
+
cy: point.y,
|
|
1295
|
+
r: options.pointRadius || 3.5,
|
|
1296
|
+
fill: options.pointFill || theme.backgroundColor,
|
|
1297
|
+
stroke,
|
|
1298
|
+
"stroke-width": 2,
|
|
1299
|
+
role: "graphics-symbol",
|
|
1300
|
+
"aria-label": `${series.name} ${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1301
|
+
});
|
|
1302
|
+
makeInteractive(
|
|
1303
|
+
circle,
|
|
1304
|
+
typeof options.onPointClick === "function",
|
|
1305
|
+
options.tooltip !== false
|
|
1306
|
+
);
|
|
1307
|
+
attachTooltip(
|
|
1308
|
+
instance,
|
|
1309
|
+
circle,
|
|
1310
|
+
options,
|
|
1311
|
+
{
|
|
1312
|
+
datum: point.raw,
|
|
1313
|
+
x: point.xValue,
|
|
1314
|
+
y: point.yValue,
|
|
1315
|
+
value: point.yValue,
|
|
1316
|
+
index: point.index,
|
|
1317
|
+
seriesIndex: series.seriesIndex,
|
|
1318
|
+
seriesName: series.name
|
|
1319
|
+
},
|
|
1320
|
+
`${series.name} \u2014 ${formatCategory(point.xValue)}: ${formatNumber(point.yValue)}`
|
|
1321
|
+
);
|
|
1322
|
+
attachClick(circle, options.onPointClick, point.raw, point.index);
|
|
1323
|
+
append(markGroup, circle);
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (labels) {
|
|
1327
|
+
points.forEach(
|
|
1328
|
+
(point) => drawDataLabel(svg, point.x, point.y - 8, point.yValue, labels, theme)
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
if (options.legend !== false) {
|
|
1333
|
+
renderTopLegend(
|
|
1334
|
+
svg,
|
|
1335
|
+
seriesList.map((series) => ({ label: series.name, color: seriesColor(series, theme) })),
|
|
1336
|
+
theme,
|
|
1337
|
+
plot
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function renderScatterChart(instance) {
|
|
1342
|
+
const shell = createSvgShell(instance);
|
|
1343
|
+
const { svg, size, theme, plot } = shell;
|
|
1344
|
+
const options = instance.options;
|
|
1345
|
+
const data = toArray(options.data);
|
|
1346
|
+
const xAccessor = createAccessor(options.x, "x");
|
|
1347
|
+
const yAccessor = createAccessor(options.y, "y");
|
|
1348
|
+
const rows = data.map((datum, index) => ({
|
|
1349
|
+
raw: datum,
|
|
1350
|
+
index,
|
|
1351
|
+
x: xAccessor(datum),
|
|
1352
|
+
y: toNumber(yAccessor(datum))
|
|
1353
|
+
})).filter((row) => row.x != null && isFiniteNumber(row.y));
|
|
1354
|
+
if (!rows.length) {
|
|
1355
|
+
renderEmpty(svg, size, theme);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const xInfo = inferXScale(rows, plot, options);
|
|
1359
|
+
const yDomain = niceDomain(
|
|
1360
|
+
rows.map((row) => row.y),
|
|
1361
|
+
{
|
|
1362
|
+
includeZero: options.yIncludeZero === true,
|
|
1363
|
+
min: options.yMin,
|
|
1364
|
+
max: options.yMax,
|
|
1365
|
+
tickCount: options.yTickCount
|
|
1366
|
+
}
|
|
1367
|
+
);
|
|
1368
|
+
const yScale = scaleLinear({ domain: yDomain, range: [plot.bottom, plot.top] });
|
|
1369
|
+
const yTicks = yScale.ticks(options.yTickCount ?? 5);
|
|
1370
|
+
const color = getColorScale(rows, options.color, theme);
|
|
1371
|
+
drawCartesianAxes(svg, {
|
|
1372
|
+
plot,
|
|
1373
|
+
xScale: xInfo.scale,
|
|
1374
|
+
yScale,
|
|
1375
|
+
xTicks: xInfo.ticks,
|
|
1376
|
+
yTicks,
|
|
1377
|
+
theme,
|
|
1378
|
+
options,
|
|
1379
|
+
categoricalX: xInfo.type === "point"
|
|
1380
|
+
});
|
|
1381
|
+
drawAnnotations(svg, options, plot, xInfo.scale, yScale, theme);
|
|
1382
|
+
const labels = dataLabelConfig(options);
|
|
1383
|
+
const markGroup = append(svg, svgEl("g", { class: "vd-chart-marks vd-chart-scatter" }));
|
|
1384
|
+
rows.forEach((row) => {
|
|
1385
|
+
const cx = xInfo.scale(xInfo.mapValue(row.x));
|
|
1386
|
+
const cy = yScale(row.y);
|
|
1387
|
+
if (!isFiniteNumber(cx) || !isFiniteNumber(cy)) return;
|
|
1388
|
+
const fill = colorForRow(color, row, theme);
|
|
1389
|
+
const circle = svgEl("circle", {
|
|
1390
|
+
class: "vd-chart-scatter-point",
|
|
1391
|
+
cx,
|
|
1392
|
+
cy,
|
|
1393
|
+
r: options.pointRadius || 4,
|
|
1394
|
+
fill,
|
|
1395
|
+
opacity: options.pointOpacity ?? 0.88,
|
|
1396
|
+
role: "graphics-symbol",
|
|
1397
|
+
"aria-label": `${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1398
|
+
});
|
|
1399
|
+
makeInteractive(circle, typeof options.onPointClick === "function", options.tooltip !== false);
|
|
1400
|
+
attachTooltip(
|
|
1401
|
+
instance,
|
|
1402
|
+
circle,
|
|
1403
|
+
options,
|
|
1404
|
+
{
|
|
1405
|
+
datum: row.raw,
|
|
1406
|
+
x: row.x,
|
|
1407
|
+
y: row.y,
|
|
1408
|
+
value: row.y,
|
|
1409
|
+
index: row.index
|
|
1410
|
+
},
|
|
1411
|
+
`${formatCategory(row.x)}: ${formatNumber(row.y)}`
|
|
1412
|
+
);
|
|
1413
|
+
attachClick(circle, options.onPointClick, row.raw, row.index);
|
|
1414
|
+
if (labels) drawDataLabel(svg, cx, cy - 8, row.y, labels, theme);
|
|
1415
|
+
append(markGroup, circle);
|
|
1416
|
+
});
|
|
1417
|
+
if (options.legend && color && color.scale) {
|
|
1418
|
+
renderTopLegend(
|
|
1419
|
+
svg,
|
|
1420
|
+
color.scale.domain().map((cat) => ({ label: cat, color: color.scale(cat) })),
|
|
1421
|
+
theme,
|
|
1422
|
+
plot
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function renderLegend(svg, rows, colorScale, theme, x, y) {
|
|
1427
|
+
const legend = append(svg, svgEl("g", { class: "vd-chart-legend" }));
|
|
1428
|
+
rows.slice(0, 8).forEach((row, index) => {
|
|
1429
|
+
const itemY = y + index * 20;
|
|
1430
|
+
append(
|
|
1431
|
+
legend,
|
|
1432
|
+
svgEl("rect", {
|
|
1433
|
+
x,
|
|
1434
|
+
y: itemY - 9,
|
|
1435
|
+
width: 10,
|
|
1436
|
+
height: 10,
|
|
1437
|
+
rx: 2,
|
|
1438
|
+
fill: colorScale(row.label)
|
|
1439
|
+
})
|
|
1440
|
+
);
|
|
1441
|
+
append(
|
|
1442
|
+
legend,
|
|
1443
|
+
setText(
|
|
1444
|
+
svgEl("text", {
|
|
1445
|
+
x: x + 16,
|
|
1446
|
+
y: itemY,
|
|
1447
|
+
fill: theme.mutedTextColor,
|
|
1448
|
+
"font-size": 11
|
|
1449
|
+
}),
|
|
1450
|
+
formatCategory(row.label)
|
|
1451
|
+
)
|
|
1452
|
+
);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function renderDonutChart(instance) {
|
|
1456
|
+
instance.options.polar = true;
|
|
1457
|
+
const shell = createSvgShell(instance);
|
|
1458
|
+
const { svg, size, theme, plot } = shell;
|
|
1459
|
+
const options = instance.options;
|
|
1460
|
+
const data = toArray(options.data);
|
|
1461
|
+
const labelAccessor = createAccessor(options.label, "label");
|
|
1462
|
+
const valueAccessor = createAccessor(options.value, "value");
|
|
1463
|
+
const rows = data.map((datum, index) => ({
|
|
1464
|
+
raw: datum,
|
|
1465
|
+
index,
|
|
1466
|
+
label: labelAccessor(datum),
|
|
1467
|
+
value: toNumber(valueAccessor(datum))
|
|
1468
|
+
})).filter((row) => row.label != null && isFiniteNumber(row.value) && row.value > 0);
|
|
1469
|
+
const total = rows.reduce((sum, row) => sum + row.value, 0);
|
|
1470
|
+
if (!rows.length || total <= 0) {
|
|
1471
|
+
renderEmpty(svg, size, theme);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const legendSpace = options.legend === false || size.width < 520 ? 0 : 128;
|
|
1475
|
+
const cx = (plot.left + plot.right - legendSpace) / 2;
|
|
1476
|
+
const cy = (plot.top + plot.bottom) / 2 + 5;
|
|
1477
|
+
const outerRadius = Math.max(
|
|
1478
|
+
28,
|
|
1479
|
+
Math.min(plot.right - plot.left - legendSpace, plot.bottom - plot.top) / 2
|
|
1480
|
+
);
|
|
1481
|
+
const ratio = Math.max(0, Math.min(0.9, Number(options.innerRadiusRatio ?? 0.62)));
|
|
1482
|
+
const innerRadius = outerRadius * ratio;
|
|
1483
|
+
const colorScale = scaleOrdinal({ domain: rows.map((row) => row.label), range: theme.colors });
|
|
1484
|
+
const labels = dataLabelConfig(options);
|
|
1485
|
+
const markGroup = append(svg, svgEl("g", { class: "vd-chart-marks vd-chart-slices" }));
|
|
1486
|
+
let cursor = -Math.PI / 2;
|
|
1487
|
+
rows.forEach((row) => {
|
|
1488
|
+
const angle = row.value / total * TAU;
|
|
1489
|
+
const start = cursor;
|
|
1490
|
+
const end = cursor + angle;
|
|
1491
|
+
cursor = end;
|
|
1492
|
+
const path = svgEl("path", {
|
|
1493
|
+
class: "vd-chart-slice",
|
|
1494
|
+
d: arcPath(cx, cy, outerRadius, innerRadius, start, end),
|
|
1495
|
+
fill: colorScale(row.label),
|
|
1496
|
+
stroke: theme.backgroundColor,
|
|
1497
|
+
"stroke-width": 2,
|
|
1498
|
+
role: "graphics-symbol",
|
|
1499
|
+
"aria-label": `${formatCategory(row.label)}: ${formatNumber(row.value)}`
|
|
1500
|
+
});
|
|
1501
|
+
makeInteractive(path, typeof options.onSliceClick === "function", options.tooltip !== false);
|
|
1502
|
+
attachTooltip(
|
|
1503
|
+
instance,
|
|
1504
|
+
path,
|
|
1505
|
+
options,
|
|
1506
|
+
{
|
|
1507
|
+
datum: row.raw,
|
|
1508
|
+
label: row.label,
|
|
1509
|
+
value: row.value,
|
|
1510
|
+
y: row.value,
|
|
1511
|
+
index: row.index
|
|
1512
|
+
},
|
|
1513
|
+
`${formatCategory(row.label)}: ${formatNumber(row.value)}`
|
|
1514
|
+
);
|
|
1515
|
+
attachClick(path, options.onSliceClick, row.raw, row.index);
|
|
1516
|
+
if (labels) {
|
|
1517
|
+
const mid = (start + end) / 2;
|
|
1518
|
+
const point = polarPoint(cx, cy, (innerRadius + outerRadius) / 2, mid);
|
|
1519
|
+
drawDataLabel(svg, point.x, point.y, row.value, labels, theme);
|
|
1520
|
+
}
|
|
1521
|
+
append(markGroup, path);
|
|
1522
|
+
});
|
|
1523
|
+
if (innerRadius > 16 && options.centerLabel !== false) {
|
|
1524
|
+
append(
|
|
1525
|
+
svg,
|
|
1526
|
+
setText(
|
|
1527
|
+
svgEl("text", {
|
|
1528
|
+
x: cx,
|
|
1529
|
+
y: cy - 2,
|
|
1530
|
+
fill: theme.textColor,
|
|
1531
|
+
"font-size": 18,
|
|
1532
|
+
"font-weight": 700,
|
|
1533
|
+
"text-anchor": "middle"
|
|
1534
|
+
}),
|
|
1535
|
+
options.centerLabel || formatNumber(total)
|
|
1536
|
+
)
|
|
1537
|
+
);
|
|
1538
|
+
append(
|
|
1539
|
+
svg,
|
|
1540
|
+
setText(
|
|
1541
|
+
svgEl("text", {
|
|
1542
|
+
x: cx,
|
|
1543
|
+
y: cy + 16,
|
|
1544
|
+
fill: theme.mutedTextColor,
|
|
1545
|
+
"font-size": 11,
|
|
1546
|
+
"text-anchor": "middle"
|
|
1547
|
+
}),
|
|
1548
|
+
options.centerSubLabel || "total"
|
|
1549
|
+
)
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
if (legendSpace) {
|
|
1553
|
+
renderLegend(svg, rows, colorScale, theme, plot.right - legendSpace + 8, plot.top + 28);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
var ChartInstance = class {
|
|
1557
|
+
constructor(kind, options, renderer) {
|
|
1558
|
+
this.kind = kind;
|
|
1559
|
+
this.options = { ...options };
|
|
1560
|
+
this.target = resolveTarget(options.target);
|
|
1561
|
+
this.renderer = renderer;
|
|
1562
|
+
this.resizeObserver = null;
|
|
1563
|
+
this.destroyed = false;
|
|
1564
|
+
this.theme = null;
|
|
1565
|
+
this.size = null;
|
|
1566
|
+
this.plot = null;
|
|
1567
|
+
this.tooltipEl = null;
|
|
1568
|
+
this.render();
|
|
1569
|
+
this.setupResizeObserver();
|
|
1570
|
+
}
|
|
1571
|
+
render() {
|
|
1572
|
+
if (this.destroyed) return this;
|
|
1573
|
+
this.renderer(this);
|
|
1574
|
+
return this;
|
|
1575
|
+
}
|
|
1576
|
+
update(nextOptions = {}) {
|
|
1577
|
+
if (this.destroyed) return this;
|
|
1578
|
+
this.options = { ...this.options, ...nextOptions };
|
|
1579
|
+
return this.render();
|
|
1580
|
+
}
|
|
1581
|
+
resize() {
|
|
1582
|
+
return this.render();
|
|
1583
|
+
}
|
|
1584
|
+
setupResizeObserver() {
|
|
1585
|
+
if (this.options.responsive === false || !hasWindow() || typeof ResizeObserver === "undefined")
|
|
1586
|
+
return;
|
|
1587
|
+
let lastWidth = this.target.clientWidth;
|
|
1588
|
+
let lastHeight = this.target.clientHeight;
|
|
1589
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1590
|
+
const width = this.target.clientWidth;
|
|
1591
|
+
const height = this.target.clientHeight;
|
|
1592
|
+
if (width === lastWidth && height === lastHeight) return;
|
|
1593
|
+
lastWidth = width;
|
|
1594
|
+
lastHeight = height;
|
|
1595
|
+
this.resize();
|
|
1596
|
+
});
|
|
1597
|
+
this.resizeObserver.observe(this.target);
|
|
1598
|
+
}
|
|
1599
|
+
ensureTooltip() {
|
|
1600
|
+
if (this.tooltipEl && this.tooltipEl.isConnected) return this.tooltipEl;
|
|
1601
|
+
const tooltip = document.createElement("div");
|
|
1602
|
+
tooltip.className = "vd-chart-tooltip";
|
|
1603
|
+
tooltip.setAttribute("role", "status");
|
|
1604
|
+
tooltip.setAttribute("aria-live", "polite");
|
|
1605
|
+
this.target.appendChild(tooltip);
|
|
1606
|
+
this.tooltipEl = tooltip;
|
|
1607
|
+
return tooltip;
|
|
1608
|
+
}
|
|
1609
|
+
showTooltip(content, event) {
|
|
1610
|
+
const tooltip = this.ensureTooltip();
|
|
1611
|
+
tooltip.textContent = String(content);
|
|
1612
|
+
const rect = this.target.getBoundingClientRect();
|
|
1613
|
+
let x = rect.width / 2;
|
|
1614
|
+
let y = rect.height / 2;
|
|
1615
|
+
if (event && isFiniteNumber(event.clientX) && isFiniteNumber(event.clientY)) {
|
|
1616
|
+
x = event.clientX - rect.left;
|
|
1617
|
+
y = event.clientY - rect.top;
|
|
1618
|
+
} else if (event && event.target && typeof event.target.getBoundingClientRect === "function") {
|
|
1619
|
+
const markRect = event.target.getBoundingClientRect();
|
|
1620
|
+
x = markRect.left + markRect.width / 2 - rect.left;
|
|
1621
|
+
y = markRect.top - rect.top;
|
|
1622
|
+
}
|
|
1623
|
+
tooltip.style.left = `${Math.max(8, Math.min(rect.width - 8, x))}px`;
|
|
1624
|
+
tooltip.style.top = `${Math.max(18, Math.min(rect.height - 8, y))}px`;
|
|
1625
|
+
tooltip.classList.add("is-visible");
|
|
1626
|
+
}
|
|
1627
|
+
hideTooltip() {
|
|
1628
|
+
if (this.tooltipEl) {
|
|
1629
|
+
this.tooltipEl.classList.remove("is-visible");
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
destroy() {
|
|
1633
|
+
if (this.destroyed) return;
|
|
1634
|
+
if (this.resizeObserver) {
|
|
1635
|
+
this.resizeObserver.disconnect();
|
|
1636
|
+
this.resizeObserver = null;
|
|
1637
|
+
}
|
|
1638
|
+
this.target.innerHTML = "";
|
|
1639
|
+
this.target.classList.remove("vd-chart-root", `vd-chart-${this.kind}`);
|
|
1640
|
+
this.destroyed = true;
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
function createChartFactory(kind, renderer, defaults = {}) {
|
|
1644
|
+
return function chartFactory(options = {}) {
|
|
1645
|
+
return new ChartInstance(kind, { ...defaults, ...options }, renderer);
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
var BarChart = createChartFactory("bar", renderBarChart);
|
|
1649
|
+
var LineChart = createChartFactory(
|
|
1650
|
+
"line",
|
|
1651
|
+
(instance) => renderLineLikeChart(instance, "line")
|
|
1652
|
+
);
|
|
1653
|
+
var AreaChart = createChartFactory(
|
|
1654
|
+
"area",
|
|
1655
|
+
(instance) => renderLineLikeChart(instance, "area")
|
|
1656
|
+
);
|
|
1657
|
+
var ScatterChart = createChartFactory("scatter", renderScatterChart);
|
|
1658
|
+
var DonutChart = createChartFactory("donut", renderDonutChart, { innerRadiusRatio: 0.62 });
|
|
1659
|
+
var PieChart = createChartFactory("pie", renderDonutChart, { innerRadiusRatio: 0 });
|
|
1660
|
+
|
|
1661
|
+
// src/charts/vue.js
|
|
1662
|
+
var FACTORIES = {
|
|
1663
|
+
bar: BarChart,
|
|
1664
|
+
line: LineChart,
|
|
1665
|
+
area: AreaChart,
|
|
1666
|
+
scatter: ScatterChart,
|
|
1667
|
+
donut: DonutChart,
|
|
1668
|
+
pie: PieChart
|
|
1669
|
+
};
|
|
1670
|
+
var CHART_PROPS = {
|
|
1671
|
+
type: { type: String, default: "bar" },
|
|
1672
|
+
data: { type: Array, default: () => [] },
|
|
1673
|
+
x: { type: [String, Function], default: void 0 },
|
|
1674
|
+
y: { type: [String, Function], default: void 0 },
|
|
1675
|
+
label: { type: [String, Function], default: void 0 },
|
|
1676
|
+
value: { type: [String, Function], default: void 0 },
|
|
1677
|
+
// CSS color, category-field name, or per-datum function `(row) => color`.
|
|
1678
|
+
color: { type: [String, Function], default: void 0 },
|
|
1679
|
+
title: { type: String, default: void 0 },
|
|
1680
|
+
description: { type: String, default: void 0 },
|
|
1681
|
+
width: { type: Number, default: void 0 },
|
|
1682
|
+
height: { type: Number, default: 300 },
|
|
1683
|
+
innerRadiusRatio: { type: Number, default: void 0 },
|
|
1684
|
+
theme: { type: Object, default: void 0 },
|
|
1685
|
+
tooltip: { type: [Function, String, Boolean], default: void 0 },
|
|
1686
|
+
responsive: { type: Boolean, default: true },
|
|
1687
|
+
// Multi-series (bar → grouped, line/area → one path each).
|
|
1688
|
+
series: { type: Array, default: void 0 },
|
|
1689
|
+
// `true` / `false` / `{ position }`.
|
|
1690
|
+
legend: { type: [Boolean, Object], default: void 0 },
|
|
1691
|
+
// Value labels on marks: `true` / `false` / `{ format, color }`.
|
|
1692
|
+
dataLabels: { type: [Boolean, Object], default: void 0 },
|
|
1693
|
+
// Reference lines: `[{ y?, x?, label?, color?, dash? }]`.
|
|
1694
|
+
annotations: { type: Array, default: void 0 },
|
|
1695
|
+
// Axis range + ticks.
|
|
1696
|
+
xMin: { type: Number, default: void 0 },
|
|
1697
|
+
xMax: { type: Number, default: void 0 },
|
|
1698
|
+
yMin: { type: Number, default: void 0 },
|
|
1699
|
+
yMax: { type: Number, default: void 0 },
|
|
1700
|
+
yTickCount: { type: Number, default: void 0 },
|
|
1701
|
+
yIncludeZero: { type: Boolean, default: void 0 },
|
|
1702
|
+
xFormat: { type: Function, default: void 0 },
|
|
1703
|
+
yFormat: { type: Function, default: void 0 },
|
|
1704
|
+
xAxis: { type: Object, default: void 0 },
|
|
1705
|
+
yAxis: { type: Object, default: void 0 }
|
|
1706
|
+
};
|
|
1707
|
+
function optionsFrom(target, props) {
|
|
1708
|
+
return {
|
|
1709
|
+
target,
|
|
1710
|
+
type: props.type,
|
|
1711
|
+
data: props.data,
|
|
1712
|
+
x: props.x,
|
|
1713
|
+
y: props.y,
|
|
1714
|
+
label: props.label,
|
|
1715
|
+
value: props.value,
|
|
1716
|
+
color: props.color,
|
|
1717
|
+
title: props.title,
|
|
1718
|
+
description: props.description,
|
|
1719
|
+
width: props.width,
|
|
1720
|
+
height: props.height,
|
|
1721
|
+
innerRadiusRatio: props.innerRadiusRatio,
|
|
1722
|
+
theme: props.theme,
|
|
1723
|
+
tooltip: props.tooltip,
|
|
1724
|
+
responsive: props.responsive,
|
|
1725
|
+
series: props.series,
|
|
1726
|
+
legend: props.legend,
|
|
1727
|
+
dataLabels: props.dataLabels,
|
|
1728
|
+
annotations: props.annotations,
|
|
1729
|
+
xMin: props.xMin,
|
|
1730
|
+
xMax: props.xMax,
|
|
1731
|
+
yMin: props.yMin,
|
|
1732
|
+
yMax: props.yMax,
|
|
1733
|
+
yTickCount: props.yTickCount,
|
|
1734
|
+
yIncludeZero: props.yIncludeZero,
|
|
1735
|
+
xFormat: props.xFormat,
|
|
1736
|
+
yFormat: props.yFormat,
|
|
1737
|
+
xAxis: props.xAxis,
|
|
1738
|
+
yAxis: props.yAxis
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
var VdChart = (0, import_vue.defineComponent)({
|
|
1742
|
+
name: "VdChart",
|
|
1743
|
+
props: CHART_PROPS,
|
|
1744
|
+
setup(props) {
|
|
1745
|
+
const el = (0, import_vue.ref)(null);
|
|
1746
|
+
let instance = null;
|
|
1747
|
+
let currentType = props.type;
|
|
1748
|
+
const create = () => {
|
|
1749
|
+
const factory = FACTORIES[props.type] || BarChart;
|
|
1750
|
+
currentType = props.type;
|
|
1751
|
+
instance = factory(optionsFrom(el.value, props));
|
|
1752
|
+
};
|
|
1753
|
+
(0, import_vue.onMounted)(() => {
|
|
1754
|
+
if (typeof window === "undefined" || !el.value) return;
|
|
1755
|
+
create();
|
|
1756
|
+
});
|
|
1757
|
+
(0, import_vue.watch)(
|
|
1758
|
+
() => [
|
|
1759
|
+
props.type,
|
|
1760
|
+
props.data,
|
|
1761
|
+
props.x,
|
|
1762
|
+
props.y,
|
|
1763
|
+
props.label,
|
|
1764
|
+
props.value,
|
|
1765
|
+
props.color,
|
|
1766
|
+
props.title,
|
|
1767
|
+
props.description,
|
|
1768
|
+
props.width,
|
|
1769
|
+
props.height,
|
|
1770
|
+
props.innerRadiusRatio,
|
|
1771
|
+
props.theme,
|
|
1772
|
+
props.tooltip,
|
|
1773
|
+
props.responsive,
|
|
1774
|
+
props.series,
|
|
1775
|
+
props.legend,
|
|
1776
|
+
props.dataLabels,
|
|
1777
|
+
props.annotations,
|
|
1778
|
+
props.xMin,
|
|
1779
|
+
props.xMax,
|
|
1780
|
+
props.yMin,
|
|
1781
|
+
props.yMax,
|
|
1782
|
+
props.yTickCount,
|
|
1783
|
+
props.yIncludeZero,
|
|
1784
|
+
props.xFormat,
|
|
1785
|
+
props.yFormat,
|
|
1786
|
+
props.xAxis,
|
|
1787
|
+
props.yAxis
|
|
1788
|
+
],
|
|
1789
|
+
() => {
|
|
1790
|
+
if (!instance) return;
|
|
1791
|
+
if (props.type !== currentType) {
|
|
1792
|
+
instance.destroy();
|
|
1793
|
+
create();
|
|
1794
|
+
} else {
|
|
1795
|
+
instance.update(optionsFrom(el.value, props));
|
|
1796
|
+
}
|
|
1797
|
+
},
|
|
1798
|
+
{ deep: true }
|
|
1799
|
+
);
|
|
1800
|
+
(0, import_vue.onBeforeUnmount)(() => {
|
|
1801
|
+
if (instance) {
|
|
1802
|
+
instance.destroy();
|
|
1803
|
+
instance = null;
|
|
1804
|
+
}
|
|
1805
|
+
});
|
|
1806
|
+
return () => (0, import_vue.h)("div", {
|
|
1807
|
+
ref: el,
|
|
1808
|
+
class: "vd-chart",
|
|
1809
|
+
style: props.height ? { minHeight: `${props.height}px` } : void 0
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
function typed(name, type) {
|
|
1814
|
+
return (0, import_vue.defineComponent)({
|
|
1815
|
+
name,
|
|
1816
|
+
props: CHART_PROPS,
|
|
1817
|
+
setup(props) {
|
|
1818
|
+
return () => (0, import_vue.h)(VdChart, { ...props, type });
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
var VdBarChart = typed("VdBarChart", "bar");
|
|
1823
|
+
var VdLineChart = typed("VdLineChart", "line");
|
|
1824
|
+
var VdAreaChart = typed("VdAreaChart", "area");
|
|
1825
|
+
var VdScatterChart = typed("VdScatterChart", "scatter");
|
|
1826
|
+
var VdDonutChart = typed("VdDonutChart", "donut");
|
|
1827
|
+
var VdPieChart = typed("VdPieChart", "pie");
|
|
1828
|
+
//# sourceMappingURL=index.cjs.map
|