You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:24:57