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
19 // You can also set the default netdata server, using the following.
20 // When this variable is not set, we assume the page is hosted on your
21 // netdata server already.
22 // var netdataServer = "http://yourhost:19999"; // set your NetData server
24 //(function(window, document, undefined) {
26 // ------------------------------------------------------------------------
27 // compatibility fixes
29 // fix IE issue with console
30 if(!window.console) { window.console = { log: function(){} }; }
32 // if string.endsWith is not defined, define it
33 if(typeof String.prototype.endsWith !== 'function') {
34 String.prototype.endsWith = function(s) {
35 if(s.length > this.length) return false;
36 return this.slice(-s.length) === s;
40 // if string.startsWith is not defined, define it
41 if(typeof String.prototype.startsWith !== 'function') {
42 String.prototype.startsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(s.length) === s;
49 var NETDATA = window.NETDATA || {};
51 // ----------------------------------------------------------------------------------------------------------------
52 // Detect the netdata server
54 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
55 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
56 NETDATA._scriptSource = function() {
59 if(typeof document.currentScript !== 'undefined') {
60 script = document.currentScript;
63 var all_scripts = document.getElementsByTagName('script');
64 script = all_scripts[all_scripts.length - 1];
67 if (typeof script.getAttribute.length !== 'undefined')
70 script = script.getAttribute('src', -1);
75 if(typeof netdataServer !== 'undefined')
76 NETDATA.serverDefault = netdataServer;
78 var s = NETDATA._scriptSource();
79 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
81 console.log('WARNING: Cannot detect the URL of the netdata server.');
82 NETDATA.serverDefault = null;
86 if(NETDATA.serverDefault === null)
87 NETDATA.serverDefault = '';
88 else if(NETDATA.serverDefault.slice(-1) !== '/')
89 NETDATA.serverDefault += '/';
91 // default URLs for all the external files we need
92 // make them RELATIVE so that the whole thing can also be
93 // installed under a web server
94 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
95 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
96 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
97 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
98 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
99 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
100 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
101 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
102 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
103 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
104 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
105 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
106 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
107 NETDATA.google_js = 'https://www.google.com/jsapi';
111 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
112 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
113 background: '#FFFFFF',
114 foreground: '#000000',
117 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
118 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
119 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
120 '#329262', '#3B3EAC' ],
121 easypiechart_track: '#f0f0f0',
122 easypiechart_scale: '#dfe0e0',
123 gauge_pointer: '#C0C0C0',
124 gauge_stroke: '#F0F0F0',
125 gauge_gradient: false
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
130 background: '#272b30',
131 foreground: '#C8C8C8',
134 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
135 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
136 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
137 '#a6a479', '#a66da8' ],
139 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
140 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
141 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
142 '#329262', '#3B3EFF' ],
143 easypiechart_track: '#373b40',
144 easypiechart_scale: '#373b40',
145 gauge_pointer: '#474b50',
146 gauge_stroke: '#373b40',
147 gauge_gradient: false
151 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
152 NETDATA.themes.current = NETDATA.themes[netdataTheme];
154 NETDATA.themes.current = NETDATA.themes.white;
156 NETDATA.colors = NETDATA.themes.current.colors;
158 // these are the colors Google Charts are using
159 // we have them here to attempt emulate their look and feel on the other chart libraries
160 // http://there4.io/2012/05/02/google-chart-color-list/
161 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
162 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
163 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
165 // an alternative set
166 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
167 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
168 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
170 // ----------------------------------------------------------------------------------------------------------------
171 // the defaults for all charts
173 // if the user does not specify any of these, the following will be used
175 NETDATA.chartDefaults = {
176 host: NETDATA.serverDefault, // the server to get data from
177 width: '100%', // the chart width - can be null
178 height: '100%', // the chart height - can be null
179 min_width: null, // the chart minimum width - can be null
180 library: 'dygraph', // the graphing library to use
181 method: 'average', // the grouping method
182 before: 0, // panning
183 after: -600, // panning
184 pixels_per_point: 1, // the detail of the chart
185 fill_luminance: 0.8 // luminance of colors in solit areas
188 // ----------------------------------------------------------------------------------------------------------------
192 pauseCallback: null, // a callback when we are really paused
194 pause: false, // when enabled we don't auto-refresh the charts
196 targets: null, // an array of all the state objects that are
197 // currently active (independently of their
198 // viewport visibility)
200 updated_dom: true, // when true, the DOM has been updated with
201 // new elements we have to check.
203 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
204 // rendering charts continiously.
205 // used with .current.fast_render_timeframe
207 page_is_visible: true, // when true, this page is visible
209 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
210 // auto-refresher for some time (when a chart is
211 // performing pan or zoom, we need to stop refreshing
212 // all other charts, to have the maximum speed for
213 // rendering the chart that is panned or zoomed).
214 // Used with .current.global_pan_sync_time
216 last_resized: new Date().getTime(), // the timestamp of the last resize request
218 last_page_scroll: 0, // the timestamp the last time the page was scrolled
220 // the current profile
221 // we may have many...
223 pixels_per_point: 1, // the minimum pixels per point for all charts
224 // increase this to speed javascript up
225 // each chart library has its own limit too
226 // the max of this and the chart library is used
227 // the final is calculated every time, so a change
228 // here will have immediate effect on the next chart
231 idle_between_charts: 100, // ms - how much time to wait between chart updates
233 fast_render_timeframe: 200, // ms - render continously until this time of continious
234 // rendering has been reached
235 // this setting is used to make it render e.g. 10
236 // charts at once, sleep idle_between_charts time
237 // and continue for another 10 charts.
239 idle_between_loops: 500, // ms - if all charts have been updated, wait this
240 // time before starting again.
242 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
244 idle_lost_focus: 500, // ms - when the window does not have focus, check
245 // if focus has been regained, every this time
247 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
248 // autorefreshing of charts is paused for this amount
251 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
252 // of time before setting up synchronized selections
255 sync_selection: true, // enable or disable selection sync
257 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
259 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
261 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
263 update_only_visible: true, // enable or disable visibility management
265 parallel_refresher: true, // enable parallel refresh of charts
267 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
269 destroy_on_hide: false, // destroy charts when they are not visible
271 show_help: true, // when enabled the charts will show some help
272 show_help_delay_show_ms: 500,
273 show_help_delay_hide_ms: 0,
275 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
277 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
278 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
280 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
282 smooth_plot: true, // enable smooth plot, where possible
284 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
286 color_fill_opacity_line: 1.0,
287 color_fill_opacity_area: 0.2,
288 color_fill_opacity_stacked: 0.8,
290 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
291 pan_and_zoom_factor_multiplier_control: 2.0,
292 pan_and_zoom_factor_multiplier_shift: 3.0,
293 pan_and_zoom_factor_multiplier_alt: 4.0,
295 setOptionCallback: function() { ; }
303 chart_data_url: false,
304 chart_errors: false, // FIXME
312 NETDATA.statistics = {
315 refreshes_active_max: 0
319 // ----------------------------------------------------------------------------------------------------------------
320 // local storage options
322 NETDATA.localStorage = {
325 callback: {} // only used for resetting back to defaults
328 NETDATA.localStorageGet = function(key, def, callback) {
331 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
332 NETDATA.localStorage.default[key.toString()] = def;
333 NETDATA.localStorage.callback[key.toString()] = callback;
336 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
338 // console.log('localStorage: loading "' + key.toString() + '"');
339 ret = localStorage.getItem(key.toString());
340 if(ret === null || ret === 'undefined') {
341 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
342 localStorage.setItem(key.toString(), JSON.stringify(def));
346 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
347 ret = JSON.parse(ret);
348 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
352 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
357 if(typeof ret === 'undefined' || ret === 'undefined') {
358 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
362 NETDATA.localStorage.current[key.toString()] = ret;
366 NETDATA.localStorageSet = function(key, value, callback) {
367 if(typeof value === 'undefined' || value === 'undefined') {
368 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
371 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372 NETDATA.localStorage.default[key.toString()] = value;
373 NETDATA.localStorage.current[key.toString()] = value;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
378 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
380 localStorage.setItem(key.toString(), JSON.stringify(value));
383 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
387 NETDATA.localStorage.current[key.toString()] = value;
391 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
393 if(typeof obj[i] === 'object') {
394 //console.log('object ' + prefix + '.' + i.toString());
395 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
399 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
403 NETDATA.setOption = function(key, value) {
404 if(key.toString() === 'setOptionCallback') {
405 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
406 NETDATA.options.current[key.toString()] = value;
407 NETDATA.options.current.setOptionCallback();
410 else if(NETDATA.options.current[key.toString()] !== value) {
411 var name = 'options.' + key.toString();
413 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
414 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
416 //console.log(NETDATA.localStorage);
417 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
418 //console.log(NETDATA.options);
419 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
421 if(typeof NETDATA.options.current.setOptionCallback === 'function')
422 NETDATA.options.current.setOptionCallback();
428 NETDATA.getOption = function(key) {
429 return NETDATA.options.current[key.toString()];
432 // read settings from local storage
433 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
435 // always start with this option enabled.
436 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
438 NETDATA.resetOptions = function() {
439 for(var i in NETDATA.localStorage.default) {
440 var a = i.split('.');
442 if(a[0] === 'options') {
443 if(a[1] === 'setOptionCallback') continue;
444 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
445 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
447 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
449 else if(a[0] === 'chart_heights') {
450 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
451 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
457 // ----------------------------------------------------------------------------------------------------------------
459 if(NETDATA.options.debug.main_loop === true)
460 console.log('welcome to NETDATA');
462 NETDATA.onresize = function() {
463 NETDATA.options.last_resized = new Date().getTime();
467 NETDATA.onscroll = function() {
468 // console.log('onscroll');
470 NETDATA.options.last_page_scroll = new Date().getTime();
471 if(NETDATA.options.targets === null) return;
473 // when the user scrolls he sees that we have
474 // hidden all the not-visible charts
475 // using this little function we try to switch
476 // the charts back to visible quickly
477 var targets = NETDATA.options.targets;
478 var len = targets.length;
479 while(len--) targets[len].isVisible();
482 window.onresize = NETDATA.onresize;
483 window.onscroll = NETDATA.onscroll;
485 // ----------------------------------------------------------------------------------------------------------------
488 NETDATA.errorCodes = {
489 100: { message: "Cannot load chart library", alert: true },
490 101: { message: "Cannot load jQuery", alert: true },
491 402: { message: "Chart library not found", alert: false },
492 403: { message: "Chart library not enabled/is failed", alert: false },
493 404: { message: "Chart not found", alert: false },
494 405: { message: "Cannot download charts index from server", alert: true },
495 406: { message: "Invalid charts index downloaded from server", alert: true },
496 407: { message: "Cannot HELLO netdata server", alert: false },
497 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
498 409: { message: "Cannot ACCESS netdata registry", alert: false },
499 410: { message: "Netdata registry ACCESS failed", alert: false },
500 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
501 412: { message: "Netdata registry DELETE failed", alert: false },
502 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
503 414: { message: "Netdata registry SWITCH failed", alert: false }
505 NETDATA.errorLast = {
511 NETDATA.error = function(code, msg) {
512 NETDATA.errorLast.code = code;
513 NETDATA.errorLast.message = msg;
514 NETDATA.errorLast.datetime = new Date().getTime();
516 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
519 if(typeof netdataErrorCallback === 'function') {
520 ret = netdataErrorCallback('system', code, msg);
523 if(ret && NETDATA.errorCodes[code].alert)
524 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
527 NETDATA.errorReset = function() {
528 NETDATA.errorLast.code = 0;
529 NETDATA.errorLast.message = "You are doing fine!";
530 NETDATA.errorLast.datetime = 0;
533 // ----------------------------------------------------------------------------------------------------------------
536 // When multiple charts need the same chart, we avoid downloading it
537 // multiple times (and having it in browser memory multiple time)
538 // by using this registry.
540 // Every time we download a chart definition, we save it here with .add()
541 // Then we try to get it back with .get(). If that fails, we download it.
543 NETDATA.chartRegistry = {
546 fixid: function(id) {
547 return id.replace(/:/g, "_").replace(/\//g, "_");
550 add: function(host, id, data) {
551 host = this.fixid(host);
554 if(typeof this.charts[host] === 'undefined')
555 this.charts[host] = {};
557 //console.log('added ' + host + '/' + id);
558 this.charts[host][id] = data;
561 get: function(host, id) {
562 host = this.fixid(host);
565 if(typeof this.charts[host] === 'undefined')
568 if(typeof this.charts[host][id] === 'undefined')
571 //console.log('cached ' + host + '/' + id);
572 return this.charts[host][id];
575 downloadAll: function(host, callback) {
576 while(host.slice(-1) === '/')
577 host = host.substring(0, host.length - 1);
582 url: host + '/api/v1/charts',
586 .done(function(data) {
588 var h = NETDATA.chartRegistry.fixid(host);
589 self.charts[h] = data.charts;
591 else NETDATA.error(406, host + '/api/v1/charts');
593 if(typeof callback === 'function')
597 NETDATA.error(405, host + '/api/v1/charts');
599 if(typeof callback === 'function')
605 // ----------------------------------------------------------------------------------------------------------------
606 // Global Pan and Zoom on charts
608 // Using this structure are synchronize all the charts, so that
609 // when you pan or zoom one, all others are automatically refreshed
610 // to the same timespan.
612 NETDATA.globalPanAndZoom = {
613 seq: 0, // timestamp ms
614 // every time a chart is panned or zoomed
615 // we set the timestamp here
616 // then we use it as a sequence number
617 // to find if other charts are syncronized
620 master: null, // the master chart (state), to which all others
623 force_before_ms: null, // the timespan to sync all other charts
624 force_after_ms: null,
627 setMaster: function(state, after, before) {
628 if(NETDATA.options.current.sync_pan_and_zoom === false)
631 if(this.master !== null && this.master !== state)
632 this.master.resetChart(true, true);
634 var now = new Date().getTime();
637 this.force_after_ms = after;
638 this.force_before_ms = before;
639 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
643 clearMaster: function() {
644 if(this.master !== null) {
645 var st = this.master;
652 this.force_after_ms = null;
653 this.force_before_ms = null;
654 NETDATA.options.auto_refresher_stop_until = 0;
657 // is the given state the master of the global
658 // pan and zoom sync?
659 isMaster: function(state) {
660 if(this.master === state) return true;
664 // are we currently have a global pan and zoom sync?
665 isActive: function() {
666 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
670 // check if a chart, other than the master
671 // needs to be refreshed, due to the global pan and zoom
672 shouldBeAutoRefreshed: function(state) {
673 if(this.master === null || this.seq === 0)
676 //if(state.needsRecreation())
679 if(state.tm.pan_and_zoom_seq === this.seq)
686 // ----------------------------------------------------------------------------------------------------------------
687 // dimensions selection
690 // move color assignment to dimensions, here
692 dimensionStatus = function(parent, label, name_div, value_div, color) {
693 this.enabled = false;
694 this.parent = parent;
696 this.name_div = null;
697 this.value_div = null;
698 this.color = NETDATA.themes.current.foreground;
700 if(parent.selected_count > parent.unselected_count)
701 this.selected = true;
703 this.selected = false;
705 this.setOptions(name_div, value_div, color);
708 dimensionStatus.prototype.invalidate = function() {
709 this.name_div = null;
710 this.value_div = null;
711 this.enabled = false;
714 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
717 if(this.name_div != name_div) {
718 this.name_div = name_div;
719 this.name_div.title = this.label;
720 this.name_div.style.color = this.color;
721 if(this.selected === false)
722 this.name_div.className = 'netdata-legend-name not-selected';
724 this.name_div.className = 'netdata-legend-name selected';
727 if(this.value_div != value_div) {
728 this.value_div = value_div;
729 this.value_div.title = this.label;
730 this.value_div.style.color = this.color;
731 if(this.selected === false)
732 this.value_div.className = 'netdata-legend-value not-selected';
734 this.value_div.className = 'netdata-legend-value selected';
741 dimensionStatus.prototype.setHandler = function() {
742 if(this.enabled === false) return;
746 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
747 this.name_div.onclick = this.value_div.onclick = function(e) {
749 if(ds.isSelected()) {
751 if(e.shiftKey === true || e.ctrlKey === true) {
752 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
755 if(ds.parent.countSelected() === 0)
756 ds.parent.selectAll();
759 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
760 if(ds.parent.countSelected() === 1) {
761 ds.parent.selectAll();
764 ds.parent.selectNone();
770 // this is not selected
771 if(e.shiftKey === true || e.ctrlKey === true) {
772 // control or shift key is pressed -> select this too
776 // no key is pressed -> select only this
777 ds.parent.selectNone();
782 ds.parent.state.redrawChart();
786 dimensionStatus.prototype.select = function() {
787 if(this.enabled === false) return;
789 this.name_div.className = 'netdata-legend-name selected';
790 this.value_div.className = 'netdata-legend-value selected';
791 this.selected = true;
794 dimensionStatus.prototype.unselect = function() {
795 if(this.enabled === false) return;
797 this.name_div.className = 'netdata-legend-name not-selected';
798 this.value_div.className = 'netdata-legend-value hidden';
799 this.selected = false;
802 dimensionStatus.prototype.isSelected = function() {
803 return(this.enabled === true && this.selected === true);
806 // ----------------------------------------------------------------------------------------------------------------
808 dimensionsVisibility = function(state) {
811 this.dimensions = {};
812 this.selected_count = 0;
813 this.unselected_count = 0;
816 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
817 if(typeof this.dimensions[label] === 'undefined') {
819 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
822 this.dimensions[label].setOptions(name_div, value_div, color);
824 return this.dimensions[label];
827 dimensionsVisibility.prototype.dimensionGet = function(label) {
828 return this.dimensions[label];
831 dimensionsVisibility.prototype.invalidateAll = function() {
832 for(var d in this.dimensions)
833 this.dimensions[d].invalidate();
836 dimensionsVisibility.prototype.selectAll = function() {
837 for(var d in this.dimensions)
838 this.dimensions[d].select();
841 dimensionsVisibility.prototype.countSelected = function() {
843 for(var d in this.dimensions)
844 if(this.dimensions[d].isSelected()) i++;
849 dimensionsVisibility.prototype.selectNone = function() {
850 for(var d in this.dimensions)
851 this.dimensions[d].unselect();
854 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
855 var ret = new Array();
856 this.selected_count = 0;
857 this.unselected_count = 0;
859 for(var i = 0, len = array.length; i < len ; i++) {
860 var ds = this.dimensions[array[i]];
861 if(typeof ds === 'undefined') {
862 // console.log(array[i] + ' is not found');
867 if(ds.isSelected()) {
869 this.selected_count++;
873 this.unselected_count++;
877 if(this.selected_count === 0 && this.unselected_count !== 0) {
879 return this.selected2BooleanArray(array);
886 // ----------------------------------------------------------------------------------------------------------------
887 // global selection sync
889 NETDATA.globalSelectionSync = {
896 if(this.state !== null)
897 this.state.globalSelectionSyncStop();
901 if(this.state !== null) {
902 this.state.globalSelectionSyncDelay();
907 // ----------------------------------------------------------------------------------------------------------------
908 // Our state object, where all per-chart values are stored
910 chartState = function(element) {
911 var self = $(element);
912 this.element = element;
915 // all private functions should use 'that', instead of 'this'
919 * show an error instead of the chart
921 var error = function(msg) {
924 if(typeof netdataErrorCallback === 'function') {
925 ret = netdataErrorCallback('chart', that.id, msg);
929 that.element.innerHTML = that.id + ': ' + msg;
930 that.enabled = false;
931 that.current = that.pan;
935 // GUID - a unique identifier for the chart
936 this.uuid = NETDATA.guid();
938 // string - the name of chart
939 this.id = self.data('netdata');
941 // string - the key for localStorage settings
942 this.settings_id = self.data('id') || null;
944 // the user given dimensions of the element
945 this.width = self.data('width') || NETDATA.chartDefaults.width;
946 this.height = self.data('height') || NETDATA.chartDefaults.height;
948 if(this.settings_id !== null) {
949 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
950 // this is the callback that will be called
951 // if and when the user resets all localStorage variables
954 resizeChartToHeight(height);
958 // string - the netdata server URL, without any path
959 this.host = self.data('host') || NETDATA.chartDefaults.host;
961 // make sure the host does not end with /
962 // all netdata API requests use absolute paths
963 while(this.host.slice(-1) === '/')
964 this.host = this.host.substring(0, this.host.length - 1);
966 // string - the grouping method requested by the user
967 this.method = self.data('method') || NETDATA.chartDefaults.method;
969 // the time-range requested by the user
970 this.after = self.data('after') || NETDATA.chartDefaults.after;
971 this.before = self.data('before') || NETDATA.chartDefaults.before;
973 // the pixels per point requested by the user
974 this.pixels_per_point = self.data('pixels-per-point') || 1;
975 this.points = self.data('points') || null;
977 // the dimensions requested by the user
978 this.dimensions = self.data('dimensions') || null;
980 // the chart library requested by the user
981 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
983 // object - the chart library used
988 this.colors_assigned = {};
989 this.colors_available = null;
991 // the element already created by the user
992 this.element_message = null;
994 // the element with the chart
995 this.element_chart = null;
997 // the element with the legend of the chart (if created by us)
998 this.element_legend = null;
999 this.element_legend_childs = {
1009 this.chart_url = null; // string - the url to download chart info
1010 this.chart = null; // object - the chart as downloaded from the server
1012 this.title = self.data('title') || null; // the title of the chart
1013 this.units = self.data('units') || null; // the units of the chart dimensions
1014 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1016 this.running = false; // boolean - true when the chart is being refreshed now
1017 this.validated = false; // boolean - has the chart been validated?
1018 this.enabled = true; // boolean - is the chart enabled for refresh?
1019 this.paused = false; // boolean - is the chart paused for any reason?
1020 this.selected = false; // boolean - is the chart shown a selection?
1021 this.debug = false; // boolean - console.log() debug info about this chart
1023 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1024 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1025 this.requested_after = null; // milliseconds - the timestamp of the request after param
1026 this.requested_before = null; // milliseconds - the timestamp of the request before param
1027 this.requested_padding = null;
1028 this.view_after = 0;
1029 this.view_before = 0;
1034 force_update_at: 0, // the timestamp to force the update at
1035 force_before_ms: null,
1036 force_after_ms: null
1041 force_update_at: 0, // the timestamp to force the update at
1042 force_before_ms: null,
1043 force_after_ms: null
1048 force_update_at: 0, // the timestamp to force the update at
1049 force_before_ms: null,
1050 force_after_ms: null
1053 // this is a pointer to one of the sub-classes below
1055 this.current = this.auto;
1057 // check the requested library is available
1058 // we don't initialize it here - it will be initialized when
1059 // this chart will be first used
1060 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1061 NETDATA.error(402, that.library_name);
1062 error('chart library "' + that.library_name + '" is not found');
1065 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1066 NETDATA.error(403, that.library_name);
1067 error('chart library "' + that.library_name + '" is not enabled');
1071 that.library = NETDATA.chartLibraries[that.library_name];
1073 // milliseconds - the time the last refresh took
1074 this.refresh_dt_ms = 0;
1076 // if we need to report the rendering speed
1077 // find the element that needs to be updated
1078 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1080 if(refresh_dt_element_name !== null)
1081 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1083 this.refresh_dt_element = null;
1085 this.dimensions_visibility = new dimensionsVisibility(this);
1087 this._updating = false;
1089 // ============================================================================================================
1090 // PRIVATE FUNCTIONS
1092 var createDOM = function() {
1093 if(that.enabled === false) return;
1095 if(that.element_message !== null) that.element_message.innerHTML = '';
1096 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1097 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1099 that.element.innerHTML = '';
1101 that.element_message = document.createElement('div');
1102 that.element_message.className = ' netdata-message hidden';
1103 that.element.appendChild(that.element_message);
1105 that.element_chart = document.createElement('div');
1106 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1107 that.element.appendChild(that.element_chart);
1109 if(that.hasLegend() === true) {
1110 that.element.className = "netdata-container-with-legend";
1111 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1113 that.element_legend = document.createElement('div');
1114 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1115 that.element.appendChild(that.element_legend);
1118 that.element.className = "netdata-container";
1119 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1121 that.element_legend = null;
1123 that.element_legend_childs.series = null;
1125 if(typeof(that.width) === 'string')
1126 $(that.element).css('width', that.width);
1127 else if(typeof(that.width) === 'number')
1128 $(that.element).css('width', that.width + 'px');
1130 if(typeof(that.library.aspect_ratio) === 'undefined') {
1131 if(typeof(that.height) === 'string')
1132 $(that.element).css('height', that.height);
1133 else if(typeof(that.height) === 'number')
1134 $(that.element).css('height', that.height + 'px');
1137 var w = that.element.offsetWidth;
1138 if(w === null || w === 0) {
1139 // the div is hidden
1140 // this will resize the chart when next viewed
1141 that.tm.last_resized = 0;
1144 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1147 if(NETDATA.chartDefaults.min_width !== null)
1148 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1150 that.tm.last_dom_created = new Date().getTime();
1156 * initialize state variables
1157 * destroy all (possibly) created state elements
1158 * create the basic DOM for a chart
1160 var init = function() {
1161 if(that.enabled === false) return;
1163 that.paused = false;
1164 that.selected = false;
1166 that.chart_created = false; // boolean - is the library.create() been called?
1167 that.updates_counter = 0; // numeric - the number of refreshes made so far
1168 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1169 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1172 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1173 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1174 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1176 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1177 last_updated: 0, // the timestamp the chart last updated with data
1178 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1180 // Used with NETDATA.globalPanAndZoom.seq
1181 last_visible_check: 0, // the time we last checked if it is visible
1182 last_resized: 0, // the time the chart was resized
1183 last_hidden: 0, // the time the chart was hidden
1184 last_unhidden: 0, // the time the chart was unhidden
1185 last_autorefreshed: 0 // the time the chart was last refreshed
1188 that.data = null; // the last data as downloaded from the netdata server
1189 that.data_url = 'invalid://'; // string - the last url used to update the chart
1190 that.data_points = 0; // number - the number of points returned from netdata
1191 that.data_after = 0; // milliseconds - the first timestamp of the data
1192 that.data_before = 0; // milliseconds - the last timestamp of the data
1193 that.data_update_every = 0; // milliseconds - the frequency to update the data
1195 that.tm.last_initialized = new Date().getTime();
1198 that.setMode('auto');
1201 var maxMessageFontSize = function() {
1202 // normally we want a font size, as tall as the element
1203 var h = that.element_message.clientHeight;
1205 // but give it some air, 20% let's say, or 5 pixels min
1206 var lost = Math.max(h * 0.2, 5);
1209 // center the text, vertically
1210 var paddingTop = (lost - 5) / 2;
1212 // but check the width too
1213 // it should fit 10 characters in it
1214 var w = that.element_message.clientWidth / 10;
1216 paddingTop += (h - w) / 2;
1220 // and don't make it too huge
1221 // 5% of the screen size is good
1222 if(h > screen.height / 20) {
1223 paddingTop += (h - (screen.height / 20)) / 2;
1224 h = screen.height / 20;
1228 that.element_message.style.fontSize = h.toString() + 'px';
1229 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1232 var showMessage = function(msg) {
1233 that.element_message.className = 'netdata-message';
1234 that.element_message.innerHTML = msg;
1235 that.element_message.style.fontSize = 'x-small';
1236 that.element_message.style.paddingTop = '0px';
1237 that.___messageHidden___ = undefined;
1240 var showMessageIcon = function(icon) {
1241 that.element_message.innerHTML = icon;
1242 that.element_message.className = 'netdata-message icon';
1243 maxMessageFontSize();
1244 that.___messageHidden___ = undefined;
1247 var hideMessage = function() {
1248 if(typeof that.___messageHidden___ === 'undefined') {
1249 that.___messageHidden___ = true;
1250 that.element_message.className = 'netdata-message hidden';
1254 var showRendering = function() {
1256 if(that.chart !== null) {
1257 if(that.chart.chart_type === 'line')
1258 icon = '<i class="fa fa-line-chart"></i>';
1260 icon = '<i class="fa fa-area-chart"></i>';
1263 icon = '<i class="fa fa-area-chart"></i>';
1265 showMessageIcon(icon + ' netdata');
1268 var showLoading = function() {
1269 if(that.chart_created === false) {
1270 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1276 var isHidden = function() {
1277 if(typeof that.___chartIsHidden___ !== 'undefined')
1283 // hide the chart, when it is not visible - called from isVisible()
1284 var hideChart = function() {
1285 // hide it, if it is not already hidden
1286 if(isHidden() === true) return;
1288 if(that.chart_created === true) {
1289 if(NETDATA.options.current.destroy_on_hide === true) {
1290 // we should destroy it
1295 that.element_chart.style.display = 'none';
1296 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1297 that.tm.last_hidden = new Date().getTime();
1300 // This works, but I not sure there are no corner cases somewhere
1301 // so it is commented - if the user has memory issues he can
1302 // set Destroy on Hide for all charts
1303 // that.data = null;
1307 that.___chartIsHidden___ = true;
1310 // unhide the chart, when it is visible - called from isVisible()
1311 var unhideChart = function() {
1312 if(isHidden() === false) return;
1314 that.___chartIsHidden___ = undefined;
1315 that.updates_since_last_unhide = 0;
1317 if(that.chart_created === false) {
1318 // we need to re-initialize it, to show our background
1319 // logo in bootstrap tabs, until the chart loads
1323 that.tm.last_unhidden = new Date().getTime();
1324 that.element_chart.style.display = '';
1325 if(that.element_legend !== null) that.element_legend.style.display = '';
1331 var canBeRendered = function() {
1332 if(isHidden() === true || that.isVisible(true) === false)
1338 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1339 var callChartLibraryUpdateSafely = function(data) {
1342 if(canBeRendered() === false)
1345 if(NETDATA.options.debug.chart_errors === true)
1346 status = that.library.update(that, data);
1349 status = that.library.update(that, data);
1356 if(status === false) {
1357 error('chart failed to be updated as ' + that.library_name);
1364 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1365 var callChartLibraryCreateSafely = function(data) {
1368 if(canBeRendered() === false)
1371 if(NETDATA.options.debug.chart_errors === true)
1372 status = that.library.create(that, data);
1375 status = that.library.create(that, data);
1382 if(status === false) {
1383 error('chart failed to be created as ' + that.library_name);
1387 that.chart_created = true;
1388 that.updates_since_last_creation = 0;
1392 // ----------------------------------------------------------------------------------------------------------------
1395 // resizeChart() - private
1396 // to be called just before the chart library to make sure that
1397 // a properly sized dom is available
1398 var resizeChart = function() {
1399 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1400 if(that.chart_created === false) return;
1402 if(that.needsRecreation()) {
1405 else if(typeof that.library.resize === 'function') {
1406 that.library.resize(that);
1408 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1409 $(that.element_legend_childs.nano).nanoScroller();
1411 maxMessageFontSize();
1414 that.tm.last_resized = new Date().getTime();
1418 // this is the actual chart resize algorithm
1420 // - resize the entire container
1421 // - update the internal states
1422 // - resize the chart as the div changes height
1423 // - update the scrollbar of the legend
1424 var resizeChartToHeight = function(h) {
1426 that.element.style.height = h;
1428 if(that.settings_id !== null)
1429 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1431 var now = new Date().getTime();
1432 NETDATA.options.last_page_scroll = now;
1433 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1436 that.tm.last_resized = 0;
1440 this.resizeHandler = function(e) {
1443 if(typeof this.event_resize === 'undefined'
1444 || this.event_resize.chart_original_w === 'undefined'
1445 || this.event_resize.chart_original_h === 'undefined')
1446 this.event_resize = {
1447 chart_original_w: this.element.clientWidth,
1448 chart_original_h: this.element.clientHeight,
1452 if(e.type === 'touchstart') {
1453 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1454 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1457 this.event_resize.mouse_start_x = e.clientX;
1458 this.event_resize.mouse_start_y = e.clientY;
1461 this.event_resize.chart_start_w = this.element.clientWidth;
1462 this.event_resize.chart_start_h = this.element.clientHeight;
1463 this.event_resize.chart_last_w = this.element.clientWidth;
1464 this.event_resize.chart_last_h = this.element.clientHeight;
1466 var now = new Date().getTime();
1467 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1468 // double click / double tap event
1470 // the optimal height of the chart
1471 // showing the entire legend
1472 var optimal = this.event_resize.chart_last_h
1473 + this.element_legend_childs.content.scrollHeight
1474 - this.element_legend_childs.content.clientHeight;
1476 // if we are not optimal, be optimal
1477 if(this.event_resize.chart_last_h != optimal)
1478 resizeChartToHeight(optimal.toString() + 'px');
1480 // else if we do not have the original height
1481 // reset to the original height
1482 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1483 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1486 this.event_resize.last = now;
1488 // process movement event
1489 document.onmousemove =
1490 document.ontouchmove =
1491 this.element_legend_childs.resize_handler.onmousemove =
1492 this.element_legend_childs.resize_handler.ontouchmove =
1497 case 'mousemove': y = e.clientY; break;
1498 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1502 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1504 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1505 resizeChartToHeight(newH.toString() + 'px');
1506 that.event_resize.chart_last_h = newH;
1511 // process end event
1512 document.onmouseup =
1513 document.ontouchend =
1514 this.element_legend_childs.resize_handler.onmouseup =
1515 this.element_legend_childs.resize_handler.ontouchend =
1517 // remove all the hooks
1518 document.onmouseup =
1519 document.onmousemove =
1520 document.ontouchmove =
1521 document.ontouchend =
1522 that.element_legend_childs.resize_handler.onmousemove =
1523 that.element_legend_childs.resize_handler.ontouchmove =
1524 that.element_legend_childs.resize_handler.onmouseout =
1525 that.element_legend_childs.resize_handler.onmouseup =
1526 that.element_legend_childs.resize_handler.ontouchend =
1529 // allow auto-refreshes
1530 NETDATA.options.auto_refresher_stop_until = 0;
1536 var noDataToShow = function() {
1537 showMessageIcon('<i class="fa fa-warning"></i> empty');
1538 that.legendUpdateDOM();
1539 that.tm.last_autorefreshed = new Date().getTime();
1540 // that.data_update_every = 30 * 1000;
1541 //that.element_chart.style.display = 'none';
1542 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1543 //that.___chartIsHidden___ = true;
1546 // ============================================================================================================
1549 this.error = function(msg) {
1553 this.setMode = function(m) {
1554 if(this.current !== null && this.current.name === m) return;
1557 this.current = this.auto;
1558 else if(m === 'pan')
1559 this.current = this.pan;
1560 else if(m === 'zoom')
1561 this.current = this.zoom;
1563 this.current = this.auto;
1565 this.current.force_update_at = 0;
1566 this.current.force_before_ms = null;
1567 this.current.force_after_ms = null;
1569 this.tm.last_mode_switch = new Date().getTime();
1572 // ----------------------------------------------------------------------------------------------------------------
1573 // global selection sync
1575 // prevent to global selection sync for some time
1576 this.globalSelectionSyncDelay = function(ms) {
1577 if(NETDATA.options.current.sync_selection === false)
1580 if(typeof ms === 'number')
1581 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1583 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1586 // can we globally apply selection sync?
1587 this.globalSelectionSyncAbility = function() {
1588 if(NETDATA.options.current.sync_selection === false)
1591 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1597 this.globalSelectionSyncIsMaster = function() {
1598 if(NETDATA.globalSelectionSync.state === this)
1604 // this chart is the master of the global selection sync
1605 this.globalSelectionSyncBeMaster = function() {
1607 if(this.globalSelectionSyncIsMaster()) {
1608 if(this.debug === true)
1609 this.log('sync: I am the master already.');
1614 if(NETDATA.globalSelectionSync.state) {
1615 if(this.debug === true)
1616 this.log('sync: I am not the sync master. Resetting global sync.');
1618 this.globalSelectionSyncStop();
1621 // become the master
1622 if(this.debug === true)
1623 this.log('sync: becoming sync master.');
1625 this.selected = true;
1626 NETDATA.globalSelectionSync.state = this;
1628 // find the all slaves
1629 var targets = NETDATA.options.targets;
1630 var len = targets.length;
1635 if(this.debug === true)
1636 st.log('sync: not adding me to sync');
1638 else if(st.globalSelectionSyncIsEligible()) {
1639 if(this.debug === true)
1640 st.log('sync: adding to sync as slave');
1642 st.globalSelectionSyncBeSlave();
1646 // this.globalSelectionSyncDelay(100);
1649 // can the chart participate to the global selection sync as a slave?
1650 this.globalSelectionSyncIsEligible = function() {
1651 if(this.enabled === true
1652 && this.library !== null
1653 && typeof this.library.setSelection === 'function'
1654 && this.isVisible() === true
1655 && this.chart_created === true)
1661 // this chart becomes a slave of the global selection sync
1662 this.globalSelectionSyncBeSlave = function() {
1663 if(NETDATA.globalSelectionSync.state !== this)
1664 NETDATA.globalSelectionSync.slaves.push(this);
1667 // sync all the visible charts to the given time
1668 // this is to be called from the chart libraries
1669 this.globalSelectionSync = function(t) {
1670 if(this.globalSelectionSyncAbility() === false) {
1671 if(this.debug === true)
1672 this.log('sync: cannot sync (yet?).');
1677 if(this.globalSelectionSyncIsMaster() === false) {
1678 if(this.debug === true)
1679 this.log('sync: trying to be sync master.');
1681 this.globalSelectionSyncBeMaster();
1683 if(this.globalSelectionSyncAbility() === false) {
1684 if(this.debug === true)
1685 this.log('sync: cannot sync (yet?).');
1691 NETDATA.globalSelectionSync.last_t = t;
1692 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1697 // stop syncing all charts to the given time
1698 this.globalSelectionSyncStop = function() {
1699 if(NETDATA.globalSelectionSync.slaves.length) {
1700 if(this.debug === true)
1701 this.log('sync: cleaning up...');
1703 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1705 if(that.debug === true)
1706 st.log('sync: not adding me to sync stop');
1709 if(that.debug === true)
1710 st.log('sync: removed slave from sync');
1712 st.clearSelection();
1716 NETDATA.globalSelectionSync.last_t = 0;
1717 NETDATA.globalSelectionSync.slaves = [];
1718 NETDATA.globalSelectionSync.state = null;
1721 this.clearSelection();
1724 this.setSelection = function(t) {
1725 if(typeof this.library.setSelection === 'function') {
1726 if(this.library.setSelection(this, t) === true)
1727 this.selected = true;
1729 this.selected = false;
1731 else this.selected = true;
1733 if(this.selected === true && this.debug === true)
1734 this.log('selection set to ' + t.toString());
1736 return this.selected;
1739 this.clearSelection = function() {
1740 if(this.selected === true) {
1741 if(typeof this.library.clearSelection === 'function') {
1742 if(this.library.clearSelection(this) === true)
1743 this.selected = false;
1745 this.selected = true;
1747 else this.selected = false;
1749 if(this.selected === false && this.debug === true)
1750 this.log('selection cleared');
1755 return this.selected;
1758 // find if a timestamp (ms) is shown in the current chart
1759 this.timeIsVisible = function(t) {
1760 if(t >= this.data_after && t <= this.data_before)
1765 this.calculateRowForTime = function(t) {
1766 if(this.timeIsVisible(t) === false) return -1;
1767 return Math.floor((t - this.data_after) / this.data_update_every);
1770 // ----------------------------------------------------------------------------------------------------------------
1773 this.log = function(msg) {
1774 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1777 this.pauseChart = function() {
1778 if(this.paused === false) {
1779 if(this.debug === true)
1780 this.log('pauseChart()');
1786 this.unpauseChart = function() {
1787 if(this.paused === true) {
1788 if(this.debug === true)
1789 this.log('unpauseChart()');
1791 this.paused = false;
1795 this.resetChart = function(dont_clear_master, dont_update) {
1796 if(this.debug === true)
1797 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1799 if(typeof dont_clear_master === 'undefined')
1800 dont_clear_master = false;
1802 if(typeof dont_update === 'undefined')
1803 dont_update = false;
1805 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1806 if(this.debug === true)
1807 this.log('resetChart() diverting to clearMaster().');
1808 // this will call us back with master === true
1809 NETDATA.globalPanAndZoom.clearMaster();
1813 this.clearSelection();
1815 this.tm.pan_and_zoom_seq = 0;
1817 this.setMode('auto');
1818 this.current.force_update_at = 0;
1819 this.current.force_before_ms = null;
1820 this.current.force_after_ms = null;
1821 this.tm.last_autorefreshed = 0;
1822 this.paused = false;
1823 this.selected = false;
1824 this.enabled = true;
1825 // this.debug = false;
1827 // do not update the chart here
1828 // or the chart will flip-flop when it is the master
1829 // of a selection sync and another chart becomes
1832 if(dont_update !== true && this.isVisible() === true) {
1837 this.updateChartPanOrZoom = function(after, before) {
1838 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1841 if(this.debug === true)
1844 if(before < after) {
1845 if(this.debug === true)
1846 this.log(logme + 'flipped parameters, rejecting it.');
1851 if(typeof this.fixed_min_duration === 'undefined')
1852 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1854 var min_duration = this.fixed_min_duration;
1855 var current_duration = Math.round(this.view_before - this.view_after);
1857 // round the numbers
1858 after = Math.round(after);
1859 before = Math.round(before);
1861 // align them to update_every
1862 // stretching them further away
1863 after -= after % this.data_update_every;
1864 before += this.data_update_every - (before % this.data_update_every);
1866 // the final wanted duration
1867 var wanted_duration = before - after;
1869 // to allow panning, accept just a point below our minimum
1870 if((current_duration - this.data_update_every) < min_duration)
1871 min_duration = current_duration - this.data_update_every;
1873 // we do it, but we adjust to minimum size and return false
1874 // when the wanted size is below the current and the minimum
1876 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1877 if(this.debug === true)
1878 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1880 min_duration = this.fixed_min_duration;
1882 var dt = (min_duration - wanted_duration) / 2;
1885 wanted_duration = before - after;
1889 var tolerance = this.data_update_every * 2;
1890 var movement = Math.abs(before - this.view_before);
1892 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1893 if(this.debug === true)
1894 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);
1898 if(this.current.name === 'auto') {
1899 this.log(logme + 'caller called me with mode: ' + this.current.name);
1900 this.setMode('pan');
1903 if(this.debug === true)
1904 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);
1906 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1907 this.current.force_after_ms = after;
1908 this.current.force_before_ms = before;
1909 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1913 this.legendFormatValue = function(value) {
1914 if(value === null || value === 'undefined') return '-';
1915 if(typeof value !== 'number') return value;
1917 var abs = Math.abs(value);
1918 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1919 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1920 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1921 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1922 return (Math.round(value * 10000) / 10000).toLocaleString();
1925 this.legendSetLabelValue = function(label, value) {
1926 var series = this.element_legend_childs.series[label];
1927 if(typeof series === 'undefined') return;
1928 if(series.value === null && series.user === null) return;
1930 // if the value has not changed, skip DOM update
1931 //if(series.last === value) return;
1934 if(typeof value === 'number') {
1935 var v = Math.abs(value);
1936 s = r = this.legendFormatValue(value);
1938 if(typeof series.last === 'number') {
1939 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1940 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1941 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1943 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1948 series.last = value;
1951 if(series.value !== null) series.value.innerHTML = s;
1952 if(series.user !== null) series.user.innerHTML = r;
1955 this.legendSetDate = function(ms) {
1956 if(typeof ms !== 'number') {
1957 this.legendShowUndefined();
1961 var d = new Date(ms);
1963 if(this.element_legend_childs.title_date)
1964 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1966 if(this.element_legend_childs.title_time)
1967 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1969 if(this.element_legend_childs.title_units)
1970 this.element_legend_childs.title_units.innerHTML = this.units;
1973 this.legendShowUndefined = function() {
1974 if(this.element_legend_childs.title_date)
1975 this.element_legend_childs.title_date.innerHTML = ' ';
1977 if(this.element_legend_childs.title_time)
1978 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1980 if(this.element_legend_childs.title_units)
1981 this.element_legend_childs.title_units.innerHTML = ' ';
1983 if(this.data && this.element_legend_childs.series !== null) {
1984 var labels = this.data.dimension_names;
1985 var i = labels.length;
1987 var label = labels[i];
1989 if(typeof label === 'undefined') continue;
1990 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1991 this.legendSetLabelValue(label, null);
1996 this.legendShowLatestValues = function() {
1997 if(this.chart === null) return;
1998 if(this.selected) return;
2000 if(this.data === null || this.element_legend_childs.series === null) {
2001 this.legendShowUndefined();
2005 var show_undefined = true;
2006 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2007 show_undefined = false;
2009 if(show_undefined) {
2010 this.legendShowUndefined();
2014 this.legendSetDate(this.view_before);
2016 var labels = this.data.dimension_names;
2017 var i = labels.length;
2019 var label = labels[i];
2021 if(typeof label === 'undefined') continue;
2022 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2025 this.legendSetLabelValue(label, null);
2027 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2031 this.legendReset = function() {
2032 this.legendShowLatestValues();
2035 // this should be called just ONCE per dimension per chart
2036 this._chartDimensionColor = function(label) {
2037 if(this.colors === null) this.chartColors();
2039 if(typeof this.colors_assigned[label] === 'undefined') {
2040 if(this.colors_available.length === 0) {
2041 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2042 this.colors_available.push(NETDATA.themes.current.colors[i]);
2045 this.colors_assigned[label] = this.colors_available.shift();
2047 if(this.debug === true)
2048 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2051 if(this.debug === true)
2052 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2055 this.colors.push(this.colors_assigned[label]);
2056 return this.colors_assigned[label];
2059 this.chartColors = function() {
2060 if(this.colors !== null) return this.colors;
2062 this.colors = new Array();
2063 this.colors_available = new Array();
2066 var c = $(this.element).data('colors');
2067 // this.log('read colors: ' + c);
2068 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2069 if(typeof c !== 'string') {
2070 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2077 for(i = 0, len = c.length; i < len ; i++) {
2079 this.colors_available.push(c[i]);
2080 // this.log('adding color: ' + c[i]);
2086 // push all the standard colors too
2087 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2088 this.colors_available.push(NETDATA.themes.current.colors[i]);
2093 this.legendUpdateDOM = function() {
2096 // check that the legend DOM is up to date for the downloaded dimensions
2097 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2098 // this.log('the legend does not have any series - requesting legend update');
2101 else if(this.data === null) {
2102 // this.log('the chart does not have any data - requesting legend update');
2105 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2109 var labels = this.data.dimension_names.toString();
2110 if(labels !== this.element_legend_childs.series.labels_key) {
2113 if(this.debug === true)
2114 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2118 if(needed === false) {
2119 // make sure colors available
2122 // do we have to update the current values?
2123 // we do this, only when the visible chart is current
2124 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2125 if(this.debug === true)
2126 this.log('chart is in latest position... updating values on legend...');
2128 //var labels = this.data.dimension_names;
2129 //var i = labels.length;
2131 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2135 if(this.colors === null) {
2136 // this is the first time we update the chart
2137 // let's assign colors to all dimensions
2138 if(this.library.track_colors() === true)
2139 for(var dim in this.chart.dimensions)
2140 this._chartDimensionColor(this.chart.dimensions[dim].name);
2142 // we will re-generate the colors for the chart
2143 // based on the selected dimensions
2146 if(this.debug === true)
2147 this.log('updating Legend DOM');
2149 // mark all dimensions as invalid
2150 this.dimensions_visibility.invalidateAll();
2152 var genLabel = function(state, parent, name, count) {
2153 var color = state._chartDimensionColor(name);
2155 var user_element = null;
2156 var user_id = self.data('show-value-of-' + name + '-at') || null;
2157 if(user_id !== null) {
2158 user_element = document.getElementById(user_id) || null;
2159 if(user_element === null)
2160 state.log('Cannot find element with id: ' + user_id);
2163 state.element_legend_childs.series[name] = {
2164 name: document.createElement('span'),
2165 value: document.createElement('span'),
2170 var label = state.element_legend_childs.series[name];
2172 // create the dimension visibility tracking for this label
2173 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2175 var rgb = NETDATA.colorHex2Rgb(color);
2176 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2177 + state.chart.chart_type
2178 + '" style="background-color: '
2179 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2180 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2182 var text = document.createTextNode(' ' + name);
2183 label.name.appendChild(text);
2186 parent.appendChild(document.createElement('br'));
2188 parent.appendChild(label.name);
2189 parent.appendChild(label.value);
2192 var content = document.createElement('div');
2194 if(this.hasLegend()) {
2195 this.element_legend_childs = {
2197 resize_handler: document.createElement('div'),
2198 toolbox: document.createElement('div'),
2199 toolbox_left: document.createElement('div'),
2200 toolbox_right: document.createElement('div'),
2201 toolbox_reset: document.createElement('div'),
2202 toolbox_zoomin: document.createElement('div'),
2203 toolbox_zoomout: document.createElement('div'),
2204 toolbox_volume: document.createElement('div'),
2205 title_date: document.createElement('span'),
2206 title_time: document.createElement('span'),
2207 title_units: document.createElement('span'),
2208 nano: document.createElement('div'),
2210 paneClass: 'netdata-legend-series-pane',
2211 sliderClass: 'netdata-legend-series-slider',
2212 contentClass: 'netdata-legend-series-content',
2213 enabledClass: '__enabled',
2214 flashedClass: '__flashed',
2215 activeClass: '__active',
2217 alwaysVisible: true,
2223 this.element_legend.innerHTML = '';
2225 if(this.library.toolboxPanAndZoom !== null) {
2227 function get_pan_and_zoom_step(event) {
2229 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2231 else if (event.shiftKey)
2232 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2234 else if (event.altKey)
2235 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2238 return NETDATA.options.current.pan_and_zoom_factor;
2241 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2242 this.element.appendChild(this.element_legend_childs.toolbox);
2244 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2245 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2246 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2247 this.element_legend_childs.toolbox_left.onclick = function(e) {
2250 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2251 var before = that.view_before - step;
2252 var after = that.view_after - step;
2253 if(after >= that.netdata_first)
2254 that.library.toolboxPanAndZoom(that, after, before);
2256 if(NETDATA.options.current.show_help === true)
2257 $(this.element_legend_childs.toolbox_left).popover({
2262 placement: 'bottom',
2263 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2265 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>'
2269 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2270 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2271 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2272 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2274 NETDATA.resetAllCharts(that);
2276 if(NETDATA.options.current.show_help === true)
2277 $(this.element_legend_childs.toolbox_reset).popover({
2282 placement: 'bottom',
2283 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2284 title: 'Chart Reset',
2285 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>'
2288 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2289 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2290 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2291 this.element_legend_childs.toolbox_right.onclick = function(e) {
2293 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2294 var before = that.view_before + step;
2295 var after = that.view_after + step;
2296 if(before <= that.netdata_last)
2297 that.library.toolboxPanAndZoom(that, after, before);
2299 if(NETDATA.options.current.show_help === true)
2300 $(this.element_legend_childs.toolbox_right).popover({
2305 placement: 'bottom',
2306 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2308 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>'
2312 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2313 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2314 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2315 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2317 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2318 var before = that.view_before - dt;
2319 var after = that.view_after + dt;
2320 that.library.toolboxPanAndZoom(that, after, before);
2322 if(NETDATA.options.current.show_help === true)
2323 $(this.element_legend_childs.toolbox_zoomin).popover({
2328 placement: 'bottom',
2329 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2330 title: 'Chart Zoom In',
2331 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>'
2334 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2335 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2336 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2337 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2339 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);
2340 var before = that.view_before + dt;
2341 var after = that.view_after - dt;
2343 that.library.toolboxPanAndZoom(that, after, before);
2345 if(NETDATA.options.current.show_help === true)
2346 $(this.element_legend_childs.toolbox_zoomout).popover({
2351 placement: 'bottom',
2352 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2353 title: 'Chart Zoom Out',
2354 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>'
2357 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2358 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2359 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2360 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2361 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2362 //e.preventDefault();
2363 //alert('clicked toolbox_volume on ' + that.id);
2367 this.element_legend_childs.toolbox = null;
2368 this.element_legend_childs.toolbox_left = null;
2369 this.element_legend_childs.toolbox_reset = null;
2370 this.element_legend_childs.toolbox_right = null;
2371 this.element_legend_childs.toolbox_zoomin = null;
2372 this.element_legend_childs.toolbox_zoomout = null;
2373 this.element_legend_childs.toolbox_volume = null;
2376 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2377 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2378 this.element.appendChild(this.element_legend_childs.resize_handler);
2379 if(NETDATA.options.current.show_help === true)
2380 $(this.element_legend_childs.resize_handler).popover({
2385 placement: 'bottom',
2386 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2387 title: 'Chart Resize',
2388 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>'
2392 this.element_legend_childs.resize_handler.onmousedown =
2394 that.resizeHandler(e);
2398 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2399 that.resizeHandler(e);
2402 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2403 this.element_legend.appendChild(this.element_legend_childs.title_date);
2405 this.element_legend.appendChild(document.createElement('br'));
2407 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2408 this.element_legend.appendChild(this.element_legend_childs.title_time);
2410 this.element_legend.appendChild(document.createElement('br'));
2412 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2413 this.element_legend.appendChild(this.element_legend_childs.title_units);
2415 this.element_legend.appendChild(document.createElement('br'));
2417 this.element_legend_childs.nano.className = 'netdata-legend-series';
2418 this.element_legend.appendChild(this.element_legend_childs.nano);
2420 content.className = 'netdata-legend-series-content';
2421 this.element_legend_childs.nano.appendChild(content);
2423 if(NETDATA.options.current.show_help === true)
2424 $(content).popover({
2429 placement: 'bottom',
2430 title: 'Chart Legend',
2431 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2432 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>'
2436 this.element_legend_childs = {
2438 resize_handler: null,
2441 toolbox_right: null,
2442 toolbox_reset: null,
2443 toolbox_zoomin: null,
2444 toolbox_zoomout: null,
2445 toolbox_volume: null,
2456 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2457 if(this.debug === true)
2458 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2460 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2461 genLabel(this, content, this.data.dimension_names[i], i);
2465 var tmp = new Array();
2466 for(var dim in this.chart.dimensions) {
2467 tmp.push(this.chart.dimensions[dim].name);
2468 genLabel(this, content, this.chart.dimensions[dim].name, i);
2470 this.element_legend_childs.series.labels_key = tmp.toString();
2471 if(this.debug === true)
2472 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2475 // create a hidden div to be used for hidding
2476 // the original legend of the chart library
2477 var el = document.createElement('div');
2478 if(this.element_legend !== null)
2479 this.element_legend.appendChild(el);
2480 el.style.display = 'none';
2482 this.element_legend_childs.hidden = document.createElement('div');
2483 el.appendChild(this.element_legend_childs.hidden);
2485 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2486 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2488 this.legendShowLatestValues();
2491 this.hasLegend = function() {
2492 if(typeof this.___hasLegendCache___ !== 'undefined')
2493 return this.___hasLegendCache___;
2496 if(this.library && this.library.legend(this) === 'right-side') {
2497 var legend = $(this.element).data('legend') || 'yes';
2498 if(legend === 'yes') leg = true;
2501 this.___hasLegendCache___ = leg;
2505 this.legendWidth = function() {
2506 return (this.hasLegend())?140:0;
2509 this.legendHeight = function() {
2510 return $(this.element).height();
2513 this.chartWidth = function() {
2514 return $(this.element).width() - this.legendWidth();
2517 this.chartHeight = function() {
2518 return $(this.element).height();
2521 this.chartPixelsPerPoint = function() {
2522 // force an options provided detail
2523 var px = this.pixels_per_point;
2525 if(this.library && px < this.library.pixels_per_point(this))
2526 px = this.library.pixels_per_point(this);
2528 if(px < NETDATA.options.current.pixels_per_point)
2529 px = NETDATA.options.current.pixels_per_point;
2534 this.needsRecreation = function() {
2536 this.chart_created === true
2538 && this.library.autoresize() === false
2539 && this.tm.last_resized < NETDATA.options.last_resized
2543 this.chartURL = function() {
2544 var after, before, points_multiplier = 1;
2545 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2546 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2548 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2549 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2550 this.view_after = after * 1000;
2551 this.view_before = before * 1000;
2553 this.requested_padding = null;
2554 points_multiplier = 1;
2556 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2557 this.tm.pan_and_zoom_seq = 0;
2559 before = Math.round(this.current.force_before_ms / 1000);
2560 after = Math.round(this.current.force_after_ms / 1000);
2561 this.view_after = after * 1000;
2562 this.view_before = before * 1000;
2564 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2565 this.requested_padding = Math.round((before - after) / 2);
2566 after -= this.requested_padding;
2567 before += this.requested_padding;
2568 this.requested_padding *= 1000;
2569 points_multiplier = 2;
2572 this.current.force_before_ms = null;
2573 this.current.force_after_ms = null;
2576 this.tm.pan_and_zoom_seq = 0;
2578 before = this.before;
2580 this.view_after = after * 1000;
2581 this.view_before = before * 1000;
2583 this.requested_padding = null;
2584 points_multiplier = 1;
2587 this.requested_after = after * 1000;
2588 this.requested_before = before * 1000;
2590 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2592 // build the data URL
2593 this.data_url = this.host + this.chart.data_url;
2594 this.data_url += "&format=" + this.library.format();
2595 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2596 this.data_url += "&group=" + this.method;
2597 this.data_url += "&options=" + this.library.options(this);
2598 this.data_url += '|jsonwrap';
2600 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2601 this.data_url += '|nonzero';
2603 if(this.append_options !== null)
2604 this.data_url += '|' + this.append_options.toString();
2607 this.data_url += "&after=" + after.toString();
2610 this.data_url += "&before=" + before.toString();
2613 this.data_url += "&dimensions=" + this.dimensions;
2615 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2616 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2619 this.redrawChart = function() {
2620 if(this.data !== null)
2621 this.updateChartWithData(this.data);
2624 this.updateChartWithData = function(data) {
2625 if(this.debug === true)
2626 this.log('updateChartWithData() called.');
2628 // this may force the chart to be re-created
2632 this.updates_counter++;
2633 this.updates_since_last_unhide++;
2634 this.updates_since_last_creation++;
2636 var started = new Date().getTime();
2638 // if the result is JSON, find the latest update-every
2639 this.data_update_every = data.view_update_every * 1000;
2640 this.data_after = data.after * 1000;
2641 this.data_before = data.before * 1000;
2642 this.netdata_first = data.first_entry * 1000;
2643 this.netdata_last = data.last_entry * 1000;
2644 this.data_points = data.points;
2647 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2648 if(this.view_after < this.data_after) {
2649 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2650 this.view_after = this.data_after;
2653 if(this.view_before > this.data_before) {
2654 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2655 this.view_before = this.data_before;
2659 this.view_after = this.data_after;
2660 this.view_before = this.data_before;
2663 if(this.debug === true) {
2664 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2666 if(this.current.force_after_ms)
2667 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2669 this.log('STATUS: forced : unset');
2671 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2672 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2673 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2674 this.log('STATUS: points : ' + (this.data_points).toString());
2677 if(this.data_points === 0) {
2682 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2683 if(this.debug === true)
2684 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2686 this.chart_created = false;
2689 // check and update the legend
2690 this.legendUpdateDOM();
2692 if(this.chart_created === true
2693 && typeof this.library.update === 'function') {
2695 if(this.debug === true)
2696 this.log('updating chart...');
2698 if(callChartLibraryUpdateSafely(data) === false)
2702 if(this.debug === true)
2703 this.log('creating chart...');
2705 if(callChartLibraryCreateSafely(data) === false)
2709 this.legendShowLatestValues();
2710 if(this.selected === true)
2711 NETDATA.globalSelectionSync.stop();
2713 // update the performance counters
2714 var now = new Date().getTime();
2715 this.tm.last_updated = now;
2717 // don't update last_autorefreshed if this chart is
2718 // forced to be updated with global PanAndZoom
2719 if(NETDATA.globalPanAndZoom.isActive())
2720 this.tm.last_autorefreshed = 0;
2722 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2723 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2725 this.tm.last_autorefreshed = now;
2728 this.refresh_dt_ms = now - started;
2729 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2731 if(this.refresh_dt_element !== null)
2732 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2735 this.updateChart = function(callback) {
2736 if(this.debug === true)
2737 this.log('updateChart() called.');
2739 if(this._updating === true) {
2740 if(this.debug === true)
2741 this.log('I am already updating...');
2743 if(typeof callback === 'function') callback();
2747 // due to late initialization of charts and libraries
2748 // we need to check this too
2749 if(this.enabled === false) {
2750 if(this.debug === true)
2751 this.log('I am not enabled');
2753 if(typeof callback === 'function') callback();
2757 if(canBeRendered() === false) {
2758 if(typeof callback === 'function') callback();
2762 if(this.chart === null) {
2763 this.getChart(function() { that.updateChart(callback); });
2767 if(this.library.initialized === false) {
2768 if(this.library.enabled === true) {
2769 this.library.initialize(function() { that.updateChart(callback); });
2773 error('chart library "' + this.library_name + '" is not available.');
2774 if(typeof callback === 'function') callback();
2779 this.clearSelection();
2782 if(this.debug === true)
2783 this.log('updating from ' + this.data_url);
2785 NETDATA.statistics.refreshes_total++;
2786 NETDATA.statistics.refreshes_active++;
2788 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2789 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2791 this._updating = true;
2793 this.xhr = $.ajax( {
2798 .success(function(data) {
2799 if(that.debug === true)
2800 that.log('data received. updating chart.');
2802 that.updateChartWithData(data);
2805 error('data download failed for url: ' + that.data_url);
2807 .always(function() {
2808 NETDATA.statistics.refreshes_active--;
2809 that._updating = false;
2810 if(typeof callback === 'function') callback();
2816 this.isVisible = function(nocache) {
2817 if(typeof nocache === 'undefined')
2820 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2822 // caching - we do not evaluate the charts visibility
2823 // if the page has not been scrolled since the last check
2824 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2825 return this.___isVisible___;
2827 this.tm.last_visible_check = new Date().getTime();
2829 var wh = window.innerHeight;
2830 var x = this.element.getBoundingClientRect();
2834 if(x.width === 0 || x.height === 0) {
2836 this.___isVisible___ = false;
2837 return this.___isVisible___;
2840 if(x.top < 0 && -x.top > x.height) {
2841 // the chart is entirely above
2842 ret = -x.top - x.height;
2844 else if(x.top > wh) {
2845 // the chart is entirely below
2849 if(ret > tolerance) {
2850 // the chart is too far
2853 this.___isVisible___ = false;
2854 return this.___isVisible___;
2857 // the chart is inside or very close
2860 this.___isVisible___ = true;
2861 return this.___isVisible___;
2865 this.isAutoRefreshable = function() {
2866 return (this.current.autorefresh);
2869 this.canBeAutoRefreshed = function() {
2870 var now = new Date().getTime();
2872 if(this.running === true) {
2873 if(this.debug === true)
2874 this.log('I am already running');
2879 if(this.enabled === false) {
2880 if(this.debug === true)
2881 this.log('I am not enabled');
2886 if(this.library === null || this.library.enabled === false) {
2887 error('charting library "' + this.library_name + '" is not available');
2888 if(this.debug === true)
2889 this.log('My chart library ' + this.library_name + ' is not available');
2894 if(this.isVisible() === false) {
2895 if(NETDATA.options.debug.visibility === true || this.debug === true)
2896 this.log('I am not visible');
2901 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2902 if(this.debug === true)
2903 this.log('timed force update detected - allowing this update');
2905 this.current.force_update_at = 0;
2909 if(this.isAutoRefreshable() === true) {
2910 // allow the first update, even if the page is not visible
2911 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2912 if(NETDATA.options.debug.focus === true || this.debug === true)
2913 this.log('canBeAutoRefreshed(): page does not have focus');
2918 if(this.needsRecreation() === true) {
2919 if(this.debug === true)
2920 this.log('canBeAutoRefreshed(): needs re-creation.');
2925 // options valid only for autoRefresh()
2926 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2927 if(NETDATA.globalPanAndZoom.isActive()) {
2928 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2929 if(this.debug === true)
2930 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2935 if(this.debug === true)
2936 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2942 if(this.selected === true) {
2943 if(this.debug === true)
2944 this.log('canBeAutoRefreshed(): I have a selection in place.');
2949 if(this.paused === true) {
2950 if(this.debug === true)
2951 this.log('canBeAutoRefreshed(): I am paused.');
2956 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2957 if(this.debug === true)
2958 this.log('canBeAutoRefreshed(): It is time to update me.');
2968 this.autoRefresh = function(callback) {
2969 if(this.canBeAutoRefreshed() === true && this.running === false) {
2972 state.running = true;
2973 state.updateChart(function() {
2974 state.running = false;
2976 if(typeof callback !== 'undefined')
2981 if(typeof callback !== 'undefined')
2986 this._defaultsFromDownloadedChart = function(chart) {
2988 this.chart_url = chart.url;
2989 this.data_update_every = chart.update_every * 1000;
2990 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2991 this.tm.last_info_downloaded = new Date().getTime();
2993 if(this.title === null)
2994 this.title = chart.title;
2996 if(this.units === null)
2997 this.units = chart.units;
3000 // fetch the chart description from the netdata server
3001 this.getChart = function(callback) {
3002 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3004 this._defaultsFromDownloadedChart(this.chart);
3005 if(typeof callback === 'function') callback();
3008 this.chart_url = "/api/v1/chart?chart=" + this.id;
3010 if(this.debug === true)
3011 this.log('downloading ' + this.chart_url);
3014 url: this.host + this.chart_url,
3018 .done(function(chart) {
3019 chart.url = that.chart_url;
3020 that._defaultsFromDownloadedChart(chart);
3021 NETDATA.chartRegistry.add(that.host, that.id, chart);
3024 NETDATA.error(404, that.chart_url);
3025 error('chart not found on url "' + that.chart_url + '"');
3027 .always(function() {
3028 if(typeof callback === 'function') callback();
3033 // ============================================================================================================
3039 NETDATA.resetAllCharts = function(state) {
3040 // first clear the global selection sync
3041 // to make sure no chart is in selected state
3042 state.globalSelectionSyncStop();
3044 // there are 2 possibilities here
3045 // a. state is the global Pan and Zoom master
3046 // b. state is not the global Pan and Zoom master
3048 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3051 // clear the global Pan and Zoom
3052 // this will also refresh the master
3053 // and unblock any charts currently mirroring the master
3054 NETDATA.globalPanAndZoom.clearMaster();
3056 // if we were not the master, reset our status too
3057 // this is required because most probably the mouse
3058 // is over this chart, blocking it from auto-refreshing
3059 if(master === false && (state.paused === true || state.selected === true))
3063 // get or create a chart state, given a DOM element
3064 NETDATA.chartState = function(element) {
3065 var state = $(element).data('netdata-state-object') || null;
3066 if(state === null) {
3067 state = new chartState(element);
3068 $(element).data('netdata-state-object', state);
3073 // ----------------------------------------------------------------------------------------------------------------
3074 // Library functions
3076 // Load a script without jquery
3077 // This is used to load jquery - after it is loaded, we use jquery
3078 NETDATA._loadjQuery = function(callback) {
3079 if(typeof jQuery === 'undefined') {
3080 if(NETDATA.options.debug.main_loop === true)
3081 console.log('loading ' + NETDATA.jQuery);
3083 var script = document.createElement('script');
3084 script.type = 'text/javascript';
3085 script.async = true;
3086 script.src = NETDATA.jQuery;
3088 // script.onabort = onError;
3089 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3090 if(typeof callback === "function")
3091 script.onload = callback;
3093 var s = document.getElementsByTagName('script')[0];
3094 s.parentNode.insertBefore(script, s);
3096 else if(typeof callback === "function")
3100 NETDATA._loadCSS = function(filename) {
3101 // don't use jQuery here
3102 // styles are loaded before jQuery
3103 // to eliminate showing an unstyled page to the user
3105 var fileref = document.createElement("link");
3106 fileref.setAttribute("rel", "stylesheet");
3107 fileref.setAttribute("type", "text/css");
3108 fileref.setAttribute("href", filename);
3110 if (typeof fileref !== 'undefined')
3111 document.getElementsByTagName("head")[0].appendChild(fileref);
3114 NETDATA.colorHex2Rgb = function(hex) {
3115 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3116 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3117 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3118 return r + r + g + g + b + b;
3121 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3123 r: parseInt(result[1], 16),
3124 g: parseInt(result[2], 16),
3125 b: parseInt(result[3], 16)
3129 NETDATA.colorLuminance = function(hex, lum) {
3130 // validate hex string
3131 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3133 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3137 // convert to decimal and change luminosity
3138 var rgb = "#", c, i;
3139 for (i = 0; i < 3; i++) {
3140 c = parseInt(hex.substr(i*2,2), 16);
3141 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3142 rgb += ("00"+c).substr(c.length);
3148 NETDATA.guid = function() {
3150 return Math.floor((1 + Math.random()) * 0x10000)
3155 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3158 NETDATA.zeropad = function(x) {
3159 if(x > -10 && x < 10) return '0' + x.toString();
3160 else return x.toString();
3163 // user function to signal us the DOM has been
3165 NETDATA.updatedDom = function() {
3166 NETDATA.options.updated_dom = true;
3169 NETDATA.ready = function(callback) {
3170 NETDATA.options.pauseCallback = callback;
3173 NETDATA.pause = function(callback) {
3174 if(NETDATA.options.pause === true)
3177 NETDATA.options.pauseCallback = callback;
3180 NETDATA.unpause = function() {
3181 NETDATA.options.pauseCallback = null;
3182 NETDATA.options.updated_dom = true;
3183 NETDATA.options.pause = false;
3186 // ----------------------------------------------------------------------------------------------------------------
3188 // this is purely sequencial charts refresher
3189 // it is meant to be autonomous
3190 NETDATA.chartRefresherNoParallel = function(index) {
3191 if(NETDATA.options.debug.mail_loop === true)
3192 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3194 if(NETDATA.options.updated_dom === true) {
3195 // the dom has been updated
3196 // get the dom parts again
3197 NETDATA.parseDom(NETDATA.chartRefresher);
3200 if(index >= NETDATA.options.targets.length) {
3201 if(NETDATA.options.debug.main_loop === true)
3202 console.log('waiting to restart main loop...');
3204 NETDATA.options.auto_refresher_fast_weight = 0;
3206 setTimeout(function() {
3207 NETDATA.chartRefresher();
3208 }, NETDATA.options.current.idle_between_loops);
3211 var state = NETDATA.options.targets[index];
3213 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3214 if(NETDATA.options.debug.main_loop === true)
3215 console.log('fast rendering...');
3217 state.autoRefresh(function() {
3218 NETDATA.chartRefresherNoParallel(++index);
3222 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3223 NETDATA.options.auto_refresher_fast_weight = 0;
3225 setTimeout(function() {
3226 state.autoRefresh(function() {
3227 NETDATA.chartRefresherNoParallel(++index);
3229 }, NETDATA.options.current.idle_between_charts);
3234 // this is part of the parallel refresher
3235 // its cause is to refresh sequencially all the charts
3236 // that depend on chart library initialization
3237 // it will call the parallel refresher back
3238 // as soon as it sees a chart that its chart library
3240 NETDATA.chartRefresher_uninitialized = function() {
3241 if(NETDATA.options.updated_dom === true) {
3242 // the dom has been updated
3243 // get the dom parts again
3244 NETDATA.parseDom(NETDATA.chartRefresher);
3248 if(NETDATA.options.sequencial.length === 0)
3249 NETDATA.chartRefresher();
3251 var state = NETDATA.options.sequencial.pop();
3252 if(state.library.initialized === true)
3253 NETDATA.chartRefresher();
3255 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3259 NETDATA.chartRefresherWaitTime = function() {
3260 return NETDATA.options.current.idle_parallel_loops;
3263 // the default refresher
3264 // it will create 2 sets of charts:
3265 // - the ones that can be refreshed in parallel
3266 // - the ones that depend on something else
3267 // the first set will be executed in parallel
3268 // the second will be given to NETDATA.chartRefresher_uninitialized()
3269 NETDATA.chartRefresher = function() {
3270 if(NETDATA.options.pause === true) {
3271 // console.log('auto-refresher is paused');
3272 setTimeout(NETDATA.chartRefresher,
3273 NETDATA.chartRefresherWaitTime());
3277 if(typeof NETDATA.options.pauseCallback === 'function') {
3278 // console.log('auto-refresher is calling pauseCallback');
3279 NETDATA.options.pause = true;
3280 NETDATA.options.pauseCallback();
3281 NETDATA.chartRefresher();
3285 if(NETDATA.options.current.parallel_refresher === false) {
3286 NETDATA.chartRefresherNoParallel(0);
3290 if(NETDATA.options.updated_dom === true) {
3291 // the dom has been updated
3292 // get the dom parts again
3293 NETDATA.parseDom(NETDATA.chartRefresher);
3297 var parallel = new Array();
3298 var targets = NETDATA.options.targets;
3299 var len = targets.length;
3302 state = targets[len];
3303 if(state.isVisible() === false || state.running === true)
3306 if(state.library.initialized === false) {
3307 if(state.library.enabled === true) {
3308 state.library.initialize(NETDATA.chartRefresher);
3312 state.error('chart library "' + state.library_name + '" is not enabled.');
3316 parallel.unshift(state);
3319 if(parallel.length > 0) {
3320 // this will execute the jobs in parallel
3321 $(parallel).each(function() {
3326 // run the next refresh iteration
3327 setTimeout(NETDATA.chartRefresher,
3328 NETDATA.chartRefresherWaitTime());
3331 NETDATA.parseDom = function(callback) {
3332 NETDATA.options.last_page_scroll = new Date().getTime();
3333 NETDATA.options.updated_dom = false;
3335 var targets = $('div[data-netdata]'); //.filter(':visible');
3337 if(NETDATA.options.debug.main_loop === true)
3338 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3340 NETDATA.options.targets = new Array();
3341 var len = targets.length;
3343 // the initialization will take care of sizing
3344 // and the "loading..." message
3345 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3348 if(typeof callback === 'function') callback();
3351 // this is the main function - where everything starts
3352 NETDATA.start = function() {
3353 // this should be called only once
3355 NETDATA.options.page_is_visible = true;
3357 $(window).blur(function() {
3358 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3359 NETDATA.options.page_is_visible = false;
3360 if(NETDATA.options.debug.focus === true)
3361 console.log('Lost Focus!');
3365 $(window).focus(function() {
3366 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3367 NETDATA.options.page_is_visible = true;
3368 if(NETDATA.options.debug.focus === true)
3369 console.log('Focus restored!');
3373 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
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('Document has no focus!');
3381 // bootstrap tab switching
3382 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3384 // bootstrap modal switching
3385 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3386 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3388 // bootstrap collapse switching
3389 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3390 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3392 NETDATA.parseDom(NETDATA.chartRefresher);
3394 // Registry initialization
3395 setTimeout(NETDATA.registry.init, 1000);
3398 // ----------------------------------------------------------------------------------------------------------------
3401 NETDATA.peityInitialize = function(callback) {
3402 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3404 url: NETDATA.peity_js,
3409 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3412 NETDATA.chartLibraries.peity.enabled = false;
3413 NETDATA.error(100, NETDATA.peity_js);
3415 .always(function() {
3416 if(typeof callback === "function")
3421 NETDATA.chartLibraries.peity.enabled = false;
3422 if(typeof callback === "function")
3427 NETDATA.peityChartUpdate = function(state, data) {
3428 state.peity_instance.innerHTML = data.result;
3430 if(state.peity_options.stroke !== state.chartColors()[0]) {
3431 state.peity_options.stroke = state.chartColors()[0];
3432 if(state.chart.chart_type === 'line')
3433 state.peity_options.fill = NETDATA.themes.current.background;
3435 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3438 $(state.peity_instance).peity('line', state.peity_options);
3442 NETDATA.peityChartCreate = function(state, data) {
3443 state.peity_instance = document.createElement('div');
3444 state.element_chart.appendChild(state.peity_instance);
3446 var self = $(state.element);
3447 state.peity_options = {
3448 stroke: NETDATA.themes.current.foreground,
3449 strokeWidth: self.data('peity-strokewidth') || 1,
3450 width: state.chartWidth(),
3451 height: state.chartHeight(),
3452 fill: NETDATA.themes.current.foreground
3455 NETDATA.peityChartUpdate(state, data);
3459 // ----------------------------------------------------------------------------------------------------------------
3462 NETDATA.sparklineInitialize = function(callback) {
3463 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3465 url: NETDATA.sparkline_js,
3470 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3473 NETDATA.chartLibraries.sparkline.enabled = false;
3474 NETDATA.error(100, NETDATA.sparkline_js);
3476 .always(function() {
3477 if(typeof callback === "function")
3482 NETDATA.chartLibraries.sparkline.enabled = false;
3483 if(typeof callback === "function")
3488 NETDATA.sparklineChartUpdate = function(state, data) {
3489 state.sparkline_options.width = state.chartWidth();
3490 state.sparkline_options.height = state.chartHeight();
3492 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3496 NETDATA.sparklineChartCreate = function(state, data) {
3497 var self = $(state.element);
3498 var type = self.data('sparkline-type') || 'line';
3499 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3500 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3501 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3502 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3503 var composite = self.data('sparkline-composite') || undefined;
3504 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3505 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3506 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3507 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3508 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3509 var spotColor = self.data('sparkline-spotcolor') || undefined;
3510 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3511 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3512 var spotRadius = self.data('sparkline-spotradius') || undefined;
3513 var valueSpots = self.data('sparkline-valuespots') || undefined;
3514 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3515 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3516 var lineWidth = self.data('sparkline-linewidth') || undefined;
3517 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3518 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3519 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3520 var xvalues = self.data('sparkline-xvalues') || undefined;
3521 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3522 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3523 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3524 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3525 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3526 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3527 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3528 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3529 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3530 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3531 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3532 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3533 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3534 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3535 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3536 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3537 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3538 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3539 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3540 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3541 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3542 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3544 state.sparkline_options = {
3546 lineColor: lineColor,
3547 fillColor: fillColor,
3548 chartRangeMin: chartRangeMin,
3549 chartRangeMax: chartRangeMax,
3550 composite: composite,
3551 enableTagOptions: enableTagOptions,
3552 tagOptionPrefix: tagOptionPrefix,
3553 tagValuesAttribute: tagValuesAttribute,
3554 disableHiddenCheck: disableHiddenCheck,
3555 defaultPixelsPerValue: defaultPixelsPerValue,
3556 spotColor: spotColor,
3557 minSpotColor: minSpotColor,
3558 maxSpotColor: maxSpotColor,
3559 spotRadius: spotRadius,
3560 valueSpots: valueSpots,
3561 highlightSpotColor: highlightSpotColor,
3562 highlightLineColor: highlightLineColor,
3563 lineWidth: lineWidth,
3564 normalRangeMin: normalRangeMin,
3565 normalRangeMax: normalRangeMax,
3566 drawNormalOnTop: drawNormalOnTop,
3568 chartRangeClip: chartRangeClip,
3569 chartRangeMinX: chartRangeMinX,
3570 chartRangeMaxX: chartRangeMaxX,
3571 disableInteraction: disableInteraction,
3572 disableTooltips: disableTooltips,
3573 disableHighlight: disableHighlight,
3574 highlightLighten: highlightLighten,
3575 highlightColor: highlightColor,
3576 tooltipContainer: tooltipContainer,
3577 tooltipClassname: tooltipClassname,
3578 tooltipChartTitle: state.title,
3579 tooltipFormat: tooltipFormat,
3580 tooltipPrefix: tooltipPrefix,
3581 tooltipSuffix: tooltipSuffix,
3582 tooltipSkipNull: tooltipSkipNull,
3583 tooltipValueLookups: tooltipValueLookups,
3584 tooltipFormatFieldlist: tooltipFormatFieldlist,
3585 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3586 numberFormatter: numberFormatter,
3587 numberDigitGroupSep: numberDigitGroupSep,
3588 numberDecimalMark: numberDecimalMark,
3589 numberDigitGroupCount: numberDigitGroupCount,
3590 animatedZooms: animatedZooms,
3591 width: state.chartWidth(),
3592 height: state.chartHeight()
3595 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3599 // ----------------------------------------------------------------------------------------------------------------
3606 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3607 if(after < state.netdata_first)
3608 after = state.netdata_first;
3610 if(before > state.netdata_last)
3611 before = state.netdata_last;
3613 state.setMode('zoom');
3614 state.globalSelectionSyncStop();
3615 state.globalSelectionSyncDelay();
3616 state.dygraph_user_action = true;
3617 state.dygraph_force_zoom = true;
3618 state.updateChartPanOrZoom(after, before);
3619 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3622 NETDATA.dygraphSetSelection = function(state, t) {
3623 if(typeof state.dygraph_instance !== 'undefined') {
3624 var r = state.calculateRowForTime(t);
3626 state.dygraph_instance.setSelection(r);
3628 state.dygraph_instance.clearSelection();
3629 state.legendShowUndefined();
3636 NETDATA.dygraphClearSelection = function(state, t) {
3637 if(typeof state.dygraph_instance !== 'undefined') {
3638 state.dygraph_instance.clearSelection();
3643 NETDATA.dygraphSmoothInitialize = function(callback) {
3645 url: NETDATA.dygraph_smooth_js,
3650 NETDATA.dygraph.smooth = true;
3651 smoothPlotter.smoothing = 0.3;
3654 NETDATA.dygraph.smooth = false;
3656 .always(function() {
3657 if(typeof callback === "function")
3662 NETDATA.dygraphInitialize = function(callback) {
3663 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3665 url: NETDATA.dygraph_js,
3670 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3673 NETDATA.chartLibraries.dygraph.enabled = false;
3674 NETDATA.error(100, NETDATA.dygraph_js);
3676 .always(function() {
3677 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3678 NETDATA.dygraphSmoothInitialize(callback);
3679 else if(typeof callback === "function")
3684 NETDATA.chartLibraries.dygraph.enabled = false;
3685 if(typeof callback === "function")
3690 NETDATA.dygraphChartUpdate = function(state, data) {
3691 var dygraph = state.dygraph_instance;
3693 if(typeof dygraph === 'undefined')
3694 return NETDATA.dygraphChartCreate(state, data);
3696 // when the chart is not visible, and hidden
3697 // if there is a window resize, dygraph detects
3698 // its element size as 0x0.
3699 // this will make it re-appear properly
3701 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3705 file: data.result.data,
3706 colors: state.chartColors(),
3707 labels: data.result.labels,
3708 labelsDivWidth: state.chartWidth() - 70,
3709 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3712 if(state.dygraph_force_zoom === true) {
3713 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3714 state.log('dygraphChartUpdate() forced zoom update');
3716 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3717 options.valueRange = null;
3718 options.isZoomedIgnoreProgrammaticZoom = true;
3719 state.dygraph_force_zoom = false;
3721 else if(state.current.name !== 'auto') {
3722 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3723 state.log('dygraphChartUpdate() loose update');
3726 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3727 state.log('dygraphChartUpdate() strict update');
3729 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3730 options.valueRange = null;
3731 options.isZoomedIgnoreProgrammaticZoom = true;
3734 if(state.dygraph_smooth_eligible === true) {
3735 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3736 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3737 NETDATA.dygraphChartCreate(state, data);
3742 dygraph.updateOptions(options);
3744 state.dygraph_last_rendered = new Date().getTime();
3748 NETDATA.dygraphChartCreate = function(state, data) {
3749 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3750 state.log('dygraphChartCreate()');
3752 var self = $(state.element);
3754 var chart_type = state.chart.chart_type;
3755 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3756 chart_type = self.data('dygraph-type') || chart_type;
3758 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3759 smooth = self.data('dygraph-smooth') || smooth;
3761 if(NETDATA.dygraph.smooth === false)
3764 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3765 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3767 state.dygraph_options = {
3768 colors: self.data('dygraph-colors') || state.chartColors(),
3770 // leave a few pixels empty on the right of the chart
3771 rightGap: self.data('dygraph-rightgap') || 5,
3772 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3773 showRoller: self.data('dygraph-showroller') || false,
3775 title: self.data('dygraph-title') || state.title,
3776 titleHeight: self.data('dygraph-titleheight') || 19,
3778 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3779 labels: data.result.labels,
3780 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3781 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3782 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3783 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3784 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3787 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3788 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3790 ylabel: state.units,
3791 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3793 // the function to plot the chart
3796 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3797 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3798 strokePattern: self.data('dygraph-strokepattern') || undefined,
3800 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3801 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3802 drawPoints: self.data('dygraph-drawpoints') || false,
3804 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3805 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3807 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3808 pointSize: self.data('dygraph-pointsize') || 1,
3810 // enabling this makes the chart with little square lines
3811 stepPlot: self.data('dygraph-stepplot') || false,
3813 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3814 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3815 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3817 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3818 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3819 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3820 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3822 drawAxis: self.data('dygraph-drawaxis') || true,
3823 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3824 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3825 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3827 drawGrid: self.data('dygraph-drawgrid') || true,
3828 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3829 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3830 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3831 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3832 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3834 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3835 sigFigs: self.data('dygraph-sigfigs') || null,
3836 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3837 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3839 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3840 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3841 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3843 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3844 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3848 ticker: Dygraph.dateTicker,
3849 axisLabelFormatter: function (d, gran) {
3850 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3852 valueFormatter: function (ms) {
3853 var d = new Date(ms);
3854 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3855 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3860 valueFormatter: function (x) {
3861 // we format legends with the state object
3862 // no need to do anything here
3863 // return (Math.round(x*100) / 100).toLocaleString();
3864 // return state.legendFormatValue(x);
3869 legendFormatter: function(data) {
3870 var elements = state.element_legend_childs;
3872 // if the hidden div is not there
3873 // we are not managing the legend
3874 if(elements.hidden === null) return;
3876 if (typeof data.x !== 'undefined') {
3877 state.legendSetDate(data.x);
3878 var i = data.series.length;
3880 var series = data.series[i];
3881 if(!series.isVisible) continue;
3882 state.legendSetLabelValue(series.label, series.y);
3888 drawCallback: function(dygraph, is_initial) {
3889 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3890 state.dygraph_user_action = false;
3892 var x_range = dygraph.xAxisRange();
3893 var after = Math.round(x_range[0]);
3894 var before = Math.round(x_range[1]);
3896 if(NETDATA.options.debug.dygraph === true)
3897 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3899 if(before <= state.netdata_last && after >= state.netdata_first)
3900 state.updateChartPanOrZoom(after, before);
3903 zoomCallback: function(minDate, maxDate, yRanges) {
3904 if(NETDATA.options.debug.dygraph === true)
3905 state.log('dygraphZoomCallback()');
3907 state.globalSelectionSyncStop();
3908 state.globalSelectionSyncDelay();
3909 state.setMode('zoom');
3911 // refresh it to the greatest possible zoom level
3912 state.dygraph_user_action = true;
3913 state.dygraph_force_zoom = true;
3914 state.updateChartPanOrZoom(minDate, maxDate);
3916 highlightCallback: function(event, x, points, row, seriesName) {
3917 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3918 state.log('dygraphHighlightCallback()');
3922 // there is a bug in dygraph when the chart is zoomed enough
3923 // the time it thinks is selected is wrong
3924 // here we calculate the time t based on the row number selected
3926 var t = state.data_after + row * state.data_update_every;
3927 // 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);
3929 state.globalSelectionSync(x);
3931 // fix legend zIndex using the internal structures of dygraph legend module
3932 // this works, but it is a hack!
3933 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3935 unhighlightCallback: function(event) {
3936 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3937 state.log('dygraphUnhighlightCallback()');
3939 state.unpauseChart();
3940 state.globalSelectionSyncStop();
3942 interactionModel : {
3943 mousedown: function(event, dygraph, context) {
3944 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3945 state.log('interactionModel.mousedown()');
3947 state.dygraph_user_action = true;
3948 state.globalSelectionSyncStop();
3950 if(NETDATA.options.debug.dygraph === true)
3951 state.log('dygraphMouseDown()');
3953 // Right-click should not initiate a zoom.
3954 if(event.button && event.button === 2) return;
3956 context.initializeMouseDown(event, dygraph, context);
3958 if(event.button && event.button === 1) {
3959 if (event.altKey || event.shiftKey) {
3960 state.setMode('pan');
3961 state.globalSelectionSyncDelay();
3962 Dygraph.startPan(event, dygraph, context);
3965 state.setMode('zoom');
3966 state.globalSelectionSyncDelay();
3967 Dygraph.startZoom(event, dygraph, context);
3971 if (event.altKey || event.shiftKey) {
3972 state.setMode('zoom');
3973 state.globalSelectionSyncDelay();
3974 Dygraph.startZoom(event, dygraph, context);
3977 state.setMode('pan');
3978 state.globalSelectionSyncDelay();
3979 Dygraph.startPan(event, dygraph, context);
3983 mousemove: function(event, dygraph, context) {
3984 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3985 state.log('interactionModel.mousemove()');
3987 if(context.isPanning) {
3988 state.dygraph_user_action = true;
3989 state.globalSelectionSyncStop();
3990 state.globalSelectionSyncDelay();
3991 state.setMode('pan');
3992 Dygraph.movePan(event, dygraph, context);
3994 else if(context.isZooming) {
3995 state.dygraph_user_action = true;
3996 state.globalSelectionSyncStop();
3997 state.globalSelectionSyncDelay();
3998 state.setMode('zoom');
3999 Dygraph.moveZoom(event, dygraph, context);
4002 mouseup: function(event, dygraph, context) {
4003 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4004 state.log('interactionModel.mouseup()');
4006 if (context.isPanning) {
4007 state.dygraph_user_action = true;
4008 state.globalSelectionSyncDelay();
4009 Dygraph.endPan(event, dygraph, context);
4011 else if (context.isZooming) {
4012 state.dygraph_user_action = true;
4013 state.globalSelectionSyncDelay();
4014 Dygraph.endZoom(event, dygraph, context);
4017 click: function(event, dygraph, context) {
4018 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4019 state.log('interactionModel.click()');
4021 event.preventDefault();
4023 dblclick: function(event, dygraph, context) {
4024 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4025 state.log('interactionModel.dblclick()');
4026 NETDATA.resetAllCharts(state);
4028 mousewheel: function(event, dygraph, context) {
4029 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4030 state.log('interactionModel.mousewheel()');
4032 // Take the offset of a mouse event on the dygraph canvas and
4033 // convert it to a pair of percentages from the bottom left.
4034 // (Not top left, bottom is where the lower value is.)
4035 function offsetToPercentage(g, offsetX, offsetY) {
4036 // This is calculating the pixel offset of the leftmost date.
4037 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4038 var yar0 = g.yAxisRange(0);
4040 // This is calculating the pixel of the higest value. (Top pixel)
4041 var yOffset = g.toDomCoords(null, yar0[1])[1];
4043 // x y w and h are relative to the corner of the drawing area,
4044 // so that the upper corner of the drawing area is (0, 0).
4045 var x = offsetX - xOffset;
4046 var y = offsetY - yOffset;
4048 // This is computing the rightmost pixel, effectively defining the
4050 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4052 // This is computing the lowest pixel, effectively defining the height.
4053 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4055 // Percentage from the left.
4056 var xPct = w === 0 ? 0 : (x / w);
4057 // Percentage from the top.
4058 var yPct = h === 0 ? 0 : (y / h);
4060 // The (1-) part below changes it from "% distance down from the top"
4061 // to "% distance up from the bottom".
4062 return [xPct, (1-yPct)];
4065 // Adjusts [x, y] toward each other by zoomInPercentage%
4066 // Split it so the left/bottom axis gets xBias/yBias of that change and
4067 // tight/top gets (1-xBias)/(1-yBias) of that change.
4069 // If a bias is missing it splits it down the middle.
4070 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4071 xBias = xBias || 0.5;
4072 yBias = yBias || 0.5;
4074 function adjustAxis(axis, zoomInPercentage, bias) {
4075 var delta = axis[1] - axis[0];
4076 var increment = delta * zoomInPercentage;
4077 var foo = [increment * bias, increment * (1-bias)];
4079 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4082 var yAxes = g.yAxisRanges();
4084 for (var i = 0; i < yAxes.length; i++) {
4085 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4088 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4091 if(event.altKey || event.shiftKey) {
4092 state.dygraph_user_action = true;
4094 state.globalSelectionSyncStop();
4095 state.globalSelectionSyncDelay();
4097 // http://dygraphs.com/gallery/interaction-api.js
4098 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4099 var percentage = normal / 50;
4101 if (!(event.offsetX && event.offsetY)){
4102 event.offsetX = event.layerX - event.target.offsetLeft;
4103 event.offsetY = event.layerY - event.target.offsetTop;
4106 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4107 var xPct = percentages[0];
4108 var yPct = percentages[1];
4110 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4112 var after = new_x_range[0];
4113 var before = new_x_range[1];
4115 var first = state.netdata_first + state.data_update_every;
4116 var last = state.netdata_last + state.data_update_every;
4119 after -= (before - last);
4126 state.setMode('zoom');
4127 if(state.updateChartPanOrZoom(after, before) === true)
4128 dygraph.updateOptions({ dateWindow: [ after, before ] });
4130 event.preventDefault();
4133 touchstart: function(event, dygraph, context) {
4134 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4135 state.log('interactionModel.touchstart()');
4137 state.dygraph_user_action = true;
4138 state.setMode('zoom');
4141 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4143 // we overwrite the touch directions at the end, to overwrite
4144 // the internal default of dygraphs
4145 context.touchDirections = { x: true, y: false };
4147 state.dygraph_last_touch_start = new Date().getTime();
4148 state.dygraph_last_touch_move = 0;
4150 if(typeof event.touches[0].pageX === 'number')
4151 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4153 state.dygraph_last_touch_page_x = 0;
4155 touchmove: function(event, dygraph, context) {
4156 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4157 state.log('interactionModel.touchmove()');
4159 state.dygraph_user_action = true;
4160 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4162 state.dygraph_last_touch_move = new Date().getTime();
4164 touchend: function(event, dygraph, context) {
4165 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4166 state.log('interactionModel.touchend()');
4168 state.dygraph_user_action = true;
4169 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4171 // if it didn't move, it is a selection
4172 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4173 // internal api of dygraphs
4174 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4175 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4176 if(NETDATA.dygraphSetSelection(state, t) === true)
4177 state.globalSelectionSync(t);
4180 // if it was double tap within double click time, reset the charts
4181 var now = new Date().getTime();
4182 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4183 if(state.dygraph_last_touch_move === 0) {
4184 var dt = now - state.dygraph_last_touch_end;
4185 if(dt <= NETDATA.options.current.double_click_speed)
4186 NETDATA.resetAllCharts(state);
4190 // remember the timestamp of the last touch end
4191 state.dygraph_last_touch_end = now;
4196 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4197 state.dygraph_options.drawGrid = false;
4198 state.dygraph_options.drawAxis = false;
4199 state.dygraph_options.title = undefined;
4200 state.dygraph_options.units = undefined;
4201 state.dygraph_options.ylabel = undefined;
4202 state.dygraph_options.yLabelWidth = 0;
4203 state.dygraph_options.labelsDivWidth = 120;
4204 state.dygraph_options.labelsDivStyles.width = '120px';
4205 state.dygraph_options.labelsSeparateLines = true;
4206 state.dygraph_options.rightGap = 0;
4209 if(smooth === true) {
4210 state.dygraph_smooth_eligible = true;
4212 if(NETDATA.options.current.smooth_plot === true)
4213 state.dygraph_options.plotter = smoothPlotter;
4215 else state.dygraph_smooth_eligible = false;
4217 state.dygraph_instance = new Dygraph(state.element_chart,
4218 data.result.data, state.dygraph_options);
4220 state.dygraph_force_zoom = false;
4221 state.dygraph_user_action = false;
4222 state.dygraph_last_rendered = new Date().getTime();
4226 // ----------------------------------------------------------------------------------------------------------------
4229 NETDATA.morrisInitialize = function(callback) {
4230 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4232 // morris requires raphael
4233 if(!NETDATA.chartLibraries.raphael.initialized) {
4234 if(NETDATA.chartLibraries.raphael.enabled) {
4235 NETDATA.raphaelInitialize(function() {
4236 NETDATA.morrisInitialize(callback);
4240 NETDATA.chartLibraries.morris.enabled = false;
4241 if(typeof callback === "function")
4246 NETDATA._loadCSS(NETDATA.morris_css);
4249 url: NETDATA.morris_js,
4254 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4257 NETDATA.chartLibraries.morris.enabled = false;
4258 NETDATA.error(100, NETDATA.morris_js);
4260 .always(function() {
4261 if(typeof callback === "function")
4267 NETDATA.chartLibraries.morris.enabled = false;
4268 if(typeof callback === "function")
4273 NETDATA.morrisChartUpdate = function(state, data) {
4274 state.morris_instance.setData(data.result.data);
4278 NETDATA.morrisChartCreate = function(state, data) {
4280 state.morris_options = {
4281 element: state.element_chart.id,
4282 data: data.result.data,
4284 ykeys: data.dimension_names,
4285 labels: data.dimension_names,
4291 continuousLine: false,
4292 behaveLikeLine: false
4295 if(state.chart.chart_type === 'line')
4296 state.morris_instance = new Morris.Line(state.morris_options);
4298 else if(state.chart.chart_type === 'area') {
4299 state.morris_options.behaveLikeLine = true;
4300 state.morris_instance = new Morris.Area(state.morris_options);
4303 state.morris_instance = new Morris.Area(state.morris_options);
4308 // ----------------------------------------------------------------------------------------------------------------
4311 NETDATA.raphaelInitialize = function(callback) {
4312 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4314 url: NETDATA.raphael_js,
4319 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4322 NETDATA.chartLibraries.raphael.enabled = false;
4323 NETDATA.error(100, NETDATA.raphael_js);
4325 .always(function() {
4326 if(typeof callback === "function")
4331 NETDATA.chartLibraries.raphael.enabled = false;
4332 if(typeof callback === "function")
4337 NETDATA.raphaelChartUpdate = function(state, data) {
4338 $(state.element_chart).raphael(data.result, {
4339 width: state.chartWidth(),
4340 height: state.chartHeight()
4346 NETDATA.raphaelChartCreate = function(state, data) {
4347 $(state.element_chart).raphael(data.result, {
4348 width: state.chartWidth(),
4349 height: state.chartHeight()
4355 // ----------------------------------------------------------------------------------------------------------------
4358 NETDATA.c3Initialize = function(callback) {
4359 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4362 if(!NETDATA.chartLibraries.d3.initialized) {
4363 if(NETDATA.chartLibraries.d3.enabled) {
4364 NETDATA.d3Initialize(function() {
4365 NETDATA.c3Initialize(callback);
4369 NETDATA.chartLibraries.c3.enabled = false;
4370 if(typeof callback === "function")
4375 NETDATA._loadCSS(NETDATA.c3_css);
4383 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4386 NETDATA.chartLibraries.c3.enabled = false;
4387 NETDATA.error(100, NETDATA.c3_js);
4389 .always(function() {
4390 if(typeof callback === "function")
4396 NETDATA.chartLibraries.c3.enabled = false;
4397 if(typeof callback === "function")
4402 NETDATA.c3ChartUpdate = function(state, data) {
4403 state.c3_instance.destroy();
4404 return NETDATA.c3ChartCreate(state, data);
4406 //state.c3_instance.load({
4407 // rows: data.result,
4414 NETDATA.c3ChartCreate = function(state, data) {
4416 state.element_chart.id = 'c3-' + state.uuid;
4417 // console.log('id = ' + state.element_chart.id);
4419 state.c3_instance = c3.generate({
4420 bindto: '#' + state.element_chart.id,
4422 width: state.chartWidth(),
4423 height: state.chartHeight()
4426 pattern: state.chartColors()
4431 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4437 format: function(x) {
4438 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4465 // console.log(state.c3_instance);
4470 // ----------------------------------------------------------------------------------------------------------------
4473 NETDATA.d3Initialize = function(callback) {
4474 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4481 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4484 NETDATA.chartLibraries.d3.enabled = false;
4485 NETDATA.error(100, NETDATA.d3_js);
4487 .always(function() {
4488 if(typeof callback === "function")
4493 NETDATA.chartLibraries.d3.enabled = false;
4494 if(typeof callback === "function")
4499 NETDATA.d3ChartUpdate = function(state, data) {
4503 NETDATA.d3ChartCreate = function(state, data) {
4507 // ----------------------------------------------------------------------------------------------------------------
4510 NETDATA.googleInitialize = function(callback) {
4511 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4513 url: NETDATA.google_js,
4518 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4519 google.load('visualization', '1.1', {
4520 'packages': ['corechart', 'controls'],
4521 'callback': callback
4525 NETDATA.chartLibraries.google.enabled = false;
4526 NETDATA.error(100, NETDATA.google_js);
4527 if(typeof callback === "function")
4532 NETDATA.chartLibraries.google.enabled = false;
4533 if(typeof callback === "function")
4538 NETDATA.googleChartUpdate = function(state, data) {
4539 var datatable = new google.visualization.DataTable(data.result);
4540 state.google_instance.draw(datatable, state.google_options);
4544 NETDATA.googleChartCreate = function(state, data) {
4545 var datatable = new google.visualization.DataTable(data.result);
4547 state.google_options = {
4548 colors: state.chartColors(),
4550 // do not set width, height - the chart resizes itself
4551 //width: state.chartWidth(),
4552 //height: state.chartHeight(),
4557 // title: "Time of Day",
4558 // format:'HH:mm:ss',
4559 viewWindowMode: 'maximized',
4571 viewWindowMode: 'pretty',
4586 focusTarget: 'category',
4593 titlePosition: 'out',
4604 curveType: 'function',
4609 switch(state.chart.chart_type) {
4611 state.google_options.vAxis.viewWindowMode = 'maximized';
4612 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4613 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4617 state.google_options.isStacked = true;
4618 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4619 state.google_options.vAxis.viewWindowMode = 'maximized';
4620 state.google_options.vAxis.minValue = null;
4621 state.google_options.vAxis.maxValue = null;
4622 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4627 state.google_options.lineWidth = 2;
4628 state.google_instance = new google.visualization.LineChart(state.element_chart);
4632 state.google_instance.draw(datatable, state.google_options);
4636 // ----------------------------------------------------------------------------------------------------------------
4638 NETDATA.percentFromValueMax = function(value, max) {
4639 if(value === null) value = 0;
4640 if(max < value) max = value;
4644 pcent = Math.round(value * 100 / max);
4645 if(pcent === 0 && value > 0) pcent = 1;
4651 // ----------------------------------------------------------------------------------------------------------------
4654 NETDATA.easypiechartInitialize = function(callback) {
4655 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4657 url: NETDATA.easypiechart_js,
4662 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4665 NETDATA.chartLibraries.easypiechart.enabled = false;
4666 NETDATA.error(100, NETDATA.easypiechart_js);
4668 .always(function() {
4669 if(typeof callback === "function")
4674 NETDATA.chartLibraries.easypiechart.enabled = false;
4675 if(typeof callback === "function")
4680 NETDATA.easypiechartClearSelection = function(state) {
4681 if(typeof state.easyPieChartEvent !== 'undefined') {
4682 if(state.easyPieChartEvent.timer !== null)
4683 clearTimeout(state.easyPieChartEvent.timer);
4685 state.easyPieChartEvent.timer = null;
4688 if(state.isAutoRefreshable() === true && state.data !== null) {
4689 NETDATA.easypiechartChartUpdate(state, state.data);
4692 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4693 state.easyPieChart_instance.update(0);
4695 state.easyPieChart_instance.enableAnimation();
4700 NETDATA.easypiechartSetSelection = function(state, t) {
4701 if(state.timeIsVisible(t) !== true)
4702 return NETDATA.easypiechartClearSelection(state);
4704 var slot = state.calculateRowForTime(t);
4705 if(slot < 0 || slot >= state.data.result.length)
4706 return NETDATA.easypiechartClearSelection(state);
4708 if(typeof state.easyPieChartEvent === 'undefined') {
4709 state.easyPieChartEvent = {
4716 var value = state.data.result[state.data.result.length - 1 - slot];
4717 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4718 var pcent = NETDATA.percentFromValueMax(value, max);
4720 state.easyPieChartEvent.value = value;
4721 state.easyPieChartEvent.pcent = pcent;
4722 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4724 if(state.easyPieChartEvent.timer === null) {
4725 state.easyPieChart_instance.disableAnimation();
4727 state.easyPieChartEvent.timer = setTimeout(function() {
4728 state.easyPieChartEvent.timer = null;
4729 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4730 }, NETDATA.options.current.charts_selection_animation_delay);
4736 NETDATA.easypiechartChartUpdate = function(state, data) {
4737 var value, max, pcent;
4739 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4745 value = data.result[0];
4746 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4747 pcent = NETDATA.percentFromValueMax(value, max);
4750 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4751 state.easyPieChart_instance.update(pcent);
4755 NETDATA.easypiechartChartCreate = function(state, data) {
4756 var self = $(state.element);
4757 var chart = $(state.element_chart);
4759 var value = data.result[0];
4760 var max = self.data('easypiechart-max-value') || null;
4761 var adjust = self.data('easypiechart-adjust') || null;
4765 state.easyPieChartMax = null;
4768 state.easyPieChartMax = max;
4770 var pcent = NETDATA.percentFromValueMax(value, max);
4772 chart.data('data-percent', pcent);
4776 case 'width': size = state.chartHeight(); break;
4777 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4778 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4780 default: size = state.chartWidth(); break;
4782 state.element.style.width = size + 'px';
4783 state.element.style.height = size + 'px';
4785 var stroke = Math.floor(size / 22);
4786 if(stroke < 3) stroke = 2;
4788 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4789 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4790 state.easyPieChartLabel = document.createElement('span');
4791 state.easyPieChartLabel.className = 'easyPieChartLabel';
4792 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4793 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4794 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4795 state.element_chart.appendChild(state.easyPieChartLabel);
4797 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4798 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4799 state.easyPieChartTitle = document.createElement('span');
4800 state.easyPieChartTitle.className = 'easyPieChartTitle';
4801 state.easyPieChartTitle.innerHTML = state.title;
4802 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4803 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4804 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4805 state.element_chart.appendChild(state.easyPieChartTitle);
4807 var unitfontsize = Math.round(titlefontsize * 0.9);
4808 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4809 state.easyPieChartUnits = document.createElement('span');
4810 state.easyPieChartUnits.className = 'easyPieChartUnits';
4811 state.easyPieChartUnits.innerHTML = state.units;
4812 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4813 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4814 state.element_chart.appendChild(state.easyPieChartUnits);
4816 chart.easyPieChart({
4817 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4818 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4819 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4820 scaleLength: self.data('easypiechart-scalelength') || 5,
4821 lineCap: self.data('easypiechart-linecap') || 'round',
4822 lineWidth: self.data('easypiechart-linewidth') || stroke,
4823 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4824 size: self.data('easypiechart-size') || size,
4825 rotate: self.data('easypiechart-rotate') || 0,
4826 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4827 easing: self.data('easypiechart-easing') || undefined
4830 // when we just re-create the chart
4831 // do not animate the first update
4833 if(typeof state.easyPieChart_instance !== 'undefined')
4836 state.easyPieChart_instance = chart.data('easyPieChart');
4837 if(animate === false) state.easyPieChart_instance.disableAnimation();
4838 state.easyPieChart_instance.update(pcent);
4839 if(animate === false) state.easyPieChart_instance.enableAnimation();
4843 // ----------------------------------------------------------------------------------------------------------------
4846 NETDATA.gaugeInitialize = function(callback) {
4847 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4849 url: NETDATA.gauge_js,
4854 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4857 NETDATA.chartLibraries.gauge.enabled = false;
4858 NETDATA.error(100, NETDATA.gauge_js);
4860 .always(function() {
4861 if(typeof callback === "function")
4866 NETDATA.chartLibraries.gauge.enabled = false;
4867 if(typeof callback === "function")
4872 NETDATA.gaugeAnimation = function(state, status) {
4875 if(typeof status === 'boolean' && status === false)
4877 else if(typeof status === 'number')
4880 state.gauge_instance.animationSpeed = speed;
4881 state.___gaugeOld__.speed = speed;
4884 NETDATA.gaugeSet = function(state, value, min, max) {
4885 if(typeof value !== 'number') value = 0;
4886 if(typeof min !== 'number') min = 0;
4887 if(typeof max !== 'number') max = 0;
4888 if(value > max) max = value;
4889 if(value < min) min = value;
4898 // gauge.js has an issue if the needle
4899 // is smaller than min or larger than max
4900 // when we set the new values
4901 // the needle will go crazy
4903 // to prevent it, we always feed it
4904 // with a percentage, so that the needle
4905 // is always between min and max
4906 var pcent = (value - min) * 100 / (max - min);
4908 // these should never happen
4909 if(pcent < 0) pcent = 0;
4910 if(pcent > 100) pcent = 100;
4912 state.gauge_instance.set(pcent);
4914 state.___gaugeOld__.value = value;
4915 state.___gaugeOld__.min = min;
4916 state.___gaugeOld__.max = max;
4919 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4920 if(state.___gaugeOld__.valueLabel !== value) {
4921 state.___gaugeOld__.valueLabel = value;
4922 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4924 if(state.___gaugeOld__.minLabel !== min) {
4925 state.___gaugeOld__.minLabel = min;
4926 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4928 if(state.___gaugeOld__.maxLabel !== max) {
4929 state.___gaugeOld__.maxLabel = max;
4930 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4934 NETDATA.gaugeClearSelection = function(state) {
4935 if(typeof state.gaugeEvent !== 'undefined') {
4936 if(state.gaugeEvent.timer !== null)
4937 clearTimeout(state.gaugeEvent.timer);
4939 state.gaugeEvent.timer = null;
4942 if(state.isAutoRefreshable() === true && state.data !== null) {
4943 NETDATA.gaugeChartUpdate(state, state.data);
4946 NETDATA.gaugeAnimation(state, false);
4947 NETDATA.gaugeSet(state, null, null, null);
4948 NETDATA.gaugeSetLabels(state, null, null, null);
4951 NETDATA.gaugeAnimation(state, true);
4955 NETDATA.gaugeSetSelection = function(state, t) {
4956 if(state.timeIsVisible(t) !== true)
4957 return NETDATA.gaugeClearSelection(state);
4959 var slot = state.calculateRowForTime(t);
4960 if(slot < 0 || slot >= state.data.result.length)
4961 return NETDATA.gaugeClearSelection(state);
4963 if(typeof state.gaugeEvent === 'undefined') {
4964 state.gaugeEvent = {
4972 var value = state.data.result[state.data.result.length - 1 - slot];
4973 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4976 state.gaugeEvent.value = value;
4977 state.gaugeEvent.max = max;
4978 state.gaugeEvent.min = min;
4979 NETDATA.gaugeSetLabels(state, value, min, max);
4981 if(state.gaugeEvent.timer === null) {
4982 NETDATA.gaugeAnimation(state, false);
4984 state.gaugeEvent.timer = setTimeout(function() {
4985 state.gaugeEvent.timer = null;
4986 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4987 }, NETDATA.options.current.charts_selection_animation_delay);
4993 NETDATA.gaugeChartUpdate = function(state, data) {
4994 var value, min, max;
4996 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5000 NETDATA.gaugeSetLabels(state, null, null, null);
5003 value = data.result[0];
5005 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5006 if(value > max) max = value;
5007 NETDATA.gaugeSetLabels(state, value, min, max);
5010 NETDATA.gaugeSet(state, value, min, max);
5014 NETDATA.gaugeChartCreate = function(state, data) {
5015 var self = $(state.element);
5016 // var chart = $(state.element_chart);
5018 var value = data.result[0];
5019 var max = self.data('gauge-max-value') || null;
5020 var adjust = self.data('gauge-adjust') || null;
5021 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5022 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5023 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5024 var stopColor = self.data('gauge-stop-color') || void 0;
5025 var generateGradient = self.data('gauge-generate-gradient') || false;
5029 state.gaugeMax = null;
5032 state.gaugeMax = max;
5034 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5036 // case 'width': width = height * ratio; break;
5038 // default: height = width / ratio; break;
5040 //state.element.style.width = width.toString() + 'px';
5041 //state.element.style.height = height.toString() + 'px';
5046 lines: 12, // The number of lines to draw
5047 angle: 0.15, // The length of each line
5048 lineWidth: 0.44, // 0.44 The line thickness
5050 length: 0.8, // 0.9 The radius of the inner circle
5051 strokeWidth: 0.035, // The rotation offset
5052 color: pointerColor // Fill color
5054 colorStart: startColor, // Colors
5055 colorStop: stopColor, // just experiment with them
5056 strokeColor: strokeColor, // to see which ones work best for you
5058 generateGradient: (generateGradient === true)?true:false,
5062 if (generateGradient.constructor === Array) {
5064 // data-gauge-generate-gradient="[0, 50, 100]"
5065 // data-gauge-gradient-percent-color-0="#FFFFFF"
5066 // data-gauge-gradient-percent-color-50="#999900"
5067 // data-gauge-gradient-percent-color-100="#000000"
5069 options.percentColors = new Array();
5070 var len = generateGradient.length;
5072 var pcent = generateGradient[len];
5073 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5074 if(color !== false) {
5075 var a = new Array();
5078 options.percentColors.unshift(a);
5081 if(options.percentColors.length === 0)
5082 delete options.percentColors;
5084 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5085 options.percentColors = [
5086 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5087 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5088 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5089 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5090 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5091 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5092 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5093 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5094 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5095 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5096 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5099 state.gauge_canvas = document.createElement('canvas');
5100 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5101 state.gauge_canvas.className = 'gaugeChart';
5102 state.gauge_canvas.width = width;
5103 state.gauge_canvas.height = height;
5104 state.element_chart.appendChild(state.gauge_canvas);
5106 var valuefontsize = Math.floor(height / 6);
5107 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5108 state.gaugeChartLabel = document.createElement('span');
5109 state.gaugeChartLabel.className = 'gaugeChartLabel';
5110 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5111 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5112 state.element_chart.appendChild(state.gaugeChartLabel);
5114 var titlefontsize = Math.round(valuefontsize / 2);
5116 state.gaugeChartTitle = document.createElement('span');
5117 state.gaugeChartTitle.className = 'gaugeChartTitle';
5118 state.gaugeChartTitle.innerHTML = state.title;
5119 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5120 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5121 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5122 state.element_chart.appendChild(state.gaugeChartTitle);
5124 var unitfontsize = Math.round(titlefontsize * 0.9);
5125 state.gaugeChartUnits = document.createElement('span');
5126 state.gaugeChartUnits.className = 'gaugeChartUnits';
5127 state.gaugeChartUnits.innerHTML = state.units;
5128 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5129 state.element_chart.appendChild(state.gaugeChartUnits);
5131 state.gaugeChartMin = document.createElement('span');
5132 state.gaugeChartMin.className = 'gaugeChartMin';
5133 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5134 state.element_chart.appendChild(state.gaugeChartMin);
5136 state.gaugeChartMax = document.createElement('span');
5137 state.gaugeChartMax.className = 'gaugeChartMax';
5138 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5139 state.element_chart.appendChild(state.gaugeChartMax);
5141 // when we just re-create the chart
5142 // do not animate the first update
5144 if(typeof state.gauge_instance !== 'undefined')
5147 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5149 state.___gaugeOld__ = {
5158 // we will always feed a percentage
5159 state.gauge_instance.minValue = 0;
5160 state.gauge_instance.maxValue = 100;
5162 NETDATA.gaugeAnimation(state, animate);
5163 NETDATA.gaugeSet(state, value, 0, max);
5164 NETDATA.gaugeSetLabels(state, value, 0, max);
5165 NETDATA.gaugeAnimation(state, true);
5169 // ----------------------------------------------------------------------------------------------------------------
5170 // Charts Libraries Registration
5172 NETDATA.chartLibraries = {
5174 initialize: NETDATA.dygraphInitialize,
5175 create: NETDATA.dygraphChartCreate,
5176 update: NETDATA.dygraphChartUpdate,
5177 resize: function(state) {
5178 if(typeof state.dygraph_instance.resize === 'function')
5179 state.dygraph_instance.resize();
5181 setSelection: NETDATA.dygraphSetSelection,
5182 clearSelection: NETDATA.dygraphClearSelection,
5183 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5186 format: function(state) { return 'json'; },
5187 options: function(state) { return 'ms|flip'; },
5188 legend: function(state) {
5189 if(this.isSparkline(state) === false)
5190 return 'right-side';
5194 autoresize: function(state) { return true; },
5195 max_updates_to_recreate: function(state) { return 5000; },
5196 track_colors: function(state) { return true; },
5197 pixels_per_point: function(state) {
5198 if(this.isSparkline(state) === false)
5204 isSparkline: function(state) {
5205 if(typeof state.dygraph_sparkline === 'undefined') {
5206 var t = $(state.element).data('dygraph-theme');
5207 if(t === 'sparkline')
5208 state.dygraph_sparkline = true;
5210 state.dygraph_sparkline = false;
5212 return state.dygraph_sparkline;
5216 initialize: NETDATA.sparklineInitialize,
5217 create: NETDATA.sparklineChartCreate,
5218 update: NETDATA.sparklineChartUpdate,
5220 setSelection: undefined, // function(state, t) { return true; },
5221 clearSelection: undefined, // function(state) { return true; },
5222 toolboxPanAndZoom: null,
5225 format: function(state) { return 'array'; },
5226 options: function(state) { return 'flip|abs'; },
5227 legend: function(state) { return null; },
5228 autoresize: function(state) { return false; },
5229 max_updates_to_recreate: function(state) { return 5000; },
5230 track_colors: function(state) { return false; },
5231 pixels_per_point: function(state) { return 3; }
5234 initialize: NETDATA.peityInitialize,
5235 create: NETDATA.peityChartCreate,
5236 update: NETDATA.peityChartUpdate,
5238 setSelection: undefined, // function(state, t) { return true; },
5239 clearSelection: undefined, // function(state) { return true; },
5240 toolboxPanAndZoom: null,
5243 format: function(state) { return 'ssvcomma'; },
5244 options: function(state) { return 'null2zero|flip|abs'; },
5245 legend: function(state) { return null; },
5246 autoresize: function(state) { return false; },
5247 max_updates_to_recreate: function(state) { return 5000; },
5248 track_colors: function(state) { return false; },
5249 pixels_per_point: function(state) { return 3; }
5252 initialize: NETDATA.morrisInitialize,
5253 create: NETDATA.morrisChartCreate,
5254 update: NETDATA.morrisChartUpdate,
5256 setSelection: undefined, // function(state, t) { return true; },
5257 clearSelection: undefined, // function(state) { return true; },
5258 toolboxPanAndZoom: null,
5261 format: function(state) { return 'json'; },
5262 options: function(state) { return 'objectrows|ms'; },
5263 legend: function(state) { return null; },
5264 autoresize: function(state) { return false; },
5265 max_updates_to_recreate: function(state) { return 50; },
5266 track_colors: function(state) { return false; },
5267 pixels_per_point: function(state) { return 15; }
5270 initialize: NETDATA.googleInitialize,
5271 create: NETDATA.googleChartCreate,
5272 update: NETDATA.googleChartUpdate,
5274 setSelection: undefined, //function(state, t) { return true; },
5275 clearSelection: undefined, //function(state) { return true; },
5276 toolboxPanAndZoom: null,
5279 format: function(state) { return 'datatable'; },
5280 options: function(state) { return ''; },
5281 legend: function(state) { return null; },
5282 autoresize: function(state) { return false; },
5283 max_updates_to_recreate: function(state) { return 300; },
5284 track_colors: function(state) { return false; },
5285 pixels_per_point: function(state) { return 4; }
5288 initialize: NETDATA.raphaelInitialize,
5289 create: NETDATA.raphaelChartCreate,
5290 update: NETDATA.raphaelChartUpdate,
5292 setSelection: undefined, // function(state, t) { return true; },
5293 clearSelection: undefined, // function(state) { return true; },
5294 toolboxPanAndZoom: null,
5297 format: function(state) { return 'json'; },
5298 options: function(state) { return ''; },
5299 legend: function(state) { return null; },
5300 autoresize: function(state) { return false; },
5301 max_updates_to_recreate: function(state) { return 5000; },
5302 track_colors: function(state) { return false; },
5303 pixels_per_point: function(state) { return 3; }
5306 initialize: NETDATA.c3Initialize,
5307 create: NETDATA.c3ChartCreate,
5308 update: NETDATA.c3ChartUpdate,
5310 setSelection: undefined, // function(state, t) { return true; },
5311 clearSelection: undefined, // function(state) { return true; },
5312 toolboxPanAndZoom: null,
5315 format: function(state) { return 'csvjsonarray'; },
5316 options: function(state) { return 'milliseconds'; },
5317 legend: function(state) { return null; },
5318 autoresize: function(state) { return false; },
5319 max_updates_to_recreate: function(state) { return 5000; },
5320 track_colors: function(state) { return false; },
5321 pixels_per_point: function(state) { return 15; }
5324 initialize: NETDATA.d3Initialize,
5325 create: NETDATA.d3ChartCreate,
5326 update: NETDATA.d3ChartUpdate,
5328 setSelection: undefined, // function(state, t) { return true; },
5329 clearSelection: undefined, // function(state) { return true; },
5330 toolboxPanAndZoom: null,
5333 format: function(state) { return 'json'; },
5334 options: function(state) { return ''; },
5335 legend: function(state) { return null; },
5336 autoresize: function(state) { return false; },
5337 max_updates_to_recreate: function(state) { return 5000; },
5338 track_colors: function(state) { return false; },
5339 pixels_per_point: function(state) { return 3; }
5342 initialize: NETDATA.easypiechartInitialize,
5343 create: NETDATA.easypiechartChartCreate,
5344 update: NETDATA.easypiechartChartUpdate,
5346 setSelection: NETDATA.easypiechartSetSelection,
5347 clearSelection: NETDATA.easypiechartClearSelection,
5348 toolboxPanAndZoom: null,
5351 format: function(state) { return 'array'; },
5352 options: function(state) { return 'absolute'; },
5353 legend: function(state) { return null; },
5354 autoresize: function(state) { return false; },
5355 max_updates_to_recreate: function(state) { return 5000; },
5356 track_colors: function(state) { return true; },
5357 pixels_per_point: function(state) { return 3; },
5361 initialize: NETDATA.gaugeInitialize,
5362 create: NETDATA.gaugeChartCreate,
5363 update: NETDATA.gaugeChartUpdate,
5365 setSelection: NETDATA.gaugeSetSelection,
5366 clearSelection: NETDATA.gaugeClearSelection,
5367 toolboxPanAndZoom: null,
5370 format: function(state) { return 'array'; },
5371 options: function(state) { return 'absolute'; },
5372 legend: function(state) { return null; },
5373 autoresize: function(state) { return false; },
5374 max_updates_to_recreate: function(state) { return 5000; },
5375 track_colors: function(state) { return true; },
5376 pixels_per_point: function(state) { return 3; },
5381 NETDATA.registerChartLibrary = function(library, url) {
5382 if(NETDATA.options.debug.libraries === true)
5383 console.log("registering chart library: " + library);
5385 NETDATA.chartLibraries[library].url = url;
5386 NETDATA.chartLibraries[library].initialized = true;
5387 NETDATA.chartLibraries[library].enabled = true;
5390 // ----------------------------------------------------------------------------------------------------------------
5391 // Load required JS libraries and CSS
5393 NETDATA.requiredJs = [
5395 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5396 isAlreadyLoaded: function() {
5397 // check if bootstrap is loaded
5398 if(typeof $().emulateTransitionEnd == 'function')
5401 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5409 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5410 isAlreadyLoaded: function() { return false; }
5413 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5414 isAlreadyLoaded: function() { return false; }
5418 NETDATA.requiredCSS = [
5420 url: NETDATA.themes.current.bootstrap_css,
5421 isAlreadyLoaded: function() {
5422 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5429 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5430 isAlreadyLoaded: function() { return false; }
5433 url: NETDATA.themes.current.dashboard_css,
5434 isAlreadyLoaded: function() { return false; }
5437 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5438 isAlreadyLoaded: function() { return false; }
5442 NETDATA.loadRequiredJs = function(index, callback) {
5443 if(index >= NETDATA.requiredJs.length) {
5444 if(typeof callback === 'function')
5449 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5450 NETDATA.loadRequiredJs(++index, callback);
5454 if(NETDATA.options.debug.main_loop === true)
5455 console.log('loading ' + NETDATA.requiredJs[index].url);
5458 url: NETDATA.requiredJs[index].url,
5462 .success(function() {
5463 if(NETDATA.options.debug.main_loop === true)
5464 console.log('loaded ' + NETDATA.requiredJs[index].url);
5466 NETDATA.loadRequiredJs(++index, callback);
5469 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5473 NETDATA.loadRequiredCSS = function(index) {
5474 if(index >= NETDATA.requiredCSS.length)
5477 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5478 NETDATA.loadRequiredCSS(++index);
5482 if(NETDATA.options.debug.main_loop === true)
5483 console.log('loading ' + NETDATA.requiredCSS[index].url);
5485 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5486 NETDATA.loadRequiredCSS(++index);
5490 // ----------------------------------------------------------------------------------------------------------------
5491 // Registry of netdata hosts
5493 NETDATA.registry = {
5494 server: null, // the netdata registry server
5495 person_guid: null, // the unique ID of this browser / user
5496 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5497 hostname: null, // the hostname of the netdata server that served dashboard.js
5498 urls: null, // the user's other URLs
5499 urls_array: null, // the user's other URLs in an array
5501 parsePersonUrls: function(person_urls) {
5502 // console.log(person_urls);
5505 NETDATA.registry.urls = {};
5506 NETDATA.registry.urls_array = new Array();
5508 var now = new Date().getTime();
5509 var apu = person_urls;
5512 if(typeof NETDATA.registry.urls[apu[i][0]] === 'undefined') {
5513 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5519 accesses: apu[i][3],
5521 alternate_urls: new Array()
5524 NETDATA.registry.urls[apu[i][0]] = obj;
5525 NETDATA.registry.urls_array.push(obj);
5528 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5530 var pu = NETDATA.registry.urls[apu[i][0]];
5531 if(pu.last_t < apu[i][2]) {
5533 pu.last_t = apu[i][2];
5534 pu.name = apu[i][4];
5536 pu.accesses += apu[i][3];
5537 pu.alternate_urls.push(apu[i][1]);
5542 if(typeof netdataRegistryCallback === 'function')
5543 netdataRegistryCallback(NETDATA.registry.urls_array);
5547 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5550 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5552 NETDATA.registry.server = data.registry;
5553 NETDATA.registry.machine_guid = data.machine_guid;
5554 NETDATA.registry.hostname = data.hostname;
5556 NETDATA.registry.access(10, function (person_urls) {
5557 NETDATA.registry.parsePersonUrls(person_urls);
5564 hello: function(host, callback) {
5565 // send HELLO to a netdata server:
5566 // 1. verifies the server is reachable
5567 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5569 url: host + '/api/v1/registry?action=hello',
5572 xhrFields: { withCredentials: true } // required for the cookie
5574 .done(function(data) {
5575 if(typeof data.status !== 'string' || data.status !== 'ok') {
5576 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5580 if(typeof callback === 'function')
5584 NETDATA.error(407, host);
5586 if(typeof callback === 'function')
5591 access: function(max_redirects, callback) {
5592 // send ACCESS to a netdata registry:
5593 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5594 // 2. it responds with a list of netdata servers we know
5595 // the registry identifies us using a cookie it sets the first time we access it
5596 // the registry may respond with a redirect URL to send us to another registry
5598 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),
5601 xhrFields: { withCredentials: true } // required for the cookie
5603 .done(function(data) {
5604 var redirect = null;
5605 if(typeof data.registry === 'string')
5606 redirect = data.registry;
5608 if(typeof data.status !== 'string' || data.status !== 'ok') {
5609 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5613 if(data === null && redirect !== null && max_redirects > 0) {
5614 NETDATA.registry.server = redirect;
5615 NETDATA.registry.access(max_redirects - 1, callback);
5618 if(typeof data.person_guid === 'string')
5619 NETDATA.registry.person_guid = data.person_guid;
5621 if(typeof callback === 'function')
5622 callback(data.urls);
5626 NETDATA.error(410, NETDATA.registry.server);
5628 if(typeof callback === 'function')
5633 delete: function(delete_url, callback) {
5634 // send DELETE to a netdata registry:
5636 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),
5639 xhrFields: { withCredentials: true } // required for the cookie
5641 .done(function(data) {
5642 if(typeof data.status !== 'string' || data.status !== 'ok') {
5643 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5647 if(typeof callback === 'function')
5651 NETDATA.error(412, NETDATA.registry.server);
5653 if(typeof callback === 'function')
5658 switch: function(new_person_guid, callback) {
5661 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,
5664 xhrFields: { withCredentials: true } // required for the cookie
5666 .done(function(data) {
5667 if(typeof data.status !== 'string' || data.status !== 'ok') {
5668 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5672 if(typeof callback === 'function')
5676 NETDATA.error(414, NETDATA.registry.server);
5678 if(typeof callback === 'function')
5684 // ----------------------------------------------------------------------------------------------------------------
5687 NETDATA.errorReset();
5688 NETDATA.loadRequiredCSS(0);
5690 NETDATA._loadjQuery(function() {
5691 NETDATA.loadRequiredJs(0, function() {
5692 if(typeof $().emulateTransitionEnd !== 'function') {
5693 // bootstrap is not available
5694 NETDATA.options.current.show_help = false;
5697 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5698 if(NETDATA.options.debug.main_loop === true)
5699 console.log('starting chart refresh thread');
5706 // window.NETDATA = NETDATA;
5707 // })(window, document);