简体   繁体   English

使用 d3 动画条形图并做出反应

[英]Animating bar chart using d3 and react

I have the following bar chart code in react and d3 (following the declarative way).我在 react 和 d3 中有以下条形图代码(遵循声明性方式)。 I would like to animate the bars and axes when they are first rendered, and after that whenever the data change, like using d3 api using the imperative way (with React I would like to keep things the declarative way).我想在第一次渲染时为条形和轴设置动画,然后每当数据发生变化时,例如使用命令式方式使用 d3 api (使用 React,我想保持声明性方式)。 So far I managed to animate the bars using react-spring when they are first rendered, and when I delete a bar by clicking on it, the width of the bars is adjusted.到目前为止,我设法在第一次渲染时使用react-spring为这些栏设置动画,当我通过单击删除一个栏时,调整了栏的宽度。 However, I am not sure how to animate the axes when the data is updated.但是,我不确定如何在数据更新时为轴设置动画。 I am also not sure how to animate the position of the bars after one of them is deleted.我也不确定如何在其中一个被删除后为条形的 position 设置动画。 I am thinking I may need an array of useRef for the x position, height and axes's ticks, but not sure how to go about it.我在想我可能需要一个用于 x position、高度和轴刻度的useRef数组,但不确定如何 go 关于它。

To summarize my questions:总结我的问题:

  1. How can animate the x position and height of the bars when the data is updated?更新数据时,如何为 x position 和条形高度设置动画?
  2. How can I animate the axes's ticks and labels when the data is updated?如何在数据更新时为轴的刻度和标签设置动画?

data.json数据.json

[
  {
    "id": 0,
    "country": "China",
    "population": 1400000000
  },
  {
    "id": 1,
    "country": "India",
    "population": 1200000000
  },
  {
    "id": 2,
    "country": "USA",
    "population": 450000000
  }
]

App.js应用程序.js

import BarChart from './components/BarChart';

function App() {
  return <BarChart />;
}

export default App;

BarChart.js条形图.js

import { useEffect, useRef } from 'react';
import { scaleBand, scaleLinear, max, format } from 'd3';
import initialData from '../data/data.json';
import { AxisBottom, AxisLeft } from './Axes';
import AnimatedMarks from './AnimatedMarks';
import Marks from './Marks';
import { useState } from 'react';

const svgDimensions = {
  width: 600,
  height: 600,
};

const margins = {
  left: 100,
  top: 20,
  right: 20,
  bottom: 100,
};

const gDimensions = {
  width: svgDimensions.width - margins.left - margins.right,
  height: svgDimensions.height - margins.top - margins.bottom,
};

const xValue = (d) => d.country;
const yValue = (d) => d.population;

const BarChart = () => {
  const [data, setData] = useState(initialData);

  const yScale = scaleLinear()
    .domain([0, max(data, yValue)])
    .range([gDimensions.height, 0]);

  const xScale = scaleBand()
    .domain(data.map(xValue))
    .range([0, gDimensions.width])
    .paddingInner(0.1)
    .paddingOuter(0.1);

  const prevBandwidth = useRef(xScale.bandwidth());
  

  useEffect(() => {
    prevBandwidth.current = xScale.bandwidth();
  }, [data]);

  c

  const deleteMarkOnClick = (i) => {
    setData(data.filter((d) => d.id !== i));
  };

  return (
    <svg width={svgDimensions.width} height={svgDimensions.height}>
      <g
        width={gDimensions.width}
        height={gDimensions.height}
        transform={`translate(${margins.left},${margins.top})`}
      >
        <AxisLeft
          yScale={yScale}
          tickFormat={(n) => format('.2s')(n).replace('G', 'B')}
        />

        <AxisBottom xScale={xScale} gDimensions={gDimensions} />

        <text
          className='xAxis-label'
          x={gDimensions.width / 2}
          y={gDimensions.height + 50}
        >
          COUNTRY
        </text>

        {data.map((d) => (
          <AnimatedMarks
            prevBandwidth={prevBandwidth}
            deleteMarkOnClick={deleteMarkOnClick}
            d={d}
            xScale={xScale}
            yScale={yScale}
            gDimensions={gDimensions}
            xValue={xValue}
            yValue={yValue}
          />
        ))}
      </g>
    </svg>
  );
};

export default BarChart;

AnimatedMarks.js AnimatedMarks.js

import { useEffect, useRef } from 'react';
import { useSpring, animated } from 'react-spring';
const AnimatedMarks = ({
  d,
  xScale,
  yScale,
  gDimensions,
  xValue,
  yValue,
  deleteMarkOnClick,
  prevBandwidth,
}) => {
  const style = useSpring({
    config: {
      duration: 1000,
    },
    from: {
      width: prevBandwidth.current,
      y: gDimensions.height,
      height: 0,
      opacity: 0,
    },
    to: {
      width: xScale.bandwidth(),
      y: yScale(yValue(d)),
      height: gDimensions.height - yScale(yValue(d)),
      opacity: 1,
    },
  });

  return (
    <animated.rect
      onClick={() => deleteMarkOnClick(d.id)}
      className='mark'
      key={d.id}
      x={xScale(xValue(d))}
      // y={yScale(yValue(d))}
      // width={xScale.bandwidth()}
      {...style}
      // height={gDimensions.height - yScale(yValue(d))}
    />
  );
};

export default AnimatedMarks;

Axes.js轴.js

export const AxisBottom = ({ xScale, gDimensions }) =>
  xScale.domain().map((xTickValue) => (
    <g
      className='tick'
      key={xTickValue}
      transform={`translate(${xScale(xTickValue) + xScale.bandwidth() / 2},${
        gDimensions.height + 20
      })`}
    >
      <line y1='-15' y2='-20' />
      <text>{xTickValue}</text>
    </g>
  ));

export const AxisLeft = ({ yScale, tickFormat }) =>
  yScale.ticks().map((yTickValue) => (
    <g
      className='tick'
      key={yTickValue}
      transform={`translate(0,${yScale(yTickValue)})`}
    >
      <line x1='5' x2='15' stroke='black' />
      <text dy='0.32em' style={{ textAnchor: 'end' }}>
        {tickFormat(yTickValue)}
      </text>
    </g>
  ));

EDIT: This is the code on codesandbox编辑:这是codesandbox上的代码

Here is an example of calling D3 transition in a React component with useEffect :下面是使用useEffect在 React 组件中调用 D3 转换的示例:

 const {useRef, useState, useEffect} = React; const Chart = () => { const [rectWidth, setRectWidth] = useState(100); const svgRef = useRef(); useEffect(() => { setInterval(() => { const width = 50 + Math.random() * 200; setRectWidth(width); }, 1000); }, []); useEffect(() => { if (svgRef.current) { d3.select(svgRef.current).select('rect').transition().duration(500).attr('width', rectWidth) } }, [rectWidth, svgRef]); return ( <svg ref={svgRef} width={300} height={100}> <rect x={10} y={10} height={20} /> </svg> ) } ReactDOM.render(<Chart />, document.querySelector("#chart"))
 svg { background-color: white; } rect { stroke: none; fill: green; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script> <div id="chart"></div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM