tangyuxian
文章80
标签37
分类5

文章分类

文章归档

echarts-多个折线图拖拽收尾自动闭合操作

echarts-多个折线图拖拽收尾自动闭合操作

拖拽折线图的收尾自动闭合成面积图

目前有这样一个需求,在一个统计图中,会有多条可拖拽的折线图,当某条折线图的首尾相连时会自动填充内部变成面积图,分开又变回折线图,如下图

image-20220604163530022

图1和图2都是4个端点,当第一个端点和最后一个端点通过拖拽相连后变回变成图2的效果,并自动填充其中颜色

一 分析

首先思考的几个问题:

  1. 如何拖拽折线图;
  2. 首个端点和最后一个端点如何正好重合(当数据过大,很难通过拖拽情况下恰好让两个端点的数值完全相同);
  3. 如何填充颜色;

以下是官方示例

  1. 一条普通的折线图示例
  2. 一条可拖拽节点的折线图示例
  3. 基础面积图示例

二 准备工作

1 如何实现拖拽

从官方的示例中我们可以发现,它采用的是graphic属性.根据官方手册我们可以发现,这个属性可以让我们在echarts图上自由绘制图形区域,以及这些图形的鼠标按下,抬起,鼠标经过,拖拽等等操作回调,借助echartsInstance.convertToPixel可进行坐标转换,将每个端点的坐标转换成像素坐标,为我们定点;

定点实现后后借助graphic中的ondrag回调函数便可知我们目前已拖拽的点的坐标,拿到的是像素坐标,再借助echartsInstance.convertFromPixel将坐标转换成我们目前可用数值

2 如何实现首尾相连

如果单靠拖拽来实现首尾两个坐标点一致太难了,因为这中间会产生特别小的误差使其无法完全一致

可借助下面这个函数算出两个坐标点的距离,可在距离小于一定数值后让两个坐标点相连

1
2
3
4
5
function distant(a, b) {
    let dx = Number(a[0]) - Number(b[0])
    let dy = Number(a[1]) - Number(b[1])
    return Math.pow(dx * dx + dy * dy, .5)
}

3 如何实现内容填充

根据官方手册series-line.areaStyle的配置项可自定义填充颜色,因为存在首尾坐标在拖拽下合并或者分开两种情况随时变换,当我们给这个属性null时会让填充失效,当给{}则会使用默认的填充配置

4 多条折线图存在的注意事项

官方示例中均为单条折线,但是在多条线中,我们要考虑的问题便增加了,在拖拽的时候要注意我们拖拽的是第几条线,拖拽完毕还要将拖拽后的数值回填到定义的数据源中

5 限制拖拽端点数值

若限制端点拖拽到坐标外,可在ondrag获取到当前数值的最大值最小值判定,然后return即可

三 示例

本糖已经开出来有人看的还是有点懵,于是完整代码在下面了,复制到本地运行看看吧;

也可点击 在线示例 ,不过在线示例是放在本糖的服务器上,哪天本糖穷到没钱续费服务器就访问不到了,还是复制到本地看最保险

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<!--
	THIS EXAMPLE WAS DOWNLOADED FROM https://echarts.apache.org/examples/zh/editor.html?c=line-draggable&code=MYewdgzgLgBBCeBbARiANgZQJYC8CmMAvDAEwAMA3AFCiSwAmAhlI0TANpUwcAsZANDAC0ARjIBdflw5CAzAOEBWSdPZCA7ADYAdIsHkV3NZtm7BfQzJIltIwYolVx1EAAcoWcGwDe0j1DQ8AC4YX25uKDwADygQgHIAFQAneBgAESTGAHMsrDAsmCgACzwIAgAFEDyoCDipcJhAgDNYmDjgPDBIpLjpAF96wpB0D1cQsPCoJKwcvCSAeTB4sHA8OuluJpAkxGZukKaAVzBgDy8ACldGTMQIAEpQjfCkvChDpLAYc6eGuIANeIwADUP3CVxuEG0TBY7Ak2igIAAYlgonh6OcSA8QQ1fgAeZBJAB8AE1AdicdxwYxblDmIx2CJxPCkSi0Ri7qC7tQGn1-oMstN6OMngixm0ABwAUnWDVQUARiHiIhI0r50iiAEEolgIMKGog8iFRGQBE9dlEQupTQ0oPBXME2gA3RhoQ5rQbcRjaiAAGTyDu8MHAAC05iADi6yjBedwBtJ4FqdXrwgalsJ5B6YOaQpprZM7Q64s7Xe6nl6dX6wAGg2BQ0lwzAmpGCDHo4NoYxg8NFRwnhMbQX4hA0Fh6HMZTjNd6AJJgMcWmB5hpNLBoboAWRAY-Wq16PMz_fz9qHI7HPUz3ATM7n0RCS_CK7Xc0327aKyre_CcYah4ig7aeQQKOpYUlOOqzvOd4Xo2q4bluhbvmsTzfuEv6FP-cSAcBE4NFe4E3gu96bLBz7wTuH7IdIlhlNMpQhJwP6gqO8SMDhR6FiOH7QRAiDDMUIRTG60HXHgjAYLagTJhS2wzIacSdPQn77qCCAoOg2D4CEqmoJguB4NBHYhB2lHcOIVB9NQZRQAkWCIHgICHFA5xHCcZyfOcDwTAA9F5MAavQ9BwEUjD0CAADuMDAFgSTAIEEBfGFRRYMARQwDqMArLAjo6lgyCBA8CIwJ0jB5QQ9CZFk2jSIg8AAMLBUkUDaFZ8zuJ4YDnIeAqMK4SXAEZdLaLsrjOccpztV8WCRIgggdhB0SeaCLxvB8jwUn-x5tFFMWBGxDSuCAQFuSENX1dcTW0I6czWSA5Ssmg5xxAKo51Gl013NB3AQMFm1oQ0wCEZ94TAPAUGgg0SRaUgOkaQQvkkODbaI3k2VAaVAlJEJiPldkWQlZJhSY_piPgDjWQHGNblfPQUSzfAi3reE4CVNUGS43kWTnHNBGCOwxQ6totOFElkLwOIXKIyh0lgLxhxlLxV0U65E0eWtjNBeFCTDGuWAjdz84S4zUs4uAstlA5rQueNFwM-rSVjlrIy61zdLzVEhvrcbDQ4CEYhkKCFnIRysYSwMi4S2FeShWF2ghfQACiV1dH60CdHMj0vEB-CvYcrjQnglRHe1EunQ1TXgI9HZdiAiA53nzAF4dU3F9QVtU7n-eF83Fe26X53Na8rVuZ1Tzdb1yUDSwQ09aNysXFNeAzTA-sLWrEOvO8nx_TAB1F-AJ11WX2iXddWt3aiD1PYKr0L4gwc4oHPL330odUG3E3fZr2ujC7LBu73h9-70B1FcKAKUNTWw6oeW0m04ifzCjZVwbEaJYFKG7MGDQV4Liwf0V-78vD2zwI7HWetXYEQAWdRqUIQHMHAZAkeA5YGEMQZ-F-1BeT4M-MzKoXQ2Y5A5r_RgbtBC71th2dgWDxBsD7lQk-jVET1kQOfPAl9nqKREYdD2PkYAAFV66RGXnSaqgCqEtTahXQ8KC6K9gpNvZibRWJA0MoYlgAcnhmRDuwoAA
	请注意,该图表不是 Apache ECharts 官方示例,而是由用户代码生成的。请注意鉴别其内容。
-->
<!DOCTYPE html>
<html lang="zh-CN" style="height: 100%">
<head>
    <meta charset="utf-8">
    <style>
        body {
            height: 100%;
        }

        .main {
            height: 70%;
        }

        #container {
            flex: 1;
            height: 100%;
            margin: 0
        }

        .tip {
            padding: 10px 100px;
        }
    </style>
</head>
<body>
<div class="main">
    <div id="container"></div>
    <div class="tip">
        <b>说明:</b>做了一个简单的示例,可以随意拖拽,拖拽后收尾相连开始判定可围成一个区域,首尾相连过程中会通过坐标计算两点距离,距离小于2时会自动吸附连接</br>
        圈定的范围颜色是根据线的颜色变化</br>
        首尾相连后可一起拖拽,快速拖拽可分离收尾连接,同时判定圈定范围失效</br>
        注意多条线的拖拽逻辑是将所有可拖拽线的点压到一个数组里,处理拖拽后逻辑要根据原始数组下标确定是拖动了哪条线
    </div>
</div>


<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.3.0/dist/echarts.min.js"></script>
<!-- Uncomment this line if you want to dataTool extension
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.3.2/dist/extension/dataTool.min.js"></script>
-->
<!-- Uncomment this line if you want to use gl extension
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts-gl@2/dist/echarts-gl.min.js"></script>
-->
<!-- Uncomment this line if you want to echarts-stat extension
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts-stat@latest/dist/ecStat.min.js"></script>
-->
<!-- Uncomment this line if you want to use map
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/china.js"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@4.9.0/map/js/world.js"></script>
-->
<!-- Uncomment these two lines if you want to use bmap extension
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=YOUR_API_KEY"></script>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.3.2/dist/extension/bmap.min.js"></script>
-->

<script type="text/javascript">
    var dom = document.getElementById('container');
    var myChart = echarts.init(dom, null, {
        renderer: 'canvas',
        useDirtyRect: false
    });
    var app = {};
    var option;
    const symbolSize = 20;
    const data = [
        [
            [10, 56],
            [14, 80],
            [17, 63],
            [22, 60],
        ],
        [
            [47, 156],
            [34, 140],
            [37, 100],
            [40, 80],
            [52, 90]
        ],
        [
            [10, 149],
            [5, 109],
            [20, 100],
            [16, 145],
        ]
    ];
    option = {
        color: ['green', 'red','blue'],
        title: {
            text: '拖拽示例',
            left: 'left'
        },
        legend: {
            data: data.map((e,i)=>'图'+(i+1))
        },
        tooltip: {
            triggerOn: 'none',
            formatter: function (params) {
                return (
                    'X: ' +
                    params.data[0].toFixed(2) +
                    '<br>Y: ' +
                    params.data[1].toFixed(2)
                );
            }
        },
        grid: {
            top: '8%',
            bottom: '12%'
        },
        xAxis: {
            min: -90,
            max: 90,
            type: 'value',
            axisLine: {onZero: false}
        },
        yAxis: {
            min: -180,
            max: 180,
            type: 'value',
            axisLine: {onZero: false}
        },
        dataZoom: [
            // {
            //     type: 'slider',
            //     xAxisIndex: 0,
            //     filterMode: 'none'
            // },
            // {
            //     type: 'slider',
            //     yAxisIndex: 0,
            //     filterMode: 'none'
            // },
            {
                type: 'inside',
                xAxisIndex: 0,
                start: 30,
                filterMode: 'none',
            },
            {
                type: 'inside',
                yAxisIndex: 0,
                start: 40,
                filterMode: 'none'
            }
        ],
        series: data.map((item,index)=>{
            return  {
                id: 'obj'+index,
                name: '图'+(index+1),
                type: 'line',
                smooth: true,
                areaStyle: null,
                symbolSize: symbolSize,
                data: data[index]
            }
        })
    };

    // Add shadow circles (which is not visible) to enable drag.
    dragListener()

    function dragListener() {
        setTimeout(function () {
            let graphicList = []
             data.map((dataItem,dataIndex)=>{
                 graphicList=[...graphicList,...dataItem.map(function (item, index) {
                     return {
                         type: 'circle',
                         position: myChart.convertToPixel('grid', item),
                         shape: {
                             cx: 0,
                             cy: 0,
                             r: symbolSize / 2
                         },
                         invisible: true,
                         draggable: true,
                         ondrag: function (dx, dy) {
                             onPointDragging(dataIndex, index, [this.x, this.y]);
                         },
                         onmousemove: function () {
                             showTooltip(dataIndex,index);
                         },
                         onmouseout: function () {
                             hideTooltip(index);
                         },
                         z: 100
                     };
                 })]
             })
            myChart.setOption({
                graphic:graphicList
            });
        }, 0);
    }

    function showTooltip(dataIndex,index) {
        myChart.dispatchAction({
            type: 'showTip',
            seriesIndex: dataIndex,
            dataIndex: index
        });
    }

    function hideTooltip(dataIndex) {
        myChart.dispatchAction({
            type: 'hideTip'
        });
    }

    function distant(a, b) {
        let dx = Number(a[0]) - Number(b[0])
        let dy = Number(a[1]) - Number(b[1])
        return Math.pow(dx * dx + dy * dy, .5)
    }

    function onPointDragging(dataIndex, index, pos) {
        let dataPos = myChart.convertFromPixel('grid', pos);
        if (dataPos[0] > option.xAxis.max || dataPos[0] < option.xAxis.min || dataPos[1] > option.yAxis.max || dataPos[1] < option.yAxis.min) {
            return dragListener()
        }
        data[dataIndex][index] = myChart.convertFromPixel('grid', pos);
        if (distant(data[dataIndex][0], data[dataIndex][data[dataIndex].length - 1]) < 2) {
            data[dataIndex][0] = data[dataIndex][data[dataIndex].length - 1];
        }
        // Update data
        option.series[dataIndex].data = data[dataIndex]
        option.series[dataIndex].areaStyle = distant(data[dataIndex][0], data[dataIndex][data[dataIndex].length - 1]) == 0 ? {} : null
        myChart.setOption({
            series: option.series
        });
        dragListener()
    }

    if (option && typeof option === 'object') {
        myChart.setOption(option);
    }

    window.addEventListener('resize', dragListener);
    myChart.on('dataZoom', dragListener);
    window.addEventListener('resize', myChart.resize);
</script>
</body>
</html>

小埋

本文作者:tangyuxian
本文链接:https://www.tangyuxian.com/2022/06/04/%E5%89%8D%E7%AB%AF/echarts/echarts-%E5%A4%9A%E4%B8%AA%E6%8A%98%E7%BA%BF%E5%9B%BE%E6%8B%96%E6%8B%BD%E6%94%B6%E5%B0%BE%E8%87%AA%E5%8A%A8%E9%97%AD%E5%90%88%E6%93%8D%E4%BD%9C/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可