@wil-works/evoke-charts 0.5.0 → 0.7.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/README.md +39 -4
- package/dist/3d.mjs +4992 -0
- package/dist/{ai-BmXDig6M.js → ai-cRi95pS2.js} +1053 -617
- package/dist/ai.mjs +2 -2
- package/dist/evoke-charts.css +197 -31
- package/dist/index.mjs +401 -204
- package/dist/palette-lwVAltky.js +117 -0
- package/dist/schema-CM4hKlpc.js +1102 -0
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as isMissingValue, A as vennHitTest, B as doughnutInnerRadius, C as windowToX, D as arcHitTest, E as ganttHitTest, F as squarifyTreemap, G as getToolboxBounds, H as scatterGroupColors, I as buildPieSlices, J as getHeatmapCategories, K as calendarHitTest, L as computeFacetGrids, M as computeSunburstGeometry, N as sunburstValue, O as chordHitTest, P as computeBins, Q as getTheme, R as computeMatrixCells, S as getSliderGeometry, T as zoomToSlice, U as scatterPointPositions, V as matrixFieldExtent, W as getLegendBounds, X as createAnimation, Y as xToCategoryIndex, Z as getPadding, _ as chartOptionsSchema, at as INTERACTION, b as createSvgRecorder, ct as resolveCursor, et as resolveTickExtendedRange, h as parseDataTable, it as minOf, j as boxplotHitTest, k as sankeyHitTest, l as generateChartSpec, n as buildChartPrompt, nt as waterfallSteps, ot as applyWheelZoom, q as funnelStepColor, rt as maxOf, st as pickTooltipRows, t as lintChartSpec, tt as updateAnimation, v as validateOptions, w as xToPercent, x as getDataZoomConfig, y as renderChart, z as computePieMaxRadius } from "./ai-cRi95pS2.js";
|
|
2
|
+
import { a as staggerProgress, i as prefersReducedMotion, n as clearSeriesPalette, r as _plugin_vue_export_helper_default, t as applySeriesPalette } from "./palette-lwVAltky.js";
|
|
3
|
+
import { S as DEFAULT_I18N_ZH, b as resolveChartPalette, y as CHART_PALETTES } from "./schema-CM4hKlpc.js";
|
|
2
4
|
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, h, inject, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, watch, withCtx } from "vue";
|
|
3
5
|
//#region src/connect.js
|
|
4
6
|
var connectGroups = /* @__PURE__ */ new Map();
|
|
@@ -22,15 +24,8 @@ function broadcastConnect(group, source, apply) {
|
|
|
22
24
|
});
|
|
23
25
|
}
|
|
24
26
|
//#endregion
|
|
25
|
-
//#region \0plugin-vue:export-helper
|
|
26
|
-
var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
27
|
-
const target = sfc.__vccOpts || sfc;
|
|
28
|
-
for (const [key, val] of props) target[key] = val;
|
|
29
|
-
return target;
|
|
30
|
-
};
|
|
31
|
-
//#endregion
|
|
32
27
|
//#region src/chart.vue
|
|
33
|
-
var _hoisted_1 = ["aria-label"];
|
|
28
|
+
var _hoisted_1 = ["tabindex", "aria-label"];
|
|
34
29
|
var _hoisted_2 = {
|
|
35
30
|
key: 1,
|
|
36
31
|
class: "ev-chart__overlay"
|
|
@@ -121,7 +116,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
121
116
|
switch (opt.type) {
|
|
122
117
|
case "pie":
|
|
123
118
|
case "doughnut":
|
|
124
|
-
case "rose": return !opt.pieData || opt.pieData.length === 0;
|
|
119
|
+
case "rose": return !opt.pieData || opt.pieData.length === 0 || opt.pieData.every((d) => !(d.value > 0));
|
|
125
120
|
case "scatter": return !opt.scatterData || opt.scatterData.length === 0;
|
|
126
121
|
case "funnel": return !opt.funnelData || opt.funnelData.length === 0;
|
|
127
122
|
case "gauge": return !opt.gauge;
|
|
@@ -140,7 +135,12 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
140
135
|
case "sankey":
|
|
141
136
|
case "chord":
|
|
142
137
|
case "arc": return !(opt.sankeyData || opt.chordData || opt.arcData)?.nodes?.length;
|
|
143
|
-
case "venn":
|
|
138
|
+
case "venn":
|
|
139
|
+
if (!opt.vennData || opt.vennData.length === 0) return true;
|
|
140
|
+
{
|
|
141
|
+
const circles = opt.vennData.filter((d) => !d.sets || d.sets.length === 1);
|
|
142
|
+
return circles.length === 0 || circles.length > 3;
|
|
143
|
+
}
|
|
144
144
|
case "gantt": return !opt.ganttData || opt.ganttData.length === 0;
|
|
145
145
|
case "scatter-matrix": return !opt.matrixFields?.length || !opt.matrixData?.length;
|
|
146
146
|
default: return !opt.labels?.length || !opt.series?.length;
|
|
@@ -236,11 +236,20 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
236
236
|
data: s.data.slice(startIdx, endIdx)
|
|
237
237
|
}));
|
|
238
238
|
if (base.candleData) sliced.candleData = base.candleData.slice(startIdx, endIdx);
|
|
239
|
+
if (base.volumeData) sliced.volumeData = base.volumeData.slice(startIdx, endIdx);
|
|
239
240
|
sliced.__sliceStart = startIdx;
|
|
241
|
+
} else if (base.candleData) {
|
|
242
|
+
const cn = base.candleData.length;
|
|
243
|
+
if (cn > 0) {
|
|
244
|
+
const { startIdx, endIdx } = zoomToSlice(range, cn);
|
|
245
|
+
sliced.candleData = base.candleData.slice(startIdx, endIdx);
|
|
246
|
+
if (base.volumeData) sliced.volumeData = base.volumeData.slice(startIdx, endIdx);
|
|
247
|
+
sliced.__sliceStart = startIdx;
|
|
248
|
+
}
|
|
240
249
|
} else if (base.scatterData && base.scatterData.length > 0) {
|
|
241
250
|
const xs = base.scatterData.map((d) => d.x);
|
|
242
|
-
const xMin =
|
|
243
|
-
const span =
|
|
251
|
+
const xMin = minOf(xs);
|
|
252
|
+
const span = maxOf(xs) - xMin || 1;
|
|
244
253
|
const lo = xMin + range.start / 100 * span;
|
|
245
254
|
const hi = xMin + range.end / 100 * span;
|
|
246
255
|
const filtered = base.scatterData.filter((d) => d.x >= lo && d.x <= hi);
|
|
@@ -334,11 +343,11 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
334
343
|
const el = tooltipRef.value;
|
|
335
344
|
const tw = el?.offsetWidth || 120;
|
|
336
345
|
const th = el?.offsetHeight || 40;
|
|
337
|
-
const gap =
|
|
346
|
+
const gap = INTERACTION.tooltip.cursorGap;
|
|
338
347
|
const margin = 2;
|
|
339
348
|
let legendBottom = margin;
|
|
340
349
|
const legend = props.options.legend || {};
|
|
341
|
-
if (legend.show !== false && (legend.position || "bottom") === "top") legendBottom =
|
|
350
|
+
if (legend.show !== false && (legend.position || "bottom") === "top") legendBottom = paddingFor(props.options, containerRect.width).top;
|
|
342
351
|
const spaceRight = containerRect.width - relX - gap;
|
|
343
352
|
const spaceLeft = relX - gap;
|
|
344
353
|
let left;
|
|
@@ -408,7 +417,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
408
417
|
lastHeight = height;
|
|
409
418
|
canvas.width = width * dpr.value;
|
|
410
419
|
canvas.height = height * dpr.value;
|
|
411
|
-
const overlayPadding =
|
|
420
|
+
const overlayPadding = paddingFor(effectiveOptions.value, width);
|
|
412
421
|
overlayInfo.value = {
|
|
413
422
|
plotArea: {
|
|
414
423
|
x: overlayPadding.left,
|
|
@@ -420,10 +429,18 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
420
429
|
options: props.options
|
|
421
430
|
};
|
|
422
431
|
const animCfg = animOverride || props.options.animation;
|
|
423
|
-
const animEnabled = animate && animCfg?.enabled !== false;
|
|
432
|
+
const animEnabled = animate && animCfg?.enabled !== false && !prefersReducedMotion();
|
|
433
|
+
const stagger = staggerOf(effectiveOptions.value);
|
|
424
434
|
animationState = createAnimation(animCfg);
|
|
425
435
|
animationState.isAnimating = animEnabled;
|
|
426
|
-
if (animEnabled) {
|
|
436
|
+
if (animEnabled && stagger > 0 && hasInterpolatable(effectiveOptions.value)) {
|
|
437
|
+
isEnterAnimating = true;
|
|
438
|
+
runDataTween(zeroSnapshot(effectiveOptions.value), animCfg, stagger, () => {
|
|
439
|
+
isEnterAnimating = false;
|
|
440
|
+
emit("animation-end");
|
|
441
|
+
emit("ready");
|
|
442
|
+
});
|
|
443
|
+
} else if (animEnabled) {
|
|
427
444
|
isEnterAnimating = true;
|
|
428
445
|
animateFrame();
|
|
429
446
|
} else {
|
|
@@ -458,7 +475,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
458
475
|
hoveredToolbox: hoveredToolbox.value
|
|
459
476
|
};
|
|
460
477
|
}
|
|
461
|
-
let focusAnimProgress = 1;
|
|
478
|
+
let focusAnimProgress = emphasisSeriesName.value ? 1 : 0;
|
|
462
479
|
let focusAnimFrameId = null;
|
|
463
480
|
function animateFocusProgress() {
|
|
464
481
|
if (focusAnimFrameId !== null) {
|
|
@@ -490,39 +507,68 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
490
507
|
}
|
|
491
508
|
}
|
|
492
509
|
function snapshotSeriesData(opt) {
|
|
493
|
-
const
|
|
494
|
-
(opt.series || []).forEach((s) =>
|
|
495
|
-
|
|
510
|
+
const series = /* @__PURE__ */ new Map();
|
|
511
|
+
(opt.series || []).forEach((s) => series.set(s.name, (s.data || []).map((v) => isMissingValue(v) ? 0 : v)));
|
|
512
|
+
const pieData = /* @__PURE__ */ new Map();
|
|
513
|
+
(opt.pieData || []).forEach((d) => pieData.set(d && d.name, Number.isFinite(Number(d && d.value)) ? Number(d.value) : 0));
|
|
514
|
+
return {
|
|
515
|
+
series,
|
|
516
|
+
pieData
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** 有没有可插值的数值面(series / pieData 任一),都没有就不该走数据级补间 */
|
|
520
|
+
function hasInterpolatable(opt) {
|
|
521
|
+
if ((opt.series || []).some((s) => Array.isArray(s.data) && s.data.length)) return true;
|
|
522
|
+
return (opt.pieData || []).length > 0;
|
|
496
523
|
}
|
|
497
524
|
function sameStructure(a, b) {
|
|
498
525
|
const la = a.labels || [];
|
|
499
526
|
const lb = b.labels || [];
|
|
500
527
|
if (la.length !== lb.length || la.some((v, i) => v !== lb[i])) return false;
|
|
501
|
-
|
|
528
|
+
if ((a.series || []).map((s) => s.name).join("|") !== (b.series || []).map((s) => s.name).join("|")) return false;
|
|
529
|
+
return (a.pieData || []).map((d) => d && d.name).join("|") === (b.pieData || []).map((d) => d && d.name).join("|");
|
|
502
530
|
}
|
|
503
|
-
function interpolateOptions(prev, next, t) {
|
|
531
|
+
function interpolateOptions(prev, next, t, stagger = 0) {
|
|
504
532
|
const offset = next.__sliceStart || 0;
|
|
533
|
+
const cols = Math.max(1, (next.labels || []).length);
|
|
505
534
|
const series = (next.series || []).map((s) => {
|
|
506
|
-
const old = prev.get(s.name);
|
|
535
|
+
const old = prev.series.get(s.name);
|
|
507
536
|
if (!old) return s;
|
|
508
|
-
const data = s.data.map((v, i) => {
|
|
537
|
+
const data = (s.data || []).map((v, i) => {
|
|
509
538
|
if (isMissingValue(v)) return v;
|
|
510
539
|
const from = old[i + offset] ?? 0;
|
|
511
|
-
|
|
540
|
+
const ti = staggerProgress(t, i, cols, stagger);
|
|
541
|
+
return from + (v - from) * ti;
|
|
512
542
|
});
|
|
513
543
|
return {
|
|
514
544
|
...s,
|
|
515
545
|
data
|
|
516
546
|
};
|
|
517
547
|
});
|
|
518
|
-
|
|
548
|
+
const out = {
|
|
519
549
|
...next,
|
|
520
550
|
series
|
|
521
551
|
};
|
|
552
|
+
if (Array.isArray(next.pieData)) {
|
|
553
|
+
const sliceCount = Math.max(1, next.pieData.length);
|
|
554
|
+
out.pieData = next.pieData.map((d, i) => {
|
|
555
|
+
const to = Number(d && d.value);
|
|
556
|
+
if (!Number.isFinite(to)) return d;
|
|
557
|
+
const from = prev.pieData.get(d && d.name) ?? 0;
|
|
558
|
+
const ti = staggerProgress(t, i, sliceCount, stagger);
|
|
559
|
+
return {
|
|
560
|
+
...d,
|
|
561
|
+
value: from + (to - from) * ti
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
522
566
|
}
|
|
523
567
|
function dataSignature(opt) {
|
|
524
|
-
const
|
|
525
|
-
|
|
568
|
+
const seriesTotal = (opt.series || []).reduce((sum, s) => sum + (s.data || []).reduce((acc, v) => acc + (isMissingValue(v) ? 0 : v), 0), 0);
|
|
569
|
+
const pieTotal = (opt.pieData || []).reduce((sum, d) => sum + (Number.isFinite(Number(d && d.value)) ? Math.abs(Number(d.value)) : 0), 0);
|
|
570
|
+
const pieCount = (opt.pieData || []).length;
|
|
571
|
+
return `${(opt.series || []).length}\u7CFB\u5217/${pieCount}\u997C\u9879/${(opt.labels || []).length}\u7C7B\u76EE/\u03A3${Math.round(seriesTotal + pieTotal)}`;
|
|
526
572
|
}
|
|
527
573
|
function stopTweenAnimation() {
|
|
528
574
|
if (tweenFrameId !== null) {
|
|
@@ -531,28 +577,32 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
531
577
|
}
|
|
532
578
|
tweenState = null;
|
|
533
579
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
580
|
+
/** 错峰占比(0 = 关闭,沿用统一 progress 的进场动画) */
|
|
581
|
+
function staggerOf(opt) {
|
|
582
|
+
const v = opt && opt.animation ? opt.animation.stagger : void 0;
|
|
583
|
+
return Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 0;
|
|
584
|
+
}
|
|
585
|
+
/** 全零数据快照 — 分段进场的起点(逐条长到目标值) */
|
|
586
|
+
function zeroSnapshot(opt) {
|
|
587
|
+
const series = /* @__PURE__ */ new Map();
|
|
588
|
+
(opt.series || []).forEach((s) => series.set(s.name, (s.data || []).map(() => 0)));
|
|
589
|
+
const pieData = /* @__PURE__ */ new Map();
|
|
590
|
+
(opt.pieData || []).forEach((d) => pieData.set(d && d.name, 0));
|
|
591
|
+
return {
|
|
592
|
+
series,
|
|
593
|
+
pieData
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/** 数据补间主循环 — 更新补间与分段进场共用,按类目错峰插值后以 progress = 1 重绘 */
|
|
597
|
+
function runDataTween(fromSnapshot, cfg, stagger, onDone) {
|
|
544
598
|
stopAnimation();
|
|
545
599
|
stopHoverAnimation();
|
|
546
|
-
isEnterAnimating = false;
|
|
547
600
|
stopTweenAnimation();
|
|
548
|
-
|
|
549
|
-
tweenState = createAnimation(nextOptions.animation);
|
|
550
|
-
const fromSig = dataSignature(prevOptions);
|
|
551
|
-
const toSig = dataSignature(nextOptions);
|
|
601
|
+
tweenState = createAnimation(cfg);
|
|
552
602
|
function frame() {
|
|
553
603
|
if (!tweenState) return;
|
|
554
604
|
const isAnimating = updateAnimation(tweenState);
|
|
555
|
-
const tweened = interpolateOptions(
|
|
605
|
+
const tweened = interpolateOptions(fromSnapshot, effectiveOptions.value, tweenState.progress, stagger);
|
|
556
606
|
try {
|
|
557
607
|
renderChart(canvasRef.value, buildRenderParams(1, tweened));
|
|
558
608
|
} catch (err) {
|
|
@@ -563,16 +613,40 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
563
613
|
else {
|
|
564
614
|
tweenFrameId = null;
|
|
565
615
|
tweenState = null;
|
|
566
|
-
|
|
567
|
-
from: fromSig,
|
|
568
|
-
to: toSig
|
|
569
|
-
});
|
|
570
|
-
emit("animation-end");
|
|
616
|
+
if (onDone) onDone();
|
|
571
617
|
}
|
|
572
618
|
}
|
|
573
619
|
tweenFrameId = requestAnimationFrame(frame);
|
|
574
620
|
return true;
|
|
575
621
|
}
|
|
622
|
+
function startTweenIfNeeded(prevOptions) {
|
|
623
|
+
const nextOptions = props.options;
|
|
624
|
+
if (nextOptions.animation?.enabled === false) return false;
|
|
625
|
+
if (prefersReducedMotion()) return false;
|
|
626
|
+
if (!hasInterpolatable(nextOptions) || !sameStructure(prevOptions, nextOptions)) return false;
|
|
627
|
+
const prevSnapshot = snapshotSeriesData(prevOptions);
|
|
628
|
+
const seriesChanged = (nextOptions.series || []).some((s) => {
|
|
629
|
+
const old = prevSnapshot.series.get(s.name);
|
|
630
|
+
if (!old) return false;
|
|
631
|
+
return (s.data || []).some((v, i) => (isMissingValue(v) ? 0 : v) !== (old[i] ?? 0));
|
|
632
|
+
});
|
|
633
|
+
const pieChanged = (nextOptions.pieData || []).some((d) => {
|
|
634
|
+
const old = prevSnapshot.pieData.get(d && d.name);
|
|
635
|
+
const v = Number(d && d.value);
|
|
636
|
+
return old !== void 0 && Number.isFinite(v) && v !== old;
|
|
637
|
+
});
|
|
638
|
+
if (!seriesChanged && !pieChanged) return false;
|
|
639
|
+
isEnterAnimating = false;
|
|
640
|
+
const fromSig = dataSignature(prevOptions);
|
|
641
|
+
const toSig = dataSignature(nextOptions);
|
|
642
|
+
return runDataTween(prevSnapshot, nextOptions.animation, staggerOf(nextOptions), () => {
|
|
643
|
+
emit("data-update", {
|
|
644
|
+
from: fromSig,
|
|
645
|
+
to: toSig
|
|
646
|
+
});
|
|
647
|
+
emit("animation-end");
|
|
648
|
+
});
|
|
649
|
+
}
|
|
576
650
|
function handleRenderError(err) {
|
|
577
651
|
const msg = err instanceof Error ? err.message : "Chart render error";
|
|
578
652
|
internalError.value = msg;
|
|
@@ -604,15 +678,47 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
604
678
|
cancelAnimationFrame(hoverAnimFrameId);
|
|
605
679
|
hoverAnimFrameId = null;
|
|
606
680
|
}
|
|
681
|
+
if (hoverRedrawFrameId !== null) {
|
|
682
|
+
cancelAnimationFrame(hoverRedrawFrameId);
|
|
683
|
+
hoverRedrawFrameId = null;
|
|
684
|
+
}
|
|
607
685
|
hoverAnimDone = null;
|
|
608
686
|
}
|
|
687
|
+
let hoverRedrawFrameId = null;
|
|
688
|
+
function scheduleHoverRedraw() {
|
|
689
|
+
if (hoverRedrawFrameId !== null) return;
|
|
690
|
+
hoverRedrawFrameId = requestAnimationFrame(() => {
|
|
691
|
+
hoverRedrawFrameId = null;
|
|
692
|
+
redraw();
|
|
693
|
+
});
|
|
694
|
+
}
|
|
609
695
|
function usesHoverAnimation() {
|
|
610
696
|
const t = props.options.type;
|
|
611
697
|
return t === "pie" || t === "doughnut" || t === "rose" || t === "sunburst" || t === "treemap" || t === "radar";
|
|
612
698
|
}
|
|
613
699
|
function animatesHoverEnter() {
|
|
614
700
|
const t = props.options.type;
|
|
615
|
-
return
|
|
701
|
+
return [
|
|
702
|
+
"pie",
|
|
703
|
+
"doughnut",
|
|
704
|
+
"rose",
|
|
705
|
+
"radar",
|
|
706
|
+
"venn",
|
|
707
|
+
"sankey",
|
|
708
|
+
"chord",
|
|
709
|
+
"arc",
|
|
710
|
+
"bar",
|
|
711
|
+
"stacked-bar",
|
|
712
|
+
"horizontal-bar",
|
|
713
|
+
"waterfall",
|
|
714
|
+
"mixed",
|
|
715
|
+
"boxplot",
|
|
716
|
+
"candle",
|
|
717
|
+
"heatmap",
|
|
718
|
+
"calendar-heatmap",
|
|
719
|
+
"funnel",
|
|
720
|
+
"gantt"
|
|
721
|
+
].includes(t);
|
|
616
722
|
}
|
|
617
723
|
function clearHover() {
|
|
618
724
|
if (hoverIndex === -1) return false;
|
|
@@ -695,15 +801,14 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
695
801
|
const theme = getTheme(isDark, props.options.theme, props.options.palette);
|
|
696
802
|
const width = rect.width;
|
|
697
803
|
const height = rect.height;
|
|
698
|
-
const padding =
|
|
804
|
+
const padding = paddingFor(effectiveOptions.value, width);
|
|
699
805
|
const plotArea = {
|
|
700
806
|
x: padding.left,
|
|
701
807
|
y: padding.top,
|
|
702
808
|
width: width - padding.left - padding.right,
|
|
703
809
|
height: height - padding.top - padding.bottom
|
|
704
810
|
};
|
|
705
|
-
const
|
|
706
|
-
const bounds = getLegendBounds(ctx2d, effectiveOptions.value, plotArea, width, height, theme, hiddenSeries.value);
|
|
811
|
+
const bounds = legendBoundsFor(canvas.getContext("2d"), effectiveOptions.value, plotArea, width, height, theme, hiddenSeries.value, isDark);
|
|
707
812
|
for (const bound of bounds) if (canvasX >= bound.x && canvasX <= bound.x + bound.width && canvasY >= bound.y && canvasY <= bound.y + bound.height) return bound.name;
|
|
708
813
|
return null;
|
|
709
814
|
}
|
|
@@ -748,7 +853,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
748
853
|
const options = effectiveOptions.value;
|
|
749
854
|
const width = rect.width;
|
|
750
855
|
const height = rect.height;
|
|
751
|
-
const padding =
|
|
856
|
+
const padding = paddingFor(options, width);
|
|
752
857
|
const plotArea = {
|
|
753
858
|
x: padding.left,
|
|
754
859
|
y: padding.top,
|
|
@@ -770,12 +875,12 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
770
875
|
const dx = canvasX - centerX;
|
|
771
876
|
const dy = canvasY - centerY;
|
|
772
877
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
773
|
-
const innerRadius = options.type === "doughnut" ?
|
|
878
|
+
const innerRadius = options.type === "doughnut" ? doughnutInnerRadius(baseRadius, options) : 0;
|
|
774
879
|
let angle = Math.atan2(dy, dx) + Math.PI / 2;
|
|
775
880
|
if (angle < 0) angle += Math.PI * 2;
|
|
776
881
|
const total = slices.reduce((sum, d) => sum + d.value, 0);
|
|
777
882
|
let currentAngle = 0;
|
|
778
|
-
const maxVal =
|
|
883
|
+
const maxVal = maxOf(slices.map((d) => d.value));
|
|
779
884
|
for (let i = 0; i < slices.length; i++) {
|
|
780
885
|
const sliceAngle = slices[i].value / total * Math.PI * 2;
|
|
781
886
|
if (angle >= currentAngle && angle < currentAngle + sliceAngle) {
|
|
@@ -831,14 +936,10 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
831
936
|
const i = Math.floor((canvasX - plotArea.x) / categoryWidth);
|
|
832
937
|
if (i < 0 || i >= labels2.length) return null;
|
|
833
938
|
const wf = options.waterfall || {};
|
|
834
|
-
const
|
|
835
|
-
const v =
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const vk = deltas[k] || 0;
|
|
839
|
-
cumulative = totalIdx.has(k) ? vk : cumulative + vk;
|
|
840
|
-
}
|
|
841
|
-
const isTotal = totalIdx.has(i);
|
|
939
|
+
const step = waterfallSteps(deltas, wf.totalIndices)[i];
|
|
940
|
+
const v = step.value;
|
|
941
|
+
const cumulative = step.to;
|
|
942
|
+
const isTotal = step.isTotal;
|
|
842
943
|
const color = isTotal ? wf.totalColor || theme.colors[0] : v >= 0 ? wf.increaseColor || "#dc2626" : wf.decreaseColor || "#16a34a";
|
|
843
944
|
return {
|
|
844
945
|
index: i,
|
|
@@ -860,20 +961,27 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
860
961
|
b.min,
|
|
861
962
|
b.max,
|
|
862
963
|
...showOutliers ? b.outliers || [] : []
|
|
863
|
-
]);
|
|
964
|
+
]).filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
864
965
|
const axisCfg = options.boxHorizontal ? options.xAxis || {} : options.yAxis || {};
|
|
865
|
-
const ext = resolveTickExtendedRange(axisCfg.min ??
|
|
966
|
+
const ext = resolveTickExtendedRange(axisCfg.min ?? (allValues.length ? minOf(allValues) : 0), axisCfg.max ?? (allValues.length ? maxOf(allValues) : 1), axisCfg.ticks || 5);
|
|
866
967
|
return boxplotHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value, {
|
|
867
968
|
min: ext.min,
|
|
868
969
|
max: ext.max
|
|
869
970
|
});
|
|
870
971
|
}
|
|
871
972
|
if (options.type === "calendar-heatmap") return calendarHitTest(canvasX, canvasY, plotArea, options, theme);
|
|
872
|
-
if (options.type === "sankey") return sankeyHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value);
|
|
973
|
+
if (options.type === "sankey") return sankeyHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value, sankeyLayoutFor(plotArea, options, theme, hiddenSeries.value, isDark));
|
|
873
974
|
if (options.type === "venn") return vennHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value);
|
|
874
975
|
if (options.type === "chord") return chordHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value);
|
|
875
|
-
if (options.type === "arc")
|
|
876
|
-
|
|
976
|
+
if (options.type === "arc") {
|
|
977
|
+
if (options.arcCircular === true) return chordHitTest(canvasX, canvasY, plotArea, {
|
|
978
|
+
...options,
|
|
979
|
+
chordData: options.arcData,
|
|
980
|
+
chordMode: "curve"
|
|
981
|
+
}, theme, hiddenSeries.value);
|
|
982
|
+
return arcHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value);
|
|
983
|
+
}
|
|
984
|
+
if (options.type === "gantt") return ganttHitTest(canvasX, canvasY, plotArea, options, theme, hiddenSeries.value, ganttLayoutFor(plotArea, options, theme, hiddenSeries.value, isDark));
|
|
877
985
|
if (options.type === "scatter-matrix") {
|
|
878
986
|
const fields = options.matrixFields || [];
|
|
879
987
|
const records = options.matrixData || [];
|
|
@@ -1014,12 +1122,12 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1014
1122
|
}
|
|
1015
1123
|
return null;
|
|
1016
1124
|
}
|
|
1017
|
-
const yValues = scatterData.map((d) => d.y);
|
|
1125
|
+
const yValues = scatterData.map((d) => d.y).filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
1018
1126
|
const axisConfig = options.yAxis || {};
|
|
1019
|
-
const rawMin = axisConfig.min ??
|
|
1020
|
-
const rawMax = axisConfig.max ??
|
|
1127
|
+
const rawMin = axisConfig.min ?? (yValues.length ? minOf(yValues) : 0);
|
|
1128
|
+
const rawMax = axisConfig.max ?? (yValues.length ? maxOf(yValues) : 1);
|
|
1021
1129
|
const yExt = resolveTickExtendedRange(rawMin, rawMax, axisConfig.ticks || 5);
|
|
1022
|
-
const positions =
|
|
1130
|
+
const positions = scatterPositionsFor(scatterData, {
|
|
1023
1131
|
min: yExt.min,
|
|
1024
1132
|
max: yExt.max
|
|
1025
1133
|
}, plotArea, options);
|
|
@@ -1156,7 +1264,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1156
1264
|
params: visibleSeries2.map((s, sIdx) => ({
|
|
1157
1265
|
seriesName: s.name,
|
|
1158
1266
|
name: labels2[i] || `Point ${i + 1}`,
|
|
1159
|
-
value: s.data[i],
|
|
1267
|
+
value: (s.data || [])[i],
|
|
1160
1268
|
color: s.color || theme.colors[options.series.indexOf(s) % theme.colors.length],
|
|
1161
1269
|
dataIndex: i,
|
|
1162
1270
|
seriesIndex: sIdx
|
|
@@ -1175,7 +1283,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1175
1283
|
params: visibleSeries2.map((s, sIdx) => ({
|
|
1176
1284
|
seriesName: s.name,
|
|
1177
1285
|
name: labels2[i],
|
|
1178
|
-
value: s.data[i],
|
|
1286
|
+
value: (s.data || [])[i],
|
|
1179
1287
|
color: s.color || theme.colors[options.series.indexOf(s) % theme.colors.length],
|
|
1180
1288
|
dataIndex: i,
|
|
1181
1289
|
seriesIndex: sIdx
|
|
@@ -1206,7 +1314,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1206
1314
|
const angle = i * angleStep - Math.PI / 2;
|
|
1207
1315
|
const ind = indicators[i];
|
|
1208
1316
|
const span = ind.max - (ind.min || 0);
|
|
1209
|
-
const maxR = span > 0 ?
|
|
1317
|
+
const maxR = span > 0 ? maxOf(series.map((s) => radius * (Math.max(0, (s.data[i] || 0) - (ind.min || 0)) / span))) : 0;
|
|
1210
1318
|
const px = centerX + Math.cos(angle) * maxR;
|
|
1211
1319
|
const py = centerY + Math.sin(angle) * maxR;
|
|
1212
1320
|
const dist = Math.sqrt((canvasX - px) ** 2 + (canvasY - py) ** 2);
|
|
@@ -1226,13 +1334,13 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1226
1334
|
if (visibleSeries.length === 0) return null;
|
|
1227
1335
|
const volume = (options.volumeData || [])[dataIndex];
|
|
1228
1336
|
if (props.options.tooltip?.showAllSeries !== false && visibleSeries.length > 1) {
|
|
1229
|
-
if (visibleSeries.every((s) => isMissingValue(s.data[dataIndex]))) return null;
|
|
1337
|
+
if (visibleSeries.every((s) => isMissingValue((s.data || [])[dataIndex]))) return null;
|
|
1230
1338
|
return {
|
|
1231
1339
|
index: dataIndex,
|
|
1232
1340
|
params: visibleSeries.map((s, sIdx) => ({
|
|
1233
1341
|
seriesName: s.name,
|
|
1234
1342
|
name: labels[dataIndex],
|
|
1235
|
-
value: s.data[dataIndex],
|
|
1343
|
+
value: (s.data || [])[dataIndex],
|
|
1236
1344
|
volume,
|
|
1237
1345
|
color: s.color || theme.colors[options.series.indexOf(s) % theme.colors.length],
|
|
1238
1346
|
dataIndex: dataIndex + sliceStartOffset(),
|
|
@@ -1241,14 +1349,14 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1241
1349
|
};
|
|
1242
1350
|
} else {
|
|
1243
1351
|
const series = visibleSeries[0];
|
|
1244
|
-
if (isMissingValue(series.data[dataIndex])) return null;
|
|
1352
|
+
if (isMissingValue((series.data || [])[dataIndex])) return null;
|
|
1245
1353
|
const color = series.color || theme.colors[0];
|
|
1246
1354
|
return {
|
|
1247
1355
|
index: dataIndex,
|
|
1248
1356
|
params: {
|
|
1249
1357
|
seriesName: series.name,
|
|
1250
1358
|
name: labels[dataIndex],
|
|
1251
|
-
value: series.data[dataIndex],
|
|
1359
|
+
value: (series.data || [])[dataIndex],
|
|
1252
1360
|
volume,
|
|
1253
1361
|
color,
|
|
1254
1362
|
dataIndex: dataIndex + sliceStartOffset(),
|
|
@@ -1297,7 +1405,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1297
1405
|
const canvas = canvasRef.value;
|
|
1298
1406
|
if (!canvas) return null;
|
|
1299
1407
|
const rect = canvas.getBoundingClientRect();
|
|
1300
|
-
const padding =
|
|
1408
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1301
1409
|
const plotArea = {
|
|
1302
1410
|
x: padding.left,
|
|
1303
1411
|
y: padding.top,
|
|
@@ -1361,7 +1469,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1361
1469
|
const rect = canvas.getBoundingClientRect();
|
|
1362
1470
|
const x = e.clientX - rect.left;
|
|
1363
1471
|
const y = e.clientY - rect.top;
|
|
1364
|
-
const padding =
|
|
1472
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1365
1473
|
if (x >= padding.left && x <= padding.left + (rect.width - padding.left - padding.right) && y >= padding.top && y <= padding.top + (rect.height - padding.top - padding.bottom)) {
|
|
1366
1474
|
touchPanning = true;
|
|
1367
1475
|
touchPanStartX = x;
|
|
@@ -1384,7 +1492,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1384
1492
|
const x = e.clientX - rect.left;
|
|
1385
1493
|
const y = e.clientY - rect.top;
|
|
1386
1494
|
if (props.options.brush?.enabled === true) {
|
|
1387
|
-
const padding =
|
|
1495
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1388
1496
|
const plotArea = {
|
|
1389
1497
|
x: padding.left,
|
|
1390
1498
|
y: padding.top,
|
|
@@ -1479,7 +1587,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1479
1587
|
const labels = options.labels || [];
|
|
1480
1588
|
if (canvas && labels.length > 0) {
|
|
1481
1589
|
const rect = canvas.getBoundingClientRect();
|
|
1482
|
-
const padding =
|
|
1590
|
+
const padding = paddingFor(options, rect.width);
|
|
1483
1591
|
const plotArea = {
|
|
1484
1592
|
x: padding.left,
|
|
1485
1593
|
y: padding.top,
|
|
@@ -1522,7 +1630,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1522
1630
|
const rect = canvas.getBoundingClientRect();
|
|
1523
1631
|
const x = e.clientX - rect.left;
|
|
1524
1632
|
const y = e.clientY - rect.top;
|
|
1525
|
-
const padding =
|
|
1633
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1526
1634
|
const plotArea = {
|
|
1527
1635
|
x: padding.left,
|
|
1528
1636
|
y: padding.top,
|
|
@@ -1550,7 +1658,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1550
1658
|
tooltipVisible.value = true;
|
|
1551
1659
|
redraw();
|
|
1552
1660
|
}
|
|
1553
|
-
function handlePointerMove(e) {
|
|
1661
|
+
function handlePointerMove(e, fromKeyboard = false) {
|
|
1554
1662
|
const canvas = canvasRef.value;
|
|
1555
1663
|
if (canvas) {
|
|
1556
1664
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -1614,11 +1722,11 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1614
1722
|
} else if (usesHoverAnimation()) {
|
|
1615
1723
|
stopHoverAnimation();
|
|
1616
1724
|
hoverAnimProgress = 1;
|
|
1617
|
-
|
|
1618
|
-
} else
|
|
1619
|
-
if (prevIndex === -1) {
|
|
1725
|
+
scheduleHoverRedraw();
|
|
1726
|
+
} else scheduleHoverRedraw();
|
|
1727
|
+
if (prevIndex === -1 || fromKeyboard) {
|
|
1620
1728
|
const emitParams = Array.isArray(result.params) ? result.params[0] : result.params;
|
|
1621
|
-
emit("hover", {
|
|
1729
|
+
if (prevIndex === -1) emit("hover", {
|
|
1622
1730
|
seriesName: emitParams.seriesName,
|
|
1623
1731
|
dataIndex: emitParams.dataIndex,
|
|
1624
1732
|
value: emitParams.value,
|
|
@@ -1675,6 +1783,9 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1675
1783
|
}
|
|
1676
1784
|
return Number.isInteger(value) ? value.toString() : parseFloat(value.toFixed(4)).toString();
|
|
1677
1785
|
}
|
|
1786
|
+
function escapeHtml(value) {
|
|
1787
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1788
|
+
}
|
|
1678
1789
|
function applyTooltipContent(params) {
|
|
1679
1790
|
const formatter = props.options.tooltip?.formatter;
|
|
1680
1791
|
if (formatter) {
|
|
@@ -1685,23 +1796,23 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1685
1796
|
const title = params[0]?.name || "";
|
|
1686
1797
|
const rows = pickTooltipRows(params).map((p) => `
|
|
1687
1798
|
<div class="ev-chart__tooltip-item">
|
|
1688
|
-
<span class="ev-chart__tooltip-dot" style="background: ${p.color}"></span>
|
|
1689
|
-
<span class="ev-chart__tooltip-name">${p.seriesName}</span>
|
|
1690
|
-
<span class="ev-chart__tooltip-value">${formatTooltipValue(p.value)}</span>
|
|
1799
|
+
<span class="ev-chart__tooltip-dot" style="background: ${escapeHtml(p.color)}"></span>
|
|
1800
|
+
<span class="ev-chart__tooltip-name">${escapeHtml(p.seriesName)}</span>
|
|
1801
|
+
<span class="ev-chart__tooltip-value">${escapeHtml(formatTooltipValue(p.value))}</span>
|
|
1691
1802
|
</div>
|
|
1692
1803
|
`).join("");
|
|
1693
1804
|
tooltipContent.value = `
|
|
1694
|
-
<div class="ev-chart__tooltip-title">${title}</div>
|
|
1805
|
+
<div class="ev-chart__tooltip-title">${escapeHtml(title)}</div>
|
|
1695
1806
|
${rows}
|
|
1696
1807
|
`;
|
|
1697
1808
|
} else {
|
|
1698
1809
|
const p = params;
|
|
1699
1810
|
tooltipContent.value = `
|
|
1700
|
-
<div class="ev-chart__tooltip-title">${p.name}</div>
|
|
1811
|
+
<div class="ev-chart__tooltip-title">${escapeHtml(p.name)}</div>
|
|
1701
1812
|
<div class="ev-chart__tooltip-item">
|
|
1702
|
-
<span class="ev-chart__tooltip-dot" style="background: ${p.color}"></span>
|
|
1703
|
-
<span class="ev-chart__tooltip-name">${p.seriesName}</span>
|
|
1704
|
-
<span class="ev-chart__tooltip-value">${formatTooltipValue(p.value)}</span>
|
|
1813
|
+
<span class="ev-chart__tooltip-dot" style="background: ${escapeHtml(p.color)}"></span>
|
|
1814
|
+
<span class="ev-chart__tooltip-name">${escapeHtml(p.seriesName)}</span>
|
|
1815
|
+
<span class="ev-chart__tooltip-value">${escapeHtml(formatTooltipValue(p.value))}</span>
|
|
1705
1816
|
</div>
|
|
1706
1817
|
`;
|
|
1707
1818
|
}
|
|
@@ -1718,7 +1829,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1718
1829
|
else if (checkToolboxHit(e.clientX, e.clientY)) zone = "toolbox";
|
|
1719
1830
|
else if (checkLegendHit(e.clientX, e.clientY)) zone = "legend";
|
|
1720
1831
|
else {
|
|
1721
|
-
const padding =
|
|
1832
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1722
1833
|
if (x >= padding.left && x <= rect.width - padding.right && y >= padding.top && y <= rect.height - padding.bottom) zone = "plot";
|
|
1723
1834
|
else {
|
|
1724
1835
|
const slider = getSliderGeo();
|
|
@@ -1759,6 +1870,67 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1759
1870
|
ariaLiveText.value = "";
|
|
1760
1871
|
}
|
|
1761
1872
|
}
|
|
1873
|
+
const KEYNAV_TYPES = /* @__PURE__ */ new Set([
|
|
1874
|
+
"line",
|
|
1875
|
+
"area",
|
|
1876
|
+
"bar",
|
|
1877
|
+
"stacked-bar",
|
|
1878
|
+
"waterfall",
|
|
1879
|
+
"mixed",
|
|
1880
|
+
"horizontal-bar"
|
|
1881
|
+
]);
|
|
1882
|
+
let keyboardClientX = -1;
|
|
1883
|
+
let keyboardClientY = -1;
|
|
1884
|
+
function handleChartKeydown(e) {
|
|
1885
|
+
const options = props.options;
|
|
1886
|
+
if (!KEYNAV_TYPES.has(options.type)) return;
|
|
1887
|
+
const count = (effectiveOptions.value.labels || []).length;
|
|
1888
|
+
if (count === 0) return;
|
|
1889
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1890
|
+
e.preventDefault();
|
|
1891
|
+
if (hoverIndex < 0 || keyboardClientX < 0) return;
|
|
1892
|
+
const result = getHoveredData(keyboardClientX, keyboardClientY);
|
|
1893
|
+
if (result) {
|
|
1894
|
+
const params = Array.isArray(result.params) ? result.params[0] : result.params;
|
|
1895
|
+
emit("click", params);
|
|
1896
|
+
}
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
const horizontal = options.type === "horizontal-bar";
|
|
1900
|
+
let i = hoverIndex;
|
|
1901
|
+
if (e.key === "Home") i = 0;
|
|
1902
|
+
else if (e.key === "End") i = count - 1;
|
|
1903
|
+
else if (horizontal && e.key === "ArrowUp") i = hoverIndex < 0 ? 0 : Math.min(count - 1, hoverIndex + 1);
|
|
1904
|
+
else if (horizontal && e.key === "ArrowDown") i = hoverIndex < 0 ? 0 : Math.max(0, hoverIndex - 1);
|
|
1905
|
+
else if (!horizontal && e.key === "ArrowRight") i = hoverIndex < 0 ? 0 : Math.min(count - 1, hoverIndex + 1);
|
|
1906
|
+
else if (!horizontal && e.key === "ArrowLeft") i = hoverIndex < 0 ? 0 : Math.max(0, hoverIndex - 1);
|
|
1907
|
+
else return;
|
|
1908
|
+
e.preventDefault();
|
|
1909
|
+
if (i === hoverIndex) return;
|
|
1910
|
+
const canvas = canvasRef.value;
|
|
1911
|
+
if (!canvas) return;
|
|
1912
|
+
const rect = canvas.getBoundingClientRect();
|
|
1913
|
+
const padding = paddingFor(effectiveOptions.value, rect.width);
|
|
1914
|
+
const plotWidth = rect.width - padding.left - padding.right;
|
|
1915
|
+
const plotHeight = rect.height - padding.top - padding.bottom;
|
|
1916
|
+
const clientX = horizontal ? rect.left + padding.left + plotWidth / 2 : rect.left + padding.left + (i + .5) * (plotWidth / count);
|
|
1917
|
+
const clientY = horizontal ? rect.top + padding.top + plotHeight - (i + .5) * (plotHeight / count) : rect.top + padding.top + plotHeight / 2;
|
|
1918
|
+
keyboardClientX = clientX;
|
|
1919
|
+
keyboardClientY = clientY;
|
|
1920
|
+
mouseX = clientX - rect.left;
|
|
1921
|
+
mouseY = clientY - rect.top;
|
|
1922
|
+
handlePointerMove({
|
|
1923
|
+
clientX,
|
|
1924
|
+
clientY,
|
|
1925
|
+
pointerType: "mouse"
|
|
1926
|
+
}, true);
|
|
1927
|
+
}
|
|
1928
|
+
function handleFocusOut() {
|
|
1929
|
+
if (clearTransientState()) {
|
|
1930
|
+
emit("unhover");
|
|
1931
|
+
ariaLiveText.value = "";
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1762
1934
|
let resizeObserver = null;
|
|
1763
1935
|
function setupResizeObserver() {
|
|
1764
1936
|
if (!props.responsive) return;
|
|
@@ -1806,7 +1978,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1806
1978
|
labels: [...opt.labels || []],
|
|
1807
1979
|
series: (opt.series || []).map((s) => ({
|
|
1808
1980
|
...s,
|
|
1809
|
-
data: [...s.data]
|
|
1981
|
+
data: [...s.data || []]
|
|
1810
1982
|
}))
|
|
1811
1983
|
};
|
|
1812
1984
|
}
|
|
@@ -1815,10 +1987,18 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1815
1987
|
if (Array.isArray(value)) return value.map(cloneSpec);
|
|
1816
1988
|
const out = {};
|
|
1817
1989
|
Object.keys(value).forEach((k) => {
|
|
1990
|
+
if (k === "__proto__") return;
|
|
1818
1991
|
out[k] = cloneSpec(value[k]);
|
|
1819
1992
|
});
|
|
1820
1993
|
return out;
|
|
1821
1994
|
}
|
|
1995
|
+
function assignSpec(target, source) {
|
|
1996
|
+
Object.keys(source).forEach((k) => {
|
|
1997
|
+
if (k === "__proto__") return;
|
|
1998
|
+
target[k] = source[k];
|
|
1999
|
+
});
|
|
2000
|
+
return target;
|
|
2001
|
+
}
|
|
1822
2002
|
const IS_DEV = typeof import.meta !== "undefined" && false;
|
|
1823
2003
|
const devWarned = /* @__PURE__ */ new Set();
|
|
1824
2004
|
function runDevValidation() {
|
|
@@ -1833,11 +2013,13 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1833
2013
|
}
|
|
1834
2014
|
let prevOptionsSnapshot = null;
|
|
1835
2015
|
watch(() => props.options, (next) => {
|
|
1836
|
-
|
|
1837
|
-
cachedPlotArea = null;
|
|
2016
|
+
invalidateHitCaches();
|
|
1838
2017
|
internalError.value = null;
|
|
1839
2018
|
runDevValidation();
|
|
1840
|
-
focusSeries.value
|
|
2019
|
+
if (focusSeries.value !== emphasisSeriesName.value) {
|
|
2020
|
+
focusSeries.value = emphasisSeriesName.value;
|
|
2021
|
+
animateFocusProgress();
|
|
2022
|
+
}
|
|
1841
2023
|
syncScenes();
|
|
1842
2024
|
const zoom = getDataZoomConfig(props.options);
|
|
1843
2025
|
const zoomKey = zoom ? JSON.stringify({
|
|
@@ -1891,6 +2073,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1891
2073
|
};
|
|
1892
2074
|
let unregisterConnector = null;
|
|
1893
2075
|
onMounted(() => {
|
|
2076
|
+
focusSeries.value = emphasisSeriesName.value;
|
|
1894
2077
|
render(true);
|
|
1895
2078
|
syncScenes();
|
|
1896
2079
|
setupResizeObserver();
|
|
@@ -1924,11 +2107,102 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1924
2107
|
});
|
|
1925
2108
|
let cachedDataExtent = null;
|
|
1926
2109
|
let cachedPlotArea = null;
|
|
2110
|
+
let cachedPadding = null;
|
|
2111
|
+
function paddingFor(options, width) {
|
|
2112
|
+
if (cachedPadding && cachedPadding.options === options && cachedPadding.width === width) return cachedPadding.value;
|
|
2113
|
+
const value = getPadding(options, width);
|
|
2114
|
+
cachedPadding = {
|
|
2115
|
+
options,
|
|
2116
|
+
width,
|
|
2117
|
+
value
|
|
2118
|
+
};
|
|
2119
|
+
return value;
|
|
2120
|
+
}
|
|
2121
|
+
let scatterHitCache = null;
|
|
2122
|
+
function scatterPositionsFor(scatterData, yRange, plotArea, options) {
|
|
2123
|
+
const c = scatterHitCache;
|
|
2124
|
+
if (c && c.scatterData === scatterData && c.options === options && c.yMin === yRange.min && c.yMax === yRange.max && c.px === plotArea.x && c.py === plotArea.y && c.pw === plotArea.width && c.ph === plotArea.height) return c.positions;
|
|
2125
|
+
const positions = scatterPointPositions(scatterData, yRange, plotArea, options);
|
|
2126
|
+
scatterHitCache = {
|
|
2127
|
+
scatterData,
|
|
2128
|
+
options,
|
|
2129
|
+
yMin: yRange.min,
|
|
2130
|
+
yMax: yRange.max,
|
|
2131
|
+
px: plotArea.x,
|
|
2132
|
+
py: plotArea.y,
|
|
2133
|
+
pw: plotArea.width,
|
|
2134
|
+
ph: plotArea.height,
|
|
2135
|
+
positions
|
|
2136
|
+
};
|
|
2137
|
+
return positions;
|
|
2138
|
+
}
|
|
2139
|
+
let legendBoundsCache = null;
|
|
2140
|
+
function legendBoundsFor(ctx2d, options, plotArea, width, height, theme, hiddenSeries, isDark) {
|
|
2141
|
+
const c = legendBoundsCache;
|
|
2142
|
+
if (c && c.options === options && c.width === width && c.height === height && c.dark === isDark && c.hidden === hiddenSeries && c.px === plotArea.x && c.py === plotArea.y && c.pw === plotArea.width && c.ph === plotArea.height) return c.bounds;
|
|
2143
|
+
const bounds = getLegendBounds(ctx2d, options, plotArea, width, height, theme, hiddenSeries);
|
|
2144
|
+
legendBoundsCache = {
|
|
2145
|
+
options,
|
|
2146
|
+
width,
|
|
2147
|
+
height,
|
|
2148
|
+
dark: isDark,
|
|
2149
|
+
hidden: hiddenSeries,
|
|
2150
|
+
px: plotArea.x,
|
|
2151
|
+
py: plotArea.y,
|
|
2152
|
+
pw: plotArea.width,
|
|
2153
|
+
ph: plotArea.height,
|
|
2154
|
+
bounds
|
|
2155
|
+
};
|
|
2156
|
+
return bounds;
|
|
2157
|
+
}
|
|
2158
|
+
let ganttLayoutCache = null;
|
|
2159
|
+
function ganttLayoutFor(plotArea, options, theme, hiddenSeries, isDark) {
|
|
2160
|
+
const c = ganttLayoutCache;
|
|
2161
|
+
if (c && c.options === options && c.dark === isDark && c.hidden === hiddenSeries && c.px === plotArea.x && c.py === plotArea.y && c.pw === plotArea.width && c.ph === plotArea.height) return c.layout;
|
|
2162
|
+
const layout = computeGanttLayout(plotArea, options, theme, hiddenSeries);
|
|
2163
|
+
ganttLayoutCache = {
|
|
2164
|
+
options,
|
|
2165
|
+
dark: isDark,
|
|
2166
|
+
hidden: hiddenSeries,
|
|
2167
|
+
px: plotArea.x,
|
|
2168
|
+
py: plotArea.y,
|
|
2169
|
+
pw: plotArea.width,
|
|
2170
|
+
ph: plotArea.height,
|
|
2171
|
+
layout
|
|
2172
|
+
};
|
|
2173
|
+
return layout;
|
|
2174
|
+
}
|
|
2175
|
+
let sankeyLayoutCache = null;
|
|
2176
|
+
function sankeyLayoutFor(plotArea, options, theme, hiddenSeries, isDark) {
|
|
2177
|
+
const c = sankeyLayoutCache;
|
|
2178
|
+
if (c && c.options === options && c.dark === isDark && c.hidden === hiddenSeries && c.px === plotArea.x && c.py === plotArea.y && c.pw === plotArea.width && c.ph === plotArea.height) return c.layout;
|
|
2179
|
+
const layout = computeSankeyLayout(plotArea, options, theme, hiddenSeries);
|
|
2180
|
+
sankeyLayoutCache = {
|
|
2181
|
+
options,
|
|
2182
|
+
dark: isDark,
|
|
2183
|
+
hidden: hiddenSeries,
|
|
2184
|
+
px: plotArea.x,
|
|
2185
|
+
py: plotArea.y,
|
|
2186
|
+
pw: plotArea.width,
|
|
2187
|
+
ph: plotArea.height,
|
|
2188
|
+
layout
|
|
2189
|
+
};
|
|
2190
|
+
return layout;
|
|
2191
|
+
}
|
|
2192
|
+
function invalidateHitCaches() {
|
|
2193
|
+
cachedDataExtent = null;
|
|
2194
|
+
cachedPlotArea = null;
|
|
2195
|
+
cachedPadding = null;
|
|
2196
|
+
scatterHitCache = null;
|
|
2197
|
+
legendBoundsCache = null;
|
|
2198
|
+
ganttLayoutCache = null;
|
|
2199
|
+
sankeyLayoutCache = null;
|
|
2200
|
+
}
|
|
1927
2201
|
__expose({
|
|
1928
2202
|
refresh: () => render(true),
|
|
1929
2203
|
update(newOptions) {
|
|
1930
|
-
|
|
1931
|
-
|
|
2204
|
+
assignSpec(props.options, newOptions);
|
|
2205
|
+
invalidateHitCaches();
|
|
1932
2206
|
specVersion.value++;
|
|
1933
2207
|
debouncedRender(true);
|
|
1934
2208
|
},
|
|
@@ -1938,9 +2212,18 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1938
2212
|
destroy() {
|
|
1939
2213
|
cleanupResizeObserver();
|
|
1940
2214
|
cleanupDarkModeObserver();
|
|
2215
|
+
window.removeEventListener("pointerup", onWindowPointerUp);
|
|
2216
|
+
window.removeEventListener("keydown", handleEscapeKey);
|
|
2217
|
+
containerRef.value?.removeEventListener("wheel", handleWheel);
|
|
2218
|
+
if (unregisterConnector) {
|
|
2219
|
+
unregisterConnector();
|
|
2220
|
+
unregisterConnector = null;
|
|
2221
|
+
}
|
|
1941
2222
|
stopAnimation();
|
|
1942
2223
|
stopHoverAnimation();
|
|
2224
|
+
stopTweenAnimation();
|
|
1943
2225
|
stopSceneTimer();
|
|
2226
|
+
stopFocusAnimation();
|
|
1944
2227
|
if (tooltipRafId !== null) {
|
|
1945
2228
|
cancelAnimationFrame(tooltipRafId);
|
|
1946
2229
|
tooltipRafId = null;
|
|
@@ -1989,7 +2272,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
1989
2272
|
if (v < min) min = v;
|
|
1990
2273
|
if (v > max) max = v;
|
|
1991
2274
|
};
|
|
1992
|
-
if (opt.series) opt.series.forEach((s) => s.data.forEach((v) => !isMissingValue(v) && consider(v)));
|
|
2275
|
+
if (opt.series) opt.series.forEach((s) => (s.data || []).forEach((v) => !isMissingValue(v) && consider(v)));
|
|
1993
2276
|
if (opt.scatterData) opt.scatterData.forEach((p) => consider(p.y));
|
|
1994
2277
|
if (opt.candleData) opt.candleData.forEach((c) => {
|
|
1995
2278
|
consider(c.high);
|
|
@@ -2016,7 +2299,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2016
2299
|
};
|
|
2017
2300
|
const width = canvas.width / dpr.value;
|
|
2018
2301
|
const height = canvas.height / dpr.value;
|
|
2019
|
-
const padding =
|
|
2302
|
+
const padding = paddingFor(effectiveOptions.value, width);
|
|
2020
2303
|
cachedPlotArea = {
|
|
2021
2304
|
x: padding.left,
|
|
2022
2305
|
y: padding.top,
|
|
@@ -2062,6 +2345,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2062
2345
|
mouseX: -1,
|
|
2063
2346
|
mouseY: -1,
|
|
2064
2347
|
showCrosshair: false,
|
|
2348
|
+
zoomRange: void 0,
|
|
2065
2349
|
brushRect: null,
|
|
2066
2350
|
hoveredToolbox: null,
|
|
2067
2351
|
focusSeries: null
|
|
@@ -2082,7 +2366,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2082
2366
|
if (spec === null || typeof spec !== "object") return;
|
|
2083
2367
|
const next = cloneSpec(spec);
|
|
2084
2368
|
Object.keys(props.options).forEach((k) => delete props.options[k]);
|
|
2085
|
-
|
|
2369
|
+
assignSpec(props.options, next);
|
|
2086
2370
|
hiddenSeries.value = /* @__PURE__ */ new Set();
|
|
2087
2371
|
focusSeries.value = null;
|
|
2088
2372
|
brushRect.value = null;
|
|
@@ -2104,8 +2388,7 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2104
2388
|
p: zoom.position,
|
|
2105
2389
|
h: zoom.height
|
|
2106
2390
|
}) : "";
|
|
2107
|
-
|
|
2108
|
-
cachedPlotArea = null;
|
|
2391
|
+
invalidateHitCaches();
|
|
2109
2392
|
specVersion.value++;
|
|
2110
2393
|
debouncedRender(true);
|
|
2111
2394
|
},
|
|
@@ -2150,7 +2433,10 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2150
2433
|
class: "ev-chart",
|
|
2151
2434
|
style: normalizeStyle([containerStyle.value, { cursor: cursorStyle.value }]),
|
|
2152
2435
|
role: "img",
|
|
2153
|
-
|
|
2436
|
+
tabindex: isEmpty.value || internalError.value ? -1 : 0,
|
|
2437
|
+
"aria-label": props.options.ariaLabel || ariaLabelText.value,
|
|
2438
|
+
onKeydown: handleChartKeydown,
|
|
2439
|
+
onFocusout: handleFocusOut
|
|
2154
2440
|
}, [
|
|
2155
2441
|
!isEmpty.value && !internalError.value ? (openBlock(), createElementBlock("canvas", {
|
|
2156
2442
|
key: 0,
|
|
@@ -2229,10 +2515,10 @@ var chart_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
|
|
|
2229
2515
|
_: 1
|
|
2230
2516
|
}),
|
|
2231
2517
|
createElementVNode("div", _hoisted_11, toDisplayString(ariaLiveText.value), 1)
|
|
2232
|
-
],
|
|
2518
|
+
], 44, _hoisted_1);
|
|
2233
2519
|
};
|
|
2234
2520
|
}
|
|
2235
|
-
}), [["__scopeId", "data-v-
|
|
2521
|
+
}), [["__scopeId", "data-v-af1c6d5e"]]);
|
|
2236
2522
|
//#endregion
|
|
2237
2523
|
//#region src/useChart.js
|
|
2238
2524
|
var USE_CHART_KEY = /* @__PURE__ */ Symbol("ev-chart-context");
|
|
@@ -2296,95 +2582,6 @@ function injectChartContext() {
|
|
|
2296
2582
|
return inject(USE_CHART_KEY, null);
|
|
2297
2583
|
}
|
|
2298
2584
|
//#endregion
|
|
2299
|
-
//#region src/palette.js
|
|
2300
|
-
/**
|
|
2301
|
-
* 配色方案应用工具 — 中性契约的官方实现
|
|
2302
|
-
*
|
|
2303
|
-
* 把一套数据色板写入 --ev-color-series-1..8 槽位令牌并派发 'ev-theme-change',
|
|
2304
|
-
* 页面上的全部图表即时重绘。任何宿主(组件库 / 站点 / 独立页面)都可直接调用,
|
|
2305
|
-
* 不必各自实现令牌写入与事件广播。
|
|
2306
|
-
*
|
|
2307
|
-
* palette 两种形态:
|
|
2308
|
-
* - string[] 单套色值,明暗模式共用;
|
|
2309
|
-
* - { light, dark } 浅/暗两组,随 html.dark 自动换挡(经注入样式表实现)。
|
|
2310
|
-
*
|
|
2311
|
-
* clearSeriesPalette() 移除覆写(内联属性 + 注入样式表),回到内置成套色板。
|
|
2312
|
-
*/
|
|
2313
|
-
var SLOT_COUNT = 8;
|
|
2314
|
-
var PALETTE_EVENT = "ev-theme-change";
|
|
2315
|
-
var STYLE_ID = "data-ev-series-palette";
|
|
2316
|
-
var paletteStyleEl = null;
|
|
2317
|
-
function dispatchPaletteChange(dark) {
|
|
2318
|
-
if (typeof document === "undefined") return;
|
|
2319
|
-
const isDark = typeof dark === "boolean" ? dark : document.documentElement.classList.contains("dark");
|
|
2320
|
-
document.documentElement.dispatchEvent(new CustomEvent(PALETTE_EVENT, {
|
|
2321
|
-
bubbles: true,
|
|
2322
|
-
detail: { dark: isDark }
|
|
2323
|
-
}));
|
|
2324
|
-
}
|
|
2325
|
-
function removeInlineSlots() {
|
|
2326
|
-
if (typeof document === "undefined") return;
|
|
2327
|
-
for (let i = 1; i <= SLOT_COUNT; i++) document.documentElement.style.removeProperty(`--ev-color-series-${i}`);
|
|
2328
|
-
}
|
|
2329
|
-
function ensurePaletteStyleEl() {
|
|
2330
|
-
if (typeof document === "undefined") return null;
|
|
2331
|
-
if (!paletteStyleEl || !paletteStyleEl.isConnected) {
|
|
2332
|
-
paletteStyleEl = document.createElement("style");
|
|
2333
|
-
paletteStyleEl.setAttribute(STYLE_ID, "");
|
|
2334
|
-
document.head.appendChild(paletteStyleEl);
|
|
2335
|
-
}
|
|
2336
|
-
return paletteStyleEl;
|
|
2337
|
-
}
|
|
2338
|
-
function normalizeSlots(colors) {
|
|
2339
|
-
const out = {};
|
|
2340
|
-
if (!Array.isArray(colors)) return out;
|
|
2341
|
-
colors.slice(0, SLOT_COUNT).forEach((color, i) => {
|
|
2342
|
-
if (color) out[`--ev-color-series-${i + 1}`] = color;
|
|
2343
|
-
});
|
|
2344
|
-
return out;
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* 应用一套数据色板
|
|
2348
|
-
* @param {string[] | { light: string[], dark: string[] }} palette
|
|
2349
|
-
* 数组:写入内联槽位(明暗共用);{ light, dark }:注入样式表,随 html.dark 自动换挡
|
|
2350
|
-
* @param {{ dark?: boolean, target?: HTMLElement }} [options]
|
|
2351
|
-
* dark 手动指定当前暗色态(默认探测 html.dark);target 改写注入目标
|
|
2352
|
-
* @returns {boolean} 是否写入成功
|
|
2353
|
-
*/
|
|
2354
|
-
function applySeriesPalette(palette, options = {}) {
|
|
2355
|
-
if (typeof document === "undefined") return false;
|
|
2356
|
-
const el = options.target ?? document.documentElement;
|
|
2357
|
-
if (!el || !palette) return false;
|
|
2358
|
-
if (Array.isArray(palette)) {
|
|
2359
|
-
paletteStyleEl?.remove();
|
|
2360
|
-
paletteStyleEl = null;
|
|
2361
|
-
for (const [name, value] of Object.entries(normalizeSlots(palette))) el.style.setProperty(name, value);
|
|
2362
|
-
dispatchPaletteChange(options.dark);
|
|
2363
|
-
return true;
|
|
2364
|
-
}
|
|
2365
|
-
const { light, dark } = palette;
|
|
2366
|
-
if (!Array.isArray(light) && !Array.isArray(dark)) return false;
|
|
2367
|
-
removeInlineSlots();
|
|
2368
|
-
const styleEl = ensurePaletteStyleEl();
|
|
2369
|
-
if (styleEl) {
|
|
2370
|
-
const vars = (colors) => colors.map((c, i) => `--ev-color-series-${i + 1}: ${c}`).join(";");
|
|
2371
|
-
styleEl.textContent = `${light ? `:root { ${vars(light)}; }` : ""}${dark ? ` html.dark { ${vars(dark)}; }` : ""}`;
|
|
2372
|
-
}
|
|
2373
|
-
dispatchPaletteChange(options.dark);
|
|
2374
|
-
return true;
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* 清除配色方案覆写(内联槽位 + 注入样式表),回到内置成套色板
|
|
2378
|
-
*/
|
|
2379
|
-
function clearSeriesPalette(options = {}) {
|
|
2380
|
-
if (typeof document === "undefined") return false;
|
|
2381
|
-
paletteStyleEl?.remove();
|
|
2382
|
-
paletteStyleEl = null;
|
|
2383
|
-
removeInlineSlots();
|
|
2384
|
-
dispatchPaletteChange(options.dark);
|
|
2385
|
-
return true;
|
|
2386
|
-
}
|
|
2387
|
-
//#endregion
|
|
2388
2585
|
//#region src/index.js
|
|
2389
2586
|
var components = { EvChart: chart_default };
|
|
2390
2587
|
function install(app, _options = {}) {
|