D3.js是一款非常好用的数据可视化js库,能够帮助我们非常方便地将数据可视化地展示。
D3与echart这些数据可视化库的区别是D3更加的底层,没有基于组件的方法调用,而是提供基于元素的api供操作(类似JQuery),因此D3的灵活度非常高,能够帮助我们完成许多定制程度较高的可视化任务。
在学习D3的过程中,我查找到的D3与react结合使用的资料大部分都有点过时,不是D3的版本比较低就是react的版本比较低,要对照最新版本的文档才能使用,比较麻烦。
因此,我根据自己的使用经历总结了D3(v7)与react的函数式组件结合使用的基本方法,方便大家参考。
元素的选择
D3需要我们选中若干个元素才能进行操作,选择器与JQuery的使用类似(没学过JQuery也没关系,其实就是css的元素选择器罢了),用法如下:
// 选择一个(第一个)div元素
const myDiv = d3.select("div");
// 选择所有的类为"my-svg"的元素
const mySVG = d3.selectAll(".mysvg")
是不是很简单,但是在react中,我们最好不要直接用元素名、类名、Id进行元素的选择,因为在react中组件是可以被复用的。如果一个页面中同一个组件出现多次,并且这个组件使用类名选择元素进行操作的话,那么这几个组件之间就会互相干扰,甚至报错。
所以,我们可以使用react的ref进行元素选择,从而精确控制元素:
export default function D3() {
const svgElement = useRef<SVGSVGElement>(null);
return <svg ref={svgElement} />;
}
使用元素时,只需给选择器传入ref的current即可
元素的初始化
我们前面使用了一个svg元素作为D3的操作元素,那么我们只要对svg进行初始化即可
这里要注意,对svg的初始化操作只需要在组件挂载时执行一次(就是类组件的componentDidMount()生命周期),所以我们选择把这个操作放在没有依赖的useEffect中执行,并写好清理函数(提高程序的健壮性,并且useEffect在开发环境会默认执行两遍,不清理副作用将影响开发)
// 这里用state存储D3包裹的svg元素,方便我们后续操作
const [svg, setSvg] = useState<d3.Selection<SVGSVGElement | null, unknown, null, undefined>>();
useEffect(() => {
const svg = d3
.select(svgElement.current)
.attr("width", 500)
.attr("height", 500);
// 初始化svg,并存到state中
setSvg(svg);
// 清理函数,将svg中的元素清空,防止重复添加
return () => {
svg?.selectChildren().remove();
setSvg(undefined);
};
}, []);
当然,就这样还不够,可以用一个简单的条形图模拟实际场景
有以下数据:
data = [277, 140, 281, 284, 177]
我们可以在初始化时将数据进行展示:
const [svg, setSvg] =
useState<
d3.Selection<SVGSVGElement | null, unknown, null, undefined>
>();
const [barCharts, setBarCharts] =
useState<
d3.Selection<SVGRectElement, number, SVGSVGElement | null, unknown>
>();
const [text, setText] =
useState<
d3.Selection<SVGTextElement, number, SVGSVGElement | null, unknown>
>();
useEffect(() => {
const svg = d3
.select(svgElement.current)
.attr("width", 500)
.attr("height", 500);
setSvg(svg);
const barCharts = svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (__, i) => i * 50)
.attr("y", (d) => 500 - d)
.attr("width", 40)
.attr("height", (d) => d)
.attr("fill", "blue")
.on("mouseover", function (d, i) {
d3.select(this).attr("fill", "red");
})
.on("mouseout", function (d, i) {
d3.select(this).attr("fill", "blue");
});
setBarCharts(barCharts);
const text = svg
.selectAll("text")
.data(data)
.enter()
.append("text")
.text((d) => d)
.attr("x", (__, i) => i * 50 + 5)
.attr("y", (d) => 500 - d + 30)
.attr("fill", "white");
setText(text);
return () => {
svg?.selectChildren().remove();
setSvg(undefined);
};
}, []);
效果如下:
数据更新
光是展示静态数据就没必要使用react了,直接使用D3一套做完不香吗?
我们现在让data动起来,那么我们必须针对data对图像进行更新才能实现动态数据展示的效果
方法很简单,用useEffect把data当作依赖即可:
useEffect(() => {
barCharts
?.data(data)
.transition()
.duration(1000)
.attr("y", (d) => 500 - d)
.attr("height", (d) => d);
text?.data(data)
.transition()
.duration(1000)
.text((d) => d)
.attr("y", (d) => 500 - d + 30);
}, [barCharts, data, text]);
效果如下:
怎么样,是不是效果还不错,这样根据data的变化,我们能很方便地在react中使用D3展示数据了
如果你觉得代码太多,可以把这些内容封装成一个hook,暴露出一个setData供修改数据就行啦
如果这篇文章对你有帮助,就点个赞吧
如果你有更好的实现,可以在评论指出,大家一起学习