Skip to content

Commit 1a05e28

Browse files
kingyue737Justineo
andauthored
feat: rendering tooltips and dataView with slots (#838)
* feat: experimental component rendered tooltip * revert slot in VChart * feat: use tooltip composable * feat: try createApp * feat: use pie chart as tooltip * feat: switch to createVNode The limitation is that the tooltip detached from the current component tree, not provide/inject will try teleport next * feat: try component with teleport * wip * add xAxis example * refactor with shallowReactive * Support dynamic slot * fix: fill empty elements with object in array * shallow copy option along the path * ssr friendly * vibe docs * typo * update according to the review * add dataView slot * chore: fix warnings and errors in demo (#839) * chore: suppress warning in demo * chore: prevent multiple intializations of esbuild-wasm in demo HMR * feat: dynamically update the theme (#841) Co-authored-by: GU Yiling <[email protected]> * feat: add dataView slot * vibe docs --------- Co-authored-by: GU Yiling <[email protected]> * fix docs typo * update according to the review * small fix * remove wrapper around slotProp * update comments * remove anys * add tooltip slot prop type * target to vue 3.3 * move slot related codes to slot.ts --------- Co-authored-by: GU Yiling <[email protected]>
1 parent 82a9f54 commit 1a05e28

File tree

13 files changed

+512
-44
lines changed

13 files changed

+512
-44
lines changed

README.md

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
155155

156156
ECharts' universal interface. Modifying this prop will trigger ECharts' `setOption` method. Read more [here →](https://echarts.apache.org/en/option.html)
157157

158-
> 💡 When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, ` notMerge: true` will be specified.
158+
> [!TIP]
159+
> When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, `notMerge: true` will be specified.
159160
160161
- `update-options: object`
161162

@@ -195,8 +196,7 @@ You can bind events with Vue's `v-on` directive.
195196
</template>
196197
```
197198

198-
> **Note**
199-
>
199+
> [!NOTE]
200200
> Only the `.once` event modifier is supported as other modifiers are tightly coupled with the DOM event system.
201201
202202
Vue-ECharts support the following events:
@@ -337,6 +337,76 @@ export default {
337337
> - [`showLoading`](https://echarts.apache.org/en/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/en/api.html#echartsInstance.hideLoading): use the `loading` and `loading-options` props instead.
338338
> - `setTheme`: use the `theme` prop instead.
339339
340+
### Slots
341+
342+
Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://echarts.apache.org/en/option.html#tooltip.formatter) and [`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/en/option.html#toolbox.feature.dataView.optionToContent) callbacks via Vue slots instead of defining them in your `option` object. This simplifies custom HTMLElement rendering using familiar Vue templating.
343+
344+
**Slot Naming Convention**
345+
346+
- Slot names begin with `tooltip`/`dataView`, followed by hyphen-separated path segments to the target.
347+
- Each segment corresponds to an `option` property name or an array index (for arrays, use the numeric index).
348+
- The constructed slot name maps directly to the nested callback it overrides.
349+
350+
**Example mappings**:
351+
352+
- `tooltip``option.tooltip.formatter`
353+
- `tooltip-baseOption``option.baseOption.tooltip.formatter`
354+
- `tooltip-xAxis-1``option.xAxis[1].tooltip.formatter`
355+
- `tooltip-series-2-data-4``option.series[2].data[4].tooltip.formatter`
356+
- `dataView``option.toolbox.feature.dataView.optionToContent`
357+
- `dataView-media-1-option``option.media[1].option.toolbox.feature.dataView.optionToContent`
358+
359+
The slot props correspond to the first parameter of the callback function.
360+
361+
<details>
362+
<summary>Usage</summary>
363+
364+
```vue
365+
<template>
366+
<v-chart :option="chartOptions">
367+
<!-- Global `tooltip.formatter` -->
368+
<template #tooltip="params">
369+
<div v-for="(param, i) in params" :key="i">
370+
<span v-html="param.marker" />
371+
<span>{{ param.seriesName }}</span>
372+
<span>{{ param.value[0] }}</span>
373+
</div>
374+
</template>
375+
376+
<!-- Tooltip on xAxis -->
377+
<template #tooltip-xAxis="params">
378+
<div>X-Axis : {{ params.value }}</div>
379+
</template>
380+
381+
<!-- Data View Content -->
382+
<template #dataView="option">
383+
<table>
384+
<thead>
385+
<tr>
386+
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
387+
{{ t }}
388+
</th>
389+
</tr>
390+
</thead>
391+
<tbody>
392+
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
393+
<th>{{ row[0] }}</th>
394+
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
395+
</tr>
396+
</tbody>
397+
</table>
398+
</template>
399+
</v-chart>
400+
</template>
401+
```
402+
403+
[Example →](https://vue-echarts.dev/#line)
404+
405+
</details>
406+
407+
> [!NOTE]
408+
> Slots take precedence over the corresponding callback defined in `props.option`.
409+
340410
### Static Methods
341411

342412
Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).

README.zh-Hans.md

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ app.component('v-chart', VueECharts)
155155

156156
ECharts 的万能接口。修改这个 prop 会触发 ECharts 实例的 `setOption` 方法。查看[详情 →](https://echarts.apache.org/zh/option.html)
157157

158-
> 💡 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`
158+
> [!TIP]
159+
> 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`
159160
160161
- `update-options: object`
161162

@@ -195,8 +196,7 @@ app.component('v-chart', VueECharts)
195196
</template>
196197
```
197198

198-
> **Note**
199-
>
199+
> [!NOTE]
200200
> 仅支持 `.once` 修饰符,因为其它修饰符都与 DOM 事件机制强耦合。
201201
202202
Vue-ECharts 支持如下事件:
@@ -337,6 +337,76 @@ export default {
337337
> - [`showLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.hideLoading):请使用 `loading``loading-options` prop。
338338
> - `setTheme`:请使用 `theme` prop。
339339
340+
### 插槽(Slots)
341+
342+
Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.formatter`](https://echarts.apache.org/zh/option.html#tooltip.formatter)[`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/zh/option.html#toolbox.feature.dataView.optionToContent) 回调,而无需在 `option` 对象中定义它们。你可以使用熟悉的 Vue 模板语法来编写自定义提示框或数据视图中的内容。
343+
344+
**插槽命名约定**
345+
346+
- 插槽名称以 `tooltip`/`dataView` 开头,后面跟随用连字符分隔的路径片段,用于定位目标。
347+
- 每个路径片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
348+
- 拼接后的插槽名称直接映射到要覆盖的嵌套回调函数。
349+
350+
**示例映射**
351+
352+
- `tooltip``option.tooltip.formatter`
353+
- `tooltip-baseOption``option.baseOption.tooltip.formatter`
354+
- `tooltip-xAxis-1``option.xAxis[1].tooltip.formatter`
355+
- `tooltip-series-2-data-4``option.series[2].data[4].tooltip.formatter`
356+
- `dataView``option.toolbox.feature.dataView.optionToContent`
357+
- `dataView-media-1-option``option.media[1].option.toolbox.feature.dataView.optionToContent`
358+
359+
插槽的 props 对象对应回调函数的第一个参数。
360+
361+
<details>
362+
<summary>用法示例</summary>
363+
364+
```vue
365+
<template>
366+
<v-chart :option="chartOptions">
367+
<!-- 全局 `tooltip.formatter` -->
368+
<template #tooltip="params">
369+
<div v-for="(param, i) in params" :key="i">
370+
<span v-html="param.marker" />
371+
<span>{{ param.seriesName }}</span>
372+
<span>{{ param.value[0] }}</span>
373+
</div>
374+
</template>
375+
376+
<!-- x轴 tooltip -->
377+
<template #tooltip-xAxis="params">
378+
<div>X轴: {{ params.value }}</div>
379+
</template>
380+
381+
<!-- 数据视图内容 -->
382+
<template #dataView="option">
383+
<table>
384+
<thead>
385+
<tr>
386+
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
387+
{{ t }}
388+
</th>
389+
</tr>
390+
</thead>
391+
<tbody>
392+
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
393+
<th>{{ row[0] }}</th>
394+
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
395+
</tr>
396+
</tbody>
397+
</table>
398+
</template>
399+
</v-chart>
400+
</template>
401+
```
402+
403+
[示例 →](https://vue-echarts.dev/#line)
404+
405+
</details>
406+
407+
> [!NOTE]
408+
> 插槽会优先于 `props.option` 中对应的回调函数。
409+
340410
### 静态方法
341411

342412
静态方法请直接通过 [`echarts` 本身](https://echarts.apache.org/zh/api.html#echarts)进行调用。

demo/Demo.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { track } from "@vercel/analytics";
88
99
import LogoChart from "./examples/LogoChart.vue";
1010
import BarChart from "./examples/BarChart.vue";
11+
import LineChart from "./examples/LineChart.vue";
1112
import PieChart from "./examples/PieChart.vue";
1213
import PolarChart from "./examples/PolarChart.vue";
1314
import ScatterChart from "./examples/ScatterChart.vue";
@@ -74,6 +75,7 @@ watch(codeOpen, (open) => {
7475
</p>
7576

7677
<bar-chart />
78+
<line-chart />
7779
<pie-chart />
7880
<polar-chart />
7981
<scatter-chart />

demo/data/line.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export default function getData() {
2+
return {
3+
textStyle: {
4+
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
5+
fontWeight: 300,
6+
},
7+
legend: { top: 20 },
8+
tooltip: {
9+
trigger: "axis",
10+
},
11+
dataset: {
12+
source: [
13+
["product", "2012", "2013", "2014", "2015", "2016", "2017"],
14+
["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
15+
["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
16+
["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
17+
["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
18+
],
19+
},
20+
xAxis: {
21+
type: "category",
22+
triggerEvent: true,
23+
tooltip: { show: true, formatter: "" },
24+
},
25+
yAxis: {
26+
triggerEvent: true,
27+
tooltip: { show: true, formatter: "" },
28+
},
29+
series: [
30+
{
31+
type: "line",
32+
smooth: true,
33+
seriesLayoutBy: "row",
34+
emphasis: { focus: "series" },
35+
},
36+
{
37+
type: "line",
38+
smooth: true,
39+
seriesLayoutBy: "row",
40+
emphasis: { focus: "series" },
41+
},
42+
{
43+
type: "line",
44+
smooth: true,
45+
seriesLayoutBy: "row",
46+
emphasis: { focus: "series" },
47+
},
48+
{
49+
type: "line",
50+
smooth: true,
51+
seriesLayoutBy: "row",
52+
emphasis: { focus: "series" },
53+
},
54+
],
55+
};
56+
}

demo/examples/Example.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ defineProps({
4343
width: fit-content;
4444
margin: 2em auto;
4545
46-
.echarts {
46+
> .echarts {
4747
width: calc(60vw + 4em);
4848
height: 360px;
4949
max-width: 720px;

demo/examples/LineChart.vue

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<script setup>
2+
import { use } from "echarts/core";
3+
import { LineChart, PieChart } from "echarts/charts";
4+
import {
5+
GridComponent,
6+
DatasetComponent,
7+
LegendComponent,
8+
TooltipComponent,
9+
ToolboxComponent,
10+
} from "echarts/components";
11+
import { shallowRef } from "vue";
12+
import VChart from "../../src/ECharts";
13+
import VExample from "./Example.vue";
14+
import getData from "../data/line";
15+
16+
use([
17+
DatasetComponent,
18+
GridComponent,
19+
LegendComponent,
20+
LineChart,
21+
TooltipComponent,
22+
ToolboxComponent,
23+
PieChart,
24+
]);
25+
26+
const option = shallowRef(getData());
27+
const axis = shallowRef("xAxis");
28+
29+
function getPieOption(params) {
30+
const option = {
31+
dataset: { source: [params[0].dimensionNames, params[0].data] },
32+
series: [
33+
{
34+
type: "pie",
35+
radius: ["60%", "100%"],
36+
seriesLayoutBy: "row",
37+
itemStyle: {
38+
borderRadius: 5,
39+
borderColor: "#fff",
40+
borderWidth: 2,
41+
},
42+
label: {
43+
position: "center",
44+
formatter: params[0].name,
45+
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
46+
fontWeight: 300,
47+
},
48+
},
49+
],
50+
};
51+
return option;
52+
}
53+
</script>
54+
55+
<template>
56+
<v-example
57+
id="line"
58+
title="Line chart"
59+
desc="(with tooltip and dataView slots)"
60+
>
61+
<v-chart :option="option" autoresize>
62+
<template #tooltip="params">
63+
<v-chart
64+
:style="{ width: '100px', height: '100px' }"
65+
:option="getPieOption(params)"
66+
autoresize
67+
/>
68+
</template>
69+
<template #[`tooltip-${axis}`]="params">
70+
{{ axis === "xAxis" ? "Year" : "Value" }}:
71+
<b>{{ params.name }}</b>
72+
</template>
73+
<template #dataView="option">
74+
<table style="margin: 20px auto">
75+
<thead>
76+
<tr>
77+
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
78+
{{ t }}
79+
</th>
80+
</tr>
81+
</thead>
82+
<tbody>
83+
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
84+
<th>{{ row[0] }}</th>
85+
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
86+
</tr>
87+
</tbody>
88+
</table>
89+
</template>
90+
</v-chart>
91+
<template #extra>
92+
<p class="actions">
93+
Custom tooltip on
94+
<select v-model="axis">
95+
<option value="xAxis">X Axis</option>
96+
<option value="yAxis">Y Axis</option>
97+
</select>
98+
</p>
99+
</template>
100+
</v-example>
101+
</template>
102+
103+
<style scoped>
104+
th,
105+
td {
106+
padding: 4px 8px;
107+
}
108+
</style>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
],
3838
"peerDependencies": {
3939
"echarts": "^6.0.0-beta.1",
40-
"vue": "^3.1.1"
40+
"vue": "^3.3.0"
4141
},
4242
"devDependencies": {
4343
"@highlightjs/vue-plugin": "^2.1.0",

0 commit comments

Comments
 (0)