@zerodep-dev/vue-tsx-pro-ui 0.3.2 → 0.4.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/dist/charts/AreaChart.d.ts +1 -1
- package/dist/charts/AreaChart.js +6 -48
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +6 -49
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.d.ts +7 -2
- package/dist/charts/ChordChart.js +151 -60
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.d.ts +2 -1
- package/dist/charts/ComboChart.js +17 -93
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.d.ts +6 -1
- package/dist/charts/DonutChart.js +134 -51
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.d.ts +22 -10
- package/dist/charts/GraphChart.js +415 -175
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.d.ts +1 -29
- package/dist/charts/LineChart.js +6 -54
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.d.ts +21 -48
- package/dist/charts/MapChart.js +426 -353
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.d.ts +7 -2
- package/dist/charts/SankeyChart.js +164 -76
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.d.ts +7 -1
- package/dist/charts/ScatterChart.js +45 -30
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.d.ts +8 -1
- package/dist/charts/TimelineChart.js +141 -89
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.d.ts +6 -0
- package/dist/charts/TreemapChart.js +174 -58
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.d.ts +6 -7
- package/dist/charts/WaterfallChart.js +46 -51
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.d.ts +44 -35
- package/dist/charts/XYChart.js +493 -281
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/axis-ticks.d.ts +1 -1
- package/dist/charts/axis-ticks.js +4 -5
- package/dist/charts/axis-ticks.js.map +1 -1
- package/dist/charts/flow.d.ts +4 -11
- package/dist/charts/flow.js +4 -4
- package/dist/charts/flow.js.map +1 -1
- package/dist/charts/frame.d.ts +6 -30
- package/dist/charts/frame.js +6 -99
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/geo/projection.d.ts +45 -0
- package/dist/charts/geo/projection.js +201 -0
- package/dist/charts/geo/projection.js.map +1 -0
- package/dist/charts/geo/topojson.d.ts +50 -0
- package/dist/charts/geo/topojson.js +119 -0
- package/dist/charts/geo/topojson.js.map +1 -0
- package/dist/charts/index.d.ts +21 -9
- package/dist/charts/index.js +19 -8
- package/dist/charts/label-layout.js.map +1 -1
- package/dist/charts/layout/chord.d.ts +44 -0
- package/dist/charts/layout/chord.js +45 -0
- package/dist/charts/layout/chord.js.map +1 -0
- package/dist/charts/layout/graph.d.ts +49 -0
- package/dist/charts/layout/graph.js +268 -0
- package/dist/charts/layout/graph.js.map +1 -0
- package/dist/charts/layout/sankey.d.ts +79 -0
- package/dist/charts/layout/sankey.js +200 -0
- package/dist/charts/layout/sankey.js.map +1 -0
- package/dist/charts/layout/treemap.d.ts +46 -0
- package/dist/charts/layout/treemap.js +152 -0
- package/dist/charts/layout/treemap.js.map +1 -0
- package/dist/charts/marks.d.ts +79 -0
- package/dist/charts/marks.js +231 -0
- package/dist/charts/marks.js.map +1 -0
- package/dist/charts/plot.d.ts +107 -0
- package/dist/charts/plot.js +231 -0
- package/dist/charts/plot.js.map +1 -0
- package/dist/charts/scale.d.ts +29 -0
- package/dist/charts/scale.js +244 -0
- package/dist/charts/scale.js.map +1 -0
- package/dist/charts/shape.d.ts +46 -0
- package/dist/charts/shape.js +131 -0
- package/dist/charts/shape.js.map +1 -0
- package/dist/charts/stack.d.ts +17 -0
- package/dist/charts/stack.js +35 -0
- package/dist/charts/stack.js.map +1 -0
- package/dist/charts/text-size.d.ts +12 -0
- package/dist/charts/text-size.js +32 -0
- package/dist/charts/text-size.js.map +1 -0
- package/dist/display/VirtualList.d.ts +1 -2
- package/dist/display/VirtualList.js +11 -21
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/input/Combobox.d.ts +1 -1
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ListPopover.d.ts +1 -1
- package/dist/input/ListPopover.js.map +1 -1
- package/dist/input/Listbox.d.ts +1 -2
- package/dist/input/Listbox.js +7 -19
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Select.d.ts +1 -1
- package/dist/input/Select.js.map +1 -1
- package/dist/maps/china-provinces.js +35680 -0
- package/dist/maps/china-provinces.js.map +1 -0
- package/dist/maps/china.d.ts +13 -0
- package/dist/maps/china.js +8 -0
- package/dist/maps/china.js.map +1 -0
- package/dist/maps/index.d.ts +9 -0
- package/dist/maps/index.js +3 -0
- package/dist/maps/world-countries.js +22273 -0
- package/dist/maps/world-countries.js.map +1 -0
- package/dist/maps/world.d.ts +13 -0
- package/dist/maps/world.js +8 -0
- package/dist/maps/world.js.map +1 -0
- package/dist/overlay/CommandPalette.d.ts +1 -1
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/detached.js +14 -1
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/QueryTable.js +0 -2
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/table/DataTable.js +1 -1
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.d.ts +0 -3
- package/dist/table/DataTableView.js +32 -26
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/table-styles.d.ts +1 -1
- package/dist/table/table-styles.js +0 -13
- package/dist/table/table-styles.js.map +1 -1
- package/package.json +7 -8
- package/dist/charts/single.d.ts +0 -41
- package/dist/charts/single.js +0 -66
- package/dist/charts/single.js.map +0 -1
package/dist/charts/XYChart.js
CHANGED
|
@@ -1,43 +1,35 @@
|
|
|
1
|
+
import { svgNumber } from "../utils/data/number.js";
|
|
1
2
|
import { toNumber } from "../utils/data/numeric.js";
|
|
2
|
-
import { colors } from "../config/theme/tokens.js";
|
|
3
3
|
import { useLang } from "../config/context.js";
|
|
4
4
|
import { Button } from "../base/Button.js";
|
|
5
5
|
import { useFormat } from "../config/lang/format.js";
|
|
6
6
|
import { Skeleton } from "../feedback/Skeleton.js";
|
|
7
|
-
import { useDetachedRender } from "../overlay/detached.js";
|
|
8
7
|
import { Slider } from "../input/Slider.js";
|
|
8
|
+
import { ChartTooltipBox, chartAxis, chartSvgStyles, pointerIn, usePlotArea } from "./plot.js";
|
|
9
|
+
import { stackValues } from "./stack.js";
|
|
10
|
+
import { linearScale, linearTicks, niceDomain } from "./scale.js";
|
|
9
11
|
import { alignedTicks } from "./axis-ticks.js";
|
|
10
|
-
import { ChartFrame,
|
|
12
|
+
import { ChartFrame, chartTooltip, useChartValueText } from "./frame.js";
|
|
11
13
|
import { resolveColor, resolveSeries, seriesNumber, seriesValue } from "./series.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
+
import { estimateTextWidth, truncateText } from "./text-size.js";
|
|
15
|
+
import { computed, createVNode, shallowRef } from "vue";
|
|
16
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
14
17
|
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
15
|
-
import { Axis, AxisType, Crosshair, Plotband, PlotbandLabelPosition, Plotline, PlotlineLabelPosition, PlotlineLineStylePresets, Position, Scatter, Tooltip, XYContainer } from "@unovis/ts";
|
|
16
18
|
//#region src/charts/XYChart.tsx
|
|
17
|
-
/**
|
|
18
|
-
* 直角坐标图表(柱状图、折线图、面积图)的公共部分:坐标轴、十字线与提示框、数值范围;外层结构(图例、单位、
|
|
19
|
-
* 空与加载状态、给读屏的数据表)交给 ChartFrame。
|
|
20
|
-
* 绘图交给 Unovis 的 XYContainer:组件实例只建一次,数据与配置变化时 update,卸载时 destroy;
|
|
21
|
-
* 绘图区换了元素(加载、空状态之后重新出现)时容器连同全部实例一起重建。
|
|
22
|
-
* 各种图只决定画什么(kind.components),坐标轴、十字线、提示框、配色都在这里统一。
|
|
23
|
-
* 目标线(targets)用 Unovis 的 Plotline。点了下钻(onItemClick):有图形可点的图(柱子)按图形的点击事件
|
|
24
|
-
* (kind.itemSelector),折线、面积图按十字线所在的那一行,散点图按悬停的点。
|
|
25
|
-
*
|
|
26
|
-
* 标注(annotations):类目上的事件画成竖线(Plotline),圈出某个系列的值画成空心圈带文字(Scatter);
|
|
27
|
-
* 区间(bands)画成浅色的带(Plotband)。百分比堆叠(kind.percent)按每个类目里各系列的占比画。
|
|
28
|
-
* 第二数值轴(kind.secondary,组合图):刻度与主轴对齐(alignedTicks),第二轴的系列换算到主轴的坐标里画,
|
|
29
|
-
* 右侧的刻度文字是不裁剪的 Plotline 标签。
|
|
30
|
-
*/
|
|
31
19
|
/** 目标线(参考线):数值轴上的一条虚线 */
|
|
32
20
|
/** 标注:类目上发生的事(竖线),或圈出某个系列在这个类目上的值 */
|
|
33
21
|
/** 区间:类目轴上的一段(试运行期、促销期),或数值轴上的一段(目标区间) */
|
|
34
22
|
/** 第二数值轴(右侧):哪些系列画在它上面,它的格式与单位 */
|
|
35
23
|
/** 加载中的骨架 */
|
|
36
24
|
/** 直角坐标图表共有的属性;未声明的属性落到外层 div(role="figure")上,aria-label 是图表的名称 */
|
|
37
|
-
/**
|
|
38
|
-
/**
|
|
39
|
-
/**
|
|
40
|
-
var
|
|
25
|
+
/** 交给各种图的绘制上下文:坐标都是 SVG 里的 px */
|
|
26
|
+
/** 一种图:画什么,以及与坐标轴、十字线有关的差别 */
|
|
27
|
+
/** 刻度文字的字号(px):主题的 xs */
|
|
28
|
+
var TICK_FONT = 12;
|
|
29
|
+
/** 刻度文字与绘图区的距离 */
|
|
30
|
+
var TICK_GAP = 8;
|
|
31
|
+
/** 散点图找最近的点时,离指针多远以内算悬停在点上(px) */
|
|
32
|
+
var POINT_REACH = 24;
|
|
41
33
|
/** 骨架柱的高度(百分比):高低错落,看起来像一组数据 */
|
|
42
34
|
var SKELETON_BARS = [
|
|
43
35
|
55,
|
|
@@ -48,9 +40,9 @@ var SKELETON_BARS = [
|
|
|
48
40
|
50,
|
|
49
41
|
65
|
|
50
42
|
];
|
|
51
|
-
/** XYChart
|
|
43
|
+
/** XYChart 的样式:缩放的滑块、加载中的骨架、标注(外层结构的样式见 chartStyles) */
|
|
52
44
|
var xyChartStyles = css.create({
|
|
53
|
-
|
|
45
|
+
svg: { touchAction: "pan-y" },
|
|
54
46
|
skeleton: {
|
|
55
47
|
display: "block",
|
|
56
48
|
width: "100%",
|
|
@@ -84,7 +76,29 @@ var xyChartStyles = css.create({
|
|
|
84
76
|
borderRadius: "_sm"
|
|
85
77
|
},
|
|
86
78
|
skeletonBarSize: (percent) => ({ height: percent }),
|
|
87
|
-
skeletonBarSizeHorizontal: (percent) => ({ width: percent })
|
|
79
|
+
skeletonBarSizeHorizontal: (percent) => ({ width: percent }),
|
|
80
|
+
targetLine: {
|
|
81
|
+
strokeWidth: 1.5,
|
|
82
|
+
strokeDasharray: "6 4",
|
|
83
|
+
pointerEvents: "none"
|
|
84
|
+
},
|
|
85
|
+
eventLine: {
|
|
86
|
+
strokeWidth: 1,
|
|
87
|
+
strokeDasharray: "3 3",
|
|
88
|
+
pointerEvents: "none"
|
|
89
|
+
},
|
|
90
|
+
mark: {
|
|
91
|
+
fill: "none",
|
|
92
|
+
strokeWidth: 2,
|
|
93
|
+
pointerEvents: "none"
|
|
94
|
+
},
|
|
95
|
+
crosshairDot: {
|
|
96
|
+
stroke: "_surface",
|
|
97
|
+
strokeWidth: 2,
|
|
98
|
+
pointerEvents: "none"
|
|
99
|
+
},
|
|
100
|
+
bandFill: (color) => ({ fill: `color-mix(in srgb, ${color} 12%, transparent)` }),
|
|
101
|
+
textFill: (color) => ({ fill: color })
|
|
88
102
|
});
|
|
89
103
|
/** 加载中绘图区的骨架:柱状图是一排高低不一的柱(横向时是条),其余是一整块 */
|
|
90
104
|
function chartSkeleton({ skeleton = "block", horizontal = false }) {
|
|
@@ -94,6 +108,9 @@ function chartSkeleton({ skeleton = "block", horizontal = false }) {
|
|
|
94
108
|
"sx": [xyChartStyles.skeletonBar, horizontal ? xyChartStyles.skeletonBarSizeHorizontal(`${percent}%`) : xyChartStyles.skeletonBarSize(`${percent}%`)]
|
|
95
109
|
}, null))]);
|
|
96
110
|
}
|
|
111
|
+
/** 数值轴上约几格:每格不少于 45px(横着的轴 90px),2–6 格 */
|
|
112
|
+
var tickCount = (length, horizontal) => Math.max(2, Math.min(6, Math.round(length / (horizontal ? 90 : 45))));
|
|
113
|
+
/** 指针悬停的状态:行的位置、提示框的位置 */
|
|
97
114
|
/** 直角坐标图表的公共实现(组件库内部用,见 BarChart、LineChart、AreaChart) */
|
|
98
115
|
var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
99
116
|
const _cache = [];
|
|
@@ -118,13 +135,13 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
118
135
|
const zoom = readonlyProp(_props, "zoom", false);
|
|
119
136
|
const zoomRange = bindable(_props, "_zoomRange", () => null, _ctx.emit);
|
|
120
137
|
const kind = readonlyProp(_props, "kind");
|
|
121
|
-
const sx = readonlyProp(_props, "sx");
|
|
122
138
|
const rest = restProps(_props, _ctx, true);
|
|
123
139
|
const lang = useLang();
|
|
124
140
|
const format = useFormat();
|
|
125
|
-
const
|
|
141
|
+
const area = usePlotArea();
|
|
126
142
|
const resolved = computed(() => resolveSeries(series.value));
|
|
127
143
|
const visible = computed(() => resolved.value.filter((item) => !hiddenSeries.value.includes(item.key)));
|
|
144
|
+
let hover = shallowRef(null);
|
|
128
145
|
const categoryText = (row) => row === void 0 ? "" : indexFormat.value ? indexFormat.value(row[index.value], row) : String(row[index.value] ?? "");
|
|
129
146
|
const valueText = useChartValueText(() => ({
|
|
130
147
|
valueFormat: valueFormat.value,
|
|
@@ -173,21 +190,9 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
173
190
|
$stable: true
|
|
174
191
|
})]);
|
|
175
192
|
};
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let hovered = null;
|
|
180
|
-
const hover = (row) => {
|
|
181
|
-
hovered = row === void 0 ? null : {
|
|
182
|
-
row,
|
|
183
|
-
index: data.value.indexOf(row)
|
|
184
|
-
};
|
|
185
|
-
};
|
|
186
|
-
/** 十字线的提示框:内容用 Vue 渲染进同一个元素,交给 Unovis 定位 */
|
|
187
|
-
function renderTooltip(row) {
|
|
188
|
-
tooltipElement ??= document.createElement("div");
|
|
189
|
-
if (kind.value.pointTooltip) hover(row);
|
|
190
|
-
renderDetached(chartTooltip(categoryText(row), kind.value.tooltipItems?.(row) ?? [...kind.value.continuousX ? [{
|
|
193
|
+
/** 提示框的内容:类目(或点的名字)与各系列的值 */
|
|
194
|
+
function tooltipContent(row) {
|
|
195
|
+
return chartTooltip(categoryText(row), kind.value.tooltipItems?.(row) ?? [...kind.value.continuousX ? [{
|
|
191
196
|
label: kind.value.continuousX.label,
|
|
192
197
|
color: "transparent",
|
|
193
198
|
value: xText(row)
|
|
@@ -199,248 +204,466 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
199
204
|
color: item.color,
|
|
200
205
|
value: kind.value.percent && value !== void 0 && total > 0 ? `${seriesText(item.key, value)}(${percentText(toNumber(value) / total)})` : seriesText(item.key, value)
|
|
201
206
|
};
|
|
202
|
-
})])
|
|
203
|
-
return tooltipElement;
|
|
207
|
+
})]);
|
|
204
208
|
}
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
container?.destroy();
|
|
209
|
+
/** 缩放:只画选中的一段(散点图的横轴是连续的数值,不按类目缩放) */
|
|
210
|
+
const range = () => zoom.value && !kind.value.continuousX ? zoomRange.value : null;
|
|
211
|
+
const shownRows = computed(() => {
|
|
212
|
+
const selected = range();
|
|
213
|
+
return selected ? data.value.slice(selected[0], selected[1] + 1) : data.value;
|
|
211
214
|
});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const rows =
|
|
215
|
+
/** 画图要的一切:比例尺、刻度、边距;随数据、属性、绘图区大小变 */
|
|
216
|
+
const layout = computed(() => {
|
|
217
|
+
const rows = shownRows.value;
|
|
218
|
+
const width = area.width;
|
|
219
|
+
const plotHeight = area.height;
|
|
220
|
+
const horizontal = kind.value.horizontal ?? false;
|
|
215
221
|
const shown = visible.value;
|
|
216
|
-
const element = plot.value;
|
|
217
|
-
if (!element || loading.value || rows.length === 0 || !seen()) return;
|
|
218
|
-
if (container && containerElement !== element) {
|
|
219
|
-
container.destroy();
|
|
220
|
-
container = void 0;
|
|
221
|
-
instances.reset();
|
|
222
|
-
}
|
|
223
222
|
const primarySeries = shown.filter((item) => !onSecondary(item.key));
|
|
224
223
|
const secondarySeries = shown.filter((item) => onSecondary(item.key));
|
|
225
224
|
const totals = rows.map((row) => primarySeries.map((item) => seriesNumber(row, item.key) ?? 0));
|
|
226
225
|
const values = [
|
|
227
|
-
...kind.value.domainValues ? kind.value.domainValues(rows, primarySeries) : kind.value.stacked ? totals.flatMap((row) => [row.reduce((sum, value) => sum + Math.max(value, 0), 0), row.reduce((sum, value) => sum + Math.min(value, 0), 0)]) :
|
|
226
|
+
...kind.value.domainValues ? kind.value.domainValues(rows, primarySeries) : kind.value.stacked ? totals.flatMap((row) => [row.reduce((sum, value) => sum + Math.max(value, 0), 0), row.reduce((sum, value) => sum + Math.min(value, 0), 0)]) : rows.flatMap((row) => primarySeries.flatMap((item) => seriesNumber(row, item.key) ?? [])),
|
|
228
227
|
...(targets.value ?? []).map((target) => toNumber(target.value)),
|
|
229
228
|
...(bands.value ?? []).filter((band) => band.axis === "value").flatMap((band) => [Number(band.from), Number(band.to)])
|
|
230
|
-
];
|
|
229
|
+
].filter(Number.isFinite);
|
|
231
230
|
const aligned = secondarySeries.length > 0 ? alignedTicks([...values, ...[valueDomain.value?.[0], valueDomain.value?.[1]].filter((end) => end !== void 0)], rows.flatMap((row) => secondarySeries.map((item) => seriesNumber(row, item.key) ?? 0))) : void 0;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
const valueLength = horizontal ? width : plotHeight;
|
|
232
|
+
let domain;
|
|
233
|
+
let ticks;
|
|
234
|
+
if (kind.value.percent) {
|
|
235
|
+
domain = [0, 1];
|
|
236
|
+
ticks = linearTicks(0, 1, tickCount(valueLength, horizontal));
|
|
237
|
+
} else if (aligned) {
|
|
238
|
+
domain = aligned.primary.domain;
|
|
239
|
+
ticks = aligned.primary.ticks;
|
|
240
|
+
} else {
|
|
241
|
+
const fromZero = kind.value.zeroBaseline !== false;
|
|
242
|
+
const low = values.length ? Math.min(...values, ...fromZero ? [0] : []) : 0;
|
|
243
|
+
const high = values.length ? Math.max(...values, ...fromZero ? [0] : []) : 1;
|
|
244
|
+
const [autoLow, autoHigh] = low === high ? [low - 1, high + 1] : [low, high];
|
|
245
|
+
const nice = niceDomain([valueDomain.value?.[0] ?? autoLow, valueDomain.value?.[1] ?? autoHigh], tickCount(valueLength, horizontal));
|
|
246
|
+
domain = [valueDomain.value?.[0] ?? nice[0], valueDomain.value?.[1] ?? nice[1]];
|
|
247
|
+
ticks = linearTicks(domain[0], domain[1], tickCount(valueLength, horizontal));
|
|
248
|
+
}
|
|
249
|
+
const valueLabel = (tick) => kind.value.percent ? format.number(tick, {
|
|
250
|
+
style: "percent",
|
|
251
|
+
maximumFractionDigits: 0
|
|
252
|
+
}) : format.number(tick, valueFormat.value);
|
|
253
|
+
const valueLabels = ticks.map(valueLabel);
|
|
254
|
+
const secondaryLabels = aligned ? aligned.secondary.ticks.map((tick) => format.number(tick, kind.value.secondary?.format)) : [];
|
|
255
|
+
const continuous = kind.value.continuousX;
|
|
256
|
+
const xValues = continuous ? rows.map((row) => continuous.value(row)).filter((x) => x !== void 0) : [];
|
|
257
|
+
const xDomain = niceDomain(xValues.length ? [Math.min(...xValues), Math.max(...xValues)] : [0, 1], tickCount(width, true));
|
|
258
|
+
const xTicks = continuous ? linearTicks(xDomain[0], xDomain[1], tickCount(width, true)) : [];
|
|
259
|
+
const widest = (labels) => Math.max(0, ...labels.map((label) => estimateTextWidth(label, TICK_FONT)));
|
|
260
|
+
const categoryLabels = rows.map((row) => categoryText(row));
|
|
261
|
+
const padding = {
|
|
262
|
+
left: 0,
|
|
263
|
+
right: 0,
|
|
264
|
+
top: 0,
|
|
265
|
+
bottom: 0,
|
|
266
|
+
...kind.value.padding
|
|
267
|
+
};
|
|
268
|
+
const bottomLabels = horizontal ? valueLabels : continuous ? xTicks.map(continuous.format) : categoryLabels;
|
|
269
|
+
const margin = horizontal ? {
|
|
270
|
+
left: Math.min(widest(categoryLabels), width * .3) + TICK_GAP,
|
|
271
|
+
right: Math.max(8, widest(valueLabels.slice(-1)) / 2),
|
|
272
|
+
top: 4,
|
|
273
|
+
bottom: 22
|
|
274
|
+
} : {
|
|
275
|
+
left: widest(valueLabels) + TICK_GAP,
|
|
276
|
+
right: aligned ? widest(secondaryLabels) + TICK_GAP : 8,
|
|
277
|
+
top: 8,
|
|
278
|
+
bottom: 22 + (continuous?.title ? 20 : 0)
|
|
279
|
+
};
|
|
280
|
+
if (!horizontal && bottomLabels.length && !kind.value.band) {
|
|
281
|
+
margin.left = Math.max(margin.left, widest(bottomLabels.slice(0, 1)) / 2 - padding.left);
|
|
282
|
+
margin.right = Math.max(margin.right, widest(bottomLabels.slice(-1)) / 2 - padding.right);
|
|
283
|
+
}
|
|
284
|
+
const plot = {
|
|
285
|
+
left: margin.left,
|
|
286
|
+
top: margin.top,
|
|
287
|
+
width: Math.max(0, width - margin.left - margin.right),
|
|
288
|
+
height: Math.max(0, plotHeight - margin.top - margin.bottom)
|
|
289
|
+
};
|
|
290
|
+
const [cStart, cEnd] = horizontal ? [plot.top + padding.top, plot.top + plot.height - padding.bottom] : [plot.left + padding.left, plot.left + plot.width - padding.right];
|
|
291
|
+
const count = rows.length;
|
|
292
|
+
const step = kind.value.band ? (cEnd - cStart) / Math.max(count, 1) : count > 1 ? (cEnd - cStart) / (count - 1) : cEnd - cStart;
|
|
293
|
+
const xScale = linearScale(xDomain, [cStart, cEnd]);
|
|
294
|
+
const category = continuous ? (row) => xScale(continuous.value(row) ?? NaN) : (_row, position) => kind.value.band ? cStart + step * (position + .5) : count > 1 ? cStart + step * position : (cStart + cEnd) / 2;
|
|
295
|
+
const valueScale = horizontal ? linearScale(domain, [plot.left + padding.left, plot.left + plot.width - padding.right]) : linearScale(domain, [plot.top + plot.height - padding.bottom, plot.top + padding.top]);
|
|
296
|
+
const valueOf = (item, row) => {
|
|
297
|
+
const value = seriesNumber(row, item.key);
|
|
298
|
+
if (value === void 0) return void 0;
|
|
299
|
+
if (kind.value.percent) {
|
|
237
300
|
const total = rowTotal(row);
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const value = seriesNumber(row, item.key);
|
|
242
|
-
return value === void 0 ? void 0 : aligned.toPrimary(value);
|
|
243
|
-
};
|
|
244
|
-
return (row) => seriesNumber(row, item.key);
|
|
301
|
+
return total === 0 ? void 0 : value / total;
|
|
302
|
+
}
|
|
303
|
+
return aligned && onSecondary(item.key) ? aligned.toPrimary(value) : value;
|
|
245
304
|
};
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
305
|
+
const stacked = kind.value.stacked ? shown.filter(kind.value.stackSeries ?? (() => true)) : [];
|
|
306
|
+
const segments = stackValues(stacked.map((item) => rows.map((row) => valueOf(item, row))));
|
|
307
|
+
const stacks = new Map(stacked.map((item, s) => [item.key, segments[s]]));
|
|
308
|
+
const every = continuous ? 1 : Math.max(1, Math.ceil((horizontal ? 16 : widest(categoryLabels) + 12) / Math.max(step, 1)));
|
|
309
|
+
const categoryTicks = continuous ? xTicks.map((tick) => ({
|
|
310
|
+
position: xScale(tick),
|
|
311
|
+
label: continuous.format(tick)
|
|
312
|
+
})) : rows.map((row, position) => ({
|
|
313
|
+
position: category(row, position),
|
|
314
|
+
label: position % every === 0 ? horizontal ? truncateText(categoryLabels[position], width * .3, TICK_FONT) : categoryLabels[position] : ""
|
|
315
|
+
}));
|
|
316
|
+
const valueTicks = ticks.map((tick, position) => ({
|
|
317
|
+
position: valueScale(tick),
|
|
318
|
+
label: valueLabels[position]
|
|
319
|
+
}));
|
|
320
|
+
const secondaryTicks = aligned ? aligned.primary.ticks.map((tick, position) => ({
|
|
321
|
+
position: valueScale(tick),
|
|
322
|
+
label: secondaryLabels[position]
|
|
323
|
+
})) : [];
|
|
324
|
+
return {
|
|
325
|
+
rows,
|
|
326
|
+
shown,
|
|
327
|
+
horizontal,
|
|
328
|
+
plot,
|
|
329
|
+
step,
|
|
330
|
+
domain,
|
|
331
|
+
category,
|
|
332
|
+
valueScale,
|
|
333
|
+
valueOf,
|
|
334
|
+
stacks,
|
|
335
|
+
categoryTicks,
|
|
336
|
+
valueTicks,
|
|
337
|
+
secondaryTicks
|
|
251
338
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
339
|
+
});
|
|
340
|
+
/** 一个类目在类目轴上的位置(标注、区间用):散点图是横轴的数值,其余是第几行;找不到时 undefined */
|
|
341
|
+
function positionOf(at) {
|
|
342
|
+
const { rows, category } = layout.value;
|
|
343
|
+
if (kind.value.continuousX) {
|
|
344
|
+
const row = rows.find((item) => kind.value.continuousX.value(item) === Number(at));
|
|
345
|
+
return row ? category(row, 0) : void 0;
|
|
346
|
+
}
|
|
347
|
+
const found = rows.findIndex((row) => row[index.value] === at);
|
|
348
|
+
return found < 0 ? void 0 : category(rows[found], found);
|
|
349
|
+
}
|
|
350
|
+
/** 指针移动:找出悬停的行(散点图是最近的点) */
|
|
351
|
+
function onPointermove(event) {
|
|
352
|
+
const svg = event.currentTarget;
|
|
353
|
+
const { x, y } = pointerIn(event, svg);
|
|
354
|
+
const { rows, plot, horizontal, category, valueScale, valueOf, shown, step } = layout.value;
|
|
355
|
+
if (!(x >= plot.left - 4 && x <= plot.left + plot.width + 4 && y >= plot.top - 4 && y <= plot.top + plot.height + 4) || rows.length === 0) {
|
|
356
|
+
hover.value = null;
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (kind.value.pointTooltip) {
|
|
360
|
+
let best = null;
|
|
361
|
+
let distance = POINT_REACH;
|
|
362
|
+
rows.forEach((row, position) => {
|
|
363
|
+
for (const item of shown) {
|
|
364
|
+
const value = valueOf(item, row);
|
|
365
|
+
if (value === void 0) continue;
|
|
366
|
+
const [px, py] = [category(row, position), valueScale(value)];
|
|
367
|
+
const d = Math.hypot(px - x, py - y);
|
|
368
|
+
if (d < distance) {
|
|
369
|
+
distance = d;
|
|
370
|
+
best = {
|
|
371
|
+
index: position,
|
|
372
|
+
x: px,
|
|
373
|
+
y: py
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
268
377
|
});
|
|
269
|
-
|
|
378
|
+
hover.value = best;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const along = horizontal ? y : x;
|
|
382
|
+
let nearest = 0;
|
|
383
|
+
rows.forEach((row, position) => {
|
|
384
|
+
if (Math.abs(category(row, position) - along) < Math.abs(category(rows[nearest], nearest) - along)) nearest = position;
|
|
270
385
|
});
|
|
271
|
-
const
|
|
386
|
+
const center = category(rows[nearest], nearest);
|
|
387
|
+
if (Math.abs(center - along) > Math.max(step, 8)) {
|
|
388
|
+
hover.value = null;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
hover.value = horizontal ? {
|
|
392
|
+
index: nearest,
|
|
393
|
+
x,
|
|
394
|
+
y: center
|
|
395
|
+
} : {
|
|
396
|
+
index: nearest,
|
|
397
|
+
x: center,
|
|
398
|
+
y
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/** 点击下钻:点在柱上按那根柱(data-index),其余按悬停的行 */
|
|
402
|
+
function onClick(event) {
|
|
403
|
+
if (!onItemClick.value) return;
|
|
404
|
+
const target = event.target?.closest?.("[data-index]");
|
|
405
|
+
const position = target ? Number(target.getAttribute("data-index")) : hover.value?.index;
|
|
406
|
+
if (position === void 0 || Number.isNaN(position)) return;
|
|
407
|
+
const offset = range()?.[0] ?? 0;
|
|
408
|
+
const row = layout.value.rows[position];
|
|
409
|
+
if (row !== void 0) onItemClick.value(row, offset + position);
|
|
410
|
+
}
|
|
411
|
+
/** 区间:浅色的带,文字在左下角(类目轴)或左上角(数值轴) */
|
|
412
|
+
function renderBands() {
|
|
413
|
+
const { plot, horizontal, step, valueScale } = layout.value;
|
|
414
|
+
return (bands.value ?? []).map((band, position) => {
|
|
272
415
|
const onIndex = band.axis !== "value";
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
416
|
+
const color = resolveColor(band.color ?? "neutral", 0);
|
|
417
|
+
let from;
|
|
418
|
+
let to;
|
|
419
|
+
if (onIndex) {
|
|
420
|
+
const a = positionOf(band.from);
|
|
421
|
+
const b = positionOf(band.to);
|
|
422
|
+
if (a === void 0 || b === void 0) return null;
|
|
423
|
+
const half = kind.value.continuousX ? 0 : step / 2;
|
|
424
|
+
from = Math.min(a, b) - half;
|
|
425
|
+
to = Math.max(a, b) + half;
|
|
426
|
+
} else {
|
|
427
|
+
from = Math.min(valueScale(Number(band.from)), valueScale(Number(band.to)));
|
|
428
|
+
to = Math.max(valueScale(Number(band.from)), valueScale(Number(band.to)));
|
|
429
|
+
}
|
|
430
|
+
const alongCategory = onIndex !== horizontal;
|
|
431
|
+
const clampedFrom = Math.max(from, alongCategory ? plot.left : plot.top);
|
|
432
|
+
const clampedTo = Math.min(to, alongCategory ? plot.left + plot.width : plot.top + plot.height);
|
|
433
|
+
const rect = alongCategory ? {
|
|
434
|
+
x: clampedFrom,
|
|
435
|
+
y: plot.top,
|
|
436
|
+
width: clampedTo - clampedFrom,
|
|
437
|
+
height: plot.height
|
|
438
|
+
} : {
|
|
439
|
+
x: plot.left,
|
|
440
|
+
y: clampedFrom,
|
|
441
|
+
width: plot.width,
|
|
442
|
+
height: clampedTo - clampedFrom
|
|
443
|
+
};
|
|
444
|
+
if (rect.width <= 0 || rect.height <= 0) return null;
|
|
445
|
+
return createVNode("g", {
|
|
446
|
+
"key": `band-${position}`,
|
|
447
|
+
"data-part": "band"
|
|
448
|
+
}, [createVNode("rect", mergeJsxProps(rect, css.attrs(xyChartStyles.bandFill(color), chartSvgStyles.noPointer)), null), band.label && createVNode("text", mergeJsxProps({
|
|
449
|
+
"x": rect.x + 6,
|
|
450
|
+
"y": onIndex && !horizontal ? rect.y + rect.height - 6 : rect.y + 6,
|
|
451
|
+
"dominant-baseline": onIndex && !horizontal ? "auto" : "hanging"
|
|
452
|
+
}, css.attrs(chartSvgStyles.labelMuted, chartSvgStyles.noPointer)), [band.label])]);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
/** 目标线与事件竖线 */
|
|
456
|
+
function renderLines() {
|
|
457
|
+
const { plot, horizontal, valueScale } = layout.value;
|
|
458
|
+
const right = plot.left + plot.width;
|
|
459
|
+
const bottom = plot.top + plot.height;
|
|
460
|
+
const targetLines = (targets.value ?? []).map((target, position) => {
|
|
461
|
+
const color = resolveColor(target.color ?? "danger", 0);
|
|
462
|
+
const at = svgNumber(valueScale(toNumber(target.value)));
|
|
463
|
+
const label = target.label ? `${target.label} ${valueText(target.value)}` : valueText(target.value);
|
|
464
|
+
return createVNode("g", {
|
|
465
|
+
"key": `target-${position}`,
|
|
466
|
+
"data-part": "target"
|
|
467
|
+
}, [horizontal ? createVNode("line", mergeJsxProps({
|
|
468
|
+
"x1": at,
|
|
469
|
+
"x2": at,
|
|
470
|
+
"y1": plot.top,
|
|
471
|
+
"y2": bottom
|
|
472
|
+
}, css.attrs(xyChartStyles.targetLine, chartSvgStyles.stroke(color))), null) : createVNode("line", mergeJsxProps({
|
|
473
|
+
"x1": plot.left,
|
|
474
|
+
"x2": right,
|
|
475
|
+
"y1": at,
|
|
476
|
+
"y2": at
|
|
477
|
+
}, css.attrs(xyChartStyles.targetLine, chartSvgStyles.stroke(color))), null), createVNode("text", mergeJsxProps({
|
|
478
|
+
"x": horizontal ? at + 4 : plot.left + 4,
|
|
479
|
+
"y": horizontal ? plot.top + 4 : at - 5,
|
|
480
|
+
"dominant-baseline": horizontal ? "hanging" : "auto"
|
|
481
|
+
}, css.attrs(chartSvgStyles.labelMuted, chartSvgStyles.halo, xyChartStyles.textFill(color), chartSvgStyles.noPointer)), [label])]);
|
|
290
482
|
});
|
|
291
|
-
const
|
|
483
|
+
const events = (annotations.value ?? []).filter((item) => item.series === void 0).map((item, position) => {
|
|
292
484
|
const at = positionOf(item.at);
|
|
293
|
-
if (at === void 0) return
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
485
|
+
if (at === void 0) return null;
|
|
486
|
+
const color = resolveColor(item.color ?? "neutral", 0);
|
|
487
|
+
const p = svgNumber(at);
|
|
488
|
+
return createVNode("g", {
|
|
489
|
+
"key": `event-${position}`,
|
|
490
|
+
"data-part": "event"
|
|
491
|
+
}, [horizontal ? createVNode("line", mergeJsxProps({
|
|
492
|
+
"x1": plot.left,
|
|
493
|
+
"x2": right,
|
|
494
|
+
"y1": p,
|
|
495
|
+
"y2": p
|
|
496
|
+
}, css.attrs(xyChartStyles.eventLine, chartSvgStyles.stroke(color))), null) : createVNode("line", mergeJsxProps({
|
|
497
|
+
"x1": p,
|
|
498
|
+
"x2": p,
|
|
499
|
+
"y1": plot.top,
|
|
500
|
+
"y2": bottom
|
|
501
|
+
}, css.attrs(xyChartStyles.eventLine, chartSvgStyles.stroke(color))), null), createVNode("text", mergeJsxProps({
|
|
502
|
+
"x": horizontal ? right - 4 : p + 4,
|
|
503
|
+
"y": horizontal ? p - 4 : plot.top + 4,
|
|
504
|
+
"text-anchor": horizontal ? "end" : "start",
|
|
505
|
+
"dominant-baseline": horizontal ? "auto" : "hanging"
|
|
506
|
+
}, css.attrs(chartSvgStyles.labelMuted, chartSvgStyles.halo, chartSvgStyles.noPointer)), [item.label])]);
|
|
307
507
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
x
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
508
|
+
return [...targetLines, ...events];
|
|
509
|
+
}
|
|
510
|
+
/** 一个系列在一行上的值的坐标:堆叠时是它在这一摞里的外端 */
|
|
511
|
+
function valuePosition(item, row, position) {
|
|
512
|
+
const segment = layout.value.stacks.get(item.key)?.[position];
|
|
513
|
+
if (segment) return segment.value === void 0 ? void 0 : segment.value < 0 ? segment.low : segment.high;
|
|
514
|
+
return layout.value.valueOf(item, row);
|
|
515
|
+
}
|
|
516
|
+
/** 标注圈出的值:空心圈,文字在上方 */
|
|
517
|
+
function renderMarks() {
|
|
518
|
+
if (layout.value.horizontal) return null;
|
|
519
|
+
return (annotations.value ?? []).filter((item) => item.series !== void 0).map((item, position) => {
|
|
520
|
+
const series = layout.value.shown.find((entry) => entry.key === item.series);
|
|
521
|
+
const rowIndex = kind.value.continuousX ? layout.value.rows.findIndex((row) => kind.value.continuousX.value(row) === Number(item.at)) : layout.value.rows.findIndex((row) => row[index.value] === item.at);
|
|
522
|
+
const row = layout.value.rows[rowIndex];
|
|
523
|
+
if (!series || row === void 0) return null;
|
|
524
|
+
const value = valuePosition(series, row, rowIndex);
|
|
525
|
+
if (value === void 0) return null;
|
|
526
|
+
const [x, y] = [svgNumber(layout.value.category(row, rowIndex)), svgNumber(layout.value.valueScale(value))];
|
|
527
|
+
const color = item.color ? resolveColor(item.color, 0) : series.color;
|
|
528
|
+
const half = estimateTextWidth(item.label, TICK_FONT) / 2;
|
|
529
|
+
const { left, width } = layout.value.plot;
|
|
530
|
+
const anchor = x + half > left + width ? "end" : x - half < left ? "start" : "middle";
|
|
531
|
+
const textX = anchor === "end" ? x + 8 : anchor === "start" ? x - 8 : x;
|
|
532
|
+
return createVNode("g", {
|
|
533
|
+
"key": `mark-${position}`,
|
|
534
|
+
"data-part": "mark"
|
|
535
|
+
}, [createVNode("circle", mergeJsxProps({
|
|
536
|
+
"cx": x,
|
|
537
|
+
"cy": y,
|
|
538
|
+
"r": 8
|
|
539
|
+
}, css.attrs(xyChartStyles.mark, chartSvgStyles.stroke(color))), null), createVNode("text", mergeJsxProps({
|
|
540
|
+
"x": textX,
|
|
541
|
+
"y": y - 14,
|
|
542
|
+
"text-anchor": anchor
|
|
543
|
+
}, css.attrs(chartSvgStyles.label, chartSvgStyles.halo, chartSvgStyles.noPointer)), [item.label])]);
|
|
340
544
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
545
|
+
}
|
|
546
|
+
/** 十字线:悬停的类目一条竖线,线上给各系列画圆点;柱状图铺一层浅底 */
|
|
547
|
+
function renderCrosshair(front) {
|
|
548
|
+
const current = hover.value;
|
|
549
|
+
if (!current || kind.value.pointTooltip) return null;
|
|
550
|
+
const { rows, plot, horizontal, step, category, valueScale } = layout.value;
|
|
551
|
+
const row = rows[current.index];
|
|
552
|
+
if (row === void 0) return null;
|
|
553
|
+
const center = category(row, current.index);
|
|
554
|
+
if (!front) {
|
|
555
|
+
if (kind.value.crosshairDots && !kind.value.band) return null;
|
|
556
|
+
const half = Math.max(step / 2, 1);
|
|
557
|
+
return horizontal ? createVNode("rect", mergeJsxProps({
|
|
558
|
+
"x": plot.left,
|
|
559
|
+
"y": center - half,
|
|
560
|
+
"width": plot.width,
|
|
561
|
+
"height": half * 2
|
|
562
|
+
}, css.attrs(chartSvgStyles.hoverBand)), null) : createVNode("rect", mergeJsxProps({
|
|
563
|
+
"x": center - half,
|
|
564
|
+
"y": plot.top,
|
|
565
|
+
"width": half * 2,
|
|
566
|
+
"height": plot.height
|
|
567
|
+
}, css.attrs(chartSvgStyles.hoverBand)), null);
|
|
568
|
+
}
|
|
569
|
+
if (!kind.value.crosshairDots) return null;
|
|
570
|
+
const dotted = kind.value.crosshairSeries ? layout.value.shown.filter(kind.value.crosshairSeries) : layout.value.shown;
|
|
571
|
+
const p = Math.round(center) + .5;
|
|
572
|
+
return createVNode("g", { "data-part": "crosshair" }, [!kind.value.band && createVNode("line", mergeJsxProps({
|
|
573
|
+
"x1": p,
|
|
574
|
+
"x2": p,
|
|
575
|
+
"y1": plot.top,
|
|
576
|
+
"y2": plot.top + plot.height
|
|
577
|
+
}, css.attrs(chartSvgStyles.crosshair)), null), dotted.map((item) => {
|
|
578
|
+
const value = valuePosition(item, row, current.index);
|
|
579
|
+
if (value === void 0) return null;
|
|
580
|
+
return createVNode("circle", mergeJsxProps({
|
|
581
|
+
"key": item.key,
|
|
582
|
+
"cx": svgNumber(center),
|
|
583
|
+
"cy": svgNumber(valueScale(value)),
|
|
584
|
+
"r": 4.5
|
|
585
|
+
}, css.attrs(xyChartStyles.crosshairDot, chartSvgStyles.fill(item.color))), null);
|
|
586
|
+
})]);
|
|
587
|
+
}
|
|
588
|
+
/** 绘图区的 SVG */
|
|
589
|
+
function renderPlot() {
|
|
590
|
+
const { rows, shown, horizontal, plot, step, domain, category, valueScale, valueOf, stacks } = layout.value;
|
|
591
|
+
const context = {
|
|
592
|
+
series: shown,
|
|
593
|
+
rows,
|
|
594
|
+
horizontal,
|
|
595
|
+
plot,
|
|
596
|
+
step,
|
|
597
|
+
domain,
|
|
598
|
+
category,
|
|
599
|
+
value: valueScale,
|
|
600
|
+
valueOf,
|
|
601
|
+
stacks,
|
|
602
|
+
point: (c, v) => horizontal ? [v, c] : [c, v],
|
|
603
|
+
hovered: hover.value?.index ?? null
|
|
604
|
+
};
|
|
605
|
+
const zero = valueScale(0);
|
|
606
|
+
const showZero = domain[0] < 0 && domain[1] > 0;
|
|
607
|
+
return createVNode("svg", mergeJsxProps({
|
|
608
|
+
"width": area.width,
|
|
609
|
+
"height": area.height
|
|
610
|
+
}, css.attrs(chartSvgStyles.svg, xyChartStyles.svg), {
|
|
611
|
+
"onPointermove": onPointermove,
|
|
612
|
+
"onPointerleave": _cache[3] || (_cache[3] = () => hover.value = null),
|
|
613
|
+
"onClick": onClick
|
|
614
|
+
}), [
|
|
615
|
+
renderBands(),
|
|
616
|
+
chartAxis({
|
|
617
|
+
side: horizontal ? "bottom" : "left",
|
|
618
|
+
ticks: layout.value.valueTicks,
|
|
619
|
+
plot,
|
|
620
|
+
grid: grid.value
|
|
371
621
|
}),
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
x,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
color: (_row, position) => dotted[position]?.color ?? "transparent",
|
|
415
|
-
getCircles: kind.value.crosshairDots ? void 0 : () => [],
|
|
416
|
-
hideWhenFarFromPointer: false,
|
|
417
|
-
onCrosshairMove: (_x, row) => hover(row)
|
|
418
|
-
});
|
|
419
|
-
tooltipBox.setConfig({ triggers: kind.value.pointTooltip ? { [kind.value.pointTooltip]: (row) => renderTooltip(row) } : {} });
|
|
420
|
-
const config = {
|
|
421
|
-
components,
|
|
422
|
-
xAxis,
|
|
423
|
-
yAxis,
|
|
424
|
-
crosshair: (tooltip.value || onItemClick.value && !kind.value.itemSelector) && !kind.value.pointTooltip ? crosshair : void 0,
|
|
425
|
-
tooltip: tooltip.value ? tooltipBox : void 0,
|
|
426
|
-
[kind.value.horizontal ? "xDomain" : "yDomain"]: domain,
|
|
427
|
-
padding: aligned ? {
|
|
428
|
-
...kind.value.padding,
|
|
429
|
-
right: Math.max(kind.value.padding?.right ?? 0, SECONDARY_AXIS_WIDTH)
|
|
430
|
-
} : kind.value.padding ?? {},
|
|
431
|
-
ariaLabel: null,
|
|
432
|
-
duration: chartDuration(element),
|
|
433
|
-
onRenderComplete: haloPlotlineLabels
|
|
622
|
+
chartAxis({
|
|
623
|
+
side: horizontal ? "left" : "bottom",
|
|
624
|
+
ticks: layout.value.categoryTicks,
|
|
625
|
+
plot,
|
|
626
|
+
grid: kind.value.continuousX ? grid.value : false
|
|
627
|
+
}),
|
|
628
|
+
layout.value.secondaryTicks.length > 0 && chartAxis({
|
|
629
|
+
side: "right",
|
|
630
|
+
ticks: layout.value.secondaryTicks,
|
|
631
|
+
plot
|
|
632
|
+
}),
|
|
633
|
+
kind.value.continuousX?.title && createVNode("text", mergeJsxProps({
|
|
634
|
+
"x": plot.left + plot.width / 2,
|
|
635
|
+
"y": area.height - 2,
|
|
636
|
+
"text-anchor": "middle"
|
|
637
|
+
}, css.attrs(chartSvgStyles.axisTitle)), [kind.value.continuousX.title]),
|
|
638
|
+
showZero && (horizontal ? createVNode("line", mergeJsxProps({
|
|
639
|
+
"x1": Math.round(zero) + .5,
|
|
640
|
+
"x2": Math.round(zero) + .5,
|
|
641
|
+
"y1": plot.top,
|
|
642
|
+
"y2": plot.top + plot.height
|
|
643
|
+
}, css.attrs(chartSvgStyles.baseline)), null) : createVNode("line", mergeJsxProps({
|
|
644
|
+
"x1": plot.left,
|
|
645
|
+
"x2": plot.left + plot.width,
|
|
646
|
+
"y1": Math.round(zero) + .5,
|
|
647
|
+
"y2": Math.round(zero) + .5
|
|
648
|
+
}, css.attrs(chartSvgStyles.baseline)), null)),
|
|
649
|
+
renderCrosshair(false),
|
|
650
|
+
kind.value.render(context),
|
|
651
|
+
renderLines(),
|
|
652
|
+
renderMarks(),
|
|
653
|
+
renderCrosshair(true)
|
|
654
|
+
]);
|
|
655
|
+
}
|
|
656
|
+
const tip = () => {
|
|
657
|
+
const current = hover.value;
|
|
658
|
+
if (!tooltip.value || !current) return null;
|
|
659
|
+
const row = layout.value.rows[current.index];
|
|
660
|
+
return row === void 0 ? null : {
|
|
661
|
+
x: current.x,
|
|
662
|
+
y: current.y,
|
|
663
|
+
content: tooltipContent(row)
|
|
434
664
|
};
|
|
435
|
-
|
|
436
|
-
else {
|
|
437
|
-
container = new XYContainer(element, config, rows);
|
|
438
|
-
containerElement = element;
|
|
439
|
-
uniqueHighlightFilter(element, `${id}-highlight`);
|
|
440
|
-
}
|
|
441
|
-
});
|
|
665
|
+
};
|
|
442
666
|
return () => createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
443
|
-
"sx": [css.select(() => !kind.value.crosshairDots && xyChartStyles.hideCrosshairLine, true), sx.value],
|
|
444
667
|
"unit": kind.value.percent ? void 0 : unit.value,
|
|
445
668
|
"unitEnd": kind.value.secondary?.unit,
|
|
446
669
|
"height": height.value,
|
|
@@ -452,9 +675,6 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
452
675
|
"isEmpty": data.value.length === 0,
|
|
453
676
|
"empty": empty.value,
|
|
454
677
|
"clickable": onItemClick.value != null,
|
|
455
|
-
"onPlotClick": onItemClick.value && !kind.value.itemSelector ? () => {
|
|
456
|
-
if (hovered) onItemClick.value(hovered.row, hovered.index);
|
|
457
|
-
} : void 0,
|
|
458
678
|
"table": kind.value.table?.(data.value) ?? {
|
|
459
679
|
columns: [
|
|
460
680
|
lang.chartCategory,
|
|
@@ -472,7 +692,12 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
472
692
|
})]
|
|
473
693
|
}))
|
|
474
694
|
},
|
|
475
|
-
"plotRef":
|
|
695
|
+
"plotRef": area.ref,
|
|
696
|
+
"plot": area.ready && shownRows.value.length > 0 ? [renderPlot(), createVNode(ChartTooltipBox, {
|
|
697
|
+
"key": "tooltip",
|
|
698
|
+
"tip": tip(),
|
|
699
|
+
"width": area.width
|
|
700
|
+
}, null)] : null,
|
|
476
701
|
"footer": zoom.value && !kind.value.continuousX && data.value.length > 2 ? zoomBar() : void 0
|
|
477
702
|
}), null);
|
|
478
703
|
}, {
|
|
@@ -500,22 +725,9 @@ var XYChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
500
725
|
"zoom",
|
|
501
726
|
"_zoomRange",
|
|
502
727
|
"onUpdate:_zoomRange",
|
|
503
|
-
"kind"
|
|
504
|
-
"sx"
|
|
728
|
+
"kind"
|
|
505
729
|
]
|
|
506
730
|
});
|
|
507
|
-
/**
|
|
508
|
-
* 目标线、事件竖线的文字描一圈表面色的边(同地图上地点的名字):压在柱子、折线上也看得清。
|
|
509
|
-
* Unovis 的 Plotline 文字没有描边的配置项,渲染完成后补在元素上;导出图片时按计算样式一并带走
|
|
510
|
-
*/
|
|
511
|
-
function haloPlotlineLabels(svg) {
|
|
512
|
-
for (const label of svg.querySelectorAll("text[class*=\"plotline-label\"]")) {
|
|
513
|
-
label.style.setProperty("paint-order", "stroke");
|
|
514
|
-
label.style.setProperty("stroke", colors.surface);
|
|
515
|
-
label.style.setProperty("stroke-width", "3px");
|
|
516
|
-
label.style.setProperty("stroke-linejoin", "round");
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
731
|
//#endregion
|
|
520
732
|
export { XYChart, xyChartStyles };
|
|
521
733
|
|