使用 D3 在 React 中绘制图表
介绍
D3 是 JavaScript 中最常用的可视化框架之一。对于具有原生 JS 背景的开发人员来说,D3 可能除了日常可视化之外还有用。但对于 React 开发人员来说,由于 React 和 D3 处理 DOM 的方式,D3 的优势并非立竿见影。本指南探讨了如何在不中断 React 机制的情况下将 D3 与 React 集成并最大化其优势。所有讨论的示例均可在Github repo中找到,供您参考。
简单的 D3 条形图
实际上,当您需要 D3 可视化时,您可能会修改 D3 文档或博客中的现有代码示例。按照相同的方法,您将首先浏览纯 D3 中的简单条形图示例。然后,您将探索将同一代码示例集成到 React 中的方法。
下面的 D3 代码执行以下操作:
- 首先,它将一个svg附加到文档,稍后将用作绘制图表的画布。
- 然后它调整svg元素的大小并绘制边框。
- 后面的代码使用了D3 的进入-更新-退出模式。如果您不熟悉这些概念,请查看此可视化。
- 使用该模式,D3 将首先为数据集中的任何新数据点绘制条形图。如果这是第一个加载的数据集,则所有数据点都将被视为新数据点。
- 然后它将更新任何现有的条形图以匹配新的数据点。
- 最后,它将从图表中删除所有多余的条形。
// chart.html
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div>
<button id="btn" onclick="changeData()">Change Data</button>
</div>
<script>
var width = 600;
var height = 400;
var datasets = [
[10, 30, 40, 20],
[10, 40, 30, 20, 50, 10],
[60, 30, 40, 20, 30]
]
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
function drawChart(data) {
var selection = svg.selectAll("rect").data(data);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, height-100]);
selection
.transition().duration(300)
.attr("height", (d) => yScale(d))
.attr("y", (d) => height - yScale(d))
selection
.enter()
.append("rect")
.attr("x", (d, i) => i * 45)
.attr("y", (d) => height)
.attr("width", 40)
.attr("height", 0)
.attr("fill", "orange")
.transition().duration(300)
.attr("height", (d) => yScale(d))
.attr("y", (d) => height - yScale(d))
selection
.exit()
.transition().duration(300)
.attr("y", (d) => height)
.attr("height", 0)
.remove()
}
var i = 0;
function changeData(){
drawChart(datasets[i++]);
if(i == datasets.length) i = 0;
}
window.addEventListener('load', function() {
changeData();
});
</script>
</body>
</html>
您可以通过直接在浏览器中打开chart.html来运行上述代码。单击“更改数据”按钮时,图表会通过添加和删除条形图以及平滑地更改现有条形图的高度来更新。D3 过渡提供了这些流畅的动画。
考虑到上图,你可以从 React/D3 集成中获得以下功能:
- 在同一页面上绘制多个图表而不互相影响。
- 轻松使用现有的 D3 代码。
- 刷新数据集。
- 当数据集发生变化时,使用动画平滑地更新图表。
- 重复使用图表组件,对父组件的修改很少。
React 和 D3
将 D3 与 React 集成的主要障碍是每个库处理 DOM 的方式存在冲突。React 使用虚拟 DOM的概念,而不直接接触实际 DOM。另一方面,D3 通过直接与 DOM 交互来提供自己的一组功能。因此,将 D3 与 React 组件集成可能会导致组件功能出现错误。
为了防止这种情况,请确保 React 和 D3 可以在各自的空间中工作。例如,如果您正在创建管理仪表板,请确保 React 管理除图表内部内容之外的每个前端方面,包括导航、按钮、表格等。但是当涉及到图表时,组件及其方面的控制权应移交给 D3。这包括转换、数据更新、渲染、鼠标交互等。
您可以尝试使用基本的 D3 示例让 React 和 D3 协同工作。在此之前,请创建一个新的 React 应用程序。
使用npm install --save d3安装 D3 。在src下创建一个charts目录,用于存储所有与 D3 相关的文件,以便组织和分离代码。使用以下代码查看 D3 是否可以与 React 一起使用:
// charts/BasicD3.js
import * as d3 from 'd3';
export function drawChart(height, width){
d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.append("text")
.attr("fill", "green")
.attr("x", 50)
.attr("y", 50)
.text("Hello D3")
}
// App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { drawChart } from './charts/BasicD3';
function App() {
const [data, setData] = useState([]);
useEffect(() => {
drawChart(400, 600);
}, []);
return (
<div className="App">
<h2>Graphs with React</h2>
<div id="chart">
</div>
</div>
);
}
export default App;
如果集成成功,您将看到绿色的“Hello D3”。这里有:
- 创建了一个div来使用 React 绘制 D3 图表。
- 使用纯 D3 代码绘制图表和文本。
- 使用useEffect()钩子在 React 应用程序加载时调用drawChart()方法。
现在您可以尝试上面的条形图示例,看看此方法是否有效。
// BasicD3.js
import * as d3 from 'd3';
export function drawChart(height, width, data){
const svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
var selection = svg.selectAll("rect").data(data);
// ....
// rest of the d3 code from chart.html
// .....
}
// App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { drawChart } from './charts/BasicD3';
const dataset = [
[10, 30, 40, 20],
[10, 40, 30, 20, 50, 10],
[60, 30, 40, 20, 30]
]
var i = 0;
function App() {
const [data, setData] = useState([]);
useEffect(() => {
changeChart();
}, []);
const changeChart = () => {
drawChart(400, 600, dataset[i++]);
if(i === dataset.length) i = 0;
}
return (
<div className="App">
<h2>Graphs with React</h2>
<button onClick={changeChart}>Change Data</button>
<div id="chart">
</div>
</div>
);
}
export default App;
在App组件中,您已在数据集变量中指定了三个数据数组。单击“更改数据”按钮时,按钮将使用新数据数组调用drawChart方法。此函数是一种在实际应用程序中演示动态数据集的方法。运行上述示例时,每次单击按钮时,都会将新图表添加到 DOM,而不是对现有图表进行更新。发生这种情况的原因是drawChart()方法开头附加了一个新的svg元素。您可以通过将图表初始化和绘制分为两部分来解决这个问题。
// BasicD3.js
import * as d3 from 'd3';
export function initChart(height, width){
d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
}
export function drawChart(height, width, data){
const svg = d3.select("#chart svg");
// ....
}
// App.js
// ...
function App() {
const [data, setData] = useState([]);
useEffect(() => {
initChart(400, 600);
changeChart();
}, []);
// ...
}
最后,图表集成工作顺利,并按要求更新。这种集成看起来相当简单。但与我们的预期相比,它存在一些问题:
- 由于div使用唯一 ID 固定,因此每页只能绘制一个图表。为了解决这个问题,请创建多个 ID(每个图表一个)并将其传递给drawChart()方法。但是,从长远来看,这不会扩展。
- 这不符合 React 的可重用性概念。理想情况下,BarChart组件应该只通过将数据、高度、宽度和其他图表选项作为参数来工作,而不是在父组件的生命周期中启动。
更好的 React/D3 集成
您可以通过在可能的情况下添加 React 模式来改进上一节中的想法。这可确保您获得两全其美的效果。以下代码显示了两个库之间更好的集成:
// BarChart.js
import * as d3 from 'd3';
import React, { useRef, useEffect } from 'react';
function BarChart({ width, height, data }){
const ref = useRef();
useEffect(() => {
const svg = d3.select(ref.current)
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
}, []);
useEffect(() => {
draw();
}, [data]);
const draw = () => {
const svg = d3.select(ref.current);
var selection = svg.selectAll("rect").data(data);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, height-100]);
selection
.transition().duration(300)
.attr("height", (d) => yScale(d))
.attr("y", (d) => height - yScale(d))
selection
.enter()
.append("rect")
.attr("x", (d, i) => i * 45)
.attr("y", (d) => height)
.attr("width", 40)
.attr("height", 0)
.attr("fill", "orange")
.transition().duration(300)
.attr("height", (d) => yScale(d))
.attr("y", (d) => height - yScale(d))
selection<fon
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~