D3 zoom 和 Slider 滑块组件 缩放

D3 在开发地图的时候,经常会使用到缩放功能,一般会提供两种缩放的方式,1、鼠标滚轴缩放,缩放的中心为鼠标所在位置。2、滑块缩放、地图一般在左上角会有滑块组件,缩放的中心为地图的中心位置。还有就是在开发一些 拓扑 组件的时候也会遇上上述类似使用场景,Apptalking 的拓扑图也是使用D3组价开发的,同样提供了上述两种缩放的功能。

d3.zoom()

D3 自身提供 zoom 缩放功能,其中也包含了 drap 拖拽的功能,参考 https://github.com/d3/d3-zoom
定点缩放 参考 https://bl.ocks.org/mbostock/a980aba1197350ff2d5a5d0f5244d8d1

示例代码

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style>
.dot circle {
fill: lightsteelblue;
stroke: steelblue;
stroke-width: 1.5px;
}

.dot circle.dragging {
fill: red;
stroke: brown;
}

.axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
</style>
<title></title>
</head>

<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = { top: -5, right: -5, bottom: -5, left: -5 },
width = 460 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
/**
* 创建zoom组件,
*/
var zoom = d3.zoom()
.scaleExtent([1, 10])
/**
* 绑定 zoom 触发事件使用的回调函数,D3 中的 zoom 只会触发事件 如 鼠标拖动,滚动滚轴,D3 会自动计算好
* 需要移动的位置和缩放的大小,在自定义的回调函数中在SVG元素上进行缩放
*/
.on("zoom", zoomed);

console.log(zoom.scaleExtent()[0], zoom.scaleExtent()[1]);

var drag = d3.drag()
.subject(function (d) { return d; })
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);

var slider = d3.select("body").append("p").append("input")
.datum({})
.attr("type", "range")
.attr("value", zoom.scaleExtent()[0])
.attr("min", zoom.scaleExtent()[0])
.attr("max", zoom.scaleExtent()[1])
.attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
// 绑定滑块的事件,在回调函数中进行缩放
.on("input", slided);
/**
* 创建接收zoom事件的实体,这里是创建在 SVG 下一个 g 标签中,zoom 可以使用在任何的 svg 标签上,一般都是在
* 上层的标签中,这也接收事件的范围更大。
*/
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
// 调用 zoom
.call(zoom);

var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
/**
* 真正缩放发元素,缩放的元素 和 接收缩放事件的不是同一个SVG元素,但是进行缩放的内容都应该放置在这个元素当中
*/
var container = svg.append("g");

container.append("g")
.attr("class", "x axis")
.selectAll("line")
.data(d3.range(0, width, 10))
.enter().append("line")
.attr("x1", function (d) { return d; })
.attr("y1", 0)
.attr("x2", function (d) { return d; })
.attr("y2", height);

container.append("g")
.attr("class", "y axis")
.selectAll("line")
.data(d3.range(0, height, 10))
.enter().append("line")
.attr("x1", 0)
.attr("y1", function (d) { return d; })
.attr("x2", width)
.attr("y2", function (d) { return d; });


var dot = container.append("g")
.attr("class", "dot")
.selectAll("circle")
.data([
{x:200,y:150},
{x:180,y:139},
{x:230,y:145},
{x:190,y:158},
{x:210,y:153},
{x:240,y:169},
{x:160,y:142},
{x:140,y:112},
{x:220,y:178},
{x:170,y:118},
{x:150,y:126}])
.enter().append("circle")
.attr("r", 5)
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.call(drag);


function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}

function zoomed() {
const currentTransform = d3.event.transform;
/**
* 进行元素的缩放,使用currentTransform toString方法,
*/
container.attr("transform", currentTransform);
/**
* 同步滑块的缩放变量,鼠标缩放和滑块缩放必须是同步的,两者的存储的临时值也是一致的,
* 鼠标缩放的时候滑块也应该同步进行滑动
*/
slider.property("value", currentTransform.k);
}

function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}

function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}

function dragended(d) {
d3.select(this).classed("dragging", false);
}

function slided(d) {
/** 使用 zoom.scaleTo 方法进行缩放,zoom 提供了多种方法,进行缩放和移动操作,
* 这里必须使用 zoom 进行操作, 这样D3 就知道当前的缩放的数据信息,如果直接操作
* container transform 属性,两者的信息就会不同步,D3还是会使用自己存储的信息,比如D3 原先
* 缩放大小为 1.5 ,滑块缩放成了 2.5, 然后进行拖动,D3 会直接还原缩放到 1.5,两者就出现了不同步问题。
* scaleTo 也只是触发事件,最终还是在 zoomed() 函数中进行缩放操作。
*/
zoom.scaleTo(svg, d3.select(this).property("value"));
}

</script>

</body>

</html>

点击运行代码