在 React 应用程序中使用 D3.js
介绍
React 和 D3.js 是两个非常流行的 JavaScript 库。React 是一个用于构建动态用户界面的库,而 D3.js 用于创建交互式、数据驱动的可视化效果(例如图表)。
尽管这两个库都被广泛使用,但集成它们仍存在一些挑战。这是因为:
- 随着时间的推移,这两个库都发生了很大的变化和发展,
- 两个库都希望直接访问文档对象模型 (DOM)
在本指南中,您将学习如何集成这两个库。您将学习如何将 D3.js 逻辑封装在图表组件中;然后我们将讨论如何确保图表在新数据到达时得到更新。
本指南将使用Mike Bostock 的简化版条形图作为示例 D3.js 图表,以将其集成到您的应用中。我们将重点关注集成点,而不是 React 和 D3.js 的实现细节。
开始一个新的 React 项目
首先通过运行以下命令创建一个新的 React 项目:
npx create-react-app my-app
cd my-app
yarn add d3
yarn start
这些命令将建立一个安装了 D3.js 的新 React 项目。
创建条形图组件
React 使用组件作为构建用户界面的构建块。因此,您的 D3.js 图表也应建模为可添加到网站不同部分的组件。
为此,创建一个名为src/BarChart.js的新文件。在此文件中,添加以下样板。
import React from 'react';
import * as d3 from 'd3';
function BarChart = () => {
return (
<svg
style={{
height: 500,
width: "100%",
marginRight: "0px",
marginLeft: "0px",
}}
>
<g className="plot-area" />
<g className="x-axis" />
<g className="y-axis" />
</svg>
);
}
export default BarChart;
该样板建立了一个基本支架,用于渲染带有绘图区域和图表轴的svg 。
创建自定义钩子来使用 D3js
React 钩子是添加命令式逃生舱以允许 D3.js 直接与 DOM 交互的一种方法。在上一步中,您使用标准 JSX 渲染svg元素作为起点。从这里开始,您可以利用 useRef和useEffect钩子将 D3.js 与已创建的svg元素链接起来,并指定何时执行 D3.js 函数。
按照惯例,钩子以use为前缀,因此你应该将新钩子命名为useD3 。在src/hooks/useD3.js中创建一个新文件并添加以下函数。
import React from 'react';
import * as d3 from 'd3';
export const useD3 = (renderChartFn, dependencies) => {
const ref = React.useRef();
React.useEffect(() => {
renderChartFn(d3.select(ref.current));
return () => {};
}, dependencies);
return ref;
}
此函数是一个钩子,它接受两个参数:
- renderChartFn是一个回调,其中包含要执行的 D3.js 代码
- dependency是一个固定长度的数组,用于告诉 React 何时运行 renderChartFn 。这对于防止不必要的重新渲染并在新数据到达时正确更新图表很有用。
在 BarChart 组件中使用自定义钩子
React 钩子只是函数,因此可以直接从BarChart组件调用它们。下面演示了使用该钩子的BarChart组件的更新版本。
下面的代码通过提供两个参数并将钩子返回的ref与svg元素链接起来来使用钩子。
import { useD3 } from './hooks/useD3';
import React from 'react';
import * as d3 from 'd3';
function BarChart({ data }) {
const ref = useD3(
(svg) => {
const height = 500;
const width = 500;
const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const x = d3
.scaleBand()
.domain(data.map((d) => d.year))
.rangeRound([margin.left, width - margin.right])
.padding(0.1);
const y1 = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.sales)])
.rangeRound([height - margin.bottom, margin.top]);
const xAxis = (g) =>
g.attr("transform", `translate(0,${height - margin.bottom})`).call(
d3
.axisBottom(x)
.tickValues(
d3
.ticks(...d3.extent(x.domain()), width / 40)
.filter((v) => x(v) !== undefined)
)
.tickSizeOuter(0)
);
const y1Axis = (g) =>
g
.attr("transform", `translate(${margin.left},0)`)
.style("color", "steelblue")
.call(d3.axisLeft(y1).ticks(null, "s"))
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(data.y1)
);
svg.select(".x-axis").call(xAxis);
svg.select(".y-axis").call(y1Axis);
svg
.select(".plot-area")
.attr("fill", "steelblue")
.selectAll(".bar")
.data(data)
.join("rect")
.attr("class", "bar")
.attr("x", (d) => x(d.year))
.attr("width", x.bandwidth())
.attr("y", (d) => y1(d.sales))
.attr("height", (d) => y1(0) - y1(d.sales));
},
[data.length]
);
return (
<svg
ref={ref}
style={{
height: 500,
width: "100%",
marginRight: "0px",
marginLeft: "0px",
}}
>
<g className="plot-area" />
<g className="x-axis" />
<g className="y-axis" />
</svg>
);
}
export default BarChart;
提供给useD3钩子的第一个参数改编自Mike Bostock 的条形图示例。在上面的代码片段中,它将组件属性中的数据变量连接到图表元素。请注意,该函数不直接使用.append,因为图表渲染函数可以运行多次,我们希望避免创建重复的元素。相反,它使用选择连接来确定应该创建、删除或修改哪些元素。
第二个参数是一个依赖项数组,React 会检查该数组以检查是否需要重新执行chartRenderFn 。在本例中,我们使用[data.length] 。如果添加或删除了数据项,这将导致 React 更新图表。如果您只是将整个数据数组[data]传递给依赖项参数,您可能会发现您的函数执行次数过多。这是因为React默认对依赖项数组中的所有元素进行浅层比较。在修改数据项的其他场景中,您可能需要将此参数更改为内容哈希或上次更新的时间戳,以便正确更新图表。
使用新的 BarChart 组件
您已创建 BarChart 组件。它以数据作为 props 来渲染BarChart。要查看其实际效果,请将src/App.js文件修改为:
import React from 'react';
import BarChart from './BarChart';
import './App.css';
const data = [
{year: 1980, efficiency: 24.3, sales: 8949000},
{year: 1985, efficiency: 27.6, sales: 10979000},
{year: 1990, efficiency: 28, sales: 9303000},
{year: 1991, efficiency: 28.4, sales: 8185000},
{year: 1992, efficiency: 27.9, sales: 8213000},
{year: 1993, efficiency: 28.4, sales: 8518000},
{year: 1994, efficiency: 28.3, sales: 8991000},
{year: 1995, efficiency: 28.6, sales: 8620000},
{year: 1996, efficiency: 28.5, sales: 8479000},
{year: 1997, efficiency: 28.7, sales: 8217000},
{year: 1998, efficiency: 28.8, sales: 8085000},
{year: 1999, efficiency: 28.3, sales: 8638000},
{year: 2000, efficiency: 28.5, sales: 8778000},
{year: 2001, efficiency: 28.8, sales: 8352000},
{year: 2002, efficien
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~