@cryptotaxi247 / netdata-1 / commits / f2bfa0dd6

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