[英]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:总结我的问题:
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.