Skip to content

Rendering tooltips and dataView with slots #838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
34e04a7
feat: experimental component rendered tooltip
kingyue737 Jun 2, 2025
79a1d7d
revert slot in VChart
kingyue737 Jun 7, 2025
42c3569
feat: use tooltip composable
kingyue737 Jun 7, 2025
978317e
feat: try createApp
kingyue737 Jun 7, 2025
89743cf
feat: use pie chart as tooltip
kingyue737 Jun 8, 2025
585279e
feat: switch to createVNode
kingyue737 Jun 9, 2025
c6bab0d
feat: try component with teleport
kingyue737 Jun 9, 2025
2072c3b
wip
kingyue737 Jun 21, 2025
177e875
add xAxis example
kingyue737 Jun 22, 2025
117df61
refactor with shallowReactive
kingyue737 Jun 22, 2025
2dccfdb
Support dynamic slot
kingyue737 Jun 23, 2025
322fda4
fix: fill empty elements with object in array
kingyue737 Jun 24, 2025
49ad09b
shallow copy option along the path
kingyue737 Jul 1, 2025
9c2f431
ssr friendly
kingyue737 Jul 1, 2025
0acb7b2
vibe docs
kingyue737 Jul 1, 2025
3556afe
typo
kingyue737 Jul 1, 2025
6aa8d6d
update according to the review
kingyue737 Jul 3, 2025
887102d
add dataView slot
kingyue737 Jul 4, 2025
c1b76d5
Merge branch '8.0' into tooltip-slot
kingyue737 Jul 4, 2025
429943a
fix docs typo
kingyue737 Jul 7, 2025
c8a8869
update according to the review
kingyue737 Jul 15, 2025
f5fdc88
small fix
kingyue737 Jul 15, 2025
49d807b
remove wrapper around slotProp
kingyue737 Jul 19, 2025
fe3040a
update comments
kingyue737 Jul 19, 2025
c7d9f45
remove anys
kingyue737 Jul 22, 2025
7e6132f
add tooltip slot prop type
kingyue737 Jul 22, 2025
00f222a
target to vue 3.3
kingyue737 Jul 24, 2025
f356f89
move slot related codes to slot.ts
kingyue737 Jul 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 73 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).

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

> 💡 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.
> [!TIP]
> 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.

- `update-options: object`

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

> **Note**
>
> [!NOTE]
> Only the `.once` event modifier is supported as other modifiers are tightly coupled with the DOM event system.

Vue-ECharts support the following events:
Expand Down Expand Up @@ -337,6 +337,76 @@ export default {
> - [`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.
> - `setTheme`: use the `theme` prop instead.

### Slots

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.

**Slot Naming Convention**

- Slot names begin with `tooltip`/`dataView`, followed by hyphen-separated path segments to the target.
- Each segment corresponds to an `option` property name or an array index (for arrays, use the numeric index).
- The constructed slot name maps directly to the nested callback it overrides.

**Example mappings**:

- `tooltip` → `option.tooltip.formatter`
- `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
- `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
- `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
- `dataView` → `option.toolbox.feature.dataView.optionToContent`
- `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`

The slot props correspond to the first parameter of the callback function.

<details>
<summary>Usage</summary>

```vue
<template>
<v-chart :option="chartOptions">
<!-- Global `tooltip.formatter` -->
<template #tooltip="params">
<div v-for="(param, i) in params" :key="i">
<span v-html="param.marker" />
<span>{{ param.seriesName }}</span>
<span>{{ param.value[0] }}</span>
</div>
</template>

<!-- Tooltip on xAxis -->
<template #tooltip-xAxis="params">
<div>X-Axis : {{ params.value }}</div>
</template>

<!-- Data View Content -->
<template #dataView="option">
<table>
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
</template>
```

[Example →](https://vue-echarts.dev/#line)

</details>

> [!NOTE]
> Slots take precedence over the corresponding callback defined in `props.option`.

### Static Methods

Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).
Expand Down
76 changes: 73 additions & 3 deletions README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ app.component('v-chart', VueECharts)

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

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

- `update-options: object`

Expand Down Expand Up @@ -195,8 +196,7 @@ app.component('v-chart', VueECharts)
</template>
```

> **Note**
>
> [!NOTE]
> 仅支持 `.once` 修饰符,因为其它修饰符都与 DOM 事件机制强耦合。

Vue-ECharts 支持如下事件:
Expand Down Expand Up @@ -337,6 +337,76 @@ export default {
> - [`showLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.hideLoading):请使用 `loading` 和 `loading-options` prop。
> - `setTheme`:请使用 `theme` prop。

### 插槽(Slots)

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 模板语法来编写自定义提示框或数据视图中的内容。

**插槽命名约定**

- 插槽名称以 `tooltip`/`dataView` 开头,后面跟随用连字符分隔的路径片段,用于定位目标。
- 每个路径片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
- 拼接后的插槽名称直接映射到要覆盖的嵌套回调函数。

**示例映射**:

- `tooltip` → `option.tooltip.formatter`
- `tooltip-baseOption` → `option.baseOption.tooltip.formatter`
- `tooltip-xAxis-1` → `option.xAxis[1].tooltip.formatter`
- `tooltip-series-2-data-4` → `option.series[2].data[4].tooltip.formatter`
- `dataView` → `option.toolbox.feature.dataView.optionToContent`
- `dataView-media-1-option` → `option.media[1].option.toolbox.feature.dataView.optionToContent`

插槽的 props 对象对应回调函数的第一个参数。

<details>
<summary>用法示例</summary>

```vue
<template>
<v-chart :option="chartOptions">
<!-- 全局 `tooltip.formatter` -->
<template #tooltip="params">
<div v-for="(param, i) in params" :key="i">
<span v-html="param.marker" />
<span>{{ param.seriesName }}</span>
<span>{{ param.value[0] }}</span>
</div>
</template>

<!-- x轴 tooltip -->
<template #tooltip-xAxis="params">
<div>X轴: {{ params.value }}</div>
</template>

<!-- 数据视图内容 -->
<template #dataView="option">
<table>
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
</template>
```

[示例 →](https://vue-echarts.dev/#line)

</details>

> [!NOTE]
> 插槽会优先于 `props.option` 中对应的回调函数。

### 静态方法

静态方法请直接通过 [`echarts` 本身](https://echarts.apache.org/zh/api.html#echarts)进行调用。
Expand Down
2 changes: 2 additions & 0 deletions demo/Demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { track } from "@vercel/analytics";

import LogoChart from "./examples/LogoChart.vue";
import BarChart from "./examples/BarChart.vue";
import LineChart from "./examples/LineChart.vue";
import PieChart from "./examples/PieChart.vue";
import PolarChart from "./examples/PolarChart.vue";
import ScatterChart from "./examples/ScatterChart.vue";
Expand Down Expand Up @@ -74,6 +75,7 @@ watch(codeOpen, (open) => {
</p>

<bar-chart />
<line-chart />
<pie-chart />
<polar-chart />
<scatter-chart />
Expand Down
56 changes: 56 additions & 0 deletions demo/data/line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300,
},
legend: { top: 20 },
tooltip: {
trigger: "axis",
},
dataset: {
source: [
["product", "2012", "2013", "2014", "2015", "2016", "2017"],
["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
],
},
xAxis: {
type: "category",
triggerEvent: true,
tooltip: { show: true, formatter: "" },
},
yAxis: {
triggerEvent: true,
tooltip: { show: true, formatter: "" },
},
series: [
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
],
};
}
2 changes: 1 addition & 1 deletion demo/examples/Example.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defineProps({
width: fit-content;
margin: 2em auto;
.echarts {
> .echarts {
width: calc(60vw + 4em);
height: 360px;
max-width: 720px;
Expand Down
108 changes: 108 additions & 0 deletions demo/examples/LineChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<script setup>
import { use } from "echarts/core";
import { LineChart, PieChart } from "echarts/charts";
import {
GridComponent,
DatasetComponent,
LegendComponent,
TooltipComponent,
ToolboxComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/line";

use([
DatasetComponent,
GridComponent,
LegendComponent,
LineChart,
TooltipComponent,
ToolboxComponent,
PieChart,
]);

const option = shallowRef(getData());
const axis = shallowRef("xAxis");

function getPieOption(params) {
const option = {
dataset: { source: [params[0].dimensionNames, params[0].data] },
series: [
{
type: "pie",
radius: ["60%", "100%"],
seriesLayoutBy: "row",
itemStyle: {
borderRadius: 5,
borderColor: "#fff",
borderWidth: 2,
},
label: {
position: "center",
formatter: params[0].name,
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300,
},
},
],
};
return option;
}
</script>

<template>
<v-example
id="line"
title="Line chart"
desc="(with tooltip and dataView slots)"
>
<v-chart :option="option" autoresize>
<template #tooltip="params">
<v-chart
:style="{ width: '100px', height: '100px' }"
:option="getPieOption(params)"
autoresize
/>
</template>
<template #[`tooltip-${axis}`]="params">
{{ axis === "xAxis" ? "Year" : "Value" }}:
<b>{{ params.name }}</b>
</template>
<template #dataView="option">
<table style="margin: 20px auto">
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
<template #extra>
<p class="actions">
Custom tooltip on
<select v-model="axis">
<option value="xAxis">X Axis</option>
<option value="yAxis">Y Axis</option>
</select>
</p>
</template>
</v-example>
</template>

<style scoped>
th,
td {
padding: 4px 8px;
}
</style>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
],
"peerDependencies": {
"echarts": "^6.0.0-beta.1",
"vue": "^3.1.1"
"vue": "^3.3.0"
},
"devDependencies": {
"@highlightjs/vue-plugin": "^2.1.0",
Expand Down
Loading