Skip to content
Advertisement

Echarts: Plot the variance of signals

I want to plot the variance of multiple signals in a chart (or basically fillup the space between an upper and a lower signal). Is it possible to create such kind of charts?

Example of a chart with signal and its variance

I saw the confidence-band example (https://echarts.apache.org/examples/en/editor.html?c=confidence-band) , however this seems to work only for one signal in a chart.

Another solution would be to draw thousands of small rectangles using markArea around the signals but this slows down the performance of the chart (e.g. when scrolling the x-axisis) and doesnt look very smooth.

Advertisement

Answer

As I know the common practice in Echarts community draw usual chart type (bar, line, …) with series (read docs) and write visual logic by custom series for unique. Also Echarts has some API methods (undocumented) like registerVisual, registerLayout that can be used for redefine layouts, computation and so on.

For described task you need to use custom series for calculate bands coordinates. It’s not very simple because (it seems to me) mandatory requirements with confidence band is rare.

About performance. Echarts by default use Canvas for render visual parts. Usually Canvas has no many parts in HTML for display chart, it’s just imageData rendered by browser and it almost doesn’t matter how many data point need to display. In other words, we see PNG, and not a lot of div, svg, g and others layers with geometric primitives as in SVG but heavy computation complex business logic may affect the responsiveness of UI as in other charts.

Below example how would I implement this feature. I’m not sure that’s the right way but it work and can be tuned.

    var dates = ['2020-01-03','2020-01-31','2020-02-17','2020-02-18','2020-03-13','2020-04-10','2020-05-01','2020-05-19','2020-05-22','2020-05-25'];
    var sensor1 = [0.6482086334797242, 0.9121368038482911, 0.3205730196548609, 0.8712238348969002, 0.4487714576177558, 0.9895025457815625, 0.0415490306934774, 0.1592908349676395, 0.5356690594518069, 0.9949108727912939];
    var sensor2 = [0.8278430459565170, 0.5700757488718124, 0.9803575576802187, 0.0770264671179814,0.2843735619252158,0.8140209568127250,0.6055633547296827,0.9554255125528607,0.1703504100638565,0.5653245914197297];

    // Calculate fake bands coordinates
    function calcContourCoords(seriesData, ctx){
      var addNoise = idx => Math.round(Math.random() * 8 * idx);
      var pixelCoords = seriesData.map((dataPoint, idx) => {
        return [
          ctx.convertToPixel({ xAxisIndex: 0 }, idx) + addNoise(idx),
          ctx.convertToPixel({ yAxisIndex: 0 }, dataPoint) + addNoise(idx)
        ]
      });

      var polyfilltype = ClipperLib.PolyFillType.pftEvenOdd;
      var linePath = new ClipperLib.Path();
      var delta = 15;
      var scale = 1;

      for (var i = 0; i < pixelCoords.length; i++){
        var point = new ClipperLib.IntPoint(...pixelCoords[i]);
        linePath.push(point);
      }

      var co = new ClipperLib.ClipperOffset(1.0, 0.25);
          co.AddPath(linePath, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etOpenSquare);
          co.Execute(linePath, delta * scale);

      return co.m_destPoly.map(c => [c.X, c.Y])
    }

    // Render visual by calculated coords
    function renderItem(params, api){

      // Prevent multiple call
      if (params.context.rendered) return;
      params.context.rendered = true;

      // Get stored in series data for band
      var series = myChart.getModel().getSeriesByName(params.seriesName)[0];
      var seriesData = series.get('data');

      // Calculate band coordinates for series
      var bandCoords = calcContourCoords(seriesData, myChart);

      // Draw band
      return {
        type: 'polygon',
        shape: {
          points: echarts.graphic.clipPointsByRect(bandCoords, {
            x: params.coordSys.x,
            y: params.coordSys.y,
            width: params.coordSys.width,
            height: params.coordSys.height
          })
        },
        style: api.style({
          fill: series.option.itemStyle.color
        })
      };
    }

    // =============

  var option = {
      tooltip: {},
      legend: {
        data:['Label']
      },
      xAxis: [
        { name: 'x0', data: dates, boundaryGap: true },
        { name: 'x1', data: dates, boundaryGap: true, show: false },
      ],
      yAxis: [
        { name: 'y0' },
        { name: 'y1', show: false },
      ],
      series: [

        // First line
        {
          name: 'Sensor1',
          type: 'line',
          data: sensor1,
          itemStyle: { color: 'rgba(69, 170, 242, 1)' },
          yAxisIndex: 0,
          xAxisIndex: 0,
        },
        {
          name: 'BandSensor1',
          type: 'custom',
          data: sensor1,
          itemStyle: { color: 'rgba(69, 170, 242, 0.2)' },
          renderItem: renderItem,
          yAxisIndex: 0,
          xAxisIndex: 0,
        },

        // Second line
        {
          name: 'Sensor2',
          type: 'line',
          data: sensor2,
          itemStyle: { color: 'rgba(253, 151, 68, 1)' },
          yAxisIndex: 1,
          xAxisIndex: 1,
        },
        {
          name: 'BandSensor2',
          type: 'custom',
          data: sensor2,
          itemStyle: { color: 'rgba(253, 151, 68, 0.2)' },
          renderItem: renderItem,
          yAxisIndex: 1,
          xAxisIndex: 1,
        },
      ]
  };

  var myChart = echarts.init(document.getElementById('main'));
      myChart.setOption(option);
<script src="https://cdn.jsdelivr.net/npm/clipper-lib@6.4.2/clipper.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/4.7.0/echarts.min.js"></script>
<div id="main" style="width: 800px;height:600px;"></div>
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement