英文:
How can I create a D3 transition animation to update a React component without re-rendering the whole plot?
问题
我有一个包装在React组件(v7)中的D3js绘图。例如,一个带有数据表和用于绘制的列参数的条形图。在更改绘图变量时,我不想重新渲染整个图,而是执行一个D3过渡动画来切换到新的变量。
现在,我已经尝试了以下存根代码,但首先我在尝试让初始图绘制时遇到了问题,其次我真的很想了解正确的React Hook方式来实现这一点...
import * as React from "react";
import * as d3 from "d3";
export function BarPlot({
data,
x,
width,
height,
}: {
data: DataTable;
x: string;
width: number;
height: number;
}) {
const svgRef = React.useRef<SVGSVGElement>(null);
const svg = d3.select(svgRef.current);
const prevX = React.useRef<string>(x);
if (svgRef.current === null) {
svg.selectAll("*").remove();
svg
.append("g")
.selectAll()
.data(data)
// …
// 正常的D3绘图代码在这里
// …
}
if (prevX.current !== x) {
// 更新图表,将旧条形图切换到新条形图进行动画过渡
prevX.current = x;
}
return <svg ref={svgRef} width={width} height={height} />;
}
当寻找正确的React方式来实现这一点时,似乎useEffect
不是正确的选择。我还尝试使用useMemo
来保存初始图表,但即使在那种情况下,我也需要手动检查过渡参数是否发生了变化...
总的来说,我认为问题是如何创建一个React组件,其中部分渲染代码在初始时执行,只有在已经渲染的组件中的某个属性发生更改时才执行另一部分。
英文:
I have a D3js Plot wrapped inside a React component (v7). For example a Bar Plot with a data table and a parameter for which column to plot. On change of the plotting variable, I do not want to re-render the whole plot but instead execute a D3 transition animation to the new variable.
Right now I have tried it following the stump-code here, but first I have problems getting the initial plot to render and second I really would like to understand what the correct React hook way is to achieve this…
import * as React from "react";
import * as d3 from "d3";
export function BarPlot({
data,
x,
width,
height,
}: {
data: DataTable;
x: string;
width: number;
height: number;
}) {
const svgRef = React.useRef<SVGSVGElement>(null);
const svg = d3.select(svgRef.current);
const prevX = React.useRef<string>(x);
if (svgRef.current === null) {
svg.selectAll("*").remove();
svg
.append("g")
.selectAll()
.data(data)
// …
// Normal d3 plotting code here
// …
}
if (prevX.current !== x) {
// Update the plot, animate the transition from plotting the old bars to the new bars
prevX.current = x;
}
return <svg ref={svgRef} width={width} height={height} />;
}
When looking around for the correct React-way to do this, it seems useEffect
is not the right choice here. I also tried to use useMemo
to save the inital plot, but even then I need to manually check whether the transitionable parameters have changed…
Abstract, I think the question is how to have a React component, where part of the render code is executed intially and another part only if the already rendered component has a change in one of the props.
答案1
得分: 1
以下是使用React和D3创建动画条形图的示例。
只需在SVG元素的ref上添加一个useEffect
,并在ref有效时构建图表(即组件挂载时)。
const MAX_VALUE = 200;
const BarChart = ({ data, height, width }) => {
const svgRef = React.useRef(null);
React.useEffect(() => {
const svg = d3.select(svgRef.current);
const xScale = d3.scaleBand()
.domain(data.map((value, index) => index.toString()))
.range([0, width])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, MAX_VALUE])
.range([height, 0]);
const xAxis = d3.axisBottom(xScale)
.ticks(data.length)
.tickFormat((_, index) => data[index].label);
svg
.select("#x-axis")
.style("transform", `translateY(${height}px)`)
.style("font-size", '16px')
.call(xAxis);
const yAxis = d3.axisLeft(yScale);
svg
.select("#y-axis")
.style("font-size", '16px')
.call(yAxis)
svg.selectAll('g.tick');
const bars = svg
.selectAll(".bar")
.data(data)
.join("g")
.classed("bar", true);
bars.append("rect")
.style("transform", "scale(1, -1)")
.attr("x", (_, index) => xScale(index.toString()))
.attr("y", -height)
.attr("width", xScale.bandwidth())
.transition()
.delay((_, index) => index * 500)
.duration(1000)
.attr("fill", d => d.color)
.attr("height", (d) => height - yScale(d.value));
}, [data]);
return (
<svg ref={svgRef} height={height} width={width} />
);
};
const data = [
{value: 50, color: '#008'},
{value: 100, color: '#00C'},
{value: 150, color: '#00f'}
];
ReactDOM.render(
<BarChart data={data} width={300} height={170} />,
document.getElementById("chart")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.4/d3.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id='chart'></div>
英文:
Here is an example of animated bar chart using React with D3.
Just add a useEffect
on the SVG element ref and build the chart when ref is valid (when the component is mounted)
<!-- begin snippet: js hide: false console: false babel: true -->
<!-- language: lang-js -->
const MAX_VALUE = 200;
const BarChart = ({ data, height, width }) => {
const svgRef = React.useRef(null);
React.useEffect(() => {
const svg = d3.select(svgRef.current);
const xScale = d3.scaleBand()
.domain(data.map((value, index) => index.toString()))
.range([0, width])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, MAX_VALUE])
.range([height, 0]);
const xAxis = d3.axisBottom(xScale)
.ticks(data.length)
.tickFormat((_, index) => data[index].label);
svg
.select("#x-axis")
.style("transform", `translateY(${height}px)`)
.style("font-size", '16px')
.call(xAxis);
const yAxis = d3.axisLeft(yScale);
svg
.select("#y-axis")
.style("font-size", '16px')
.call(yAxis)
svg.selectAll('g.tick');
const bars = svg
.selectAll(".bar")
.data(data)
.join("g")
.classed("bar", true);
bars.append("rect")
.style("transform", "scale(1, -1)")
.attr("x", (_, index) => xScale(index.toString()))
.attr("y", -height)
.attr("width", xScale.bandwidth())
.transition()
.delay((_, index) => index * 500)
.duration(1000)
.attr("fill", d => d.color)
.attr("height", (d) => height - yScale(d.value));
}, [data]);
return (
<svg ref={svgRef} height={height} width={width} />
);
};
const data = [
{value: 50, color: '#008'},
{value: 100, color: '#00C'},
{value: 150, color: '#00f'}
];
ReactDOM.render(
<BarChart data={data} width={300} height={170} />,
document.getElementById("chart")
);
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.4/d3.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id='chart'></div>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论