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 netdataNoRegistry = true; // Don't update the registry for this access
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = true; // enable/disable help
20 // You can also set the default netdata server, using the following.
21 // When this variable is not set, we assume the page is hosted on your
22 // netdata server already.
23 // var netdataServer = "http://yourhost:19999"; // set your NetData server
25 //(function(window, document, undefined) {
27 // ------------------------------------------------------------------------
28 // compatibility fixes
30 // fix IE issue with console
31 if(!window.console) { window.console = { log: function(){} }; }
33 // if string.endsWith is not defined, define it
34 if(typeof String.prototype.endsWith !== 'function') {
35 String.prototype.endsWith = function(s) {
36 if(s.length > this.length) return false;
37 return this.slice(-s.length) === s;
41 // if string.startsWith is not defined, define it
42 if(typeof String.prototype.startsWith !== 'function') {
43 String.prototype.startsWith = function(s) {
44 if(s.length > this.length) return false;
45 return this.slice(s.length) === s;
50 var NETDATA = window.NETDATA || {};
52 // ----------------------------------------------------------------------------------------------------------------
53 // Detect the netdata server
55 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
56 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
57 NETDATA._scriptSource = function() {
60 if(typeof document.currentScript !== 'undefined') {
61 script = document.currentScript;
64 var all_scripts = document.getElementsByTagName('script');
65 script = all_scripts[all_scripts.length - 1];
68 if (typeof script.getAttribute.length !== 'undefined')
71 script = script.getAttribute('src', -1);
76 if(typeof netdataServer !== 'undefined')
77 NETDATA.serverDefault = netdataServer;
79 var s = NETDATA._scriptSource();
80 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
82 console.log('WARNING: Cannot detect the URL of the netdata server.');
83 NETDATA.serverDefault = null;
87 if(NETDATA.serverDefault === null)
88 NETDATA.serverDefault = '';
89 else if(NETDATA.serverDefault.slice(-1) !== '/')
90 NETDATA.serverDefault += '/';
92 // default URLs for all the external files we need
93 // make them RELATIVE so that the whole thing can also be
94 // installed under a web server
95 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
96 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
97 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
98 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
99 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
100 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
101 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
102 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
103 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
104 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
105 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
106 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
107 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
108 NETDATA.google_js = 'https://www.google.com/jsapi';
112 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
113 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
114 background: '#FFFFFF',
115 foreground: '#000000',
118 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
119 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
120 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
121 '#329262', '#3B3EAC' ],
122 easypiechart_track: '#f0f0f0',
123 easypiechart_scale: '#dfe0e0',
124 gauge_pointer: '#C0C0C0',
125 gauge_stroke: '#F0F0F0',
126 gauge_gradient: false
129 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
130 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
131 background: '#272b30',
132 foreground: '#C8C8C8',
135 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
136 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
137 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
138 '#a6a479', '#a66da8' ],
140 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
141 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
142 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
143 '#329262', '#3B3EFF' ],
144 easypiechart_track: '#373b40',
145 easypiechart_scale: '#373b40',
146 gauge_pointer: '#474b50',
147 gauge_stroke: '#373b40',
148 gauge_gradient: false
152 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
153 NETDATA.themes.current = NETDATA.themes[netdataTheme];
155 NETDATA.themes.current = NETDATA.themes.white;
157 if(typeof netdataShowHelp === 'undefined')
158 netdataShowHelp = true;
160 NETDATA.colors = NETDATA.themes.current.colors;
162 // these are the colors Google Charts are using
163 // we have them here to attempt emulate their look and feel on the other chart libraries
164 // http://there4.io/2012/05/02/google-chart-color-list/
165 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
166 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
167 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
169 // an alternative set
170 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
171 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
172 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
174 // ----------------------------------------------------------------------------------------------------------------
175 // the defaults for all charts
177 // if the user does not specify any of these, the following will be used
179 NETDATA.chartDefaults = {
180 host: NETDATA.serverDefault, // the server to get data from
181 width: '100%', // the chart width - can be null
182 height: '100%', // the chart height - can be null
183 min_width: null, // the chart minimum width - can be null
184 library: 'dygraph', // the graphing library to use
185 method: 'average', // the grouping method
186 before: 0, // panning
187 after: -600, // panning
188 pixels_per_point: 1, // the detail of the chart
189 fill_luminance: 0.8 // luminance of colors in solit areas
192 // ----------------------------------------------------------------------------------------------------------------
196 pauseCallback: null, // a callback when we are really paused
198 pause: false, // when enabled we don't auto-refresh the charts
200 targets: null, // an array of all the state objects that are
201 // currently active (independently of their
202 // viewport visibility)
204 updated_dom: true, // when true, the DOM has been updated with
205 // new elements we have to check.
207 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
208 // rendering charts continiously.
209 // used with .current.fast_render_timeframe
211 page_is_visible: true, // when true, this page is visible
213 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
214 // auto-refresher for some time (when a chart is
215 // performing pan or zoom, we need to stop refreshing
216 // all other charts, to have the maximum speed for
217 // rendering the chart that is panned or zoomed).
218 // Used with .current.global_pan_sync_time
220 last_resized: new Date().getTime(), // the timestamp of the last resize request
222 last_page_scroll: 0, // the timestamp the last time the page was scrolled
224 // the current profile
225 // we may have many...
227 pixels_per_point: 1, // the minimum pixels per point for all charts
228 // increase this to speed javascript up
229 // each chart library has its own limit too
230 // the max of this and the chart library is used
231 // the final is calculated every time, so a change
232 // here will have immediate effect on the next chart
235 idle_between_charts: 100, // ms - how much time to wait between chart updates
237 fast_render_timeframe: 200, // ms - render continously until this time of continious
238 // rendering has been reached
239 // this setting is used to make it render e.g. 10
240 // charts at once, sleep idle_between_charts time
241 // and continue for another 10 charts.
243 idle_between_loops: 500, // ms - if all charts have been updated, wait this
244 // time before starting again.
246 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
248 idle_lost_focus: 500, // ms - when the window does not have focus, check
249 // if focus has been regained, every this time
251 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
252 // autorefreshing of charts is paused for this amount
255 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
256 // of time before setting up synchronized selections
259 sync_selection: true, // enable or disable selection sync
261 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
263 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
265 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
267 update_only_visible: true, // enable or disable visibility management
269 parallel_refresher: true, // enable parallel refresh of charts
271 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
273 destroy_on_hide: false, // destroy charts when they are not visible
275 show_help: netdataShowHelp, // when enabled the charts will show some help
276 show_help_delay_show_ms: 500,
277 show_help_delay_hide_ms: 0,
279 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
281 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
282 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
284 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
286 smooth_plot: true, // enable smooth plot, where possible
288 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
290 color_fill_opacity_line: 1.0,
291 color_fill_opacity_area: 0.2,
292 color_fill_opacity_stacked: 0.8,
294 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
295 pan_and_zoom_factor_multiplier_control: 2.0,
296 pan_and_zoom_factor_multiplier_shift: 3.0,
297 pan_and_zoom_factor_multiplier_alt: 4.0,
299 setOptionCallback: function() { ; }
307 chart_data_url: false,
308 chart_errors: false, // FIXME
316 NETDATA.statistics = {
319 refreshes_active_max: 0
323 // ----------------------------------------------------------------------------------------------------------------
324 // local storage options
326 NETDATA.localStorage = {
329 callback: {} // only used for resetting back to defaults
332 NETDATA.localStorageGet = function(key, def, callback) {
335 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
336 NETDATA.localStorage.default[key.toString()] = def;
337 NETDATA.localStorage.callback[key.toString()] = callback;
340 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
342 // console.log('localStorage: loading "' + key.toString() + '"');
343 ret = localStorage.getItem(key.toString());
344 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
345 if(ret === null || ret === 'undefined') {
346 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
347 localStorage.setItem(key.toString(), JSON.stringify(def));
351 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
352 ret = JSON.parse(ret);
353 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
357 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
362 if(typeof ret === 'undefined' || ret === 'undefined') {
363 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
367 NETDATA.localStorage.current[key.toString()] = ret;
371 NETDATA.localStorageSet = function(key, value, callback) {
372 if(typeof value === 'undefined' || value === 'undefined') {
373 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
376 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
377 NETDATA.localStorage.default[key.toString()] = value;
378 NETDATA.localStorage.current[key.toString()] = value;
379 NETDATA.localStorage.callback[key.toString()] = callback;
382 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
383 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
385 localStorage.setItem(key.toString(), JSON.stringify(value));
388 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
392 NETDATA.localStorage.current[key.toString()] = value;
396 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
398 if(typeof obj[i] === 'object') {
399 //console.log('object ' + prefix + '.' + i.toString());
400 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
404 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
408 NETDATA.setOption = function(key, value) {
409 if(key.toString() === 'setOptionCallback') {
410 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
411 NETDATA.options.current[key.toString()] = value;
412 NETDATA.options.current.setOptionCallback();
415 else if(NETDATA.options.current[key.toString()] !== value) {
416 var name = 'options.' + key.toString();
418 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
419 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
421 //console.log(NETDATA.localStorage);
422 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
423 //console.log(NETDATA.options);
424 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
426 if(typeof NETDATA.options.current.setOptionCallback === 'function')
427 NETDATA.options.current.setOptionCallback();
433 NETDATA.getOption = function(key) {
434 return NETDATA.options.current[key.toString()];
437 // read settings from local storage
438 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
440 // always start with this option enabled.
441 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
443 NETDATA.resetOptions = function() {
444 for(var i in NETDATA.localStorage.default) {
445 var a = i.split('.');
447 if(a[0] === 'options') {
448 if(a[1] === 'setOptionCallback') continue;
449 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
450 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
452 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
454 else if(a[0] === 'chart_heights') {
455 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
456 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
462 // ----------------------------------------------------------------------------------------------------------------
464 if(NETDATA.options.debug.main_loop === true)
465 console.log('welcome to NETDATA');
467 NETDATA.onresize = function() {
468 NETDATA.options.last_resized = new Date().getTime();
472 NETDATA.onscroll = function() {
473 // console.log('onscroll');
475 NETDATA.options.last_page_scroll = new Date().getTime();
476 if(NETDATA.options.targets === null) return;
478 // when the user scrolls he sees that we have
479 // hidden all the not-visible charts
480 // using this little function we try to switch
481 // the charts back to visible quickly
482 var targets = NETDATA.options.targets;
483 var len = targets.length;
484 while(len--) targets[len].isVisible();
487 window.onresize = NETDATA.onresize;
488 window.onscroll = NETDATA.onscroll;
490 // ----------------------------------------------------------------------------------------------------------------
493 NETDATA.errorCodes = {
494 100: { message: "Cannot load chart library", alert: true },
495 101: { message: "Cannot load jQuery", alert: true },
496 402: { message: "Chart library not found", alert: false },
497 403: { message: "Chart library not enabled/is failed", alert: false },
498 404: { message: "Chart not found", alert: false },
499 405: { message: "Cannot download charts index from server", alert: true },
500 406: { message: "Invalid charts index downloaded from server", alert: true },
501 407: { message: "Cannot HELLO netdata server", alert: false },
502 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
503 409: { message: "Cannot ACCESS netdata registry", alert: false },
504 410: { message: "Netdata registry ACCESS failed", alert: false },
505 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
506 412: { message: "Netdata registry DELETE failed", alert: false },
507 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
508 414: { message: "Netdata registry SWITCH failed", alert: false }
510 NETDATA.errorLast = {
516 NETDATA.error = function(code, msg) {
517 NETDATA.errorLast.code = code;
518 NETDATA.errorLast.message = msg;
519 NETDATA.errorLast.datetime = new Date().getTime();
521 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
524 if(typeof netdataErrorCallback === 'function') {
525 ret = netdataErrorCallback('system', code, msg);
528 if(ret && NETDATA.errorCodes[code].alert)
529 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
532 NETDATA.errorReset = function() {
533 NETDATA.errorLast.code = 0;
534 NETDATA.errorLast.message = "You are doing fine!";
535 NETDATA.errorLast.datetime = 0;
538 // ----------------------------------------------------------------------------------------------------------------
541 // When multiple charts need the same chart, we avoid downloading it
542 // multiple times (and having it in browser memory multiple time)
543 // by using this registry.
545 // Every time we download a chart definition, we save it here with .add()
546 // Then we try to get it back with .get(). If that fails, we download it.
548 NETDATA.chartRegistry = {
551 fixid: function(id) {
552 return id.replace(/:/g, "_").replace(/\//g, "_");
555 add: function(host, id, data) {
556 host = this.fixid(host);
559 if(typeof this.charts[host] === 'undefined')
560 this.charts[host] = {};
562 //console.log('added ' + host + '/' + id);
563 this.charts[host][id] = data;
566 get: function(host, id) {
567 host = this.fixid(host);
570 if(typeof this.charts[host] === 'undefined')
573 if(typeof this.charts[host][id] === 'undefined')
576 //console.log('cached ' + host + '/' + id);
577 return this.charts[host][id];
580 downloadAll: function(host, callback) {
581 while(host.slice(-1) === '/')
582 host = host.substring(0, host.length - 1);
587 url: host + '/api/v1/charts',
590 xhrFields: { withCredentials: true } // required for the cookie
592 .done(function(data) {
594 var h = NETDATA.chartRegistry.fixid(host);
595 self.charts[h] = data.charts;
597 else NETDATA.error(406, host + '/api/v1/charts');
599 if(typeof callback === 'function')
603 NETDATA.error(405, host + '/api/v1/charts');
605 if(typeof callback === 'function')
611 // ----------------------------------------------------------------------------------------------------------------
612 // Global Pan and Zoom on charts
614 // Using this structure are synchronize all the charts, so that
615 // when you pan or zoom one, all others are automatically refreshed
616 // to the same timespan.
618 NETDATA.globalPanAndZoom = {
619 seq: 0, // timestamp ms
620 // every time a chart is panned or zoomed
621 // we set the timestamp here
622 // then we use it as a sequence number
623 // to find if other charts are syncronized
626 master: null, // the master chart (state), to which all others
629 force_before_ms: null, // the timespan to sync all other charts
630 force_after_ms: null,
635 setMaster: function(state, after, before) {
636 if(NETDATA.options.current.sync_pan_and_zoom === false)
639 if(this.master !== null && this.master !== state)
640 this.master.resetChart(true, true);
642 var now = new Date().getTime();
645 this.force_after_ms = after;
646 this.force_before_ms = before;
647 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
649 if(typeof this.callback === 'function')
650 this.callback(true, after, before);
654 clearMaster: function() {
655 if(this.master !== null) {
656 var st = this.master;
663 this.force_after_ms = null;
664 this.force_before_ms = null;
665 NETDATA.options.auto_refresher_stop_until = 0;
667 if(typeof this.callback === 'function')
668 this.callback(false, 0, 0);
671 // is the given state the master of the global
672 // pan and zoom sync?
673 isMaster: function(state) {
674 if(this.master === state) return true;
678 // are we currently have a global pan and zoom sync?
679 isActive: function() {
680 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
684 // check if a chart, other than the master
685 // needs to be refreshed, due to the global pan and zoom
686 shouldBeAutoRefreshed: function(state) {
687 if(this.master === null || this.seq === 0)
690 //if(state.needsRecreation())
693 if(state.tm.pan_and_zoom_seq === this.seq)
700 // ----------------------------------------------------------------------------------------------------------------
701 // dimensions selection
704 // move color assignment to dimensions, here
706 dimensionStatus = function(parent, label, name_div, value_div, color) {
707 this.enabled = false;
708 this.parent = parent;
710 this.name_div = null;
711 this.value_div = null;
712 this.color = NETDATA.themes.current.foreground;
714 if(parent.selected_count > parent.unselected_count)
715 this.selected = true;
717 this.selected = false;
719 this.setOptions(name_div, value_div, color);
722 dimensionStatus.prototype.invalidate = function() {
723 this.name_div = null;
724 this.value_div = null;
725 this.enabled = false;
728 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
731 if(this.name_div != name_div) {
732 this.name_div = name_div;
733 this.name_div.title = this.label;
734 this.name_div.style.color = this.color;
735 if(this.selected === false)
736 this.name_div.className = 'netdata-legend-name not-selected';
738 this.name_div.className = 'netdata-legend-name selected';
741 if(this.value_div != value_div) {
742 this.value_div = value_div;
743 this.value_div.title = this.label;
744 this.value_div.style.color = this.color;
745 if(this.selected === false)
746 this.value_div.className = 'netdata-legend-value not-selected';
748 this.value_div.className = 'netdata-legend-value selected';
755 dimensionStatus.prototype.setHandler = function() {
756 if(this.enabled === false) return;
760 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
761 this.name_div.onclick = this.value_div.onclick = function(e) {
763 if(ds.isSelected()) {
765 if(e.shiftKey === true || e.ctrlKey === true) {
766 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
769 if(ds.parent.countSelected() === 0)
770 ds.parent.selectAll();
773 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
774 if(ds.parent.countSelected() === 1) {
775 ds.parent.selectAll();
778 ds.parent.selectNone();
784 // this is not selected
785 if(e.shiftKey === true || e.ctrlKey === true) {
786 // control or shift key is pressed -> select this too
790 // no key is pressed -> select only this
791 ds.parent.selectNone();
796 ds.parent.state.redrawChart();
800 dimensionStatus.prototype.select = function() {
801 if(this.enabled === false) return;
803 this.name_div.className = 'netdata-legend-name selected';
804 this.value_div.className = 'netdata-legend-value selected';
805 this.selected = true;
808 dimensionStatus.prototype.unselect = function() {
809 if(this.enabled === false) return;
811 this.name_div.className = 'netdata-legend-name not-selected';
812 this.value_div.className = 'netdata-legend-value hidden';
813 this.selected = false;
816 dimensionStatus.prototype.isSelected = function() {
817 return(this.enabled === true && this.selected === true);
820 // ----------------------------------------------------------------------------------------------------------------
822 dimensionsVisibility = function(state) {
825 this.dimensions = {};
826 this.selected_count = 0;
827 this.unselected_count = 0;
830 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
831 if(typeof this.dimensions[label] === 'undefined') {
833 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
836 this.dimensions[label].setOptions(name_div, value_div, color);
838 return this.dimensions[label];
841 dimensionsVisibility.prototype.dimensionGet = function(label) {
842 return this.dimensions[label];
845 dimensionsVisibility.prototype.invalidateAll = function() {
846 for(var d in this.dimensions)
847 this.dimensions[d].invalidate();
850 dimensionsVisibility.prototype.selectAll = function() {
851 for(var d in this.dimensions)
852 this.dimensions[d].select();
855 dimensionsVisibility.prototype.countSelected = function() {
857 for(var d in this.dimensions)
858 if(this.dimensions[d].isSelected()) i++;
863 dimensionsVisibility.prototype.selectNone = function() {
864 for(var d in this.dimensions)
865 this.dimensions[d].unselect();
868 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
869 var ret = new Array();
870 this.selected_count = 0;
871 this.unselected_count = 0;
873 for(var i = 0, len = array.length; i < len ; i++) {
874 var ds = this.dimensions[array[i]];
875 if(typeof ds === 'undefined') {
876 // console.log(array[i] + ' is not found');
881 if(ds.isSelected()) {
883 this.selected_count++;
887 this.unselected_count++;
891 if(this.selected_count === 0 && this.unselected_count !== 0) {
893 return this.selected2BooleanArray(array);
900 // ----------------------------------------------------------------------------------------------------------------
901 // global selection sync
903 NETDATA.globalSelectionSync = {
910 if(this.state !== null)
911 this.state.globalSelectionSyncStop();
915 if(this.state !== null) {
916 this.state.globalSelectionSyncDelay();
921 // ----------------------------------------------------------------------------------------------------------------
922 // Our state object, where all per-chart values are stored
924 chartState = function(element) {
925 var self = $(element);
926 this.element = element;
929 // all private functions should use 'that', instead of 'this'
933 * show an error instead of the chart
935 var error = function(msg) {
938 if(typeof netdataErrorCallback === 'function') {
939 ret = netdataErrorCallback('chart', that.id, msg);
943 that.element.innerHTML = that.id + ': ' + msg;
944 that.enabled = false;
945 that.current = that.pan;
949 // GUID - a unique identifier for the chart
950 this.uuid = NETDATA.guid();
952 // string - the name of chart
953 this.id = self.data('netdata');
955 // string - the key for localStorage settings
956 this.settings_id = self.data('id') || null;
958 // the user given dimensions of the element
959 this.width = self.data('width') || NETDATA.chartDefaults.width;
960 this.height = self.data('height') || NETDATA.chartDefaults.height;
962 if(this.settings_id !== null) {
963 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
964 // this is the callback that will be called
965 // if and when the user resets all localStorage variables
968 resizeChartToHeight(height);
972 // string - the netdata server URL, without any path
973 this.host = self.data('host') || NETDATA.chartDefaults.host;
975 // make sure the host does not end with /
976 // all netdata API requests use absolute paths
977 while(this.host.slice(-1) === '/')
978 this.host = this.host.substring(0, this.host.length - 1);
980 // string - the grouping method requested by the user
981 this.method = self.data('method') || NETDATA.chartDefaults.method;
983 // the time-range requested by the user
984 this.after = self.data('after') || NETDATA.chartDefaults.after;
985 this.before = self.data('before') || NETDATA.chartDefaults.before;
987 // the pixels per point requested by the user
988 this.pixels_per_point = self.data('pixels-per-point') || 1;
989 this.points = self.data('points') || null;
991 // the dimensions requested by the user
992 this.dimensions = self.data('dimensions') || null;
994 // the chart library requested by the user
995 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
997 // object - the chart library used
1002 this.colors_assigned = {};
1003 this.colors_available = null;
1005 // the element already created by the user
1006 this.element_message = null;
1008 // the element with the chart
1009 this.element_chart = null;
1011 // the element with the legend of the chart (if created by us)
1012 this.element_legend = null;
1013 this.element_legend_childs = {
1023 this.chart_url = null; // string - the url to download chart info
1024 this.chart = null; // object - the chart as downloaded from the server
1026 this.title = self.data('title') || null; // the title of the chart
1027 this.units = self.data('units') || null; // the units of the chart dimensions
1028 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1030 this.running = false; // boolean - true when the chart is being refreshed now
1031 this.validated = false; // boolean - has the chart been validated?
1032 this.enabled = true; // boolean - is the chart enabled for refresh?
1033 this.paused = false; // boolean - is the chart paused for any reason?
1034 this.selected = false; // boolean - is the chart shown a selection?
1035 this.debug = false; // boolean - console.log() debug info about this chart
1037 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1038 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1039 this.requested_after = null; // milliseconds - the timestamp of the request after param
1040 this.requested_before = null; // milliseconds - the timestamp of the request before param
1041 this.requested_padding = null;
1042 this.view_after = 0;
1043 this.view_before = 0;
1048 force_update_at: 0, // the timestamp to force the update at
1049 force_before_ms: null,
1050 force_after_ms: null
1055 force_update_at: 0, // the timestamp to force the update at
1056 force_before_ms: null,
1057 force_after_ms: null
1062 force_update_at: 0, // the timestamp to force the update at
1063 force_before_ms: null,
1064 force_after_ms: null
1067 // this is a pointer to one of the sub-classes below
1069 this.current = this.auto;
1071 // check the requested library is available
1072 // we don't initialize it here - it will be initialized when
1073 // this chart will be first used
1074 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1075 NETDATA.error(402, that.library_name);
1076 error('chart library "' + that.library_name + '" is not found');
1079 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1080 NETDATA.error(403, that.library_name);
1081 error('chart library "' + that.library_name + '" is not enabled');
1085 that.library = NETDATA.chartLibraries[that.library_name];
1087 // milliseconds - the time the last refresh took
1088 this.refresh_dt_ms = 0;
1090 // if we need to report the rendering speed
1091 // find the element that needs to be updated
1092 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1094 if(refresh_dt_element_name !== null)
1095 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1097 this.refresh_dt_element = null;
1099 this.dimensions_visibility = new dimensionsVisibility(this);
1101 this._updating = false;
1103 // ============================================================================================================
1104 // PRIVATE FUNCTIONS
1106 var createDOM = function() {
1107 if(that.enabled === false) return;
1109 if(that.element_message !== null) that.element_message.innerHTML = '';
1110 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1111 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1113 that.element.innerHTML = '';
1115 that.element_message = document.createElement('div');
1116 that.element_message.className = ' netdata-message hidden';
1117 that.element.appendChild(that.element_message);
1119 that.element_chart = document.createElement('div');
1120 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1121 that.element.appendChild(that.element_chart);
1123 if(that.hasLegend() === true) {
1124 that.element.className = "netdata-container-with-legend";
1125 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1127 that.element_legend = document.createElement('div');
1128 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1129 that.element.appendChild(that.element_legend);
1132 that.element.className = "netdata-container";
1133 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1135 that.element_legend = null;
1137 that.element_legend_childs.series = null;
1139 if(typeof(that.width) === 'string')
1140 $(that.element).css('width', that.width);
1141 else if(typeof(that.width) === 'number')
1142 $(that.element).css('width', that.width + 'px');
1144 if(typeof(that.library.aspect_ratio) === 'undefined') {
1145 if(typeof(that.height) === 'string')
1146 $(that.element).css('height', that.height);
1147 else if(typeof(that.height) === 'number')
1148 $(that.element).css('height', that.height + 'px');
1151 var w = that.element.offsetWidth;
1152 if(w === null || w === 0) {
1153 // the div is hidden
1154 // this will resize the chart when next viewed
1155 that.tm.last_resized = 0;
1158 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1161 if(NETDATA.chartDefaults.min_width !== null)
1162 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1164 that.tm.last_dom_created = new Date().getTime();
1170 * initialize state variables
1171 * destroy all (possibly) created state elements
1172 * create the basic DOM for a chart
1174 var init = function() {
1175 if(that.enabled === false) return;
1177 that.paused = false;
1178 that.selected = false;
1180 that.chart_created = false; // boolean - is the library.create() been called?
1181 that.updates_counter = 0; // numeric - the number of refreshes made so far
1182 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1183 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1186 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1187 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1188 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1190 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1191 last_updated: 0, // the timestamp the chart last updated with data
1192 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1194 // Used with NETDATA.globalPanAndZoom.seq
1195 last_visible_check: 0, // the time we last checked if it is visible
1196 last_resized: 0, // the time the chart was resized
1197 last_hidden: 0, // the time the chart was hidden
1198 last_unhidden: 0, // the time the chart was unhidden
1199 last_autorefreshed: 0 // the time the chart was last refreshed
1202 that.data = null; // the last data as downloaded from the netdata server
1203 that.data_url = 'invalid://'; // string - the last url used to update the chart
1204 that.data_points = 0; // number - the number of points returned from netdata
1205 that.data_after = 0; // milliseconds - the first timestamp of the data
1206 that.data_before = 0; // milliseconds - the last timestamp of the data
1207 that.data_update_every = 0; // milliseconds - the frequency to update the data
1209 that.tm.last_initialized = new Date().getTime();
1212 that.setMode('auto');
1215 var maxMessageFontSize = function() {
1216 // normally we want a font size, as tall as the element
1217 var h = that.element_message.clientHeight;
1219 // but give it some air, 20% let's say, or 5 pixels min
1220 var lost = Math.max(h * 0.2, 5);
1223 // center the text, vertically
1224 var paddingTop = (lost - 5) / 2;
1226 // but check the width too
1227 // it should fit 10 characters in it
1228 var w = that.element_message.clientWidth / 10;
1230 paddingTop += (h - w) / 2;
1234 // and don't make it too huge
1235 // 5% of the screen size is good
1236 if(h > screen.height / 20) {
1237 paddingTop += (h - (screen.height / 20)) / 2;
1238 h = screen.height / 20;
1242 that.element_message.style.fontSize = h.toString() + 'px';
1243 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1246 var showMessage = function(msg) {
1247 that.element_message.className = 'netdata-message';
1248 that.element_message.innerHTML = msg;
1249 that.element_message.style.fontSize = 'x-small';
1250 that.element_message.style.paddingTop = '0px';
1251 that.___messageHidden___ = undefined;
1254 var showMessageIcon = function(icon) {
1255 that.element_message.innerHTML = icon;
1256 that.element_message.className = 'netdata-message icon';
1257 maxMessageFontSize();
1258 that.___messageHidden___ = undefined;
1261 var hideMessage = function() {
1262 if(typeof that.___messageHidden___ === 'undefined') {
1263 that.___messageHidden___ = true;
1264 that.element_message.className = 'netdata-message hidden';
1268 var showRendering = function() {
1270 if(that.chart !== null) {
1271 if(that.chart.chart_type === 'line')
1272 icon = '<i class="fa fa-line-chart"></i>';
1274 icon = '<i class="fa fa-area-chart"></i>';
1277 icon = '<i class="fa fa-area-chart"></i>';
1279 showMessageIcon(icon + ' netdata');
1282 var showLoading = function() {
1283 if(that.chart_created === false) {
1284 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1290 var isHidden = function() {
1291 if(typeof that.___chartIsHidden___ !== 'undefined')
1297 // hide the chart, when it is not visible - called from isVisible()
1298 var hideChart = function() {
1299 // hide it, if it is not already hidden
1300 if(isHidden() === true) return;
1302 if(that.chart_created === true) {
1303 if(NETDATA.options.current.destroy_on_hide === true) {
1304 // we should destroy it
1309 that.element_chart.style.display = 'none';
1310 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1311 that.tm.last_hidden = new Date().getTime();
1314 // This works, but I not sure there are no corner cases somewhere
1315 // so it is commented - if the user has memory issues he can
1316 // set Destroy on Hide for all charts
1317 // that.data = null;
1321 that.___chartIsHidden___ = true;
1324 // unhide the chart, when it is visible - called from isVisible()
1325 var unhideChart = function() {
1326 if(isHidden() === false) return;
1328 that.___chartIsHidden___ = undefined;
1329 that.updates_since_last_unhide = 0;
1331 if(that.chart_created === false) {
1332 // we need to re-initialize it, to show our background
1333 // logo in bootstrap tabs, until the chart loads
1337 that.tm.last_unhidden = new Date().getTime();
1338 that.element_chart.style.display = '';
1339 if(that.element_legend !== null) that.element_legend.style.display = '';
1345 var canBeRendered = function() {
1346 if(isHidden() === true || that.isVisible(true) === false)
1352 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1353 var callChartLibraryUpdateSafely = function(data) {
1356 if(canBeRendered() === false)
1359 if(NETDATA.options.debug.chart_errors === true)
1360 status = that.library.update(that, data);
1363 status = that.library.update(that, data);
1370 if(status === false) {
1371 error('chart failed to be updated as ' + that.library_name);
1378 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1379 var callChartLibraryCreateSafely = function(data) {
1382 if(canBeRendered() === false)
1385 if(NETDATA.options.debug.chart_errors === true)
1386 status = that.library.create(that, data);
1389 status = that.library.create(that, data);
1396 if(status === false) {
1397 error('chart failed to be created as ' + that.library_name);
1401 that.chart_created = true;
1402 that.updates_since_last_creation = 0;
1406 // ----------------------------------------------------------------------------------------------------------------
1409 // resizeChart() - private
1410 // to be called just before the chart library to make sure that
1411 // a properly sized dom is available
1412 var resizeChart = function() {
1413 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1414 if(that.chart_created === false) return;
1416 if(that.needsRecreation()) {
1419 else if(typeof that.library.resize === 'function') {
1420 that.library.resize(that);
1422 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1423 $(that.element_legend_childs.nano).nanoScroller();
1425 maxMessageFontSize();
1428 that.tm.last_resized = new Date().getTime();
1432 // this is the actual chart resize algorithm
1434 // - resize the entire container
1435 // - update the internal states
1436 // - resize the chart as the div changes height
1437 // - update the scrollbar of the legend
1438 var resizeChartToHeight = function(h) {
1440 that.element.style.height = h;
1442 if(that.settings_id !== null)
1443 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1445 var now = new Date().getTime();
1446 NETDATA.options.last_page_scroll = now;
1447 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1450 that.tm.last_resized = 0;
1454 this.resizeHandler = function(e) {
1457 if(typeof this.event_resize === 'undefined'
1458 || this.event_resize.chart_original_w === 'undefined'
1459 || this.event_resize.chart_original_h === 'undefined')
1460 this.event_resize = {
1461 chart_original_w: this.element.clientWidth,
1462 chart_original_h: this.element.clientHeight,
1466 if(e.type === 'touchstart') {
1467 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1468 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1471 this.event_resize.mouse_start_x = e.clientX;
1472 this.event_resize.mouse_start_y = e.clientY;
1475 this.event_resize.chart_start_w = this.element.clientWidth;
1476 this.event_resize.chart_start_h = this.element.clientHeight;
1477 this.event_resize.chart_last_w = this.element.clientWidth;
1478 this.event_resize.chart_last_h = this.element.clientHeight;
1480 var now = new Date().getTime();
1481 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1482 // double click / double tap event
1484 // the optimal height of the chart
1485 // showing the entire legend
1486 var optimal = this.event_resize.chart_last_h
1487 + this.element_legend_childs.content.scrollHeight
1488 - this.element_legend_childs.content.clientHeight;
1490 // if we are not optimal, be optimal
1491 if(this.event_resize.chart_last_h != optimal)
1492 resizeChartToHeight(optimal.toString() + 'px');
1494 // else if we do not have the original height
1495 // reset to the original height
1496 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1497 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1500 this.event_resize.last = now;
1502 // process movement event
1503 document.onmousemove =
1504 document.ontouchmove =
1505 this.element_legend_childs.resize_handler.onmousemove =
1506 this.element_legend_childs.resize_handler.ontouchmove =
1511 case 'mousemove': y = e.clientY; break;
1512 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1516 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1518 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1519 resizeChartToHeight(newH.toString() + 'px');
1520 that.event_resize.chart_last_h = newH;
1525 // process end event
1526 document.onmouseup =
1527 document.ontouchend =
1528 this.element_legend_childs.resize_handler.onmouseup =
1529 this.element_legend_childs.resize_handler.ontouchend =
1531 // remove all the hooks
1532 document.onmouseup =
1533 document.onmousemove =
1534 document.ontouchmove =
1535 document.ontouchend =
1536 that.element_legend_childs.resize_handler.onmousemove =
1537 that.element_legend_childs.resize_handler.ontouchmove =
1538 that.element_legend_childs.resize_handler.onmouseout =
1539 that.element_legend_childs.resize_handler.onmouseup =
1540 that.element_legend_childs.resize_handler.ontouchend =
1543 // allow auto-refreshes
1544 NETDATA.options.auto_refresher_stop_until = 0;
1550 var noDataToShow = function() {
1551 showMessageIcon('<i class="fa fa-warning"></i> empty');
1552 that.legendUpdateDOM();
1553 that.tm.last_autorefreshed = new Date().getTime();
1554 // that.data_update_every = 30 * 1000;
1555 //that.element_chart.style.display = 'none';
1556 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1557 //that.___chartIsHidden___ = true;
1560 // ============================================================================================================
1563 this.error = function(msg) {
1567 this.setMode = function(m) {
1568 if(this.current !== null && this.current.name === m) return;
1571 this.current = this.auto;
1572 else if(m === 'pan')
1573 this.current = this.pan;
1574 else if(m === 'zoom')
1575 this.current = this.zoom;
1577 this.current = this.auto;
1579 this.current.force_update_at = 0;
1580 this.current.force_before_ms = null;
1581 this.current.force_after_ms = null;
1583 this.tm.last_mode_switch = new Date().getTime();
1586 // ----------------------------------------------------------------------------------------------------------------
1587 // global selection sync
1589 // prevent to global selection sync for some time
1590 this.globalSelectionSyncDelay = function(ms) {
1591 if(NETDATA.options.current.sync_selection === false)
1594 if(typeof ms === 'number')
1595 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1597 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1600 // can we globally apply selection sync?
1601 this.globalSelectionSyncAbility = function() {
1602 if(NETDATA.options.current.sync_selection === false)
1605 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1611 this.globalSelectionSyncIsMaster = function() {
1612 if(NETDATA.globalSelectionSync.state === this)
1618 // this chart is the master of the global selection sync
1619 this.globalSelectionSyncBeMaster = function() {
1621 if(this.globalSelectionSyncIsMaster()) {
1622 if(this.debug === true)
1623 this.log('sync: I am the master already.');
1628 if(NETDATA.globalSelectionSync.state) {
1629 if(this.debug === true)
1630 this.log('sync: I am not the sync master. Resetting global sync.');
1632 this.globalSelectionSyncStop();
1635 // become the master
1636 if(this.debug === true)
1637 this.log('sync: becoming sync master.');
1639 this.selected = true;
1640 NETDATA.globalSelectionSync.state = this;
1642 // find the all slaves
1643 var targets = NETDATA.options.targets;
1644 var len = targets.length;
1649 if(this.debug === true)
1650 st.log('sync: not adding me to sync');
1652 else if(st.globalSelectionSyncIsEligible()) {
1653 if(this.debug === true)
1654 st.log('sync: adding to sync as slave');
1656 st.globalSelectionSyncBeSlave();
1660 // this.globalSelectionSyncDelay(100);
1663 // can the chart participate to the global selection sync as a slave?
1664 this.globalSelectionSyncIsEligible = function() {
1665 if(this.enabled === true
1666 && this.library !== null
1667 && typeof this.library.setSelection === 'function'
1668 && this.isVisible() === true
1669 && this.chart_created === true)
1675 // this chart becomes a slave of the global selection sync
1676 this.globalSelectionSyncBeSlave = function() {
1677 if(NETDATA.globalSelectionSync.state !== this)
1678 NETDATA.globalSelectionSync.slaves.push(this);
1681 // sync all the visible charts to the given time
1682 // this is to be called from the chart libraries
1683 this.globalSelectionSync = function(t) {
1684 if(this.globalSelectionSyncAbility() === false) {
1685 if(this.debug === true)
1686 this.log('sync: cannot sync (yet?).');
1691 if(this.globalSelectionSyncIsMaster() === false) {
1692 if(this.debug === true)
1693 this.log('sync: trying to be sync master.');
1695 this.globalSelectionSyncBeMaster();
1697 if(this.globalSelectionSyncAbility() === false) {
1698 if(this.debug === true)
1699 this.log('sync: cannot sync (yet?).');
1705 NETDATA.globalSelectionSync.last_t = t;
1706 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1711 // stop syncing all charts to the given time
1712 this.globalSelectionSyncStop = function() {
1713 if(NETDATA.globalSelectionSync.slaves.length) {
1714 if(this.debug === true)
1715 this.log('sync: cleaning up...');
1717 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1719 if(that.debug === true)
1720 st.log('sync: not adding me to sync stop');
1723 if(that.debug === true)
1724 st.log('sync: removed slave from sync');
1726 st.clearSelection();
1730 NETDATA.globalSelectionSync.last_t = 0;
1731 NETDATA.globalSelectionSync.slaves = [];
1732 NETDATA.globalSelectionSync.state = null;
1735 this.clearSelection();
1738 this.setSelection = function(t) {
1739 if(typeof this.library.setSelection === 'function') {
1740 if(this.library.setSelection(this, t) === true)
1741 this.selected = true;
1743 this.selected = false;
1745 else this.selected = true;
1747 if(this.selected === true && this.debug === true)
1748 this.log('selection set to ' + t.toString());
1750 return this.selected;
1753 this.clearSelection = function() {
1754 if(this.selected === true) {
1755 if(typeof this.library.clearSelection === 'function') {
1756 if(this.library.clearSelection(this) === true)
1757 this.selected = false;
1759 this.selected = true;
1761 else this.selected = false;
1763 if(this.selected === false && this.debug === true)
1764 this.log('selection cleared');
1769 return this.selected;
1772 // find if a timestamp (ms) is shown in the current chart
1773 this.timeIsVisible = function(t) {
1774 if(t >= this.data_after && t <= this.data_before)
1779 this.calculateRowForTime = function(t) {
1780 if(this.timeIsVisible(t) === false) return -1;
1781 return Math.floor((t - this.data_after) / this.data_update_every);
1784 // ----------------------------------------------------------------------------------------------------------------
1787 this.log = function(msg) {
1788 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1791 this.pauseChart = function() {
1792 if(this.paused === false) {
1793 if(this.debug === true)
1794 this.log('pauseChart()');
1800 this.unpauseChart = function() {
1801 if(this.paused === true) {
1802 if(this.debug === true)
1803 this.log('unpauseChart()');
1805 this.paused = false;
1809 this.resetChart = function(dont_clear_master, dont_update) {
1810 if(this.debug === true)
1811 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1813 if(typeof dont_clear_master === 'undefined')
1814 dont_clear_master = false;
1816 if(typeof dont_update === 'undefined')
1817 dont_update = false;
1819 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1820 if(this.debug === true)
1821 this.log('resetChart() diverting to clearMaster().');
1822 // this will call us back with master === true
1823 NETDATA.globalPanAndZoom.clearMaster();
1827 this.clearSelection();
1829 this.tm.pan_and_zoom_seq = 0;
1831 this.setMode('auto');
1832 this.current.force_update_at = 0;
1833 this.current.force_before_ms = null;
1834 this.current.force_after_ms = null;
1835 this.tm.last_autorefreshed = 0;
1836 this.paused = false;
1837 this.selected = false;
1838 this.enabled = true;
1839 // this.debug = false;
1841 // do not update the chart here
1842 // or the chart will flip-flop when it is the master
1843 // of a selection sync and another chart becomes
1846 if(dont_update !== true && this.isVisible() === true) {
1851 this.updateChartPanOrZoom = function(after, before) {
1852 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1855 if(this.debug === true)
1858 if(before < after) {
1859 if(this.debug === true)
1860 this.log(logme + 'flipped parameters, rejecting it.');
1865 if(typeof this.fixed_min_duration === 'undefined')
1866 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1868 var min_duration = this.fixed_min_duration;
1869 var current_duration = Math.round(this.view_before - this.view_after);
1871 // round the numbers
1872 after = Math.round(after);
1873 before = Math.round(before);
1875 // align them to update_every
1876 // stretching them further away
1877 after -= after % this.data_update_every;
1878 before += this.data_update_every - (before % this.data_update_every);
1880 // the final wanted duration
1881 var wanted_duration = before - after;
1883 // to allow panning, accept just a point below our minimum
1884 if((current_duration - this.data_update_every) < min_duration)
1885 min_duration = current_duration - this.data_update_every;
1887 // we do it, but we adjust to minimum size and return false
1888 // when the wanted size is below the current and the minimum
1890 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1891 if(this.debug === true)
1892 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1894 min_duration = this.fixed_min_duration;
1896 var dt = (min_duration - wanted_duration) / 2;
1899 wanted_duration = before - after;
1903 var tolerance = this.data_update_every * 2;
1904 var movement = Math.abs(before - this.view_before);
1906 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1907 if(this.debug === true)
1908 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);
1912 if(this.current.name === 'auto') {
1913 this.log(logme + 'caller called me with mode: ' + this.current.name);
1914 this.setMode('pan');
1917 if(this.debug === true)
1918 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);
1920 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1921 this.current.force_after_ms = after;
1922 this.current.force_before_ms = before;
1923 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1927 this.legendFormatValue = function(value) {
1928 if(value === null || value === 'undefined') return '-';
1929 if(typeof value !== 'number') return value;
1931 var abs = Math.abs(value);
1932 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1933 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1934 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1935 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1936 return (Math.round(value * 10000) / 10000).toLocaleString();
1939 this.legendSetLabelValue = function(label, value) {
1940 var series = this.element_legend_childs.series[label];
1941 if(typeof series === 'undefined') return;
1942 if(series.value === null && series.user === null) return;
1944 // if the value has not changed, skip DOM update
1945 //if(series.last === value) return;
1948 if(typeof value === 'number') {
1949 var v = Math.abs(value);
1950 s = r = this.legendFormatValue(value);
1952 if(typeof series.last === 'number') {
1953 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1954 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1955 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1957 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1962 series.last = value;
1965 if(series.value !== null) series.value.innerHTML = s;
1966 if(series.user !== null) series.user.innerHTML = r;
1969 this.legendSetDate = function(ms) {
1970 if(typeof ms !== 'number') {
1971 this.legendShowUndefined();
1975 var d = new Date(ms);
1977 if(this.element_legend_childs.title_date)
1978 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1980 if(this.element_legend_childs.title_time)
1981 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1983 if(this.element_legend_childs.title_units)
1984 this.element_legend_childs.title_units.innerHTML = this.units;
1987 this.legendShowUndefined = function() {
1988 if(this.element_legend_childs.title_date)
1989 this.element_legend_childs.title_date.innerHTML = ' ';
1991 if(this.element_legend_childs.title_time)
1992 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1994 if(this.element_legend_childs.title_units)
1995 this.element_legend_childs.title_units.innerHTML = ' ';
1997 if(this.data && this.element_legend_childs.series !== null) {
1998 var labels = this.data.dimension_names;
1999 var i = labels.length;
2001 var label = labels[i];
2003 if(typeof label === 'undefined') continue;
2004 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2005 this.legendSetLabelValue(label, null);
2010 this.legendShowLatestValues = function() {
2011 if(this.chart === null) return;
2012 if(this.selected) return;
2014 if(this.data === null || this.element_legend_childs.series === null) {
2015 this.legendShowUndefined();
2019 var show_undefined = true;
2020 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2021 show_undefined = false;
2023 if(show_undefined) {
2024 this.legendShowUndefined();
2028 this.legendSetDate(this.view_before);
2030 var labels = this.data.dimension_names;
2031 var i = labels.length;
2033 var label = labels[i];
2035 if(typeof label === 'undefined') continue;
2036 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2039 this.legendSetLabelValue(label, null);
2041 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2045 this.legendReset = function() {
2046 this.legendShowLatestValues();
2049 // this should be called just ONCE per dimension per chart
2050 this._chartDimensionColor = function(label) {
2051 if(this.colors === null) this.chartColors();
2053 if(typeof this.colors_assigned[label] === 'undefined') {
2054 if(this.colors_available.length === 0) {
2055 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2056 this.colors_available.push(NETDATA.themes.current.colors[i]);
2059 this.colors_assigned[label] = this.colors_available.shift();
2061 if(this.debug === true)
2062 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2065 if(this.debug === true)
2066 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2069 this.colors.push(this.colors_assigned[label]);
2070 return this.colors_assigned[label];
2073 this.chartColors = function() {
2074 if(this.colors !== null) return this.colors;
2076 this.colors = new Array();
2077 this.colors_available = new Array();
2080 var c = $(this.element).data('colors');
2081 // this.log('read colors: ' + c);
2082 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2083 if(typeof c !== 'string') {
2084 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2091 for(i = 0, len = c.length; i < len ; i++) {
2093 this.colors_available.push(c[i]);
2094 // this.log('adding color: ' + c[i]);
2100 // push all the standard colors too
2101 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2102 this.colors_available.push(NETDATA.themes.current.colors[i]);
2107 this.legendUpdateDOM = function() {
2110 // check that the legend DOM is up to date for the downloaded dimensions
2111 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2112 // this.log('the legend does not have any series - requesting legend update');
2115 else if(this.data === null) {
2116 // this.log('the chart does not have any data - requesting legend update');
2119 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2123 var labels = this.data.dimension_names.toString();
2124 if(labels !== this.element_legend_childs.series.labels_key) {
2127 if(this.debug === true)
2128 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2132 if(needed === false) {
2133 // make sure colors available
2136 // do we have to update the current values?
2137 // we do this, only when the visible chart is current
2138 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2139 if(this.debug === true)
2140 this.log('chart is in latest position... updating values on legend...');
2142 //var labels = this.data.dimension_names;
2143 //var i = labels.length;
2145 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2149 if(this.colors === null) {
2150 // this is the first time we update the chart
2151 // let's assign colors to all dimensions
2152 if(this.library.track_colors() === true)
2153 for(var dim in this.chart.dimensions)
2154 this._chartDimensionColor(this.chart.dimensions[dim].name);
2156 // we will re-generate the colors for the chart
2157 // based on the selected dimensions
2160 if(this.debug === true)
2161 this.log('updating Legend DOM');
2163 // mark all dimensions as invalid
2164 this.dimensions_visibility.invalidateAll();
2166 var genLabel = function(state, parent, dim, name, count) {
2167 var color = state._chartDimensionColor(name);
2169 var user_element = null;
2170 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2171 if(user_id !== null) {
2172 user_element = document.getElementById(user_id) || null;
2173 if(user_element === null)
2174 state.log('Cannot find element with id: ' + user_id);
2177 state.element_legend_childs.series[name] = {
2178 name: document.createElement('span'),
2179 value: document.createElement('span'),
2184 var label = state.element_legend_childs.series[name];
2186 // create the dimension visibility tracking for this label
2187 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2189 var rgb = NETDATA.colorHex2Rgb(color);
2190 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2191 + state.chart.chart_type
2192 + '" style="background-color: '
2193 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2194 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2196 var text = document.createTextNode(' ' + name);
2197 label.name.appendChild(text);
2200 parent.appendChild(document.createElement('br'));
2202 parent.appendChild(label.name);
2203 parent.appendChild(label.value);
2206 var content = document.createElement('div');
2208 if(this.hasLegend()) {
2209 this.element_legend_childs = {
2211 resize_handler: document.createElement('div'),
2212 toolbox: document.createElement('div'),
2213 toolbox_left: document.createElement('div'),
2214 toolbox_right: document.createElement('div'),
2215 toolbox_reset: document.createElement('div'),
2216 toolbox_zoomin: document.createElement('div'),
2217 toolbox_zoomout: document.createElement('div'),
2218 toolbox_volume: document.createElement('div'),
2219 title_date: document.createElement('span'),
2220 title_time: document.createElement('span'),
2221 title_units: document.createElement('span'),
2222 nano: document.createElement('div'),
2224 paneClass: 'netdata-legend-series-pane',
2225 sliderClass: 'netdata-legend-series-slider',
2226 contentClass: 'netdata-legend-series-content',
2227 enabledClass: '__enabled',
2228 flashedClass: '__flashed',
2229 activeClass: '__active',
2231 alwaysVisible: true,
2237 this.element_legend.innerHTML = '';
2239 if(this.library.toolboxPanAndZoom !== null) {
2241 function get_pan_and_zoom_step(event) {
2243 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2245 else if (event.shiftKey)
2246 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2248 else if (event.altKey)
2249 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2252 return NETDATA.options.current.pan_and_zoom_factor;
2255 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2256 this.element.appendChild(this.element_legend_childs.toolbox);
2258 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2259 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2260 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2261 this.element_legend_childs.toolbox_left.onclick = function(e) {
2264 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2265 var before = that.view_before - step;
2266 var after = that.view_after - step;
2267 if(after >= that.netdata_first)
2268 that.library.toolboxPanAndZoom(that, after, before);
2270 if(NETDATA.options.current.show_help === true)
2271 $(this.element_legend_childs.toolbox_left).popover({
2276 placement: 'bottom',
2277 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2279 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>'
2283 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2284 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2285 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2286 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2288 NETDATA.resetAllCharts(that);
2290 if(NETDATA.options.current.show_help === true)
2291 $(this.element_legend_childs.toolbox_reset).popover({
2296 placement: 'bottom',
2297 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2298 title: 'Chart Reset',
2299 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>'
2302 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2303 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2304 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2305 this.element_legend_childs.toolbox_right.onclick = function(e) {
2307 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2308 var before = that.view_before + step;
2309 var after = that.view_after + step;
2310 if(before <= that.netdata_last)
2311 that.library.toolboxPanAndZoom(that, after, before);
2313 if(NETDATA.options.current.show_help === true)
2314 $(this.element_legend_childs.toolbox_right).popover({
2319 placement: 'bottom',
2320 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2322 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>'
2326 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2327 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2328 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2329 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2331 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2332 var before = that.view_before - dt;
2333 var after = that.view_after + dt;
2334 that.library.toolboxPanAndZoom(that, after, before);
2336 if(NETDATA.options.current.show_help === true)
2337 $(this.element_legend_childs.toolbox_zoomin).popover({
2342 placement: 'bottom',
2343 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2344 title: 'Chart Zoom In',
2345 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>'
2348 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2349 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2350 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2351 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2353 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);
2354 var before = that.view_before + dt;
2355 var after = that.view_after - dt;
2357 that.library.toolboxPanAndZoom(that, after, before);
2359 if(NETDATA.options.current.show_help === true)
2360 $(this.element_legend_childs.toolbox_zoomout).popover({
2365 placement: 'bottom',
2366 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2367 title: 'Chart Zoom Out',
2368 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>'
2371 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2372 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2373 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2374 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2375 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2376 //e.preventDefault();
2377 //alert('clicked toolbox_volume on ' + that.id);
2381 this.element_legend_childs.toolbox = null;
2382 this.element_legend_childs.toolbox_left = null;
2383 this.element_legend_childs.toolbox_reset = null;
2384 this.element_legend_childs.toolbox_right = null;
2385 this.element_legend_childs.toolbox_zoomin = null;
2386 this.element_legend_childs.toolbox_zoomout = null;
2387 this.element_legend_childs.toolbox_volume = null;
2390 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2391 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2392 this.element.appendChild(this.element_legend_childs.resize_handler);
2393 if(NETDATA.options.current.show_help === true)
2394 $(this.element_legend_childs.resize_handler).popover({
2399 placement: 'bottom',
2400 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2401 title: 'Chart Resize',
2402 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>'
2406 this.element_legend_childs.resize_handler.onmousedown =
2408 that.resizeHandler(e);
2412 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2413 that.resizeHandler(e);
2416 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2417 this.element_legend.appendChild(this.element_legend_childs.title_date);
2419 this.element_legend.appendChild(document.createElement('br'));
2421 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2422 this.element_legend.appendChild(this.element_legend_childs.title_time);
2424 this.element_legend.appendChild(document.createElement('br'));
2426 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2427 this.element_legend.appendChild(this.element_legend_childs.title_units);
2429 this.element_legend.appendChild(document.createElement('br'));
2431 this.element_legend_childs.nano.className = 'netdata-legend-series';
2432 this.element_legend.appendChild(this.element_legend_childs.nano);
2434 content.className = 'netdata-legend-series-content';
2435 this.element_legend_childs.nano.appendChild(content);
2437 if(NETDATA.options.current.show_help === true)
2438 $(content).popover({
2443 placement: 'bottom',
2444 title: 'Chart Legend',
2445 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2446 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>'
2450 this.element_legend_childs = {
2452 resize_handler: null,
2455 toolbox_right: null,
2456 toolbox_reset: null,
2457 toolbox_zoomin: null,
2458 toolbox_zoomout: null,
2459 toolbox_volume: null,
2470 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2471 if(this.debug === true)
2472 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2474 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2475 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2479 var tmp = new Array();
2480 for(var dim in this.chart.dimensions) {
2481 tmp.push(this.chart.dimensions[dim].name);
2482 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2484 this.element_legend_childs.series.labels_key = tmp.toString();
2485 if(this.debug === true)
2486 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2489 // create a hidden div to be used for hidding
2490 // the original legend of the chart library
2491 var el = document.createElement('div');
2492 if(this.element_legend !== null)
2493 this.element_legend.appendChild(el);
2494 el.style.display = 'none';
2496 this.element_legend_childs.hidden = document.createElement('div');
2497 el.appendChild(this.element_legend_childs.hidden);
2499 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2500 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2502 this.legendShowLatestValues();
2505 this.hasLegend = function() {
2506 if(typeof this.___hasLegendCache___ !== 'undefined')
2507 return this.___hasLegendCache___;
2510 if(this.library && this.library.legend(this) === 'right-side') {
2511 var legend = $(this.element).data('legend') || 'yes';
2512 if(legend === 'yes') leg = true;
2515 this.___hasLegendCache___ = leg;
2519 this.legendWidth = function() {
2520 return (this.hasLegend())?140:0;
2523 this.legendHeight = function() {
2524 return $(this.element).height();
2527 this.chartWidth = function() {
2528 return $(this.element).width() - this.legendWidth();
2531 this.chartHeight = function() {
2532 return $(this.element).height();
2535 this.chartPixelsPerPoint = function() {
2536 // force an options provided detail
2537 var px = this.pixels_per_point;
2539 if(this.library && px < this.library.pixels_per_point(this))
2540 px = this.library.pixels_per_point(this);
2542 if(px < NETDATA.options.current.pixels_per_point)
2543 px = NETDATA.options.current.pixels_per_point;
2548 this.needsRecreation = function() {
2550 this.chart_created === true
2552 && this.library.autoresize() === false
2553 && this.tm.last_resized < NETDATA.options.last_resized
2557 this.chartURL = function() {
2558 var after, before, points_multiplier = 1;
2559 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2560 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2562 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2563 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2564 this.view_after = after * 1000;
2565 this.view_before = before * 1000;
2567 this.requested_padding = null;
2568 points_multiplier = 1;
2570 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2571 this.tm.pan_and_zoom_seq = 0;
2573 before = Math.round(this.current.force_before_ms / 1000);
2574 after = Math.round(this.current.force_after_ms / 1000);
2575 this.view_after = after * 1000;
2576 this.view_before = before * 1000;
2578 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2579 this.requested_padding = Math.round((before - after) / 2);
2580 after -= this.requested_padding;
2581 before += this.requested_padding;
2582 this.requested_padding *= 1000;
2583 points_multiplier = 2;
2586 this.current.force_before_ms = null;
2587 this.current.force_after_ms = null;
2590 this.tm.pan_and_zoom_seq = 0;
2592 before = this.before;
2594 this.view_after = after * 1000;
2595 this.view_before = before * 1000;
2597 this.requested_padding = null;
2598 points_multiplier = 1;
2601 this.requested_after = after * 1000;
2602 this.requested_before = before * 1000;
2604 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2606 // build the data URL
2607 this.data_url = this.host + this.chart.data_url;
2608 this.data_url += "&format=" + this.library.format();
2609 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2610 this.data_url += "&group=" + this.method;
2611 this.data_url += "&options=" + this.library.options(this);
2612 this.data_url += '|jsonwrap';
2614 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2615 this.data_url += '|nonzero';
2617 if(this.append_options !== null)
2618 this.data_url += '|' + this.append_options.toString();
2621 this.data_url += "&after=" + after.toString();
2624 this.data_url += "&before=" + before.toString();
2627 this.data_url += "&dimensions=" + this.dimensions;
2629 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2630 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2633 this.redrawChart = function() {
2634 if(this.data !== null)
2635 this.updateChartWithData(this.data);
2638 this.updateChartWithData = function(data) {
2639 if(this.debug === true)
2640 this.log('updateChartWithData() called.');
2642 // this may force the chart to be re-created
2646 this.updates_counter++;
2647 this.updates_since_last_unhide++;
2648 this.updates_since_last_creation++;
2650 var started = new Date().getTime();
2652 // if the result is JSON, find the latest update-every
2653 this.data_update_every = data.view_update_every * 1000;
2654 this.data_after = data.after * 1000;
2655 this.data_before = data.before * 1000;
2656 this.netdata_first = data.first_entry * 1000;
2657 this.netdata_last = data.last_entry * 1000;
2658 this.data_points = data.points;
2661 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2662 if(this.view_after < this.data_after) {
2663 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2664 this.view_after = this.data_after;
2667 if(this.view_before > this.data_before) {
2668 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2669 this.view_before = this.data_before;
2673 this.view_after = this.data_after;
2674 this.view_before = this.data_before;
2677 if(this.debug === true) {
2678 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2680 if(this.current.force_after_ms)
2681 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2683 this.log('STATUS: forced : unset');
2685 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2686 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2687 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2688 this.log('STATUS: points : ' + (this.data_points).toString());
2691 if(this.data_points === 0) {
2696 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2697 if(this.debug === true)
2698 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2700 this.chart_created = false;
2703 // check and update the legend
2704 this.legendUpdateDOM();
2706 if(this.chart_created === true
2707 && typeof this.library.update === 'function') {
2709 if(this.debug === true)
2710 this.log('updating chart...');
2712 if(callChartLibraryUpdateSafely(data) === false)
2716 if(this.debug === true)
2717 this.log('creating chart...');
2719 if(callChartLibraryCreateSafely(data) === false)
2723 this.legendShowLatestValues();
2724 if(this.selected === true)
2725 NETDATA.globalSelectionSync.stop();
2727 // update the performance counters
2728 var now = new Date().getTime();
2729 this.tm.last_updated = now;
2731 // don't update last_autorefreshed if this chart is
2732 // forced to be updated with global PanAndZoom
2733 if(NETDATA.globalPanAndZoom.isActive())
2734 this.tm.last_autorefreshed = 0;
2736 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2737 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2739 this.tm.last_autorefreshed = now;
2742 this.refresh_dt_ms = now - started;
2743 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2745 if(this.refresh_dt_element !== null)
2746 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2749 this.updateChart = function(callback) {
2750 if(this.debug === true)
2751 this.log('updateChart() called.');
2753 if(this._updating === true) {
2754 if(this.debug === true)
2755 this.log('I am already updating...');
2757 if(typeof callback === 'function') callback();
2761 // due to late initialization of charts and libraries
2762 // we need to check this too
2763 if(this.enabled === false) {
2764 if(this.debug === true)
2765 this.log('I am not enabled');
2767 if(typeof callback === 'function') callback();
2771 if(canBeRendered() === false) {
2772 if(typeof callback === 'function') callback();
2776 if(this.chart === null) {
2777 this.getChart(function() { that.updateChart(callback); });
2781 if(this.library.initialized === false) {
2782 if(this.library.enabled === true) {
2783 this.library.initialize(function() { that.updateChart(callback); });
2787 error('chart library "' + this.library_name + '" is not available.');
2788 if(typeof callback === 'function') callback();
2793 this.clearSelection();
2796 if(this.debug === true)
2797 this.log('updating from ' + this.data_url);
2799 NETDATA.statistics.refreshes_total++;
2800 NETDATA.statistics.refreshes_active++;
2802 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2803 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2805 this._updating = true;
2807 this.xhr = $.ajax( {
2811 xhrFields: { withCredentials: true } // required for the cookie
2813 .success(function(data) {
2814 if(that.debug === true)
2815 that.log('data received. updating chart.');
2817 that.updateChartWithData(data);
2820 error('data download failed for url: ' + that.data_url);
2822 .always(function() {
2823 NETDATA.statistics.refreshes_active--;
2824 that._updating = false;
2825 if(typeof callback === 'function') callback();
2831 this.isVisible = function(nocache) {
2832 if(typeof nocache === 'undefined')
2835 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2837 // caching - we do not evaluate the charts visibility
2838 // if the page has not been scrolled since the last check
2839 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2840 return this.___isVisible___;
2842 this.tm.last_visible_check = new Date().getTime();
2844 var wh = window.innerHeight;
2845 var x = this.element.getBoundingClientRect();
2849 if(x.width === 0 || x.height === 0) {
2851 this.___isVisible___ = false;
2852 return this.___isVisible___;
2855 if(x.top < 0 && -x.top > x.height) {
2856 // the chart is entirely above
2857 ret = -x.top - x.height;
2859 else if(x.top > wh) {
2860 // the chart is entirely below
2864 if(ret > tolerance) {
2865 // the chart is too far
2868 this.___isVisible___ = false;
2869 return this.___isVisible___;
2872 // the chart is inside or very close
2875 this.___isVisible___ = true;
2876 return this.___isVisible___;
2880 this.isAutoRefreshable = function() {
2881 return (this.current.autorefresh);
2884 this.canBeAutoRefreshed = function() {
2885 var now = new Date().getTime();
2887 if(this.running === true) {
2888 if(this.debug === true)
2889 this.log('I am already running');
2894 if(this.enabled === false) {
2895 if(this.debug === true)
2896 this.log('I am not enabled');
2901 if(this.library === null || this.library.enabled === false) {
2902 error('charting library "' + this.library_name + '" is not available');
2903 if(this.debug === true)
2904 this.log('My chart library ' + this.library_name + ' is not available');
2909 if(this.isVisible() === false) {
2910 if(NETDATA.options.debug.visibility === true || this.debug === true)
2911 this.log('I am not visible');
2916 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2917 if(this.debug === true)
2918 this.log('timed force update detected - allowing this update');
2920 this.current.force_update_at = 0;
2924 if(this.isAutoRefreshable() === true) {
2925 // allow the first update, even if the page is not visible
2926 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2927 if(NETDATA.options.debug.focus === true || this.debug === true)
2928 this.log('canBeAutoRefreshed(): page does not have focus');
2933 if(this.needsRecreation() === true) {
2934 if(this.debug === true)
2935 this.log('canBeAutoRefreshed(): needs re-creation.');
2940 // options valid only for autoRefresh()
2941 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2942 if(NETDATA.globalPanAndZoom.isActive()) {
2943 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2944 if(this.debug === true)
2945 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2950 if(this.debug === true)
2951 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2957 if(this.selected === true) {
2958 if(this.debug === true)
2959 this.log('canBeAutoRefreshed(): I have a selection in place.');
2964 if(this.paused === true) {
2965 if(this.debug === true)
2966 this.log('canBeAutoRefreshed(): I am paused.');
2971 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2972 if(this.debug === true)
2973 this.log('canBeAutoRefreshed(): It is time to update me.');
2983 this.autoRefresh = function(callback) {
2984 if(this.canBeAutoRefreshed() === true && this.running === false) {
2987 state.running = true;
2988 state.updateChart(function() {
2989 state.running = false;
2991 if(typeof callback !== 'undefined')
2996 if(typeof callback !== 'undefined')
3001 this._defaultsFromDownloadedChart = function(chart) {
3003 this.chart_url = chart.url;
3004 this.data_update_every = chart.update_every * 1000;
3005 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3006 this.tm.last_info_downloaded = new Date().getTime();
3008 if(this.title === null)
3009 this.title = chart.title;
3011 if(this.units === null)
3012 this.units = chart.units;
3015 // fetch the chart description from the netdata server
3016 this.getChart = function(callback) {
3017 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3019 this._defaultsFromDownloadedChart(this.chart);
3020 if(typeof callback === 'function') callback();
3023 this.chart_url = "/api/v1/chart?chart=" + this.id;
3025 if(this.debug === true)
3026 this.log('downloading ' + this.chart_url);
3029 url: this.host + this.chart_url,
3032 xhrFields: { withCredentials: true } // required for the cookie
3034 .done(function(chart) {
3035 chart.url = that.chart_url;
3036 that._defaultsFromDownloadedChart(chart);
3037 NETDATA.chartRegistry.add(that.host, that.id, chart);
3040 NETDATA.error(404, that.chart_url);
3041 error('chart not found on url "' + that.chart_url + '"');
3043 .always(function() {
3044 if(typeof callback === 'function') callback();
3049 // ============================================================================================================
3055 NETDATA.resetAllCharts = function(state) {
3056 // first clear the global selection sync
3057 // to make sure no chart is in selected state
3058 state.globalSelectionSyncStop();
3060 // there are 2 possibilities here
3061 // a. state is the global Pan and Zoom master
3062 // b. state is not the global Pan and Zoom master
3064 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3067 // clear the global Pan and Zoom
3068 // this will also refresh the master
3069 // and unblock any charts currently mirroring the master
3070 NETDATA.globalPanAndZoom.clearMaster();
3072 // if we were not the master, reset our status too
3073 // this is required because most probably the mouse
3074 // is over this chart, blocking it from auto-refreshing
3075 if(master === false && (state.paused === true || state.selected === true))
3079 // get or create a chart state, given a DOM element
3080 NETDATA.chartState = function(element) {
3081 var state = $(element).data('netdata-state-object') || null;
3082 if(state === null) {
3083 state = new chartState(element);
3084 $(element).data('netdata-state-object', state);
3089 // ----------------------------------------------------------------------------------------------------------------
3090 // Library functions
3092 // Load a script without jquery
3093 // This is used to load jquery - after it is loaded, we use jquery
3094 NETDATA._loadjQuery = function(callback) {
3095 if(typeof jQuery === 'undefined') {
3096 if(NETDATA.options.debug.main_loop === true)
3097 console.log('loading ' + NETDATA.jQuery);
3099 var script = document.createElement('script');
3100 script.type = 'text/javascript';
3101 script.async = true;
3102 script.src = NETDATA.jQuery;
3104 // script.onabort = onError;
3105 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3106 if(typeof callback === "function")
3107 script.onload = callback;
3109 var s = document.getElementsByTagName('script')[0];
3110 s.parentNode.insertBefore(script, s);
3112 else if(typeof callback === "function")
3116 NETDATA._loadCSS = function(filename) {
3117 // don't use jQuery here
3118 // styles are loaded before jQuery
3119 // to eliminate showing an unstyled page to the user
3121 var fileref = document.createElement("link");
3122 fileref.setAttribute("rel", "stylesheet");
3123 fileref.setAttribute("type", "text/css");
3124 fileref.setAttribute("href", filename);
3126 if (typeof fileref !== 'undefined')
3127 document.getElementsByTagName("head")[0].appendChild(fileref);
3130 NETDATA.colorHex2Rgb = function(hex) {
3131 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3132 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3133 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3134 return r + r + g + g + b + b;
3137 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3139 r: parseInt(result[1], 16),
3140 g: parseInt(result[2], 16),
3141 b: parseInt(result[3], 16)
3145 NETDATA.colorLuminance = function(hex, lum) {
3146 // validate hex string
3147 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3149 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3153 // convert to decimal and change luminosity
3154 var rgb = "#", c, i;
3155 for (i = 0; i < 3; i++) {
3156 c = parseInt(hex.substr(i*2,2), 16);
3157 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3158 rgb += ("00"+c).substr(c.length);
3164 NETDATA.guid = function() {
3166 return Math.floor((1 + Math.random()) * 0x10000)
3171 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3174 NETDATA.zeropad = function(x) {
3175 if(x > -10 && x < 10) return '0' + x.toString();
3176 else return x.toString();
3179 // user function to signal us the DOM has been
3181 NETDATA.updatedDom = function() {
3182 NETDATA.options.updated_dom = true;
3185 NETDATA.ready = function(callback) {
3186 NETDATA.options.pauseCallback = callback;
3189 NETDATA.pause = function(callback) {
3190 if(NETDATA.options.pause === true)
3193 NETDATA.options.pauseCallback = callback;
3196 NETDATA.unpause = function() {
3197 NETDATA.options.pauseCallback = null;
3198 NETDATA.options.updated_dom = true;
3199 NETDATA.options.pause = false;
3202 // ----------------------------------------------------------------------------------------------------------------
3204 // this is purely sequencial charts refresher
3205 // it is meant to be autonomous
3206 NETDATA.chartRefresherNoParallel = function(index) {
3207 if(NETDATA.options.debug.mail_loop === true)
3208 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3210 if(NETDATA.options.updated_dom === true) {
3211 // the dom has been updated
3212 // get the dom parts again
3213 NETDATA.parseDom(NETDATA.chartRefresher);
3216 if(index >= NETDATA.options.targets.length) {
3217 if(NETDATA.options.debug.main_loop === true)
3218 console.log('waiting to restart main loop...');
3220 NETDATA.options.auto_refresher_fast_weight = 0;
3222 setTimeout(function() {
3223 NETDATA.chartRefresher();
3224 }, NETDATA.options.current.idle_between_loops);
3227 var state = NETDATA.options.targets[index];
3229 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3230 if(NETDATA.options.debug.main_loop === true)
3231 console.log('fast rendering...');
3233 state.autoRefresh(function() {
3234 NETDATA.chartRefresherNoParallel(++index);
3238 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3239 NETDATA.options.auto_refresher_fast_weight = 0;
3241 setTimeout(function() {
3242 state.autoRefresh(function() {
3243 NETDATA.chartRefresherNoParallel(++index);
3245 }, NETDATA.options.current.idle_between_charts);
3250 // this is part of the parallel refresher
3251 // its cause is to refresh sequencially all the charts
3252 // that depend on chart library initialization
3253 // it will call the parallel refresher back
3254 // as soon as it sees a chart that its chart library
3256 NETDATA.chartRefresher_uninitialized = function() {
3257 if(NETDATA.options.updated_dom === true) {
3258 // the dom has been updated
3259 // get the dom parts again
3260 NETDATA.parseDom(NETDATA.chartRefresher);
3264 if(NETDATA.options.sequencial.length === 0)
3265 NETDATA.chartRefresher();
3267 var state = NETDATA.options.sequencial.pop();
3268 if(state.library.initialized === true)
3269 NETDATA.chartRefresher();
3271 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3275 NETDATA.chartRefresherWaitTime = function() {
3276 return NETDATA.options.current.idle_parallel_loops;
3279 // the default refresher
3280 // it will create 2 sets of charts:
3281 // - the ones that can be refreshed in parallel
3282 // - the ones that depend on something else
3283 // the first set will be executed in parallel
3284 // the second will be given to NETDATA.chartRefresher_uninitialized()
3285 NETDATA.chartRefresher = function() {
3286 if(NETDATA.options.pause === true) {
3287 // console.log('auto-refresher is paused');
3288 setTimeout(NETDATA.chartRefresher,
3289 NETDATA.chartRefresherWaitTime());
3293 if(typeof NETDATA.options.pauseCallback === 'function') {
3294 // console.log('auto-refresher is calling pauseCallback');
3295 NETDATA.options.pause = true;
3296 NETDATA.options.pauseCallback();
3297 NETDATA.chartRefresher();
3301 if(NETDATA.options.current.parallel_refresher === false) {
3302 NETDATA.chartRefresherNoParallel(0);
3306 if(NETDATA.options.updated_dom === true) {
3307 // the dom has been updated
3308 // get the dom parts again
3309 NETDATA.parseDom(NETDATA.chartRefresher);
3313 var parallel = new Array();
3314 var targets = NETDATA.options.targets;
3315 var len = targets.length;
3318 state = targets[len];
3319 if(state.isVisible() === false || state.running === true)
3322 if(state.library.initialized === false) {
3323 if(state.library.enabled === true) {
3324 state.library.initialize(NETDATA.chartRefresher);
3328 state.error('chart library "' + state.library_name + '" is not enabled.');
3332 parallel.unshift(state);
3335 if(parallel.length > 0) {
3336 // this will execute the jobs in parallel
3337 $(parallel).each(function() {
3342 // run the next refresh iteration
3343 setTimeout(NETDATA.chartRefresher,
3344 NETDATA.chartRefresherWaitTime());
3347 NETDATA.parseDom = function(callback) {
3348 NETDATA.options.last_page_scroll = new Date().getTime();
3349 NETDATA.options.updated_dom = false;
3351 var targets = $('div[data-netdata]'); //.filter(':visible');
3353 if(NETDATA.options.debug.main_loop === true)
3354 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3356 NETDATA.options.targets = new Array();
3357 var len = targets.length;
3359 // the initialization will take care of sizing
3360 // and the "loading..." message
3361 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3364 if(typeof callback === 'function') callback();
3367 // this is the main function - where everything starts
3368 NETDATA.start = function() {
3369 // this should be called only once
3371 NETDATA.options.page_is_visible = true;
3373 $(window).blur(function() {
3374 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3375 NETDATA.options.page_is_visible = false;
3376 if(NETDATA.options.debug.focus === true)
3377 console.log('Lost Focus!');
3381 $(window).focus(function() {
3382 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3383 NETDATA.options.page_is_visible = true;
3384 if(NETDATA.options.debug.focus === true)
3385 console.log('Focus restored!');
3389 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3390 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3391 NETDATA.options.page_is_visible = false;
3392 if(NETDATA.options.debug.focus === true)
3393 console.log('Document has no focus!');
3397 // bootstrap tab switching
3398 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3400 // bootstrap modal switching
3401 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3402 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3404 // bootstrap collapse switching
3405 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3406 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3408 NETDATA.parseDom(NETDATA.chartRefresher);
3410 // Registry initialization
3411 setTimeout(NETDATA.registry.init, 1000);
3414 // ----------------------------------------------------------------------------------------------------------------
3417 NETDATA.peityInitialize = function(callback) {
3418 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3420 url: NETDATA.peity_js,
3423 xhrFields: { withCredentials: true } // required for the cookie
3426 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3429 NETDATA.chartLibraries.peity.enabled = false;
3430 NETDATA.error(100, NETDATA.peity_js);
3432 .always(function() {
3433 if(typeof callback === "function")
3438 NETDATA.chartLibraries.peity.enabled = false;
3439 if(typeof callback === "function")
3444 NETDATA.peityChartUpdate = function(state, data) {
3445 state.peity_instance.innerHTML = data.result;
3447 if(state.peity_options.stroke !== state.chartColors()[0]) {
3448 state.peity_options.stroke = state.chartColors()[0];
3449 if(state.chart.chart_type === 'line')
3450 state.peity_options.fill = NETDATA.themes.current.background;
3452 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3455 $(state.peity_instance).peity('line', state.peity_options);
3459 NETDATA.peityChartCreate = function(state, data) {
3460 state.peity_instance = document.createElement('div');
3461 state.element_chart.appendChild(state.peity_instance);
3463 var self = $(state.element);
3464 state.peity_options = {
3465 stroke: NETDATA.themes.current.foreground,
3466 strokeWidth: self.data('peity-strokewidth') || 1,
3467 width: state.chartWidth(),
3468 height: state.chartHeight(),
3469 fill: NETDATA.themes.current.foreground
3472 NETDATA.peityChartUpdate(state, data);
3476 // ----------------------------------------------------------------------------------------------------------------
3479 NETDATA.sparklineInitialize = function(callback) {
3480 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3482 url: NETDATA.sparkline_js,
3485 xhrFields: { withCredentials: true } // required for the cookie
3488 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3491 NETDATA.chartLibraries.sparkline.enabled = false;
3492 NETDATA.error(100, NETDATA.sparkline_js);
3494 .always(function() {
3495 if(typeof callback === "function")
3500 NETDATA.chartLibraries.sparkline.enabled = false;
3501 if(typeof callback === "function")
3506 NETDATA.sparklineChartUpdate = function(state, data) {
3507 state.sparkline_options.width = state.chartWidth();
3508 state.sparkline_options.height = state.chartHeight();
3510 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3514 NETDATA.sparklineChartCreate = function(state, data) {
3515 var self = $(state.element);
3516 var type = self.data('sparkline-type') || 'line';
3517 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3518 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3519 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3520 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3521 var composite = self.data('sparkline-composite') || undefined;
3522 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3523 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3524 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3525 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3526 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3527 var spotColor = self.data('sparkline-spotcolor') || undefined;
3528 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3529 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3530 var spotRadius = self.data('sparkline-spotradius') || undefined;
3531 var valueSpots = self.data('sparkline-valuespots') || undefined;
3532 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3533 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3534 var lineWidth = self.data('sparkline-linewidth') || undefined;
3535 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3536 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3537 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3538 var xvalues = self.data('sparkline-xvalues') || undefined;
3539 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3540 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3541 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3542 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3543 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3544 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3545 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3546 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3547 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3548 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3549 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3550 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3551 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3552 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3553 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3554 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3555 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3556 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3557 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3558 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3559 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3560 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3562 if(spotColor === 'disable') spotColor='';
3563 if(minSpotColor === 'disable') minSpotColor='';
3564 if(maxSpotColor === 'disable') maxSpotColor='';
3566 state.sparkline_options = {
3568 lineColor: lineColor,
3569 fillColor: fillColor,
3570 chartRangeMin: chartRangeMin,
3571 chartRangeMax: chartRangeMax,
3572 composite: composite,
3573 enableTagOptions: enableTagOptions,
3574 tagOptionPrefix: tagOptionPrefix,
3575 tagValuesAttribute: tagValuesAttribute,
3576 disableHiddenCheck: disableHiddenCheck,
3577 defaultPixelsPerValue: defaultPixelsPerValue,
3578 spotColor: spotColor,
3579 minSpotColor: minSpotColor,
3580 maxSpotColor: maxSpotColor,
3581 spotRadius: spotRadius,
3582 valueSpots: valueSpots,
3583 highlightSpotColor: highlightSpotColor,
3584 highlightLineColor: highlightLineColor,
3585 lineWidth: lineWidth,
3586 normalRangeMin: normalRangeMin,
3587 normalRangeMax: normalRangeMax,
3588 drawNormalOnTop: drawNormalOnTop,
3590 chartRangeClip: chartRangeClip,
3591 chartRangeMinX: chartRangeMinX,
3592 chartRangeMaxX: chartRangeMaxX,
3593 disableInteraction: disableInteraction,
3594 disableTooltips: disableTooltips,
3595 disableHighlight: disableHighlight,
3596 highlightLighten: highlightLighten,
3597 highlightColor: highlightColor,
3598 tooltipContainer: tooltipContainer,
3599 tooltipClassname: tooltipClassname,
3600 tooltipChartTitle: state.title,
3601 tooltipFormat: tooltipFormat,
3602 tooltipPrefix: tooltipPrefix,
3603 tooltipSuffix: tooltipSuffix,
3604 tooltipSkipNull: tooltipSkipNull,
3605 tooltipValueLookups: tooltipValueLookups,
3606 tooltipFormatFieldlist: tooltipFormatFieldlist,
3607 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3608 numberFormatter: numberFormatter,
3609 numberDigitGroupSep: numberDigitGroupSep,
3610 numberDecimalMark: numberDecimalMark,
3611 numberDigitGroupCount: numberDigitGroupCount,
3612 animatedZooms: animatedZooms,
3613 width: state.chartWidth(),
3614 height: state.chartHeight()
3617 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3621 // ----------------------------------------------------------------------------------------------------------------
3628 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3629 if(after < state.netdata_first)
3630 after = state.netdata_first;
3632 if(before > state.netdata_last)
3633 before = state.netdata_last;
3635 state.setMode('zoom');
3636 state.globalSelectionSyncStop();
3637 state.globalSelectionSyncDelay();
3638 state.dygraph_user_action = true;
3639 state.dygraph_force_zoom = true;
3640 state.updateChartPanOrZoom(after, before);
3641 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3644 NETDATA.dygraphSetSelection = function(state, t) {
3645 if(typeof state.dygraph_instance !== 'undefined') {
3646 var r = state.calculateRowForTime(t);
3648 state.dygraph_instance.setSelection(r);
3650 state.dygraph_instance.clearSelection();
3651 state.legendShowUndefined();
3658 NETDATA.dygraphClearSelection = function(state, t) {
3659 if(typeof state.dygraph_instance !== 'undefined') {
3660 state.dygraph_instance.clearSelection();
3665 NETDATA.dygraphSmoothInitialize = function(callback) {
3667 url: NETDATA.dygraph_smooth_js,
3670 xhrFields: { withCredentials: true } // required for the cookie
3673 NETDATA.dygraph.smooth = true;
3674 smoothPlotter.smoothing = 0.3;
3677 NETDATA.dygraph.smooth = false;
3679 .always(function() {
3680 if(typeof callback === "function")
3685 NETDATA.dygraphInitialize = function(callback) {
3686 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3688 url: NETDATA.dygraph_js,
3691 xhrFields: { withCredentials: true } // required for the cookie
3694 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3697 NETDATA.chartLibraries.dygraph.enabled = false;
3698 NETDATA.error(100, NETDATA.dygraph_js);
3700 .always(function() {
3701 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3702 NETDATA.dygraphSmoothInitialize(callback);
3703 else if(typeof callback === "function")
3708 NETDATA.chartLibraries.dygraph.enabled = false;
3709 if(typeof callback === "function")
3714 NETDATA.dygraphChartUpdate = function(state, data) {
3715 var dygraph = state.dygraph_instance;
3717 if(typeof dygraph === 'undefined')
3718 return NETDATA.dygraphChartCreate(state, data);
3720 // when the chart is not visible, and hidden
3721 // if there is a window resize, dygraph detects
3722 // its element size as 0x0.
3723 // this will make it re-appear properly
3725 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3729 file: data.result.data,
3730 colors: state.chartColors(),
3731 labels: data.result.labels,
3732 labelsDivWidth: state.chartWidth() - 70,
3733 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3736 if(state.dygraph_force_zoom === true) {
3737 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3738 state.log('dygraphChartUpdate() forced zoom update');
3740 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3741 options.valueRange = state.dygraph_options.valueRange;
3742 options.isZoomedIgnoreProgrammaticZoom = true;
3743 state.dygraph_force_zoom = false;
3745 else if(state.current.name !== 'auto') {
3746 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3747 state.log('dygraphChartUpdate() loose update');
3749 options.valueRange = state.dygraph_options.valueRange;
3752 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3753 state.log('dygraphChartUpdate() strict update');
3755 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3756 options.valueRange = state.dygraph_options.valueRange;
3757 options.isZoomedIgnoreProgrammaticZoom = true;
3760 if(state.dygraph_smooth_eligible === true) {
3761 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3762 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3763 NETDATA.dygraphChartCreate(state, data);
3768 dygraph.updateOptions(options);
3770 state.dygraph_last_rendered = new Date().getTime();
3774 NETDATA.dygraphChartCreate = function(state, data) {
3775 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3776 state.log('dygraphChartCreate()');
3778 var self = $(state.element);
3780 var chart_type = state.chart.chart_type;
3781 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3782 chart_type = self.data('dygraph-type') || chart_type;
3784 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3785 smooth = self.data('dygraph-smooth') || smooth;
3787 if(NETDATA.dygraph.smooth === false)
3790 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3791 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3793 state.dygraph_options = {
3794 colors: self.data('dygraph-colors') || state.chartColors(),
3796 // leave a few pixels empty on the right of the chart
3797 rightGap: self.data('dygraph-rightgap') || 5,
3798 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3799 showRoller: self.data('dygraph-showroller') || false,
3801 title: self.data('dygraph-title') || state.title,
3802 titleHeight: self.data('dygraph-titleheight') || 19,
3804 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3805 labels: data.result.labels,
3806 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3807 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3808 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3809 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3810 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3813 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3814 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3816 includeZero: self.data('dygraph-includezero') || false,
3817 xRangePad: self.data('dygraph-xrangepad') || 0,
3818 yRangePad: self.data('dygraph-yrangepad') || 1,
3820 valueRange: self.data('dygraph-valuerange') || null,
3822 ylabel: state.units,
3823 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3825 // the function to plot the chart
3828 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3829 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3830 strokePattern: self.data('dygraph-strokepattern') || undefined,
3832 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3833 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3834 drawPoints: self.data('dygraph-drawpoints') || false,
3836 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3837 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3839 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3840 pointSize: self.data('dygraph-pointsize') || 1,
3842 // enabling this makes the chart with little square lines
3843 stepPlot: self.data('dygraph-stepplot') || false,
3845 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3846 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3847 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3849 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3850 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3851 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3852 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3854 drawAxis: self.data('dygraph-drawaxis') || true,
3855 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3856 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3857 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3859 drawGrid: self.data('dygraph-drawgrid') || true,
3860 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3861 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3862 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3863 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3864 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3866 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3867 sigFigs: self.data('dygraph-sigfigs') || null,
3868 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3869 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3871 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3872 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3873 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3875 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3876 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3880 ticker: Dygraph.dateTicker,
3881 axisLabelFormatter: function (d, gran) {
3882 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3884 valueFormatter: function (ms) {
3885 var d = new Date(ms);
3886 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3887 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3892 valueFormatter: function (x) {
3893 // we format legends with the state object
3894 // no need to do anything here
3895 // return (Math.round(x*100) / 100).toLocaleString();
3896 // return state.legendFormatValue(x);
3901 legendFormatter: function(data) {
3902 var elements = state.element_legend_childs;
3904 // if the hidden div is not there
3905 // we are not managing the legend
3906 if(elements.hidden === null) return;
3908 if (typeof data.x !== 'undefined') {
3909 state.legendSetDate(data.x);
3910 var i = data.series.length;
3912 var series = data.series[i];
3913 if(!series.isVisible) continue;
3914 state.legendSetLabelValue(series.label, series.y);
3920 drawCallback: function(dygraph, is_initial) {
3921 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3922 state.dygraph_user_action = false;
3924 var x_range = dygraph.xAxisRange();
3925 var after = Math.round(x_range[0]);
3926 var before = Math.round(x_range[1]);
3928 if(NETDATA.options.debug.dygraph === true)
3929 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3931 if(before <= state.netdata_last && after >= state.netdata_first)
3932 state.updateChartPanOrZoom(after, before);
3935 zoomCallback: function(minDate, maxDate, yRanges) {
3936 if(NETDATA.options.debug.dygraph === true)
3937 state.log('dygraphZoomCallback()');
3939 state.globalSelectionSyncStop();
3940 state.globalSelectionSyncDelay();
3941 state.setMode('zoom');
3943 // refresh it to the greatest possible zoom level
3944 state.dygraph_user_action = true;
3945 state.dygraph_force_zoom = true;
3946 state.updateChartPanOrZoom(minDate, maxDate);
3948 highlightCallback: function(event, x, points, row, seriesName) {
3949 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3950 state.log('dygraphHighlightCallback()');
3954 // there is a bug in dygraph when the chart is zoomed enough
3955 // the time it thinks is selected is wrong
3956 // here we calculate the time t based on the row number selected
3958 var t = state.data_after + row * state.data_update_every;
3959 // 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);
3961 state.globalSelectionSync(x);
3963 // fix legend zIndex using the internal structures of dygraph legend module
3964 // this works, but it is a hack!
3965 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3967 unhighlightCallback: function(event) {
3968 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3969 state.log('dygraphUnhighlightCallback()');
3971 state.unpauseChart();
3972 state.globalSelectionSyncStop();
3974 interactionModel : {
3975 mousedown: function(event, dygraph, context) {
3976 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3977 state.log('interactionModel.mousedown()');
3979 state.dygraph_user_action = true;
3980 state.globalSelectionSyncStop();
3982 if(NETDATA.options.debug.dygraph === true)
3983 state.log('dygraphMouseDown()');
3985 // Right-click should not initiate a zoom.
3986 if(event.button && event.button === 2) return;
3988 context.initializeMouseDown(event, dygraph, context);
3990 if(event.button && event.button === 1) {
3991 if (event.altKey || event.shiftKey) {
3992 state.setMode('pan');
3993 state.globalSelectionSyncDelay();
3994 Dygraph.startPan(event, dygraph, context);
3997 state.setMode('zoom');
3998 state.globalSelectionSyncDelay();
3999 Dygraph.startZoom(event, dygraph, context);
4003 if (event.altKey || event.shiftKey) {
4004 state.setMode('zoom');
4005 state.globalSelectionSyncDelay();
4006 Dygraph.startZoom(event, dygraph, context);
4009 state.setMode('pan');
4010 state.globalSelectionSyncDelay();
4011 Dygraph.startPan(event, dygraph, context);
4015 mousemove: function(event, dygraph, context) {
4016 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4017 state.log('interactionModel.mousemove()');
4019 if(context.isPanning) {
4020 state.dygraph_user_action = true;
4021 state.globalSelectionSyncStop();
4022 state.globalSelectionSyncDelay();
4023 state.setMode('pan');
4024 Dygraph.movePan(event, dygraph, context);
4026 else if(context.isZooming) {
4027 state.dygraph_user_action = true;
4028 state.globalSelectionSyncStop();
4029 state.globalSelectionSyncDelay();
4030 state.setMode('zoom');
4031 Dygraph.moveZoom(event, dygraph, context);
4034 mouseup: function(event, dygraph, context) {
4035 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4036 state.log('interactionModel.mouseup()');
4038 if (context.isPanning) {
4039 state.dygraph_user_action = true;
4040 state.globalSelectionSyncDelay();
4041 Dygraph.endPan(event, dygraph, context);
4043 else if (context.isZooming) {
4044 state.dygraph_user_action = true;
4045 state.globalSelectionSyncDelay();
4046 Dygraph.endZoom(event, dygraph, context);
4049 click: function(event, dygraph, context) {
4050 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4051 state.log('interactionModel.click()');
4053 event.preventDefault();
4055 dblclick: function(event, dygraph, context) {
4056 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4057 state.log('interactionModel.dblclick()');
4058 NETDATA.resetAllCharts(state);
4060 mousewheel: function(event, dygraph, context) {
4061 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4062 state.log('interactionModel.mousewheel()');
4064 // Take the offset of a mouse event on the dygraph canvas and
4065 // convert it to a pair of percentages from the bottom left.
4066 // (Not top left, bottom is where the lower value is.)
4067 function offsetToPercentage(g, offsetX, offsetY) {
4068 // This is calculating the pixel offset of the leftmost date.
4069 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4070 var yar0 = g.yAxisRange(0);
4072 // This is calculating the pixel of the higest value. (Top pixel)
4073 var yOffset = g.toDomCoords(null, yar0[1])[1];
4075 // x y w and h are relative to the corner of the drawing area,
4076 // so that the upper corner of the drawing area is (0, 0).
4077 var x = offsetX - xOffset;
4078 var y = offsetY - yOffset;
4080 // This is computing the rightmost pixel, effectively defining the
4082 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4084 // This is computing the lowest pixel, effectively defining the height.
4085 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4087 // Percentage from the left.
4088 var xPct = w === 0 ? 0 : (x / w);
4089 // Percentage from the top.
4090 var yPct = h === 0 ? 0 : (y / h);
4092 // The (1-) part below changes it from "% distance down from the top"
4093 // to "% distance up from the bottom".
4094 return [xPct, (1-yPct)];
4097 // Adjusts [x, y] toward each other by zoomInPercentage%
4098 // Split it so the left/bottom axis gets xBias/yBias of that change and
4099 // tight/top gets (1-xBias)/(1-yBias) of that change.
4101 // If a bias is missing it splits it down the middle.
4102 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4103 xBias = xBias || 0.5;
4104 yBias = yBias || 0.5;
4106 function adjustAxis(axis, zoomInPercentage, bias) {
4107 var delta = axis[1] - axis[0];
4108 var increment = delta * zoomInPercentage;
4109 var foo = [increment * bias, increment * (1-bias)];
4111 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4114 var yAxes = g.yAxisRanges();
4116 for (var i = 0; i < yAxes.length; i++) {
4117 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4120 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4123 if(event.altKey || event.shiftKey) {
4124 state.dygraph_user_action = true;
4126 state.globalSelectionSyncStop();
4127 state.globalSelectionSyncDelay();
4129 // http://dygraphs.com/gallery/interaction-api.js
4130 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4131 var percentage = normal / 50;
4133 if (!(event.offsetX && event.offsetY)){
4134 event.offsetX = event.layerX - event.target.offsetLeft;
4135 event.offsetY = event.layerY - event.target.offsetTop;
4138 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4139 var xPct = percentages[0];
4140 var yPct = percentages[1];
4142 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4144 var after = new_x_range[0];
4145 var before = new_x_range[1];
4147 var first = state.netdata_first + state.data_update_every;
4148 var last = state.netdata_last + state.data_update_every;
4151 after -= (before - last);
4158 state.setMode('zoom');
4159 if(state.updateChartPanOrZoom(after, before) === true)
4160 dygraph.updateOptions({ dateWindow: [ after, before ] });
4162 event.preventDefault();
4165 touchstart: function(event, dygraph, context) {
4166 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4167 state.log('interactionModel.touchstart()');
4169 state.dygraph_user_action = true;
4170 state.setMode('zoom');
4173 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4175 // we overwrite the touch directions at the end, to overwrite
4176 // the internal default of dygraphs
4177 context.touchDirections = { x: true, y: false };
4179 state.dygraph_last_touch_start = new Date().getTime();
4180 state.dygraph_last_touch_move = 0;
4182 if(typeof event.touches[0].pageX === 'number')
4183 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4185 state.dygraph_last_touch_page_x = 0;
4187 touchmove: function(event, dygraph, context) {
4188 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4189 state.log('interactionModel.touchmove()');
4191 state.dygraph_user_action = true;
4192 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4194 state.dygraph_last_touch_move = new Date().getTime();
4196 touchend: function(event, dygraph, context) {
4197 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4198 state.log('interactionModel.touchend()');
4200 state.dygraph_user_action = true;
4201 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4203 // if it didn't move, it is a selection
4204 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4205 // internal api of dygraphs
4206 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4207 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4208 if(NETDATA.dygraphSetSelection(state, t) === true)
4209 state.globalSelectionSync(t);
4212 // if it was double tap within double click time, reset the charts
4213 var now = new Date().getTime();
4214 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4215 if(state.dygraph_last_touch_move === 0) {
4216 var dt = now - state.dygraph_last_touch_end;
4217 if(dt <= NETDATA.options.current.double_click_speed)
4218 NETDATA.resetAllCharts(state);
4222 // remember the timestamp of the last touch end
4223 state.dygraph_last_touch_end = now;
4228 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4229 state.dygraph_options.drawGrid = false;
4230 state.dygraph_options.drawAxis = false;
4231 state.dygraph_options.title = undefined;
4232 state.dygraph_options.units = undefined;
4233 state.dygraph_options.ylabel = undefined;
4234 state.dygraph_options.yLabelWidth = 0;
4235 state.dygraph_options.labelsDivWidth = 120;
4236 state.dygraph_options.labelsDivStyles.width = '120px';
4237 state.dygraph_options.labelsSeparateLines = true;
4238 state.dygraph_options.rightGap = 0;
4239 state.dygraph_options.yRangePad = 1;
4242 if(smooth === true) {
4243 state.dygraph_smooth_eligible = true;
4245 if(NETDATA.options.current.smooth_plot === true)
4246 state.dygraph_options.plotter = smoothPlotter;
4248 else state.dygraph_smooth_eligible = false;
4250 state.dygraph_instance = new Dygraph(state.element_chart,
4251 data.result.data, state.dygraph_options);
4253 state.dygraph_force_zoom = false;
4254 state.dygraph_user_action = false;
4255 state.dygraph_last_rendered = new Date().getTime();
4259 // ----------------------------------------------------------------------------------------------------------------
4262 NETDATA.morrisInitialize = function(callback) {
4263 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4265 // morris requires raphael
4266 if(!NETDATA.chartLibraries.raphael.initialized) {
4267 if(NETDATA.chartLibraries.raphael.enabled) {
4268 NETDATA.raphaelInitialize(function() {
4269 NETDATA.morrisInitialize(callback);
4273 NETDATA.chartLibraries.morris.enabled = false;
4274 if(typeof callback === "function")
4279 NETDATA._loadCSS(NETDATA.morris_css);
4282 url: NETDATA.morris_js,
4285 xhrFields: { withCredentials: true } // required for the cookie
4288 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4291 NETDATA.chartLibraries.morris.enabled = false;
4292 NETDATA.error(100, NETDATA.morris_js);
4294 .always(function() {
4295 if(typeof callback === "function")
4301 NETDATA.chartLibraries.morris.enabled = false;
4302 if(typeof callback === "function")
4307 NETDATA.morrisChartUpdate = function(state, data) {
4308 state.morris_instance.setData(data.result.data);
4312 NETDATA.morrisChartCreate = function(state, data) {
4314 state.morris_options = {
4315 element: state.element_chart.id,
4316 data: data.result.data,
4318 ykeys: data.dimension_names,
4319 labels: data.dimension_names,
4325 continuousLine: false,
4326 behaveLikeLine: false
4329 if(state.chart.chart_type === 'line')
4330 state.morris_instance = new Morris.Line(state.morris_options);
4332 else if(state.chart.chart_type === 'area') {
4333 state.morris_options.behaveLikeLine = true;
4334 state.morris_instance = new Morris.Area(state.morris_options);
4337 state.morris_instance = new Morris.Area(state.morris_options);
4342 // ----------------------------------------------------------------------------------------------------------------
4345 NETDATA.raphaelInitialize = function(callback) {
4346 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4348 url: NETDATA.raphael_js,
4351 xhrFields: { withCredentials: true } // required for the cookie
4354 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4357 NETDATA.chartLibraries.raphael.enabled = false;
4358 NETDATA.error(100, NETDATA.raphael_js);
4360 .always(function() {
4361 if(typeof callback === "function")
4366 NETDATA.chartLibraries.raphael.enabled = false;
4367 if(typeof callback === "function")
4372 NETDATA.raphaelChartUpdate = function(state, data) {
4373 $(state.element_chart).raphael(data.result, {
4374 width: state.chartWidth(),
4375 height: state.chartHeight()
4381 NETDATA.raphaelChartCreate = function(state, data) {
4382 $(state.element_chart).raphael(data.result, {
4383 width: state.chartWidth(),
4384 height: state.chartHeight()
4390 // ----------------------------------------------------------------------------------------------------------------
4393 NETDATA.c3Initialize = function(callback) {
4394 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4397 if(!NETDATA.chartLibraries.d3.initialized) {
4398 if(NETDATA.chartLibraries.d3.enabled) {
4399 NETDATA.d3Initialize(function() {
4400 NETDATA.c3Initialize(callback);
4404 NETDATA.chartLibraries.c3.enabled = false;
4405 if(typeof callback === "function")
4410 NETDATA._loadCSS(NETDATA.c3_css);
4416 xhrFields: { withCredentials: true } // required for the cookie
4419 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4422 NETDATA.chartLibraries.c3.enabled = false;
4423 NETDATA.error(100, NETDATA.c3_js);
4425 .always(function() {
4426 if(typeof callback === "function")
4432 NETDATA.chartLibraries.c3.enabled = false;
4433 if(typeof callback === "function")
4438 NETDATA.c3ChartUpdate = function(state, data) {
4439 state.c3_instance.destroy();
4440 return NETDATA.c3ChartCreate(state, data);
4442 //state.c3_instance.load({
4443 // rows: data.result,
4450 NETDATA.c3ChartCreate = function(state, data) {
4452 state.element_chart.id = 'c3-' + state.uuid;
4453 // console.log('id = ' + state.element_chart.id);
4455 state.c3_instance = c3.generate({
4456 bindto: '#' + state.element_chart.id,
4458 width: state.chartWidth(),
4459 height: state.chartHeight()
4462 pattern: state.chartColors()
4467 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4473 format: function(x) {
4474 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4501 // console.log(state.c3_instance);
4506 // ----------------------------------------------------------------------------------------------------------------
4509 NETDATA.d3Initialize = function(callback) {
4510 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4515 xhrFields: { withCredentials: true } // required for the cookie
4518 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4521 NETDATA.chartLibraries.d3.enabled = false;
4522 NETDATA.error(100, NETDATA.d3_js);
4524 .always(function() {
4525 if(typeof callback === "function")
4530 NETDATA.chartLibraries.d3.enabled = false;
4531 if(typeof callback === "function")
4536 NETDATA.d3ChartUpdate = function(state, data) {
4540 NETDATA.d3ChartCreate = function(state, data) {
4544 // ----------------------------------------------------------------------------------------------------------------
4547 NETDATA.googleInitialize = function(callback) {
4548 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4550 url: NETDATA.google_js,
4553 xhrFields: { withCredentials: true } // required for the cookie
4556 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4557 google.load('visualization', '1.1', {
4558 'packages': ['corechart', 'controls'],
4559 'callback': callback
4563 NETDATA.chartLibraries.google.enabled = false;
4564 NETDATA.error(100, NETDATA.google_js);
4565 if(typeof callback === "function")
4570 NETDATA.chartLibraries.google.enabled = false;
4571 if(typeof callback === "function")
4576 NETDATA.googleChartUpdate = function(state, data) {
4577 var datatable = new google.visualization.DataTable(data.result);
4578 state.google_instance.draw(datatable, state.google_options);
4582 NETDATA.googleChartCreate = function(state, data) {
4583 var datatable = new google.visualization.DataTable(data.result);
4585 state.google_options = {
4586 colors: state.chartColors(),
4588 // do not set width, height - the chart resizes itself
4589 //width: state.chartWidth(),
4590 //height: state.chartHeight(),
4595 // title: "Time of Day",
4596 // format:'HH:mm:ss',
4597 viewWindowMode: 'maximized',
4609 viewWindowMode: 'pretty',
4624 focusTarget: 'category',
4631 titlePosition: 'out',
4642 curveType: 'function',
4647 switch(state.chart.chart_type) {
4649 state.google_options.vAxis.viewWindowMode = 'maximized';
4650 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4651 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4655 state.google_options.isStacked = true;
4656 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4657 state.google_options.vAxis.viewWindowMode = 'maximized';
4658 state.google_options.vAxis.minValue = null;
4659 state.google_options.vAxis.maxValue = null;
4660 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4665 state.google_options.lineWidth = 2;
4666 state.google_instance = new google.visualization.LineChart(state.element_chart);
4670 state.google_instance.draw(datatable, state.google_options);
4674 // ----------------------------------------------------------------------------------------------------------------
4676 NETDATA.percentFromValueMax = function(value, max) {
4677 if(value === null) value = 0;
4678 if(max < value) max = value;
4682 pcent = Math.round(value * 100 / max);
4683 if(pcent === 0 && value > 0) pcent = 1;
4689 // ----------------------------------------------------------------------------------------------------------------
4692 NETDATA.easypiechartInitialize = function(callback) {
4693 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4695 url: NETDATA.easypiechart_js,
4698 xhrFields: { withCredentials: true } // required for the cookie
4701 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4704 NETDATA.chartLibraries.easypiechart.enabled = false;
4705 NETDATA.error(100, NETDATA.easypiechart_js);
4707 .always(function() {
4708 if(typeof callback === "function")
4713 NETDATA.chartLibraries.easypiechart.enabled = false;
4714 if(typeof callback === "function")
4719 NETDATA.easypiechartClearSelection = function(state) {
4720 if(typeof state.easyPieChartEvent !== 'undefined') {
4721 if(state.easyPieChartEvent.timer !== null)
4722 clearTimeout(state.easyPieChartEvent.timer);
4724 state.easyPieChartEvent.timer = null;
4727 if(state.isAutoRefreshable() === true && state.data !== null) {
4728 NETDATA.easypiechartChartUpdate(state, state.data);
4731 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4732 state.easyPieChart_instance.update(0);
4734 state.easyPieChart_instance.enableAnimation();
4739 NETDATA.easypiechartSetSelection = function(state, t) {
4740 if(state.timeIsVisible(t) !== true)
4741 return NETDATA.easypiechartClearSelection(state);
4743 var slot = state.calculateRowForTime(t);
4744 if(slot < 0 || slot >= state.data.result.length)
4745 return NETDATA.easypiechartClearSelection(state);
4747 if(typeof state.easyPieChartEvent === 'undefined') {
4748 state.easyPieChartEvent = {
4755 var value = state.data.result[state.data.result.length - 1 - slot];
4756 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4757 var pcent = NETDATA.percentFromValueMax(value, max);
4759 state.easyPieChartEvent.value = value;
4760 state.easyPieChartEvent.pcent = pcent;
4761 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4763 if(state.easyPieChartEvent.timer === null) {
4764 state.easyPieChart_instance.disableAnimation();
4766 state.easyPieChartEvent.timer = setTimeout(function() {
4767 state.easyPieChartEvent.timer = null;
4768 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4769 }, NETDATA.options.current.charts_selection_animation_delay);
4775 NETDATA.easypiechartChartUpdate = function(state, data) {
4776 var value, max, pcent;
4778 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4784 value = data.result[0];
4785 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4786 pcent = NETDATA.percentFromValueMax(value, max);
4789 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4790 state.easyPieChart_instance.update(pcent);
4794 NETDATA.easypiechartChartCreate = function(state, data) {
4795 var self = $(state.element);
4796 var chart = $(state.element_chart);
4798 var value = data.result[0];
4799 var max = self.data('easypiechart-max-value') || null;
4800 var adjust = self.data('easypiechart-adjust') || null;
4804 state.easyPieChartMax = null;
4807 state.easyPieChartMax = max;
4809 var pcent = NETDATA.percentFromValueMax(value, max);
4811 chart.data('data-percent', pcent);
4815 case 'width': size = state.chartHeight(); break;
4816 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4817 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4819 default: size = state.chartWidth(); break;
4821 state.element.style.width = size + 'px';
4822 state.element.style.height = size + 'px';
4824 var stroke = Math.floor(size / 22);
4825 if(stroke < 3) stroke = 2;
4827 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4828 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4829 state.easyPieChartLabel = document.createElement('span');
4830 state.easyPieChartLabel.className = 'easyPieChartLabel';
4831 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4832 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4833 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4834 state.element_chart.appendChild(state.easyPieChartLabel);
4836 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4837 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4838 state.easyPieChartTitle = document.createElement('span');
4839 state.easyPieChartTitle.className = 'easyPieChartTitle';
4840 state.easyPieChartTitle.innerHTML = state.title;
4841 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4842 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4843 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4844 state.element_chart.appendChild(state.easyPieChartTitle);
4846 var unitfontsize = Math.round(titlefontsize * 0.9);
4847 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4848 state.easyPieChartUnits = document.createElement('span');
4849 state.easyPieChartUnits.className = 'easyPieChartUnits';
4850 state.easyPieChartUnits.innerHTML = state.units;
4851 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4852 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4853 state.element_chart.appendChild(state.easyPieChartUnits);
4855 chart.easyPieChart({
4856 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4857 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4858 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4859 scaleLength: self.data('easypiechart-scalelength') || 5,
4860 lineCap: self.data('easypiechart-linecap') || 'round',
4861 lineWidth: self.data('easypiechart-linewidth') || stroke,
4862 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4863 size: self.data('easypiechart-size') || size,
4864 rotate: self.data('easypiechart-rotate') || 0,
4865 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4866 easing: self.data('easypiechart-easing') || undefined
4869 // when we just re-create the chart
4870 // do not animate the first update
4872 if(typeof state.easyPieChart_instance !== 'undefined')
4875 state.easyPieChart_instance = chart.data('easyPieChart');
4876 if(animate === false) state.easyPieChart_instance.disableAnimation();
4877 state.easyPieChart_instance.update(pcent);
4878 if(animate === false) state.easyPieChart_instance.enableAnimation();
4882 // ----------------------------------------------------------------------------------------------------------------
4885 NETDATA.gaugeInitialize = function(callback) {
4886 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4888 url: NETDATA.gauge_js,
4891 xhrFields: { withCredentials: true } // required for the cookie
4894 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4897 NETDATA.chartLibraries.gauge.enabled = false;
4898 NETDATA.error(100, NETDATA.gauge_js);
4900 .always(function() {
4901 if(typeof callback === "function")
4906 NETDATA.chartLibraries.gauge.enabled = false;
4907 if(typeof callback === "function")
4912 NETDATA.gaugeAnimation = function(state, status) {
4915 if(typeof status === 'boolean' && status === false)
4917 else if(typeof status === 'number')
4920 state.gauge_instance.animationSpeed = speed;
4921 state.___gaugeOld__.speed = speed;
4924 NETDATA.gaugeSet = function(state, value, min, max) {
4925 if(typeof value !== 'number') value = 0;
4926 if(typeof min !== 'number') min = 0;
4927 if(typeof max !== 'number') max = 0;
4928 if(value > max) max = value;
4929 if(value < min) min = value;
4938 // gauge.js has an issue if the needle
4939 // is smaller than min or larger than max
4940 // when we set the new values
4941 // the needle will go crazy
4943 // to prevent it, we always feed it
4944 // with a percentage, so that the needle
4945 // is always between min and max
4946 var pcent = (value - min) * 100 / (max - min);
4948 // these should never happen
4949 if(pcent < 0) pcent = 0;
4950 if(pcent > 100) pcent = 100;
4952 state.gauge_instance.set(pcent);
4954 state.___gaugeOld__.value = value;
4955 state.___gaugeOld__.min = min;
4956 state.___gaugeOld__.max = max;
4959 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4960 if(state.___gaugeOld__.valueLabel !== value) {
4961 state.___gaugeOld__.valueLabel = value;
4962 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4964 if(state.___gaugeOld__.minLabel !== min) {
4965 state.___gaugeOld__.minLabel = min;
4966 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4968 if(state.___gaugeOld__.maxLabel !== max) {
4969 state.___gaugeOld__.maxLabel = max;
4970 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4974 NETDATA.gaugeClearSelection = function(state) {
4975 if(typeof state.gaugeEvent !== 'undefined') {
4976 if(state.gaugeEvent.timer !== null)
4977 clearTimeout(state.gaugeEvent.timer);
4979 state.gaugeEvent.timer = null;
4982 if(state.isAutoRefreshable() === true && state.data !== null) {
4983 NETDATA.gaugeChartUpdate(state, state.data);
4986 NETDATA.gaugeAnimation(state, false);
4987 NETDATA.gaugeSet(state, null, null, null);
4988 NETDATA.gaugeSetLabels(state, null, null, null);
4991 NETDATA.gaugeAnimation(state, true);
4995 NETDATA.gaugeSetSelection = function(state, t) {
4996 if(state.timeIsVisible(t) !== true)
4997 return NETDATA.gaugeClearSelection(state);
4999 var slot = state.calculateRowForTime(t);
5000 if(slot < 0 || slot >= state.data.result.length)
5001 return NETDATA.gaugeClearSelection(state);
5003 if(typeof state.gaugeEvent === 'undefined') {
5004 state.gaugeEvent = {
5012 var value = state.data.result[state.data.result.length - 1 - slot];
5013 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5016 state.gaugeEvent.value = value;
5017 state.gaugeEvent.max = max;
5018 state.gaugeEvent.min = min;
5019 NETDATA.gaugeSetLabels(state, value, min, max);
5021 if(state.gaugeEvent.timer === null) {
5022 NETDATA.gaugeAnimation(state, false);
5024 state.gaugeEvent.timer = setTimeout(function() {
5025 state.gaugeEvent.timer = null;
5026 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5027 }, NETDATA.options.current.charts_selection_animation_delay);
5033 NETDATA.gaugeChartUpdate = function(state, data) {
5034 var value, min, max;
5036 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5040 NETDATA.gaugeSetLabels(state, null, null, null);
5043 value = data.result[0];
5045 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5046 if(value > max) max = value;
5047 NETDATA.gaugeSetLabels(state, value, min, max);
5050 NETDATA.gaugeSet(state, value, min, max);
5054 NETDATA.gaugeChartCreate = function(state, data) {
5055 var self = $(state.element);
5056 // var chart = $(state.element_chart);
5058 var value = data.result[0];
5059 var max = self.data('gauge-max-value') || null;
5060 var adjust = self.data('gauge-adjust') || null;
5061 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5062 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5063 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5064 var stopColor = self.data('gauge-stop-color') || void 0;
5065 var generateGradient = self.data('gauge-generate-gradient') || false;
5069 state.gaugeMax = null;
5072 state.gaugeMax = max;
5074 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5076 // case 'width': width = height * ratio; break;
5078 // default: height = width / ratio; break;
5080 //state.element.style.width = width.toString() + 'px';
5081 //state.element.style.height = height.toString() + 'px';
5086 lines: 12, // The number of lines to draw
5087 angle: 0.15, // The length of each line
5088 lineWidth: 0.44, // 0.44 The line thickness
5090 length: 0.8, // 0.9 The radius of the inner circle
5091 strokeWidth: 0.035, // The rotation offset
5092 color: pointerColor // Fill color
5094 colorStart: startColor, // Colors
5095 colorStop: stopColor, // just experiment with them
5096 strokeColor: strokeColor, // to see which ones work best for you
5098 generateGradient: (generateGradient === true)?true:false,
5102 if (generateGradient.constructor === Array) {
5104 // data-gauge-generate-gradient="[0, 50, 100]"
5105 // data-gauge-gradient-percent-color-0="#FFFFFF"
5106 // data-gauge-gradient-percent-color-50="#999900"
5107 // data-gauge-gradient-percent-color-100="#000000"
5109 options.percentColors = new Array();
5110 var len = generateGradient.length;
5112 var pcent = generateGradient[len];
5113 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5114 if(color !== false) {
5115 var a = new Array();
5118 options.percentColors.unshift(a);
5121 if(options.percentColors.length === 0)
5122 delete options.percentColors;
5124 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5125 options.percentColors = [
5126 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5127 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5128 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5129 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5130 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5131 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5132 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5133 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5134 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5135 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5136 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5139 state.gauge_canvas = document.createElement('canvas');
5140 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5141 state.gauge_canvas.className = 'gaugeChart';
5142 state.gauge_canvas.width = width;
5143 state.gauge_canvas.height = height;
5144 state.element_chart.appendChild(state.gauge_canvas);
5146 var valuefontsize = Math.floor(height / 6);
5147 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5148 state.gaugeChartLabel = document.createElement('span');
5149 state.gaugeChartLabel.className = 'gaugeChartLabel';
5150 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5151 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5152 state.element_chart.appendChild(state.gaugeChartLabel);
5154 var titlefontsize = Math.round(valuefontsize / 2);
5156 state.gaugeChartTitle = document.createElement('span');
5157 state.gaugeChartTitle.className = 'gaugeChartTitle';
5158 state.gaugeChartTitle.innerHTML = state.title;
5159 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5160 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5161 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5162 state.element_chart.appendChild(state.gaugeChartTitle);
5164 var unitfontsize = Math.round(titlefontsize * 0.9);
5165 state.gaugeChartUnits = document.createElement('span');
5166 state.gaugeChartUnits.className = 'gaugeChartUnits';
5167 state.gaugeChartUnits.innerHTML = state.units;
5168 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5169 state.element_chart.appendChild(state.gaugeChartUnits);
5171 state.gaugeChartMin = document.createElement('span');
5172 state.gaugeChartMin.className = 'gaugeChartMin';
5173 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5174 state.element_chart.appendChild(state.gaugeChartMin);
5176 state.gaugeChartMax = document.createElement('span');
5177 state.gaugeChartMax.className = 'gaugeChartMax';
5178 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5179 state.element_chart.appendChild(state.gaugeChartMax);
5181 // when we just re-create the chart
5182 // do not animate the first update
5184 if(typeof state.gauge_instance !== 'undefined')
5187 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5189 state.___gaugeOld__ = {
5198 // we will always feed a percentage
5199 state.gauge_instance.minValue = 0;
5200 state.gauge_instance.maxValue = 100;
5202 NETDATA.gaugeAnimation(state, animate);
5203 NETDATA.gaugeSet(state, value, 0, max);
5204 NETDATA.gaugeSetLabels(state, value, 0, max);
5205 NETDATA.gaugeAnimation(state, true);
5209 // ----------------------------------------------------------------------------------------------------------------
5210 // Charts Libraries Registration
5212 NETDATA.chartLibraries = {
5214 initialize: NETDATA.dygraphInitialize,
5215 create: NETDATA.dygraphChartCreate,
5216 update: NETDATA.dygraphChartUpdate,
5217 resize: function(state) {
5218 if(typeof state.dygraph_instance.resize === 'function')
5219 state.dygraph_instance.resize();
5221 setSelection: NETDATA.dygraphSetSelection,
5222 clearSelection: NETDATA.dygraphClearSelection,
5223 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5226 format: function(state) { return 'json'; },
5227 options: function(state) { return 'ms|flip'; },
5228 legend: function(state) {
5229 if(this.isSparkline(state) === false)
5230 return 'right-side';
5234 autoresize: function(state) { return true; },
5235 max_updates_to_recreate: function(state) { return 5000; },
5236 track_colors: function(state) { return true; },
5237 pixels_per_point: function(state) {
5238 if(this.isSparkline(state) === false)
5244 isSparkline: function(state) {
5245 if(typeof state.dygraph_sparkline === 'undefined') {
5246 var t = $(state.element).data('dygraph-theme');
5247 if(t === 'sparkline')
5248 state.dygraph_sparkline = true;
5250 state.dygraph_sparkline = false;
5252 return state.dygraph_sparkline;
5256 initialize: NETDATA.sparklineInitialize,
5257 create: NETDATA.sparklineChartCreate,
5258 update: NETDATA.sparklineChartUpdate,
5260 setSelection: undefined, // function(state, t) { return true; },
5261 clearSelection: undefined, // function(state) { return true; },
5262 toolboxPanAndZoom: null,
5265 format: function(state) { return 'array'; },
5266 options: function(state) { return 'flip|abs'; },
5267 legend: function(state) { return null; },
5268 autoresize: function(state) { return false; },
5269 max_updates_to_recreate: function(state) { return 5000; },
5270 track_colors: function(state) { return false; },
5271 pixels_per_point: function(state) { return 3; }
5274 initialize: NETDATA.peityInitialize,
5275 create: NETDATA.peityChartCreate,
5276 update: NETDATA.peityChartUpdate,
5278 setSelection: undefined, // function(state, t) { return true; },
5279 clearSelection: undefined, // function(state) { return true; },
5280 toolboxPanAndZoom: null,
5283 format: function(state) { return 'ssvcomma'; },
5284 options: function(state) { return 'null2zero|flip|abs'; },
5285 legend: function(state) { return null; },
5286 autoresize: function(state) { return false; },
5287 max_updates_to_recreate: function(state) { return 5000; },
5288 track_colors: function(state) { return false; },
5289 pixels_per_point: function(state) { return 3; }
5292 initialize: NETDATA.morrisInitialize,
5293 create: NETDATA.morrisChartCreate,
5294 update: NETDATA.morrisChartUpdate,
5296 setSelection: undefined, // function(state, t) { return true; },
5297 clearSelection: undefined, // function(state) { return true; },
5298 toolboxPanAndZoom: null,
5301 format: function(state) { return 'json'; },
5302 options: function(state) { return 'objectrows|ms'; },
5303 legend: function(state) { return null; },
5304 autoresize: function(state) { return false; },
5305 max_updates_to_recreate: function(state) { return 50; },
5306 track_colors: function(state) { return false; },
5307 pixels_per_point: function(state) { return 15; }
5310 initialize: NETDATA.googleInitialize,
5311 create: NETDATA.googleChartCreate,
5312 update: NETDATA.googleChartUpdate,
5314 setSelection: undefined, //function(state, t) { return true; },
5315 clearSelection: undefined, //function(state) { return true; },
5316 toolboxPanAndZoom: null,
5319 format: function(state) { return 'datatable'; },
5320 options: function(state) { return ''; },
5321 legend: function(state) { return null; },
5322 autoresize: function(state) { return false; },
5323 max_updates_to_recreate: function(state) { return 300; },
5324 track_colors: function(state) { return false; },
5325 pixels_per_point: function(state) { return 4; }
5328 initialize: NETDATA.raphaelInitialize,
5329 create: NETDATA.raphaelChartCreate,
5330 update: NETDATA.raphaelChartUpdate,
5332 setSelection: undefined, // function(state, t) { return true; },
5333 clearSelection: undefined, // function(state) { return true; },
5334 toolboxPanAndZoom: null,
5337 format: function(state) { return 'json'; },
5338 options: function(state) { return ''; },
5339 legend: function(state) { return null; },
5340 autoresize: function(state) { return false; },
5341 max_updates_to_recreate: function(state) { return 5000; },
5342 track_colors: function(state) { return false; },
5343 pixels_per_point: function(state) { return 3; }
5346 initialize: NETDATA.c3Initialize,
5347 create: NETDATA.c3ChartCreate,
5348 update: NETDATA.c3ChartUpdate,
5350 setSelection: undefined, // function(state, t) { return true; },
5351 clearSelection: undefined, // function(state) { return true; },
5352 toolboxPanAndZoom: null,
5355 format: function(state) { return 'csvjsonarray'; },
5356 options: function(state) { return 'milliseconds'; },
5357 legend: function(state) { return null; },
5358 autoresize: function(state) { return false; },
5359 max_updates_to_recreate: function(state) { return 5000; },
5360 track_colors: function(state) { return false; },
5361 pixels_per_point: function(state) { return 15; }
5364 initialize: NETDATA.d3Initialize,
5365 create: NETDATA.d3ChartCreate,
5366 update: NETDATA.d3ChartUpdate,
5368 setSelection: undefined, // function(state, t) { return true; },
5369 clearSelection: undefined, // function(state) { return true; },
5370 toolboxPanAndZoom: null,
5373 format: function(state) { return 'json'; },
5374 options: function(state) { return ''; },
5375 legend: function(state) { return null; },
5376 autoresize: function(state) { return false; },
5377 max_updates_to_recreate: function(state) { return 5000; },
5378 track_colors: function(state) { return false; },
5379 pixels_per_point: function(state) { return 3; }
5382 initialize: NETDATA.easypiechartInitialize,
5383 create: NETDATA.easypiechartChartCreate,
5384 update: NETDATA.easypiechartChartUpdate,
5386 setSelection: NETDATA.easypiechartSetSelection,
5387 clearSelection: NETDATA.easypiechartClearSelection,
5388 toolboxPanAndZoom: null,
5391 format: function(state) { return 'array'; },
5392 options: function(state) { return 'absolute'; },
5393 legend: function(state) { return null; },
5394 autoresize: function(state) { return false; },
5395 max_updates_to_recreate: function(state) { return 5000; },
5396 track_colors: function(state) { return true; },
5397 pixels_per_point: function(state) { return 3; },
5401 initialize: NETDATA.gaugeInitialize,
5402 create: NETDATA.gaugeChartCreate,
5403 update: NETDATA.gaugeChartUpdate,
5405 setSelection: NETDATA.gaugeSetSelection,
5406 clearSelection: NETDATA.gaugeClearSelection,
5407 toolboxPanAndZoom: null,
5410 format: function(state) { return 'array'; },
5411 options: function(state) { return 'absolute'; },
5412 legend: function(state) { return null; },
5413 autoresize: function(state) { return false; },
5414 max_updates_to_recreate: function(state) { return 5000; },
5415 track_colors: function(state) { return true; },
5416 pixels_per_point: function(state) { return 3; },
5421 NETDATA.registerChartLibrary = function(library, url) {
5422 if(NETDATA.options.debug.libraries === true)
5423 console.log("registering chart library: " + library);
5425 NETDATA.chartLibraries[library].url = url;
5426 NETDATA.chartLibraries[library].initialized = true;
5427 NETDATA.chartLibraries[library].enabled = true;
5430 // ----------------------------------------------------------------------------------------------------------------
5431 // Load required JS libraries and CSS
5433 NETDATA.requiredJs = [
5435 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5436 isAlreadyLoaded: function() {
5437 // check if bootstrap is loaded
5438 if(typeof $().emulateTransitionEnd == 'function')
5441 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5449 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5450 isAlreadyLoaded: function() { return false; }
5453 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5454 isAlreadyLoaded: function() { return false; }
5458 NETDATA.requiredCSS = [
5460 url: NETDATA.themes.current.bootstrap_css,
5461 isAlreadyLoaded: function() {
5462 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5469 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5470 isAlreadyLoaded: function() { return false; }
5473 url: NETDATA.themes.current.dashboard_css,
5474 isAlreadyLoaded: function() { return false; }
5477 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5478 isAlreadyLoaded: function() { return false; }
5482 NETDATA.loadRequiredJs = function(index, callback) {
5483 if(index >= NETDATA.requiredJs.length) {
5484 if(typeof callback === 'function')
5489 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5490 NETDATA.loadRequiredJs(++index, callback);
5494 if(NETDATA.options.debug.main_loop === true)
5495 console.log('loading ' + NETDATA.requiredJs[index].url);
5498 url: NETDATA.requiredJs[index].url,
5501 xhrFields: { withCredentials: true } // required for the cookie
5503 .success(function() {
5504 if(NETDATA.options.debug.main_loop === true)
5505 console.log('loaded ' + NETDATA.requiredJs[index].url);
5507 NETDATA.loadRequiredJs(++index, callback);
5510 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5514 NETDATA.loadRequiredCSS = function(index) {
5515 if(index >= NETDATA.requiredCSS.length)
5518 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5519 NETDATA.loadRequiredCSS(++index);
5523 if(NETDATA.options.debug.main_loop === true)
5524 console.log('loading ' + NETDATA.requiredCSS[index].url);
5526 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5527 NETDATA.loadRequiredCSS(++index);
5531 // ----------------------------------------------------------------------------------------------------------------
5532 // Registry of netdata hosts
5534 NETDATA.registry = {
5535 server: null, // the netdata registry server
5536 person_guid: null, // the unique ID of this browser / user
5537 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5538 hostname: null, // the hostname of the netdata server that served dashboard.js
5539 machines: null, // the user's other URLs
5540 machines_array: null, // the user's other URLs in an array
5543 parsePersonUrls: function(person_urls) {
5544 // console.log(person_urls);
5545 NETDATA.registry.person_urls = person_urls;
5548 NETDATA.registry.machines = {};
5549 NETDATA.registry.machines_array = new Array();
5551 var now = new Date().getTime();
5552 var apu = person_urls;
5555 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5556 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5562 accesses: apu[i][3],
5564 alternate_urls: new Array()
5566 obj.alternate_urls.push(apu[i][1]);
5568 NETDATA.registry.machines[apu[i][0]] = obj;
5569 NETDATA.registry.machines_array.push(obj);
5572 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5574 var pu = NETDATA.registry.machines[apu[i][0]];
5575 if(pu.last_t < apu[i][2]) {
5577 pu.last_t = apu[i][2];
5578 pu.name = apu[i][4];
5580 pu.accesses += apu[i][3];
5581 pu.alternate_urls.push(apu[i][1]);
5586 if(typeof netdataRegistryCallback === 'function')
5587 netdataRegistryCallback(NETDATA.registry.machines_array);
5591 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5594 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5596 NETDATA.registry.server = data.registry;
5597 NETDATA.registry.machine_guid = data.machine_guid;
5598 NETDATA.registry.hostname = data.hostname;
5600 NETDATA.registry.access(2, function (person_urls) {
5601 NETDATA.registry.parsePersonUrls(person_urls);
5608 hello: function(host, callback) {
5609 while(host.slice(-1) === '/')
5610 host = host.substring(0, host.length - 1);
5612 // send HELLO to a netdata server:
5613 // 1. verifies the server is reachable
5614 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5616 url: host + '/api/v1/registry?action=hello',
5619 xhrFields: { withCredentials: true } // required for the cookie
5621 .done(function(data) {
5622 if(typeof data.status !== 'string' || data.status !== 'ok') {
5623 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5627 if(typeof callback === 'function')
5631 NETDATA.error(407, host);
5633 if(typeof callback === 'function')
5638 access: function(max_redirects, callback) {
5639 // send ACCESS to a netdata registry:
5640 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5641 // 2. it responds with a list of netdata servers we know
5642 // the registry identifies us using a cookie it sets the first time we access it
5643 // the registry may respond with a redirect URL to send us to another registry
5645 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),
5648 xhrFields: { withCredentials: true } // required for the cookie
5650 .done(function(data) {
5651 var redirect = null;
5652 if(typeof data.registry === 'string')
5653 redirect = data.registry;
5655 if(typeof data.status !== 'string' || data.status !== 'ok') {
5656 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5661 if(redirect !== null && max_redirects > 0) {
5662 NETDATA.registry.server = redirect;
5663 NETDATA.registry.access(max_redirects - 1, callback);
5666 if(typeof callback === 'function')
5671 if(typeof data.person_guid === 'string')
5672 NETDATA.registry.person_guid = data.person_guid;
5674 if(typeof callback === 'function')
5675 callback(data.urls);
5679 NETDATA.error(410, NETDATA.registry.server);
5681 if(typeof callback === 'function')
5686 delete: function(delete_url, callback) {
5687 // send DELETE to a netdata registry:
5689 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),
5692 xhrFields: { withCredentials: true } // required for the cookie
5694 .done(function(data) {
5695 if(typeof data.status !== 'string' || data.status !== 'ok') {
5696 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5700 if(typeof callback === 'function')
5704 NETDATA.error(412, NETDATA.registry.server);
5706 if(typeof callback === 'function')
5711 switch: function(new_person_guid, callback) {
5714 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,
5717 xhrFields: { withCredentials: true } // required for the cookie
5719 .done(function(data) {
5720 if(typeof data.status !== 'string' || data.status !== 'ok') {
5721 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5725 if(typeof callback === 'function')
5729 NETDATA.error(414, NETDATA.registry.server);
5731 if(typeof callback === 'function')
5737 // ----------------------------------------------------------------------------------------------------------------
5740 NETDATA.errorReset();
5741 NETDATA.loadRequiredCSS(0);
5743 NETDATA._loadjQuery(function() {
5744 NETDATA.loadRequiredJs(0, function() {
5745 if(typeof $().emulateTransitionEnd !== 'function') {
5746 // bootstrap is not available
5747 NETDATA.options.current.show_help = false;
5750 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5751 if(NETDATA.options.debug.main_loop === true)
5752 console.log('starting chart refresh thread');
5759 // window.NETDATA = NETDATA;
5760 // })(window, document);