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, dim, name, count) {
2153 var color = state._chartDimensionColor(name);
2155 var user_element = null;
2156 var user_id = self.data('show-value-of-' + dim + '-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_ids[i], 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, dim, 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, 3000);
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 if(spotColor === 'disable') spotColor='';
3545 if(minSpotColor === 'disable') minSpotColor='';
3546 if(maxSpotColor === 'disable') maxSpotColor='';
3548 state.sparkline_options = {
3550 lineColor: lineColor,
3551 fillColor: fillColor,
3552 chartRangeMin: chartRangeMin,
3553 chartRangeMax: chartRangeMax,
3554 composite: composite,
3555 enableTagOptions: enableTagOptions,
3556 tagOptionPrefix: tagOptionPrefix,
3557 tagValuesAttribute: tagValuesAttribute,
3558 disableHiddenCheck: disableHiddenCheck,
3559 defaultPixelsPerValue: defaultPixelsPerValue,
3560 spotColor: spotColor,
3561 minSpotColor: minSpotColor,
3562 maxSpotColor: maxSpotColor,
3563 spotRadius: spotRadius,
3564 valueSpots: valueSpots,
3565 highlightSpotColor: highlightSpotColor,
3566 highlightLineColor: highlightLineColor,
3567 lineWidth: lineWidth,
3568 normalRangeMin: normalRangeMin,
3569 normalRangeMax: normalRangeMax,
3570 drawNormalOnTop: drawNormalOnTop,
3572 chartRangeClip: chartRangeClip,
3573 chartRangeMinX: chartRangeMinX,
3574 chartRangeMaxX: chartRangeMaxX,
3575 disableInteraction: disableInteraction,
3576 disableTooltips: disableTooltips,
3577 disableHighlight: disableHighlight,
3578 highlightLighten: highlightLighten,
3579 highlightColor: highlightColor,
3580 tooltipContainer: tooltipContainer,
3581 tooltipClassname: tooltipClassname,
3582 tooltipChartTitle: state.title,
3583 tooltipFormat: tooltipFormat,
3584 tooltipPrefix: tooltipPrefix,
3585 tooltipSuffix: tooltipSuffix,
3586 tooltipSkipNull: tooltipSkipNull,
3587 tooltipValueLookups: tooltipValueLookups,
3588 tooltipFormatFieldlist: tooltipFormatFieldlist,
3589 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3590 numberFormatter: numberFormatter,
3591 numberDigitGroupSep: numberDigitGroupSep,
3592 numberDecimalMark: numberDecimalMark,
3593 numberDigitGroupCount: numberDigitGroupCount,
3594 animatedZooms: animatedZooms,
3595 width: state.chartWidth(),
3596 height: state.chartHeight()
3599 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3603 // ----------------------------------------------------------------------------------------------------------------
3610 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3611 if(after < state.netdata_first)
3612 after = state.netdata_first;
3614 if(before > state.netdata_last)
3615 before = state.netdata_last;
3617 state.setMode('zoom');
3618 state.globalSelectionSyncStop();
3619 state.globalSelectionSyncDelay();
3620 state.dygraph_user_action = true;
3621 state.dygraph_force_zoom = true;
3622 state.updateChartPanOrZoom(after, before);
3623 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3626 NETDATA.dygraphSetSelection = function(state, t) {
3627 if(typeof state.dygraph_instance !== 'undefined') {
3628 var r = state.calculateRowForTime(t);
3630 state.dygraph_instance.setSelection(r);
3632 state.dygraph_instance.clearSelection();
3633 state.legendShowUndefined();
3640 NETDATA.dygraphClearSelection = function(state, t) {
3641 if(typeof state.dygraph_instance !== 'undefined') {
3642 state.dygraph_instance.clearSelection();
3647 NETDATA.dygraphSmoothInitialize = function(callback) {
3649 url: NETDATA.dygraph_smooth_js,
3654 NETDATA.dygraph.smooth = true;
3655 smoothPlotter.smoothing = 0.3;
3658 NETDATA.dygraph.smooth = false;
3660 .always(function() {
3661 if(typeof callback === "function")
3666 NETDATA.dygraphInitialize = function(callback) {
3667 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3669 url: NETDATA.dygraph_js,
3674 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3677 NETDATA.chartLibraries.dygraph.enabled = false;
3678 NETDATA.error(100, NETDATA.dygraph_js);
3680 .always(function() {
3681 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3682 NETDATA.dygraphSmoothInitialize(callback);
3683 else if(typeof callback === "function")
3688 NETDATA.chartLibraries.dygraph.enabled = false;
3689 if(typeof callback === "function")
3694 NETDATA.dygraphChartUpdate = function(state, data) {
3695 var dygraph = state.dygraph_instance;
3697 if(typeof dygraph === 'undefined')
3698 return NETDATA.dygraphChartCreate(state, data);
3700 // when the chart is not visible, and hidden
3701 // if there is a window resize, dygraph detects
3702 // its element size as 0x0.
3703 // this will make it re-appear properly
3705 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3709 file: data.result.data,
3710 colors: state.chartColors(),
3711 labels: data.result.labels,
3712 labelsDivWidth: state.chartWidth() - 70,
3713 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3716 if(state.dygraph_force_zoom === true) {
3717 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3718 state.log('dygraphChartUpdate() forced zoom update');
3720 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3721 options.valueRange = state.dygraph_options.valueRange;
3722 options.isZoomedIgnoreProgrammaticZoom = true;
3723 state.dygraph_force_zoom = false;
3725 else if(state.current.name !== 'auto') {
3726 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3727 state.log('dygraphChartUpdate() loose update');
3729 options.valueRange = state.dygraph_options.valueRange;
3732 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3733 state.log('dygraphChartUpdate() strict update');
3735 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3736 options.valueRange = state.dygraph_options.valueRange;
3737 options.isZoomedIgnoreProgrammaticZoom = true;
3740 if(state.dygraph_smooth_eligible === true) {
3741 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3742 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3743 NETDATA.dygraphChartCreate(state, data);
3748 dygraph.updateOptions(options);
3750 state.dygraph_last_rendered = new Date().getTime();
3754 NETDATA.dygraphChartCreate = function(state, data) {
3755 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3756 state.log('dygraphChartCreate()');
3758 var self = $(state.element);
3760 var chart_type = state.chart.chart_type;
3761 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3762 chart_type = self.data('dygraph-type') || chart_type;
3764 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3765 smooth = self.data('dygraph-smooth') || smooth;
3767 if(NETDATA.dygraph.smooth === false)
3770 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3771 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3773 state.dygraph_options = {
3774 colors: self.data('dygraph-colors') || state.chartColors(),
3776 // leave a few pixels empty on the right of the chart
3777 rightGap: self.data('dygraph-rightgap') || 5,
3778 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3779 showRoller: self.data('dygraph-showroller') || false,
3781 title: self.data('dygraph-title') || state.title,
3782 titleHeight: self.data('dygraph-titleheight') || 19,
3784 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3785 labels: data.result.labels,
3786 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3787 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3788 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3789 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3790 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3793 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3794 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3796 includeZero: self.data('dygraph-includezero') || false,
3797 xRangePad: self.data('dygraph-xrangepad') || 0,
3798 yRangePad: self.data('dygraph-yrangepad') || 1,
3800 valueRange: self.data('dygraph-valuerange') || null,
3802 ylabel: state.units,
3803 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3805 // the function to plot the chart
3808 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3809 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3810 strokePattern: self.data('dygraph-strokepattern') || undefined,
3812 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3813 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3814 drawPoints: self.data('dygraph-drawpoints') || false,
3816 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3817 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3819 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3820 pointSize: self.data('dygraph-pointsize') || 1,
3822 // enabling this makes the chart with little square lines
3823 stepPlot: self.data('dygraph-stepplot') || false,
3825 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3826 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3827 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3829 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3830 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3831 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3832 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3834 drawAxis: self.data('dygraph-drawaxis') || true,
3835 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3836 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3837 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3839 drawGrid: self.data('dygraph-drawgrid') || true,
3840 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3841 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3842 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3843 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3844 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3846 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3847 sigFigs: self.data('dygraph-sigfigs') || null,
3848 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3849 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3851 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3852 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3853 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3855 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3856 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3860 ticker: Dygraph.dateTicker,
3861 axisLabelFormatter: function (d, gran) {
3862 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3864 valueFormatter: function (ms) {
3865 var d = new Date(ms);
3866 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3867 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3872 valueFormatter: function (x) {
3873 // we format legends with the state object
3874 // no need to do anything here
3875 // return (Math.round(x*100) / 100).toLocaleString();
3876 // return state.legendFormatValue(x);
3881 legendFormatter: function(data) {
3882 var elements = state.element_legend_childs;
3884 // if the hidden div is not there
3885 // we are not managing the legend
3886 if(elements.hidden === null) return;
3888 if (typeof data.x !== 'undefined') {
3889 state.legendSetDate(data.x);
3890 var i = data.series.length;
3892 var series = data.series[i];
3893 if(!series.isVisible) continue;
3894 state.legendSetLabelValue(series.label, series.y);
3900 drawCallback: function(dygraph, is_initial) {
3901 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3902 state.dygraph_user_action = false;
3904 var x_range = dygraph.xAxisRange();
3905 var after = Math.round(x_range[0]);
3906 var before = Math.round(x_range[1]);
3908 if(NETDATA.options.debug.dygraph === true)
3909 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3911 if(before <= state.netdata_last && after >= state.netdata_first)
3912 state.updateChartPanOrZoom(after, before);
3915 zoomCallback: function(minDate, maxDate, yRanges) {
3916 if(NETDATA.options.debug.dygraph === true)
3917 state.log('dygraphZoomCallback()');
3919 state.globalSelectionSyncStop();
3920 state.globalSelectionSyncDelay();
3921 state.setMode('zoom');
3923 // refresh it to the greatest possible zoom level
3924 state.dygraph_user_action = true;
3925 state.dygraph_force_zoom = true;
3926 state.updateChartPanOrZoom(minDate, maxDate);
3928 highlightCallback: function(event, x, points, row, seriesName) {
3929 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3930 state.log('dygraphHighlightCallback()');
3934 // there is a bug in dygraph when the chart is zoomed enough
3935 // the time it thinks is selected is wrong
3936 // here we calculate the time t based on the row number selected
3938 var t = state.data_after + row * state.data_update_every;
3939 // 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);
3941 state.globalSelectionSync(x);
3943 // fix legend zIndex using the internal structures of dygraph legend module
3944 // this works, but it is a hack!
3945 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3947 unhighlightCallback: function(event) {
3948 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3949 state.log('dygraphUnhighlightCallback()');
3951 state.unpauseChart();
3952 state.globalSelectionSyncStop();
3954 interactionModel : {
3955 mousedown: function(event, dygraph, context) {
3956 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3957 state.log('interactionModel.mousedown()');
3959 state.dygraph_user_action = true;
3960 state.globalSelectionSyncStop();
3962 if(NETDATA.options.debug.dygraph === true)
3963 state.log('dygraphMouseDown()');
3965 // Right-click should not initiate a zoom.
3966 if(event.button && event.button === 2) return;
3968 context.initializeMouseDown(event, dygraph, context);
3970 if(event.button && event.button === 1) {
3971 if (event.altKey || event.shiftKey) {
3972 state.setMode('pan');
3973 state.globalSelectionSyncDelay();
3974 Dygraph.startPan(event, dygraph, context);
3977 state.setMode('zoom');
3978 state.globalSelectionSyncDelay();
3979 Dygraph.startZoom(event, dygraph, context);
3983 if (event.altKey || event.shiftKey) {
3984 state.setMode('zoom');
3985 state.globalSelectionSyncDelay();
3986 Dygraph.startZoom(event, dygraph, context);
3989 state.setMode('pan');
3990 state.globalSelectionSyncDelay();
3991 Dygraph.startPan(event, dygraph, context);
3995 mousemove: function(event, dygraph, context) {
3996 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3997 state.log('interactionModel.mousemove()');
3999 if(context.isPanning) {
4000 state.dygraph_user_action = true;
4001 state.globalSelectionSyncStop();
4002 state.globalSelectionSyncDelay();
4003 state.setMode('pan');
4004 Dygraph.movePan(event, dygraph, context);
4006 else if(context.isZooming) {
4007 state.dygraph_user_action = true;
4008 state.globalSelectionSyncStop();
4009 state.globalSelectionSyncDelay();
4010 state.setMode('zoom');
4011 Dygraph.moveZoom(event, dygraph, context);
4014 mouseup: function(event, dygraph, context) {
4015 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4016 state.log('interactionModel.mouseup()');
4018 if (context.isPanning) {
4019 state.dygraph_user_action = true;
4020 state.globalSelectionSyncDelay();
4021 Dygraph.endPan(event, dygraph, context);
4023 else if (context.isZooming) {
4024 state.dygraph_user_action = true;
4025 state.globalSelectionSyncDelay();
4026 Dygraph.endZoom(event, dygraph, context);
4029 click: function(event, dygraph, context) {
4030 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4031 state.log('interactionModel.click()');
4033 event.preventDefault();
4035 dblclick: function(event, dygraph, context) {
4036 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4037 state.log('interactionModel.dblclick()');
4038 NETDATA.resetAllCharts(state);
4040 mousewheel: function(event, dygraph, context) {
4041 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4042 state.log('interactionModel.mousewheel()');
4044 // Take the offset of a mouse event on the dygraph canvas and
4045 // convert it to a pair of percentages from the bottom left.
4046 // (Not top left, bottom is where the lower value is.)
4047 function offsetToPercentage(g, offsetX, offsetY) {
4048 // This is calculating the pixel offset of the leftmost date.
4049 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4050 var yar0 = g.yAxisRange(0);
4052 // This is calculating the pixel of the higest value. (Top pixel)
4053 var yOffset = g.toDomCoords(null, yar0[1])[1];
4055 // x y w and h are relative to the corner of the drawing area,
4056 // so that the upper corner of the drawing area is (0, 0).
4057 var x = offsetX - xOffset;
4058 var y = offsetY - yOffset;
4060 // This is computing the rightmost pixel, effectively defining the
4062 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4064 // This is computing the lowest pixel, effectively defining the height.
4065 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4067 // Percentage from the left.
4068 var xPct = w === 0 ? 0 : (x / w);
4069 // Percentage from the top.
4070 var yPct = h === 0 ? 0 : (y / h);
4072 // The (1-) part below changes it from "% distance down from the top"
4073 // to "% distance up from the bottom".
4074 return [xPct, (1-yPct)];
4077 // Adjusts [x, y] toward each other by zoomInPercentage%
4078 // Split it so the left/bottom axis gets xBias/yBias of that change and
4079 // tight/top gets (1-xBias)/(1-yBias) of that change.
4081 // If a bias is missing it splits it down the middle.
4082 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4083 xBias = xBias || 0.5;
4084 yBias = yBias || 0.5;
4086 function adjustAxis(axis, zoomInPercentage, bias) {
4087 var delta = axis[1] - axis[0];
4088 var increment = delta * zoomInPercentage;
4089 var foo = [increment * bias, increment * (1-bias)];
4091 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4094 var yAxes = g.yAxisRanges();
4096 for (var i = 0; i < yAxes.length; i++) {
4097 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4100 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4103 if(event.altKey || event.shiftKey) {
4104 state.dygraph_user_action = true;
4106 state.globalSelectionSyncStop();
4107 state.globalSelectionSyncDelay();
4109 // http://dygraphs.com/gallery/interaction-api.js
4110 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4111 var percentage = normal / 50;
4113 if (!(event.offsetX && event.offsetY)){
4114 event.offsetX = event.layerX - event.target.offsetLeft;
4115 event.offsetY = event.layerY - event.target.offsetTop;
4118 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4119 var xPct = percentages[0];
4120 var yPct = percentages[1];
4122 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4124 var after = new_x_range[0];
4125 var before = new_x_range[1];
4127 var first = state.netdata_first + state.data_update_every;
4128 var last = state.netdata_last + state.data_update_every;
4131 after -= (before - last);
4138 state.setMode('zoom');
4139 if(state.updateChartPanOrZoom(after, before) === true)
4140 dygraph.updateOptions({ dateWindow: [ after, before ] });
4142 event.preventDefault();
4145 touchstart: function(event, dygraph, context) {
4146 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4147 state.log('interactionModel.touchstart()');
4149 state.dygraph_user_action = true;
4150 state.setMode('zoom');
4153 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4155 // we overwrite the touch directions at the end, to overwrite
4156 // the internal default of dygraphs
4157 context.touchDirections = { x: true, y: false };
4159 state.dygraph_last_touch_start = new Date().getTime();
4160 state.dygraph_last_touch_move = 0;
4162 if(typeof event.touches[0].pageX === 'number')
4163 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4165 state.dygraph_last_touch_page_x = 0;
4167 touchmove: function(event, dygraph, context) {
4168 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4169 state.log('interactionModel.touchmove()');
4171 state.dygraph_user_action = true;
4172 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4174 state.dygraph_last_touch_move = new Date().getTime();
4176 touchend: function(event, dygraph, context) {
4177 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4178 state.log('interactionModel.touchend()');
4180 state.dygraph_user_action = true;
4181 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4183 // if it didn't move, it is a selection
4184 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4185 // internal api of dygraphs
4186 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4187 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4188 if(NETDATA.dygraphSetSelection(state, t) === true)
4189 state.globalSelectionSync(t);
4192 // if it was double tap within double click time, reset the charts
4193 var now = new Date().getTime();
4194 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4195 if(state.dygraph_last_touch_move === 0) {
4196 var dt = now - state.dygraph_last_touch_end;
4197 if(dt <= NETDATA.options.current.double_click_speed)
4198 NETDATA.resetAllCharts(state);
4202 // remember the timestamp of the last touch end
4203 state.dygraph_last_touch_end = now;
4208 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4209 state.dygraph_options.drawGrid = false;
4210 state.dygraph_options.drawAxis = false;
4211 state.dygraph_options.title = undefined;
4212 state.dygraph_options.units = undefined;
4213 state.dygraph_options.ylabel = undefined;
4214 state.dygraph_options.yLabelWidth = 0;
4215 state.dygraph_options.labelsDivWidth = 120;
4216 state.dygraph_options.labelsDivStyles.width = '120px';
4217 state.dygraph_options.labelsSeparateLines = true;
4218 state.dygraph_options.rightGap = 0;
4219 state.dygraph_options.yRangePad = 1;
4222 if(smooth === true) {
4223 state.dygraph_smooth_eligible = true;
4225 if(NETDATA.options.current.smooth_plot === true)
4226 state.dygraph_options.plotter = smoothPlotter;
4228 else state.dygraph_smooth_eligible = false;
4230 state.dygraph_instance = new Dygraph(state.element_chart,
4231 data.result.data, state.dygraph_options);
4233 state.dygraph_force_zoom = false;
4234 state.dygraph_user_action = false;
4235 state.dygraph_last_rendered = new Date().getTime();
4239 // ----------------------------------------------------------------------------------------------------------------
4242 NETDATA.morrisInitialize = function(callback) {
4243 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4245 // morris requires raphael
4246 if(!NETDATA.chartLibraries.raphael.initialized) {
4247 if(NETDATA.chartLibraries.raphael.enabled) {
4248 NETDATA.raphaelInitialize(function() {
4249 NETDATA.morrisInitialize(callback);
4253 NETDATA.chartLibraries.morris.enabled = false;
4254 if(typeof callback === "function")
4259 NETDATA._loadCSS(NETDATA.morris_css);
4262 url: NETDATA.morris_js,
4267 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4270 NETDATA.chartLibraries.morris.enabled = false;
4271 NETDATA.error(100, NETDATA.morris_js);
4273 .always(function() {
4274 if(typeof callback === "function")
4280 NETDATA.chartLibraries.morris.enabled = false;
4281 if(typeof callback === "function")
4286 NETDATA.morrisChartUpdate = function(state, data) {
4287 state.morris_instance.setData(data.result.data);
4291 NETDATA.morrisChartCreate = function(state, data) {
4293 state.morris_options = {
4294 element: state.element_chart.id,
4295 data: data.result.data,
4297 ykeys: data.dimension_names,
4298 labels: data.dimension_names,
4304 continuousLine: false,
4305 behaveLikeLine: false
4308 if(state.chart.chart_type === 'line')
4309 state.morris_instance = new Morris.Line(state.morris_options);
4311 else if(state.chart.chart_type === 'area') {
4312 state.morris_options.behaveLikeLine = true;
4313 state.morris_instance = new Morris.Area(state.morris_options);
4316 state.morris_instance = new Morris.Area(state.morris_options);
4321 // ----------------------------------------------------------------------------------------------------------------
4324 NETDATA.raphaelInitialize = function(callback) {
4325 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4327 url: NETDATA.raphael_js,
4332 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4335 NETDATA.chartLibraries.raphael.enabled = false;
4336 NETDATA.error(100, NETDATA.raphael_js);
4338 .always(function() {
4339 if(typeof callback === "function")
4344 NETDATA.chartLibraries.raphael.enabled = false;
4345 if(typeof callback === "function")
4350 NETDATA.raphaelChartUpdate = function(state, data) {
4351 $(state.element_chart).raphael(data.result, {
4352 width: state.chartWidth(),
4353 height: state.chartHeight()
4359 NETDATA.raphaelChartCreate = function(state, data) {
4360 $(state.element_chart).raphael(data.result, {
4361 width: state.chartWidth(),
4362 height: state.chartHeight()
4368 // ----------------------------------------------------------------------------------------------------------------
4371 NETDATA.c3Initialize = function(callback) {
4372 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4375 if(!NETDATA.chartLibraries.d3.initialized) {
4376 if(NETDATA.chartLibraries.d3.enabled) {
4377 NETDATA.d3Initialize(function() {
4378 NETDATA.c3Initialize(callback);
4382 NETDATA.chartLibraries.c3.enabled = false;
4383 if(typeof callback === "function")
4388 NETDATA._loadCSS(NETDATA.c3_css);
4396 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4399 NETDATA.chartLibraries.c3.enabled = false;
4400 NETDATA.error(100, NETDATA.c3_js);
4402 .always(function() {
4403 if(typeof callback === "function")
4409 NETDATA.chartLibraries.c3.enabled = false;
4410 if(typeof callback === "function")
4415 NETDATA.c3ChartUpdate = function(state, data) {
4416 state.c3_instance.destroy();
4417 return NETDATA.c3ChartCreate(state, data);
4419 //state.c3_instance.load({
4420 // rows: data.result,
4427 NETDATA.c3ChartCreate = function(state, data) {
4429 state.element_chart.id = 'c3-' + state.uuid;
4430 // console.log('id = ' + state.element_chart.id);
4432 state.c3_instance = c3.generate({
4433 bindto: '#' + state.element_chart.id,
4435 width: state.chartWidth(),
4436 height: state.chartHeight()
4439 pattern: state.chartColors()
4444 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4450 format: function(x) {
4451 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4478 // console.log(state.c3_instance);
4483 // ----------------------------------------------------------------------------------------------------------------
4486 NETDATA.d3Initialize = function(callback) {
4487 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4494 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4497 NETDATA.chartLibraries.d3.enabled = false;
4498 NETDATA.error(100, NETDATA.d3_js);
4500 .always(function() {
4501 if(typeof callback === "function")
4506 NETDATA.chartLibraries.d3.enabled = false;
4507 if(typeof callback === "function")
4512 NETDATA.d3ChartUpdate = function(state, data) {
4516 NETDATA.d3ChartCreate = function(state, data) {
4520 // ----------------------------------------------------------------------------------------------------------------
4523 NETDATA.googleInitialize = function(callback) {
4524 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4526 url: NETDATA.google_js,
4531 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4532 google.load('visualization', '1.1', {
4533 'packages': ['corechart', 'controls'],
4534 'callback': callback
4538 NETDATA.chartLibraries.google.enabled = false;
4539 NETDATA.error(100, NETDATA.google_js);
4540 if(typeof callback === "function")
4545 NETDATA.chartLibraries.google.enabled = false;
4546 if(typeof callback === "function")
4551 NETDATA.googleChartUpdate = function(state, data) {
4552 var datatable = new google.visualization.DataTable(data.result);
4553 state.google_instance.draw(datatable, state.google_options);
4557 NETDATA.googleChartCreate = function(state, data) {
4558 var datatable = new google.visualization.DataTable(data.result);
4560 state.google_options = {
4561 colors: state.chartColors(),
4563 // do not set width, height - the chart resizes itself
4564 //width: state.chartWidth(),
4565 //height: state.chartHeight(),
4570 // title: "Time of Day",
4571 // format:'HH:mm:ss',
4572 viewWindowMode: 'maximized',
4584 viewWindowMode: 'pretty',
4599 focusTarget: 'category',
4606 titlePosition: 'out',
4617 curveType: 'function',
4622 switch(state.chart.chart_type) {
4624 state.google_options.vAxis.viewWindowMode = 'maximized';
4625 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4626 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4630 state.google_options.isStacked = true;
4631 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4632 state.google_options.vAxis.viewWindowMode = 'maximized';
4633 state.google_options.vAxis.minValue = null;
4634 state.google_options.vAxis.maxValue = null;
4635 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4640 state.google_options.lineWidth = 2;
4641 state.google_instance = new google.visualization.LineChart(state.element_chart);
4645 state.google_instance.draw(datatable, state.google_options);
4649 // ----------------------------------------------------------------------------------------------------------------
4651 NETDATA.percentFromValueMax = function(value, max) {
4652 if(value === null) value = 0;
4653 if(max < value) max = value;
4657 pcent = Math.round(value * 100 / max);
4658 if(pcent === 0 && value > 0) pcent = 1;
4664 // ----------------------------------------------------------------------------------------------------------------
4667 NETDATA.easypiechartInitialize = function(callback) {
4668 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4670 url: NETDATA.easypiechart_js,
4675 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4678 NETDATA.chartLibraries.easypiechart.enabled = false;
4679 NETDATA.error(100, NETDATA.easypiechart_js);
4681 .always(function() {
4682 if(typeof callback === "function")
4687 NETDATA.chartLibraries.easypiechart.enabled = false;
4688 if(typeof callback === "function")
4693 NETDATA.easypiechartClearSelection = function(state) {
4694 if(typeof state.easyPieChartEvent !== 'undefined') {
4695 if(state.easyPieChartEvent.timer !== null)
4696 clearTimeout(state.easyPieChartEvent.timer);
4698 state.easyPieChartEvent.timer = null;
4701 if(state.isAutoRefreshable() === true && state.data !== null) {
4702 NETDATA.easypiechartChartUpdate(state, state.data);
4705 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4706 state.easyPieChart_instance.update(0);
4708 state.easyPieChart_instance.enableAnimation();
4713 NETDATA.easypiechartSetSelection = function(state, t) {
4714 if(state.timeIsVisible(t) !== true)
4715 return NETDATA.easypiechartClearSelection(state);
4717 var slot = state.calculateRowForTime(t);
4718 if(slot < 0 || slot >= state.data.result.length)
4719 return NETDATA.easypiechartClearSelection(state);
4721 if(typeof state.easyPieChartEvent === 'undefined') {
4722 state.easyPieChartEvent = {
4729 var value = state.data.result[state.data.result.length - 1 - slot];
4730 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4731 var pcent = NETDATA.percentFromValueMax(value, max);
4733 state.easyPieChartEvent.value = value;
4734 state.easyPieChartEvent.pcent = pcent;
4735 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4737 if(state.easyPieChartEvent.timer === null) {
4738 state.easyPieChart_instance.disableAnimation();
4740 state.easyPieChartEvent.timer = setTimeout(function() {
4741 state.easyPieChartEvent.timer = null;
4742 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4743 }, NETDATA.options.current.charts_selection_animation_delay);
4749 NETDATA.easypiechartChartUpdate = function(state, data) {
4750 var value, max, pcent;
4752 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4758 value = data.result[0];
4759 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4760 pcent = NETDATA.percentFromValueMax(value, max);
4763 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4764 state.easyPieChart_instance.update(pcent);
4768 NETDATA.easypiechartChartCreate = function(state, data) {
4769 var self = $(state.element);
4770 var chart = $(state.element_chart);
4772 var value = data.result[0];
4773 var max = self.data('easypiechart-max-value') || null;
4774 var adjust = self.data('easypiechart-adjust') || null;
4778 state.easyPieChartMax = null;
4781 state.easyPieChartMax = max;
4783 var pcent = NETDATA.percentFromValueMax(value, max);
4785 chart.data('data-percent', pcent);
4789 case 'width': size = state.chartHeight(); break;
4790 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4791 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4793 default: size = state.chartWidth(); break;
4795 state.element.style.width = size + 'px';
4796 state.element.style.height = size + 'px';
4798 var stroke = Math.floor(size / 22);
4799 if(stroke < 3) stroke = 2;
4801 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4802 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4803 state.easyPieChartLabel = document.createElement('span');
4804 state.easyPieChartLabel.className = 'easyPieChartLabel';
4805 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4806 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4807 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4808 state.element_chart.appendChild(state.easyPieChartLabel);
4810 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4811 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4812 state.easyPieChartTitle = document.createElement('span');
4813 state.easyPieChartTitle.className = 'easyPieChartTitle';
4814 state.easyPieChartTitle.innerHTML = state.title;
4815 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4816 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4817 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4818 state.element_chart.appendChild(state.easyPieChartTitle);
4820 var unitfontsize = Math.round(titlefontsize * 0.9);
4821 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4822 state.easyPieChartUnits = document.createElement('span');
4823 state.easyPieChartUnits.className = 'easyPieChartUnits';
4824 state.easyPieChartUnits.innerHTML = state.units;
4825 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4826 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4827 state.element_chart.appendChild(state.easyPieChartUnits);
4829 chart.easyPieChart({
4830 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4831 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4832 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4833 scaleLength: self.data('easypiechart-scalelength') || 5,
4834 lineCap: self.data('easypiechart-linecap') || 'round',
4835 lineWidth: self.data('easypiechart-linewidth') || stroke,
4836 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4837 size: self.data('easypiechart-size') || size,
4838 rotate: self.data('easypiechart-rotate') || 0,
4839 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4840 easing: self.data('easypiechart-easing') || undefined
4843 // when we just re-create the chart
4844 // do not animate the first update
4846 if(typeof state.easyPieChart_instance !== 'undefined')
4849 state.easyPieChart_instance = chart.data('easyPieChart');
4850 if(animate === false) state.easyPieChart_instance.disableAnimation();
4851 state.easyPieChart_instance.update(pcent);
4852 if(animate === false) state.easyPieChart_instance.enableAnimation();
4856 // ----------------------------------------------------------------------------------------------------------------
4859 NETDATA.gaugeInitialize = function(callback) {
4860 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4862 url: NETDATA.gauge_js,
4867 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4870 NETDATA.chartLibraries.gauge.enabled = false;
4871 NETDATA.error(100, NETDATA.gauge_js);
4873 .always(function() {
4874 if(typeof callback === "function")
4879 NETDATA.chartLibraries.gauge.enabled = false;
4880 if(typeof callback === "function")
4885 NETDATA.gaugeAnimation = function(state, status) {
4888 if(typeof status === 'boolean' && status === false)
4890 else if(typeof status === 'number')
4893 state.gauge_instance.animationSpeed = speed;
4894 state.___gaugeOld__.speed = speed;
4897 NETDATA.gaugeSet = function(state, value, min, max) {
4898 if(typeof value !== 'number') value = 0;
4899 if(typeof min !== 'number') min = 0;
4900 if(typeof max !== 'number') max = 0;
4901 if(value > max) max = value;
4902 if(value < min) min = value;
4911 // gauge.js has an issue if the needle
4912 // is smaller than min or larger than max
4913 // when we set the new values
4914 // the needle will go crazy
4916 // to prevent it, we always feed it
4917 // with a percentage, so that the needle
4918 // is always between min and max
4919 var pcent = (value - min) * 100 / (max - min);
4921 // these should never happen
4922 if(pcent < 0) pcent = 0;
4923 if(pcent > 100) pcent = 100;
4925 state.gauge_instance.set(pcent);
4927 state.___gaugeOld__.value = value;
4928 state.___gaugeOld__.min = min;
4929 state.___gaugeOld__.max = max;
4932 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4933 if(state.___gaugeOld__.valueLabel !== value) {
4934 state.___gaugeOld__.valueLabel = value;
4935 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4937 if(state.___gaugeOld__.minLabel !== min) {
4938 state.___gaugeOld__.minLabel = min;
4939 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4941 if(state.___gaugeOld__.maxLabel !== max) {
4942 state.___gaugeOld__.maxLabel = max;
4943 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4947 NETDATA.gaugeClearSelection = function(state) {
4948 if(typeof state.gaugeEvent !== 'undefined') {
4949 if(state.gaugeEvent.timer !== null)
4950 clearTimeout(state.gaugeEvent.timer);
4952 state.gaugeEvent.timer = null;
4955 if(state.isAutoRefreshable() === true && state.data !== null) {
4956 NETDATA.gaugeChartUpdate(state, state.data);
4959 NETDATA.gaugeAnimation(state, false);
4960 NETDATA.gaugeSet(state, null, null, null);
4961 NETDATA.gaugeSetLabels(state, null, null, null);
4964 NETDATA.gaugeAnimation(state, true);
4968 NETDATA.gaugeSetSelection = function(state, t) {
4969 if(state.timeIsVisible(t) !== true)
4970 return NETDATA.gaugeClearSelection(state);
4972 var slot = state.calculateRowForTime(t);
4973 if(slot < 0 || slot >= state.data.result.length)
4974 return NETDATA.gaugeClearSelection(state);
4976 if(typeof state.gaugeEvent === 'undefined') {
4977 state.gaugeEvent = {
4985 var value = state.data.result[state.data.result.length - 1 - slot];
4986 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4989 state.gaugeEvent.value = value;
4990 state.gaugeEvent.max = max;
4991 state.gaugeEvent.min = min;
4992 NETDATA.gaugeSetLabels(state, value, min, max);
4994 if(state.gaugeEvent.timer === null) {
4995 NETDATA.gaugeAnimation(state, false);
4997 state.gaugeEvent.timer = setTimeout(function() {
4998 state.gaugeEvent.timer = null;
4999 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5000 }, NETDATA.options.current.charts_selection_animation_delay);
5006 NETDATA.gaugeChartUpdate = function(state, data) {
5007 var value, min, max;
5009 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5013 NETDATA.gaugeSetLabels(state, null, null, null);
5016 value = data.result[0];
5018 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5019 if(value > max) max = value;
5020 NETDATA.gaugeSetLabels(state, value, min, max);
5023 NETDATA.gaugeSet(state, value, min, max);
5027 NETDATA.gaugeChartCreate = function(state, data) {
5028 var self = $(state.element);
5029 // var chart = $(state.element_chart);
5031 var value = data.result[0];
5032 var max = self.data('gauge-max-value') || null;
5033 var adjust = self.data('gauge-adjust') || null;
5034 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5035 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5036 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5037 var stopColor = self.data('gauge-stop-color') || void 0;
5038 var generateGradient = self.data('gauge-generate-gradient') || false;
5042 state.gaugeMax = null;
5045 state.gaugeMax = max;
5047 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5049 // case 'width': width = height * ratio; break;
5051 // default: height = width / ratio; break;
5053 //state.element.style.width = width.toString() + 'px';
5054 //state.element.style.height = height.toString() + 'px';
5059 lines: 12, // The number of lines to draw
5060 angle: 0.15, // The length of each line
5061 lineWidth: 0.44, // 0.44 The line thickness
5063 length: 0.8, // 0.9 The radius of the inner circle
5064 strokeWidth: 0.035, // The rotation offset
5065 color: pointerColor // Fill color
5067 colorStart: startColor, // Colors
5068 colorStop: stopColor, // just experiment with them
5069 strokeColor: strokeColor, // to see which ones work best for you
5071 generateGradient: (generateGradient === true)?true:false,
5075 if (generateGradient.constructor === Array) {
5077 // data-gauge-generate-gradient="[0, 50, 100]"
5078 // data-gauge-gradient-percent-color-0="#FFFFFF"
5079 // data-gauge-gradient-percent-color-50="#999900"
5080 // data-gauge-gradient-percent-color-100="#000000"
5082 options.percentColors = new Array();
5083 var len = generateGradient.length;
5085 var pcent = generateGradient[len];
5086 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5087 if(color !== false) {
5088 var a = new Array();
5091 options.percentColors.unshift(a);
5094 if(options.percentColors.length === 0)
5095 delete options.percentColors;
5097 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5098 options.percentColors = [
5099 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5100 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5101 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5102 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5103 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5104 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5105 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5106 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5107 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5108 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5109 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5112 state.gauge_canvas = document.createElement('canvas');
5113 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5114 state.gauge_canvas.className = 'gaugeChart';
5115 state.gauge_canvas.width = width;
5116 state.gauge_canvas.height = height;
5117 state.element_chart.appendChild(state.gauge_canvas);
5119 var valuefontsize = Math.floor(height / 6);
5120 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5121 state.gaugeChartLabel = document.createElement('span');
5122 state.gaugeChartLabel.className = 'gaugeChartLabel';
5123 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5124 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5125 state.element_chart.appendChild(state.gaugeChartLabel);
5127 var titlefontsize = Math.round(valuefontsize / 2);
5129 state.gaugeChartTitle = document.createElement('span');
5130 state.gaugeChartTitle.className = 'gaugeChartTitle';
5131 state.gaugeChartTitle.innerHTML = state.title;
5132 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5133 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5134 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5135 state.element_chart.appendChild(state.gaugeChartTitle);
5137 var unitfontsize = Math.round(titlefontsize * 0.9);
5138 state.gaugeChartUnits = document.createElement('span');
5139 state.gaugeChartUnits.className = 'gaugeChartUnits';
5140 state.gaugeChartUnits.innerHTML = state.units;
5141 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5142 state.element_chart.appendChild(state.gaugeChartUnits);
5144 state.gaugeChartMin = document.createElement('span');
5145 state.gaugeChartMin.className = 'gaugeChartMin';
5146 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5147 state.element_chart.appendChild(state.gaugeChartMin);
5149 state.gaugeChartMax = document.createElement('span');
5150 state.gaugeChartMax.className = 'gaugeChartMax';
5151 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5152 state.element_chart.appendChild(state.gaugeChartMax);
5154 // when we just re-create the chart
5155 // do not animate the first update
5157 if(typeof state.gauge_instance !== 'undefined')
5160 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5162 state.___gaugeOld__ = {
5171 // we will always feed a percentage
5172 state.gauge_instance.minValue = 0;
5173 state.gauge_instance.maxValue = 100;
5175 NETDATA.gaugeAnimation(state, animate);
5176 NETDATA.gaugeSet(state, value, 0, max);
5177 NETDATA.gaugeSetLabels(state, value, 0, max);
5178 NETDATA.gaugeAnimation(state, true);
5182 // ----------------------------------------------------------------------------------------------------------------
5183 // Charts Libraries Registration
5185 NETDATA.chartLibraries = {
5187 initialize: NETDATA.dygraphInitialize,
5188 create: NETDATA.dygraphChartCreate,
5189 update: NETDATA.dygraphChartUpdate,
5190 resize: function(state) {
5191 if(typeof state.dygraph_instance.resize === 'function')
5192 state.dygraph_instance.resize();
5194 setSelection: NETDATA.dygraphSetSelection,
5195 clearSelection: NETDATA.dygraphClearSelection,
5196 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5199 format: function(state) { return 'json'; },
5200 options: function(state) { return 'ms|flip'; },
5201 legend: function(state) {
5202 if(this.isSparkline(state) === false)
5203 return 'right-side';
5207 autoresize: function(state) { return true; },
5208 max_updates_to_recreate: function(state) { return 5000; },
5209 track_colors: function(state) { return true; },
5210 pixels_per_point: function(state) {
5211 if(this.isSparkline(state) === false)
5217 isSparkline: function(state) {
5218 if(typeof state.dygraph_sparkline === 'undefined') {
5219 var t = $(state.element).data('dygraph-theme');
5220 if(t === 'sparkline')
5221 state.dygraph_sparkline = true;
5223 state.dygraph_sparkline = false;
5225 return state.dygraph_sparkline;
5229 initialize: NETDATA.sparklineInitialize,
5230 create: NETDATA.sparklineChartCreate,
5231 update: NETDATA.sparklineChartUpdate,
5233 setSelection: undefined, // function(state, t) { return true; },
5234 clearSelection: undefined, // function(state) { return true; },
5235 toolboxPanAndZoom: null,
5238 format: function(state) { return 'array'; },
5239 options: function(state) { return 'flip|abs'; },
5240 legend: function(state) { return null; },
5241 autoresize: function(state) { return false; },
5242 max_updates_to_recreate: function(state) { return 5000; },
5243 track_colors: function(state) { return false; },
5244 pixels_per_point: function(state) { return 3; }
5247 initialize: NETDATA.peityInitialize,
5248 create: NETDATA.peityChartCreate,
5249 update: NETDATA.peityChartUpdate,
5251 setSelection: undefined, // function(state, t) { return true; },
5252 clearSelection: undefined, // function(state) { return true; },
5253 toolboxPanAndZoom: null,
5256 format: function(state) { return 'ssvcomma'; },
5257 options: function(state) { return 'null2zero|flip|abs'; },
5258 legend: function(state) { return null; },
5259 autoresize: function(state) { return false; },
5260 max_updates_to_recreate: function(state) { return 5000; },
5261 track_colors: function(state) { return false; },
5262 pixels_per_point: function(state) { return 3; }
5265 initialize: NETDATA.morrisInitialize,
5266 create: NETDATA.morrisChartCreate,
5267 update: NETDATA.morrisChartUpdate,
5269 setSelection: undefined, // function(state, t) { return true; },
5270 clearSelection: undefined, // function(state) { return true; },
5271 toolboxPanAndZoom: null,
5274 format: function(state) { return 'json'; },
5275 options: function(state) { return 'objectrows|ms'; },
5276 legend: function(state) { return null; },
5277 autoresize: function(state) { return false; },
5278 max_updates_to_recreate: function(state) { return 50; },
5279 track_colors: function(state) { return false; },
5280 pixels_per_point: function(state) { return 15; }
5283 initialize: NETDATA.googleInitialize,
5284 create: NETDATA.googleChartCreate,
5285 update: NETDATA.googleChartUpdate,
5287 setSelection: undefined, //function(state, t) { return true; },
5288 clearSelection: undefined, //function(state) { return true; },
5289 toolboxPanAndZoom: null,
5292 format: function(state) { return 'datatable'; },
5293 options: function(state) { return ''; },
5294 legend: function(state) { return null; },
5295 autoresize: function(state) { return false; },
5296 max_updates_to_recreate: function(state) { return 300; },
5297 track_colors: function(state) { return false; },
5298 pixels_per_point: function(state) { return 4; }
5301 initialize: NETDATA.raphaelInitialize,
5302 create: NETDATA.raphaelChartCreate,
5303 update: NETDATA.raphaelChartUpdate,
5305 setSelection: undefined, // function(state, t) { return true; },
5306 clearSelection: undefined, // function(state) { return true; },
5307 toolboxPanAndZoom: null,
5310 format: function(state) { return 'json'; },
5311 options: function(state) { return ''; },
5312 legend: function(state) { return null; },
5313 autoresize: function(state) { return false; },
5314 max_updates_to_recreate: function(state) { return 5000; },
5315 track_colors: function(state) { return false; },
5316 pixels_per_point: function(state) { return 3; }
5319 initialize: NETDATA.c3Initialize,
5320 create: NETDATA.c3ChartCreate,
5321 update: NETDATA.c3ChartUpdate,
5323 setSelection: undefined, // function(state, t) { return true; },
5324 clearSelection: undefined, // function(state) { return true; },
5325 toolboxPanAndZoom: null,
5328 format: function(state) { return 'csvjsonarray'; },
5329 options: function(state) { return 'milliseconds'; },
5330 legend: function(state) { return null; },
5331 autoresize: function(state) { return false; },
5332 max_updates_to_recreate: function(state) { return 5000; },
5333 track_colors: function(state) { return false; },
5334 pixels_per_point: function(state) { return 15; }
5337 initialize: NETDATA.d3Initialize,
5338 create: NETDATA.d3ChartCreate,
5339 update: NETDATA.d3ChartUpdate,
5341 setSelection: undefined, // function(state, t) { return true; },
5342 clearSelection: undefined, // function(state) { return true; },
5343 toolboxPanAndZoom: null,
5346 format: function(state) { return 'json'; },
5347 options: function(state) { return ''; },
5348 legend: function(state) { return null; },
5349 autoresize: function(state) { return false; },
5350 max_updates_to_recreate: function(state) { return 5000; },
5351 track_colors: function(state) { return false; },
5352 pixels_per_point: function(state) { return 3; }
5355 initialize: NETDATA.easypiechartInitialize,
5356 create: NETDATA.easypiechartChartCreate,
5357 update: NETDATA.easypiechartChartUpdate,
5359 setSelection: NETDATA.easypiechartSetSelection,
5360 clearSelection: NETDATA.easypiechartClearSelection,
5361 toolboxPanAndZoom: null,
5364 format: function(state) { return 'array'; },
5365 options: function(state) { return 'absolute'; },
5366 legend: function(state) { return null; },
5367 autoresize: function(state) { return false; },
5368 max_updates_to_recreate: function(state) { return 5000; },
5369 track_colors: function(state) { return true; },
5370 pixels_per_point: function(state) { return 3; },
5374 initialize: NETDATA.gaugeInitialize,
5375 create: NETDATA.gaugeChartCreate,
5376 update: NETDATA.gaugeChartUpdate,
5378 setSelection: NETDATA.gaugeSetSelection,
5379 clearSelection: NETDATA.gaugeClearSelection,
5380 toolboxPanAndZoom: null,
5383 format: function(state) { return 'array'; },
5384 options: function(state) { return 'absolute'; },
5385 legend: function(state) { return null; },
5386 autoresize: function(state) { return false; },
5387 max_updates_to_recreate: function(state) { return 5000; },
5388 track_colors: function(state) { return true; },
5389 pixels_per_point: function(state) { return 3; },
5394 NETDATA.registerChartLibrary = function(library, url) {
5395 if(NETDATA.options.debug.libraries === true)
5396 console.log("registering chart library: " + library);
5398 NETDATA.chartLibraries[library].url = url;
5399 NETDATA.chartLibraries[library].initialized = true;
5400 NETDATA.chartLibraries[library].enabled = true;
5403 // ----------------------------------------------------------------------------------------------------------------
5404 // Load required JS libraries and CSS
5406 NETDATA.requiredJs = [
5408 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5409 isAlreadyLoaded: function() {
5410 // check if bootstrap is loaded
5411 if(typeof $().emulateTransitionEnd == 'function')
5414 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5422 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5423 isAlreadyLoaded: function() { return false; }
5426 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5427 isAlreadyLoaded: function() { return false; }
5431 NETDATA.requiredCSS = [
5433 url: NETDATA.themes.current.bootstrap_css,
5434 isAlreadyLoaded: function() {
5435 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5442 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5443 isAlreadyLoaded: function() { return false; }
5446 url: NETDATA.themes.current.dashboard_css,
5447 isAlreadyLoaded: function() { return false; }
5450 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5451 isAlreadyLoaded: function() { return false; }
5455 NETDATA.loadRequiredJs = function(index, callback) {
5456 if(index >= NETDATA.requiredJs.length) {
5457 if(typeof callback === 'function')
5462 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5463 NETDATA.loadRequiredJs(++index, callback);
5467 if(NETDATA.options.debug.main_loop === true)
5468 console.log('loading ' + NETDATA.requiredJs[index].url);
5471 url: NETDATA.requiredJs[index].url,
5475 .success(function() {
5476 if(NETDATA.options.debug.main_loop === true)
5477 console.log('loaded ' + NETDATA.requiredJs[index].url);
5479 NETDATA.loadRequiredJs(++index, callback);
5482 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5486 NETDATA.loadRequiredCSS = function(index) {
5487 if(index >= NETDATA.requiredCSS.length)
5490 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5491 NETDATA.loadRequiredCSS(++index);
5495 if(NETDATA.options.debug.main_loop === true)
5496 console.log('loading ' + NETDATA.requiredCSS[index].url);
5498 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5499 NETDATA.loadRequiredCSS(++index);
5503 // ----------------------------------------------------------------------------------------------------------------
5504 // Registry of netdata hosts
5506 NETDATA.registry = {
5507 server: null, // the netdata registry server
5508 person_guid: null, // the unique ID of this browser / user
5509 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5510 hostname: null, // the hostname of the netdata server that served dashboard.js
5511 machines: null, // the user's other URLs
5512 machines_array: null, // the user's other URLs in an array
5515 parsePersonUrls: function(person_urls) {
5516 // console.log(person_urls);
5517 NETDATA.registry.person_urls = person_urls;
5520 NETDATA.registry.machines = {};
5521 NETDATA.registry.machines_array = new Array();
5523 var now = new Date().getTime();
5524 var apu = person_urls;
5527 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5528 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5534 accesses: apu[i][3],
5536 alternate_urls: new Array()
5538 obj.alternate_urls.push(apu[i][1]);
5540 NETDATA.registry.machines[apu[i][0]] = obj;
5541 NETDATA.registry.machines_array.push(obj);
5544 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5546 var pu = NETDATA.registry.machines[apu[i][0]];
5547 if(pu.last_t < apu[i][2]) {
5549 pu.last_t = apu[i][2];
5550 pu.name = apu[i][4];
5552 pu.accesses += apu[i][3];
5553 pu.alternate_urls.push(apu[i][1]);
5558 if(typeof netdataRegistryCallback === 'function')
5559 netdataRegistryCallback(NETDATA.registry.machines_array);
5563 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5566 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5568 NETDATA.registry.server = data.registry;
5569 NETDATA.registry.machine_guid = data.machine_guid;
5570 NETDATA.registry.hostname = data.hostname;
5572 NETDATA.registry.access(2, function (person_urls) {
5573 NETDATA.registry.parsePersonUrls(person_urls);
5580 hello: function(host, callback) {
5581 while(host.slice(-1) === '/')
5582 host = host.substring(0, host.length - 1);
5584 // send HELLO to a netdata server:
5585 // 1. verifies the server is reachable
5586 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5588 url: host + '/api/v1/registry?action=hello',
5591 xhrFields: { withCredentials: true } // required for the cookie
5593 .done(function(data) {
5594 if(typeof data.status !== 'string' || data.status !== 'ok') {
5595 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5599 if(typeof callback === 'function')
5603 NETDATA.error(407, host);
5605 if(typeof callback === 'function')
5610 access: function(max_redirects, callback) {
5611 // send ACCESS to a netdata registry:
5612 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5613 // 2. it responds with a list of netdata servers we know
5614 // the registry identifies us using a cookie it sets the first time we access it
5615 // the registry may respond with a redirect URL to send us to another registry
5617 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),
5620 xhrFields: { withCredentials: true } // required for the cookie
5622 .done(function(data) {
5623 var redirect = null;
5624 if(typeof data.registry === 'string')
5625 redirect = data.registry;
5627 if(typeof data.status !== 'string' || data.status !== 'ok') {
5628 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5632 if(data === null && redirect !== null && max_redirects > 0) {
5633 NETDATA.registry.server = redirect;
5634 NETDATA.registry.access(max_redirects - 1, callback);
5637 if(typeof data.person_guid === 'string')
5638 NETDATA.registry.person_guid = data.person_guid;
5640 if(typeof callback === 'function')
5641 callback(data.urls);
5645 NETDATA.error(410, NETDATA.registry.server);
5647 if(typeof callback === 'function')
5652 delete: function(delete_url, callback) {
5653 // send DELETE to a netdata registry:
5655 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),
5658 xhrFields: { withCredentials: true } // required for the cookie
5660 .done(function(data) {
5661 if(typeof data.status !== 'string' || data.status !== 'ok') {
5662 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5666 if(typeof callback === 'function')
5670 NETDATA.error(412, NETDATA.registry.server);
5672 if(typeof callback === 'function')
5677 switch: function(new_person_guid, callback) {
5680 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,
5683 xhrFields: { withCredentials: true } // required for the cookie
5685 .done(function(data) {
5686 if(typeof data.status !== 'string' || data.status !== 'ok') {
5687 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5691 if(typeof callback === 'function')
5695 NETDATA.error(414, NETDATA.registry.server);
5697 if(typeof callback === 'function')
5703 // ----------------------------------------------------------------------------------------------------------------
5706 NETDATA.errorReset();
5707 NETDATA.loadRequiredCSS(0);
5709 NETDATA._loadjQuery(function() {
5710 NETDATA.loadRequiredJs(0, function() {
5711 if(typeof $().emulateTransitionEnd !== 'function') {
5712 // bootstrap is not available
5713 NETDATA.options.current.show_help = false;
5716 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5717 if(NETDATA.options.debug.main_loop === true)
5718 console.log('starting chart refresh thread');
5725 // window.NETDATA = NETDATA;
5726 // })(window, document);