Vite.js中react-financial-charts悬停效果失效的调试与优化
问题描述
基于react-financial-charts的示例代码,使用Vite.js重构后图表整体显示正常,但悬停(hover)效果完全失效,包括十字光标、OHLC提示框、均线提示框和Elder Ray提示框均无法触发。需要修复悬停交互问题,并对图表进行易用性和美观度优化。
提供的代码如下:
App.jsx
import React from "react"; import { format } from "d3-format"; import { timeFormat } from "d3-time-format"; import { elderRay, ema, discontinuousTimeScaleProviderBuilder, Chart, ChartCanvas, CurrentCoordinate, BarSeries, CandlestickSeries, ElderRaySeries, LineSeries, MovingAverageTooltip, OHLCTooltip, SingleValueTooltip, lastVisibleItemBasedZoomAnchor, XAxis, YAxis, CrossHairCursor, EdgeIndicator, MouseCoordinateX, MouseCoordinateY, ZoomButtons, withDeviceRatio, withSize } from "react-financial-charts"; import { initialData } from "./data.jsx"; const rotateArray2 = function (nums, k) { function reverse(arr, start, end) { while (start < end) { [arr[start], arr[end]] = [arr[end], arr[start]]; start++; end--; } } k %= nums.length; reverse(nums, 0, nums.length - 1); reverse(nums, 0, k - 1); reverse(nums, k, nums.length - 1); return nums; }; const App = () => { const ScaleProvider = discontinuousTimeScaleProviderBuilder().inputDateAccessor( (d) => new Date(d.date) ); const height = 700; const width = 900; const margin = { left: 0, right: 48, top: 0, bottom: 24 }; const ema12 = ema() .id(1) .options({ windowSize: 12 }) .merge((d, c) => { d.ema12 = c; }) .accessor((d) => d.ema12); const ema26 = ema() .id(2) .options({ windowSize: 26 }) .merge((d, c) => { d.ema26 = c; }) .accessor((d) => d.ema26); const elder = elderRay(); // 重复定义的函数,可删除 // const rotateArray2 = function (nums, k) { ... } const calculatedData = elder(ema26(ema12(rotateArray2(initialData)))); const { data, xScale, xAccessor, displayXAccessor } = ScaleProvider( rotateArray2(initialData) // 这里使用了原始数据,而非计算后的数据 ); const pricesDisplayFormat = format(".2f"); const max = xAccessor(data[data.length - 1]); const min = xAccessor(data[Math.max(0, 0)]); const xExtents = [min, max + 5]; const gridHeight = height - margin.top - margin.bottom; const elderRayHeight = 100; const elderRayOrigin = (_, h) => [0, h - elderRayHeight]; const barChartHeight = gridHeight / 4; const barChartOrigin = (_, h) => [0, h - barChartHeight - elderRayHeight]; const chartHeight = gridHeight - elderRayHeight; const dateTimeFormat = "%d %b"; const timeDisplayFormat = timeFormat(dateTimeFormat); const barChartExtents = (data) => { return data.volume; }; const candleChartExtents = (data) => { return [data.high, data.low]; }; const yEdgeIndicator = (data) => { return data.close; }; const volumeColor = (data) => { return data.close > data.open ? "rgba(38, 166, 154, 0.3)" : "rgba(239, 83, 80, 0.3)"; }; const volumeSeries = (data) => { return data.volume; }; const openCloseColor = (data) => { return data.close > data.open ? "#26a69a" : "#ef5350"; }; return ( <ChartCanvas height={height} ratio={3} width={width} margin={margin} data={data} // 同样传递了原始数据,缺少计算字段 displayXAccessor={displayXAccessor} seriesName="Data" xScale={xScale} xAccessor={xAccessor} xExtents={xExtents} zoomAnchor={lastVisibleItemBasedZoomAnchor} > <Chart id={2} height={barChartHeight} origin={barChartOrigin} yExtents={barChartExtents} > <BarSeries fillStyle={volumeColor} yAccessor={volumeSeries} /> </Chart> <Chart id={3} height={chartHeight} yExtents={candleChartExtents}> <XAxis showGridLines showTickLabel={false} /> <YAxis showGridLines tickFormat={pricesDisplayFormat} /> <CandlestickSeries /> <LineSeries yAccessor={ema26.accessor()} strokeStyle={ema26.stroke()} /> <CurrentCoordinate yAccessor={ema26.accessor()} fillStyle={ema26.stroke()} /> <LineSeries yAccessor={ema12.accessor()} strokeStyle={ema12.stroke()} /> <CurrentCoordinate yAccessor={ema12.accessor()} fillStyle={ema12.stroke()} /> <MouseCoordinateY rectWidth={margin.right} displayFormat={pricesDisplayFormat} /> <EdgeIndicator itemType="last" rectWidth={margin.right} fill={openCloseColor} lineStroke={openCloseColor} displayFormat={pricesDisplayFormat} yAccessor={yEdgeIndicator} /> <MovingAverageTooltip origin={[8, 24]} options={[ { yAccessor: ema26.accessor(), type: "EMA", stroke: ema26.stroke(), windowSize: ema26.options().windowSize }, { yAccessor: ema12.accessor(), type: "EMA", stroke: ema12.stroke(), windowSize: ema12.options().windowSize } ]} /> <ZoomButtons /> <OHLCTooltip origin={[8, 16]} /> </Chart> <Chart id={4} height={elderRayHeight} yExtents={[0, elder.accessor()]} origin={elderRayOrigin} padding={{ top: 8, bottom: 8 }} > <XAxis showGridLines gridLinesStrokeStyle="#e0e3eb" /> <YAxis ticks={4} tickFormat={pricesDisplayFormat} /> <MouseCoordinateX displayFormat={timeDisplayFormat} /> <MouseCoordinateY rectWidth={margin.right} displayFormat={pricesDisplayFormat} /> <ElderRaySeries yAccessor={elder.accessor()} /> <SingleValueTooltip yAccessor={elder.accessor()} yLabel="Elder Ray" yDisplayFormat={(d) => `${pricesDisplayFormat(d.bullPower)}, ${pricesDisplayFormat( d.bearPower )}` } origin={[8, 16]} /> </Chart> <CrossHairCursor /> </ChartCanvas> ); }; export default App;
main.jsx
import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.jsx' import './index.css' createRoot(document.getElementById('root')).render( <StrictMode> <App /> </StrictMode>, )
解决方案与优化
一、修复悬停交互失效问题
悬停失效的核心原因是ChartCanvas使用了未包含计算字段的原始数据,导致tooltip和十字光标无法读取EMA、Elder Ray等计算值:
1. 替换数据源
将ScaleProvider的参数和ChartCanvas的data属性改为calculatedData(即经过EMA和Elder Ray计算后的数组):
// 替换这部分代码 const calculatedData = elder(ema26(ema12(rotateArray2(initialData)))); const { data, xScale, xAccessor, displayXAccessor } = ScaleProvider(calculatedData); // 用calculatedData替代原始数据 // 同时修改ChartCanvas的data属性 <ChartCanvas ... data={calculatedData} // 传递计算后的数据 ... >
2. 启用高阶组件优化交互
导入的withDeviceRatio和withSize是react-financial-charts的核心交互优化组件,需要用它们包裹App组件,确保画布适配设备像素比和容器尺寸:
// 在App.jsx末尾导出时添加包裹 export default withDeviceRatio(withSize(App));
3. 移除重复定义的函数
删除App组件内重复定义的rotateArray2函数,避免变量冲突。
二、美观度与易用性优化
1. 统一主题样式
在index.css中添加全局样式,优化图表的视觉效果:
/* index.css */ .react-financial-charts { font-family: 'Segoe UI', Roboto, sans-serif; } /* 优化tooltip背景和样式 */ .tooltip { background-color: rgba(0, 0, 0, 0.8); color: #fff; padding: 8px 12px; border-radius: 4px; font-size: 12px; } /* 优化网格线样式 */ .grid-line { stroke: #e5e7eb; stroke-width: 0.5; } /* 调整坐标轴字体 */ .axis text { fill: #6b7280; font-size: 11px; }
2. 调整布局与交互细节
- 将
MouseCoordinateX移至主K线图(id=3的Chart)内,确保鼠标X坐标始终显示:<Chart id={3} height={chartHeight} yExtents={candleChartExtents}> ... <MouseCoordinateX displayFormat={timeDisplayFormat} /> ... </Chart> - 调整
CrossHairCursor的样式,增强视觉辨识度:<CrossHairCursor stroke="#9ca3af" strokeWidth={1} /> - 给
ZoomButtons添加位置偏移,避免遮挡K线:<ZoomButtons origin={[10, 10]} />
3. 响应式适配
使用withSize后,移除硬编码的width属性,让图表自适应容器宽度:
const App = ({ width }) => { // 从withSize注入width const height = 700; const margin = { left: 0, right: 48, top: 0, bottom: 24 }; ... <ChartCanvas height={height} ratio={3} width={width} // 使用注入的width ... > ... }
内容的提问来源于stack exchange,提问作者Loc
相关产品推荐
相关产品推荐

