【发布时间】:2021-08-19 05:36:14
【问题描述】:
我在 react 和 d3 中有以下条形图代码(遵循声明性方式)。我想在第一次渲染条形和轴时对其进行动画处理,然后在数据更改时设置动画,例如使用命令式方式使用 d3 api(使用 React,我希望以声明性方式保持内容)。到目前为止,我设法在第一次渲染时使用react-spring 设置了动画条,当我通过单击删除条时,调整了条的宽度。但是,我不确定如何在数据更新时为轴设置动画。我也不确定如何在其中一个条被删除后为条的位置设置动画。我想我可能需要一个 useRef 数组来表示 x 位置、高度和轴的刻度,但不知道该怎么做。
总结一下我的问题:
- 如何在数据更新时为条形的 x 位置和高度设置动画?
- 如何在数据更新时为坐标区的刻度和标签设置动画?
数据.json
[
{
"id": 0,
"country": "China",
"population": 1400000000
},
{
"id": 1,
"country": "India",
"population": 1200000000
},
{
"id": 2,
"country": "USA",
"population": 450000000
}
]
App.js
import BarChart from './components/BarChart';
function App() {
return <BarChart />;
}
export default App;
BarChart.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
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
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>
));
编辑:这是codesandbox上的代码
【问题讨论】:
-
你能准备一个工作沙箱并发布一个链接吗?
-
@MichaelRovinsky 我将代码添加到代码沙箱中,并将链接包含在我的问题中。
-
刚刚检查了您的沙箱。每次更改
BarChart组件中的数据时,都需要重新创建xScale和yScale:xScale = useMemo(..., [data]) -
@MichaelRovinsky 这在我更新数据时会自动发生,因为数据是一个状态变量。 xScale 和 yScale 正确更新,轴刻度和标签正确更改。我的问题是如何动画轴刻度和标签的变化。在d3 api中我们使用
.transition().duration(),但是在React中当我们使用声明式的方式时,如何实现这个转换呢?我实现了带宽的转换,因为它对所有条形都是固定的。但其他过渡似乎很棘手。
标签: javascript reactjs d3.js