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?
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>