reactHooksWithD3

Using ReactHooks With D3

教程: https://www.youtube.com/watch?v=hR8xtl_IbCw&list=PLDZ4p-ENjbiPo4WH7KdHjh_EMI7Ic8b2B&index=3&pbjreload=101

源码:https://github.com/muratkemaldar/using-react-hooks-with-d3

The Basics

在过去,我们必须依赖于 class component 和 life circle method 来进行D3的绘图。但是在React引入了hooks之后,我们的选择就不仅仅是class component了。我们可以利用 React 中的Hooks函数配合D3 来进行绘图。

首先我们来介绍我们需要用到的hooks

useState

https://zhuanlan.zhihu.com/p/92349623

useRef

https://blog.csdn.net/weixin_43720095/article/details/104966218

ref是React提供的用来操纵React组件实例或者DOM元素的接口。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
然而,useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。
这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

useRef 有什么作用呢,其实很简单,总共有三种用法

  • 作用于Dom元素
  • 获取子组件的实例(只有类组件可用)
  • 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx

useEffect

http://www.ptbird.cn/react-hoot-useEffect.html

https://zhuanlan.zhihu.com/p/84697185

Effect Hook 可以让你能够在 Function 组件中执行副作用(side effects):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
// 类似于 componentDidMount 和 ComponentDidUpdate
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

上面代码看过很多次了,是 React 文档中 Hook 部分一直使用的计数器示例,但是多了个新的功能:把文档标题设置为包含点击次数的自定义消息。而这就是一个副作用。

数据获取,设置订阅或者手动直接更改 React 组件中的 DOM 都属于副作用。有的人习惯成这种行为为 effects,我是比较习惯叫 side effects 也就是副作用的, 这是个概念,需要在 React 必须习惯的概念。

如果熟悉 React 类声明周期方法,可以把 useEffect Hook 视作 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合体。

useEffect的第二个参数可选 ,可用于定义其依赖的所有变量。如果其中一个变量发生变化,则useEffect会再次运行。如果包含变量的数组为空,则在更新组件时useEffect不会再执行,因为它不会监听任何变量的变更。。

使用useEffect,不要调用函数层次太多,代码应该一眼看清楚哪些函数会被useEffect调用

React+D3 with Hooks :

首先我们在一个function component当中引入 state,是一个数组
然后我们创建一个ref对象,这个对象是一个svg

在然后我们使用useEffect Hooks也就是给这个svg对象添加一些副作用,这个副作用就是渲染我们的图元。在useEffect 当中,我们把svgRef中的current属性(就是一个svg对象)抽象出来称为svg,我么可以对这个svg添加属性,这样就可以渲染出来了。

两个按钮的原理: 点击 Update Data的话,利用setData,把data中所有的值全部加上5. 点击Filter Data的时候,筛选出小于等于25的值,然后通过setData更新data,因为更新过后的data和useEffect中的data数组不一样,所以useEffect会被重新调用,从而实现更新效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { useRef, useEffect, useState } from "react";
import "./App.css";
import { select } from "d3";
const App = () => {
const [data, setData] = useState([25, 30, 45, 60, 20]);
const svgRef = useRef();
useEffect(() => {
const svg = select(svgRef.current);
svg
.selectAll("circle")
.data(data)
.join("circle")
.attr("r", (value) => value)
.attr("cx", (value) => value * 2)
.attr("cy", (value) => value * 2)
.attr("stroke", "red");
}, [data]);
return (
<React.Fragment>
<svg ref={svgRef}> </svg>
<br />
<button onClick={() => setData(data.map((value) => value + 5))}>
Update Data
</button>
<button onClick={() => setData(data.filter(value=>value<=25))}>
Filter Data
</button>
</React.Fragment>
);
};

export default App;

Curved Line Chart

现在我们来画曲线图,我们需要d3库当中的 linecurveCardinal(用来把折线变成曲线)

line: line方法用于绘制直线,只需要起始和结束点的x轴和y轴坐标就可以确定一段直线的位置。它会为我们的path生成d属性。d这个属性,包含一系列的方法与参数.因此我们可以把这个属性称作一个“微语言” ,这些方法与参数其实就是告诉电脑”如何在纸上移动你的钢笔”.

path: 可以渲染各种各样的图形,d3.line 可以帮助我们生成 d 属性用于描述折线。

那么对于myline这个图元,怎么确定他的横纵坐标呢?

对于横坐标,我们传入value和value的index,然后令横坐标就等于index*50 ,在svg画布上每个点的横坐标相当于:0,50,100,…这样的间隔 ;对于纵坐标,就随便我们定了;我们还加入了curve(curveCarginal)目的就是让曲线变得光滑

我们在正式画图的时候,这里要了解 .data([data]) 的意思。这里我们打整个data数组放到另一个数组当中。这样,d3 就只会根据我们的data数组生成一条path,如果是.data(data)的话,d3会为数组当中的 每一个元素生成一条path,也就是仅仅是几个点罢了。 所以我们用 .data([data])的写法,

.attr("d", (value) => myline(value)) 中,第二个参数是一个回调函数,value值接收我们整个data数组,也就是[[25, 30, 45, 60, 20,29,99]] 作为参数 因为这里只有一个子数组,那么就仅仅生成一条path。 然后我们把数据交给myline去处理,让他生成每一个点的横纵坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useRef, useEffect, useState } from "react";
import "./App.css";
import { select, line ,curveCardinal} from "d3";
const App = () => {
const [data, setData] = useState([25, 30, 45, 60, 20,29,99]);
const svgRef = useRef();
useEffect(() => {
const svg = select(svgRef.current);
const myline = line()
.x((value, index) => index * 50)
.y((value) => 150-value)
.curve(curveCardinal);
svg
.selectAll("path")
.data([data])
.join("path")//为data新建一个path
.attr("d", (value) => myline(value))
.attr("fill","none")
.attr("stroke","blue");
}, [data]);
return (
<React.Fragment>
<svg ref={svgRef}></svg>
<br />
<button onClick={() => setData(data.map((value) => value + 5))}>
Update Data
</button>
<button onClick={() => setData(data.filter((value) => value <= 25))}>
Filter Data
</button>
</React.Fragment>
);
};

export default App;

Axes and Scales

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import React, { useRef, useEffect, useState } from "react";
import "./App.css";
import {
select,
line,
curveCardinal,
axisBottom,
axisRight,
scaleLinear,
} from "d3";

function App() {
const [data, setData] = useState([25, 30, 45, 60, 20, 65, 75]);
const svgRef = useRef();

// will be called initially and on every data change
useEffect(() => {
const svg = select(svgRef.current);
const xScale = scaleLinear()
.domain([0, data.length - 1])
.range([0, 300]);

const yScale = scaleLinear().domain([0, 150]).range([150, 0]);

const xAxis = axisBottom(xScale)
.ticks(data.length)
.tickFormat((index) => index + 1);
svg
.select(".x-axis")
.style("transform", "translateY(150px)")
.call(xAxis)
.attr("fill", "black");

const yAxis = axisRight(yScale);
svg.select(".y-axis").style("transform", "translateX(300px)").call(yAxis);

// generates the "d" attribute of a path element
const myLine = line()
.x((value, index) => xScale(index))
.y(yScale)
.curve(curveCardinal);

// renders path element, and attaches
// the "d" attribute from line generator above
svg
.selectAll(".line")
.data([data])
.join("path")
.attr("class", "line")
.attr("d", myLine)
.attr("fill", "none")
.attr("stroke", "blue");
}, [data]);

return (
<React.Fragment>
<svg ref={svgRef}>
<g className="x-axis" />
<g className="y-axis" />
</svg>
<br />
<br />
<br />
<br />
<button onClick={() => setData(data.map((value) => value + 5))}>
Update data
</button>
<button onClick={() => setData(data.filter((value) => value < 35))}>
Filter data
</button>
</React.Fragment>
);
}

export default App;

Animated Bar Chart

Interactivity

Responsive

-------------本文结束,感谢您的阅读-------------