javascriptcssechartsapache-echarts

Dragging vertical markLines in Apache echarts


I want to add several vertical marklines to a line chart and allow users to do horizontal dragging, similar to this Highcharts example.

Setting xAxis.axisPointer is useful in this sense. However, how can I allow the whole vertical line to be dragged on the horizontal line, rather than using the button at the botton as shown below ?

Here's my attempt:

Edit echart-line-vanilla (forked)

enter image description here

import "./styles.css";
import echarts from "echarts";

const myChart = echarts.init(document.getElementById("app"));

const option = {
  xAxis: {
    type: "time",
    nameLocation: "center",
    nameGap: 20,
    interval: 420000,
    min: 1625741884000,
    max: 1625749084000,
    axisLabel: {
      rotate: 0
    },
    boundaryGap: ["0%", "0%"],
    axisPointer: {
      value: 1625742000000,
      snap: true,
      lineStyle: {
        color: "#ff0000",
        width: 4
      },
      handle: {
        show: true,
        color: "#ff0000"
      }
    }
  },
  yAxis: [
    {
      type: "value",
      nameLocation: "center",
      nameGap: 8,
      interval: 0.33,
      min: 1,
      max: 5.33,
      axisLabel: {
        margin: 24,
        rotate: 0
      }
    }
  ],
  series: [
    {
      id: "test",
      name: "Average Time",
      yAxisIndex: 0,
      data: [
        {
          value: [1625741884000, 1]
        },
        {
          value: [1625741885000, 1]
        },
        {
          value: [1625741890000, 1]
        },
         ...

      ],
      subSeries: [],
      invert: false,
      type: "line",
      showSymbol: false,
      symbolSize: 5,
      smooth: false,
      color: "#4da6e8",
      lineStyle: {}
    }
  ]
};

myChart.setOption(option);

Solution

  • As far as I know, there is no way of having 2 axisPointer on the same chart. I've been looking for this feature for a while now, but I still can't find a perfect solution. A workaround using graphics can be used to achieve something similar to what you want, but it's far from perfect. I still share it in case it's of any help to you.

    var myChart = echarts.init(document.getElementById('main'));
    
    let base = +new Date(1988, 9, 3);
    let oneDay = 24 * 3600 * 1000;
    let data = [[base, Math.random() * 300]];
    
    for (let i = 1; i < 20000; i++) {
        let now = new Date((base += oneDay));
        data.push([+now, Math.round((Math.random() - 0.5) * 20 + data[i - 1][1])]);
    }
    
    option = {
      grid: {
        top: '40px',
        bottom: '60px',
        left: '50px',
        right: '30px'
      },
      xAxis: {
        type: 'time',
        boundaryGap: false,
        axisLine: {onZero: false},
        axisPointer:
        {
          show: true,
          type: 'line',
        },
      },
      yAxis: {
        type: 'value',
        boundaryGap: [0, '100%']
      },
      series: [
        {
        name: 'Fake Data',
        type: 'line',
        smooth: true,
        symbol: 'none',
        areaStyle: {},
        data: data
        }
      ],
      graphic: {
        elements: [
        {
        type: 'group',
        left: 'center',
        draggable: 'horizontal',
        ondrag: function (params) {    
          var pointInPixel = [params.offsetX, params.offsetY];
          var pointInGrid = myChart.convertFromPixel('grid', pointInPixel);
    
          var xTime = new Date(pointInGrid[0])
    
          //get closest value from cursor
          var point = data.reduce((prev, curr) => Math.abs(new Date(curr[0]).valueOf() - xTime.valueOf()) < Math.abs(new Date(prev[0]).valueOf() - xTime.valueOf()) ? curr : prev)
    
          //console.log('poi', new Date(pointInGrid[0]), new Date(point[0]), point[1])
    
          var d = document.getElementById('value2');
          d.style.left = params.offsetX+'px';
          d.innerHTML = point[1]
        },
        children: [
        {
          id: 'bar1',
          type: 'rect',
          top: '30px',
          shape: {
              width: 2,
              height: 685
          },
          style: {
              fill: "#ff0000"
          },
          cursor: 'ew-resize'
        },
        {
          type: 'circle',
          top: '740px',
          shape: {
              r:10
          },
          style: {
              fill: "#ff0000"
          },
        }
        ]
        },
        {
          type: 'group',
          left: '150px',
          draggable: 'horizontal',
          ondrag: function (params) {    
            var pointInPixel = [params.offsetX, params.offsetY];
            var pointInGrid = myChart.convertFromPixel('grid', pointInPixel);
    
            var xTime = new Date(pointInGrid[0])
    
            //get closest value from cursor
            var point = data.reduce((prev, curr) => Math.abs(new Date(curr[0]).valueOf() - xTime.valueOf()) < Math.abs(new Date(prev[0]).valueOf() - xTime.valueOf()) ? curr : prev)
    
            //console.log('poi', new Date(pointInGrid[0]), new Date(point[0]), point[1])
    
            var d = document.getElementById('value1');
            d.style.left = params.offsetX+'px';
            d.innerHTML = point[1]
          },
          children: [
            {
                type: 'rect',
                top: '30px',
                shape: {
                    width: 2,
                    height: 685
                },
                style: {
                    fill: "#0000ff"
                },
                cursor: 'ew-resize'
            },
          ]
        },
      ]}
    };
    
    myChart .setOption(option)
    <html>
      <body>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.3.2/echarts.min.js"></script>
        <div id="main" style="width: 1200px; height:775px;"></div>
        <div id="value1" style="background-color: blue; color: white; position: absolute; top: 280px; left: 185px">0</div>
        <div id="value2" style="background-color: red; position: absolute; top: 280px; left: 605px">0</div>
      </body>
    </html>

    The graphic.elements.ondrag function is quite interesting as it allows to get the value of the series along the cursor. Here on the example, the value is displayed in a div that is above the 'cursor', but it can be displayed in a prettier way somewhere else.