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

Open
wants to merge 28 commits into
base: 8.0
Choose a base branch
from
Open

Conversation

kingyue737
Copy link
Member

@kingyue737 kingyue737 commented Jun 7, 2025

Note

This is an AI generated summary of this PR after all the discussion we had. Reviewed by @Justineo.

Slot-based Custom Rendering for tooltip and dataView

This PR introduces slot-based rendering for tooltip.formatter and toolbox.dataView.optionToContent in Vue-ECharts.
During review we briefly experimented with an internal POC component, but that approach was discarded in favor of first-class Vue slots, so no extra component is shipped.


Key Features

Area Description
Slot Naming Convention Slot names start with tooltip or dataView and map to the corresponding option path. Join path segments with hyphens and use numbers for array indices — e.g. tooltip-series-2-data-4option.series[2].data[4].tooltip.formatter.
Dynamic & Nested Slots Slots can be added or removed at runtime, making the API flexible for multi-series or dynamically generated charts.

Implementation Highlights

  • src/composables/slot.ts
    Parses slot names into option paths and returns wrapped formatter / optionToContent callbacks. These callbacks return a container HTMLElement with the rendered slot content.

  • src/ECharts.ts
    Collects user slots in setup(), injects them into the correct option paths, and calls setOption. Watches slot changes so the chart updates reactively.

Deprecated design

Summary

This PR proposes a new echarts-tooltip component. The component provides a formatter method that can be used with ECharts' tooltip.formatter option, allowing users to define tooltip content using Vue's template syntax.

The slot props are what users passed to the formatter method. The component teleports its slot content to a detached DOM element so that it does not exist in the html body.

Example

<script setup>
const tooltipRef = useTemplateRef('tooltip')
const option = shallowRef({
  tooltip: {
    trigger: "axis",
    formatter: (params) => {
      const name = params[0].name;
      const value = params[0].value;
      return tooltipRef.value?.formatter({ name, value });
    },
  },
});
</script>

<template>
  <v-chart :option="option" />
  <v-chart-tooltip ref="tooltip" v-slot="{ name, value }">
    <div>
      <span>{{ name }}</span>
      <span>{{ value }}</span>
    </div>
  </v-chart-tooltip>
</template>

For a practical example, see the Line Chart in the demo preview which features a Pie Chart as its tooltip.

Design Rationale

Why a Separate Component?

  1. Users have multiple ways to define tooltip formatters, and a single ECharts instance may contain different types of charts. Adding a slot directly to VChart would make it difficult to handle these various use cases intuitively.

tooltip.formatter

  1. The showTip event doesn't provide sufficient information for implementing the approach suggested in Use Web Components without native class support detection #836 (comment).

  2. We may need to support ECharts' asynchronous tooltip formatting:

const option = shallowRef({
  tooltip: {
    formatter: function (params, ticket, callback) {
      fetch(`url?id=${params.dataIndex}`).then((remoteData) => {
        callback(ticket, tooltipRef.value.formatter(remoteData));
      });
      return "Loading";
    },
  },
});

By making it a separate component, users can:

  • Decide how to compose it with their chart options
  • Benefit from tree-shaking when the component is unused
  • Have more control over params passed to the formatter function

@Justineo , do you have any other ideas for us to try?

Copy link

vercel bot commented Jun 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
vue-echarts ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 24, 2025 2:47pm

The limitation is that the tooltip detached from the current component tree, not provide/inject

will try teleport next
@Justineo
Copy link
Member

How about we go over the external API first?

@Justineo
Copy link
Member

  1. Users have multiple ways to define tooltip formatters, and a single ECharts instance may contain different types of charts. Adding a slot directly to VChart would make it difficult to handle these various use cases intuitively.

We could consider supporting multiple or dynamic slots—e.g., tooltip, tooltip-series-line, etc.—which could then map to the corresponding tooltip configurations at different levels. The idea would be to let slots fully override the tooltip formatters.

2. The showTip event doesn't provide sufficient information for implementing the approach suggested in Use Web Components without native class support detection #836 (comment).

From what I’ve seen, showTip is triggered whenever the pointer moves within the chart. Could you clarify what specific information is missing for our use case?

3. We may need to support ECharts' asynchronous tooltip formatting:

If we can implement tooltips as slots, we should be able to make the content reactive to async states or even render <Suspense>s.


The most tricky part I can think of is when and how to update the slot props when showTip is fired.

@kingyue737
Copy link
Member Author

We could consider supporting multiple or dynamic slots—e.g., tooltip, tooltip-series-line, etc.

I also considered this, but then I remembered that setOption can accept an array of options. So I decided to propose a separate component instead of designing a slot name convention, which might be a bit complicated 😂.

For multiple series of the same type but with different formatters, should we add a modifier like tooltip-series-line.[id]?

Most series support formatter for each single datum. But I think users can achieve similar functionality by using a series-level tooltip formatter and params.dataIndex. So we might not need to support it directly.

From what I’ve seen, showTip is triggered whenever the pointer moves within the chart. Could you clarify what specific information is missing for our use case?

I think the slotProps should be the params object from the tooltip.formatter callback if the slots belong to <VChart>. The payload of the showTip event is different from the params of the tooltip.formatter callback.

The most tricky part I can think of is when and how to update the slot props when showTip is fired.

We could override users' tooltip.formatter like this:

formatter(params) {
    // slotName.slotProps is a shallowRef
    slotName.slotProps.value = params

    return toHtml(slotContent)
}

The slot props would be updated whenever tooltip.formatter is called. So there’s no need to rely on the showTip event, just like here:

https://github.com/ecomfe/vue-echarts/pull/838/files#diff-bd261dd584a35b75c759fd268819ef41b77999d7e1fc0a703b7552b8449660daR11-R15

@kingyue737
Copy link
Member Author

kingyue737 commented Jun 21, 2025

How about use the property path of the tooltip.formatter as the slot name🤔?

  • global: option.tooltip.formatter
<template #tooltip />
  • axis: option.xAxis[1].tooltip.formatter
<template #tooltip:xAxis[1] />
  • series: option.series[3].tooltip.formatter
<template #tooltip:series[3] />
  • series.data / geo.regions: option.series[3].data[2].tooltip.formatter
<template #tooltip:series[3].data[2] />
  • media(baseOption): option.baseOption.tooltip.formatter
<template #tooltip:baseOption />
  • media: option[1].option.tooltip.formatter
<template #tooltip:[1].option />

@kingyue737
Copy link
Member Author

kingyue737 commented Jun 22, 2025

I feel the base functionality is mostly complete. However, it currently doesn't support dynamically changing slot names or adding/removing slots. With the current approach, implementing this might be tricky. For example:

<script setup>
// Work
const slotNames = ref(['tooltip', 'tooltip:series[0]'])

// Don't work
setTimeout(()=>{
  slotNames.value.push('tooltip:series[1]')
},1000)
</script>

<template>
  <v-chart>
    <template v-for="slotName in slotNames" #[slotName]={ params } >
        <!-- content -->
    </template>
  </v-chart>
</template>

Edit: Supported in the latest commit

@kingyue737 kingyue737 marked this pull request as ready for review June 23, 2025 15:39
Copy link
Member

@Justineo Justineo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the design and implementation is pretty good now. Left a few small questions.

@kingyue737 kingyue737 changed the title Rendering tooltips with slots Rendering tooltips and dataView with slots Jul 4, 2025
Copy link
Member

@Justineo Justineo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have another option: how do we support [contentToOption](https://echarts.apache.org/zh/option.html#toolbox.feature.dataView.contentToOption)?

@kingyue737
Copy link
Member Author

kingyue737 commented Jul 15, 2025

I have another option: how do we support [contentToOption](https://echarts.apache.org/zh/option.html#toolbox.feature.dataView.contentToOption)?

The usage of contentToOption remains unaffected. The root element of the slot content can still be accessed from the callback's first parameter. In fact, it is now more flexible since users can also use template refs from child elements instead of just the root element:

<script>
const input = useTemplateRef("input");

const option = {
  toolbox: {
    feature: {
      dataView: {
        contentToOption(_container) {
          return inputToOption(input);
        },
      },
    },
  },
};
</script>

<template>
  <v-chart :option="option">
    <template #dataView>
      <div>
        <div>title</div>
        <textarea ref="input" />
      </div>
    </template>
  </v-chart>
</template>

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a slot-based tooltip rendering system for vue-echarts, allowing developers to define tooltip content using Vue template syntax instead of the traditional ECharts formatter callbacks. The implementation includes support for both tooltip and dataView slots with a flexible naming convention.

  • Adds a new composable useSlotOption that handles slot-based rendering by teleporting content to detached DOM elements
  • Integrates slot functionality directly into the main ECharts component with automatic option patching
  • Provides utility functions for array index validation and set comparison

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/utils.ts Adds utility functions for array index validation and set comparison
src/composables/slot.ts New composable implementing the core slot rendering functionality
src/composables/index.ts Exports the new slot composable
src/ECharts.ts Integrates slot functionality into the main component with slot type definitions
demo/examples/LineChart.vue Demo implementation showcasing tooltip and dataView slots
demo/examples/Example.vue CSS selector specificity fix for nested charts
demo/data/line.js Test data for the line chart demo
demo/Demo.vue Adds the new line chart demo to the demo list
README.zh-Hans.md Chinese documentation for the new slot features
README.md English documentation for the new slot features

@kingyue737
Copy link
Member Author

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants