React 中的 D3 树形图
介绍
树形图是一种实用的图表,可以对分层数据进行整体可视化。除了结构之外,它还提供了层次结构的组成概念,这使其更有价值。树形图可用于分析股票、组织不同部门的绩效、可视化各国的 COVID 传播情况等。
使用 D3 实现树形图很简单,我们可以在网上找到很多示例代码。但是在 React-D3 集成中复制相同的代码可能有点棘手。在本指南中,我们将探讨如何以中等复杂度复制一个这样的示例。然后,我们将在数据更新中添加动画以模拟真实的数据集。本指南中的代码可在Github repo中找到。
初始化 D3 图表组件
首先,创建组件框架以向 React 添加 D3 支持并与其集成。查看 Pluralsight 的指南《使用D3 在 React 中绘制图表》以了解如何创建简单的 D3 图表。使用该指南中演示的BarChart结构,可以按如下方式初始化 Treemap 图表。
// Treemap.js
import * as d3 from 'd3';
import React, { useRef, useEffect } from 'react';
function Treemap({ 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 = () => {
}
return (
<div className="chart">
<svg ref={ref}>
</svg>
</div>
)
}
export default Treemap;
// App.js
import React from 'react';
import './App.css';
import Treemap from './Treemap';
const dataset = [];
function App() {
return (
<div className="App">
<h2>Graphs with React</h2>
<Treemap width={600} height={400} data={dataset} />
</div>
);
}
export default App;
确保使用npm install --save d3 安装了 D3 。您可以启动应用程序并验证组件和 D3 是否按预期运行。
创建具有多个层次结构的树形图
使用具有多个层次结构的数据集可以更好地展示树形图的可视化功能。此树形图示例使用包含三个简单层次结构的数据集对树形图概念进行了出色的介绍,并且没有过渡和缩放等额外复杂性。
您可以通过将 JSON 数据直接复制到数据变量中来将数据集导入 React。这样暂时避免了通过 React 获取数据的麻烦。显示的其余 D3 代码只需进行少量更改即可导入 React。
// Treemap.js
// ...
const draw = () => {
const svg = d3.select(ref.current);
// Give the data to this cluster layout:
var root = d3.hierarchy(data).sum(function(d){ return d.value});
// initialize treemap
d3.treemap()
.size([width, height])
.paddingTop(28)
.paddingRight(7)
.paddingInner(3)
(root);
const color = d3.scaleOrdinal()
.domain(["boss1", "boss2", "boss3"])
.range([ "#402D54", "#D18975", "#8FD175"]);
const opacity = d3.scaleLinear()
.domain([10, 30])
.range([.5,1]);
// Select the nodes
var nodes = svg
.selectAll("rect")
.data(root.leaves())
// draw rectangles
nodes.enter()
.append("rect")
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.x1 - d.x0; })
.attr('height', function (d) { return d.y1 - d.y0; })
.style("stroke", "black")
.style("fill", function(d){ return color(d.parent.data.name)} )
.style("opacity", function(d){ return opacity(d.data.value)})
nodes.exit().remove()
// select node titles
var nodeText = svg
.selectAll("text")
.data(root.leaves())
// add the text
nodeText.enter()
.append("text")
.attr("x", function(d){ return d.x0+5}) // +10 to adjust position (more right)
.attr("y", function(d){ return d.y0+20}) // +20 to adjust position (lower)
.text(function(d){ return d.data.name.replace('mister_','') })
.attr("font-size", "19px")
.attr("fill", "white")
// select node titles
var nodeVals = svg
.selectAll("vals")
.data(root.leaves())
// add the values
nodeVals.enter()
.append("text")
.attr("x", function(d){ return d.x0+5}) // +10 to adjust position (more right)
.attr("y", function(d){ return d.y0+35}) // +20 to adjust position (lower)
.text(function(d){ return d.data.value })
.attr("font-size", "11px")
.attr("fill", "white")
// add the parent node titles
svg
.selectAll("titles")
.data(root.descendants().filter(function(d){return d.depth==1}))
.enter()
.append("text")
.attr("x", function(d){ return d.x0})
.attr("y", function(d){ return d.y0+21})
.text(function(d){ return d.data.name })
.attr("font-size", "19px")
.attr("fill", function(d){ return color(d.data.name)} )
// Add the chart heading
svg
.append("text")
.attr("x", 0)
.attr("y", 14) // +20 to adjust position (lower)
.text("Three group leaders and 14 employees")
.attr("font-size", "19px")
.attr("fill", "grey" )
}
// ...
// App.js
// ....
const dataset = {"children":[{"name":"boss1","children":[{"name":"mister_a","group":"A","value":28,"colname":"level3"},{"name":"mister_b","group":"A","value":19,"colname":"level3"},{"name":"mister_c","group":"C","value":18,"colname":"level3"},{"name":"mister_d","group":"C","value":19,"colname":"level3"}],"colname":"level2"},{"name":"boss2","children":[{"name":"mister_e","group":"C","value":14,"colname":"level3"},{"name":"mister_f","group":"A","value":11,"colname":"level3"},{"name":"mister_g","group":"B","value":15,"colname":"level3"},{"name":"mister_h","group":"B","value":16,"colname":"level3"}],"colname":"level2"},{"name":"boss3","children":[{"name":"mister_i","group":"B","value":10,"colname":"level3"},{"name":"mister_j","group":"A","value":13,"colname":"level3"},{"name":"mister_k","group":"A","value":13,"colname":"level3"},{"name":"mister_l","group":"D","value":25,"colname":"level3"},{"name":"mister_m","group":"D","value":16,"colname":"level3"},{"name":"mister_n","group":"D","value":28,"colname":"level3"}],"colname":"level2"}],"name":"CEO"};
// ....
通过上述更改,您现在可以看到 Treemap 图表按预期实现。请注意,由于图表的高度和宽度不同,图表矩形的大小可能与原始示例中显示的大小不同。但要点是,大小应该表达节点的相对值差异。
添加过渡效果
虽然上述代码可以完成工作,但在实际场景中,您通常需要处理动态数据。因此,必须调整 Treemap。理想情况下,转换应该在以下两种情况下发生:
- 节点值更新时动画矩形大小
- 当添加/删除节点时,动画添加/删除矩形(此示例中的节点是员工或小组负责人)
按照上一个指南的过渡步骤,这可以轻松完成。以下代码修改draw()方法以添加所需的动画。然后,您可以向App组件添加更多方法来进行数据更新并直观地观察动画。
// ...
const draw = () => {
const svg = d3.select(ref.current);
// Give the data to this cluster layout:
var root = d3.hierarchy(data).sum(function(d){ return d.value});
// initialize treemap
d3.treemap()
.size([width, height])
.paddingTop(28)
.paddingRight(7)
.paddingInner(3)
(root);
const color = d3.scaleOrdinal()
.domain(["boss1", "boss2", "boss3", "boss4"])
.
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~