tangyuxian
文章79
标签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 如何实现首尾相连

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

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

    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即可

三 示例

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

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

<!--
    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 协议进行许可