1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // You can also set the default netdata server, using the following.
22 // When this variable is not set, we assume the page is hosted on your
23 // netdata server already.
24 // var netdataServer = "http://yourhost:19999"; // set your NetData server
26 //(function(window, document, undefined) {
28 // ------------------------------------------------------------------------
29 // compatibility fixes
31 // fix IE issue with console
32 if(!window.console) { window.console = { log: function(){} }; }
34 // if string.endsWith is not defined, define it
35 if(typeof String.prototype.endsWith !== 'function') {
36 String.prototype.endsWith = function(s) {
37 if(s.length > this.length) return false;
38 return this.slice(-s.length) === s;
42 // if string.startsWith is not defined, define it
43 if(typeof String.prototype.startsWith !== 'function') {
44 String.prototype.startsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(s.length) === s;
51 var NETDATA = window.NETDATA || {};
53 // ----------------------------------------------------------------------------------------------------------------
54 // Detect the netdata server
56 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
57 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
58 NETDATA._scriptSource = function() {
61 if(typeof document.currentScript !== 'undefined') {
62 script = document.currentScript;
65 var all_scripts = document.getElementsByTagName('script');
66 script = all_scripts[all_scripts.length - 1];
69 if (typeof script.getAttribute.length !== 'undefined')
72 script = script.getAttribute('src', -1);
77 if(typeof netdataServer !== 'undefined')
78 NETDATA.serverDefault = netdataServer;
80 var s = NETDATA._scriptSource();
81 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
83 console.log('WARNING: Cannot detect the URL of the netdata server.');
84 NETDATA.serverDefault = null;
88 if(NETDATA.serverDefault === null)
89 NETDATA.serverDefault = '';
90 else if(NETDATA.serverDefault.slice(-1) !== '/')
91 NETDATA.serverDefault += '/';
93 // default URLs for all the external files we need
94 // make them RELATIVE so that the whole thing can also be
95 // installed under a web server
96 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
97 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
98 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
99 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
100 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
101 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
102 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
103 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
104 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
105 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
106 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
107 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
108 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
109 NETDATA.google_js = 'https://www.google.com/jsapi';
113 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
114 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
115 background: '#FFFFFF',
116 foreground: '#000000',
119 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
120 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
121 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
122 '#329262', '#3B3EAC' ],
123 easypiechart_track: '#f0f0f0',
124 easypiechart_scale: '#dfe0e0',
125 gauge_pointer: '#C0C0C0',
126 gauge_stroke: '#F0F0F0',
127 gauge_gradient: false
130 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
131 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
132 background: '#272b30',
133 foreground: '#C8C8C8',
136 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
137 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
138 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
139 '#a6a479', '#a66da8' ],
141 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
142 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
143 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
144 '#329262', '#3B3EFF' ],
145 easypiechart_track: '#373b40',
146 easypiechart_scale: '#373b40',
147 gauge_pointer: '#474b50',
148 gauge_stroke: '#373b40',
149 gauge_gradient: false
153 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
154 NETDATA.themes.current = NETDATA.themes[netdataTheme];
156 NETDATA.themes.current = NETDATA.themes.white;
158 NETDATA.colors = NETDATA.themes.current.colors;
160 // these are the colors Google Charts are using
161 // we have them here to attempt emulate their look and feel on the other chart libraries
162 // http://there4.io/2012/05/02/google-chart-color-list/
163 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
164 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
165 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
167 // an alternative set
168 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
169 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
170 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
172 if(typeof netdataShowHelp === 'undefined')
173 netdataShowHelp = true;
175 if(typeof netdataShowAlarms === 'undefined')
176 netdataShowAlarms = false;
178 if(typeof netdataRegistry === 'undefined') {
179 // backward compatibility
180 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
181 netdataRegistry = true;
183 netdataRegistry = false;
185 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
186 netdataRegistry = true;
188 // ----------------------------------------------------------------------------------------------------------------
189 // the defaults for all charts
191 // if the user does not specify any of these, the following will be used
193 NETDATA.chartDefaults = {
194 host: NETDATA.serverDefault, // the server to get data from
195 width: '100%', // the chart width - can be null
196 height: '100%', // the chart height - can be null
197 min_width: null, // the chart minimum width - can be null
198 library: 'dygraph', // the graphing library to use
199 method: 'average', // the grouping method
200 before: 0, // panning
201 after: -600, // panning
202 pixels_per_point: 1, // the detail of the chart
203 fill_luminance: 0.8 // luminance of colors in solit areas
206 // ----------------------------------------------------------------------------------------------------------------
210 pauseCallback: null, // a callback when we are really paused
212 pause: false, // when enabled we don't auto-refresh the charts
214 targets: null, // an array of all the state objects that are
215 // currently active (independently of their
216 // viewport visibility)
218 updated_dom: true, // when true, the DOM has been updated with
219 // new elements we have to check.
221 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
222 // rendering charts continiously.
223 // used with .current.fast_render_timeframe
225 page_is_visible: true, // when true, this page is visible
227 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
228 // auto-refresher for some time (when a chart is
229 // performing pan or zoom, we need to stop refreshing
230 // all other charts, to have the maximum speed for
231 // rendering the chart that is panned or zoomed).
232 // Used with .current.global_pan_sync_time
234 last_resized: new Date().getTime(), // the timestamp of the last resize request
236 last_page_scroll: 0, // the timestamp the last time the page was scrolled
238 // the current profile
239 // we may have many...
241 pixels_per_point: 1, // the minimum pixels per point for all charts
242 // increase this to speed javascript up
243 // each chart library has its own limit too
244 // the max of this and the chart library is used
245 // the final is calculated every time, so a change
246 // here will have immediate effect on the next chart
249 idle_between_charts: 100, // ms - how much time to wait between chart updates
251 fast_render_timeframe: 200, // ms - render continously until this time of continious
252 // rendering has been reached
253 // this setting is used to make it render e.g. 10
254 // charts at once, sleep idle_between_charts time
255 // and continue for another 10 charts.
257 idle_between_loops: 500, // ms - if all charts have been updated, wait this
258 // time before starting again.
260 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
262 idle_lost_focus: 500, // ms - when the window does not have focus, check
263 // if focus has been regained, every this time
265 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
266 // autorefreshing of charts is paused for this amount
269 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
270 // of time before setting up synchronized selections
273 sync_selection: true, // enable or disable selection sync
275 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
277 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
279 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
281 update_only_visible: true, // enable or disable visibility management
283 parallel_refresher: true, // enable parallel refresh of charts
285 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
287 destroy_on_hide: false, // destroy charts when they are not visible
289 show_help: netdataShowHelp, // when enabled the charts will show some help
290 show_help_delay_show_ms: 500,
291 show_help_delay_hide_ms: 0,
293 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
295 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
296 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
298 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
300 smooth_plot: true, // enable smooth plot, where possible
302 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
304 color_fill_opacity_line: 1.0,
305 color_fill_opacity_area: 0.2,
306 color_fill_opacity_stacked: 0.8,
308 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
309 pan_and_zoom_factor_multiplier_control: 2.0,
310 pan_and_zoom_factor_multiplier_shift: 3.0,
311 pan_and_zoom_factor_multiplier_alt: 4.0,
313 abort_ajax_on_scroll: false,
315 setOptionCallback: function() { ; }
323 chart_data_url: false,
324 chart_errors: false, // FIXME
332 NETDATA.statistics = {
335 refreshes_active_max: 0
339 // ----------------------------------------------------------------------------------------------------------------
340 // local storage options
342 NETDATA.localStorage = {
345 callback: {} // only used for resetting back to defaults
348 NETDATA.localStorageGet = function(key, def, callback) {
351 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
352 NETDATA.localStorage.default[key.toString()] = def;
353 NETDATA.localStorage.callback[key.toString()] = callback;
356 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
358 // console.log('localStorage: loading "' + key.toString() + '"');
359 ret = localStorage.getItem(key.toString());
360 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
361 if(ret === null || ret === 'undefined') {
362 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
363 localStorage.setItem(key.toString(), JSON.stringify(def));
367 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
368 ret = JSON.parse(ret);
369 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
373 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
378 if(typeof ret === 'undefined' || ret === 'undefined') {
379 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
383 NETDATA.localStorage.current[key.toString()] = ret;
387 NETDATA.localStorageSet = function(key, value, callback) {
388 if(typeof value === 'undefined' || value === 'undefined') {
389 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
392 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
393 NETDATA.localStorage.default[key.toString()] = value;
394 NETDATA.localStorage.current[key.toString()] = value;
395 NETDATA.localStorage.callback[key.toString()] = callback;
398 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
399 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
401 localStorage.setItem(key.toString(), JSON.stringify(value));
404 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
408 NETDATA.localStorage.current[key.toString()] = value;
412 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
414 if(typeof obj[i] === 'object') {
415 //console.log('object ' + prefix + '.' + i.toString());
416 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
420 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
424 NETDATA.setOption = function(key, value) {
425 if(key.toString() === 'setOptionCallback') {
426 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
427 NETDATA.options.current[key.toString()] = value;
428 NETDATA.options.current.setOptionCallback();
431 else if(NETDATA.options.current[key.toString()] !== value) {
432 var name = 'options.' + key.toString();
434 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
435 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
437 //console.log(NETDATA.localStorage);
438 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
439 //console.log(NETDATA.options);
440 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
442 if(typeof NETDATA.options.current.setOptionCallback === 'function')
443 NETDATA.options.current.setOptionCallback();
449 NETDATA.getOption = function(key) {
450 return NETDATA.options.current[key.toString()];
453 // read settings from local storage
454 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
456 // always start with this option enabled.
457 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
459 NETDATA.resetOptions = function() {
460 for(var i in NETDATA.localStorage.default) {
461 var a = i.split('.');
463 if(a[0] === 'options') {
464 if(a[1] === 'setOptionCallback') continue;
465 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
466 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
468 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
470 else if(a[0] === 'chart_heights') {
471 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
472 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
478 // ----------------------------------------------------------------------------------------------------------------
480 if(NETDATA.options.debug.main_loop === true)
481 console.log('welcome to NETDATA');
483 NETDATA.onresize = function() {
484 NETDATA.options.last_resized = new Date().getTime();
488 NETDATA.onscroll = function() {
489 // console.log('onscroll');
491 NETDATA.options.last_page_scroll = new Date().getTime();
492 NETDATA.options.auto_refresher_stop_until = 0;
494 if(NETDATA.options.targets === null) return;
496 // when the user scrolls he sees that we have
497 // hidden all the not-visible charts
498 // using this little function we try to switch
499 // the charts back to visible quickly
500 var targets = NETDATA.options.targets;
501 var len = targets.length;
502 if(NETDATA.options.abort_ajax_on_scroll === true) {
504 if (targets[len]._updating === true) {
505 if (typeof targets[len].xhr !== 'undefined') {
506 targets[len].xhr.abort();
507 targets[len].running = false;
508 targets[len]._updating = false;
510 targets[len].isVisible();
516 targets[len].isVisible();
520 window.onresize = NETDATA.onresize;
521 window.onscroll = NETDATA.onscroll;
523 // ----------------------------------------------------------------------------------------------------------------
526 NETDATA.errorCodes = {
527 100: { message: "Cannot load chart library", alert: true },
528 101: { message: "Cannot load jQuery", alert: true },
529 402: { message: "Chart library not found", alert: false },
530 403: { message: "Chart library not enabled/is failed", alert: false },
531 404: { message: "Chart not found", alert: false },
532 405: { message: "Cannot download charts index from server", alert: true },
533 406: { message: "Invalid charts index downloaded from server", alert: true },
534 407: { message: "Cannot HELLO netdata server", alert: false },
535 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
536 409: { message: "Cannot ACCESS netdata registry", alert: false },
537 410: { message: "Netdata registry ACCESS failed", alert: false },
538 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
539 412: { message: "Netdata registry DELETE failed", alert: false },
540 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
541 414: { message: "Netdata registry SWITCH failed", alert: false },
542 415: { message: "Netdata alarms download failed", alert: false },
543 416: { message: "Netdata alarms log download failed", alert: false }
545 NETDATA.errorLast = {
551 NETDATA.error = function(code, msg) {
552 NETDATA.errorLast.code = code;
553 NETDATA.errorLast.message = msg;
554 NETDATA.errorLast.datetime = new Date().getTime();
556 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
559 if(typeof netdataErrorCallback === 'function') {
560 ret = netdataErrorCallback('system', code, msg);
563 if(ret && NETDATA.errorCodes[code].alert)
564 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
567 NETDATA.errorReset = function() {
568 NETDATA.errorLast.code = 0;
569 NETDATA.errorLast.message = "You are doing fine!";
570 NETDATA.errorLast.datetime = 0;
573 // ----------------------------------------------------------------------------------------------------------------
576 // When multiple charts need the same chart, we avoid downloading it
577 // multiple times (and having it in browser memory multiple time)
578 // by using this registry.
580 // Every time we download a chart definition, we save it here with .add()
581 // Then we try to get it back with .get(). If that fails, we download it.
583 NETDATA.chartRegistry = {
586 fixid: function(id) {
587 return id.replace(/:/g, "_").replace(/\//g, "_");
590 add: function(host, id, data) {
591 host = this.fixid(host);
594 if(typeof this.charts[host] === 'undefined')
595 this.charts[host] = {};
597 //console.log('added ' + host + '/' + id);
598 this.charts[host][id] = data;
601 get: function(host, id) {
602 host = this.fixid(host);
605 if(typeof this.charts[host] === 'undefined')
608 if(typeof this.charts[host][id] === 'undefined')
611 //console.log('cached ' + host + '/' + id);
612 return this.charts[host][id];
615 downloadAll: function(host, callback) {
616 while(host.slice(-1) === '/')
617 host = host.substring(0, host.length - 1);
622 url: host + '/api/v1/charts',
625 xhrFields: { withCredentials: true } // required for the cookie
627 .done(function(data) {
629 var h = NETDATA.chartRegistry.fixid(host);
630 self.charts[h] = data.charts;
632 else NETDATA.error(406, host + '/api/v1/charts');
634 if(typeof callback === 'function')
638 NETDATA.error(405, host + '/api/v1/charts');
640 if(typeof callback === 'function')
646 // ----------------------------------------------------------------------------------------------------------------
647 // Global Pan and Zoom on charts
649 // Using this structure are synchronize all the charts, so that
650 // when you pan or zoom one, all others are automatically refreshed
651 // to the same timespan.
653 NETDATA.globalPanAndZoom = {
654 seq: 0, // timestamp ms
655 // every time a chart is panned or zoomed
656 // we set the timestamp here
657 // then we use it as a sequence number
658 // to find if other charts are syncronized
661 master: null, // the master chart (state), to which all others
664 force_before_ms: null, // the timespan to sync all other charts
665 force_after_ms: null,
670 setMaster: function(state, after, before) {
671 if(NETDATA.options.current.sync_pan_and_zoom === false)
674 if(this.master !== null && this.master !== state)
675 this.master.resetChart(true, true);
677 var now = new Date().getTime();
680 this.force_after_ms = after;
681 this.force_before_ms = before;
682 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
684 if(typeof this.callback === 'function')
685 this.callback(true, after, before);
689 clearMaster: function() {
690 if(this.master !== null) {
691 var st = this.master;
698 this.force_after_ms = null;
699 this.force_before_ms = null;
700 NETDATA.options.auto_refresher_stop_until = 0;
702 if(typeof this.callback === 'function')
703 this.callback(false, 0, 0);
706 // is the given state the master of the global
707 // pan and zoom sync?
708 isMaster: function(state) {
709 if(this.master === state) return true;
713 // are we currently have a global pan and zoom sync?
714 isActive: function() {
715 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
719 // check if a chart, other than the master
720 // needs to be refreshed, due to the global pan and zoom
721 shouldBeAutoRefreshed: function(state) {
722 if(this.master === null || this.seq === 0)
725 //if(state.needsRecreation())
728 if(state.tm.pan_and_zoom_seq === this.seq)
735 // ----------------------------------------------------------------------------------------------------------------
736 // dimensions selection
739 // move color assignment to dimensions, here
741 dimensionStatus = function(parent, label, name_div, value_div, color) {
742 this.enabled = false;
743 this.parent = parent;
745 this.name_div = null;
746 this.value_div = null;
747 this.color = NETDATA.themes.current.foreground;
749 if(parent.selected_count > parent.unselected_count)
750 this.selected = true;
752 this.selected = false;
754 this.setOptions(name_div, value_div, color);
757 dimensionStatus.prototype.invalidate = function() {
758 this.name_div = null;
759 this.value_div = null;
760 this.enabled = false;
763 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
766 if(this.name_div != name_div) {
767 this.name_div = name_div;
768 this.name_div.title = this.label;
769 this.name_div.style.color = this.color;
770 if(this.selected === false)
771 this.name_div.className = 'netdata-legend-name not-selected';
773 this.name_div.className = 'netdata-legend-name selected';
776 if(this.value_div != value_div) {
777 this.value_div = value_div;
778 this.value_div.title = this.label;
779 this.value_div.style.color = this.color;
780 if(this.selected === false)
781 this.value_div.className = 'netdata-legend-value not-selected';
783 this.value_div.className = 'netdata-legend-value selected';
790 dimensionStatus.prototype.setHandler = function() {
791 if(this.enabled === false) return;
795 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
796 this.name_div.onclick = this.value_div.onclick = function(e) {
798 if(ds.isSelected()) {
800 if(e.shiftKey === true || e.ctrlKey === true) {
801 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
804 if(ds.parent.countSelected() === 0)
805 ds.parent.selectAll();
808 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
809 if(ds.parent.countSelected() === 1) {
810 ds.parent.selectAll();
813 ds.parent.selectNone();
819 // this is not selected
820 if(e.shiftKey === true || e.ctrlKey === true) {
821 // control or shift key is pressed -> select this too
825 // no key is pressed -> select only this
826 ds.parent.selectNone();
831 ds.parent.state.redrawChart();
835 dimensionStatus.prototype.select = function() {
836 if(this.enabled === false) return;
838 this.name_div.className = 'netdata-legend-name selected';
839 this.value_div.className = 'netdata-legend-value selected';
840 this.selected = true;
843 dimensionStatus.prototype.unselect = function() {
844 if(this.enabled === false) return;
846 this.name_div.className = 'netdata-legend-name not-selected';
847 this.value_div.className = 'netdata-legend-value hidden';
848 this.selected = false;
851 dimensionStatus.prototype.isSelected = function() {
852 return(this.enabled === true && this.selected === true);
855 // ----------------------------------------------------------------------------------------------------------------
857 dimensionsVisibility = function(state) {
860 this.dimensions = {};
861 this.selected_count = 0;
862 this.unselected_count = 0;
865 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
866 if(typeof this.dimensions[label] === 'undefined') {
868 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
871 this.dimensions[label].setOptions(name_div, value_div, color);
873 return this.dimensions[label];
876 dimensionsVisibility.prototype.dimensionGet = function(label) {
877 return this.dimensions[label];
880 dimensionsVisibility.prototype.invalidateAll = function() {
881 for(var d in this.dimensions)
882 this.dimensions[d].invalidate();
885 dimensionsVisibility.prototype.selectAll = function() {
886 for(var d in this.dimensions)
887 this.dimensions[d].select();
890 dimensionsVisibility.prototype.countSelected = function() {
892 for(var d in this.dimensions)
893 if(this.dimensions[d].isSelected()) i++;
898 dimensionsVisibility.prototype.selectNone = function() {
899 for(var d in this.dimensions)
900 this.dimensions[d].unselect();
903 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
904 var ret = new Array();
905 this.selected_count = 0;
906 this.unselected_count = 0;
908 for(var i = 0, len = array.length; i < len ; i++) {
909 var ds = this.dimensions[array[i]];
910 if(typeof ds === 'undefined') {
911 // console.log(array[i] + ' is not found');
916 if(ds.isSelected()) {
918 this.selected_count++;
922 this.unselected_count++;
926 if(this.selected_count === 0 && this.unselected_count !== 0) {
928 return this.selected2BooleanArray(array);
935 // ----------------------------------------------------------------------------------------------------------------
936 // global selection sync
938 NETDATA.globalSelectionSync = {
945 if(this.state !== null)
946 this.state.globalSelectionSyncStop();
950 if(this.state !== null) {
951 this.state.globalSelectionSyncDelay();
956 // ----------------------------------------------------------------------------------------------------------------
957 // Our state object, where all per-chart values are stored
959 chartState = function(element) {
960 var self = $(element);
961 this.element = element;
964 // all private functions should use 'that', instead of 'this'
968 * show an error instead of the chart
970 var error = function(msg) {
973 if(typeof netdataErrorCallback === 'function') {
974 ret = netdataErrorCallback('chart', that.id, msg);
978 that.element.innerHTML = that.id + ': ' + msg;
979 that.enabled = false;
980 that.current = that.pan;
984 // GUID - a unique identifier for the chart
985 this.uuid = NETDATA.guid();
987 // string - the name of chart
988 this.id = self.data('netdata');
990 // string - the key for localStorage settings
991 this.settings_id = self.data('id') || null;
993 // the user given dimensions of the element
994 this.width = self.data('width') || NETDATA.chartDefaults.width;
995 this.height = self.data('height') || NETDATA.chartDefaults.height;
997 if(this.settings_id !== null) {
998 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
999 // this is the callback that will be called
1000 // if and when the user resets all localStorage variables
1001 // to their defaults
1003 resizeChartToHeight(height);
1007 // string - the netdata server URL, without any path
1008 this.host = self.data('host') || NETDATA.chartDefaults.host;
1010 // make sure the host does not end with /
1011 // all netdata API requests use absolute paths
1012 while(this.host.slice(-1) === '/')
1013 this.host = this.host.substring(0, this.host.length - 1);
1015 // string - the grouping method requested by the user
1016 this.method = self.data('method') || NETDATA.chartDefaults.method;
1018 // the time-range requested by the user
1019 this.after = self.data('after') || NETDATA.chartDefaults.after;
1020 this.before = self.data('before') || NETDATA.chartDefaults.before;
1022 // the pixels per point requested by the user
1023 this.pixels_per_point = self.data('pixels-per-point') || 1;
1024 this.points = self.data('points') || null;
1026 // the dimensions requested by the user
1027 this.dimensions = self.data('dimensions') || null;
1029 // the chart library requested by the user
1030 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1032 // object - the chart library used
1033 this.library = null;
1037 this.colors_assigned = {};
1038 this.colors_available = null;
1040 // the element already created by the user
1041 this.element_message = null;
1043 // the element with the chart
1044 this.element_chart = null;
1046 // the element with the legend of the chart (if created by us)
1047 this.element_legend = null;
1048 this.element_legend_childs = {
1058 this.chart_url = null; // string - the url to download chart info
1059 this.chart = null; // object - the chart as downloaded from the server
1061 this.title = self.data('title') || null; // the title of the chart
1062 this.units = self.data('units') || null; // the units of the chart dimensions
1063 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1065 this.running = false; // boolean - true when the chart is being refreshed now
1066 this.validated = false; // boolean - has the chart been validated?
1067 this.enabled = true; // boolean - is the chart enabled for refresh?
1068 this.paused = false; // boolean - is the chart paused for any reason?
1069 this.selected = false; // boolean - is the chart shown a selection?
1070 this.debug = false; // boolean - console.log() debug info about this chart
1072 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1073 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1074 this.requested_after = null; // milliseconds - the timestamp of the request after param
1075 this.requested_before = null; // milliseconds - the timestamp of the request before param
1076 this.requested_padding = null;
1077 this.view_after = 0;
1078 this.view_before = 0;
1083 force_update_at: 0, // the timestamp to force the update at
1084 force_before_ms: null,
1085 force_after_ms: null
1090 force_update_at: 0, // the timestamp to force the update at
1091 force_before_ms: null,
1092 force_after_ms: null
1097 force_update_at: 0, // the timestamp to force the update at
1098 force_before_ms: null,
1099 force_after_ms: null
1102 // this is a pointer to one of the sub-classes below
1104 this.current = this.auto;
1106 // check the requested library is available
1107 // we don't initialize it here - it will be initialized when
1108 // this chart will be first used
1109 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1110 NETDATA.error(402, that.library_name);
1111 error('chart library "' + that.library_name + '" is not found');
1114 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1115 NETDATA.error(403, that.library_name);
1116 error('chart library "' + that.library_name + '" is not enabled');
1120 that.library = NETDATA.chartLibraries[that.library_name];
1122 // milliseconds - the time the last refresh took
1123 this.refresh_dt_ms = 0;
1125 // if we need to report the rendering speed
1126 // find the element that needs to be updated
1127 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1129 if(refresh_dt_element_name !== null)
1130 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1132 this.refresh_dt_element = null;
1134 this.dimensions_visibility = new dimensionsVisibility(this);
1136 this._updating = false;
1138 // ============================================================================================================
1139 // PRIVATE FUNCTIONS
1141 var createDOM = function() {
1142 if(that.enabled === false) return;
1144 if(that.element_message !== null) that.element_message.innerHTML = '';
1145 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1146 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1148 that.element.innerHTML = '';
1150 that.element_message = document.createElement('div');
1151 that.element_message.className = ' netdata-message hidden';
1152 that.element.appendChild(that.element_message);
1154 that.element_chart = document.createElement('div');
1155 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1156 that.element.appendChild(that.element_chart);
1158 if(that.hasLegend() === true) {
1159 that.element.className = "netdata-container-with-legend";
1160 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1162 that.element_legend = document.createElement('div');
1163 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1164 that.element.appendChild(that.element_legend);
1167 that.element.className = "netdata-container";
1168 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1170 that.element_legend = null;
1172 that.element_legend_childs.series = null;
1174 if(typeof(that.width) === 'string')
1175 $(that.element).css('width', that.width);
1176 else if(typeof(that.width) === 'number')
1177 $(that.element).css('width', that.width + 'px');
1179 if(typeof(that.library.aspect_ratio) === 'undefined') {
1180 if(typeof(that.height) === 'string')
1181 $(that.element).css('height', that.height);
1182 else if(typeof(that.height) === 'number')
1183 $(that.element).css('height', that.height + 'px');
1186 var w = that.element.offsetWidth;
1187 if(w === null || w === 0) {
1188 // the div is hidden
1189 // this will resize the chart when next viewed
1190 that.tm.last_resized = 0;
1193 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1196 if(NETDATA.chartDefaults.min_width !== null)
1197 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1199 that.tm.last_dom_created = new Date().getTime();
1205 * initialize state variables
1206 * destroy all (possibly) created state elements
1207 * create the basic DOM for a chart
1209 var init = function() {
1210 if(that.enabled === false) return;
1212 that.paused = false;
1213 that.selected = false;
1215 that.chart_created = false; // boolean - is the library.create() been called?
1216 that.updates_counter = 0; // numeric - the number of refreshes made so far
1217 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1218 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1221 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1222 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1223 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1225 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1226 last_updated: 0, // the timestamp the chart last updated with data
1227 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1229 // Used with NETDATA.globalPanAndZoom.seq
1230 last_visible_check: 0, // the time we last checked if it is visible
1231 last_resized: 0, // the time the chart was resized
1232 last_hidden: 0, // the time the chart was hidden
1233 last_unhidden: 0, // the time the chart was unhidden
1234 last_autorefreshed: 0 // the time the chart was last refreshed
1237 that.data = null; // the last data as downloaded from the netdata server
1238 that.data_url = 'invalid://'; // string - the last url used to update the chart
1239 that.data_points = 0; // number - the number of points returned from netdata
1240 that.data_after = 0; // milliseconds - the first timestamp of the data
1241 that.data_before = 0; // milliseconds - the last timestamp of the data
1242 that.data_update_every = 0; // milliseconds - the frequency to update the data
1244 that.tm.last_initialized = new Date().getTime();
1247 that.setMode('auto');
1250 var maxMessageFontSize = function() {
1251 // normally we want a font size, as tall as the element
1252 var h = that.element_message.clientHeight;
1254 // but give it some air, 20% let's say, or 5 pixels min
1255 var lost = Math.max(h * 0.2, 5);
1258 // center the text, vertically
1259 var paddingTop = (lost - 5) / 2;
1261 // but check the width too
1262 // it should fit 10 characters in it
1263 var w = that.element_message.clientWidth / 10;
1265 paddingTop += (h - w) / 2;
1269 // and don't make it too huge
1270 // 5% of the screen size is good
1271 if(h > screen.height / 20) {
1272 paddingTop += (h - (screen.height / 20)) / 2;
1273 h = screen.height / 20;
1277 that.element_message.style.fontSize = h.toString() + 'px';
1278 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1281 var showMessage = function(msg) {
1282 that.element_message.className = 'netdata-message';
1283 that.element_message.innerHTML = msg;
1284 that.element_message.style.fontSize = 'x-small';
1285 that.element_message.style.paddingTop = '0px';
1286 that.___messageHidden___ = undefined;
1289 var showMessageIcon = function(icon) {
1290 that.element_message.innerHTML = icon;
1291 that.element_message.className = 'netdata-message icon';
1292 maxMessageFontSize();
1293 that.___messageHidden___ = undefined;
1296 var hideMessage = function() {
1297 if(typeof that.___messageHidden___ === 'undefined') {
1298 that.___messageHidden___ = true;
1299 that.element_message.className = 'netdata-message hidden';
1303 var showRendering = function() {
1305 if(that.chart !== null) {
1306 if(that.chart.chart_type === 'line')
1307 icon = '<i class="fa fa-line-chart"></i>';
1309 icon = '<i class="fa fa-area-chart"></i>';
1312 icon = '<i class="fa fa-area-chart"></i>';
1314 showMessageIcon(icon + ' netdata');
1317 var showLoading = function() {
1318 if(that.chart_created === false) {
1319 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1325 var isHidden = function() {
1326 if(typeof that.___chartIsHidden___ !== 'undefined')
1332 // hide the chart, when it is not visible - called from isVisible()
1333 var hideChart = function() {
1334 // hide it, if it is not already hidden
1335 if(isHidden() === true) return;
1337 if(that.chart_created === true) {
1338 if(NETDATA.options.current.destroy_on_hide === true) {
1339 // we should destroy it
1344 that.element_chart.style.display = 'none';
1345 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1346 that.tm.last_hidden = new Date().getTime();
1349 // This works, but I not sure there are no corner cases somewhere
1350 // so it is commented - if the user has memory issues he can
1351 // set Destroy on Hide for all charts
1352 // that.data = null;
1356 that.___chartIsHidden___ = true;
1359 // unhide the chart, when it is visible - called from isVisible()
1360 var unhideChart = function() {
1361 if(isHidden() === false) return;
1363 that.___chartIsHidden___ = undefined;
1364 that.updates_since_last_unhide = 0;
1366 if(that.chart_created === false) {
1367 // we need to re-initialize it, to show our background
1368 // logo in bootstrap tabs, until the chart loads
1372 that.tm.last_unhidden = new Date().getTime();
1373 that.element_chart.style.display = '';
1374 if(that.element_legend !== null) that.element_legend.style.display = '';
1380 var canBeRendered = function() {
1381 if(isHidden() === true || that.isVisible(true) === false)
1387 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1388 var callChartLibraryUpdateSafely = function(data) {
1391 if(canBeRendered() === false)
1394 if(NETDATA.options.debug.chart_errors === true)
1395 status = that.library.update(that, data);
1398 status = that.library.update(that, data);
1405 if(status === false) {
1406 error('chart failed to be updated as ' + that.library_name);
1413 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1414 var callChartLibraryCreateSafely = function(data) {
1417 if(canBeRendered() === false)
1420 if(NETDATA.options.debug.chart_errors === true)
1421 status = that.library.create(that, data);
1424 status = that.library.create(that, data);
1431 if(status === false) {
1432 error('chart failed to be created as ' + that.library_name);
1436 that.chart_created = true;
1437 that.updates_since_last_creation = 0;
1441 // ----------------------------------------------------------------------------------------------------------------
1444 // resizeChart() - private
1445 // to be called just before the chart library to make sure that
1446 // a properly sized dom is available
1447 var resizeChart = function() {
1448 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1449 if(that.chart_created === false) return;
1451 if(that.needsRecreation()) {
1454 else if(typeof that.library.resize === 'function') {
1455 that.library.resize(that);
1457 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1458 $(that.element_legend_childs.nano).nanoScroller();
1460 maxMessageFontSize();
1463 that.tm.last_resized = new Date().getTime();
1467 // this is the actual chart resize algorithm
1469 // - resize the entire container
1470 // - update the internal states
1471 // - resize the chart as the div changes height
1472 // - update the scrollbar of the legend
1473 var resizeChartToHeight = function(h) {
1475 that.element.style.height = h;
1477 if(that.settings_id !== null)
1478 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1480 var now = new Date().getTime();
1481 NETDATA.options.last_page_scroll = now;
1482 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1485 that.tm.last_resized = 0;
1489 this.resizeHandler = function(e) {
1492 if(typeof this.event_resize === 'undefined'
1493 || this.event_resize.chart_original_w === 'undefined'
1494 || this.event_resize.chart_original_h === 'undefined')
1495 this.event_resize = {
1496 chart_original_w: this.element.clientWidth,
1497 chart_original_h: this.element.clientHeight,
1501 if(e.type === 'touchstart') {
1502 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1503 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1506 this.event_resize.mouse_start_x = e.clientX;
1507 this.event_resize.mouse_start_y = e.clientY;
1510 this.event_resize.chart_start_w = this.element.clientWidth;
1511 this.event_resize.chart_start_h = this.element.clientHeight;
1512 this.event_resize.chart_last_w = this.element.clientWidth;
1513 this.event_resize.chart_last_h = this.element.clientHeight;
1515 var now = new Date().getTime();
1516 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1517 // double click / double tap event
1519 // the optimal height of the chart
1520 // showing the entire legend
1521 var optimal = this.event_resize.chart_last_h
1522 + this.element_legend_childs.content.scrollHeight
1523 - this.element_legend_childs.content.clientHeight;
1525 // if we are not optimal, be optimal
1526 if(this.event_resize.chart_last_h != optimal)
1527 resizeChartToHeight(optimal.toString() + 'px');
1529 // else if we do not have the original height
1530 // reset to the original height
1531 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1532 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1535 this.event_resize.last = now;
1537 // process movement event
1538 document.onmousemove =
1539 document.ontouchmove =
1540 this.element_legend_childs.resize_handler.onmousemove =
1541 this.element_legend_childs.resize_handler.ontouchmove =
1546 case 'mousemove': y = e.clientY; break;
1547 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1551 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1553 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1554 resizeChartToHeight(newH.toString() + 'px');
1555 that.event_resize.chart_last_h = newH;
1560 // process end event
1561 document.onmouseup =
1562 document.ontouchend =
1563 this.element_legend_childs.resize_handler.onmouseup =
1564 this.element_legend_childs.resize_handler.ontouchend =
1566 // remove all the hooks
1567 document.onmouseup =
1568 document.onmousemove =
1569 document.ontouchmove =
1570 document.ontouchend =
1571 that.element_legend_childs.resize_handler.onmousemove =
1572 that.element_legend_childs.resize_handler.ontouchmove =
1573 that.element_legend_childs.resize_handler.onmouseout =
1574 that.element_legend_childs.resize_handler.onmouseup =
1575 that.element_legend_childs.resize_handler.ontouchend =
1578 // allow auto-refreshes
1579 NETDATA.options.auto_refresher_stop_until = 0;
1585 var noDataToShow = function() {
1586 showMessageIcon('<i class="fa fa-warning"></i> empty');
1587 that.legendUpdateDOM();
1588 that.tm.last_autorefreshed = new Date().getTime();
1589 // that.data_update_every = 30 * 1000;
1590 //that.element_chart.style.display = 'none';
1591 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1592 //that.___chartIsHidden___ = true;
1595 // ============================================================================================================
1598 this.error = function(msg) {
1602 this.setMode = function(m) {
1603 if(this.current !== null && this.current.name === m) return;
1606 this.current = this.auto;
1607 else if(m === 'pan')
1608 this.current = this.pan;
1609 else if(m === 'zoom')
1610 this.current = this.zoom;
1612 this.current = this.auto;
1614 this.current.force_update_at = 0;
1615 this.current.force_before_ms = null;
1616 this.current.force_after_ms = null;
1618 this.tm.last_mode_switch = new Date().getTime();
1621 // ----------------------------------------------------------------------------------------------------------------
1622 // global selection sync
1624 // prevent to global selection sync for some time
1625 this.globalSelectionSyncDelay = function(ms) {
1626 if(NETDATA.options.current.sync_selection === false)
1629 if(typeof ms === 'number')
1630 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1632 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1635 // can we globally apply selection sync?
1636 this.globalSelectionSyncAbility = function() {
1637 if(NETDATA.options.current.sync_selection === false)
1640 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1646 this.globalSelectionSyncIsMaster = function() {
1647 if(NETDATA.globalSelectionSync.state === this)
1653 // this chart is the master of the global selection sync
1654 this.globalSelectionSyncBeMaster = function() {
1656 if(this.globalSelectionSyncIsMaster()) {
1657 if(this.debug === true)
1658 this.log('sync: I am the master already.');
1663 if(NETDATA.globalSelectionSync.state) {
1664 if(this.debug === true)
1665 this.log('sync: I am not the sync master. Resetting global sync.');
1667 this.globalSelectionSyncStop();
1670 // become the master
1671 if(this.debug === true)
1672 this.log('sync: becoming sync master.');
1674 this.selected = true;
1675 NETDATA.globalSelectionSync.state = this;
1677 // find the all slaves
1678 var targets = NETDATA.options.targets;
1679 var len = targets.length;
1684 if(this.debug === true)
1685 st.log('sync: not adding me to sync');
1687 else if(st.globalSelectionSyncIsEligible()) {
1688 if(this.debug === true)
1689 st.log('sync: adding to sync as slave');
1691 st.globalSelectionSyncBeSlave();
1695 // this.globalSelectionSyncDelay(100);
1698 // can the chart participate to the global selection sync as a slave?
1699 this.globalSelectionSyncIsEligible = function() {
1700 if(this.enabled === true
1701 && this.library !== null
1702 && typeof this.library.setSelection === 'function'
1703 && this.isVisible() === true
1704 && this.chart_created === true)
1710 // this chart becomes a slave of the global selection sync
1711 this.globalSelectionSyncBeSlave = function() {
1712 if(NETDATA.globalSelectionSync.state !== this)
1713 NETDATA.globalSelectionSync.slaves.push(this);
1716 // sync all the visible charts to the given time
1717 // this is to be called from the chart libraries
1718 this.globalSelectionSync = function(t) {
1719 if(this.globalSelectionSyncAbility() === false) {
1720 if(this.debug === true)
1721 this.log('sync: cannot sync (yet?).');
1726 if(this.globalSelectionSyncIsMaster() === false) {
1727 if(this.debug === true)
1728 this.log('sync: trying to be sync master.');
1730 this.globalSelectionSyncBeMaster();
1732 if(this.globalSelectionSyncAbility() === false) {
1733 if(this.debug === true)
1734 this.log('sync: cannot sync (yet?).');
1740 NETDATA.globalSelectionSync.last_t = t;
1741 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1746 // stop syncing all charts to the given time
1747 this.globalSelectionSyncStop = function() {
1748 if(NETDATA.globalSelectionSync.slaves.length) {
1749 if(this.debug === true)
1750 this.log('sync: cleaning up...');
1752 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1754 if(that.debug === true)
1755 st.log('sync: not adding me to sync stop');
1758 if(that.debug === true)
1759 st.log('sync: removed slave from sync');
1761 st.clearSelection();
1765 NETDATA.globalSelectionSync.last_t = 0;
1766 NETDATA.globalSelectionSync.slaves = [];
1767 NETDATA.globalSelectionSync.state = null;
1770 this.clearSelection();
1773 this.setSelection = function(t) {
1774 if(typeof this.library.setSelection === 'function') {
1775 if(this.library.setSelection(this, t) === true)
1776 this.selected = true;
1778 this.selected = false;
1780 else this.selected = true;
1782 if(this.selected === true && this.debug === true)
1783 this.log('selection set to ' + t.toString());
1785 return this.selected;
1788 this.clearSelection = function() {
1789 if(this.selected === true) {
1790 if(typeof this.library.clearSelection === 'function') {
1791 if(this.library.clearSelection(this) === true)
1792 this.selected = false;
1794 this.selected = true;
1796 else this.selected = false;
1798 if(this.selected === false && this.debug === true)
1799 this.log('selection cleared');
1804 return this.selected;
1807 // find if a timestamp (ms) is shown in the current chart
1808 this.timeIsVisible = function(t) {
1809 if(t >= this.data_after && t <= this.data_before)
1814 this.calculateRowForTime = function(t) {
1815 if(this.timeIsVisible(t) === false) return -1;
1816 return Math.floor((t - this.data_after) / this.data_update_every);
1819 // ----------------------------------------------------------------------------------------------------------------
1822 this.log = function(msg) {
1823 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1826 this.pauseChart = function() {
1827 if(this.paused === false) {
1828 if(this.debug === true)
1829 this.log('pauseChart()');
1835 this.unpauseChart = function() {
1836 if(this.paused === true) {
1837 if(this.debug === true)
1838 this.log('unpauseChart()');
1840 this.paused = false;
1844 this.resetChart = function(dont_clear_master, dont_update) {
1845 if(this.debug === true)
1846 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1848 if(typeof dont_clear_master === 'undefined')
1849 dont_clear_master = false;
1851 if(typeof dont_update === 'undefined')
1852 dont_update = false;
1854 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1855 if(this.debug === true)
1856 this.log('resetChart() diverting to clearMaster().');
1857 // this will call us back with master === true
1858 NETDATA.globalPanAndZoom.clearMaster();
1862 this.clearSelection();
1864 this.tm.pan_and_zoom_seq = 0;
1866 this.setMode('auto');
1867 this.current.force_update_at = 0;
1868 this.current.force_before_ms = null;
1869 this.current.force_after_ms = null;
1870 this.tm.last_autorefreshed = 0;
1871 this.paused = false;
1872 this.selected = false;
1873 this.enabled = true;
1874 // this.debug = false;
1876 // do not update the chart here
1877 // or the chart will flip-flop when it is the master
1878 // of a selection sync and another chart becomes
1881 if(dont_update !== true && this.isVisible() === true) {
1886 this.updateChartPanOrZoom = function(after, before) {
1887 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1890 if(this.debug === true)
1893 if(before < after) {
1894 if(this.debug === true)
1895 this.log(logme + 'flipped parameters, rejecting it.');
1900 if(typeof this.fixed_min_duration === 'undefined')
1901 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1903 var min_duration = this.fixed_min_duration;
1904 var current_duration = Math.round(this.view_before - this.view_after);
1906 // round the numbers
1907 after = Math.round(after);
1908 before = Math.round(before);
1910 // align them to update_every
1911 // stretching them further away
1912 after -= after % this.data_update_every;
1913 before += this.data_update_every - (before % this.data_update_every);
1915 // the final wanted duration
1916 var wanted_duration = before - after;
1918 // to allow panning, accept just a point below our minimum
1919 if((current_duration - this.data_update_every) < min_duration)
1920 min_duration = current_duration - this.data_update_every;
1922 // we do it, but we adjust to minimum size and return false
1923 // when the wanted size is below the current and the minimum
1925 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1926 if(this.debug === true)
1927 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1929 min_duration = this.fixed_min_duration;
1931 var dt = (min_duration - wanted_duration) / 2;
1934 wanted_duration = before - after;
1938 var tolerance = this.data_update_every * 2;
1939 var movement = Math.abs(before - this.view_before);
1941 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1942 if(this.debug === true)
1943 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
1947 if(this.current.name === 'auto') {
1948 this.log(logme + 'caller called me with mode: ' + this.current.name);
1949 this.setMode('pan');
1952 if(this.debug === true)
1953 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
1955 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1956 this.current.force_after_ms = after;
1957 this.current.force_before_ms = before;
1958 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1962 this.legendFormatValue = function(value) {
1963 if(value === null || value === 'undefined') return '-';
1964 if(typeof value !== 'number') return value;
1966 var abs = Math.abs(value);
1967 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1968 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1969 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1970 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1971 return (Math.round(value * 10000) / 10000).toLocaleString();
1974 this.legendSetLabelValue = function(label, value) {
1975 var series = this.element_legend_childs.series[label];
1976 if(typeof series === 'undefined') return;
1977 if(series.value === null && series.user === null) return;
1979 // if the value has not changed, skip DOM update
1980 //if(series.last === value) return;
1983 if(typeof value === 'number') {
1984 var v = Math.abs(value);
1985 s = r = this.legendFormatValue(value);
1987 if(typeof series.last === 'number') {
1988 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1989 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1990 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1992 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1997 series.last = value;
2000 if(series.value !== null) series.value.innerHTML = s;
2001 if(series.user !== null) series.user.innerHTML = r;
2004 this.legendSetDate = function(ms) {
2005 if(typeof ms !== 'number') {
2006 this.legendShowUndefined();
2010 var d = new Date(ms);
2012 if(this.element_legend_childs.title_date)
2013 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2015 if(this.element_legend_childs.title_time)
2016 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2018 if(this.element_legend_childs.title_units)
2019 this.element_legend_childs.title_units.innerHTML = this.units;
2022 this.legendShowUndefined = function() {
2023 if(this.element_legend_childs.title_date)
2024 this.element_legend_childs.title_date.innerHTML = ' ';
2026 if(this.element_legend_childs.title_time)
2027 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2029 if(this.element_legend_childs.title_units)
2030 this.element_legend_childs.title_units.innerHTML = ' ';
2032 if(this.data && this.element_legend_childs.series !== null) {
2033 var labels = this.data.dimension_names;
2034 var i = labels.length;
2036 var label = labels[i];
2038 if(typeof label === 'undefined') continue;
2039 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2040 this.legendSetLabelValue(label, null);
2045 this.legendShowLatestValues = function() {
2046 if(this.chart === null) return;
2047 if(this.selected) return;
2049 if(this.data === null || this.element_legend_childs.series === null) {
2050 this.legendShowUndefined();
2054 var show_undefined = true;
2055 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2056 show_undefined = false;
2058 if(show_undefined) {
2059 this.legendShowUndefined();
2063 this.legendSetDate(this.view_before);
2065 var labels = this.data.dimension_names;
2066 var i = labels.length;
2068 var label = labels[i];
2070 if(typeof label === 'undefined') continue;
2071 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2074 this.legendSetLabelValue(label, null);
2076 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2080 this.legendReset = function() {
2081 this.legendShowLatestValues();
2084 // this should be called just ONCE per dimension per chart
2085 this._chartDimensionColor = function(label) {
2086 if(this.colors === null) this.chartColors();
2088 if(typeof this.colors_assigned[label] === 'undefined') {
2089 if(this.colors_available.length === 0) {
2090 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2091 this.colors_available.push(NETDATA.themes.current.colors[i]);
2094 this.colors_assigned[label] = this.colors_available.shift();
2096 if(this.debug === true)
2097 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2100 if(this.debug === true)
2101 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2104 this.colors.push(this.colors_assigned[label]);
2105 return this.colors_assigned[label];
2108 this.chartColors = function() {
2109 if(this.colors !== null) return this.colors;
2111 this.colors = new Array();
2112 this.colors_available = new Array();
2115 var c = $(this.element).data('colors');
2116 // this.log('read colors: ' + c);
2117 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2118 if(typeof c !== 'string') {
2119 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2126 for(i = 0, len = c.length; i < len ; i++) {
2128 this.colors_available.push(c[i]);
2129 // this.log('adding color: ' + c[i]);
2135 // push all the standard colors too
2136 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2137 this.colors_available.push(NETDATA.themes.current.colors[i]);
2142 this.legendUpdateDOM = function() {
2145 // check that the legend DOM is up to date for the downloaded dimensions
2146 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2147 // this.log('the legend does not have any series - requesting legend update');
2150 else if(this.data === null) {
2151 // this.log('the chart does not have any data - requesting legend update');
2154 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2158 var labels = this.data.dimension_names.toString();
2159 if(labels !== this.element_legend_childs.series.labels_key) {
2162 if(this.debug === true)
2163 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2167 if(needed === false) {
2168 // make sure colors available
2171 // do we have to update the current values?
2172 // we do this, only when the visible chart is current
2173 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2174 if(this.debug === true)
2175 this.log('chart is in latest position... updating values on legend...');
2177 //var labels = this.data.dimension_names;
2178 //var i = labels.length;
2180 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2184 if(this.colors === null) {
2185 // this is the first time we update the chart
2186 // let's assign colors to all dimensions
2187 if(this.library.track_colors() === true)
2188 for(var dim in this.chart.dimensions)
2189 this._chartDimensionColor(this.chart.dimensions[dim].name);
2191 // we will re-generate the colors for the chart
2192 // based on the selected dimensions
2195 if(this.debug === true)
2196 this.log('updating Legend DOM');
2198 // mark all dimensions as invalid
2199 this.dimensions_visibility.invalidateAll();
2201 var genLabel = function(state, parent, dim, name, count) {
2202 var color = state._chartDimensionColor(name);
2204 var user_element = null;
2205 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2206 if(user_id !== null) {
2207 user_element = document.getElementById(user_id) || null;
2208 if(user_element === null)
2209 state.log('Cannot find element with id: ' + user_id);
2212 state.element_legend_childs.series[name] = {
2213 name: document.createElement('span'),
2214 value: document.createElement('span'),
2219 var label = state.element_legend_childs.series[name];
2221 // create the dimension visibility tracking for this label
2222 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2224 var rgb = NETDATA.colorHex2Rgb(color);
2225 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2226 + state.chart.chart_type
2227 + '" style="background-color: '
2228 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2229 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2231 var text = document.createTextNode(' ' + name);
2232 label.name.appendChild(text);
2235 parent.appendChild(document.createElement('br'));
2237 parent.appendChild(label.name);
2238 parent.appendChild(label.value);
2241 var content = document.createElement('div');
2243 if(this.hasLegend()) {
2244 this.element_legend_childs = {
2246 resize_handler: document.createElement('div'),
2247 toolbox: document.createElement('div'),
2248 toolbox_left: document.createElement('div'),
2249 toolbox_right: document.createElement('div'),
2250 toolbox_reset: document.createElement('div'),
2251 toolbox_zoomin: document.createElement('div'),
2252 toolbox_zoomout: document.createElement('div'),
2253 toolbox_volume: document.createElement('div'),
2254 title_date: document.createElement('span'),
2255 title_time: document.createElement('span'),
2256 title_units: document.createElement('span'),
2257 nano: document.createElement('div'),
2259 paneClass: 'netdata-legend-series-pane',
2260 sliderClass: 'netdata-legend-series-slider',
2261 contentClass: 'netdata-legend-series-content',
2262 enabledClass: '__enabled',
2263 flashedClass: '__flashed',
2264 activeClass: '__active',
2266 alwaysVisible: true,
2272 this.element_legend.innerHTML = '';
2274 if(this.library.toolboxPanAndZoom !== null) {
2276 function get_pan_and_zoom_step(event) {
2278 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2280 else if (event.shiftKey)
2281 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2283 else if (event.altKey)
2284 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2287 return NETDATA.options.current.pan_and_zoom_factor;
2290 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2291 this.element.appendChild(this.element_legend_childs.toolbox);
2293 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2294 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2295 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2296 this.element_legend_childs.toolbox_left.onclick = function(e) {
2299 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2300 var before = that.view_before - step;
2301 var after = that.view_after - step;
2302 if(after >= that.netdata_first)
2303 that.library.toolboxPanAndZoom(that, after, before);
2305 if(NETDATA.options.current.show_help === true)
2306 $(this.element_legend_childs.toolbox_left).popover({
2311 placement: 'bottom',
2312 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2314 content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2318 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2319 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2320 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2321 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2323 NETDATA.resetAllCharts(that);
2325 if(NETDATA.options.current.show_help === true)
2326 $(this.element_legend_childs.toolbox_reset).popover({
2331 placement: 'bottom',
2332 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2333 title: 'Chart Reset',
2334 content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2337 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2338 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2339 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2340 this.element_legend_childs.toolbox_right.onclick = function(e) {
2342 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2343 var before = that.view_before + step;
2344 var after = that.view_after + step;
2345 if(before <= that.netdata_last)
2346 that.library.toolboxPanAndZoom(that, after, before);
2348 if(NETDATA.options.current.show_help === true)
2349 $(this.element_legend_childs.toolbox_right).popover({
2354 placement: 'bottom',
2355 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2357 content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2361 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2362 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2363 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2364 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2366 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2367 var before = that.view_before - dt;
2368 var after = that.view_after + dt;
2369 that.library.toolboxPanAndZoom(that, after, before);
2371 if(NETDATA.options.current.show_help === true)
2372 $(this.element_legend_childs.toolbox_zoomin).popover({
2377 placement: 'bottom',
2378 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2379 title: 'Chart Zoom In',
2380 content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2383 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2384 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2385 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2386 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2388 var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2389 var before = that.view_before + dt;
2390 var after = that.view_after - dt;
2392 that.library.toolboxPanAndZoom(that, after, before);
2394 if(NETDATA.options.current.show_help === true)
2395 $(this.element_legend_childs.toolbox_zoomout).popover({
2400 placement: 'bottom',
2401 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2402 title: 'Chart Zoom Out',
2403 content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2406 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2407 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2408 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2409 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2410 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2411 //e.preventDefault();
2412 //alert('clicked toolbox_volume on ' + that.id);
2416 this.element_legend_childs.toolbox = null;
2417 this.element_legend_childs.toolbox_left = null;
2418 this.element_legend_childs.toolbox_reset = null;
2419 this.element_legend_childs.toolbox_right = null;
2420 this.element_legend_childs.toolbox_zoomin = null;
2421 this.element_legend_childs.toolbox_zoomout = null;
2422 this.element_legend_childs.toolbox_volume = null;
2425 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2426 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2427 this.element.appendChild(this.element_legend_childs.resize_handler);
2428 if(NETDATA.options.current.show_help === true)
2429 $(this.element_legend_childs.resize_handler).popover({
2434 placement: 'bottom',
2435 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2436 title: 'Chart Resize',
2437 content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2441 this.element_legend_childs.resize_handler.onmousedown =
2443 that.resizeHandler(e);
2447 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2448 that.resizeHandler(e);
2451 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2452 this.element_legend.appendChild(this.element_legend_childs.title_date);
2454 this.element_legend.appendChild(document.createElement('br'));
2456 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2457 this.element_legend.appendChild(this.element_legend_childs.title_time);
2459 this.element_legend.appendChild(document.createElement('br'));
2461 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2462 this.element_legend.appendChild(this.element_legend_childs.title_units);
2464 this.element_legend.appendChild(document.createElement('br'));
2466 this.element_legend_childs.nano.className = 'netdata-legend-series';
2467 this.element_legend.appendChild(this.element_legend_childs.nano);
2469 content.className = 'netdata-legend-series-content';
2470 this.element_legend_childs.nano.appendChild(content);
2472 if(NETDATA.options.current.show_help === true)
2473 $(content).popover({
2478 placement: 'bottom',
2479 title: 'Chart Legend',
2480 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2481 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2485 this.element_legend_childs = {
2487 resize_handler: null,
2490 toolbox_right: null,
2491 toolbox_reset: null,
2492 toolbox_zoomin: null,
2493 toolbox_zoomout: null,
2494 toolbox_volume: null,
2505 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2506 if(this.debug === true)
2507 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2509 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2510 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2514 var tmp = new Array();
2515 for(var dim in this.chart.dimensions) {
2516 tmp.push(this.chart.dimensions[dim].name);
2517 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2519 this.element_legend_childs.series.labels_key = tmp.toString();
2520 if(this.debug === true)
2521 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2524 // create a hidden div to be used for hidding
2525 // the original legend of the chart library
2526 var el = document.createElement('div');
2527 if(this.element_legend !== null)
2528 this.element_legend.appendChild(el);
2529 el.style.display = 'none';
2531 this.element_legend_childs.hidden = document.createElement('div');
2532 el.appendChild(this.element_legend_childs.hidden);
2534 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2535 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2537 this.legendShowLatestValues();
2540 this.hasLegend = function() {
2541 if(typeof this.___hasLegendCache___ !== 'undefined')
2542 return this.___hasLegendCache___;
2545 if(this.library && this.library.legend(this) === 'right-side') {
2546 var legend = $(this.element).data('legend') || 'yes';
2547 if(legend === 'yes') leg = true;
2550 this.___hasLegendCache___ = leg;
2554 this.legendWidth = function() {
2555 return (this.hasLegend())?140:0;
2558 this.legendHeight = function() {
2559 return $(this.element).height();
2562 this.chartWidth = function() {
2563 return $(this.element).width() - this.legendWidth();
2566 this.chartHeight = function() {
2567 return $(this.element).height();
2570 this.chartPixelsPerPoint = function() {
2571 // force an options provided detail
2572 var px = this.pixels_per_point;
2574 if(this.library && px < this.library.pixels_per_point(this))
2575 px = this.library.pixels_per_point(this);
2577 if(px < NETDATA.options.current.pixels_per_point)
2578 px = NETDATA.options.current.pixels_per_point;
2583 this.needsRecreation = function() {
2585 this.chart_created === true
2587 && this.library.autoresize() === false
2588 && this.tm.last_resized < NETDATA.options.last_resized
2592 this.chartURL = function() {
2593 var after, before, points_multiplier = 1;
2594 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2595 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2597 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2598 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2599 this.view_after = after * 1000;
2600 this.view_before = before * 1000;
2602 this.requested_padding = null;
2603 points_multiplier = 1;
2605 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2606 this.tm.pan_and_zoom_seq = 0;
2608 before = Math.round(this.current.force_before_ms / 1000);
2609 after = Math.round(this.current.force_after_ms / 1000);
2610 this.view_after = after * 1000;
2611 this.view_before = before * 1000;
2613 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2614 this.requested_padding = Math.round((before - after) / 2);
2615 after -= this.requested_padding;
2616 before += this.requested_padding;
2617 this.requested_padding *= 1000;
2618 points_multiplier = 2;
2621 this.current.force_before_ms = null;
2622 this.current.force_after_ms = null;
2625 this.tm.pan_and_zoom_seq = 0;
2627 before = this.before;
2629 this.view_after = after * 1000;
2630 this.view_before = before * 1000;
2632 this.requested_padding = null;
2633 points_multiplier = 1;
2636 this.requested_after = after * 1000;
2637 this.requested_before = before * 1000;
2639 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2641 // build the data URL
2642 this.data_url = this.host + this.chart.data_url;
2643 this.data_url += "&format=" + this.library.format();
2644 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2645 this.data_url += "&group=" + this.method;
2646 this.data_url += "&options=" + this.library.options(this);
2647 this.data_url += '|jsonwrap';
2649 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2650 this.data_url += '|nonzero';
2652 if(this.append_options !== null)
2653 this.data_url += '|' + this.append_options.toString();
2656 this.data_url += "&after=" + after.toString();
2659 this.data_url += "&before=" + before.toString();
2662 this.data_url += "&dimensions=" + this.dimensions;
2664 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2665 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2668 this.redrawChart = function() {
2669 if(this.data !== null)
2670 this.updateChartWithData(this.data);
2673 this.updateChartWithData = function(data) {
2674 if(this.debug === true)
2675 this.log('updateChartWithData() called.');
2677 // this may force the chart to be re-created
2681 this.updates_counter++;
2682 this.updates_since_last_unhide++;
2683 this.updates_since_last_creation++;
2685 var started = new Date().getTime();
2687 // if the result is JSON, find the latest update-every
2688 this.data_update_every = data.view_update_every * 1000;
2689 this.data_after = data.after * 1000;
2690 this.data_before = data.before * 1000;
2691 this.netdata_first = data.first_entry * 1000;
2692 this.netdata_last = data.last_entry * 1000;
2693 this.data_points = data.points;
2696 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2697 if(this.view_after < this.data_after) {
2698 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2699 this.view_after = this.data_after;
2702 if(this.view_before > this.data_before) {
2703 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2704 this.view_before = this.data_before;
2708 this.view_after = this.data_after;
2709 this.view_before = this.data_before;
2712 if(this.debug === true) {
2713 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2715 if(this.current.force_after_ms)
2716 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2718 this.log('STATUS: forced : unset');
2720 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2721 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2722 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2723 this.log('STATUS: points : ' + (this.data_points).toString());
2726 if(this.data_points === 0) {
2731 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2732 if(this.debug === true)
2733 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2735 this.chart_created = false;
2738 // check and update the legend
2739 this.legendUpdateDOM();
2741 if(this.chart_created === true
2742 && typeof this.library.update === 'function') {
2744 if(this.debug === true)
2745 this.log('updating chart...');
2747 if(callChartLibraryUpdateSafely(data) === false)
2751 if(this.debug === true)
2752 this.log('creating chart...');
2754 if(callChartLibraryCreateSafely(data) === false)
2758 this.legendShowLatestValues();
2759 if(this.selected === true)
2760 NETDATA.globalSelectionSync.stop();
2762 // update the performance counters
2763 var now = new Date().getTime();
2764 this.tm.last_updated = now;
2766 // don't update last_autorefreshed if this chart is
2767 // forced to be updated with global PanAndZoom
2768 if(NETDATA.globalPanAndZoom.isActive())
2769 this.tm.last_autorefreshed = 0;
2771 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2772 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2774 this.tm.last_autorefreshed = now;
2777 this.refresh_dt_ms = now - started;
2778 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2780 if(this.refresh_dt_element !== null)
2781 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2784 this.updateChart = function(callback) {
2785 if(this.debug === true)
2786 this.log('updateChart() called.');
2788 if(this._updating === true) {
2789 if(this.debug === true)
2790 this.log('I am already updating...');
2792 if(typeof callback === 'function') callback();
2796 // due to late initialization of charts and libraries
2797 // we need to check this too
2798 if(this.enabled === false) {
2799 if(this.debug === true)
2800 this.log('I am not enabled');
2802 if(typeof callback === 'function') callback();
2806 if(canBeRendered() === false) {
2807 if(typeof callback === 'function') callback();
2811 if(this.chart === null) {
2812 this.getChart(function() { that.updateChart(callback); });
2816 if(this.library.initialized === false) {
2817 if(this.library.enabled === true) {
2818 this.library.initialize(function() { that.updateChart(callback); });
2822 error('chart library "' + this.library_name + '" is not available.');
2823 if(typeof callback === 'function') callback();
2828 this.clearSelection();
2831 if(this.debug === true)
2832 this.log('updating from ' + this.data_url);
2834 NETDATA.statistics.refreshes_total++;
2835 NETDATA.statistics.refreshes_active++;
2837 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2838 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2840 this._updating = true;
2842 this.xhr = $.ajax( {
2846 xhrFields: { withCredentials: true } // required for the cookie
2848 .done(function(data) {
2849 that.xhr = undefined;
2851 if(that.debug === true)
2852 that.log('data received. updating chart.');
2854 that.updateChartWithData(data);
2856 .fail(function(msg) {
2857 that.xhr = undefined;
2859 if(msg.statusText !== 'abort')
2860 error('data download failed for url: ' + that.data_url);
2862 .always(function() {
2863 that.xhr = undefined;
2865 NETDATA.statistics.refreshes_active--;
2866 that._updating = false;
2867 if(typeof callback === 'function') callback();
2873 this.isVisible = function(nocache) {
2874 if(typeof nocache === 'undefined')
2877 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2879 // caching - we do not evaluate the charts visibility
2880 // if the page has not been scrolled since the last check
2881 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2882 return this.___isVisible___;
2884 this.tm.last_visible_check = new Date().getTime();
2886 var wh = window.innerHeight;
2887 var x = this.element.getBoundingClientRect();
2891 if(x.width === 0 || x.height === 0) {
2893 this.___isVisible___ = false;
2894 return this.___isVisible___;
2897 if(x.top < 0 && -x.top > x.height) {
2898 // the chart is entirely above
2899 ret = -x.top - x.height;
2901 else if(x.top > wh) {
2902 // the chart is entirely below
2906 if(ret > tolerance) {
2907 // the chart is too far
2910 this.___isVisible___ = false;
2911 return this.___isVisible___;
2914 // the chart is inside or very close
2917 this.___isVisible___ = true;
2918 return this.___isVisible___;
2922 this.isAutoRefreshable = function() {
2923 return (this.current.autorefresh);
2926 this.canBeAutoRefreshed = function() {
2927 var now = new Date().getTime();
2929 if(this.running === true) {
2930 if(this.debug === true)
2931 this.log('I am already running');
2936 if(this.enabled === false) {
2937 if(this.debug === true)
2938 this.log('I am not enabled');
2943 if(this.library === null || this.library.enabled === false) {
2944 error('charting library "' + this.library_name + '" is not available');
2945 if(this.debug === true)
2946 this.log('My chart library ' + this.library_name + ' is not available');
2951 if(this.isVisible() === false) {
2952 if(NETDATA.options.debug.visibility === true || this.debug === true)
2953 this.log('I am not visible');
2958 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2959 if(this.debug === true)
2960 this.log('timed force update detected - allowing this update');
2962 this.current.force_update_at = 0;
2966 if(this.isAutoRefreshable() === true) {
2967 // allow the first update, even if the page is not visible
2968 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2969 if(NETDATA.options.debug.focus === true || this.debug === true)
2970 this.log('canBeAutoRefreshed(): page does not have focus');
2975 if(this.needsRecreation() === true) {
2976 if(this.debug === true)
2977 this.log('canBeAutoRefreshed(): needs re-creation.');
2982 // options valid only for autoRefresh()
2983 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2984 if(NETDATA.globalPanAndZoom.isActive()) {
2985 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2986 if(this.debug === true)
2987 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2992 if(this.debug === true)
2993 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2999 if(this.selected === true) {
3000 if(this.debug === true)
3001 this.log('canBeAutoRefreshed(): I have a selection in place.');
3006 if(this.paused === true) {
3007 if(this.debug === true)
3008 this.log('canBeAutoRefreshed(): I am paused.');
3013 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3014 if(this.debug === true)
3015 this.log('canBeAutoRefreshed(): It is time to update me.');
3025 this.autoRefresh = function(callback) {
3026 if(this.canBeAutoRefreshed() === true && this.running === false) {
3029 state.running = true;
3030 state.updateChart(function() {
3031 state.running = false;
3033 if(typeof callback !== 'undefined')
3038 if(typeof callback !== 'undefined')
3043 this._defaultsFromDownloadedChart = function(chart) {
3045 this.chart_url = chart.url;
3046 this.data_update_every = chart.update_every * 1000;
3047 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3048 this.tm.last_info_downloaded = new Date().getTime();
3050 if(this.title === null)
3051 this.title = chart.title;
3053 if(this.units === null)
3054 this.units = chart.units;
3057 // fetch the chart description from the netdata server
3058 this.getChart = function(callback) {
3059 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3061 this._defaultsFromDownloadedChart(this.chart);
3062 if(typeof callback === 'function') callback();
3065 this.chart_url = "/api/v1/chart?chart=" + this.id;
3067 if(this.debug === true)
3068 this.log('downloading ' + this.chart_url);
3071 url: this.host + this.chart_url,
3074 xhrFields: { withCredentials: true } // required for the cookie
3076 .done(function(chart) {
3077 chart.url = that.chart_url;
3078 that._defaultsFromDownloadedChart(chart);
3079 NETDATA.chartRegistry.add(that.host, that.id, chart);
3082 NETDATA.error(404, that.chart_url);
3083 error('chart not found on url "' + that.chart_url + '"');
3085 .always(function() {
3086 if(typeof callback === 'function') callback();
3091 // ============================================================================================================
3097 NETDATA.resetAllCharts = function(state) {
3098 // first clear the global selection sync
3099 // to make sure no chart is in selected state
3100 state.globalSelectionSyncStop();
3102 // there are 2 possibilities here
3103 // a. state is the global Pan and Zoom master
3104 // b. state is not the global Pan and Zoom master
3106 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3109 // clear the global Pan and Zoom
3110 // this will also refresh the master
3111 // and unblock any charts currently mirroring the master
3112 NETDATA.globalPanAndZoom.clearMaster();
3114 // if we were not the master, reset our status too
3115 // this is required because most probably the mouse
3116 // is over this chart, blocking it from auto-refreshing
3117 if(master === false && (state.paused === true || state.selected === true))
3121 // get or create a chart state, given a DOM element
3122 NETDATA.chartState = function(element) {
3123 var state = $(element).data('netdata-state-object') || null;
3124 if(state === null) {
3125 state = new chartState(element);
3126 $(element).data('netdata-state-object', state);
3131 // ----------------------------------------------------------------------------------------------------------------
3132 // Library functions
3134 // Load a script without jquery
3135 // This is used to load jquery - after it is loaded, we use jquery
3136 NETDATA._loadjQuery = function(callback) {
3137 if(typeof jQuery === 'undefined') {
3138 if(NETDATA.options.debug.main_loop === true)
3139 console.log('loading ' + NETDATA.jQuery);
3141 var script = document.createElement('script');
3142 script.type = 'text/javascript';
3143 script.async = true;
3144 script.src = NETDATA.jQuery;
3146 // script.onabort = onError;
3147 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3148 if(typeof callback === "function")
3149 script.onload = callback;
3151 var s = document.getElementsByTagName('script')[0];
3152 s.parentNode.insertBefore(script, s);
3154 else if(typeof callback === "function")
3158 NETDATA._loadCSS = function(filename) {
3159 // don't use jQuery here
3160 // styles are loaded before jQuery
3161 // to eliminate showing an unstyled page to the user
3163 var fileref = document.createElement("link");
3164 fileref.setAttribute("rel", "stylesheet");
3165 fileref.setAttribute("type", "text/css");
3166 fileref.setAttribute("href", filename);
3168 if (typeof fileref !== 'undefined')
3169 document.getElementsByTagName("head")[0].appendChild(fileref);
3172 NETDATA.colorHex2Rgb = function(hex) {
3173 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3174 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3175 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3176 return r + r + g + g + b + b;
3179 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3181 r: parseInt(result[1], 16),
3182 g: parseInt(result[2], 16),
3183 b: parseInt(result[3], 16)
3187 NETDATA.colorLuminance = function(hex, lum) {
3188 // validate hex string
3189 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3191 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3195 // convert to decimal and change luminosity
3196 var rgb = "#", c, i;
3197 for (i = 0; i < 3; i++) {
3198 c = parseInt(hex.substr(i*2,2), 16);
3199 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3200 rgb += ("00"+c).substr(c.length);
3206 NETDATA.guid = function() {
3208 return Math.floor((1 + Math.random()) * 0x10000)
3213 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3216 NETDATA.zeropad = function(x) {
3217 if(x > -10 && x < 10) return '0' + x.toString();
3218 else return x.toString();
3221 // user function to signal us the DOM has been
3223 NETDATA.updatedDom = function() {
3224 NETDATA.options.updated_dom = true;
3227 NETDATA.ready = function(callback) {
3228 NETDATA.options.pauseCallback = callback;
3231 NETDATA.pause = function(callback) {
3232 if(NETDATA.options.pause === true)
3235 NETDATA.options.pauseCallback = callback;
3238 NETDATA.unpause = function() {
3239 NETDATA.options.pauseCallback = null;
3240 NETDATA.options.updated_dom = true;
3241 NETDATA.options.pause = false;
3244 // ----------------------------------------------------------------------------------------------------------------
3246 // this is purely sequencial charts refresher
3247 // it is meant to be autonomous
3248 NETDATA.chartRefresherNoParallel = function(index) {
3249 if(NETDATA.options.debug.mail_loop === true)
3250 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3252 if(NETDATA.options.updated_dom === true) {
3253 // the dom has been updated
3254 // get the dom parts again
3255 NETDATA.parseDom(NETDATA.chartRefresher);
3258 if(index >= NETDATA.options.targets.length) {
3259 if(NETDATA.options.debug.main_loop === true)
3260 console.log('waiting to restart main loop...');
3262 NETDATA.options.auto_refresher_fast_weight = 0;
3264 setTimeout(function() {
3265 NETDATA.chartRefresher();
3266 }, NETDATA.options.current.idle_between_loops);
3269 var state = NETDATA.options.targets[index];
3271 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3272 if(NETDATA.options.debug.main_loop === true)
3273 console.log('fast rendering...');
3275 state.autoRefresh(function() {
3276 NETDATA.chartRefresherNoParallel(++index);
3280 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3281 NETDATA.options.auto_refresher_fast_weight = 0;
3283 setTimeout(function() {
3284 state.autoRefresh(function() {
3285 NETDATA.chartRefresherNoParallel(++index);
3287 }, NETDATA.options.current.idle_between_charts);
3292 // this is part of the parallel refresher
3293 // its cause is to refresh sequencially all the charts
3294 // that depend on chart library initialization
3295 // it will call the parallel refresher back
3296 // as soon as it sees a chart that its chart library
3298 NETDATA.chartRefresher_uninitialized = function() {
3299 if(NETDATA.options.updated_dom === true) {
3300 // the dom has been updated
3301 // get the dom parts again
3302 NETDATA.parseDom(NETDATA.chartRefresher);
3306 if(NETDATA.options.sequencial.length === 0)
3307 NETDATA.chartRefresher();
3309 var state = NETDATA.options.sequencial.pop();
3310 if(state.library.initialized === true)
3311 NETDATA.chartRefresher();
3313 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3317 NETDATA.chartRefresherWaitTime = function() {
3318 return NETDATA.options.current.idle_parallel_loops;
3321 // the default refresher
3322 // it will create 2 sets of charts:
3323 // - the ones that can be refreshed in parallel
3324 // - the ones that depend on something else
3325 // the first set will be executed in parallel
3326 // the second will be given to NETDATA.chartRefresher_uninitialized()
3327 NETDATA.chartRefresher = function() {
3328 // console.log('auto-refresher...');
3330 if(NETDATA.options.pause === true) {
3331 // console.log('auto-refresher is paused');
3332 setTimeout(NETDATA.chartRefresher,
3333 NETDATA.chartRefresherWaitTime());
3337 if(typeof NETDATA.options.pauseCallback === 'function') {
3338 // console.log('auto-refresher is calling pauseCallback');
3339 NETDATA.options.pause = true;
3340 NETDATA.options.pauseCallback();
3341 NETDATA.chartRefresher();
3345 if(NETDATA.options.current.parallel_refresher === false) {
3346 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3347 NETDATA.chartRefresherNoParallel(0);
3351 if(NETDATA.options.updated_dom === true) {
3352 // the dom has been updated
3353 // get the dom parts again
3354 // console.log('auto-refresher is calling parseDom()');
3355 NETDATA.parseDom(NETDATA.chartRefresher);
3359 var parallel = new Array();
3360 var targets = NETDATA.options.targets;
3361 var len = targets.length;
3364 state = targets[len];
3365 if(state.isVisible() === false || state.running === true)
3368 if(state.library.initialized === false) {
3369 if(state.library.enabled === true) {
3370 state.library.initialize(NETDATA.chartRefresher);
3374 state.error('chart library "' + state.library_name + '" is not enabled.');
3378 parallel.unshift(state);
3381 if(parallel.length > 0) {
3382 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3383 // this will execute the jobs in parallel
3384 $(parallel).each(function() {
3389 // console.log('auto-refresher nothing to do');
3392 // run the next refresh iteration
3393 setTimeout(NETDATA.chartRefresher,
3394 NETDATA.chartRefresherWaitTime());
3397 NETDATA.parseDom = function(callback) {
3398 NETDATA.options.last_page_scroll = new Date().getTime();
3399 NETDATA.options.updated_dom = false;
3401 var targets = $('div[data-netdata]'); //.filter(':visible');
3403 if(NETDATA.options.debug.main_loop === true)
3404 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3406 NETDATA.options.targets = new Array();
3407 var len = targets.length;
3409 // the initialization will take care of sizing
3410 // and the "loading..." message
3411 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3414 if(typeof callback === 'function') callback();
3417 // this is the main function - where everything starts
3418 NETDATA.start = function() {
3419 // this should be called only once
3421 NETDATA.options.page_is_visible = true;
3423 $(window).blur(function() {
3424 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3425 NETDATA.options.page_is_visible = false;
3426 if(NETDATA.options.debug.focus === true)
3427 console.log('Lost Focus!');
3431 $(window).focus(function() {
3432 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3433 NETDATA.options.page_is_visible = true;
3434 if(NETDATA.options.debug.focus === true)
3435 console.log('Focus restored!');
3439 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3440 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3441 NETDATA.options.page_is_visible = false;
3442 if(NETDATA.options.debug.focus === true)
3443 console.log('Document has no focus!');
3447 // bootstrap tab switching
3448 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3450 // bootstrap modal switching
3451 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3452 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3454 // bootstrap collapse switching
3455 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3456 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3458 NETDATA.parseDom(NETDATA.chartRefresher);
3460 // Alarms initialization
3461 setTimeout(NETDATA.alarms.init, 1000);
3463 // Registry initialization
3464 setTimeout(NETDATA.registry.init, 1500);
3467 // ----------------------------------------------------------------------------------------------------------------
3470 NETDATA.peityInitialize = function(callback) {
3471 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3473 url: NETDATA.peity_js,
3476 xhrFields: { withCredentials: true } // required for the cookie
3479 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3482 NETDATA.chartLibraries.peity.enabled = false;
3483 NETDATA.error(100, NETDATA.peity_js);
3485 .always(function() {
3486 if(typeof callback === "function")
3491 NETDATA.chartLibraries.peity.enabled = false;
3492 if(typeof callback === "function")
3497 NETDATA.peityChartUpdate = function(state, data) {
3498 state.peity_instance.innerHTML = data.result;
3500 if(state.peity_options.stroke !== state.chartColors()[0]) {
3501 state.peity_options.stroke = state.chartColors()[0];
3502 if(state.chart.chart_type === 'line')
3503 state.peity_options.fill = NETDATA.themes.current.background;
3505 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3508 $(state.peity_instance).peity('line', state.peity_options);
3512 NETDATA.peityChartCreate = function(state, data) {
3513 state.peity_instance = document.createElement('div');
3514 state.element_chart.appendChild(state.peity_instance);
3516 var self = $(state.element);
3517 state.peity_options = {
3518 stroke: NETDATA.themes.current.foreground,
3519 strokeWidth: self.data('peity-strokewidth') || 1,
3520 width: state.chartWidth(),
3521 height: state.chartHeight(),
3522 fill: NETDATA.themes.current.foreground
3525 NETDATA.peityChartUpdate(state, data);
3529 // ----------------------------------------------------------------------------------------------------------------
3532 NETDATA.sparklineInitialize = function(callback) {
3533 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3535 url: NETDATA.sparkline_js,
3538 xhrFields: { withCredentials: true } // required for the cookie
3541 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3544 NETDATA.chartLibraries.sparkline.enabled = false;
3545 NETDATA.error(100, NETDATA.sparkline_js);
3547 .always(function() {
3548 if(typeof callback === "function")
3553 NETDATA.chartLibraries.sparkline.enabled = false;
3554 if(typeof callback === "function")
3559 NETDATA.sparklineChartUpdate = function(state, data) {
3560 state.sparkline_options.width = state.chartWidth();
3561 state.sparkline_options.height = state.chartHeight();
3563 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3567 NETDATA.sparklineChartCreate = function(state, data) {
3568 var self = $(state.element);
3569 var type = self.data('sparkline-type') || 'line';
3570 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3571 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3572 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3573 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3574 var composite = self.data('sparkline-composite') || undefined;
3575 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3576 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3577 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3578 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3579 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3580 var spotColor = self.data('sparkline-spotcolor') || undefined;
3581 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3582 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3583 var spotRadius = self.data('sparkline-spotradius') || undefined;
3584 var valueSpots = self.data('sparkline-valuespots') || undefined;
3585 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3586 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3587 var lineWidth = self.data('sparkline-linewidth') || undefined;
3588 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3589 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3590 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3591 var xvalues = self.data('sparkline-xvalues') || undefined;
3592 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3593 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3594 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3595 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3596 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3597 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3598 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3599 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3600 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3601 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3602 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3603 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3604 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3605 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3606 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3607 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3608 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3609 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3610 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3611 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3612 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3613 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3615 if(spotColor === 'disable') spotColor='';
3616 if(minSpotColor === 'disable') minSpotColor='';
3617 if(maxSpotColor === 'disable') maxSpotColor='';
3619 state.sparkline_options = {
3621 lineColor: lineColor,
3622 fillColor: fillColor,
3623 chartRangeMin: chartRangeMin,
3624 chartRangeMax: chartRangeMax,
3625 composite: composite,
3626 enableTagOptions: enableTagOptions,
3627 tagOptionPrefix: tagOptionPrefix,
3628 tagValuesAttribute: tagValuesAttribute,
3629 disableHiddenCheck: disableHiddenCheck,
3630 defaultPixelsPerValue: defaultPixelsPerValue,
3631 spotColor: spotColor,
3632 minSpotColor: minSpotColor,
3633 maxSpotColor: maxSpotColor,
3634 spotRadius: spotRadius,
3635 valueSpots: valueSpots,
3636 highlightSpotColor: highlightSpotColor,
3637 highlightLineColor: highlightLineColor,
3638 lineWidth: lineWidth,
3639 normalRangeMin: normalRangeMin,
3640 normalRangeMax: normalRangeMax,
3641 drawNormalOnTop: drawNormalOnTop,
3643 chartRangeClip: chartRangeClip,
3644 chartRangeMinX: chartRangeMinX,
3645 chartRangeMaxX: chartRangeMaxX,
3646 disableInteraction: disableInteraction,
3647 disableTooltips: disableTooltips,
3648 disableHighlight: disableHighlight,
3649 highlightLighten: highlightLighten,
3650 highlightColor: highlightColor,
3651 tooltipContainer: tooltipContainer,
3652 tooltipClassname: tooltipClassname,
3653 tooltipChartTitle: state.title,
3654 tooltipFormat: tooltipFormat,
3655 tooltipPrefix: tooltipPrefix,
3656 tooltipSuffix: tooltipSuffix,
3657 tooltipSkipNull: tooltipSkipNull,
3658 tooltipValueLookups: tooltipValueLookups,
3659 tooltipFormatFieldlist: tooltipFormatFieldlist,
3660 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3661 numberFormatter: numberFormatter,
3662 numberDigitGroupSep: numberDigitGroupSep,
3663 numberDecimalMark: numberDecimalMark,
3664 numberDigitGroupCount: numberDigitGroupCount,
3665 animatedZooms: animatedZooms,
3666 width: state.chartWidth(),
3667 height: state.chartHeight()
3670 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3674 // ----------------------------------------------------------------------------------------------------------------
3681 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3682 if(after < state.netdata_first)
3683 after = state.netdata_first;
3685 if(before > state.netdata_last)
3686 before = state.netdata_last;
3688 state.setMode('zoom');
3689 state.globalSelectionSyncStop();
3690 state.globalSelectionSyncDelay();
3691 state.dygraph_user_action = true;
3692 state.dygraph_force_zoom = true;
3693 state.updateChartPanOrZoom(after, before);
3694 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3697 NETDATA.dygraphSetSelection = function(state, t) {
3698 if(typeof state.dygraph_instance !== 'undefined') {
3699 var r = state.calculateRowForTime(t);
3701 state.dygraph_instance.setSelection(r);
3703 state.dygraph_instance.clearSelection();
3704 state.legendShowUndefined();
3711 NETDATA.dygraphClearSelection = function(state, t) {
3712 if(typeof state.dygraph_instance !== 'undefined') {
3713 state.dygraph_instance.clearSelection();
3718 NETDATA.dygraphSmoothInitialize = function(callback) {
3720 url: NETDATA.dygraph_smooth_js,
3723 xhrFields: { withCredentials: true } // required for the cookie
3726 NETDATA.dygraph.smooth = true;
3727 smoothPlotter.smoothing = 0.3;
3730 NETDATA.dygraph.smooth = false;
3732 .always(function() {
3733 if(typeof callback === "function")
3738 NETDATA.dygraphInitialize = function(callback) {
3739 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3741 url: NETDATA.dygraph_js,
3744 xhrFields: { withCredentials: true } // required for the cookie
3747 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3750 NETDATA.chartLibraries.dygraph.enabled = false;
3751 NETDATA.error(100, NETDATA.dygraph_js);
3753 .always(function() {
3754 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3755 NETDATA.dygraphSmoothInitialize(callback);
3756 else if(typeof callback === "function")
3761 NETDATA.chartLibraries.dygraph.enabled = false;
3762 if(typeof callback === "function")
3767 NETDATA.dygraphChartUpdate = function(state, data) {
3768 var dygraph = state.dygraph_instance;
3770 if(typeof dygraph === 'undefined')
3771 return NETDATA.dygraphChartCreate(state, data);
3773 // when the chart is not visible, and hidden
3774 // if there is a window resize, dygraph detects
3775 // its element size as 0x0.
3776 // this will make it re-appear properly
3778 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3782 file: data.result.data,
3783 colors: state.chartColors(),
3784 labels: data.result.labels,
3785 labelsDivWidth: state.chartWidth() - 70,
3786 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3789 if(state.dygraph_force_zoom === true) {
3790 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3791 state.log('dygraphChartUpdate() forced zoom update');
3793 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3794 options.valueRange = state.dygraph_options.valueRange;
3795 options.isZoomedIgnoreProgrammaticZoom = true;
3796 state.dygraph_force_zoom = false;
3798 else if(state.current.name !== 'auto') {
3799 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3800 state.log('dygraphChartUpdate() loose update');
3802 options.valueRange = state.dygraph_options.valueRange;
3805 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3806 state.log('dygraphChartUpdate() strict update');
3808 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3809 options.valueRange = state.dygraph_options.valueRange;
3810 options.isZoomedIgnoreProgrammaticZoom = true;
3813 if(state.dygraph_smooth_eligible === true) {
3814 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3815 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3816 NETDATA.dygraphChartCreate(state, data);
3821 dygraph.updateOptions(options);
3823 state.dygraph_last_rendered = new Date().getTime();
3827 NETDATA.dygraphChartCreate = function(state, data) {
3828 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3829 state.log('dygraphChartCreate()');
3831 var self = $(state.element);
3833 var chart_type = state.chart.chart_type;
3834 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3835 chart_type = self.data('dygraph-type') || chart_type;
3837 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3838 smooth = self.data('dygraph-smooth') || smooth;
3840 if(NETDATA.dygraph.smooth === false)
3843 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3844 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3846 state.dygraph_options = {
3847 colors: self.data('dygraph-colors') || state.chartColors(),
3849 // leave a few pixels empty on the right of the chart
3850 rightGap: self.data('dygraph-rightgap') || 5,
3851 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3852 showRoller: self.data('dygraph-showroller') || false,
3854 title: self.data('dygraph-title') || state.title,
3855 titleHeight: self.data('dygraph-titleheight') || 19,
3857 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3858 labels: data.result.labels,
3859 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3860 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3861 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3862 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3863 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3866 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3867 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3869 includeZero: self.data('dygraph-includezero') || false,
3870 xRangePad: self.data('dygraph-xrangepad') || 0,
3871 yRangePad: self.data('dygraph-yrangepad') || 1,
3873 valueRange: self.data('dygraph-valuerange') || null,
3875 ylabel: state.units,
3876 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3878 // the function to plot the chart
3881 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3882 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3883 strokePattern: self.data('dygraph-strokepattern') || undefined,
3885 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3886 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3887 drawPoints: self.data('dygraph-drawpoints') || false,
3889 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3890 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3892 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3893 pointSize: self.data('dygraph-pointsize') || 1,
3895 // enabling this makes the chart with little square lines
3896 stepPlot: self.data('dygraph-stepplot') || false,
3898 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3899 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3900 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3902 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3903 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3904 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3905 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3907 drawAxis: self.data('dygraph-drawaxis') || true,
3908 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3909 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3910 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3912 drawGrid: self.data('dygraph-drawgrid') || true,
3913 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3914 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3915 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3916 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3917 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3919 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3920 sigFigs: self.data('dygraph-sigfigs') || null,
3921 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3922 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3924 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3925 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3926 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3928 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3929 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3933 ticker: Dygraph.dateTicker,
3934 axisLabelFormatter: function (d, gran) {
3935 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3937 valueFormatter: function (ms) {
3938 var d = new Date(ms);
3939 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3940 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3945 valueFormatter: function (x) {
3946 // we format legends with the state object
3947 // no need to do anything here
3948 // return (Math.round(x*100) / 100).toLocaleString();
3949 // return state.legendFormatValue(x);
3954 legendFormatter: function(data) {
3955 var elements = state.element_legend_childs;
3957 // if the hidden div is not there
3958 // we are not managing the legend
3959 if(elements.hidden === null) return;
3961 if (typeof data.x !== 'undefined') {
3962 state.legendSetDate(data.x);
3963 var i = data.series.length;
3965 var series = data.series[i];
3966 if(!series.isVisible) continue;
3967 state.legendSetLabelValue(series.label, series.y);
3973 drawCallback: function(dygraph, is_initial) {
3974 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3975 state.dygraph_user_action = false;
3977 var x_range = dygraph.xAxisRange();
3978 var after = Math.round(x_range[0]);
3979 var before = Math.round(x_range[1]);
3981 if(NETDATA.options.debug.dygraph === true)
3982 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3984 if(before <= state.netdata_last && after >= state.netdata_first)
3985 state.updateChartPanOrZoom(after, before);
3988 zoomCallback: function(minDate, maxDate, yRanges) {
3989 if(NETDATA.options.debug.dygraph === true)
3990 state.log('dygraphZoomCallback()');
3992 state.globalSelectionSyncStop();
3993 state.globalSelectionSyncDelay();
3994 state.setMode('zoom');
3996 // refresh it to the greatest possible zoom level
3997 state.dygraph_user_action = true;
3998 state.dygraph_force_zoom = true;
3999 state.updateChartPanOrZoom(minDate, maxDate);
4001 highlightCallback: function(event, x, points, row, seriesName) {
4002 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4003 state.log('dygraphHighlightCallback()');
4007 // there is a bug in dygraph when the chart is zoomed enough
4008 // the time it thinks is selected is wrong
4009 // here we calculate the time t based on the row number selected
4011 var t = state.data_after + row * state.data_update_every;
4012 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
4014 state.globalSelectionSync(x);
4016 // fix legend zIndex using the internal structures of dygraph legend module
4017 // this works, but it is a hack!
4018 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4020 unhighlightCallback: function(event) {
4021 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4022 state.log('dygraphUnhighlightCallback()');
4024 state.unpauseChart();
4025 state.globalSelectionSyncStop();
4027 interactionModel : {
4028 mousedown: function(event, dygraph, context) {
4029 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4030 state.log('interactionModel.mousedown()');
4032 state.dygraph_user_action = true;
4033 state.globalSelectionSyncStop();
4035 if(NETDATA.options.debug.dygraph === true)
4036 state.log('dygraphMouseDown()');
4038 // Right-click should not initiate a zoom.
4039 if(event.button && event.button === 2) return;
4041 context.initializeMouseDown(event, dygraph, context);
4043 if(event.button && event.button === 1) {
4044 if (event.altKey || event.shiftKey) {
4045 state.setMode('pan');
4046 state.globalSelectionSyncDelay();
4047 Dygraph.startPan(event, dygraph, context);
4050 state.setMode('zoom');
4051 state.globalSelectionSyncDelay();
4052 Dygraph.startZoom(event, dygraph, context);
4056 if (event.altKey || event.shiftKey) {
4057 state.setMode('zoom');
4058 state.globalSelectionSyncDelay();
4059 Dygraph.startZoom(event, dygraph, context);
4062 state.setMode('pan');
4063 state.globalSelectionSyncDelay();
4064 Dygraph.startPan(event, dygraph, context);
4068 mousemove: function(event, dygraph, context) {
4069 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4070 state.log('interactionModel.mousemove()');
4072 if(context.isPanning) {
4073 state.dygraph_user_action = true;
4074 state.globalSelectionSyncStop();
4075 state.globalSelectionSyncDelay();
4076 state.setMode('pan');
4077 Dygraph.movePan(event, dygraph, context);
4079 else if(context.isZooming) {
4080 state.dygraph_user_action = true;
4081 state.globalSelectionSyncStop();
4082 state.globalSelectionSyncDelay();
4083 state.setMode('zoom');
4084 Dygraph.moveZoom(event, dygraph, context);
4087 mouseup: function(event, dygraph, context) {
4088 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4089 state.log('interactionModel.mouseup()');
4091 if (context.isPanning) {
4092 state.dygraph_user_action = true;
4093 state.globalSelectionSyncDelay();
4094 Dygraph.endPan(event, dygraph, context);
4096 else if (context.isZooming) {
4097 state.dygraph_user_action = true;
4098 state.globalSelectionSyncDelay();
4099 Dygraph.endZoom(event, dygraph, context);
4102 click: function(event, dygraph, context) {
4103 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4104 state.log('interactionModel.click()');
4106 event.preventDefault();
4108 dblclick: function(event, dygraph, context) {
4109 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4110 state.log('interactionModel.dblclick()');
4111 NETDATA.resetAllCharts(state);
4113 mousewheel: function(event, dygraph, context) {
4114 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4115 state.log('interactionModel.mousewheel()');
4117 // Take the offset of a mouse event on the dygraph canvas and
4118 // convert it to a pair of percentages from the bottom left.
4119 // (Not top left, bottom is where the lower value is.)
4120 function offsetToPercentage(g, offsetX, offsetY) {
4121 // This is calculating the pixel offset of the leftmost date.
4122 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4123 var yar0 = g.yAxisRange(0);
4125 // This is calculating the pixel of the higest value. (Top pixel)
4126 var yOffset = g.toDomCoords(null, yar0[1])[1];
4128 // x y w and h are relative to the corner of the drawing area,
4129 // so that the upper corner of the drawing area is (0, 0).
4130 var x = offsetX - xOffset;
4131 var y = offsetY - yOffset;
4133 // This is computing the rightmost pixel, effectively defining the
4135 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4137 // This is computing the lowest pixel, effectively defining the height.
4138 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4140 // Percentage from the left.
4141 var xPct = w === 0 ? 0 : (x / w);
4142 // Percentage from the top.
4143 var yPct = h === 0 ? 0 : (y / h);
4145 // The (1-) part below changes it from "% distance down from the top"
4146 // to "% distance up from the bottom".
4147 return [xPct, (1-yPct)];
4150 // Adjusts [x, y] toward each other by zoomInPercentage%
4151 // Split it so the left/bottom axis gets xBias/yBias of that change and
4152 // tight/top gets (1-xBias)/(1-yBias) of that change.
4154 // If a bias is missing it splits it down the middle.
4155 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4156 xBias = xBias || 0.5;
4157 yBias = yBias || 0.5;
4159 function adjustAxis(axis, zoomInPercentage, bias) {
4160 var delta = axis[1] - axis[0];
4161 var increment = delta * zoomInPercentage;
4162 var foo = [increment * bias, increment * (1-bias)];
4164 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4167 var yAxes = g.yAxisRanges();
4169 for (var i = 0; i < yAxes.length; i++) {
4170 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4173 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4176 if(event.altKey || event.shiftKey) {
4177 state.dygraph_user_action = true;
4179 state.globalSelectionSyncStop();
4180 state.globalSelectionSyncDelay();
4182 // http://dygraphs.com/gallery/interaction-api.js
4183 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4184 var percentage = normal / 50;
4186 if (!(event.offsetX && event.offsetY)){
4187 event.offsetX = event.layerX - event.target.offsetLeft;
4188 event.offsetY = event.layerY - event.target.offsetTop;
4191 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4192 var xPct = percentages[0];
4193 var yPct = percentages[1];
4195 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4197 var after = new_x_range[0];
4198 var before = new_x_range[1];
4200 var first = state.netdata_first + state.data_update_every;
4201 var last = state.netdata_last + state.data_update_every;
4204 after -= (before - last);
4211 state.setMode('zoom');
4212 if(state.updateChartPanOrZoom(after, before) === true)
4213 dygraph.updateOptions({ dateWindow: [ after, before ] });
4215 event.preventDefault();
4218 touchstart: function(event, dygraph, context) {
4219 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4220 state.log('interactionModel.touchstart()');
4222 state.dygraph_user_action = true;
4223 state.setMode('zoom');
4226 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4228 // we overwrite the touch directions at the end, to overwrite
4229 // the internal default of dygraphs
4230 context.touchDirections = { x: true, y: false };
4232 state.dygraph_last_touch_start = new Date().getTime();
4233 state.dygraph_last_touch_move = 0;
4235 if(typeof event.touches[0].pageX === 'number')
4236 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4238 state.dygraph_last_touch_page_x = 0;
4240 touchmove: function(event, dygraph, context) {
4241 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4242 state.log('interactionModel.touchmove()');
4244 state.dygraph_user_action = true;
4245 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4247 state.dygraph_last_touch_move = new Date().getTime();
4249 touchend: function(event, dygraph, context) {
4250 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4251 state.log('interactionModel.touchend()');
4253 state.dygraph_user_action = true;
4254 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4256 // if it didn't move, it is a selection
4257 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4258 // internal api of dygraphs
4259 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4260 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4261 if(NETDATA.dygraphSetSelection(state, t) === true)
4262 state.globalSelectionSync(t);
4265 // if it was double tap within double click time, reset the charts
4266 var now = new Date().getTime();
4267 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4268 if(state.dygraph_last_touch_move === 0) {
4269 var dt = now - state.dygraph_last_touch_end;
4270 if(dt <= NETDATA.options.current.double_click_speed)
4271 NETDATA.resetAllCharts(state);
4275 // remember the timestamp of the last touch end
4276 state.dygraph_last_touch_end = now;
4281 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4282 state.dygraph_options.drawGrid = false;
4283 state.dygraph_options.drawAxis = false;
4284 state.dygraph_options.title = undefined;
4285 state.dygraph_options.units = undefined;
4286 state.dygraph_options.ylabel = undefined;
4287 state.dygraph_options.yLabelWidth = 0;
4288 state.dygraph_options.labelsDivWidth = 120;
4289 state.dygraph_options.labelsDivStyles.width = '120px';
4290 state.dygraph_options.labelsSeparateLines = true;
4291 state.dygraph_options.rightGap = 0;
4292 state.dygraph_options.yRangePad = 1;
4295 if(smooth === true) {
4296 state.dygraph_smooth_eligible = true;
4298 if(NETDATA.options.current.smooth_plot === true)
4299 state.dygraph_options.plotter = smoothPlotter;
4301 else state.dygraph_smooth_eligible = false;
4303 state.dygraph_instance = new Dygraph(state.element_chart,
4304 data.result.data, state.dygraph_options);
4306 state.dygraph_force_zoom = false;
4307 state.dygraph_user_action = false;
4308 state.dygraph_last_rendered = new Date().getTime();
4312 // ----------------------------------------------------------------------------------------------------------------
4315 NETDATA.morrisInitialize = function(callback) {
4316 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4318 // morris requires raphael
4319 if(!NETDATA.chartLibraries.raphael.initialized) {
4320 if(NETDATA.chartLibraries.raphael.enabled) {
4321 NETDATA.raphaelInitialize(function() {
4322 NETDATA.morrisInitialize(callback);
4326 NETDATA.chartLibraries.morris.enabled = false;
4327 if(typeof callback === "function")
4332 NETDATA._loadCSS(NETDATA.morris_css);
4335 url: NETDATA.morris_js,
4338 xhrFields: { withCredentials: true } // required for the cookie
4341 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4344 NETDATA.chartLibraries.morris.enabled = false;
4345 NETDATA.error(100, NETDATA.morris_js);
4347 .always(function() {
4348 if(typeof callback === "function")
4354 NETDATA.chartLibraries.morris.enabled = false;
4355 if(typeof callback === "function")
4360 NETDATA.morrisChartUpdate = function(state, data) {
4361 state.morris_instance.setData(data.result.data);
4365 NETDATA.morrisChartCreate = function(state, data) {
4367 state.morris_options = {
4368 element: state.element_chart.id,
4369 data: data.result.data,
4371 ykeys: data.dimension_names,
4372 labels: data.dimension_names,
4378 continuousLine: false,
4379 behaveLikeLine: false
4382 if(state.chart.chart_type === 'line')
4383 state.morris_instance = new Morris.Line(state.morris_options);
4385 else if(state.chart.chart_type === 'area') {
4386 state.morris_options.behaveLikeLine = true;
4387 state.morris_instance = new Morris.Area(state.morris_options);
4390 state.morris_instance = new Morris.Area(state.morris_options);
4395 // ----------------------------------------------------------------------------------------------------------------
4398 NETDATA.raphaelInitialize = function(callback) {
4399 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4401 url: NETDATA.raphael_js,
4404 xhrFields: { withCredentials: true } // required for the cookie
4407 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4410 NETDATA.chartLibraries.raphael.enabled = false;
4411 NETDATA.error(100, NETDATA.raphael_js);
4413 .always(function() {
4414 if(typeof callback === "function")
4419 NETDATA.chartLibraries.raphael.enabled = false;
4420 if(typeof callback === "function")
4425 NETDATA.raphaelChartUpdate = function(state, data) {
4426 $(state.element_chart).raphael(data.result, {
4427 width: state.chartWidth(),
4428 height: state.chartHeight()
4434 NETDATA.raphaelChartCreate = function(state, data) {
4435 $(state.element_chart).raphael(data.result, {
4436 width: state.chartWidth(),
4437 height: state.chartHeight()
4443 // ----------------------------------------------------------------------------------------------------------------
4446 NETDATA.c3Initialize = function(callback) {
4447 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4450 if(!NETDATA.chartLibraries.d3.initialized) {
4451 if(NETDATA.chartLibraries.d3.enabled) {
4452 NETDATA.d3Initialize(function() {
4453 NETDATA.c3Initialize(callback);
4457 NETDATA.chartLibraries.c3.enabled = false;
4458 if(typeof callback === "function")
4463 NETDATA._loadCSS(NETDATA.c3_css);
4469 xhrFields: { withCredentials: true } // required for the cookie
4472 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4475 NETDATA.chartLibraries.c3.enabled = false;
4476 NETDATA.error(100, NETDATA.c3_js);
4478 .always(function() {
4479 if(typeof callback === "function")
4485 NETDATA.chartLibraries.c3.enabled = false;
4486 if(typeof callback === "function")
4491 NETDATA.c3ChartUpdate = function(state, data) {
4492 state.c3_instance.destroy();
4493 return NETDATA.c3ChartCreate(state, data);
4495 //state.c3_instance.load({
4496 // rows: data.result,
4503 NETDATA.c3ChartCreate = function(state, data) {
4505 state.element_chart.id = 'c3-' + state.uuid;
4506 // console.log('id = ' + state.element_chart.id);
4508 state.c3_instance = c3.generate({
4509 bindto: '#' + state.element_chart.id,
4511 width: state.chartWidth(),
4512 height: state.chartHeight()
4515 pattern: state.chartColors()
4520 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4526 format: function(x) {
4527 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4554 // console.log(state.c3_instance);
4559 // ----------------------------------------------------------------------------------------------------------------
4562 NETDATA.d3Initialize = function(callback) {
4563 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4568 xhrFields: { withCredentials: true } // required for the cookie
4571 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4574 NETDATA.chartLibraries.d3.enabled = false;
4575 NETDATA.error(100, NETDATA.d3_js);
4577 .always(function() {
4578 if(typeof callback === "function")
4583 NETDATA.chartLibraries.d3.enabled = false;
4584 if(typeof callback === "function")
4589 NETDATA.d3ChartUpdate = function(state, data) {
4593 NETDATA.d3ChartCreate = function(state, data) {
4597 // ----------------------------------------------------------------------------------------------------------------
4600 NETDATA.googleInitialize = function(callback) {
4601 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4603 url: NETDATA.google_js,
4606 xhrFields: { withCredentials: true } // required for the cookie
4609 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4610 google.load('visualization', '1.1', {
4611 'packages': ['corechart', 'controls'],
4612 'callback': callback
4616 NETDATA.chartLibraries.google.enabled = false;
4617 NETDATA.error(100, NETDATA.google_js);
4618 if(typeof callback === "function")
4623 NETDATA.chartLibraries.google.enabled = false;
4624 if(typeof callback === "function")
4629 NETDATA.googleChartUpdate = function(state, data) {
4630 var datatable = new google.visualization.DataTable(data.result);
4631 state.google_instance.draw(datatable, state.google_options);
4635 NETDATA.googleChartCreate = function(state, data) {
4636 var datatable = new google.visualization.DataTable(data.result);
4638 state.google_options = {
4639 colors: state.chartColors(),
4641 // do not set width, height - the chart resizes itself
4642 //width: state.chartWidth(),
4643 //height: state.chartHeight(),
4648 // title: "Time of Day",
4649 // format:'HH:mm:ss',
4650 viewWindowMode: 'maximized',
4662 viewWindowMode: 'pretty',
4677 focusTarget: 'category',
4684 titlePosition: 'out',
4695 curveType: 'function',
4700 switch(state.chart.chart_type) {
4702 state.google_options.vAxis.viewWindowMode = 'maximized';
4703 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4704 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4708 state.google_options.isStacked = true;
4709 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4710 state.google_options.vAxis.viewWindowMode = 'maximized';
4711 state.google_options.vAxis.minValue = null;
4712 state.google_options.vAxis.maxValue = null;
4713 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4718 state.google_options.lineWidth = 2;
4719 state.google_instance = new google.visualization.LineChart(state.element_chart);
4723 state.google_instance.draw(datatable, state.google_options);
4727 // ----------------------------------------------------------------------------------------------------------------
4729 NETDATA.percentFromValueMax = function(value, max) {
4730 if(value === null) value = 0;
4731 if(max < value) max = value;
4735 pcent = Math.round(value * 100 / max);
4736 if(pcent === 0 && value > 0) pcent = 1;
4742 // ----------------------------------------------------------------------------------------------------------------
4745 NETDATA.easypiechartInitialize = function(callback) {
4746 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4748 url: NETDATA.easypiechart_js,
4751 xhrFields: { withCredentials: true } // required for the cookie
4754 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4757 NETDATA.chartLibraries.easypiechart.enabled = false;
4758 NETDATA.error(100, NETDATA.easypiechart_js);
4760 .always(function() {
4761 if(typeof callback === "function")
4766 NETDATA.chartLibraries.easypiechart.enabled = false;
4767 if(typeof callback === "function")
4772 NETDATA.easypiechartClearSelection = function(state) {
4773 if(typeof state.easyPieChartEvent !== 'undefined') {
4774 if(state.easyPieChartEvent.timer !== null)
4775 clearTimeout(state.easyPieChartEvent.timer);
4777 state.easyPieChartEvent.timer = null;
4780 if(state.isAutoRefreshable() === true && state.data !== null) {
4781 NETDATA.easypiechartChartUpdate(state, state.data);
4784 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4785 state.easyPieChart_instance.update(0);
4787 state.easyPieChart_instance.enableAnimation();
4792 NETDATA.easypiechartSetSelection = function(state, t) {
4793 if(state.timeIsVisible(t) !== true)
4794 return NETDATA.easypiechartClearSelection(state);
4796 var slot = state.calculateRowForTime(t);
4797 if(slot < 0 || slot >= state.data.result.length)
4798 return NETDATA.easypiechartClearSelection(state);
4800 if(typeof state.easyPieChartEvent === 'undefined') {
4801 state.easyPieChartEvent = {
4808 var value = state.data.result[state.data.result.length - 1 - slot];
4809 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4810 var pcent = NETDATA.percentFromValueMax(value, max);
4812 state.easyPieChartEvent.value = value;
4813 state.easyPieChartEvent.pcent = pcent;
4814 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4816 if(state.easyPieChartEvent.timer === null) {
4817 state.easyPieChart_instance.disableAnimation();
4819 state.easyPieChartEvent.timer = setTimeout(function() {
4820 state.easyPieChartEvent.timer = null;
4821 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4822 }, NETDATA.options.current.charts_selection_animation_delay);
4828 NETDATA.easypiechartChartUpdate = function(state, data) {
4829 var value, max, pcent;
4831 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4837 value = data.result[0];
4838 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4839 pcent = NETDATA.percentFromValueMax(value, max);
4842 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4843 state.easyPieChart_instance.update(pcent);
4847 NETDATA.easypiechartChartCreate = function(state, data) {
4848 var self = $(state.element);
4849 var chart = $(state.element_chart);
4851 var value = data.result[0];
4852 var max = self.data('easypiechart-max-value') || null;
4853 var adjust = self.data('easypiechart-adjust') || null;
4857 state.easyPieChartMax = null;
4860 state.easyPieChartMax = max;
4862 var pcent = NETDATA.percentFromValueMax(value, max);
4864 chart.data('data-percent', pcent);
4868 case 'width': size = state.chartHeight(); break;
4869 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4870 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4872 default: size = state.chartWidth(); break;
4874 state.element.style.width = size + 'px';
4875 state.element.style.height = size + 'px';
4877 var stroke = Math.floor(size / 22);
4878 if(stroke < 3) stroke = 2;
4880 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4881 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4882 state.easyPieChartLabel = document.createElement('span');
4883 state.easyPieChartLabel.className = 'easyPieChartLabel';
4884 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4885 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4886 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4887 state.element_chart.appendChild(state.easyPieChartLabel);
4889 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4890 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4891 state.easyPieChartTitle = document.createElement('span');
4892 state.easyPieChartTitle.className = 'easyPieChartTitle';
4893 state.easyPieChartTitle.innerHTML = state.title;
4894 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4895 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4896 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4897 state.element_chart.appendChild(state.easyPieChartTitle);
4899 var unitfontsize = Math.round(titlefontsize * 0.9);
4900 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4901 state.easyPieChartUnits = document.createElement('span');
4902 state.easyPieChartUnits.className = 'easyPieChartUnits';
4903 state.easyPieChartUnits.innerHTML = state.units;
4904 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4905 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4906 state.element_chart.appendChild(state.easyPieChartUnits);
4908 chart.easyPieChart({
4909 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4910 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4911 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4912 scaleLength: self.data('easypiechart-scalelength') || 5,
4913 lineCap: self.data('easypiechart-linecap') || 'round',
4914 lineWidth: self.data('easypiechart-linewidth') || stroke,
4915 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4916 size: self.data('easypiechart-size') || size,
4917 rotate: self.data('easypiechart-rotate') || 0,
4918 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4919 easing: self.data('easypiechart-easing') || undefined
4922 // when we just re-create the chart
4923 // do not animate the first update
4925 if(typeof state.easyPieChart_instance !== 'undefined')
4928 state.easyPieChart_instance = chart.data('easyPieChart');
4929 if(animate === false) state.easyPieChart_instance.disableAnimation();
4930 state.easyPieChart_instance.update(pcent);
4931 if(animate === false) state.easyPieChart_instance.enableAnimation();
4935 // ----------------------------------------------------------------------------------------------------------------
4938 NETDATA.gaugeInitialize = function(callback) {
4939 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4941 url: NETDATA.gauge_js,
4944 xhrFields: { withCredentials: true } // required for the cookie
4947 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4950 NETDATA.chartLibraries.gauge.enabled = false;
4951 NETDATA.error(100, NETDATA.gauge_js);
4953 .always(function() {
4954 if(typeof callback === "function")
4959 NETDATA.chartLibraries.gauge.enabled = false;
4960 if(typeof callback === "function")
4965 NETDATA.gaugeAnimation = function(state, status) {
4968 if(typeof status === 'boolean' && status === false)
4970 else if(typeof status === 'number')
4973 state.gauge_instance.animationSpeed = speed;
4974 state.___gaugeOld__.speed = speed;
4977 NETDATA.gaugeSet = function(state, value, min, max) {
4978 if(typeof value !== 'number') value = 0;
4979 if(typeof min !== 'number') min = 0;
4980 if(typeof max !== 'number') max = 0;
4981 if(value > max) max = value;
4982 if(value < min) min = value;
4991 // gauge.js has an issue if the needle
4992 // is smaller than min or larger than max
4993 // when we set the new values
4994 // the needle will go crazy
4996 // to prevent it, we always feed it
4997 // with a percentage, so that the needle
4998 // is always between min and max
4999 var pcent = (value - min) * 100 / (max - min);
5001 // these should never happen
5002 if(pcent < 0) pcent = 0;
5003 if(pcent > 100) pcent = 100;
5005 state.gauge_instance.set(pcent);
5007 state.___gaugeOld__.value = value;
5008 state.___gaugeOld__.min = min;
5009 state.___gaugeOld__.max = max;
5012 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5013 if(state.___gaugeOld__.valueLabel !== value) {
5014 state.___gaugeOld__.valueLabel = value;
5015 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5017 if(state.___gaugeOld__.minLabel !== min) {
5018 state.___gaugeOld__.minLabel = min;
5019 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5021 if(state.___gaugeOld__.maxLabel !== max) {
5022 state.___gaugeOld__.maxLabel = max;
5023 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5027 NETDATA.gaugeClearSelection = function(state) {
5028 if(typeof state.gaugeEvent !== 'undefined') {
5029 if(state.gaugeEvent.timer !== null)
5030 clearTimeout(state.gaugeEvent.timer);
5032 state.gaugeEvent.timer = null;
5035 if(state.isAutoRefreshable() === true && state.data !== null) {
5036 NETDATA.gaugeChartUpdate(state, state.data);
5039 NETDATA.gaugeAnimation(state, false);
5040 NETDATA.gaugeSet(state, null, null, null);
5041 NETDATA.gaugeSetLabels(state, null, null, null);
5044 NETDATA.gaugeAnimation(state, true);
5048 NETDATA.gaugeSetSelection = function(state, t) {
5049 if(state.timeIsVisible(t) !== true)
5050 return NETDATA.gaugeClearSelection(state);
5052 var slot = state.calculateRowForTime(t);
5053 if(slot < 0 || slot >= state.data.result.length)
5054 return NETDATA.gaugeClearSelection(state);
5056 if(typeof state.gaugeEvent === 'undefined') {
5057 state.gaugeEvent = {
5065 var value = state.data.result[state.data.result.length - 1 - slot];
5066 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5069 state.gaugeEvent.value = value;
5070 state.gaugeEvent.max = max;
5071 state.gaugeEvent.min = min;
5072 NETDATA.gaugeSetLabels(state, value, min, max);
5074 if(state.gaugeEvent.timer === null) {
5075 NETDATA.gaugeAnimation(state, false);
5077 state.gaugeEvent.timer = setTimeout(function() {
5078 state.gaugeEvent.timer = null;
5079 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5080 }, NETDATA.options.current.charts_selection_animation_delay);
5086 NETDATA.gaugeChartUpdate = function(state, data) {
5087 var value, min, max;
5089 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5093 NETDATA.gaugeSetLabels(state, null, null, null);
5096 value = data.result[0];
5098 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5099 if(value > max) max = value;
5100 NETDATA.gaugeSetLabels(state, value, min, max);
5103 NETDATA.gaugeSet(state, value, min, max);
5107 NETDATA.gaugeChartCreate = function(state, data) {
5108 var self = $(state.element);
5109 // var chart = $(state.element_chart);
5111 var value = data.result[0];
5112 var max = self.data('gauge-max-value') || null;
5113 var adjust = self.data('gauge-adjust') || null;
5114 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5115 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5116 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5117 var stopColor = self.data('gauge-stop-color') || void 0;
5118 var generateGradient = self.data('gauge-generate-gradient') || false;
5122 state.gaugeMax = null;
5125 state.gaugeMax = max;
5127 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5129 // case 'width': width = height * ratio; break;
5131 // default: height = width / ratio; break;
5133 //state.element.style.width = width.toString() + 'px';
5134 //state.element.style.height = height.toString() + 'px';
5139 lines: 12, // The number of lines to draw
5140 angle: 0.15, // The length of each line
5141 lineWidth: 0.44, // 0.44 The line thickness
5143 length: 0.8, // 0.9 The radius of the inner circle
5144 strokeWidth: 0.035, // The rotation offset
5145 color: pointerColor // Fill color
5147 colorStart: startColor, // Colors
5148 colorStop: stopColor, // just experiment with them
5149 strokeColor: strokeColor, // to see which ones work best for you
5151 generateGradient: (generateGradient === true)?true:false,
5155 if (generateGradient.constructor === Array) {
5157 // data-gauge-generate-gradient="[0, 50, 100]"
5158 // data-gauge-gradient-percent-color-0="#FFFFFF"
5159 // data-gauge-gradient-percent-color-50="#999900"
5160 // data-gauge-gradient-percent-color-100="#000000"
5162 options.percentColors = new Array();
5163 var len = generateGradient.length;
5165 var pcent = generateGradient[len];
5166 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5167 if(color !== false) {
5168 var a = new Array();
5171 options.percentColors.unshift(a);
5174 if(options.percentColors.length === 0)
5175 delete options.percentColors;
5177 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5178 options.percentColors = [
5179 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5180 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5181 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5182 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5183 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5184 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5185 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5186 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5187 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5188 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5189 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5192 state.gauge_canvas = document.createElement('canvas');
5193 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5194 state.gauge_canvas.className = 'gaugeChart';
5195 state.gauge_canvas.width = width;
5196 state.gauge_canvas.height = height;
5197 state.element_chart.appendChild(state.gauge_canvas);
5199 var valuefontsize = Math.floor(height / 6);
5200 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5201 state.gaugeChartLabel = document.createElement('span');
5202 state.gaugeChartLabel.className = 'gaugeChartLabel';
5203 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5204 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5205 state.element_chart.appendChild(state.gaugeChartLabel);
5207 var titlefontsize = Math.round(valuefontsize / 2);
5209 state.gaugeChartTitle = document.createElement('span');
5210 state.gaugeChartTitle.className = 'gaugeChartTitle';
5211 state.gaugeChartTitle.innerHTML = state.title;
5212 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5213 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5214 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5215 state.element_chart.appendChild(state.gaugeChartTitle);
5217 var unitfontsize = Math.round(titlefontsize * 0.9);
5218 state.gaugeChartUnits = document.createElement('span');
5219 state.gaugeChartUnits.className = 'gaugeChartUnits';
5220 state.gaugeChartUnits.innerHTML = state.units;
5221 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5222 state.element_chart.appendChild(state.gaugeChartUnits);
5224 state.gaugeChartMin = document.createElement('span');
5225 state.gaugeChartMin.className = 'gaugeChartMin';
5226 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5227 state.element_chart.appendChild(state.gaugeChartMin);
5229 state.gaugeChartMax = document.createElement('span');
5230 state.gaugeChartMax.className = 'gaugeChartMax';
5231 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5232 state.element_chart.appendChild(state.gaugeChartMax);
5234 // when we just re-create the chart
5235 // do not animate the first update
5237 if(typeof state.gauge_instance !== 'undefined')
5240 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5242 state.___gaugeOld__ = {
5251 // we will always feed a percentage
5252 state.gauge_instance.minValue = 0;
5253 state.gauge_instance.maxValue = 100;
5255 NETDATA.gaugeAnimation(state, animate);
5256 NETDATA.gaugeSet(state, value, 0, max);
5257 NETDATA.gaugeSetLabels(state, value, 0, max);
5258 NETDATA.gaugeAnimation(state, true);
5262 // ----------------------------------------------------------------------------------------------------------------
5263 // Charts Libraries Registration
5265 NETDATA.chartLibraries = {
5267 initialize: NETDATA.dygraphInitialize,
5268 create: NETDATA.dygraphChartCreate,
5269 update: NETDATA.dygraphChartUpdate,
5270 resize: function(state) {
5271 if(typeof state.dygraph_instance.resize === 'function')
5272 state.dygraph_instance.resize();
5274 setSelection: NETDATA.dygraphSetSelection,
5275 clearSelection: NETDATA.dygraphClearSelection,
5276 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5279 format: function(state) { return 'json'; },
5280 options: function(state) { return 'ms|flip'; },
5281 legend: function(state) {
5282 if(this.isSparkline(state) === false)
5283 return 'right-side';
5287 autoresize: function(state) { return true; },
5288 max_updates_to_recreate: function(state) { return 5000; },
5289 track_colors: function(state) { return true; },
5290 pixels_per_point: function(state) {
5291 if(this.isSparkline(state) === false)
5297 isSparkline: function(state) {
5298 if(typeof state.dygraph_sparkline === 'undefined') {
5299 var t = $(state.element).data('dygraph-theme');
5300 if(t === 'sparkline')
5301 state.dygraph_sparkline = true;
5303 state.dygraph_sparkline = false;
5305 return state.dygraph_sparkline;
5309 initialize: NETDATA.sparklineInitialize,
5310 create: NETDATA.sparklineChartCreate,
5311 update: NETDATA.sparklineChartUpdate,
5313 setSelection: undefined, // function(state, t) { return true; },
5314 clearSelection: undefined, // function(state) { return true; },
5315 toolboxPanAndZoom: null,
5318 format: function(state) { return 'array'; },
5319 options: function(state) { return 'flip|abs'; },
5320 legend: function(state) { return null; },
5321 autoresize: function(state) { return false; },
5322 max_updates_to_recreate: function(state) { return 5000; },
5323 track_colors: function(state) { return false; },
5324 pixels_per_point: function(state) { return 3; }
5327 initialize: NETDATA.peityInitialize,
5328 create: NETDATA.peityChartCreate,
5329 update: NETDATA.peityChartUpdate,
5331 setSelection: undefined, // function(state, t) { return true; },
5332 clearSelection: undefined, // function(state) { return true; },
5333 toolboxPanAndZoom: null,
5336 format: function(state) { return 'ssvcomma'; },
5337 options: function(state) { return 'null2zero|flip|abs'; },
5338 legend: function(state) { return null; },
5339 autoresize: function(state) { return false; },
5340 max_updates_to_recreate: function(state) { return 5000; },
5341 track_colors: function(state) { return false; },
5342 pixels_per_point: function(state) { return 3; }
5345 initialize: NETDATA.morrisInitialize,
5346 create: NETDATA.morrisChartCreate,
5347 update: NETDATA.morrisChartUpdate,
5349 setSelection: undefined, // function(state, t) { return true; },
5350 clearSelection: undefined, // function(state) { return true; },
5351 toolboxPanAndZoom: null,
5354 format: function(state) { return 'json'; },
5355 options: function(state) { return 'objectrows|ms'; },
5356 legend: function(state) { return null; },
5357 autoresize: function(state) { return false; },
5358 max_updates_to_recreate: function(state) { return 50; },
5359 track_colors: function(state) { return false; },
5360 pixels_per_point: function(state) { return 15; }
5363 initialize: NETDATA.googleInitialize,
5364 create: NETDATA.googleChartCreate,
5365 update: NETDATA.googleChartUpdate,
5367 setSelection: undefined, //function(state, t) { return true; },
5368 clearSelection: undefined, //function(state) { return true; },
5369 toolboxPanAndZoom: null,
5372 format: function(state) { return 'datatable'; },
5373 options: function(state) { return ''; },
5374 legend: function(state) { return null; },
5375 autoresize: function(state) { return false; },
5376 max_updates_to_recreate: function(state) { return 300; },
5377 track_colors: function(state) { return false; },
5378 pixels_per_point: function(state) { return 4; }
5381 initialize: NETDATA.raphaelInitialize,
5382 create: NETDATA.raphaelChartCreate,
5383 update: NETDATA.raphaelChartUpdate,
5385 setSelection: undefined, // function(state, t) { return true; },
5386 clearSelection: undefined, // function(state) { return true; },
5387 toolboxPanAndZoom: null,
5390 format: function(state) { return 'json'; },
5391 options: function(state) { return ''; },
5392 legend: function(state) { return null; },
5393 autoresize: function(state) { return false; },
5394 max_updates_to_recreate: function(state) { return 5000; },
5395 track_colors: function(state) { return false; },
5396 pixels_per_point: function(state) { return 3; }
5399 initialize: NETDATA.c3Initialize,
5400 create: NETDATA.c3ChartCreate,
5401 update: NETDATA.c3ChartUpdate,
5403 setSelection: undefined, // function(state, t) { return true; },
5404 clearSelection: undefined, // function(state) { return true; },
5405 toolboxPanAndZoom: null,
5408 format: function(state) { return 'csvjsonarray'; },
5409 options: function(state) { return 'milliseconds'; },
5410 legend: function(state) { return null; },
5411 autoresize: function(state) { return false; },
5412 max_updates_to_recreate: function(state) { return 5000; },
5413 track_colors: function(state) { return false; },
5414 pixels_per_point: function(state) { return 15; }
5417 initialize: NETDATA.d3Initialize,
5418 create: NETDATA.d3ChartCreate,
5419 update: NETDATA.d3ChartUpdate,
5421 setSelection: undefined, // function(state, t) { return true; },
5422 clearSelection: undefined, // function(state) { return true; },
5423 toolboxPanAndZoom: null,
5426 format: function(state) { return 'json'; },
5427 options: function(state) { return ''; },
5428 legend: function(state) { return null; },
5429 autoresize: function(state) { return false; },
5430 max_updates_to_recreate: function(state) { return 5000; },
5431 track_colors: function(state) { return false; },
5432 pixels_per_point: function(state) { return 3; }
5435 initialize: NETDATA.easypiechartInitialize,
5436 create: NETDATA.easypiechartChartCreate,
5437 update: NETDATA.easypiechartChartUpdate,
5439 setSelection: NETDATA.easypiechartSetSelection,
5440 clearSelection: NETDATA.easypiechartClearSelection,
5441 toolboxPanAndZoom: null,
5444 format: function(state) { return 'array'; },
5445 options: function(state) { return 'absolute'; },
5446 legend: function(state) { return null; },
5447 autoresize: function(state) { return false; },
5448 max_updates_to_recreate: function(state) { return 5000; },
5449 track_colors: function(state) { return true; },
5450 pixels_per_point: function(state) { return 3; },
5454 initialize: NETDATA.gaugeInitialize,
5455 create: NETDATA.gaugeChartCreate,
5456 update: NETDATA.gaugeChartUpdate,
5458 setSelection: NETDATA.gaugeSetSelection,
5459 clearSelection: NETDATA.gaugeClearSelection,
5460 toolboxPanAndZoom: null,
5463 format: function(state) { return 'array'; },
5464 options: function(state) { return 'absolute'; },
5465 legend: function(state) { return null; },
5466 autoresize: function(state) { return false; },
5467 max_updates_to_recreate: function(state) { return 5000; },
5468 track_colors: function(state) { return true; },
5469 pixels_per_point: function(state) { return 3; },
5474 NETDATA.registerChartLibrary = function(library, url) {
5475 if(NETDATA.options.debug.libraries === true)
5476 console.log("registering chart library: " + library);
5478 NETDATA.chartLibraries[library].url = url;
5479 NETDATA.chartLibraries[library].initialized = true;
5480 NETDATA.chartLibraries[library].enabled = true;
5483 // ----------------------------------------------------------------------------------------------------------------
5484 // Load required JS libraries and CSS
5486 NETDATA.requiredJs = [
5488 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5489 isAlreadyLoaded: function() {
5490 // check if bootstrap is loaded
5491 if(typeof $().emulateTransitionEnd == 'function')
5494 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5502 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5503 isAlreadyLoaded: function() { return false; }
5506 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5507 isAlreadyLoaded: function() { return false; }
5511 NETDATA.requiredCSS = [
5513 url: NETDATA.themes.current.bootstrap_css,
5514 isAlreadyLoaded: function() {
5515 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5522 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5523 isAlreadyLoaded: function() { return false; }
5526 url: NETDATA.themes.current.dashboard_css,
5527 isAlreadyLoaded: function() { return false; }
5530 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5531 isAlreadyLoaded: function() { return false; }
5535 NETDATA.loadRequiredJs = function(index, callback) {
5536 if(index >= NETDATA.requiredJs.length) {
5537 if(typeof callback === 'function')
5542 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5543 NETDATA.loadRequiredJs(++index, callback);
5547 if(NETDATA.options.debug.main_loop === true)
5548 console.log('loading ' + NETDATA.requiredJs[index].url);
5551 url: NETDATA.requiredJs[index].url,
5554 xhrFields: { withCredentials: true } // required for the cookie
5556 .success(function() {
5557 if(NETDATA.options.debug.main_loop === true)
5558 console.log('loaded ' + NETDATA.requiredJs[index].url);
5560 NETDATA.loadRequiredJs(++index, callback);
5563 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5567 NETDATA.loadRequiredCSS = function(index) {
5568 if(index >= NETDATA.requiredCSS.length)
5571 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5572 NETDATA.loadRequiredCSS(++index);
5576 if(NETDATA.options.debug.main_loop === true)
5577 console.log('loading ' + NETDATA.requiredCSS[index].url);
5579 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5580 NETDATA.loadRequiredCSS(++index);
5584 // ----------------------------------------------------------------------------------------------------------------
5585 // Registry of netdata hosts
5588 notifications: false, // when true, the browser supports notifications (may not be granted though)
5589 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5590 // notifications_shown: new Array(),
5592 server: null, // the server to connect to for fetching alarms
5593 current: null, // the list of raised alarms - updated in the background
5594 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5596 notify: function(entry) {
5597 // console.log('alarm ' + entry.unique_id);
5599 if(entry.updated === true) {
5600 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5604 var name = entry.name.replace(/_/g, ' ');
5605 var status = entry.status.toLowerCase();
5606 var title = name + ' = ' + ((entry.value === null)?'NaN':Math.floor(entry.value)).toString() + ' ' + entry.units;
5607 var tag = entry.alarm_id;
5608 var icon = 'images/seo-performance-128.png';
5609 var interaction = false;
5613 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5615 switch(entry.status) {
5623 case 'UNINITIALIZED':
5627 if(NETDATA.alarms.last_notification_id === 0) {
5628 // console.log('alarm ' + entry.unique_id + ' is not current');
5631 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5632 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5635 title = name + ' back to normal';
5636 icon = 'images/check-mark-2-128-green.png'
5637 interaction = false;
5641 if(entry.old_status === 'CRITICAL')
5642 status = 'demoted to ' + entry.status.toLowerCase();
5644 icon = 'images/alert-128-orange.png';
5645 interaction = false;
5649 if(entry.old_status === 'WARNING')
5650 status = 'escalated to ' + entry.status.toLowerCase();
5652 icon = 'images/alert-128-red.png'
5657 console.log('invalid alarm status ' + entry.status);
5662 // cleanup old notifications with the same alarm_id as this one
5663 // FIXME: it does not seem to work on any web browser!
5664 var len = NETDATA.alarms.notifications_shown.length;
5666 var n = NETDATA.alarms.notifications_shown[len];
5667 if(n.data.alarm_id === entry.alarm_id) {
5668 console.log('removing old alarm ' + n.data.unique_id);
5670 // close the notification
5673 // remove it from the array
5674 NETDATA.alarms.notifications_shown.splice(len, 1);
5675 len = NETDATA.alarms.notifications_shown.length;
5681 // show this notification
5682 // console.log('new notification: ' + title);
5683 var n = new Notification(title, {
5684 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5686 requireInteraction: interaction,
5687 icon: NETDATA.serverDefault + icon,
5689 // FIXME: does not seem to work on any web browser
5690 // onclick: function(event) {
5691 // event.preventDefault();
5692 // console.log('clicked: ');
5693 // console.log(event);
5698 // NETDATA.alarms.notifications_shown.push(n);
5699 // console.log(entry);
5703 notifyAll: function() {
5704 // console.log('FETCHING ALARM LOG');
5705 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5706 // console.log('ALARM LOG FETCHED');
5708 if(data === null || typeof data !== 'object') {
5709 console.log('invalid alarms log response');
5713 if(data.length === 0) {
5714 console.log('received empty alarm log');
5718 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5720 data.sort(function(a, b) {
5721 if(a.unique_id > b.unique_id) return -1;
5722 if(a.unique_id < b.unique_id) return 1;
5726 var len = data.length;
5728 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5729 NETDATA.alarms.notify(data[len]);
5732 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5735 NETDATA.alarms.last_notification_id = data[0].unique_id;
5736 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5740 check_notifications: function() {
5741 // returns true if we should fire 1+ notifications
5743 if(NETDATA.alarms.notifications !== true) {
5744 // console.log('notifications not available');
5748 if(Notification.permission !== 'granted') {
5749 // console.log('notifications not granted');
5753 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5754 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5756 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5757 // console.log('new alarms detected');
5760 //else console.log('no new alarms');
5762 // else console.log('cannot process alarms');
5767 get: function(what, callback) {
5769 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5772 xhrFields: { withCredentials: true } // required for the cookie
5774 .done(function(data) {
5775 if(typeof callback === 'function')
5779 NETDATA.error(415, NETDATA.alarms.server);
5781 if(typeof callback === 'function')
5786 update_forever: function() {
5787 NETDATA.alarms.get('active', function(data) {
5789 NETDATA.alarms.current = data;
5791 if(NETDATA.alarms.check_notifications() === true) {
5792 NETDATA.alarms.notifyAll();
5795 if (typeof NETDATA.alarms.callback === 'function') {
5796 NETDATA.alarms.callback(data);
5799 // Health monitoring is disabled on this netdata
5800 if(data.status === false) return;
5803 setTimeout(NETDATA.alarms.update_forever, 10000);
5807 get_log: function(last_id, callback) {
5808 // console.log('fetching all log after ' + last_id.toString());
5810 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5813 xhrFields: { withCredentials: true } // required for the cookie
5815 .done(function(data) {
5816 if(typeof callback === 'function')
5820 NETDATA.error(416, NETDATA.alarms.server);
5822 if(typeof callback === 'function')
5828 var host = NETDATA.serverDefault;
5829 while(host.slice(-1) === '/')
5830 host = host.substring(0, host.length - 1);
5831 NETDATA.alarms.server = host;
5833 if(netdataShowAlarms === true) {
5834 NETDATA.alarms.update_forever();
5836 if('Notification' in window) {
5837 // console.log('notifications available');
5838 NETDATA.alarms.notifications = true;
5840 if(Notification.permission === 'default')
5841 Notification.requestPermission();
5847 // ----------------------------------------------------------------------------------------------------------------
5848 // Registry of netdata hosts
5850 NETDATA.registry = {
5851 server: null, // the netdata registry server
5852 person_guid: null, // the unique ID of this browser / user
5853 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5854 hostname: null, // the hostname of the netdata server that served dashboard.js
5855 machines: null, // the user's other URLs
5856 machines_array: null, // the user's other URLs in an array
5859 parsePersonUrls: function(person_urls) {
5860 // console.log(person_urls);
5861 NETDATA.registry.person_urls = person_urls;
5864 NETDATA.registry.machines = {};
5865 NETDATA.registry.machines_array = new Array();
5867 var now = new Date().getTime();
5868 var apu = person_urls;
5871 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5872 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5878 accesses: apu[i][3],
5880 alternate_urls: new Array()
5882 obj.alternate_urls.push(apu[i][1]);
5884 NETDATA.registry.machines[apu[i][0]] = obj;
5885 NETDATA.registry.machines_array.push(obj);
5888 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5890 var pu = NETDATA.registry.machines[apu[i][0]];
5891 if(pu.last_t < apu[i][2]) {
5893 pu.last_t = apu[i][2];
5894 pu.name = apu[i][4];
5896 pu.accesses += apu[i][3];
5897 pu.alternate_urls.push(apu[i][1]);
5902 if(typeof netdataRegistryCallback === 'function')
5903 netdataRegistryCallback(NETDATA.registry.machines_array);
5907 if(netdataRegistry !== true) return;
5909 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5911 NETDATA.registry.server = data.registry;
5912 NETDATA.registry.machine_guid = data.machine_guid;
5913 NETDATA.registry.hostname = data.hostname;
5915 NETDATA.registry.access(2, function (person_urls) {
5916 NETDATA.registry.parsePersonUrls(person_urls);
5923 hello: function(host, callback) {
5924 while(host.slice(-1) === '/')
5925 host = host.substring(0, host.length - 1);
5927 // send HELLO to a netdata server:
5928 // 1. verifies the server is reachable
5929 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5931 url: host + '/api/v1/registry?action=hello',
5934 xhrFields: { withCredentials: true } // required for the cookie
5936 .done(function(data) {
5937 if(typeof data.status !== 'string' || data.status !== 'ok') {
5938 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5942 if(typeof callback === 'function')
5946 NETDATA.error(407, host);
5948 if(typeof callback === 'function')
5953 access: function(max_redirects, callback) {
5954 // send ACCESS to a netdata registry:
5955 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5956 // 2. it responds with a list of netdata servers we know
5957 // the registry identifies us using a cookie it sets the first time we access it
5958 // the registry may respond with a redirect URL to send us to another registry
5960 url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
5963 xhrFields: { withCredentials: true } // required for the cookie
5965 .done(function(data) {
5966 var redirect = null;
5967 if(typeof data.registry === 'string')
5968 redirect = data.registry;
5970 if(typeof data.status !== 'string' || data.status !== 'ok') {
5971 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5976 if(redirect !== null && max_redirects > 0) {
5977 NETDATA.registry.server = redirect;
5978 NETDATA.registry.access(max_redirects - 1, callback);
5981 if(typeof callback === 'function')
5986 if(typeof data.person_guid === 'string')
5987 NETDATA.registry.person_guid = data.person_guid;
5989 if(typeof callback === 'function')
5990 callback(data.urls);
5994 NETDATA.error(410, NETDATA.registry.server);
5996 if(typeof callback === 'function')
6001 delete: function(delete_url, callback) {
6002 // send DELETE to a netdata registry:
6004 url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
6007 xhrFields: { withCredentials: true } // required for the cookie
6009 .done(function(data) {
6010 if(typeof data.status !== 'string' || data.status !== 'ok') {
6011 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6015 if(typeof callback === 'function')
6019 NETDATA.error(412, NETDATA.registry.server);
6021 if(typeof callback === 'function')
6026 switch: function(new_person_guid, callback) {
6029 url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
6032 xhrFields: { withCredentials: true } // required for the cookie
6034 .done(function(data) {
6035 if(typeof data.status !== 'string' || data.status !== 'ok') {
6036 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6040 if(typeof callback === 'function')
6044 NETDATA.error(414, NETDATA.registry.server);
6046 if(typeof callback === 'function')
6052 // ----------------------------------------------------------------------------------------------------------------
6055 NETDATA.errorReset();
6056 NETDATA.loadRequiredCSS(0);
6058 NETDATA._loadjQuery(function() {
6059 NETDATA.loadRequiredJs(0, function() {
6060 if(typeof $().emulateTransitionEnd !== 'function') {
6061 // bootstrap is not available
6062 NETDATA.options.current.show_help = false;
6065 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6066 if(NETDATA.options.debug.main_loop === true)
6067 console.log('starting chart refresh thread');
6074 // window.NETDATA = NETDATA;
6075 // })(window, document);