SVG连线图

需求

主要内容:

  1. 静态绘制
  2. 缩放/拖拽联动
  3. 选中效果/扩大点击区域/曲线可被选中而svg画布其他部分鼠标穿透
  4. 悬浮效果+获取tooltip数据

静态绘制曲线集

svg

1
2
3
4
5
6
7
8
9
10
11
12
13
<svg class="home-broadband-chart-line" version="1.1" xmlns="http://www.w3.org/2000/svg">

// active 选中效果
<g class="line-group active">

// 扩大点击区域(d为曲线: M起点 Q折点, 终点)
<path class="line-bg" d="M790,130 Q598.2258262634277,147.52609517415354 406.45165252685547,312.57828552246065"></path>

// 真实曲线(d为曲线(同上): M起点 Q折点, 终点)
<path class="line" d="同上" style="stroke: rgb(238, 80, 66);"></path>

</g>
</svg>

css

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
.home-broadband-chart-line {
pointer-events: none; // 设置svg鼠标穿透
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;

.line-group {
cursor: pointer;
fill: none;
pointer-events: visiblepainted; // 设置具体曲线可鼠标穿透
path {
fill: none; // 只渲染stroke曲线,不渲染闭合填充
}
&:hover, &.active {
.line {
stroke-width: 3;
}
}
}

.line-bg {
stroke: transparent; // 透明,用于扩大点击区域
stroke-width: 10;
}

.line {
stroke: #55E38D;
stroke-width: 1;
stroke-dasharray: 5, 2; // 虚线效果
}
}

参考: visiblepainted

动态绘制曲线集

获得每一条线的起始坐标,输入svg绘制即可。

曲线

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
<svg
className="home-broadband-chart-line"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
{
indexLinkList
.map((item) => {
const {
id,
name,
bId,
aId,
legend,
} = item;
const { color } = MAP_LEGEND_INFO[legend];

const startPos = posType[bId] || [0, 0]; // 起点位置
const x1 = startPos[0];
const y1 = startPos[1];

const endPos = posMap[aId] || [0, 0]; // 终点位置
const x2 = endPos[0];
const y2 = endPos[1];

const cx = (x1 + x2) / 2; // 折点位置 x
const cy = (y1 + y2) / 3; // 折点位置 y
const operator = ['group', 'idc'].indexOf(bId) > -1 ? 1 : 2; // 弧方向

return (
<g
key={id}
className={`line-group ${id === currentId ? 'active' : ''}`}
onMouseOver={e => this.onLinkHover(e, { id, name, color })}
onFocus={e => this.onLinkHover(e, { id, name, color })}
onMouseOut={this.onLinkOut}
onBlur={this.onLinkOut}
onClick={() => onSelect(id)}
>
<path
className="line-bg"
d={`M${x1},${y1} Q${cx},${operator * cy} ${x2},${y2}`}
/>
<path
className="line"
d={`M${x1},${y1} Q${cx},${operator * cy} ${x2},${y2}`}
style={{
stroke: color,
}}
/>
</g>
);
})
}
</svg>

起点位置

起点是四个DOM定点,只需获取四个DOM元素的中心位置(简单DOM操作,根据实际情况而定)

1
2
3
4
5
6
7
8
9
10
11
12
// 该示例不通用
getPos = () => {
const { id } = this.props;
const { offsetLeft, offsetTop } = this[id].offsetParent; // 位置
const ball = this[id].children[0];
const xCenter = (this[id].clientWidth - ball.clientWidth) / 2; // 中心
const yCenter = ball.clientHeight / 2;
return {
x: offsetLeft + this[id].offsetLeft + xCenter,
y: (offsetTop + this[id].offsetTop) + yCenter,
};
};

终点位置

终点是echarts图结点,需要获取echarts点位置

1
2
3
4
5
6
7
getPixel(info) {
const { pos, geo = 'geo' } = info;
const { lng, lat } = pos;
const { chart } = this;
const pixel = chart.convertToPixel(geo, [lng, lat]); // 经纬度转平面坐标
return pixel;
}

resize(拖拽/缩放)

echarts图形变动——重新获取地图各点平面坐标(终点位置)

1
2
3
4
5
6
7
8
9
setDataZoom(func) {
const { chart } = this;
chart.on('georoam', () => { // 监听echart地图事件,同时响应缩放和拖拽
func();
});
return this;
}

chart.setDataZoom(() => this.getMapPixel(mapList));

窗口变动——重新更新四个DOM元素平面坐标(起点位置)

1
2
window.addEventListener('resize', this.resize);。
window.removeEventListener('resize', this.resize);

判断鼠标“是否在同一条连线上连续移动”

鼠标在同一条连线上连续移动,不应重新调取tooltip接口,而应直接采用原有tooltip接口数据。

已知鼠标方法:

  • 鼠标落在连线: onmouseover
  • 鼠标离开连线: onmouseout

鼠标在曲线上移动过程中,视觉上虽然是连续的,但其实在一瞬间先经历了onmouseout又onmouseover的过程。

思路: 利用定时器setTimeout,来延缓onmouseout。即在200ms内,再次onmouseover,则不再执行onmouseout的方法。

1
2
3
4
5
6
7
8
onLinkOut = () => {
this.timer = setTimeout(this.onReset, 200);
};
onLinkHover =(event, link) => {
if (this.timer) {
clearTimeout(this.timer);
}
}

定时器有广泛的应用意义:

  1. 为了防止用户鼠标不经意划过链接,腾讯网首页几乎所有鼠标经过事件都进行了【延时处理】,即 鼠标经过后200毫秒,才执行鼠标经过事件。

该方案是节流、防抖的思想之一。

节流、防抖

描述

因频繁执行DOM操作,资源加载等行为,导致UI停顿甚至浏览器崩溃。

常见导致原因如以下持续触发的事件——

  • 浏览器的 onresize / onscroll 等事件。
  • 鼠标的 mousemove / mousedown 等事件。
  • 键盘的 keydown / keyup 等事件。
  • 地图的 zoomend / dragend / moveend 等事件。

功能

节流throttle

作用
以一定的频率执行后续处理。
让水龙头每隔200毫秒流出一滴水。源源不断的流出水而又不浪费。

场景
拖拽DOM。在一定时间内多次执行,会感觉流畅。(如果使用防抖,一定时间内执行一次,会感觉卡顿)。

分类

  • 时间戳
  • 定时版本
防抖debounce

作用
停止n毫秒后,执行后续处理

场景
搜索。当联想搜索时,用户连续不断输入字符keyup,不应立即发送请求,而应等一段时间不输入了,才发送一次请求

分类

  • 非立即防抖
  • 立即防抖
非立即防抖

触发事件后函数不会立即执行,而是在n秒之后执行,如果n秒之内又触发了事件,则会重新计算函数执行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
function debounce(func, wait) {
var timer = null; // 定义计时器
var context = this;
var args = arguments;
return function () {
if (timer) {
clearTimeout(timer); // 在n时间内,又被执行,则清除计时器,重新计时
}
timer = setTimeout(function () {
func.apply(context, args)
}, wait); // 触发事件后,等待n时间后才执行
}
}

立即防抖

触发事件后函数会立即执行,然后n秒内不触发事件才会执行函数的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
function debounce(func, wait) {
var timeout = null;
var context = this;
var args = arguments;
return function () {
if (timeout) clearTimeout(timeout);
var callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}

参考

  1. 节流(throttle)与防抖(debounce)
-------------Keep It Simple Stupid-------------
0%