如何减less数据图表,但保持极端

我有一个数据库,每隔10分钟就有一个月的数据集。 (所以每10分钟一个数据集)

现在我想在三个图表上显示数据:过去24小时,过去7天和过去30天。

数据如下所示:

{ "data" : 278, "date" : ISODate("2016-08-31T01:51:05.315Z") } { "data" : 627, "date" : ISODate("2016-08-31T01:51:06.361Z") } { "data" : 146, "date" : ISODate("2016-08-31T01:51:07.938Z") } // etc 

对于24小时图,我只是输出最后24小时的数据,这很容易。

对于其他图表,我减less了数据:

 const data = {}; //data from database let newData = []; const interval = 7; //for 7 days the interval is 7, for 30 days it's 30 for( let i = 0; i < data.length; i += interval ) { newData.push( data[ i ] ); }; 

这工作正常,但极端事件,其中data0或与其他值平均大大不同,可能会丢失取决于您search数据的时间。 但是,不稀疏数据会导致通过pipe道发送的大量数据点必须在前端进行处理。 我想避免这一点。

现在我的问题

我怎样才能减less7天的数据,同时保持极端呢? 这里最有效的方法是什么?

补充 :从本质上讲,我认为我试图简化一个图来减less点,但保持整体形状。 (如果您从纯粹的图像angular度来看待它)

像节点中的Douglas-Peuckeralgorithm的实现? 在这里输入图像描述

正如您在评论中提到的,Ramer-Douglas-Peucker(RDP)algorithm用于处理2D数据中的数据点,但是您希望将其用于X值固定的graphics数据。 我修改了这个由M Oehm提供的algorithm的Javascript实现,只考虑了计算中的垂直(Y)距离。

另一方面,数据平滑通常build议减less图中数据点的数量(见csgillespie的这篇文章 )。

为了比较这两种方法,我做了一个小的testing程序。 重置button创build新的testing数据。 一个algorithm可以被select和应用来获得减less的点数,由指定的时间间隔分开。 然而,在RDPalgorithm的情况下,得到的点不是均匀分布的。 为了得到与指定时间间隔相同的点数,我反复运行计算,每次调整espilon值,直到达到正确的点数。

从我的testing中,RDPalgorithm提供了更好的结果。 唯一的缺点是点之间的间距不同。 我不认为这是可以避免的,因为我们要保持原始数据中不均匀分布的极端点。

这里是代码片断,在全页面模式中更好看:

 var svgns = 'http://www.w3.org/2000/svg'; var graph = document.getElementById('graph1'); var grpRawData = document.getElementById('grpRawData'); var grpCalculatedData = document.getElementById('grpCalculatedData'); var btnReset = document.getElementById('btnReset'); var cmbMethod = document.getElementById('cmbMethod'); var btnAddCalculated = document.getElementById('btnAddCalculated'); var btnClearCalculated = document.getElementById('btnClearCalculated'); var data = []; var calculatedCount = 0; var colors = ['black', 'red', 'green', 'blue', 'orange', 'purple']; var getPeriod = function () { return parseInt(document.getElementById('txtPeriod').value, 10); }; var clearGroup = function (grp) { while (grp.lastChild) { grp.removeChild(grp.lastChild); } }; var showPoints = function (grp, pts, markerSize, color) { var i, point; for (i = 0; i < pts.length; i++) { point = pts[i]; var marker = document.createElementNS(svgns, 'circle'); marker.setAttributeNS(null, 'cx', point.x); marker.setAttributeNS(null, 'cy', point.y); marker.setAttributeNS(null, 'r', markerSize); marker.setAttributeNS(null, 'fill', color); grp.appendChild(marker); } }; // Create and display test data var showRawData = function () { var i, x, y; var r = 0; data = []; for (i = 1; i < 500; i++) { x = i; r += 15.0 * (Math.random() * Math.random() - 0.25); y = 150 + 30 * Math.sin(x / 200) * Math.sin((x - 37) / 61) + 2 * Math.sin((x - 7) / 11) + r; data.push({ x: x, y: y }); } showPoints(grpRawData, data, 1, '#888'); }; // Gaussian kernel smoother var createGaussianKernelData = function () { var i, x, y; var r = 0; var result = []; var period = getPeriod(); for (i = Math.floor(period / 2) ; i < data.length; i += period) { x = data[i].x; y = gaussianKernel(i); result.push({ x: x, y: y }); } return result; }; var gaussianKernel = function (index) { var halfRange = Math.floor(getPeriod() / 2); var distance, factor; var totalValue = 0; var totalFactor = 0; for (i = index - halfRange; i <= index + halfRange; i++) { if (0 <= i && i < data.length) { distance = Math.abs(i - index); factor = Math.exp(-Math.pow(distance, 2)); totalFactor += factor; totalValue += data[i].y * factor; } } return totalValue / totalFactor; }; // Ramer-Douglas-Peucker algorithm var ramerDouglasPeuckerRecursive = function (pts, first, last, eps) { if (first >= last - 1) { return [pts[first]]; } var slope = (pts[last].y - pts[first].y) / (pts[last].x - pts[first].x); var x0 = pts[first].x; var y0 = pts[first].y; var iMax = first; var max = -1; var p, dy; // Calculate vertical distance for (var i = first + 1; i < last; i++) { p = pts[i]; y = y0 + slope * (px - x0); dy = Math.abs(py - y); if (dy > max) { max = dy; iMax = i; } } if (max < eps) { return [pts[first]]; } var p1 = ramerDouglasPeuckerRecursive(pts, first, iMax, eps); var p2 = ramerDouglasPeuckerRecursive(pts, iMax, last, eps); return p1.concat(p2); } var internalRamerDouglasPeucker = function (pts, eps) { var p = ramerDouglasPeuckerRecursive(data, 0, pts.length - 1, eps); return p.concat([pts[pts.length - 1]]); } var createRamerDouglasPeuckerData = function () { var finalPointCount = Math.round(data.length / getPeriod()); var epsilon = getPeriod(); var pts = internalRamerDouglasPeucker(data, epsilon); var iteration = 0; // Iterate until the correct number of points is obtained while (pts.length != finalPointCount && iteration++ < 20) { epsilon *= Math.sqrt(pts.length / finalPointCount); pts = internalRamerDouglasPeucker(data, epsilon); } return pts; }; // Event handlers btnReset.addEventListener('click', function () { calculatedCount = 0; clearGroup(grpRawData); clearGroup(grpCalculatedData); showRawData(); }); btnClearCalculated.addEventListener('click', function () { calculatedCount = 0; clearGroup(grpCalculatedData); }); btnAddCalculated.addEventListener('click', function () { switch (cmbMethod.value) { case "Gaussian": showPoints(grpCalculatedData, createGaussianKernelData(), 2, colors[calculatedCount++]); break; case "RDP": showPoints(grpCalculatedData, createRamerDouglasPeuckerData(), 2, colors[calculatedCount++]); return; } }); showRawData(); 
 div { margin-bottom: 6px; } 
 <div> <button id="btnReset">Reset</button>&nbsp; <select id="cmbMethod"> <option value="RDP">Ramer-Douglas-Peucker</option> <option value="Gaussian">Gaussian kernel</option> </select>&nbsp; <label for="txtPeriod">Interval: </label> <input id="txtPeriod" type="text" style="width: 36px;" value="7" /> </div> <div> <button id="btnAddCalculated">Add calculated points</button> <button id="btnClearCalculated">Clear calculated points</button> </div> <svg id="svg1" width="765" height="450" viewBox="0 0 510 300"> <g id="graph1" transform="translate(0,300) scale(1,-1)"> <rect width="500" height="300" stroke="black" fill="#eee"></rect> <g id="grpRawData"></g> <g id="grpCalculatedData"></g> </g> </svg>