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 state.sparkline_options = {
3546 lineColor: lineColor,
3547 fillColor: fillColor,
3548 chartRangeMin: chartRangeMin,
3549 chartRangeMax: chartRangeMax,
3550 composite: composite,
3551 enableTagOptions: enableTagOptions,
3552 tagOptionPrefix: tagOptionPrefix,
3553 tagValuesAttribute: tagValuesAttribute,
3554 disableHiddenCheck: disableHiddenCheck,
3555 defaultPixelsPerValue: defaultPixelsPerValue,
3556 spotColor: spotColor,
3557 minSpotColor: minSpotColor,
3558 maxSpotColor: maxSpotColor,
3559 spotRadius: spotRadius,
3560 valueSpots: valueSpots,
3561 highlightSpotColor: highlightSpotColor,
3562 highlightLineColor: highlightLineColor,
3563 lineWidth: lineWidth,
3564 normalRangeMin: normalRangeMin,
3565 normalRangeMax: normalRangeMax,
3566 drawNormalOnTop: drawNormalOnTop,
3568 chartRangeClip: chartRangeClip,
3569 chartRangeMinX: chartRangeMinX,
3570 chartRangeMaxX: chartRangeMaxX,
3571 disableInteraction: disableInteraction,
3572 disableTooltips: disableTooltips,
3573 disableHighlight: disableHighlight,
3574 highlightLighten: highlightLighten,
3575 highlightColor: highlightColor,
3576 tooltipContainer: tooltipContainer,
3577 tooltipClassname: tooltipClassname,
3578 tooltipChartTitle: state.title,
3579 tooltipFormat: tooltipFormat,
3580 tooltipPrefix: tooltipPrefix,
3581 tooltipSuffix: tooltipSuffix,
3582 tooltipSkipNull: tooltipSkipNull,
3583 tooltipValueLookups: tooltipValueLookups,
3584 tooltipFormatFieldlist: tooltipFormatFieldlist,
3585 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3586 numberFormatter: numberFormatter,
3587 numberDigitGroupSep: numberDigitGroupSep,
3588 numberDecimalMark: numberDecimalMark,
3589 numberDigitGroupCount: numberDigitGroupCount,
3590 animatedZooms: animatedZooms,
3591 width: state.chartWidth(),
3592 height: state.chartHeight()
3595 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3599 // ----------------------------------------------------------------------------------------------------------------
3606 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3607 if(after < state.netdata_first)
3608 after = state.netdata_first;
3610 if(before > state.netdata_last)
3611 before = state.netdata_last;
3613 state.setMode('zoom');
3614 state.globalSelectionSyncStop();
3615 state.globalSelectionSyncDelay();
3616 state.dygraph_user_action = true;
3617 state.dygraph_force_zoom = true;
3618 state.updateChartPanOrZoom(after, before);
3619 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3622 NETDATA.dygraphSetSelection = function(state, t) {
3623 if(typeof state.dygraph_instance !== 'undefined') {
3624 var r = state.calculateRowForTime(t);
3626 state.dygraph_instance.setSelection(r);
3628 state.dygraph_instance.clearSelection();
3629 state.legendShowUndefined();
3636 NETDATA.dygraphClearSelection = function(state, t) {
3637 if(typeof state.dygraph_instance !== 'undefined') {
3638 state.dygraph_instance.clearSelection();
3643 NETDATA.dygraphSmoothInitialize = function(callback) {
3645 url: NETDATA.dygraph_smooth_js,
3650 NETDATA.dygraph.smooth = true;
3651 smoothPlotter.smoothing = 0.3;
3654 NETDATA.dygraph.smooth = false;
3656 .always(function() {
3657 if(typeof callback === "function")
3662 NETDATA.dygraphInitialize = function(callback) {
3663 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3665 url: NETDATA.dygraph_js,
3670 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3673 NETDATA.chartLibraries.dygraph.enabled = false;
3674 NETDATA.error(100, NETDATA.dygraph_js);
3676 .always(function() {
3677 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3678 NETDATA.dygraphSmoothInitialize(callback);
3679 else if(typeof callback === "function")
3684 NETDATA.chartLibraries.dygraph.enabled = false;
3685 if(typeof callback === "function")
3690 NETDATA.dygraphChartUpdate = function(state, data) {
3691 var dygraph = state.dygraph_instance;
3693 if(typeof dygraph === 'undefined')
3694 return NETDATA.dygraphChartCreate(state, data);
3696 // when the chart is not visible, and hidden
3697 // if there is a window resize, dygraph detects
3698 // its element size as 0x0.
3699 // this will make it re-appear properly
3701 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3705 file: data.result.data,
3706 colors: state.chartColors(),
3707 labels: data.result.labels,
3708 labelsDivWidth: state.chartWidth() - 70,
3709 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3712 if(state.dygraph_force_zoom === true) {
3713 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3714 state.log('dygraphChartUpdate() forced zoom update');
3716 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3717 options.valueRange = null;
3718 options.isZoomedIgnoreProgrammaticZoom = true;
3719 state.dygraph_force_zoom = false;
3721 else if(state.current.name !== 'auto') {
3722 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3723 state.log('dygraphChartUpdate() loose update');
3726 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3727 state.log('dygraphChartUpdate() strict update');
3729 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3730 options.valueRange = null;
3731 options.isZoomedIgnoreProgrammaticZoom = true;
3734 if(state.dygraph_smooth_eligible === true) {
3735 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3736 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3737 NETDATA.dygraphChartCreate(state, data);
3742 dygraph.updateOptions(options);
3744 state.dygraph_last_rendered = new Date().getTime();
3748 NETDATA.dygraphChartCreate = function(state, data) {
3749 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3750 state.log('dygraphChartCreate()');
3752 var self = $(state.element);
3754 var chart_type = state.chart.chart_type;
3755 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3756 chart_type = self.data('dygraph-type') || chart_type;
3758 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3759 smooth = self.data('dygraph-smooth') || smooth;
3761 if(NETDATA.dygraph.smooth === false)
3764 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3765 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3767 state.dygraph_options = {
3768 colors: self.data('dygraph-colors') || state.chartColors(),
3770 // leave a few pixels empty on the right of the chart
3771 rightGap: self.data('dygraph-rightgap') || 5,
3772 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3773 showRoller: self.data('dygraph-showroller') || false,
3775 title: self.data('dygraph-title') || state.title,
3776 titleHeight: self.data('dygraph-titleheight') || 19,
3778 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3779 labels: data.result.labels,
3780 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3781 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3782 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3783 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3784 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3787 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3788 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3790 includeZero: self.data('dygraph-includezero') || false,
3791 xRangePad: self.data('dygraph-xrangepad') || 0,
3792 yRangePad: self.data('dygraph-yrangepad') || 1,
3794 ylabel: state.units,
3795 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3797 // the function to plot the chart
3800 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3801 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3802 strokePattern: self.data('dygraph-strokepattern') || undefined,
3804 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3805 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3806 drawPoints: self.data('dygraph-drawpoints') || false,
3808 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3809 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3811 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3812 pointSize: self.data('dygraph-pointsize') || 1,
3814 // enabling this makes the chart with little square lines
3815 stepPlot: self.data('dygraph-stepplot') || false,
3817 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3818 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3819 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3821 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3822 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3823 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3824 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3826 drawAxis: self.data('dygraph-drawaxis') || true,
3827 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3828 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3829 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3831 drawGrid: self.data('dygraph-drawgrid') || true,
3832 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3833 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3834 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3835 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3836 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3838 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3839 sigFigs: self.data('dygraph-sigfigs') || null,
3840 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3841 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3843 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3844 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3845 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3847 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3848 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3852 ticker: Dygraph.dateTicker,
3853 axisLabelFormatter: function (d, gran) {
3854 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3856 valueFormatter: function (ms) {
3857 var d = new Date(ms);
3858 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3859 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3864 valueFormatter: function (x) {
3865 // we format legends with the state object
3866 // no need to do anything here
3867 // return (Math.round(x*100) / 100).toLocaleString();
3868 // return state.legendFormatValue(x);
3873 legendFormatter: function(data) {
3874 var elements = state.element_legend_childs;
3876 // if the hidden div is not there
3877 // we are not managing the legend
3878 if(elements.hidden === null) return;
3880 if (typeof data.x !== 'undefined') {
3881 state.legendSetDate(data.x);
3882 var i = data.series.length;
3884 var series = data.series[i];
3885 if(!series.isVisible) continue;
3886 state.legendSetLabelValue(series.label, series.y);
3892 drawCallback: function(dygraph, is_initial) {
3893 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3894 state.dygraph_user_action = false;
3896 var x_range = dygraph.xAxisRange();
3897 var after = Math.round(x_range[0]);
3898 var before = Math.round(x_range[1]);
3900 if(NETDATA.options.debug.dygraph === true)
3901 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3903 if(before <= state.netdata_last && after >= state.netdata_first)
3904 state.updateChartPanOrZoom(after, before);
3907 zoomCallback: function(minDate, maxDate, yRanges) {
3908 if(NETDATA.options.debug.dygraph === true)
3909 state.log('dygraphZoomCallback()');
3911 state.globalSelectionSyncStop();
3912 state.globalSelectionSyncDelay();
3913 state.setMode('zoom');
3915 // refresh it to the greatest possible zoom level
3916 state.dygraph_user_action = true;
3917 state.dygraph_force_zoom = true;
3918 state.updateChartPanOrZoom(minDate, maxDate);
3920 highlightCallback: function(event, x, points, row, seriesName) {
3921 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3922 state.log('dygraphHighlightCallback()');
3926 // there is a bug in dygraph when the chart is zoomed enough
3927 // the time it thinks is selected is wrong
3928 // here we calculate the time t based on the row number selected
3930 var t = state.data_after + row * state.data_update_every;
3931 // 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);
3933 state.globalSelectionSync(x);
3935 // fix legend zIndex using the internal structures of dygraph legend module
3936 // this works, but it is a hack!
3937 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3939 unhighlightCallback: function(event) {
3940 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3941 state.log('dygraphUnhighlightCallback()');
3943 state.unpauseChart();
3944 state.globalSelectionSyncStop();
3946 interactionModel : {
3947 mousedown: function(event, dygraph, context) {
3948 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3949 state.log('interactionModel.mousedown()');
3951 state.dygraph_user_action = true;
3952 state.globalSelectionSyncStop();
3954 if(NETDATA.options.debug.dygraph === true)
3955 state.log('dygraphMouseDown()');
3957 // Right-click should not initiate a zoom.
3958 if(event.button && event.button === 2) return;
3960 context.initializeMouseDown(event, dygraph, context);
3962 if(event.button && event.button === 1) {
3963 if (event.altKey || event.shiftKey) {
3964 state.setMode('pan');
3965 state.globalSelectionSyncDelay();
3966 Dygraph.startPan(event, dygraph, context);
3969 state.setMode('zoom');
3970 state.globalSelectionSyncDelay();
3971 Dygraph.startZoom(event, dygraph, context);
3975 if (event.altKey || event.shiftKey) {
3976 state.setMode('zoom');
3977 state.globalSelectionSyncDelay();
3978 Dygraph.startZoom(event, dygraph, context);
3981 state.setMode('pan');
3982 state.globalSelectionSyncDelay();
3983 Dygraph.startPan(event, dygraph, context);
3987 mousemove: function(event, dygraph, context) {
3988 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3989 state.log('interactionModel.mousemove()');
3991 if(context.isPanning) {
3992 state.dygraph_user_action = true;
3993 state.globalSelectionSyncStop();
3994 state.globalSelectionSyncDelay();
3995 state.setMode('pan');
3996 Dygraph.movePan(event, dygraph, context);
3998 else if(context.isZooming) {
3999 state.dygraph_user_action = true;
4000 state.globalSelectionSyncStop();
4001 state.globalSelectionSyncDelay();
4002 state.setMode('zoom');
4003 Dygraph.moveZoom(event, dygraph, context);
4006 mouseup: function(event, dygraph, context) {
4007 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4008 state.log('interactionModel.mouseup()');
4010 if (context.isPanning) {
4011 state.dygraph_user_action = true;
4012 state.globalSelectionSyncDelay();
4013 Dygraph.endPan(event, dygraph, context);
4015 else if (context.isZooming) {
4016 state.dygraph_user_action = true;
4017 state.globalSelectionSyncDelay();
4018 Dygraph.endZoom(event, dygraph, context);
4021 click: function(event, dygraph, context) {
4022 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4023 state.log('interactionModel.click()');
4025 event.preventDefault();
4027 dblclick: function(event, dygraph, context) {
4028 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4029 state.log('interactionModel.dblclick()');
4030 NETDATA.resetAllCharts(state);
4032 mousewheel: function(event, dygraph, context) {
4033 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4034 state.log('interactionModel.mousewheel()');
4036 // Take the offset of a mouse event on the dygraph canvas and
4037 // convert it to a pair of percentages from the bottom left.
4038 // (Not top left, bottom is where the lower value is.)
4039 function offsetToPercentage(g, offsetX, offsetY) {
4040 // This is calculating the pixel offset of the leftmost date.
4041 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4042 var yar0 = g.yAxisRange(0);
4044 // This is calculating the pixel of the higest value. (Top pixel)
4045 var yOffset = g.toDomCoords(null, yar0[1])[1];
4047 // x y w and h are relative to the corner of the drawing area,
4048 // so that the upper corner of the drawing area is (0, 0).
4049 var x = offsetX - xOffset;
4050 var y = offsetY - yOffset;
4052 // This is computing the rightmost pixel, effectively defining the
4054 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4056 // This is computing the lowest pixel, effectively defining the height.
4057 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4059 // Percentage from the left.
4060 var xPct = w === 0 ? 0 : (x / w);
4061 // Percentage from the top.
4062 var yPct = h === 0 ? 0 : (y / h);
4064 // The (1-) part below changes it from "% distance down from the top"
4065 // to "% distance up from the bottom".
4066 return [xPct, (1-yPct)];
4069 // Adjusts [x, y] toward each other by zoomInPercentage%
4070 // Split it so the left/bottom axis gets xBias/yBias of that change and
4071 // tight/top gets (1-xBias)/(1-yBias) of that change.
4073 // If a bias is missing it splits it down the middle.
4074 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4075 xBias = xBias || 0.5;
4076 yBias = yBias || 0.5;
4078 function adjustAxis(axis, zoomInPercentage, bias) {
4079 var delta = axis[1] - axis[0];
4080 var increment = delta * zoomInPercentage;
4081 var foo = [increment * bias, increment * (1-bias)];
4083 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4086 var yAxes = g.yAxisRanges();
4088 for (var i = 0; i < yAxes.length; i++) {
4089 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4092 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4095 if(event.altKey || event.shiftKey) {
4096 state.dygraph_user_action = true;
4098 state.globalSelectionSyncStop();
4099 state.globalSelectionSyncDelay();
4101 // http://dygraphs.com/gallery/interaction-api.js
4102 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4103 var percentage = normal / 50;
4105 if (!(event.offsetX && event.offsetY)){
4106 event.offsetX = event.layerX - event.target.offsetLeft;
4107 event.offsetY = event.layerY - event.target.offsetTop;
4110 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4111 var xPct = percentages[0];
4112 var yPct = percentages[1];
4114 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4116 var after = new_x_range[0];
4117 var before = new_x_range[1];
4119 var first = state.netdata_first + state.data_update_every;
4120 var last = state.netdata_last + state.data_update_every;
4123 after -= (before - last);
4130 state.setMode('zoom');
4131 if(state.updateChartPanOrZoom(after, before) === true)
4132 dygraph.updateOptions({ dateWindow: [ after, before ] });
4134 event.preventDefault();
4137 touchstart: function(event, dygraph, context) {
4138 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4139 state.log('interactionModel.touchstart()');
4141 state.dygraph_user_action = true;
4142 state.setMode('zoom');
4145 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4147 // we overwrite the touch directions at the end, to overwrite
4148 // the internal default of dygraphs
4149 context.touchDirections = { x: true, y: false };
4151 state.dygraph_last_touch_start = new Date().getTime();
4152 state.dygraph_last_touch_move = 0;
4154 if(typeof event.touches[0].pageX === 'number')
4155 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4157 state.dygraph_last_touch_page_x = 0;
4159 touchmove: function(event, dygraph, context) {
4160 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4161 state.log('interactionModel.touchmove()');
4163 state.dygraph_user_action = true;
4164 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4166 state.dygraph_last_touch_move = new Date().getTime();
4168 touchend: function(event, dygraph, context) {
4169 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4170 state.log('interactionModel.touchend()');
4172 state.dygraph_user_action = true;
4173 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4175 // if it didn't move, it is a selection
4176 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4177 // internal api of dygraphs
4178 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4179 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4180 if(NETDATA.dygraphSetSelection(state, t) === true)
4181 state.globalSelectionSync(t);
4184 // if it was double tap within double click time, reset the charts
4185 var now = new Date().getTime();
4186 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4187 if(state.dygraph_last_touch_move === 0) {
4188 var dt = now - state.dygraph_last_touch_end;
4189 if(dt <= NETDATA.options.current.double_click_speed)
4190 NETDATA.resetAllCharts(state);
4194 // remember the timestamp of the last touch end
4195 state.dygraph_last_touch_end = now;
4200 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4201 state.dygraph_options.drawGrid = false;
4202 state.dygraph_options.drawAxis = false;
4203 state.dygraph_options.title = undefined;
4204 state.dygraph_options.units = undefined;
4205 state.dygraph_options.ylabel = undefined;
4206 state.dygraph_options.yLabelWidth = 0;
4207 state.dygraph_options.labelsDivWidth = 120;
4208 state.dygraph_options.labelsDivStyles.width = '120px';
4209 state.dygraph_options.labelsSeparateLines = true;
4210 state.dygraph_options.rightGap = 0;
4211 state.dygraph_options.yRangePad = 1;
4214 if(smooth === true) {
4215 state.dygraph_smooth_eligible = true;
4217 if(NETDATA.options.current.smooth_plot === true)
4218 state.dygraph_options.plotter = smoothPlotter;
4220 else state.dygraph_smooth_eligible = false;
4222 state.dygraph_instance = new Dygraph(state.element_chart,
4223 data.result.data, state.dygraph_options);
4225 state.dygraph_force_zoom = false;
4226 state.dygraph_user_action = false;
4227 state.dygraph_last_rendered = new Date().getTime();
4231 // ----------------------------------------------------------------------------------------------------------------
4234 NETDATA.morrisInitialize = function(callback) {
4235 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4237 // morris requires raphael
4238 if(!NETDATA.chartLibraries.raphael.initialized) {
4239 if(NETDATA.chartLibraries.raphael.enabled) {
4240 NETDATA.raphaelInitialize(function() {
4241 NETDATA.morrisInitialize(callback);
4245 NETDATA.chartLibraries.morris.enabled = false;
4246 if(typeof callback === "function")
4251 NETDATA._loadCSS(NETDATA.morris_css);
4254 url: NETDATA.morris_js,
4259 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4262 NETDATA.chartLibraries.morris.enabled = false;
4263 NETDATA.error(100, NETDATA.morris_js);
4265 .always(function() {
4266 if(typeof callback === "function")
4272 NETDATA.chartLibraries.morris.enabled = false;
4273 if(typeof callback === "function")
4278 NETDATA.morrisChartUpdate = function(state, data) {
4279 state.morris_instance.setData(data.result.data);
4283 NETDATA.morrisChartCreate = function(state, data) {
4285 state.morris_options = {
4286 element: state.element_chart.id,
4287 data: data.result.data,
4289 ykeys: data.dimension_names,
4290 labels: data.dimension_names,
4296 continuousLine: false,
4297 behaveLikeLine: false
4300 if(state.chart.chart_type === 'line')
4301 state.morris_instance = new Morris.Line(state.morris_options);
4303 else if(state.chart.chart_type === 'area') {
4304 state.morris_options.behaveLikeLine = true;
4305 state.morris_instance = new Morris.Area(state.morris_options);
4308 state.morris_instance = new Morris.Area(state.morris_options);
4313 // ----------------------------------------------------------------------------------------------------------------
4316 NETDATA.raphaelInitialize = function(callback) {
4317 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4319 url: NETDATA.raphael_js,
4324 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4327 NETDATA.chartLibraries.raphael.enabled = false;
4328 NETDATA.error(100, NETDATA.raphael_js);
4330 .always(function() {
4331 if(typeof callback === "function")
4336 NETDATA.chartLibraries.raphael.enabled = false;
4337 if(typeof callback === "function")
4342 NETDATA.raphaelChartUpdate = function(state, data) {
4343 $(state.element_chart).raphael(data.result, {
4344 width: state.chartWidth(),
4345 height: state.chartHeight()
4351 NETDATA.raphaelChartCreate = function(state, data) {
4352 $(state.element_chart).raphael(data.result, {
4353 width: state.chartWidth(),
4354 height: state.chartHeight()
4360 // ----------------------------------------------------------------------------------------------------------------
4363 NETDATA.c3Initialize = function(callback) {
4364 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4367 if(!NETDATA.chartLibraries.d3.initialized) {
4368 if(NETDATA.chartLibraries.d3.enabled) {
4369 NETDATA.d3Initialize(function() {
4370 NETDATA.c3Initialize(callback);
4374 NETDATA.chartLibraries.c3.enabled = false;
4375 if(typeof callback === "function")
4380 NETDATA._loadCSS(NETDATA.c3_css);
4388 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4391 NETDATA.chartLibraries.c3.enabled = false;
4392 NETDATA.error(100, NETDATA.c3_js);
4394 .always(function() {
4395 if(typeof callback === "function")
4401 NETDATA.chartLibraries.c3.enabled = false;
4402 if(typeof callback === "function")
4407 NETDATA.c3ChartUpdate = function(state, data) {
4408 state.c3_instance.destroy();
4409 return NETDATA.c3ChartCreate(state, data);
4411 //state.c3_instance.load({
4412 // rows: data.result,
4419 NETDATA.c3ChartCreate = function(state, data) {
4421 state.element_chart.id = 'c3-' + state.uuid;
4422 // console.log('id = ' + state.element_chart.id);
4424 state.c3_instance = c3.generate({
4425 bindto: '#' + state.element_chart.id,
4427 width: state.chartWidth(),
4428 height: state.chartHeight()
4431 pattern: state.chartColors()
4436 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4442 format: function(x) {
4443 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4470 // console.log(state.c3_instance);
4475 // ----------------------------------------------------------------------------------------------------------------
4478 NETDATA.d3Initialize = function(callback) {
4479 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4486 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4489 NETDATA.chartLibraries.d3.enabled = false;
4490 NETDATA.error(100, NETDATA.d3_js);
4492 .always(function() {
4493 if(typeof callback === "function")
4498 NETDATA.chartLibraries.d3.enabled = false;
4499 if(typeof callback === "function")
4504 NETDATA.d3ChartUpdate = function(state, data) {
4508 NETDATA.d3ChartCreate = function(state, data) {
4512 // ----------------------------------------------------------------------------------------------------------------
4515 NETDATA.googleInitialize = function(callback) {
4516 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4518 url: NETDATA.google_js,
4523 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4524 google.load('visualization', '1.1', {
4525 'packages': ['corechart', 'controls'],
4526 'callback': callback
4530 NETDATA.chartLibraries.google.enabled = false;
4531 NETDATA.error(100, NETDATA.google_js);
4532 if(typeof callback === "function")
4537 NETDATA.chartLibraries.google.enabled = false;
4538 if(typeof callback === "function")
4543 NETDATA.googleChartUpdate = function(state, data) {
4544 var datatable = new google.visualization.DataTable(data.result);
4545 state.google_instance.draw(datatable, state.google_options);
4549 NETDATA.googleChartCreate = function(state, data) {
4550 var datatable = new google.visualization.DataTable(data.result);
4552 state.google_options = {
4553 colors: state.chartColors(),
4555 // do not set width, height - the chart resizes itself
4556 //width: state.chartWidth(),
4557 //height: state.chartHeight(),
4562 // title: "Time of Day",
4563 // format:'HH:mm:ss',
4564 viewWindowMode: 'maximized',
4576 viewWindowMode: 'pretty',
4591 focusTarget: 'category',
4598 titlePosition: 'out',
4609 curveType: 'function',
4614 switch(state.chart.chart_type) {
4616 state.google_options.vAxis.viewWindowMode = 'maximized';
4617 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4618 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4622 state.google_options.isStacked = true;
4623 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4624 state.google_options.vAxis.viewWindowMode = 'maximized';
4625 state.google_options.vAxis.minValue = null;
4626 state.google_options.vAxis.maxValue = null;
4627 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4632 state.google_options.lineWidth = 2;
4633 state.google_instance = new google.visualization.LineChart(state.element_chart);
4637 state.google_instance.draw(datatable, state.google_options);
4641 // ----------------------------------------------------------------------------------------------------------------
4643 NETDATA.percentFromValueMax = function(value, max) {
4644 if(value === null) value = 0;
4645 if(max < value) max = value;
4649 pcent = Math.round(value * 100 / max);
4650 if(pcent === 0 && value > 0) pcent = 1;
4656 // ----------------------------------------------------------------------------------------------------------------
4659 NETDATA.easypiechartInitialize = function(callback) {
4660 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4662 url: NETDATA.easypiechart_js,
4667 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4670 NETDATA.chartLibraries.easypiechart.enabled = false;
4671 NETDATA.error(100, NETDATA.easypiechart_js);
4673 .always(function() {
4674 if(typeof callback === "function")
4679 NETDATA.chartLibraries.easypiechart.enabled = false;
4680 if(typeof callback === "function")
4685 NETDATA.easypiechartClearSelection = function(state) {
4686 if(typeof state.easyPieChartEvent !== 'undefined') {
4687 if(state.easyPieChartEvent.timer !== null)
4688 clearTimeout(state.easyPieChartEvent.timer);
4690 state.easyPieChartEvent.timer = null;
4693 if(state.isAutoRefreshable() === true && state.data !== null) {
4694 NETDATA.easypiechartChartUpdate(state, state.data);
4697 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4698 state.easyPieChart_instance.update(0);
4700 state.easyPieChart_instance.enableAnimation();
4705 NETDATA.easypiechartSetSelection = function(state, t) {
4706 if(state.timeIsVisible(t) !== true)
4707 return NETDATA.easypiechartClearSelection(state);
4709 var slot = state.calculateRowForTime(t);
4710 if(slot < 0 || slot >= state.data.result.length)
4711 return NETDATA.easypiechartClearSelection(state);
4713 if(typeof state.easyPieChartEvent === 'undefined') {
4714 state.easyPieChartEvent = {
4721 var value = state.data.result[state.data.result.length - 1 - slot];
4722 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4723 var pcent = NETDATA.percentFromValueMax(value, max);
4725 state.easyPieChartEvent.value = value;
4726 state.easyPieChartEvent.pcent = pcent;
4727 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4729 if(state.easyPieChartEvent.timer === null) {
4730 state.easyPieChart_instance.disableAnimation();
4732 state.easyPieChartEvent.timer = setTimeout(function() {
4733 state.easyPieChartEvent.timer = null;
4734 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4735 }, NETDATA.options.current.charts_selection_animation_delay);
4741 NETDATA.easypiechartChartUpdate = function(state, data) {
4742 var value, max, pcent;
4744 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4750 value = data.result[0];
4751 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4752 pcent = NETDATA.percentFromValueMax(value, max);
4755 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4756 state.easyPieChart_instance.update(pcent);
4760 NETDATA.easypiechartChartCreate = function(state, data) {
4761 var self = $(state.element);
4762 var chart = $(state.element_chart);
4764 var value = data.result[0];
4765 var max = self.data('easypiechart-max-value') || null;
4766 var adjust = self.data('easypiechart-adjust') || null;
4770 state.easyPieChartMax = null;
4773 state.easyPieChartMax = max;
4775 var pcent = NETDATA.percentFromValueMax(value, max);
4777 chart.data('data-percent', pcent);
4781 case 'width': size = state.chartHeight(); break;
4782 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4783 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4785 default: size = state.chartWidth(); break;
4787 state.element.style.width = size + 'px';
4788 state.element.style.height = size + 'px';
4790 var stroke = Math.floor(size / 22);
4791 if(stroke < 3) stroke = 2;
4793 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4794 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4795 state.easyPieChartLabel = document.createElement('span');
4796 state.easyPieChartLabel.className = 'easyPieChartLabel';
4797 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4798 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4799 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4800 state.element_chart.appendChild(state.easyPieChartLabel);
4802 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4803 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4804 state.easyPieChartTitle = document.createElement('span');
4805 state.easyPieChartTitle.className = 'easyPieChartTitle';
4806 state.easyPieChartTitle.innerHTML = state.title;
4807 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4808 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4809 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4810 state.element_chart.appendChild(state.easyPieChartTitle);
4812 var unitfontsize = Math.round(titlefontsize * 0.9);
4813 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4814 state.easyPieChartUnits = document.createElement('span');
4815 state.easyPieChartUnits.className = 'easyPieChartUnits';
4816 state.easyPieChartUnits.innerHTML = state.units;
4817 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4818 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4819 state.element_chart.appendChild(state.easyPieChartUnits);
4821 chart.easyPieChart({
4822 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4823 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4824 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4825 scaleLength: self.data('easypiechart-scalelength') || 5,
4826 lineCap: self.data('easypiechart-linecap') || 'round',
4827 lineWidth: self.data('easypiechart-linewidth') || stroke,
4828 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4829 size: self.data('easypiechart-size') || size,
4830 rotate: self.data('easypiechart-rotate') || 0,
4831 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4832 easing: self.data('easypiechart-easing') || undefined
4835 // when we just re-create the chart
4836 // do not animate the first update
4838 if(typeof state.easyPieChart_instance !== 'undefined')
4841 state.easyPieChart_instance = chart.data('easyPieChart');
4842 if(animate === false) state.easyPieChart_instance.disableAnimation();
4843 state.easyPieChart_instance.update(pcent);
4844 if(animate === false) state.easyPieChart_instance.enableAnimation();
4848 // ----------------------------------------------------------------------------------------------------------------
4851 NETDATA.gaugeInitialize = function(callback) {
4852 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4854 url: NETDATA.gauge_js,
4859 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4862 NETDATA.chartLibraries.gauge.enabled = false;
4863 NETDATA.error(100, NETDATA.gauge_js);
4865 .always(function() {
4866 if(typeof callback === "function")
4871 NETDATA.chartLibraries.gauge.enabled = false;
4872 if(typeof callback === "function")
4877 NETDATA.gaugeAnimation = function(state, status) {
4880 if(typeof status === 'boolean' && status === false)
4882 else if(typeof status === 'number')
4885 state.gauge_instance.animationSpeed = speed;
4886 state.___gaugeOld__.speed = speed;
4889 NETDATA.gaugeSet = function(state, value, min, max) {
4890 if(typeof value !== 'number') value = 0;
4891 if(typeof min !== 'number') min = 0;
4892 if(typeof max !== 'number') max = 0;
4893 if(value > max) max = value;
4894 if(value < min) min = value;
4903 // gauge.js has an issue if the needle
4904 // is smaller than min or larger than max
4905 // when we set the new values
4906 // the needle will go crazy
4908 // to prevent it, we always feed it
4909 // with a percentage, so that the needle
4910 // is always between min and max
4911 var pcent = (value - min) * 100 / (max - min);
4913 // these should never happen
4914 if(pcent < 0) pcent = 0;
4915 if(pcent > 100) pcent = 100;
4917 state.gauge_instance.set(pcent);
4919 state.___gaugeOld__.value = value;
4920 state.___gaugeOld__.min = min;
4921 state.___gaugeOld__.max = max;
4924 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4925 if(state.___gaugeOld__.valueLabel !== value) {
4926 state.___gaugeOld__.valueLabel = value;
4927 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4929 if(state.___gaugeOld__.minLabel !== min) {
4930 state.___gaugeOld__.minLabel = min;
4931 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4933 if(state.___gaugeOld__.maxLabel !== max) {
4934 state.___gaugeOld__.maxLabel = max;
4935 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4939 NETDATA.gaugeClearSelection = function(state) {
4940 if(typeof state.gaugeEvent !== 'undefined') {
4941 if(state.gaugeEvent.timer !== null)
4942 clearTimeout(state.gaugeEvent.timer);
4944 state.gaugeEvent.timer = null;
4947 if(state.isAutoRefreshable() === true && state.data !== null) {
4948 NETDATA.gaugeChartUpdate(state, state.data);
4951 NETDATA.gaugeAnimation(state, false);
4952 NETDATA.gaugeSet(state, null, null, null);
4953 NETDATA.gaugeSetLabels(state, null, null, null);
4956 NETDATA.gaugeAnimation(state, true);
4960 NETDATA.gaugeSetSelection = function(state, t) {
4961 if(state.timeIsVisible(t) !== true)
4962 return NETDATA.gaugeClearSelection(state);
4964 var slot = state.calculateRowForTime(t);
4965 if(slot < 0 || slot >= state.data.result.length)
4966 return NETDATA.gaugeClearSelection(state);
4968 if(typeof state.gaugeEvent === 'undefined') {
4969 state.gaugeEvent = {
4977 var value = state.data.result[state.data.result.length - 1 - slot];
4978 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4981 state.gaugeEvent.value = value;
4982 state.gaugeEvent.max = max;
4983 state.gaugeEvent.min = min;
4984 NETDATA.gaugeSetLabels(state, value, min, max);
4986 if(state.gaugeEvent.timer === null) {
4987 NETDATA.gaugeAnimation(state, false);
4989 state.gaugeEvent.timer = setTimeout(function() {
4990 state.gaugeEvent.timer = null;
4991 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4992 }, NETDATA.options.current.charts_selection_animation_delay);
4998 NETDATA.gaugeChartUpdate = function(state, data) {
4999 var value, min, max;
5001 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5005 NETDATA.gaugeSetLabels(state, null, null, null);
5008 value = data.result[0];
5010 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5011 if(value > max) max = value;
5012 NETDATA.gaugeSetLabels(state, value, min, max);
5015 NETDATA.gaugeSet(state, value, min, max);
5019 NETDATA.gaugeChartCreate = function(state, data) {
5020 var self = $(state.element);
5021 // var chart = $(state.element_chart);
5023 var value = data.result[0];
5024 var max = self.data('gauge-max-value') || null;
5025 var adjust = self.data('gauge-adjust') || null;
5026 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5027 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5028 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5029 var stopColor = self.data('gauge-stop-color') || void 0;
5030 var generateGradient = self.data('gauge-generate-gradient') || false;
5034 state.gaugeMax = null;
5037 state.gaugeMax = max;
5039 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5041 // case 'width': width = height * ratio; break;
5043 // default: height = width / ratio; break;
5045 //state.element.style.width = width.toString() + 'px';
5046 //state.element.style.height = height.toString() + 'px';
5051 lines: 12, // The number of lines to draw
5052 angle: 0.15, // The length of each line
5053 lineWidth: 0.44, // 0.44 The line thickness
5055 length: 0.8, // 0.9 The radius of the inner circle
5056 strokeWidth: 0.035, // The rotation offset
5057 color: pointerColor // Fill color
5059 colorStart: startColor, // Colors
5060 colorStop: stopColor, // just experiment with them
5061 strokeColor: strokeColor, // to see which ones work best for you
5063 generateGradient: (generateGradient === true)?true:false,
5067 if (generateGradient.constructor === Array) {
5069 // data-gauge-generate-gradient="[0, 50, 100]"
5070 // data-gauge-gradient-percent-color-0="#FFFFFF"
5071 // data-gauge-gradient-percent-color-50="#999900"
5072 // data-gauge-gradient-percent-color-100="#000000"
5074 options.percentColors = new Array();
5075 var len = generateGradient.length;
5077 var pcent = generateGradient[len];
5078 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5079 if(color !== false) {
5080 var a = new Array();
5083 options.percentColors.unshift(a);
5086 if(options.percentColors.length === 0)
5087 delete options.percentColors;
5089 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5090 options.percentColors = [
5091 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5092 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5093 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5094 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5095 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5096 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5097 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5098 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5099 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5100 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5101 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5104 state.gauge_canvas = document.createElement('canvas');
5105 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5106 state.gauge_canvas.className = 'gaugeChart';
5107 state.gauge_canvas.width = width;
5108 state.gauge_canvas.height = height;
5109 state.element_chart.appendChild(state.gauge_canvas);
5111 var valuefontsize = Math.floor(height / 6);
5112 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5113 state.gaugeChartLabel = document.createElement('span');
5114 state.gaugeChartLabel.className = 'gaugeChartLabel';
5115 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5116 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5117 state.element_chart.appendChild(state.gaugeChartLabel);
5119 var titlefontsize = Math.round(valuefontsize / 2);
5121 state.gaugeChartTitle = document.createElement('span');
5122 state.gaugeChartTitle.className = 'gaugeChartTitle';
5123 state.gaugeChartTitle.innerHTML = state.title;
5124 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5125 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5126 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5127 state.element_chart.appendChild(state.gaugeChartTitle);
5129 var unitfontsize = Math.round(titlefontsize * 0.9);
5130 state.gaugeChartUnits = document.createElement('span');
5131 state.gaugeChartUnits.className = 'gaugeChartUnits';
5132 state.gaugeChartUnits.innerHTML = state.units;
5133 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5134 state.element_chart.appendChild(state.gaugeChartUnits);
5136 state.gaugeChartMin = document.createElement('span');
5137 state.gaugeChartMin.className = 'gaugeChartMin';
5138 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5139 state.element_chart.appendChild(state.gaugeChartMin);
5141 state.gaugeChartMax = document.createElement('span');
5142 state.gaugeChartMax.className = 'gaugeChartMax';
5143 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5144 state.element_chart.appendChild(state.gaugeChartMax);
5146 // when we just re-create the chart
5147 // do not animate the first update
5149 if(typeof state.gauge_instance !== 'undefined')
5152 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5154 state.___gaugeOld__ = {
5163 // we will always feed a percentage
5164 state.gauge_instance.minValue = 0;
5165 state.gauge_instance.maxValue = 100;
5167 NETDATA.gaugeAnimation(state, animate);
5168 NETDATA.gaugeSet(state, value, 0, max);
5169 NETDATA.gaugeSetLabels(state, value, 0, max);
5170 NETDATA.gaugeAnimation(state, true);
5174 // ----------------------------------------------------------------------------------------------------------------
5175 // Charts Libraries Registration
5177 NETDATA.chartLibraries = {
5179 initialize: NETDATA.dygraphInitialize,
5180 create: NETDATA.dygraphChartCreate,
5181 update: NETDATA.dygraphChartUpdate,
5182 resize: function(state) {
5183 if(typeof state.dygraph_instance.resize === 'function')
5184 state.dygraph_instance.resize();
5186 setSelection: NETDATA.dygraphSetSelection,
5187 clearSelection: NETDATA.dygraphClearSelection,
5188 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5191 format: function(state) { return 'json'; },
5192 options: function(state) { return 'ms|flip'; },
5193 legend: function(state) {
5194 if(this.isSparkline(state) === false)
5195 return 'right-side';
5199 autoresize: function(state) { return true; },
5200 max_updates_to_recreate: function(state) { return 5000; },
5201 track_colors: function(state) { return true; },
5202 pixels_per_point: function(state) {
5203 if(this.isSparkline(state) === false)
5209 isSparkline: function(state) {
5210 if(typeof state.dygraph_sparkline === 'undefined') {
5211 var t = $(state.element).data('dygraph-theme');
5212 if(t === 'sparkline')
5213 state.dygraph_sparkline = true;
5215 state.dygraph_sparkline = false;
5217 return state.dygraph_sparkline;
5221 initialize: NETDATA.sparklineInitialize,
5222 create: NETDATA.sparklineChartCreate,
5223 update: NETDATA.sparklineChartUpdate,
5225 setSelection: undefined, // function(state, t) { return true; },
5226 clearSelection: undefined, // function(state) { return true; },
5227 toolboxPanAndZoom: null,
5230 format: function(state) { return 'array'; },
5231 options: function(state) { return 'flip|abs'; },
5232 legend: function(state) { return null; },
5233 autoresize: function(state) { return false; },
5234 max_updates_to_recreate: function(state) { return 5000; },
5235 track_colors: function(state) { return false; },
5236 pixels_per_point: function(state) { return 3; }
5239 initialize: NETDATA.peityInitialize,
5240 create: NETDATA.peityChartCreate,
5241 update: NETDATA.peityChartUpdate,
5243 setSelection: undefined, // function(state, t) { return true; },
5244 clearSelection: undefined, // function(state) { return true; },
5245 toolboxPanAndZoom: null,
5248 format: function(state) { return 'ssvcomma'; },
5249 options: function(state) { return 'null2zero|flip|abs'; },
5250 legend: function(state) { return null; },
5251 autoresize: function(state) { return false; },
5252 max_updates_to_recreate: function(state) { return 5000; },
5253 track_colors: function(state) { return false; },
5254 pixels_per_point: function(state) { return 3; }
5257 initialize: NETDATA.morrisInitialize,
5258 create: NETDATA.morrisChartCreate,
5259 update: NETDATA.morrisChartUpdate,
5261 setSelection: undefined, // function(state, t) { return true; },
5262 clearSelection: undefined, // function(state) { return true; },
5263 toolboxPanAndZoom: null,
5266 format: function(state) { return 'json'; },
5267 options: function(state) { return 'objectrows|ms'; },
5268 legend: function(state) { return null; },
5269 autoresize: function(state) { return false; },
5270 max_updates_to_recreate: function(state) { return 50; },
5271 track_colors: function(state) { return false; },
5272 pixels_per_point: function(state) { return 15; }
5275 initialize: NETDATA.googleInitialize,
5276 create: NETDATA.googleChartCreate,
5277 update: NETDATA.googleChartUpdate,
5279 setSelection: undefined, //function(state, t) { return true; },
5280 clearSelection: undefined, //function(state) { return true; },
5281 toolboxPanAndZoom: null,
5284 format: function(state) { return 'datatable'; },
5285 options: function(state) { return ''; },
5286 legend: function(state) { return null; },
5287 autoresize: function(state) { return false; },
5288 max_updates_to_recreate: function(state) { return 300; },
5289 track_colors: function(state) { return false; },
5290 pixels_per_point: function(state) { return 4; }
5293 initialize: NETDATA.raphaelInitialize,
5294 create: NETDATA.raphaelChartCreate,
5295 update: NETDATA.raphaelChartUpdate,
5297 setSelection: undefined, // function(state, t) { return true; },
5298 clearSelection: undefined, // function(state) { return true; },
5299 toolboxPanAndZoom: null,
5302 format: function(state) { return 'json'; },
5303 options: function(state) { return ''; },
5304 legend: function(state) { return null; },
5305 autoresize: function(state) { return false; },
5306 max_updates_to_recreate: function(state) { return 5000; },
5307 track_colors: function(state) { return false; },
5308 pixels_per_point: function(state) { return 3; }
5311 initialize: NETDATA.c3Initialize,
5312 create: NETDATA.c3ChartCreate,
5313 update: NETDATA.c3ChartUpdate,
5315 setSelection: undefined, // function(state, t) { return true; },
5316 clearSelection: undefined, // function(state) { return true; },
5317 toolboxPanAndZoom: null,
5320 format: function(state) { return 'csvjsonarray'; },
5321 options: function(state) { return 'milliseconds'; },
5322 legend: function(state) { return null; },
5323 autoresize: function(state) { return false; },
5324 max_updates_to_recreate: function(state) { return 5000; },
5325 track_colors: function(state) { return false; },
5326 pixels_per_point: function(state) { return 15; }
5329 initialize: NETDATA.d3Initialize,
5330 create: NETDATA.d3ChartCreate,
5331 update: NETDATA.d3ChartUpdate,
5333 setSelection: undefined, // function(state, t) { return true; },
5334 clearSelection: undefined, // function(state) { return true; },
5335 toolboxPanAndZoom: null,
5338 format: function(state) { return 'json'; },
5339 options: function(state) { return ''; },
5340 legend: function(state) { return null; },
5341 autoresize: function(state) { return false; },
5342 max_updates_to_recreate: function(state) { return 5000; },
5343 track_colors: function(state) { return false; },
5344 pixels_per_point: function(state) { return 3; }
5347 initialize: NETDATA.easypiechartInitialize,
5348 create: NETDATA.easypiechartChartCreate,
5349 update: NETDATA.easypiechartChartUpdate,
5351 setSelection: NETDATA.easypiechartSetSelection,
5352 clearSelection: NETDATA.easypiechartClearSelection,
5353 toolboxPanAndZoom: null,
5356 format: function(state) { return 'array'; },
5357 options: function(state) { return 'absolute'; },
5358 legend: function(state) { return null; },
5359 autoresize: function(state) { return false; },
5360 max_updates_to_recreate: function(state) { return 5000; },
5361 track_colors: function(state) { return true; },
5362 pixels_per_point: function(state) { return 3; },
5366 initialize: NETDATA.gaugeInitialize,
5367 create: NETDATA.gaugeChartCreate,
5368 update: NETDATA.gaugeChartUpdate,
5370 setSelection: NETDATA.gaugeSetSelection,
5371 clearSelection: NETDATA.gaugeClearSelection,
5372 toolboxPanAndZoom: null,
5375 format: function(state) { return 'array'; },
5376 options: function(state) { return 'absolute'; },
5377 legend: function(state) { return null; },
5378 autoresize: function(state) { return false; },
5379 max_updates_to_recreate: function(state) { return 5000; },
5380 track_colors: function(state) { return true; },
5381 pixels_per_point: function(state) { return 3; },
5386 NETDATA.registerChartLibrary = function(library, url) {
5387 if(NETDATA.options.debug.libraries === true)
5388 console.log("registering chart library: " + library);
5390 NETDATA.chartLibraries[library].url = url;
5391 NETDATA.chartLibraries[library].initialized = true;
5392 NETDATA.chartLibraries[library].enabled = true;
5395 // ----------------------------------------------------------------------------------------------------------------
5396 // Load required JS libraries and CSS
5398 NETDATA.requiredJs = [
5400 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5401 isAlreadyLoaded: function() {
5402 // check if bootstrap is loaded
5403 if(typeof $().emulateTransitionEnd == 'function')
5406 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5414 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5415 isAlreadyLoaded: function() { return false; }
5418 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5419 isAlreadyLoaded: function() { return false; }
5423 NETDATA.requiredCSS = [
5425 url: NETDATA.themes.current.bootstrap_css,
5426 isAlreadyLoaded: function() {
5427 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5434 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5435 isAlreadyLoaded: function() { return false; }
5438 url: NETDATA.themes.current.dashboard_css,
5439 isAlreadyLoaded: function() { return false; }
5442 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5443 isAlreadyLoaded: function() { return false; }
5447 NETDATA.loadRequiredJs = function(index, callback) {
5448 if(index >= NETDATA.requiredJs.length) {
5449 if(typeof callback === 'function')
5454 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5455 NETDATA.loadRequiredJs(++index, callback);
5459 if(NETDATA.options.debug.main_loop === true)
5460 console.log('loading ' + NETDATA.requiredJs[index].url);
5463 url: NETDATA.requiredJs[index].url,
5467 .success(function() {
5468 if(NETDATA.options.debug.main_loop === true)
5469 console.log('loaded ' + NETDATA.requiredJs[index].url);
5471 NETDATA.loadRequiredJs(++index, callback);
5474 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5478 NETDATA.loadRequiredCSS = function(index) {
5479 if(index >= NETDATA.requiredCSS.length)
5482 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5483 NETDATA.loadRequiredCSS(++index);
5487 if(NETDATA.options.debug.main_loop === true)
5488 console.log('loading ' + NETDATA.requiredCSS[index].url);
5490 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5491 NETDATA.loadRequiredCSS(++index);
5495 // ----------------------------------------------------------------------------------------------------------------
5496 // Registry of netdata hosts
5498 NETDATA.registry = {
5499 server: null, // the netdata registry server
5500 person_guid: null, // the unique ID of this browser / user
5501 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5502 hostname: null, // the hostname of the netdata server that served dashboard.js
5503 urls: null, // the user's other URLs
5504 urls_array: null, // the user's other URLs in an array
5506 parsePersonUrls: function(person_urls) {
5507 // console.log(person_urls);
5510 NETDATA.registry.urls = {};
5511 NETDATA.registry.urls_array = new Array();
5513 var now = new Date().getTime();
5514 var apu = person_urls;
5517 if(typeof NETDATA.registry.urls[apu[i][0]] === 'undefined') {
5518 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5524 accesses: apu[i][3],
5526 alternate_urls: new Array()
5529 NETDATA.registry.urls[apu[i][0]] = obj;
5530 NETDATA.registry.urls_array.push(obj);
5533 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5535 var pu = NETDATA.registry.urls[apu[i][0]];
5536 if(pu.last_t < apu[i][2]) {
5538 pu.last_t = apu[i][2];
5539 pu.name = apu[i][4];
5541 pu.accesses += apu[i][3];
5542 pu.alternate_urls.push(apu[i][1]);
5547 if(typeof netdataRegistryCallback === 'function')
5548 netdataRegistryCallback(NETDATA.registry.urls_array);
5552 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5555 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5557 NETDATA.registry.server = data.registry;
5558 NETDATA.registry.machine_guid = data.machine_guid;
5559 NETDATA.registry.hostname = data.hostname;
5561 NETDATA.registry.access(10, function (person_urls) {
5562 NETDATA.registry.parsePersonUrls(person_urls);
5569 hello: function(host, callback) {
5570 // send HELLO to a netdata server:
5571 // 1. verifies the server is reachable
5572 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5574 url: host + '/api/v1/registry?action=hello',
5577 xhrFields: { withCredentials: true } // required for the cookie
5579 .done(function(data) {
5580 if(typeof data.status !== 'string' || data.status !== 'ok') {
5581 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5585 if(typeof callback === 'function')
5589 NETDATA.error(407, host);
5591 if(typeof callback === 'function')
5596 access: function(max_redirects, callback) {
5597 // send ACCESS to a netdata registry:
5598 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5599 // 2. it responds with a list of netdata servers we know
5600 // the registry identifies us using a cookie it sets the first time we access it
5601 // the registry may respond with a redirect URL to send us to another registry
5603 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),
5606 xhrFields: { withCredentials: true } // required for the cookie
5608 .done(function(data) {
5609 var redirect = null;
5610 if(typeof data.registry === 'string')
5611 redirect = data.registry;
5613 if(typeof data.status !== 'string' || data.status !== 'ok') {
5614 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5618 if(data === null && redirect !== null && max_redirects > 0) {
5619 NETDATA.registry.server = redirect;
5620 NETDATA.registry.access(max_redirects - 1, callback);
5623 if(typeof data.person_guid === 'string')
5624 NETDATA.registry.person_guid = data.person_guid;
5626 if(typeof callback === 'function')
5627 callback(data.urls);
5631 NETDATA.error(410, NETDATA.registry.server);
5633 if(typeof callback === 'function')
5638 delete: function(delete_url, callback) {
5639 // send DELETE to a netdata registry:
5641 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),
5644 xhrFields: { withCredentials: true } // required for the cookie
5646 .done(function(data) {
5647 if(typeof data.status !== 'string' || data.status !== 'ok') {
5648 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5652 if(typeof callback === 'function')
5656 NETDATA.error(412, NETDATA.registry.server);
5658 if(typeof callback === 'function')
5663 switch: function(new_person_guid, callback) {
5666 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,
5669 xhrFields: { withCredentials: true } // required for the cookie
5671 .done(function(data) {
5672 if(typeof data.status !== 'string' || data.status !== 'ok') {
5673 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5677 if(typeof callback === 'function')
5681 NETDATA.error(414, NETDATA.registry.server);
5683 if(typeof callback === 'function')
5689 // ----------------------------------------------------------------------------------------------------------------
5692 NETDATA.errorReset();
5693 NETDATA.loadRequiredCSS(0);
5695 NETDATA._loadjQuery(function() {
5696 NETDATA.loadRequiredJs(0, function() {
5697 if(typeof $().emulateTransitionEnd !== 'function') {
5698 // bootstrap is not available
5699 NETDATA.options.current.show_help = false;
5702 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5703 if(NETDATA.options.debug.main_loop === true)
5704 console.log('starting chart refresh thread');
5711 // window.NETDATA = NETDATA;
5712 // })(window, document);