Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .cursor/rules/specify-rules.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Auto-generated from all feature plans. Last updated: 2026-01-15

## Active Technologies
- TypeScript/React 18 + @visactor/react-vchart, @visactor/vchar (001-react-vchart-demo)

- TypeScript 4.9.5 + @visactor/vchart, @visactor/vrender-components (~1.0.37), @visactor/vutils (001-scrollbar-wheel-step)

Expand All @@ -22,6 +23,8 @@ npm test && npm run lint
TypeScript 4.9.5: Follow standard conventions

## Recent Changes
- 001-react-vchart-demo: Added TypeScript/React 18 + @visactor/react-vchart, @visactor/vchar
- 001-react-vchart-demo: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]

- 001-scrollbar-wheel-step: Added TypeScript 4.9.5 + @visactor/vchart, @visactor/vrender-components (~1.0.37), @visactor/vutils

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: fix style of react-vchart tooltip render\n\n",
"type": "none",
"packageName": "@visactor/vchart"
}
],
"packageName": "@visactor/vchart",
"email": "dingling112@gmail.com"
}
132 changes: 132 additions & 0 deletions docs/assets/examples-react/en/component/react-common-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
category: examples
group: component
title: React Common Chart With Custom Tooltip
keywords: commonChart,composition,tooltip,legend
order: 0-5
cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vchart/preview/theme/theme-switch.png
option: commonChart#theme
---

# React Common Chart With Custom Tooltip

## Live Demo

```javascript livedemo template=react-vchart
const root = document.getElementById(CONTAINER_ID);
const { CommonChart } = ReactVChart;
const { useRef, useEffect } = React;

const data0 = [
{ time: '2:00', value: 8, type: 'Douyin' },
{ time: '4:00', value: 9, type: 'Douyin' },
{ time: '6:00', value: 11, type: 'Douyin' },
{ time: '8:00', value: 14, type: 'Douyin' },
{ time: '10:00', value: 16, type: 'Douyin' },
{ time: '12:00', value: 17, type: 'Douyin' },
{ time: '14:00', value: 17, type: 'Douyin' },
{ time: '16:00', value: 16, type: 'Douyin' },
{ time: '18:00', value: 15, type: 'Douyin' },
{ time: '2:00', value: 7, type: 'Bilibili' },
{ time: '4:00', value: 8, type: 'Bilibili' },
{ time: '6:00', value: 9, type: 'Bilibili' },
{ time: '8:00', value: 10, type: 'Bilibili' },
{ time: '10:00', value: 9, type: 'Bilibili' },
{ time: '12:00', value: 12, type: 'Bilibili' },
{ time: '14:00', value: 14, type: 'Bilibili' },
{ time: '16:00', value: 12, type: 'Bilibili' },
{ time: '18:00', value: 14, type: 'Bilibili' }
];

const data1 = [
{ time: '2:00', value: 15, type: 'Total' },
{ time: '4:00', value: 17, type: 'Total' },
{ time: '6:00', value: 20, type: 'Total' },
{ time: '8:00', value: 24, type: 'Total' },
{ time: '10:00', value: 25, type: 'Total' },
{ time: '12:00', value: 29, type: 'Total' },
{ time: '14:00', value: 31, type: 'Total' },
{ time: '16:00', value: 28, type: 'Total' },
{ time: '18:00', value: 29, type: 'Total' }
];

const spec = {
type: 'common',
data: [
{ id: 'id0', values: data0 },
{ id: 'id1', values: data1 }
],
series: [
{
type: 'bar',
dataIndex: 0,
xField: ['time', 'type'],
yField: 'value',
seriesField: 'type'
},
{
type: 'line',
dataIndex: 1,
xField: 'time',
yField: 'value',
seriesField: 'type'
}
],
axes: [
{
orient: 'bottom',
type: 'band'
},
{
orient: 'left',
type: 'linear'
}
],
legends: {
visible: true,
orient: 'right'
},
tooltip: {
visible: true,
reserveDefaultTooltip: false,
enterable: true,
tooltipRender: actualTooltip => (
<div style={{ textAlign: 'center', marginTop: 5 }}>
<button>🔥 {actualTooltip.title.value}</button>
</div>
)
}
};

const Card = () => {
const chartRef = useRef(null);
useEffect(() => {
window['vchart'] = chartRef;
}, []);

const handleLegendItemClick = params => {
console.log(params);
};

return <CommonChart ref={chartRef} spec={spec} onLegendItemClick={handleLegendItemClick} />;
};

ReactDom.createRoot(root).render(<Card />);

window.customRelease = () => {
ReactDom.unmountComponentAtNode(root);
};
```

## Interaction Notes

- Clicking a legend item triggers a callback for tracking or series filtering
- Tooltip renders a custom React button component

## Copy and Cleanup

After copying the demo locally, call `window.customRelease()` to unmount the chart instance.

## Tutorial

[Theme](link)
7 changes: 7 additions & 0 deletions docs/assets/examples-react/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
"zh": "带自定义 tooltip 的组合图"
}
},
{
"path": "react-common-demo",
"title": {
"en": "React Common Chart With Custom Tooltip",
"zh": "React 组合图自定义 Tooltip"
}
},
{
"path": "clock",
"title": {
Expand Down
132 changes: 132 additions & 0 deletions docs/assets/examples-react/zh/component/react-common-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
category: examples
group: component
title: React 组合图自定义 Tooltip
keywords: commonChart,composition,tooltip,legend
order: 0-5
cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vchart/preview/theme/theme-switch.png
option: commonChart#theme
---

# React 组合图自定义 Tooltip

## 代码演示

```javascript livedemo template=react-vchart
const root = document.getElementById(CONTAINER_ID);
const { CommonChart } = ReactVChart;
const { useRef, useEffect } = React;

const data0 = [
{ time: '2:00', value: 8, type: 'Douyin' },
{ time: '4:00', value: 9, type: 'Douyin' },
{ time: '6:00', value: 11, type: 'Douyin' },
{ time: '8:00', value: 14, type: 'Douyin' },
{ time: '10:00', value: 16, type: 'Douyin' },
{ time: '12:00', value: 17, type: 'Douyin' },
{ time: '14:00', value: 17, type: 'Douyin' },
{ time: '16:00', value: 16, type: 'Douyin' },
{ time: '18:00', value: 15, type: 'Douyin' },
{ time: '2:00', value: 7, type: 'Bilibili' },
{ time: '4:00', value: 8, type: 'Bilibili' },
{ time: '6:00', value: 9, type: 'Bilibili' },
{ time: '8:00', value: 10, type: 'Bilibili' },
{ time: '10:00', value: 9, type: 'Bilibili' },
{ time: '12:00', value: 12, type: 'Bilibili' },
{ time: '14:00', value: 14, type: 'Bilibili' },
{ time: '16:00', value: 12, type: 'Bilibili' },
{ time: '18:00', value: 14, type: 'Bilibili' }
];

const data1 = [
{ time: '2:00', value: 15, type: 'Total' },
{ time: '4:00', value: 17, type: 'Total' },
{ time: '6:00', value: 20, type: 'Total' },
{ time: '8:00', value: 24, type: 'Total' },
{ time: '10:00', value: 25, type: 'Total' },
{ time: '12:00', value: 29, type: 'Total' },
{ time: '14:00', value: 31, type: 'Total' },
{ time: '16:00', value: 28, type: 'Total' },
{ time: '18:00', value: 29, type: 'Total' }
];

const spec = {
type: 'common',
data: [
{ id: 'id0', values: data0 },
{ id: 'id1', values: data1 }
],
series: [
{
type: 'bar',
dataIndex: 0,
xField: ['time', 'type'],
yField: 'value',
seriesField: 'type'
},
{
type: 'line',
dataIndex: 1,
xField: 'time',
yField: 'value',
seriesField: 'type'
}
],
axes: [
{
orient: 'bottom',
type: 'band'
},
{
orient: 'left',
type: 'linear'
}
],
legends: {
visible: true,
orient: 'right'
},
tooltip: {
visible: true,
reserveDefaultTooltip: false,
enterable: true,
tooltipRender: actualTooltip => (
<div style={{ textAlign: 'center', marginTop: 5 }}>
<button>🔥 {actualTooltip.title.value}</button>
</div>
)
}
};

const Card = () => {
const chartRef = useRef(null);
useEffect(() => {
window['vchart'] = chartRef;
}, []);

const handleLegendItemClick = params => {
console.log(params);
};

return <CommonChart ref={chartRef} spec={spec} onLegendItemClick={handleLegendItemClick} />;
};

ReactDom.createRoot(root).render(<Card />);

window.customRelease = () => {
ReactDom.unmountComponentAtNode(root);
};
```

## 交互说明

- 点击图例项会触发回调,可用于埋点或过滤系列
- Tooltip 使用自定义 React 组件渲染按钮

## 复制与卸载

复制代码到本地运行后,如需卸载示例,可调用 `window.customRelease()` 释放图表实例。

## 相关教程

[主题](link)
4 changes: 3 additions & 1 deletion packages/react-vchart/demo/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserConfig, defineConfig } from 'vite';
import type { UserConfig } from 'vite';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

Expand Down Expand Up @@ -26,6 +27,7 @@ export default defineConfig({
alias: {
'@visactor/vchart': path.resolve(__dirname, '../../vchart/src/index.ts'),
'@visactor/vutils-extension': path.resolve(__dirname, '../../vutils-extension/src/index.ts'),
'@visactor/vchart-extension': path.resolve(__dirname, '../../vchart-extension/src/index.ts'),
...localConf.resolve?.alias
}
}
Expand Down
21 changes: 17 additions & 4 deletions packages/react-vchart/src/components/tooltip/util.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ export const initCustomTooltip = (
props: BaseChartProps,
spec?: TooltipProps
) => {
const nextSpec = { ...(spec ?? {}) } as TooltipProps;
let render: TooltipRender;
if (spec?.tooltipRender) {
render = spec.tooltipRender;
delete spec.tooltipRender;
delete nextSpec.tooltipRender;
} else if (props.tooltipRender) {
render = props.tooltipRender;
}
Expand All @@ -24,12 +25,25 @@ export const initCustomTooltip = (
let reserve: boolean;
if (spec?.reserveDefaultTooltip) {
reserve = spec.reserveDefaultTooltip;
delete spec.reserveDefaultTooltip;
delete nextSpec.reserveDefaultTooltip;
} else {
reserve = props.reserveDefaultTooltip;
}
if (!reserve) {
const panelStyle = { ...(nextSpec.style?.panel ?? {}) };
panelStyle.padding = 0;
panelStyle.border = {
...panelStyle.border,
radius: 0
};
panelStyle.backgroundColor = 'unset';
nextSpec.style = {
...(nextSpec.style ?? {}),
panel: panelStyle
};
}
return {
...spec,
...nextSpec,
updateElement: (el, actualTooltip, params) => {
const { changePositionOnly } = params;
if (changePositionOnly) {
Expand All @@ -39,7 +53,6 @@ export const initCustomTooltip = (
el.style.width = 'auto';
el.style.height = 'auto';
el.style.minHeight = 'auto';
el.style.padding = '0px';
for (let i = 0; i < el.children.length; i++) {
const childNode = el.children[i] as HTMLElement;
if (childNode.className !== REACT_TOOLTIP_ClASS_NAME && childNode.style.display !== 'none') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const getPanelStyle = (
panelStyle.borderRadius = isValidNumber(radius) ? `${radius}px` : `${radius}`;
}

if (padding) {
if (isValid(padding)) {
panelPadding = normalizePadding(padding);
panelStyle.padding = getPixelPropertyStr(panelPadding);
}
Expand Down
Loading
Loading