aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMateusz Kurek <mkurek@users.noreply.github.com>2016-07-12 10:47:27 +0200
committerGitHub <noreply@github.com>2016-07-12 10:47:27 +0200
commit81b4da9b14c3b2af72e9ccbabeb727675d98c3ea (patch)
treeeb2cf276e343d935777a86d4dc159e98e0e4b21e
parentdd70294118ebba97759681158853cc90a2b8c696 (diff)
parentf6a8a400223ffd7cd402eade90fcf1015ffa1d4c (diff)
downloadtipboard-81b4da9b14c3b2af72e9ccbabeb727675d98c3ea.tar.gz
tipboard-81b4da9b14c3b2af72e9ccbabeb727675d98c3ea.tar.bz2
tipboard-81b4da9b14c3b2af72e9ccbabeb727675d98c3ea.zip
Merge pull request #38 from misiek08/develop
Allow to simplify massive data to make charts more readable
-rw-r--r--doc/tile__line_chart.rst20
-rw-r--r--tipboard/settings.py1
-rw-r--r--tipboard/static/js/lib/simplify.js121
-rw-r--r--tipboard/tiles/line_chart.js66
4 files changed, 207 insertions, 1 deletions
diff --git a/doc/tile__line_chart.rst b/doc/tile__line_chart.rst
index ce7152d..3ad764d 100644
--- a/doc/tile__line_chart.rst
+++ b/doc/tile__line_chart.rst
@@ -52,7 +52,7 @@ Example::
::
- value = {<jqplot_config>}
+ value = {<jqplot_config>, simplify: <simplify_config>}
where:
@@ -74,6 +74,24 @@ Example::
-- this will set up the grid (in white color), black background and will turn
off shadow effects as well as borders.
+simplify_config
+
+::
+
+ simplify_config = {
+ tolerancy: 10,
+ data_points_limit: 50, // we will TRY to achieve lower number of data points than this
+ max_simplifying_steps: 5,
+ simplify_step_multiplicator: 1.5
+ };
+
+Each option is self-describing. This feature tries to optimize dataset to achieve points count lower than `data_points_limit`. If simplify_config is not set, there won't be any simplify process at all (you will just have your raw data displayed).
+
+::
+
+ curl -X POST http://127.0.0.1:7272/api/v0.1/dev_key/tileconfig/test_line
+ -d 'value={"simplify": {"tolerancy": 2}}'
+
.. note::
In case of displaying multiple plots on a single chart (e.g. for more than
diff --git a/tipboard/settings.py b/tipboard/settings.py
index 83d344d..cedd034 100644
--- a/tipboard/settings.py
+++ b/tipboard/settings.py
@@ -104,6 +104,7 @@ TIPBOARD_CSS_STYLES = [
]
TIPBOARD_JAVASCRIPTS = [
'js/lib/jquery.js',
+ 'js/lib/simplify.js',
'js/lib/jquery.fullscreen.js',
'js/lib/jqplot/jquery.jqplot.js',
'js/lib/jqplot/plugins/jqplot.trendline.js',
diff --git a/tipboard/static/js/lib/simplify.js b/tipboard/static/js/lib/simplify.js
new file mode 100644
index 0000000..63a5ddb
--- /dev/null
+++ b/tipboard/static/js/lib/simplify.js
@@ -0,0 +1,121 @@
+/*
+ (c) 2013, Vladimir Agafonkin
+ Simplify.js, a high-performance JS polyline simplification library
+ mourner.github.io/simplify-js
+*/
+
+(function () { 'use strict';
+
+// to suit your point format, run search/replace for '.x' and '.y';
+// for 3D version, see 3d branch (configurability would draw significant performance overhead)
+
+// square distance between 2 points
+function getSqDist(p1, p2) {
+
+ var dx = p1.x - p2.x,
+ dy = p1.y - p2.y;
+
+ return dx * dx + dy * dy;
+}
+
+// square distance from a point to a segment
+function getSqSegDist(p, p1, p2) {
+
+ var x = p1.x,
+ y = p1.y,
+ dx = p2.x - x,
+ dy = p2.y - y;
+
+ if (dx !== 0 || dy !== 0) {
+
+ var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
+
+ if (t > 1) {
+ x = p2.x;
+ y = p2.y;
+
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = p.x - x;
+ dy = p.y - y;
+
+ return dx * dx + dy * dy;
+}
+// rest of the code doesn't care about point format
+
+// basic distance-based simplification
+function simplifyRadialDist(points, sqTolerance) {
+
+ var prevPoint = points[0],
+ newPoints = [prevPoint],
+ point;
+
+ for (var i = 1, len = points.length; i < len; i++) {
+ point = points[i];
+
+ if (getSqDist(point, prevPoint) > sqTolerance) {
+ newPoints.push(point);
+ prevPoint = point;
+ }
+ }
+
+ if (prevPoint !== point) newPoints.push(point);
+
+ return newPoints;
+}
+
+function simplifyDPStep(points, first, last, sqTolerance, simplified) {
+ var maxSqDist = 0,
+ index;
+
+ for (var i = first + 1; i < last; i++) {
+ var sqDist = getSqSegDist(points[i], points[first], points[last]);
+
+ if (sqDist > maxSqDist) {
+ index = i;
+ maxSqDist = sqDist;
+ }
+ }
+
+ if (maxSqDist > sqTolerance) {
+ if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
+ simplified.push(points[index]);
+ if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
+ }
+}
+
+// simplification using Ramer-Douglas-Peucker algorithm
+function simplifyDouglasPeucker(points, sqTolerance) {
+ var last = points.length - 1;
+
+ var simplified = [points[0]];
+ simplifyDPStep(points, 0, last, sqTolerance, simplified);
+ simplified.push(points[last]);
+
+ return simplified;
+}
+
+// both algorithms combined for awesome performance
+function simplify(points, tolerance, highestQuality) {
+
+ if (points.length <= 2) return points;
+
+ var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
+
+ points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
+ points = simplifyDouglasPeucker(points, sqTolerance);
+
+ return points;
+}
+
+// export as AMD module / Node module / browser or worker variable
+if (typeof define === 'function' && define.amd) define(function() { return simplify; });
+else if (typeof module !== 'undefined') module.exports = simplify;
+else if (typeof self !== 'undefined') self.simplify = simplify;
+else window.simplify = simplify;
+
+})();
diff --git a/tipboard/tiles/line_chart.js b/tipboard/tiles/line_chart.js
index 0d91fbe..b4a023c 100644
--- a/tipboard/tiles/line_chart.js
+++ b/tipboard/tiles/line_chart.js
@@ -1,6 +1,69 @@
/*jslint browser: true, devel: true*/
/*global WebSocket: false, Tipboard: false*/
+function simplifyLineData(series_data, user_config) {
+ var config = {
+ tolerancy: 10,
+ data_points_limit: 50, // we will TRY to achieve lower number of data points than this
+ max_simplifying_steps: 5,
+ simplify_step_multiplicator: 1.5
+ };
+
+ $.extend(config, user_config);
+
+ var simplify_data = new Array();
+ var return_data = new Array();
+
+ for(var series = 0; series < series_data.length; series++) {
+ simplify_data[series] = new Array();
+ return_data[series] = new Array();
+
+ // converting data to format acceptable by simplify.js library
+ if(typeof series_data[series][0] === typeof []) {
+ for(var tick = 0; tick < series_data[series].length; tick++) {
+ simplify_data[series].push({
+ x: tick,
+ y: series_data[series][tick][1],
+ key: series_data[series][tick][0]
+ });
+ }
+ } else {
+ for(var tick = 0; tick < series_data[series].length; tick++) {
+ simplify_data[series].push({
+ x: tick,
+ y: series_data[series][tick],
+ key: null
+ });
+ }
+ }
+
+ var current_tolerance = config.tolerancy;
+ for(var i = 0; i < config.max_simplifying_steps; i++) {
+ simplify_data[series] = simplify(simplify_data[series], current_tolerance);
+ if(simplify_data[series].length < config.data_points_limit) break;
+ current_tolerance = Math.floor(current_tolerance * config.simplify_step_multiplicator);
+ }
+
+ // prepare in data format understandable by jqplot
+ for(var tick = 0; tick < simplify_data[series].length; tick++) {
+ if(simplify_data[series][tick].key != null)
+ return_data[series][tick] = [
+ simplify_data[series][tick].key,
+ simplify_data[series][tick].y
+ ];
+ else
+ return_data[series][simplify_data[series][tick].x] = simplify_data[series][tick].y;
+ }
+
+ // fill all created gaps with null for jqplot
+ for(var i = 0; i < return_data[series].length; i++) {
+ if(!return_data[series][i])
+ return_data[series][i] = null;
+ }
+ }
+
+ return return_data;
+}
function updateTileLine(tileId, data, meta, tipboard) {
var tile = Tipboard.Dashboard.id2node(tileId);
@@ -21,6 +84,9 @@ function updateTileLine(tileId, data, meta, tipboard) {
// TODO use Tipboard.Dashboard.buildChart
Tipboard.DisplayUtils.expandLastChild(tile);
Tipboard.DisplayUtils.expandLastChild($(tile).find('.tile-content')[0]);
+
+ if(config.simplify) data.series_list = simplifyLineData(data.series_list, config.simplify);
+
Tipboard.Dashboard.chartsIds[tileId] = $.jqplot(
tileId + '-chart', data.series_list, config
);