dygraphs support now panning and zooming
Costa Tsaousis (ktsaou) committed
Dec 4, 2015 at 02:40 UTC
f2bfa0dd60c7a99716ff071fb71d8f41d96e1ebf
2 files changed
+287
-41
web/dashboard.html
+2
-2
@@ -167,8 +167,8 @@ the total of all dimensions.
167
<hr>
168
<h1>Dygraph Charts</h1>
169
The fastest charting engine that can chart complete charts (not just sparklines).
170
-The charts are zoomable (drag their contents to zoom-in, double click to zoom-out).
171
-For the moment zooming is just on the presentation layer (the data do not become more detailed when you zoom).
170
+The charts are zoomable (drag their contents to pan, shift with mouse wheel to zoom-in or zoom-out, double click to reset it).
171
+<b>Netdata magic!</b> Realtime charts on your web page!
172
<br/>
173
<div style="width: 32%; display: inline-block;">
174
<div data-netdata="system.processes"
web/dashboard.js
+285
-39
@@ -90,6 +90,8 @@
90
updated_dom: 1,
91
last_paused: 0,
92
page_is_visible: 1,
93
+ double_click_ms: 100,
94
+ refreshed_stop_until: 0,
95
96
current: {
97
pixels_per_point: 1,
@@ -102,12 +104,13 @@
104
debug: {
105
show_boxes: 0,
106
main_loop: 0,
105
- focus: 1,
107
+ focus: 0,
108
visibility: 0,
107
- chart_data_url: 1,
108
- chart_errors: 1,
109
+ chart_data_url: 0,
110
+ chart_errors: 0,
111
chart_timing: 0,
112
chart_calls: 0,
113
+ dygraph: 0
114
}
115
}
116
@@ -158,7 +161,7 @@
161
+ message
162
+ '</small></div>';
163
161
- self.data('created', false);
164
+ self.data('chart-created', false);
165
}
166
167
// ----------------------------------------------------------------------------------------------------------------
@@ -255,6 +258,11 @@
258
var dimensions = self.data('dimensions') || null;
259
var pixels_per_point = self.data('pixels-per-point') || NETDATA.chartLibraries[library].pixels_per_point;
260
261
+ if(self.data('chart-panning') || self.data('chart-zooming')) {
262
+ before = self.data('panning-before') || 0;
263
+ after = self.data('panning-after') || 0;
264
+ }
265
+
266
// force an options provided detail
267
if(pixels_per_point < NETDATA.options.current.pixels_per_point)
268
pixels_per_point = NETDATA.options.current.pixels_per_point
@@ -267,8 +275,9 @@
275
var url = host + chart.data_url;
276
url += "&format=" + format;
277
url += "&points=" + points.toString();
270
- url += "&options=" + options;
278
url += "&group=" + method;
279
+ url += "&options=" + options;
280
+ if(NETDATA.chartLibraries[library].jsonWrapper) url += '|jsonwrap';
281
282
if(after)
283
url += "&after=" + after.toString();
@@ -282,6 +291,7 @@
291
self.data('calculated-width', width)
292
.data('calculated-height', height)
293
.data('calculated-points', points)
294
+ .data('calculated-options', options)
295
.data('calculated-url', url);
296
297
if(NETDATA.options.debug.chart_data_url) console.log('generateChartDataURL(): ' + url + ' WxH:' + width + 'x' + height + ' points: ' + points + ' library: ' + library);
@@ -301,8 +311,13 @@
311
if(!self.data('prepared')) {
312
self.data('prepared', true)
313
.data('updated', 0)
304
- .data('created', false)
305
- .data('enabled', false);
314
+ .data('chart-created', false)
315
+ .data('refresher-enabled', false)
316
+ .data('chart-panning', false)
317
+ .data('chart-zooming', false)
318
+ .data('chart-paused', false)
319
+ .data('panning-before', 0)
320
+ .data('panning-after', 0);
321
322
var id = self.data('netdata');
323
var host = self.data('host') || NETDATA.chartDefaults.host;
@@ -336,7 +351,7 @@
351
.done(function(chart) {
352
self.data('chart', chart)
353
.data('update-every', chart.update_every * 1000)
339
- .data('enabled', true)
354
+ .data('refresher-enabled', true)
355
.data('host', host)
356
.data('chart-library', library);
357
})
@@ -389,15 +404,18 @@
404
.bind('create', function(event, data) {
405
var self = $(this);
406
392
- if(NETDATA.options.debug.chart_errors)
407
+ if(NETDATA.options.debug.chart_errors) {
408
NETDATA.chartLibraries[self.data('chart-library')].create(this, data);
409
+ self.data('chart-created', true);
410
+ }
411
else {
412
try {
413
NETDATA.chartLibraries[self.data('chart-library')].create(this, data);
414
+ self.data('chart-created', true);
415
}
416
catch(err) {
417
NETDATA.messageInABox(this, 'chart "' + self.data('netdata') + '" failed to be created as ' + self.data('chart-library'));
400
- self.data('created', false);
418
+ self.data('chart-created', false);
419
}
420
}
421
})
@@ -411,7 +429,7 @@
429
}
430
catch(err) {
431
NETDATA.messageInABox(this, 'chart "' + self.data('netdata') + '" failed to be updated as ' + self.data('chart-library'));
414
- self.data('created', false);
432
+ self.data('chart-created', false);
433
}
434
}
435
});
@@ -436,18 +454,18 @@
454
//var c = new chart();
455
//c.color();
456
439
- NETDATA.chartValuesDownloader = function(element, callback) {
457
+ NETDATA.chartValuesDownloader = function(element, callback, panning) {
458
var self = $(element);
459
var last = self.data('updated') || 0;
460
var every = self.data('update-every') || 1;
461
462
// check if this chart has to be refreshed now
463
var now = new Date().getTime();
446
- if(last + every > now) {
464
+ if(!panning && last + every > now) {
465
if(NETDATA.options.debug.chart_timing) console.log(self.data('netdata') + ' too soon - skipping.');
466
if(typeof callback == 'function') callback();
467
}
450
- else if(!self.visible(true)) {
468
+ else if(!panning && !self.visible(true)) {
469
if(NETDATA.options.debug.visibility) console.log(self.data('netdata') + ' is NOT visible.');
470
if(typeof callback == 'function') callback();
471
}
@@ -461,11 +479,18 @@
479
var started = new Date().getTime();
480
481
// if the result is JSON, find the latest update-every
464
- if(NETDATA.chartLibraries[self.data('chart-library')].jsonWrapper &&
465
- typeof data.update_every != 'undefined')
482
+ if(NETDATA.chartLibraries[self.data('chart-library')].jsonWrapper) {
483
+ if(!panning && typeof data.update_every != 'undefined')
484
self.data('update-every', data.update_every * 1000);
485
468
- if(self.data('created')) {
486
+ if(typeof data.after != 'undefined')
487
+ self.data('panning-after', data.after);
488
+
489
+ if(typeof data.before != 'undefined')
490
+ self.data('panning-before', data.before);
491
+ }
492
+
493
+ if(self.data('chart-created')) {
494
if(NETDATA.options.debug.chart_calls) console.log('updating ' + self.data('chart-library') + ' chart ' + self.data('netdata'));
495
self.trigger('update', [data]);
496
// NETDATA.chartLibraries[self.data('chart-library')].update(element, data);
@@ -474,7 +499,6 @@
499
if(NETDATA.options.debug.chart_calls) console.log('creating ' + self.data('chart-library') + ' chart ' + self.data('netdata'));
500
self.trigger('create', [data]);
501
//NETDATA.chartLibraries[self.data('chart-library')].create(element, data);
477
- self.data('created', true);
502
}
503
504
var ended = new Date().getTime();
@@ -512,7 +536,13 @@
536
else {
537
now = new Date().getTime();
538
515
- if(NETDATA.options.updated_dom) {
539
+ if(now < NETDATA.options.refreshed_stop_until) {
540
+ if(NETDATA.options.debug.main_loop) console.log('refreshed stopped until...');
541
+ setTimeout(function() {
542
+ NETDATA.chartRefresher(index);
543
+ }, NETDATA.options.current.idle_between_loops);
544
+ }
545
+ else if(NETDATA.options.updated_dom) {
546
// the dom has been updated
547
// get the dom parts again
548
NETDATA.getDomCharts(function() {
@@ -531,7 +561,20 @@
561
}
562
else {
563
var self = $(target);
534
- if(!self.data('enabled')) {
564
+ if(!self.data('refresher-enabled')) {
565
+ if(NETDATA.options.debug.main_loop) console.log('chart has refresher disabled...');
566
+ NETDATA.chartRefresher(++index);
567
+ }
568
+ else if(self.data('chart-panning')) {
569
+ if(NETDATA.options.debug.main_loop) console.log('chart is panning...');
570
+ NETDATA.chartRefresher(++index);
571
+ }
572
+ else if(self.data('chart-zooming')) {
573
+ if(NETDATA.options.debug.main_loop) console.log('chart is zooming...');
574
+ NETDATA.chartRefresher(++index);
575
+ }
576
+ else if(self.data('chart-paused')) {
577
+ if(NETDATA.options.debug.main_loop) console.log('chart is paused...');
578
NETDATA.chartRefresher(++index);
579
}
580
else {
@@ -540,7 +583,7 @@
583
584
NETDATA.chartValuesDownloader(target, function() {
585
NETDATA.chartRefresher(++index);
543
- });
586
+ }, false);
587
}
588
else {
589
if(NETDATA.options.debug.main_loop) console.log('waiting for next refresh...');
@@ -549,7 +592,7 @@
592
setTimeout(function() {
593
NETDATA.chartValuesDownloader(target, function() {
594
NETDATA.chartRefresher(++index);
552
- });
595
+ }, false);
596
}, NETDATA.options.current.idle_between_charts);
597
}
598
}
@@ -606,7 +649,7 @@
649
650
self.data('peity-uuid', uuid)
651
.data('peity-instance', instance)
609
- .data('created', true);
652
+ .data('chart-created', true);
653
}
654
655
// ----------------------------------------------------------------------------------------------------------------
@@ -749,7 +792,7 @@
792
self.sparkline(data.result, options);
793
self.data('sparkline-options', options)
794
.data('uuid', uuid)
752
- .data('created', true);
795
+ .data('chart-created', true);
796
};
797
798
// ----------------------------------------------------------------------------------------------------------------
@@ -797,18 +840,207 @@
840
}
841
};
842
843
+ NETDATA.dygraphClickCallback = function(element, dygraph, event, x, points) {
844
+ if(NETDATA.options.debug.dygraph) console.log('dygraphClickCallback()');
845
+
846
+ // NETDATA.dygraphDoubleClickDetect(element, dygraph);
847
+ NETDATA.dygraphReset(element, dygraph);
848
+ }
849
+
850
+ NETDATA.dygraphPointClickCallback = function(element, dygraph, event, point) {
851
+ if(NETDATA.options.debug.dygraph) console.log('dygraphPointClickCallback()');
852
+
853
+ // NETDATA.dygraphDoubleClickDetect(element, dygraph);
854
+ NETDATA.dygraphReset(element, dygraph);
855
+ }
856
+
857
+ NETDATA.dygraphDrawCallback = function(element, dygraph, is_initial) {
858
+ if(NETDATA.options.debug.dygraph) console.log('dygraphDrawCallback()');
859
+
860
+ var x_range = dygraph.xAxisRange();
861
+ var after = Math.round(x_range[0] / 1000);
862
+ var before = Math.round(x_range[1] / 1000);
863
+
864
+ NETDATA.dygraphZoomOrPan(element, dygraph, after, before, false, true);
865
+ }
866
+
867
+ NETDATA.dygraphZoomCallback = function(element, dygraph, minDate, maxDate, yRanges) {
868
+ if(NETDATA.options.debug.dygraph) console.log('dygraphZoomCallback()');
869
+
870
+ NETDATA.dygraphZoomOrPan(element, dygraph, Math.round(minDate / 1000), Math.round(maxDate / 1000), false, true);
871
+ }
872
+
873
+ NETDATA.dygraphHighlightCallback = function(element, dygraph, event, x, points, row, seriesName) {
874
+ if(NETDATA.options.debug.dygraph) console.log('dygraphHighlightCallback()');
875
+
876
+ // stop refreshing
877
+ self = $(element);
878
+ self.data('chart-paused', true);
879
+ }
880
+
881
+ NETDATA.dygraphUnhighlightCallback = function(element, dygraph) {
882
+ if(NETDATA.options.debug.dygraph) console.log('dygraphUnhighlightCallback()');
883
+
884
+ // start refreshing
885
+ self = $(element);
886
+ self.data('chart-paused', false);
887
+ }
888
+
889
+ NETDATA.dygraphMouseDown = function(element, dygraph, event, context) {
890
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseDown()');
891
+
892
+ context.initializeMouseDown(event, dygraph, context);
893
+ if (event.altKey || event.shiftKey)
894
+ Dygraph.startZoom(event, dygraph, context);
895
+ else
896
+ Dygraph.startPan(event, dygraph, context);
897
+ }
898
+
899
+ NETDATA.dygraphMouseMove = function(element, dygraph, event, context) {
900
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseMove()');
901
+
902
+ if (context.isPanning)
903
+ Dygraph.movePan(event, dygraph, context);
904
+ else if (context.isZooming)
905
+ Dygraph.moveZoom(event, dygraph, context);
906
+ }
907
+
908
+ NETDATA.dygraphMouseUp = function(element, dygraph, event, context) {
909
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseUp()');
910
+
911
+ if (context.isPanning)
912
+ Dygraph.endPan(event, dygraph, context);
913
+ else if (context.isZooming)
914
+ Dygraph.endZoom(event, dygraph, context);
915
+ }
916
+
917
+ NETDATA.dygraphMouseClick = function(element, dygraph, event, context) {
918
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseClick()');
919
+
920
+ }
921
+
922
+ NETDATA.dygraphMouseDoubleClick = function(element, dygraph, event, context) {
923
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseDoubleClick()');
924
+ NETDATA.dygraphReset(element, dygraph);
925
+ }
926
+
927
+ NETDATA.dygraphMouseWheel = function(element, dygraph, event, context) {
928
+ if(NETDATA.options.debug.dygraph) console.log('dygraphMouseWheel()');
929
+
930
+ if(event.altKey || event.shiftKey) {
931
+ // http://dygraphs.com/gallery/interaction-api.js
932
+ var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
933
+ var percentage = normal / 10;
934
+
935
+ self = $(element);
936
+ var before_old = self.data('panning-before');
937
+ var after_old = self.data('panning-after');
938
+ var range_old = before_old - after_old;
939
+
940
+ var range = range_old * ( 1 - percentage );
941
+ var dt = Math.round((range_old - range) / 2);
942
+ before = before_old - dt;
943
+ after = after_old + dt;
944
+
945
+ if(NETDATA.options.debug.dygraph) console.log('percent: ' + percentage + ' from ' + after_old + ' - ' + before_old + ' to ' + after + ' - ' + before + ', range from ' + (before_old - after_old).toString() + ' to ' + (before - after).toString());
946
+
947
+ NETDATA.dygraphZoomOrPan(element, dygraph, after, before, true);
948
+ }
949
+ }
950
+
951
+ NETDATA.dygraphReset = function(element, dygraph) {
952
+ if(NETDATA.options.debug.dygraph) console.log('dygraphReset()');
953
+
954
+ self = $(element);
955
+
956
+ if(self.data('chart-panning') || self.data('chart-paused')) {
957
+ self.data('chart-panning', false)
958
+ .data('chart-zooming', false)
959
+ .data('chart-paused', false)
960
+ .data('panning-before', 0)
961
+ .data('panning-after', 0)
962
+ .data('updated', 0)
963
+ .data('panning-before-max', 0);
964
+
965
+ NETDATA.options.refreshed_stop_until = 0;
966
+ NETDATA.chartValuesDownloader(element, null, false);
967
+ }
968
+ }
969
+
970
+ NETDATA.dygraphZoomOrPan = function(element, dygraph, after, before, zoom) {
971
+ if(NETDATA.options.debug.dygraph) console.log('dygraphZoomOrPan()');
972
+
973
+ self = $(element);
974
+
975
+ var before_old = self.data('panning-before');
976
+ var after_old = self.data('panning-after');
977
+ var range_old = before_old - after_old;
978
+
979
+ var shift = Math.abs(before_old - before);
980
+
981
+ var range = before - after;
982
+
983
+ if((shift > range_old / 20 || zoom) && range > 10) {
984
+ // the user is panning
985
+ if(NETDATA.options.debug.dygraph) console.log('dygraphZoomOrPan() ' + after_old + ' - ' + before_old + ', NEW: ' + after + ' - ' + before + ', shift by: ' + shift + ' old range: ' + (before_old - after_old).toString() + ' new range: ' + (before - after).toString());
986
+
987
+ if(!self.data('chart-panning') && !self.data('chart-zooming')) {
988
+ self.data('chart-panning', true)
989
+ .data('panning-before-max', before);
990
+ }
991
+
992
+ var before_max = self.data('panning-before-max');
993
+ if(zoom || before <= before_max) {
994
+ NETDATA.options.refreshed_stop_until = now + 2000;
995
+
996
+ console.log('dygraphZoomOrPan() rendering.......................................................................................................');
997
+
998
+ if(zoom) {
999
+ self.data('chart-panning', false)
1000
+ .data('chart-zooming', true);
1001
+ }
1002
+ else {
1003
+ self.data('chart-panning', true)
1004
+ .data('chart-zooming', false);
1005
+ }
1006
+
1007
+ self.data('chart-paused', true)
1008
+ .data('panning-before', before)
1009
+ .data('panning-after', after)
1010
+ .data('updated', 0);
1011
+
1012
+ NETDATA.chartValuesDownloader(element, null, true);
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
NETDATA.dygraphChartUpdate = function(element, data) {
1018
+ if(NETDATA.options.debug.dygraph) console.log('dygraphChartUpdate()');
1019
+
1020
var self = $(element);
1021
var dygraph = self.data('dygraph-instance');
1022
804
- dygraph.updateOptions({
805
- file: data.result.data,
806
- labels: data.result.labels,
807
- labelsDivWidth: self.width() - 70
808
- });
1023
+ if(self.data('chart-panning')) {
1024
+ dygraph.updateOptions({
1025
+ file: data.result.data,
1026
+ labels: data.result.labels,
1027
+ labelsDivWidth: self.width() - 70
1028
+ });
1029
+ }
1030
+ else {
1031
+ dygraph.updateOptions({
1032
+ file: data.result.data,
1033
+ labels: data.result.labels,
1034
+ labelsDivWidth: self.width() - 70,
1035
+ dateWindow: null,
1036
+ valueRange: null
1037
+ });
1038
+ }
1039
};
1040
1041
NETDATA.dygraphChartCreate = function(element, data) {
1042
+ if(NETDATA.options.debug.dygraph) console.log('dygraphChartCreate()');
1043
+
1044
var self = $(element);
1045
var chart = self.data('chart');
1046
var title = self.data('dygraph-title') || chart.title;
@@ -922,7 +1154,21 @@
1154
y: {
1155
pixelsPerLabel: 15
1156
}
925
- }
1157
+ },
1158
+ //clickCallback: function(e, x, points) { NETDATA.dygraphClickCallback(element, this, e, x, points); },
1159
+ //pointClickCallback: function(e, point) { NETDATA.dygraphPointClickCallback(element, this, e, point); },
1160
+ drawCallback: function(dygraph, is_initial) { NETDATA.dygraphDrawCallback(element, dygraph, is_initial); },
1161
+ zoomCallback: function(minDate, maxDate, yRanges) { NETDATA.dygraphZoomCallback(element, this, minDate, maxDate, yRanges); },
1162
+ highlightCallback: function(event, x, points, row, seriesName) { NETDATA.dygraphHighlightCallback(element, this, event, x, points, row, seriesName); },
1163
+ unhighlightCallback: function(event) { NETDATA.dygraphUnhighlightCallback(element, this, event); },
1164
+ interactionModel : {
1165
+ mousedown: function(event, dygraph, context) { NETDATA.dygraphMouseDown(element, dygraph, event, context); },
1166
+ mousemove: function(event, dygraph, context) { NETDATA.dygraphMouseMove(element, dygraph, event, context); },
1167
+ mouseup: function(event, dygraph, context) { NETDATA.dygraphMouseUp(element, dygraph, event, context); },
1168
+ click: function(event, dygraph, context) { NETDATA.dygraphMouseClick(element, dygraph, event, context); },
1169
+ dblclick: function(event, dygraph, context) { NETDATA.dygraphMouseDoubleClick(element, dygraph, event, context); },
1170
+ mousewheel: function(event, dygraph, context) { NETDATA.dygraphMouseWheel(element, dygraph, event, context); }
1171
+ }
1172
};
1173
1174
var uuid = NETDATA.guid();
@@ -934,7 +1180,7 @@
1180
self.data('dygraph-instance', dchart)
1181
.data('dygraph-options', options)
1182
.data('uuid', uuid)
937
- .data('created', true);
1183
+ .data('chart-created', true);
1184
1185
//NETDATA.dygraphAllCharts.push(dchart);
1186
//if(NETDATA.dygraphAllCharts.length > 1)
@@ -1034,7 +1280,7 @@
1280
morris = new Morris.Area(options);
1281
1282
self.data('morris-instance', morris)
1037
- .data('created', true);
1283
+ .data('chart-created', true);
1284
};
1285
1286
// ----------------------------------------------------------------------------------------------------------------
@@ -1077,7 +1323,7 @@
1323
width: self.data('calculated-width'),
1324
height: self.data('calculated-height')
1325
})
1080
- .data('created', true);
1326
+ .data('chart-created', true);
1327
};
1328
1329
// ----------------------------------------------------------------------------------------------------------------
@@ -1214,7 +1460,7 @@
1460
self.data('google-instance', gchart)
1461
.data('google-options', options)
1462
.data('uuid', uuid)
1217
- .data('created', true);
1463
+ .data('chart-created', true);
1464
};
1465
1466
// ----------------------------------------------------------------------------------------------------------------
@@ -1228,7 +1474,7 @@
1474
initialized: false,
1475
enabled: true,
1476
format: 'json',
1231
- options: 'ms|flip|jsonwrap',
1477
+ options: 'ms|flip',
1478
jsonWrapper: true,
1479
pixels_per_point: 2,
1480
detects_dimensions_on_update: false
@@ -1240,7 +1486,7 @@
1486
initialized: false,
1487
enabled: true,
1488
format: 'array',
1243
- options: 'flip|abs|jsonwrap',
1489
+ options: 'flip|abs',
1490
jsonWrapper: true,
1491
pixels_per_point: 2,
1492
detects_dimensions_on_update: false
@@ -1252,7 +1498,7 @@
1498
initialized: false,
1499
enabled: true,
1500
format: 'ssvcomma',
1255
- options: 'null2zero|flip|abs|jsonwrap',
1501
+ options: 'null2zero|flip|abs',
1502
jsonWrapper: true,
1503
pixels_per_point: 2,
1504
detects_dimensions_on_update: false
@@ -1264,7 +1510,7 @@
1510
initialized: false,
1511
enabled: true,
1512
format: 'json',
1267
- options: 'objectrows|ms|jsonwrap',
1513
+ options: 'objectrows|ms',
1514
jsonWrapper: true,
1515
pixels_per_point: 10,
1516
detects_dimensions_on_update: false
@@ -1276,7 +1522,7 @@
1522
initialized: false,
1523
enabled: true,
1524
format: 'datatable',
1279
- options: 'jsonwrap',
1525
+ options: '',
1526
jsonWrapper: true,
1527
pixels_per_point: 2,
1528
detects_dimensions_on_update: true