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 }
501 NETDATA.errorLast = {
507 NETDATA.error = function(code, msg) {
508 NETDATA.errorLast.code = code;
509 NETDATA.errorLast.message = msg;
510 NETDATA.errorLast.datetime = new Date().getTime();
512 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
515 if(typeof netdataErrorCallback === 'function') {
516 ret = netdataErrorCallback('system', code, msg);
519 if(ret && NETDATA.errorCodes[code].alert)
520 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
523 NETDATA.errorReset = function() {
524 NETDATA.errorLast.code = 0;
525 NETDATA.errorLast.message = "You are doing fine!";
526 NETDATA.errorLast.datetime = 0;
529 // ----------------------------------------------------------------------------------------------------------------
532 // When multiple charts need the same chart, we avoid downloading it
533 // multiple times (and having it in browser memory multiple time)
534 // by using this registry.
536 // Every time we download a chart definition, we save it here with .add()
537 // Then we try to get it back with .get(). If that fails, we download it.
539 NETDATA.chartRegistry = {
542 fixid: function(id) {
543 return id.replace(/:/g, "_").replace(/\//g, "_");
546 add: function(host, id, data) {
547 host = this.fixid(host);
550 if(typeof this.charts[host] === 'undefined')
551 this.charts[host] = {};
553 //console.log('added ' + host + '/' + id);
554 this.charts[host][id] = data;
557 get: function(host, id) {
558 host = this.fixid(host);
561 if(typeof this.charts[host] === 'undefined')
564 if(typeof this.charts[host][id] === 'undefined')
567 //console.log('cached ' + host + '/' + id);
568 return this.charts[host][id];
571 downloadAll: function(host, callback) {
572 while(host.slice(-1) === '/')
573 host = host.substring(0, host.length - 1);
578 url: host + '/api/v1/charts',
582 .done(function(data) {
584 var h = NETDATA.chartRegistry.fixid(host);
585 self.charts[h] = data.charts;
587 else NETDATA.error(406, host + '/api/v1/charts');
589 if(typeof callback === 'function')
593 NETDATA.error(405, host + '/api/v1/charts');
595 if(typeof callback === 'function')
601 // ----------------------------------------------------------------------------------------------------------------
602 // Global Pan and Zoom on charts
604 // Using this structure are synchronize all the charts, so that
605 // when you pan or zoom one, all others are automatically refreshed
606 // to the same timespan.
608 NETDATA.globalPanAndZoom = {
609 seq: 0, // timestamp ms
610 // every time a chart is panned or zoomed
611 // we set the timestamp here
612 // then we use it as a sequence number
613 // to find if other charts are syncronized
616 master: null, // the master chart (state), to which all others
619 force_before_ms: null, // the timespan to sync all other charts
620 force_after_ms: null,
623 setMaster: function(state, after, before) {
624 if(NETDATA.options.current.sync_pan_and_zoom === false)
627 if(this.master !== null && this.master !== state)
628 this.master.resetChart(true, true);
630 var now = new Date().getTime();
633 this.force_after_ms = after;
634 this.force_before_ms = before;
635 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
639 clearMaster: function() {
640 if(this.master !== null) {
641 var st = this.master;
648 this.force_after_ms = null;
649 this.force_before_ms = null;
650 NETDATA.options.auto_refresher_stop_until = 0;
653 // is the given state the master of the global
654 // pan and zoom sync?
655 isMaster: function(state) {
656 if(this.master === state) return true;
660 // are we currently have a global pan and zoom sync?
661 isActive: function() {
662 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
666 // check if a chart, other than the master
667 // needs to be refreshed, due to the global pan and zoom
668 shouldBeAutoRefreshed: function(state) {
669 if(this.master === null || this.seq === 0)
672 //if(state.needsRecreation())
675 if(state.tm.pan_and_zoom_seq === this.seq)
682 // ----------------------------------------------------------------------------------------------------------------
683 // dimensions selection
686 // move color assignment to dimensions, here
688 dimensionStatus = function(parent, label, name_div, value_div, color) {
689 this.enabled = false;
690 this.parent = parent;
692 this.name_div = null;
693 this.value_div = null;
694 this.color = NETDATA.themes.current.foreground;
696 if(parent.selected_count > parent.unselected_count)
697 this.selected = true;
699 this.selected = false;
701 this.setOptions(name_div, value_div, color);
704 dimensionStatus.prototype.invalidate = function() {
705 this.name_div = null;
706 this.value_div = null;
707 this.enabled = false;
710 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
713 if(this.name_div != name_div) {
714 this.name_div = name_div;
715 this.name_div.title = this.label;
716 this.name_div.style.color = this.color;
717 if(this.selected === false)
718 this.name_div.className = 'netdata-legend-name not-selected';
720 this.name_div.className = 'netdata-legend-name selected';
723 if(this.value_div != value_div) {
724 this.value_div = value_div;
725 this.value_div.title = this.label;
726 this.value_div.style.color = this.color;
727 if(this.selected === false)
728 this.value_div.className = 'netdata-legend-value not-selected';
730 this.value_div.className = 'netdata-legend-value selected';
737 dimensionStatus.prototype.setHandler = function() {
738 if(this.enabled === false) return;
742 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
743 this.name_div.onclick = this.value_div.onclick = function(e) {
745 if(ds.isSelected()) {
747 if(e.shiftKey === true || e.ctrlKey === true) {
748 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
751 if(ds.parent.countSelected() === 0)
752 ds.parent.selectAll();
755 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
756 if(ds.parent.countSelected() === 1) {
757 ds.parent.selectAll();
760 ds.parent.selectNone();
766 // this is not selected
767 if(e.shiftKey === true || e.ctrlKey === true) {
768 // control or shift key is pressed -> select this too
772 // no key is pressed -> select only this
773 ds.parent.selectNone();
778 ds.parent.state.redrawChart();
782 dimensionStatus.prototype.select = function() {
783 if(this.enabled === false) return;
785 this.name_div.className = 'netdata-legend-name selected';
786 this.value_div.className = 'netdata-legend-value selected';
787 this.selected = true;
790 dimensionStatus.prototype.unselect = function() {
791 if(this.enabled === false) return;
793 this.name_div.className = 'netdata-legend-name not-selected';
794 this.value_div.className = 'netdata-legend-value hidden';
795 this.selected = false;
798 dimensionStatus.prototype.isSelected = function() {
799 return(this.enabled === true && this.selected === true);
802 // ----------------------------------------------------------------------------------------------------------------
804 dimensionsVisibility = function(state) {
807 this.dimensions = {};
808 this.selected_count = 0;
809 this.unselected_count = 0;
812 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
813 if(typeof this.dimensions[label] === 'undefined') {
815 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
818 this.dimensions[label].setOptions(name_div, value_div, color);
820 return this.dimensions[label];
823 dimensionsVisibility.prototype.dimensionGet = function(label) {
824 return this.dimensions[label];
827 dimensionsVisibility.prototype.invalidateAll = function() {
828 for(var d in this.dimensions)
829 this.dimensions[d].invalidate();
832 dimensionsVisibility.prototype.selectAll = function() {
833 for(var d in this.dimensions)
834 this.dimensions[d].select();
837 dimensionsVisibility.prototype.countSelected = function() {
839 for(var d in this.dimensions)
840 if(this.dimensions[d].isSelected()) i++;
845 dimensionsVisibility.prototype.selectNone = function() {
846 for(var d in this.dimensions)
847 this.dimensions[d].unselect();
850 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
851 var ret = new Array();
852 this.selected_count = 0;
853 this.unselected_count = 0;
855 for(var i = 0, len = array.length; i < len ; i++) {
856 var ds = this.dimensions[array[i]];
857 if(typeof ds === 'undefined') {
858 // console.log(array[i] + ' is not found');
863 if(ds.isSelected()) {
865 this.selected_count++;
869 this.unselected_count++;
873 if(this.selected_count === 0 && this.unselected_count !== 0) {
875 return this.selected2BooleanArray(array);
882 // ----------------------------------------------------------------------------------------------------------------
883 // global selection sync
885 NETDATA.globalSelectionSync = {
892 if(this.state !== null)
893 this.state.globalSelectionSyncStop();
897 if(this.state !== null) {
898 this.state.globalSelectionSyncDelay();
903 // ----------------------------------------------------------------------------------------------------------------
904 // Our state object, where all per-chart values are stored
906 chartState = function(element) {
907 var self = $(element);
908 this.element = element;
911 // all private functions should use 'that', instead of 'this'
915 * show an error instead of the chart
917 var error = function(msg) {
920 if(typeof netdataErrorCallback === 'function') {
921 ret = netdataErrorCallback('chart', that.id, msg);
925 that.element.innerHTML = that.id + ': ' + msg;
926 that.enabled = false;
927 that.current = that.pan;
931 // GUID - a unique identifier for the chart
932 this.uuid = NETDATA.guid();
934 // string - the name of chart
935 this.id = self.data('netdata');
937 // string - the key for localStorage settings
938 this.settings_id = self.data('id') || null;
940 // the user given dimensions of the element
941 this.width = self.data('width') || NETDATA.chartDefaults.width;
942 this.height = self.data('height') || NETDATA.chartDefaults.height;
944 if(this.settings_id !== null) {
945 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
946 // this is the callback that will be called
947 // if and when the user resets all localStorage variables
950 resizeChartToHeight(height);
954 // string - the netdata server URL, without any path
955 this.host = self.data('host') || NETDATA.chartDefaults.host;
957 // make sure the host does not end with /
958 // all netdata API requests use absolute paths
959 while(this.host.slice(-1) === '/')
960 this.host = this.host.substring(0, this.host.length - 1);
962 // string - the grouping method requested by the user
963 this.method = self.data('method') || NETDATA.chartDefaults.method;
965 // the time-range requested by the user
966 this.after = self.data('after') || NETDATA.chartDefaults.after;
967 this.before = self.data('before') || NETDATA.chartDefaults.before;
969 // the pixels per point requested by the user
970 this.pixels_per_point = self.data('pixels-per-point') || 1;
971 this.points = self.data('points') || null;
973 // the dimensions requested by the user
974 this.dimensions = self.data('dimensions') || null;
976 // the chart library requested by the user
977 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
979 // object - the chart library used
984 this.colors_assigned = {};
985 this.colors_available = null;
987 // the element already created by the user
988 this.element_message = null;
990 // the element with the chart
991 this.element_chart = null;
993 // the element with the legend of the chart (if created by us)
994 this.element_legend = null;
995 this.element_legend_childs = {
1005 this.chart_url = null; // string - the url to download chart info
1006 this.chart = null; // object - the chart as downloaded from the server
1008 this.title = self.data('title') || null; // the title of the chart
1009 this.units = self.data('units') || null; // the units of the chart dimensions
1010 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1012 this.running = false; // boolean - true when the chart is being refreshed now
1013 this.validated = false; // boolean - has the chart been validated?
1014 this.enabled = true; // boolean - is the chart enabled for refresh?
1015 this.paused = false; // boolean - is the chart paused for any reason?
1016 this.selected = false; // boolean - is the chart shown a selection?
1017 this.debug = false; // boolean - console.log() debug info about this chart
1019 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1020 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1021 this.requested_after = null; // milliseconds - the timestamp of the request after param
1022 this.requested_before = null; // milliseconds - the timestamp of the request before param
1023 this.requested_padding = null;
1024 this.view_after = 0;
1025 this.view_before = 0;
1030 force_update_at: 0, // the timestamp to force the update at
1031 force_before_ms: null,
1032 force_after_ms: null
1037 force_update_at: 0, // the timestamp to force the update at
1038 force_before_ms: null,
1039 force_after_ms: null
1044 force_update_at: 0, // the timestamp to force the update at
1045 force_before_ms: null,
1046 force_after_ms: null
1049 // this is a pointer to one of the sub-classes below
1051 this.current = this.auto;
1053 // check the requested library is available
1054 // we don't initialize it here - it will be initialized when
1055 // this chart will be first used
1056 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1057 NETDATA.error(402, that.library_name);
1058 error('chart library "' + that.library_name + '" is not found');
1061 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1062 NETDATA.error(403, that.library_name);
1063 error('chart library "' + that.library_name + '" is not enabled');
1067 that.library = NETDATA.chartLibraries[that.library_name];
1069 // milliseconds - the time the last refresh took
1070 this.refresh_dt_ms = 0;
1072 // if we need to report the rendering speed
1073 // find the element that needs to be updated
1074 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1076 if(refresh_dt_element_name !== null)
1077 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1079 this.refresh_dt_element = null;
1081 this.dimensions_visibility = new dimensionsVisibility(this);
1083 this._updating = false;
1085 // ============================================================================================================
1086 // PRIVATE FUNCTIONS
1088 var createDOM = function() {
1089 if(that.enabled === false) return;
1091 if(that.element_message !== null) that.element_message.innerHTML = '';
1092 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1093 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1095 that.element.innerHTML = '';
1097 that.element_message = document.createElement('div');
1098 that.element_message.className = ' netdata-message hidden';
1099 that.element.appendChild(that.element_message);
1101 that.element_chart = document.createElement('div');
1102 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1103 that.element.appendChild(that.element_chart);
1105 if(that.hasLegend() === true) {
1106 that.element.className = "netdata-container-with-legend";
1107 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1109 that.element_legend = document.createElement('div');
1110 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1111 that.element.appendChild(that.element_legend);
1114 that.element.className = "netdata-container";
1115 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1117 that.element_legend = null;
1119 that.element_legend_childs.series = null;
1121 if(typeof(that.width) === 'string')
1122 $(that.element).css('width', that.width);
1123 else if(typeof(that.width) === 'number')
1124 $(that.element).css('width', that.width + 'px');
1126 if(typeof(that.library.aspect_ratio) === 'undefined') {
1127 if(typeof(that.height) === 'string')
1128 $(that.element).css('height', that.height);
1129 else if(typeof(that.height) === 'number')
1130 $(that.element).css('height', that.height + 'px');
1133 var w = that.element.offsetWidth;
1134 if(w === null || w === 0) {
1135 // the div is hidden
1136 // this will resize the chart when next viewed
1137 that.tm.last_resized = 0;
1140 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1143 if(NETDATA.chartDefaults.min_width !== null)
1144 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1146 that.tm.last_dom_created = new Date().getTime();
1152 * initialize state variables
1153 * destroy all (possibly) created state elements
1154 * create the basic DOM for a chart
1156 var init = function() {
1157 if(that.enabled === false) return;
1159 that.paused = false;
1160 that.selected = false;
1162 that.chart_created = false; // boolean - is the library.create() been called?
1163 that.updates_counter = 0; // numeric - the number of refreshes made so far
1164 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1165 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1168 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1169 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1170 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1172 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1173 last_updated: 0, // the timestamp the chart last updated with data
1174 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1176 // Used with NETDATA.globalPanAndZoom.seq
1177 last_visible_check: 0, // the time we last checked if it is visible
1178 last_resized: 0, // the time the chart was resized
1179 last_hidden: 0, // the time the chart was hidden
1180 last_unhidden: 0, // the time the chart was unhidden
1181 last_autorefreshed: 0 // the time the chart was last refreshed
1184 that.data = null; // the last data as downloaded from the netdata server
1185 that.data_url = 'invalid://'; // string - the last url used to update the chart
1186 that.data_points = 0; // number - the number of points returned from netdata
1187 that.data_after = 0; // milliseconds - the first timestamp of the data
1188 that.data_before = 0; // milliseconds - the last timestamp of the data
1189 that.data_update_every = 0; // milliseconds - the frequency to update the data
1191 that.tm.last_initialized = new Date().getTime();
1194 that.setMode('auto');
1197 var maxMessageFontSize = function() {
1198 // normally we want a font size, as tall as the element
1199 var h = that.element_message.clientHeight;
1201 // but give it some air, 20% let's say, or 5 pixels min
1202 var lost = Math.max(h * 0.2, 5);
1205 // center the text, vertically
1206 var paddingTop = (lost - 5) / 2;
1208 // but check the width too
1209 // it should fit 10 characters in it
1210 var w = that.element_message.clientWidth / 10;
1212 paddingTop += (h - w) / 2;
1216 // and don't make it too huge
1217 // 5% of the screen size is good
1218 if(h > screen.height / 20) {
1219 paddingTop += (h - (screen.height / 20)) / 2;
1220 h = screen.height / 20;
1224 that.element_message.style.fontSize = h.toString() + 'px';
1225 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1228 var showMessage = function(msg) {
1229 that.element_message.className = 'netdata-message';
1230 that.element_message.innerHTML = msg;
1231 that.element_message.style.fontSize = 'x-small';
1232 that.element_message.style.paddingTop = '0px';
1233 that.___messageHidden___ = undefined;
1236 var showMessageIcon = function(icon) {
1237 that.element_message.innerHTML = icon;
1238 that.element_message.className = 'netdata-message icon';
1239 maxMessageFontSize();
1240 that.___messageHidden___ = undefined;
1243 var hideMessage = function() {
1244 if(typeof that.___messageHidden___ === 'undefined') {
1245 that.___messageHidden___ = true;
1246 that.element_message.className = 'netdata-message hidden';
1250 var showRendering = function() {
1252 if(that.chart !== null) {
1253 if(that.chart.chart_type === 'line')
1254 icon = '<i class="fa fa-line-chart"></i>';
1256 icon = '<i class="fa fa-area-chart"></i>';
1259 icon = '<i class="fa fa-area-chart"></i>';
1261 showMessageIcon(icon + ' netdata');
1264 var showLoading = function() {
1265 if(that.chart_created === false) {
1266 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1272 var isHidden = function() {
1273 if(typeof that.___chartIsHidden___ !== 'undefined')
1279 // hide the chart, when it is not visible - called from isVisible()
1280 var hideChart = function() {
1281 // hide it, if it is not already hidden
1282 if(isHidden() === true) return;
1284 if(that.chart_created === true) {
1285 if(NETDATA.options.current.destroy_on_hide === true) {
1286 // we should destroy it
1291 that.element_chart.style.display = 'none';
1292 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1293 that.tm.last_hidden = new Date().getTime();
1296 // This works, but I not sure there are no corner cases somewhere
1297 // so it is commented - if the user has memory issues he can
1298 // set Destroy on Hide for all charts
1299 // that.data = null;
1303 that.___chartIsHidden___ = true;
1306 // unhide the chart, when it is visible - called from isVisible()
1307 var unhideChart = function() {
1308 if(isHidden() === false) return;
1310 that.___chartIsHidden___ = undefined;
1311 that.updates_since_last_unhide = 0;
1313 if(that.chart_created === false) {
1314 // we need to re-initialize it, to show our background
1315 // logo in bootstrap tabs, until the chart loads
1319 that.tm.last_unhidden = new Date().getTime();
1320 that.element_chart.style.display = '';
1321 if(that.element_legend !== null) that.element_legend.style.display = '';
1327 var canBeRendered = function() {
1328 if(isHidden() === true || that.isVisible(true) === false)
1334 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1335 var callChartLibraryUpdateSafely = function(data) {
1338 if(canBeRendered() === false)
1341 if(NETDATA.options.debug.chart_errors === true)
1342 status = that.library.update(that, data);
1345 status = that.library.update(that, data);
1352 if(status === false) {
1353 error('chart failed to be updated as ' + that.library_name);
1360 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1361 var callChartLibraryCreateSafely = function(data) {
1364 if(canBeRendered() === false)
1367 if(NETDATA.options.debug.chart_errors === true)
1368 status = that.library.create(that, data);
1371 status = that.library.create(that, data);
1378 if(status === false) {
1379 error('chart failed to be created as ' + that.library_name);
1383 that.chart_created = true;
1384 that.updates_since_last_creation = 0;
1388 // ----------------------------------------------------------------------------------------------------------------
1391 // resizeChart() - private
1392 // to be called just before the chart library to make sure that
1393 // a properly sized dom is available
1394 var resizeChart = function() {
1395 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1396 if(that.chart_created === false) return;
1398 if(that.needsRecreation()) {
1401 else if(typeof that.library.resize === 'function') {
1402 that.library.resize(that);
1404 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1405 $(that.element_legend_childs.nano).nanoScroller();
1407 maxMessageFontSize();
1410 that.tm.last_resized = new Date().getTime();
1414 // this is the actual chart resize algorithm
1416 // - resize the entire container
1417 // - update the internal states
1418 // - resize the chart as the div changes height
1419 // - update the scrollbar of the legend
1420 var resizeChartToHeight = function(h) {
1422 that.element.style.height = h;
1424 if(that.settings_id !== null)
1425 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1427 var now = new Date().getTime();
1428 NETDATA.options.last_page_scroll = now;
1429 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1432 that.tm.last_resized = 0;
1436 this.resizeHandler = function(e) {
1439 if(typeof this.event_resize === 'undefined'
1440 || this.event_resize.chart_original_w === 'undefined'
1441 || this.event_resize.chart_original_h === 'undefined')
1442 this.event_resize = {
1443 chart_original_w: this.element.clientWidth,
1444 chart_original_h: this.element.clientHeight,
1448 if(e.type === 'touchstart') {
1449 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1450 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1453 this.event_resize.mouse_start_x = e.clientX;
1454 this.event_resize.mouse_start_y = e.clientY;
1457 this.event_resize.chart_start_w = this.element.clientWidth;
1458 this.event_resize.chart_start_h = this.element.clientHeight;
1459 this.event_resize.chart_last_w = this.element.clientWidth;
1460 this.event_resize.chart_last_h = this.element.clientHeight;
1462 var now = new Date().getTime();
1463 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1464 // double click / double tap event
1466 // the optimal height of the chart
1467 // showing the entire legend
1468 var optimal = this.event_resize.chart_last_h
1469 + this.element_legend_childs.content.scrollHeight
1470 - this.element_legend_childs.content.clientHeight;
1472 // if we are not optimal, be optimal
1473 if(this.event_resize.chart_last_h != optimal)
1474 resizeChartToHeight(optimal.toString() + 'px');
1476 // else if we do not have the original height
1477 // reset to the original height
1478 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1479 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1482 this.event_resize.last = now;
1484 // process movement event
1485 document.onmousemove =
1486 document.ontouchmove =
1487 this.element_legend_childs.resize_handler.onmousemove =
1488 this.element_legend_childs.resize_handler.ontouchmove =
1493 case 'mousemove': y = e.clientY; break;
1494 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1498 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1500 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1501 resizeChartToHeight(newH.toString() + 'px');
1502 that.event_resize.chart_last_h = newH;
1507 // process end event
1508 document.onmouseup =
1509 document.ontouchend =
1510 this.element_legend_childs.resize_handler.onmouseup =
1511 this.element_legend_childs.resize_handler.ontouchend =
1513 // remove all the hooks
1514 document.onmouseup =
1515 document.onmousemove =
1516 document.ontouchmove =
1517 document.ontouchend =
1518 that.element_legend_childs.resize_handler.onmousemove =
1519 that.element_legend_childs.resize_handler.ontouchmove =
1520 that.element_legend_childs.resize_handler.onmouseout =
1521 that.element_legend_childs.resize_handler.onmouseup =
1522 that.element_legend_childs.resize_handler.ontouchend =
1525 // allow auto-refreshes
1526 NETDATA.options.auto_refresher_stop_until = 0;
1532 var noDataToShow = function() {
1533 showMessageIcon('<i class="fa fa-warning"></i> empty');
1534 that.legendUpdateDOM();
1535 that.tm.last_autorefreshed = new Date().getTime();
1536 // that.data_update_every = 30 * 1000;
1537 //that.element_chart.style.display = 'none';
1538 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1539 //that.___chartIsHidden___ = true;
1542 // ============================================================================================================
1545 this.error = function(msg) {
1549 this.setMode = function(m) {
1550 if(this.current !== null && this.current.name === m) return;
1553 this.current = this.auto;
1554 else if(m === 'pan')
1555 this.current = this.pan;
1556 else if(m === 'zoom')
1557 this.current = this.zoom;
1559 this.current = this.auto;
1561 this.current.force_update_at = 0;
1562 this.current.force_before_ms = null;
1563 this.current.force_after_ms = null;
1565 this.tm.last_mode_switch = new Date().getTime();
1568 // ----------------------------------------------------------------------------------------------------------------
1569 // global selection sync
1571 // prevent to global selection sync for some time
1572 this.globalSelectionSyncDelay = function(ms) {
1573 if(NETDATA.options.current.sync_selection === false)
1576 if(typeof ms === 'number')
1577 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1579 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1582 // can we globally apply selection sync?
1583 this.globalSelectionSyncAbility = function() {
1584 if(NETDATA.options.current.sync_selection === false)
1587 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1593 this.globalSelectionSyncIsMaster = function() {
1594 if(NETDATA.globalSelectionSync.state === this)
1600 // this chart is the master of the global selection sync
1601 this.globalSelectionSyncBeMaster = function() {
1603 if(this.globalSelectionSyncIsMaster()) {
1604 if(this.debug === true)
1605 this.log('sync: I am the master already.');
1610 if(NETDATA.globalSelectionSync.state) {
1611 if(this.debug === true)
1612 this.log('sync: I am not the sync master. Resetting global sync.');
1614 this.globalSelectionSyncStop();
1617 // become the master
1618 if(this.debug === true)
1619 this.log('sync: becoming sync master.');
1621 this.selected = true;
1622 NETDATA.globalSelectionSync.state = this;
1624 // find the all slaves
1625 var targets = NETDATA.options.targets;
1626 var len = targets.length;
1631 if(this.debug === true)
1632 st.log('sync: not adding me to sync');
1634 else if(st.globalSelectionSyncIsEligible()) {
1635 if(this.debug === true)
1636 st.log('sync: adding to sync as slave');
1638 st.globalSelectionSyncBeSlave();
1642 // this.globalSelectionSyncDelay(100);
1645 // can the chart participate to the global selection sync as a slave?
1646 this.globalSelectionSyncIsEligible = function() {
1647 if(this.enabled === true
1648 && this.library !== null
1649 && typeof this.library.setSelection === 'function'
1650 && this.isVisible() === true
1651 && this.chart_created === true)
1657 // this chart becomes a slave of the global selection sync
1658 this.globalSelectionSyncBeSlave = function() {
1659 if(NETDATA.globalSelectionSync.state !== this)
1660 NETDATA.globalSelectionSync.slaves.push(this);
1663 // sync all the visible charts to the given time
1664 // this is to be called from the chart libraries
1665 this.globalSelectionSync = function(t) {
1666 if(this.globalSelectionSyncAbility() === false) {
1667 if(this.debug === true)
1668 this.log('sync: cannot sync (yet?).');
1673 if(this.globalSelectionSyncIsMaster() === false) {
1674 if(this.debug === true)
1675 this.log('sync: trying to be sync master.');
1677 this.globalSelectionSyncBeMaster();
1679 if(this.globalSelectionSyncAbility() === false) {
1680 if(this.debug === true)
1681 this.log('sync: cannot sync (yet?).');
1687 NETDATA.globalSelectionSync.last_t = t;
1688 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1693 // stop syncing all charts to the given time
1694 this.globalSelectionSyncStop = function() {
1695 if(NETDATA.globalSelectionSync.slaves.length) {
1696 if(this.debug === true)
1697 this.log('sync: cleaning up...');
1699 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1701 if(that.debug === true)
1702 st.log('sync: not adding me to sync stop');
1705 if(that.debug === true)
1706 st.log('sync: removed slave from sync');
1708 st.clearSelection();
1712 NETDATA.globalSelectionSync.last_t = 0;
1713 NETDATA.globalSelectionSync.slaves = [];
1714 NETDATA.globalSelectionSync.state = null;
1717 this.clearSelection();
1720 this.setSelection = function(t) {
1721 if(typeof this.library.setSelection === 'function') {
1722 if(this.library.setSelection(this, t) === true)
1723 this.selected = true;
1725 this.selected = false;
1727 else this.selected = true;
1729 if(this.selected === true && this.debug === true)
1730 this.log('selection set to ' + t.toString());
1732 return this.selected;
1735 this.clearSelection = function() {
1736 if(this.selected === true) {
1737 if(typeof this.library.clearSelection === 'function') {
1738 if(this.library.clearSelection(this) === true)
1739 this.selected = false;
1741 this.selected = true;
1743 else this.selected = false;
1745 if(this.selected === false && this.debug === true)
1746 this.log('selection cleared');
1751 return this.selected;
1754 // find if a timestamp (ms) is shown in the current chart
1755 this.timeIsVisible = function(t) {
1756 if(t >= this.data_after && t <= this.data_before)
1761 this.calculateRowForTime = function(t) {
1762 if(this.timeIsVisible(t) === false) return -1;
1763 return Math.floor((t - this.data_after) / this.data_update_every);
1766 // ----------------------------------------------------------------------------------------------------------------
1769 this.log = function(msg) {
1770 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1773 this.pauseChart = function() {
1774 if(this.paused === false) {
1775 if(this.debug === true)
1776 this.log('pauseChart()');
1782 this.unpauseChart = function() {
1783 if(this.paused === true) {
1784 if(this.debug === true)
1785 this.log('unpauseChart()');
1787 this.paused = false;
1791 this.resetChart = function(dont_clear_master, dont_update) {
1792 if(this.debug === true)
1793 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1795 if(typeof dont_clear_master === 'undefined')
1796 dont_clear_master = false;
1798 if(typeof dont_update === 'undefined')
1799 dont_update = false;
1801 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1802 if(this.debug === true)
1803 this.log('resetChart() diverting to clearMaster().');
1804 // this will call us back with master === true
1805 NETDATA.globalPanAndZoom.clearMaster();
1809 this.clearSelection();
1811 this.tm.pan_and_zoom_seq = 0;
1813 this.setMode('auto');
1814 this.current.force_update_at = 0;
1815 this.current.force_before_ms = null;
1816 this.current.force_after_ms = null;
1817 this.tm.last_autorefreshed = 0;
1818 this.paused = false;
1819 this.selected = false;
1820 this.enabled = true;
1821 // this.debug = false;
1823 // do not update the chart here
1824 // or the chart will flip-flop when it is the master
1825 // of a selection sync and another chart becomes
1828 if(dont_update !== true && this.isVisible() === true) {
1833 this.updateChartPanOrZoom = function(after, before) {
1834 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1837 if(this.debug === true)
1840 if(before < after) {
1841 if(this.debug === true)
1842 this.log(logme + 'flipped parameters, rejecting it.');
1847 if(typeof this.fixed_min_duration === 'undefined')
1848 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1850 var min_duration = this.fixed_min_duration;
1851 var current_duration = Math.round(this.view_before - this.view_after);
1853 // round the numbers
1854 after = Math.round(after);
1855 before = Math.round(before);
1857 // align them to update_every
1858 // stretching them further away
1859 after -= after % this.data_update_every;
1860 before += this.data_update_every - (before % this.data_update_every);
1862 // the final wanted duration
1863 var wanted_duration = before - after;
1865 // to allow panning, accept just a point below our minimum
1866 if((current_duration - this.data_update_every) < min_duration)
1867 min_duration = current_duration - this.data_update_every;
1869 // we do it, but we adjust to minimum size and return false
1870 // when the wanted size is below the current and the minimum
1872 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1873 if(this.debug === true)
1874 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1876 min_duration = this.fixed_min_duration;
1878 var dt = (min_duration - wanted_duration) / 2;
1881 wanted_duration = before - after;
1885 var tolerance = this.data_update_every * 2;
1886 var movement = Math.abs(before - this.view_before);
1888 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1889 if(this.debug === true)
1890 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);
1894 if(this.current.name === 'auto') {
1895 this.log(logme + 'caller called me with mode: ' + this.current.name);
1896 this.setMode('pan');
1899 if(this.debug === true)
1900 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);
1902 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1903 this.current.force_after_ms = after;
1904 this.current.force_before_ms = before;
1905 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1909 this.legendFormatValue = function(value) {
1910 if(value === null || value === 'undefined') return '-';
1911 if(typeof value !== 'number') return value;
1913 var abs = Math.abs(value);
1914 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1915 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1916 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1917 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1918 return (Math.round(value * 10000) / 10000).toLocaleString();
1921 this.legendSetLabelValue = function(label, value) {
1922 var series = this.element_legend_childs.series[label];
1923 if(typeof series === 'undefined') return;
1924 if(series.value === null && series.user === null) return;
1926 // if the value has not changed, skip DOM update
1927 //if(series.last === value) return;
1930 if(typeof value === 'number') {
1931 var v = Math.abs(value);
1932 s = r = this.legendFormatValue(value);
1934 if(typeof series.last === 'number') {
1935 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1936 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1937 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1939 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1944 series.last = value;
1947 if(series.value !== null) series.value.innerHTML = s;
1948 if(series.user !== null) series.user.innerHTML = r;
1951 this.legendSetDate = function(ms) {
1952 if(typeof ms !== 'number') {
1953 this.legendShowUndefined();
1957 var d = new Date(ms);
1959 if(this.element_legend_childs.title_date)
1960 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1962 if(this.element_legend_childs.title_time)
1963 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1965 if(this.element_legend_childs.title_units)
1966 this.element_legend_childs.title_units.innerHTML = this.units;
1969 this.legendShowUndefined = function() {
1970 if(this.element_legend_childs.title_date)
1971 this.element_legend_childs.title_date.innerHTML = ' ';
1973 if(this.element_legend_childs.title_time)
1974 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1976 if(this.element_legend_childs.title_units)
1977 this.element_legend_childs.title_units.innerHTML = ' ';
1979 if(this.data && this.element_legend_childs.series !== null) {
1980 var labels = this.data.dimension_names;
1981 var i = labels.length;
1983 var label = labels[i];
1985 if(typeof label === 'undefined') continue;
1986 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1987 this.legendSetLabelValue(label, null);
1992 this.legendShowLatestValues = function() {
1993 if(this.chart === null) return;
1994 if(this.selected) return;
1996 if(this.data === null || this.element_legend_childs.series === null) {
1997 this.legendShowUndefined();
2001 var show_undefined = true;
2002 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2003 show_undefined = false;
2005 if(show_undefined) {
2006 this.legendShowUndefined();
2010 this.legendSetDate(this.view_before);
2012 var labels = this.data.dimension_names;
2013 var i = labels.length;
2015 var label = labels[i];
2017 if(typeof label === 'undefined') continue;
2018 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2021 this.legendSetLabelValue(label, null);
2023 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2027 this.legendReset = function() {
2028 this.legendShowLatestValues();
2031 // this should be called just ONCE per dimension per chart
2032 this._chartDimensionColor = function(label) {
2033 if(this.colors === null) this.chartColors();
2035 if(typeof this.colors_assigned[label] === 'undefined') {
2036 if(this.colors_available.length === 0) {
2037 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2038 this.colors_available.push(NETDATA.themes.current.colors[i]);
2041 this.colors_assigned[label] = this.colors_available.shift();
2043 if(this.debug === true)
2044 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2047 if(this.debug === true)
2048 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2051 this.colors.push(this.colors_assigned[label]);
2052 return this.colors_assigned[label];
2055 this.chartColors = function() {
2056 if(this.colors !== null) return this.colors;
2058 this.colors = new Array();
2059 this.colors_available = new Array();
2062 var c = $(this.element).data('colors');
2063 // this.log('read colors: ' + c);
2064 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2065 if(typeof c !== 'string') {
2066 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2073 for(i = 0, len = c.length; i < len ; i++) {
2075 this.colors_available.push(c[i]);
2076 // this.log('adding color: ' + c[i]);
2082 // push all the standard colors too
2083 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2084 this.colors_available.push(NETDATA.themes.current.colors[i]);
2089 this.legendUpdateDOM = function() {
2092 // check that the legend DOM is up to date for the downloaded dimensions
2093 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2094 // this.log('the legend does not have any series - requesting legend update');
2097 else if(this.data === null) {
2098 // this.log('the chart does not have any data - requesting legend update');
2101 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2105 var labels = this.data.dimension_names.toString();
2106 if(labels !== this.element_legend_childs.series.labels_key) {
2109 if(this.debug === true)
2110 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2114 if(needed === false) {
2115 // make sure colors available
2118 // do we have to update the current values?
2119 // we do this, only when the visible chart is current
2120 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2121 if(this.debug === true)
2122 this.log('chart is in latest position... updating values on legend...');
2124 //var labels = this.data.dimension_names;
2125 //var i = labels.length;
2127 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2131 if(this.colors === null) {
2132 // this is the first time we update the chart
2133 // let's assign colors to all dimensions
2134 if(this.library.track_colors() === true)
2135 for(var dim in this.chart.dimensions)
2136 this._chartDimensionColor(this.chart.dimensions[dim].name);
2138 // we will re-generate the colors for the chart
2139 // based on the selected dimensions
2142 if(this.debug === true)
2143 this.log('updating Legend DOM');
2145 // mark all dimensions as invalid
2146 this.dimensions_visibility.invalidateAll();
2148 var genLabel = function(state, parent, name, count) {
2149 var color = state._chartDimensionColor(name);
2151 var user_element = null;
2152 var user_id = self.data('show-value-of-' + name + '-at') || null;
2153 if(user_id !== null) {
2154 user_element = document.getElementById(user_id) || null;
2155 if(user_element === null)
2156 state.log('Cannot find element with id: ' + user_id);
2159 state.element_legend_childs.series[name] = {
2160 name: document.createElement('span'),
2161 value: document.createElement('span'),
2166 var label = state.element_legend_childs.series[name];
2168 // create the dimension visibility tracking for this label
2169 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2171 var rgb = NETDATA.colorHex2Rgb(color);
2172 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2173 + state.chart.chart_type
2174 + '" style="background-color: '
2175 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2176 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2178 var text = document.createTextNode(' ' + name);
2179 label.name.appendChild(text);
2182 parent.appendChild(document.createElement('br'));
2184 parent.appendChild(label.name);
2185 parent.appendChild(label.value);
2188 var content = document.createElement('div');
2190 if(this.hasLegend()) {
2191 this.element_legend_childs = {
2193 resize_handler: document.createElement('div'),
2194 toolbox: document.createElement('div'),
2195 toolbox_left: document.createElement('div'),
2196 toolbox_right: document.createElement('div'),
2197 toolbox_reset: document.createElement('div'),
2198 toolbox_zoomin: document.createElement('div'),
2199 toolbox_zoomout: document.createElement('div'),
2200 toolbox_volume: document.createElement('div'),
2201 title_date: document.createElement('span'),
2202 title_time: document.createElement('span'),
2203 title_units: document.createElement('span'),
2204 nano: document.createElement('div'),
2206 paneClass: 'netdata-legend-series-pane',
2207 sliderClass: 'netdata-legend-series-slider',
2208 contentClass: 'netdata-legend-series-content',
2209 enabledClass: '__enabled',
2210 flashedClass: '__flashed',
2211 activeClass: '__active',
2213 alwaysVisible: true,
2219 this.element_legend.innerHTML = '';
2221 if(this.library.toolboxPanAndZoom !== null) {
2223 function get_pan_and_zoom_step(event) {
2225 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2227 else if (event.shiftKey)
2228 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2230 else if (event.altKey)
2231 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2234 return NETDATA.options.current.pan_and_zoom_factor;
2237 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2238 this.element.appendChild(this.element_legend_childs.toolbox);
2240 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2241 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2242 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2243 this.element_legend_childs.toolbox_left.onclick = function(e) {
2246 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2247 var before = that.view_before - step;
2248 var after = that.view_after - step;
2249 if(after >= that.netdata_first)
2250 that.library.toolboxPanAndZoom(that, after, before);
2252 if(NETDATA.options.current.show_help === true)
2253 $(this.element_legend_childs.toolbox_left).popover({
2258 placement: 'bottom',
2259 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2261 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>'
2265 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2266 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2267 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2268 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2270 NETDATA.resetAllCharts(that);
2272 if(NETDATA.options.current.show_help === true)
2273 $(this.element_legend_childs.toolbox_reset).popover({
2278 placement: 'bottom',
2279 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2280 title: 'Chart Reset',
2281 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>'
2284 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2285 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2286 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2287 this.element_legend_childs.toolbox_right.onclick = function(e) {
2289 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2290 var before = that.view_before + step;
2291 var after = that.view_after + step;
2292 if(before <= that.netdata_last)
2293 that.library.toolboxPanAndZoom(that, after, before);
2295 if(NETDATA.options.current.show_help === true)
2296 $(this.element_legend_childs.toolbox_right).popover({
2301 placement: 'bottom',
2302 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2304 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>'
2308 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2309 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2310 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2311 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2313 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2314 var before = that.view_before - dt;
2315 var after = that.view_after + dt;
2316 that.library.toolboxPanAndZoom(that, after, before);
2318 if(NETDATA.options.current.show_help === true)
2319 $(this.element_legend_childs.toolbox_zoomin).popover({
2324 placement: 'bottom',
2325 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2326 title: 'Chart Zoom In',
2327 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>'
2330 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2331 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2332 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2333 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2335 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);
2336 var before = that.view_before + dt;
2337 var after = that.view_after - dt;
2339 that.library.toolboxPanAndZoom(that, after, before);
2341 if(NETDATA.options.current.show_help === true)
2342 $(this.element_legend_childs.toolbox_zoomout).popover({
2347 placement: 'bottom',
2348 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2349 title: 'Chart Zoom Out',
2350 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>'
2353 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2354 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2355 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2356 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2357 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2358 //e.preventDefault();
2359 //alert('clicked toolbox_volume on ' + that.id);
2363 this.element_legend_childs.toolbox = null;
2364 this.element_legend_childs.toolbox_left = null;
2365 this.element_legend_childs.toolbox_reset = null;
2366 this.element_legend_childs.toolbox_right = null;
2367 this.element_legend_childs.toolbox_zoomin = null;
2368 this.element_legend_childs.toolbox_zoomout = null;
2369 this.element_legend_childs.toolbox_volume = null;
2372 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2373 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2374 this.element.appendChild(this.element_legend_childs.resize_handler);
2375 if(NETDATA.options.current.show_help === true)
2376 $(this.element_legend_childs.resize_handler).popover({
2381 placement: 'bottom',
2382 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2383 title: 'Chart Resize',
2384 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>'
2388 this.element_legend_childs.resize_handler.onmousedown =
2390 that.resizeHandler(e);
2394 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2395 that.resizeHandler(e);
2398 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2399 this.element_legend.appendChild(this.element_legend_childs.title_date);
2401 this.element_legend.appendChild(document.createElement('br'));
2403 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2404 this.element_legend.appendChild(this.element_legend_childs.title_time);
2406 this.element_legend.appendChild(document.createElement('br'));
2408 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2409 this.element_legend.appendChild(this.element_legend_childs.title_units);
2411 this.element_legend.appendChild(document.createElement('br'));
2413 this.element_legend_childs.nano.className = 'netdata-legend-series';
2414 this.element_legend.appendChild(this.element_legend_childs.nano);
2416 content.className = 'netdata-legend-series-content';
2417 this.element_legend_childs.nano.appendChild(content);
2419 if(NETDATA.options.current.show_help === true)
2420 $(content).popover({
2425 placement: 'bottom',
2426 title: 'Chart Legend',
2427 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2428 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>'
2432 this.element_legend_childs = {
2434 resize_handler: null,
2437 toolbox_right: null,
2438 toolbox_reset: null,
2439 toolbox_zoomin: null,
2440 toolbox_zoomout: null,
2441 toolbox_volume: null,
2452 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2453 if(this.debug === true)
2454 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2456 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2457 genLabel(this, content, this.data.dimension_names[i], i);
2461 var tmp = new Array();
2462 for(var dim in this.chart.dimensions) {
2463 tmp.push(this.chart.dimensions[dim].name);
2464 genLabel(this, content, this.chart.dimensions[dim].name, i);
2466 this.element_legend_childs.series.labels_key = tmp.toString();
2467 if(this.debug === true)
2468 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2471 // create a hidden div to be used for hidding
2472 // the original legend of the chart library
2473 var el = document.createElement('div');
2474 if(this.element_legend !== null)
2475 this.element_legend.appendChild(el);
2476 el.style.display = 'none';
2478 this.element_legend_childs.hidden = document.createElement('div');
2479 el.appendChild(this.element_legend_childs.hidden);
2481 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2482 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2484 this.legendShowLatestValues();
2487 this.hasLegend = function() {
2488 if(typeof this.___hasLegendCache___ !== 'undefined')
2489 return this.___hasLegendCache___;
2492 if(this.library && this.library.legend(this) === 'right-side') {
2493 var legend = $(this.element).data('legend') || 'yes';
2494 if(legend === 'yes') leg = true;
2497 this.___hasLegendCache___ = leg;
2501 this.legendWidth = function() {
2502 return (this.hasLegend())?140:0;
2505 this.legendHeight = function() {
2506 return $(this.element).height();
2509 this.chartWidth = function() {
2510 return $(this.element).width() - this.legendWidth();
2513 this.chartHeight = function() {
2514 return $(this.element).height();
2517 this.chartPixelsPerPoint = function() {
2518 // force an options provided detail
2519 var px = this.pixels_per_point;
2521 if(this.library && px < this.library.pixels_per_point(this))
2522 px = this.library.pixels_per_point(this);
2524 if(px < NETDATA.options.current.pixels_per_point)
2525 px = NETDATA.options.current.pixels_per_point;
2530 this.needsRecreation = function() {
2532 this.chart_created === true
2534 && this.library.autoresize() === false
2535 && this.tm.last_resized < NETDATA.options.last_resized
2539 this.chartURL = function() {
2540 var after, before, points_multiplier = 1;
2541 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2542 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2544 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2545 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2546 this.view_after = after * 1000;
2547 this.view_before = before * 1000;
2549 this.requested_padding = null;
2550 points_multiplier = 1;
2552 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2553 this.tm.pan_and_zoom_seq = 0;
2555 before = Math.round(this.current.force_before_ms / 1000);
2556 after = Math.round(this.current.force_after_ms / 1000);
2557 this.view_after = after * 1000;
2558 this.view_before = before * 1000;
2560 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2561 this.requested_padding = Math.round((before - after) / 2);
2562 after -= this.requested_padding;
2563 before += this.requested_padding;
2564 this.requested_padding *= 1000;
2565 points_multiplier = 2;
2568 this.current.force_before_ms = null;
2569 this.current.force_after_ms = null;
2572 this.tm.pan_and_zoom_seq = 0;
2574 before = this.before;
2576 this.view_after = after * 1000;
2577 this.view_before = before * 1000;
2579 this.requested_padding = null;
2580 points_multiplier = 1;
2583 this.requested_after = after * 1000;
2584 this.requested_before = before * 1000;
2586 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2588 // build the data URL
2589 this.data_url = this.host + this.chart.data_url;
2590 this.data_url += "&format=" + this.library.format();
2591 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2592 this.data_url += "&group=" + this.method;
2593 this.data_url += "&options=" + this.library.options(this);
2594 this.data_url += '|jsonwrap';
2596 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2597 this.data_url += '|nonzero';
2599 if(this.append_options !== null)
2600 this.data_url += '|' + this.append_options.toString();
2603 this.data_url += "&after=" + after.toString();
2606 this.data_url += "&before=" + before.toString();
2609 this.data_url += "&dimensions=" + this.dimensions;
2611 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2612 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2615 this.redrawChart = function() {
2616 if(this.data !== null)
2617 this.updateChartWithData(this.data);
2620 this.updateChartWithData = function(data) {
2621 if(this.debug === true)
2622 this.log('updateChartWithData() called.');
2624 // this may force the chart to be re-created
2628 this.updates_counter++;
2629 this.updates_since_last_unhide++;
2630 this.updates_since_last_creation++;
2632 var started = new Date().getTime();
2634 // if the result is JSON, find the latest update-every
2635 this.data_update_every = data.view_update_every * 1000;
2636 this.data_after = data.after * 1000;
2637 this.data_before = data.before * 1000;
2638 this.netdata_first = data.first_entry * 1000;
2639 this.netdata_last = data.last_entry * 1000;
2640 this.data_points = data.points;
2643 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2644 if(this.view_after < this.data_after) {
2645 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2646 this.view_after = this.data_after;
2649 if(this.view_before > this.data_before) {
2650 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2651 this.view_before = this.data_before;
2655 this.view_after = this.data_after;
2656 this.view_before = this.data_before;
2659 if(this.debug === true) {
2660 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2662 if(this.current.force_after_ms)
2663 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2665 this.log('STATUS: forced : unset');
2667 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2668 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2669 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2670 this.log('STATUS: points : ' + (this.data_points).toString());
2673 if(this.data_points === 0) {
2678 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2679 if(this.debug === true)
2680 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2682 this.chart_created = false;
2685 // check and update the legend
2686 this.legendUpdateDOM();
2688 if(this.chart_created === true
2689 && typeof this.library.update === 'function') {
2691 if(this.debug === true)
2692 this.log('updating chart...');
2694 if(callChartLibraryUpdateSafely(data) === false)
2698 if(this.debug === true)
2699 this.log('creating chart...');
2701 if(callChartLibraryCreateSafely(data) === false)
2705 this.legendShowLatestValues();
2706 if(this.selected === true)
2707 NETDATA.globalSelectionSync.stop();
2709 // update the performance counters
2710 var now = new Date().getTime();
2711 this.tm.last_updated = now;
2713 // don't update last_autorefreshed if this chart is
2714 // forced to be updated with global PanAndZoom
2715 if(NETDATA.globalPanAndZoom.isActive())
2716 this.tm.last_autorefreshed = 0;
2718 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2719 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2721 this.tm.last_autorefreshed = now;
2724 this.refresh_dt_ms = now - started;
2725 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2727 if(this.refresh_dt_element !== null)
2728 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2731 this.updateChart = function(callback) {
2732 if(this.debug === true)
2733 this.log('updateChart() called.');
2735 if(this._updating === true) {
2736 if(this.debug === true)
2737 this.log('I am already updating...');
2739 if(typeof callback === 'function') callback();
2743 // due to late initialization of charts and libraries
2744 // we need to check this too
2745 if(this.enabled === false) {
2746 if(this.debug === true)
2747 this.log('I am not enabled');
2749 if(typeof callback === 'function') callback();
2753 if(canBeRendered() === false) {
2754 if(typeof callback === 'function') callback();
2758 if(this.chart === null) {
2759 this.getChart(function() { that.updateChart(callback); });
2763 if(this.library.initialized === false) {
2764 if(this.library.enabled === true) {
2765 this.library.initialize(function() { that.updateChart(callback); });
2769 error('chart library "' + this.library_name + '" is not available.');
2770 if(typeof callback === 'function') callback();
2775 this.clearSelection();
2778 if(this.debug === true)
2779 this.log('updating from ' + this.data_url);
2781 NETDATA.statistics.refreshes_total++;
2782 NETDATA.statistics.refreshes_active++;
2784 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2785 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2787 this._updating = true;
2789 this.xhr = $.ajax( {
2794 .success(function(data) {
2795 if(that.debug === true)
2796 that.log('data received. updating chart.');
2798 that.updateChartWithData(data);
2801 error('data download failed for url: ' + that.data_url);
2803 .always(function() {
2804 NETDATA.statistics.refreshes_active--;
2805 that._updating = false;
2806 if(typeof callback === 'function') callback();
2812 this.isVisible = function(nocache) {
2813 if(typeof nocache === 'undefined')
2816 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2818 // caching - we do not evaluate the charts visibility
2819 // if the page has not been scrolled since the last check
2820 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2821 return this.___isVisible___;
2823 this.tm.last_visible_check = new Date().getTime();
2825 var wh = window.innerHeight;
2826 var x = this.element.getBoundingClientRect();
2830 if(x.width === 0 || x.height === 0) {
2832 this.___isVisible___ = false;
2833 return this.___isVisible___;
2836 if(x.top < 0 && -x.top > x.height) {
2837 // the chart is entirely above
2838 ret = -x.top - x.height;
2840 else if(x.top > wh) {
2841 // the chart is entirely below
2845 if(ret > tolerance) {
2846 // the chart is too far
2849 this.___isVisible___ = false;
2850 return this.___isVisible___;
2853 // the chart is inside or very close
2856 this.___isVisible___ = true;
2857 return this.___isVisible___;
2861 this.isAutoRefreshable = function() {
2862 return (this.current.autorefresh);
2865 this.canBeAutoRefreshed = function() {
2866 var now = new Date().getTime();
2868 if(this.running === true) {
2869 if(this.debug === true)
2870 this.log('I am already running');
2875 if(this.enabled === false) {
2876 if(this.debug === true)
2877 this.log('I am not enabled');
2882 if(this.library === null || this.library.enabled === false) {
2883 error('charting library "' + this.library_name + '" is not available');
2884 if(this.debug === true)
2885 this.log('My chart library ' + this.library_name + ' is not available');
2890 if(this.isVisible() === false) {
2891 if(NETDATA.options.debug.visibility === true || this.debug === true)
2892 this.log('I am not visible');
2897 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2898 if(this.debug === true)
2899 this.log('timed force update detected - allowing this update');
2901 this.current.force_update_at = 0;
2905 if(this.isAutoRefreshable() === true) {
2906 // allow the first update, even if the page is not visible
2907 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2908 if(NETDATA.options.debug.focus === true || this.debug === true)
2909 this.log('canBeAutoRefreshed(): page does not have focus');
2914 if(this.needsRecreation() === true) {
2915 if(this.debug === true)
2916 this.log('canBeAutoRefreshed(): needs re-creation.');
2921 // options valid only for autoRefresh()
2922 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2923 if(NETDATA.globalPanAndZoom.isActive()) {
2924 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2925 if(this.debug === true)
2926 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2931 if(this.debug === true)
2932 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2938 if(this.selected === true) {
2939 if(this.debug === true)
2940 this.log('canBeAutoRefreshed(): I have a selection in place.');
2945 if(this.paused === true) {
2946 if(this.debug === true)
2947 this.log('canBeAutoRefreshed(): I am paused.');
2952 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2953 if(this.debug === true)
2954 this.log('canBeAutoRefreshed(): It is time to update me.');
2964 this.autoRefresh = function(callback) {
2965 if(this.canBeAutoRefreshed() === true && this.running === false) {
2968 state.running = true;
2969 state.updateChart(function() {
2970 state.running = false;
2972 if(typeof callback !== 'undefined')
2977 if(typeof callback !== 'undefined')
2982 this._defaultsFromDownloadedChart = function(chart) {
2984 this.chart_url = chart.url;
2985 this.data_update_every = chart.update_every * 1000;
2986 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2987 this.tm.last_info_downloaded = new Date().getTime();
2989 if(this.title === null)
2990 this.title = chart.title;
2992 if(this.units === null)
2993 this.units = chart.units;
2996 // fetch the chart description from the netdata server
2997 this.getChart = function(callback) {
2998 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3000 this._defaultsFromDownloadedChart(this.chart);
3001 if(typeof callback === 'function') callback();
3004 this.chart_url = "/api/v1/chart?chart=" + this.id;
3006 if(this.debug === true)
3007 this.log('downloading ' + this.chart_url);
3010 url: this.host + this.chart_url,
3014 .done(function(chart) {
3015 chart.url = that.chart_url;
3016 that._defaultsFromDownloadedChart(chart);
3017 NETDATA.chartRegistry.add(that.host, that.id, chart);
3020 NETDATA.error(404, that.chart_url);
3021 error('chart not found on url "' + that.chart_url + '"');
3023 .always(function() {
3024 if(typeof callback === 'function') callback();
3029 // ============================================================================================================
3035 NETDATA.resetAllCharts = function(state) {
3036 // first clear the global selection sync
3037 // to make sure no chart is in selected state
3038 state.globalSelectionSyncStop();
3040 // there are 2 possibilities here
3041 // a. state is the global Pan and Zoom master
3042 // b. state is not the global Pan and Zoom master
3044 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3047 // clear the global Pan and Zoom
3048 // this will also refresh the master
3049 // and unblock any charts currently mirroring the master
3050 NETDATA.globalPanAndZoom.clearMaster();
3052 // if we were not the master, reset our status too
3053 // this is required because most probably the mouse
3054 // is over this chart, blocking it from auto-refreshing
3055 if(master === false && (state.paused === true || state.selected === true))
3059 // get or create a chart state, given a DOM element
3060 NETDATA.chartState = function(element) {
3061 var state = $(element).data('netdata-state-object') || null;
3062 if(state === null) {
3063 state = new chartState(element);
3064 $(element).data('netdata-state-object', state);
3069 // ----------------------------------------------------------------------------------------------------------------
3070 // Library functions
3072 // Load a script without jquery
3073 // This is used to load jquery - after it is loaded, we use jquery
3074 NETDATA._loadjQuery = function(callback) {
3075 if(typeof jQuery === 'undefined') {
3076 if(NETDATA.options.debug.main_loop === true)
3077 console.log('loading ' + NETDATA.jQuery);
3079 var script = document.createElement('script');
3080 script.type = 'text/javascript';
3081 script.async = true;
3082 script.src = NETDATA.jQuery;
3084 // script.onabort = onError;
3085 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3086 if(typeof callback === "function")
3087 script.onload = callback;
3089 var s = document.getElementsByTagName('script')[0];
3090 s.parentNode.insertBefore(script, s);
3092 else if(typeof callback === "function")
3096 NETDATA._loadCSS = function(filename) {
3097 // don't use jQuery here
3098 // styles are loaded before jQuery
3099 // to eliminate showing an unstyled page to the user
3101 var fileref = document.createElement("link");
3102 fileref.setAttribute("rel", "stylesheet");
3103 fileref.setAttribute("type", "text/css");
3104 fileref.setAttribute("href", filename);
3106 if (typeof fileref !== 'undefined')
3107 document.getElementsByTagName("head")[0].appendChild(fileref);
3110 NETDATA.colorHex2Rgb = function(hex) {
3111 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3112 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3113 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3114 return r + r + g + g + b + b;
3117 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3119 r: parseInt(result[1], 16),
3120 g: parseInt(result[2], 16),
3121 b: parseInt(result[3], 16)
3125 NETDATA.colorLuminance = function(hex, lum) {
3126 // validate hex string
3127 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3129 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3133 // convert to decimal and change luminosity
3134 var rgb = "#", c, i;
3135 for (i = 0; i < 3; i++) {
3136 c = parseInt(hex.substr(i*2,2), 16);
3137 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3138 rgb += ("00"+c).substr(c.length);
3144 NETDATA.guid = function() {
3146 return Math.floor((1 + Math.random()) * 0x10000)
3151 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3154 NETDATA.zeropad = function(x) {
3155 if(x > -10 && x < 10) return '0' + x.toString();
3156 else return x.toString();
3159 // user function to signal us the DOM has been
3161 NETDATA.updatedDom = function() {
3162 NETDATA.options.updated_dom = true;
3165 NETDATA.ready = function(callback) {
3166 NETDATA.options.pauseCallback = callback;
3169 NETDATA.pause = function(callback) {
3170 if(NETDATA.options.pause === true)
3173 NETDATA.options.pauseCallback = callback;
3176 NETDATA.unpause = function() {
3177 NETDATA.options.pauseCallback = null;
3178 NETDATA.options.updated_dom = true;
3179 NETDATA.options.pause = false;
3182 // ----------------------------------------------------------------------------------------------------------------
3184 // this is purely sequencial charts refresher
3185 // it is meant to be autonomous
3186 NETDATA.chartRefresherNoParallel = function(index) {
3187 if(NETDATA.options.debug.mail_loop === true)
3188 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3190 if(NETDATA.options.updated_dom === true) {
3191 // the dom has been updated
3192 // get the dom parts again
3193 NETDATA.parseDom(NETDATA.chartRefresher);
3196 if(index >= NETDATA.options.targets.length) {
3197 if(NETDATA.options.debug.main_loop === true)
3198 console.log('waiting to restart main loop...');
3200 NETDATA.options.auto_refresher_fast_weight = 0;
3202 setTimeout(function() {
3203 NETDATA.chartRefresher();
3204 }, NETDATA.options.current.idle_between_loops);
3207 var state = NETDATA.options.targets[index];
3209 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3210 if(NETDATA.options.debug.main_loop === true)
3211 console.log('fast rendering...');
3213 state.autoRefresh(function() {
3214 NETDATA.chartRefresherNoParallel(++index);
3218 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3219 NETDATA.options.auto_refresher_fast_weight = 0;
3221 setTimeout(function() {
3222 state.autoRefresh(function() {
3223 NETDATA.chartRefresherNoParallel(++index);
3225 }, NETDATA.options.current.idle_between_charts);
3230 // this is part of the parallel refresher
3231 // its cause is to refresh sequencially all the charts
3232 // that depend on chart library initialization
3233 // it will call the parallel refresher back
3234 // as soon as it sees a chart that its chart library
3236 NETDATA.chartRefresher_uninitialized = function() {
3237 if(NETDATA.options.updated_dom === true) {
3238 // the dom has been updated
3239 // get the dom parts again
3240 NETDATA.parseDom(NETDATA.chartRefresher);
3244 if(NETDATA.options.sequencial.length === 0)
3245 NETDATA.chartRefresher();
3247 var state = NETDATA.options.sequencial.pop();
3248 if(state.library.initialized === true)
3249 NETDATA.chartRefresher();
3251 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3255 NETDATA.chartRefresherWaitTime = function() {
3256 return NETDATA.options.current.idle_parallel_loops;
3259 // the default refresher
3260 // it will create 2 sets of charts:
3261 // - the ones that can be refreshed in parallel
3262 // - the ones that depend on something else
3263 // the first set will be executed in parallel
3264 // the second will be given to NETDATA.chartRefresher_uninitialized()
3265 NETDATA.chartRefresher = function() {
3266 if(NETDATA.options.pause === true) {
3267 // console.log('auto-refresher is paused');
3268 setTimeout(NETDATA.chartRefresher,
3269 NETDATA.chartRefresherWaitTime());
3273 if(typeof NETDATA.options.pauseCallback === 'function') {
3274 // console.log('auto-refresher is calling pauseCallback');
3275 NETDATA.options.pause = true;
3276 NETDATA.options.pauseCallback();
3277 NETDATA.chartRefresher();
3281 if(NETDATA.options.current.parallel_refresher === false) {
3282 NETDATA.chartRefresherNoParallel(0);
3286 if(NETDATA.options.updated_dom === true) {
3287 // the dom has been updated
3288 // get the dom parts again
3289 NETDATA.parseDom(NETDATA.chartRefresher);
3293 var parallel = new Array();
3294 var targets = NETDATA.options.targets;
3295 var len = targets.length;
3298 state = targets[len];
3299 if(state.isVisible() === false || state.running === true)
3302 if(state.library.initialized === false) {
3303 if(state.library.enabled === true) {
3304 state.library.initialize(NETDATA.chartRefresher);
3308 state.error('chart library "' + state.library_name + '" is not enabled.');
3312 parallel.unshift(state);
3315 if(parallel.length > 0) {
3316 // this will execute the jobs in parallel
3317 $(parallel).each(function() {
3322 // run the next refresh iteration
3323 setTimeout(NETDATA.chartRefresher,
3324 NETDATA.chartRefresherWaitTime());
3327 NETDATA.parseDom = function(callback) {
3328 NETDATA.options.last_page_scroll = new Date().getTime();
3329 NETDATA.options.updated_dom = false;
3331 var targets = $('div[data-netdata]'); //.filter(':visible');
3333 if(NETDATA.options.debug.main_loop === true)
3334 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3336 NETDATA.options.targets = new Array();
3337 var len = targets.length;
3339 // the initialization will take care of sizing
3340 // and the "loading..." message
3341 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3344 if(typeof callback === 'function') callback();
3347 // this is the main function - where everything starts
3348 NETDATA.start = function() {
3349 // this should be called only once
3351 NETDATA.options.page_is_visible = true;
3353 $(window).blur(function() {
3354 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3355 NETDATA.options.page_is_visible = false;
3356 if(NETDATA.options.debug.focus === true)
3357 console.log('Lost Focus!');
3361 $(window).focus(function() {
3362 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3363 NETDATA.options.page_is_visible = true;
3364 if(NETDATA.options.debug.focus === true)
3365 console.log('Focus restored!');
3369 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3370 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3371 NETDATA.options.page_is_visible = false;
3372 if(NETDATA.options.debug.focus === true)
3373 console.log('Document has no focus!');
3377 // bootstrap tab switching
3378 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3380 // bootstrap modal switching
3381 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3382 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3384 // bootstrap collapse switching
3385 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3386 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3388 NETDATA.parseDom(NETDATA.chartRefresher);
3390 // Registry initialization
3391 setTimeout(NETDATA.registry.init, 1000);
3394 // ----------------------------------------------------------------------------------------------------------------
3397 NETDATA.peityInitialize = function(callback) {
3398 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3400 url: NETDATA.peity_js,
3405 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3408 NETDATA.chartLibraries.peity.enabled = false;
3409 NETDATA.error(100, NETDATA.peity_js);
3411 .always(function() {
3412 if(typeof callback === "function")
3417 NETDATA.chartLibraries.peity.enabled = false;
3418 if(typeof callback === "function")
3423 NETDATA.peityChartUpdate = function(state, data) {
3424 state.peity_instance.innerHTML = data.result;
3426 if(state.peity_options.stroke !== state.chartColors()[0]) {
3427 state.peity_options.stroke = state.chartColors()[0];
3428 if(state.chart.chart_type === 'line')
3429 state.peity_options.fill = NETDATA.themes.current.background;
3431 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3434 $(state.peity_instance).peity('line', state.peity_options);
3438 NETDATA.peityChartCreate = function(state, data) {
3439 state.peity_instance = document.createElement('div');
3440 state.element_chart.appendChild(state.peity_instance);
3442 var self = $(state.element);
3443 state.peity_options = {
3444 stroke: NETDATA.themes.current.foreground,
3445 strokeWidth: self.data('peity-strokewidth') || 1,
3446 width: state.chartWidth(),
3447 height: state.chartHeight(),
3448 fill: NETDATA.themes.current.foreground
3451 NETDATA.peityChartUpdate(state, data);
3455 // ----------------------------------------------------------------------------------------------------------------
3458 NETDATA.sparklineInitialize = function(callback) {
3459 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3461 url: NETDATA.sparkline_js,
3466 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3469 NETDATA.chartLibraries.sparkline.enabled = false;
3470 NETDATA.error(100, NETDATA.sparkline_js);
3472 .always(function() {
3473 if(typeof callback === "function")
3478 NETDATA.chartLibraries.sparkline.enabled = false;
3479 if(typeof callback === "function")
3484 NETDATA.sparklineChartUpdate = function(state, data) {
3485 state.sparkline_options.width = state.chartWidth();
3486 state.sparkline_options.height = state.chartHeight();
3488 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3492 NETDATA.sparklineChartCreate = function(state, data) {
3493 var self = $(state.element);
3494 var type = self.data('sparkline-type') || 'line';
3495 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3496 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3497 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3498 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3499 var composite = self.data('sparkline-composite') || undefined;
3500 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3501 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3502 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3503 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3504 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3505 var spotColor = self.data('sparkline-spotcolor') || undefined;
3506 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3507 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3508 var spotRadius = self.data('sparkline-spotradius') || undefined;
3509 var valueSpots = self.data('sparkline-valuespots') || undefined;
3510 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3511 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3512 var lineWidth = self.data('sparkline-linewidth') || undefined;
3513 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3514 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3515 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3516 var xvalues = self.data('sparkline-xvalues') || undefined;
3517 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3518 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3519 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3520 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3521 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3522 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3523 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3524 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3525 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3526 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3527 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3528 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3529 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3530 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3531 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3532 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3533 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3534 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3535 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3536 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3537 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3538 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3540 state.sparkline_options = {
3542 lineColor: lineColor,
3543 fillColor: fillColor,
3544 chartRangeMin: chartRangeMin,
3545 chartRangeMax: chartRangeMax,
3546 composite: composite,
3547 enableTagOptions: enableTagOptions,
3548 tagOptionPrefix: tagOptionPrefix,
3549 tagValuesAttribute: tagValuesAttribute,
3550 disableHiddenCheck: disableHiddenCheck,
3551 defaultPixelsPerValue: defaultPixelsPerValue,
3552 spotColor: spotColor,
3553 minSpotColor: minSpotColor,
3554 maxSpotColor: maxSpotColor,
3555 spotRadius: spotRadius,
3556 valueSpots: valueSpots,
3557 highlightSpotColor: highlightSpotColor,
3558 highlightLineColor: highlightLineColor,
3559 lineWidth: lineWidth,
3560 normalRangeMin: normalRangeMin,
3561 normalRangeMax: normalRangeMax,
3562 drawNormalOnTop: drawNormalOnTop,
3564 chartRangeClip: chartRangeClip,
3565 chartRangeMinX: chartRangeMinX,
3566 chartRangeMaxX: chartRangeMaxX,
3567 disableInteraction: disableInteraction,
3568 disableTooltips: disableTooltips,
3569 disableHighlight: disableHighlight,
3570 highlightLighten: highlightLighten,
3571 highlightColor: highlightColor,
3572 tooltipContainer: tooltipContainer,
3573 tooltipClassname: tooltipClassname,
3574 tooltipChartTitle: state.title,
3575 tooltipFormat: tooltipFormat,
3576 tooltipPrefix: tooltipPrefix,
3577 tooltipSuffix: tooltipSuffix,
3578 tooltipSkipNull: tooltipSkipNull,
3579 tooltipValueLookups: tooltipValueLookups,
3580 tooltipFormatFieldlist: tooltipFormatFieldlist,
3581 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3582 numberFormatter: numberFormatter,
3583 numberDigitGroupSep: numberDigitGroupSep,
3584 numberDecimalMark: numberDecimalMark,
3585 numberDigitGroupCount: numberDigitGroupCount,
3586 animatedZooms: animatedZooms,
3587 width: state.chartWidth(),
3588 height: state.chartHeight()
3591 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3595 // ----------------------------------------------------------------------------------------------------------------
3602 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3603 if(after < state.netdata_first)
3604 after = state.netdata_first;
3606 if(before > state.netdata_last)
3607 before = state.netdata_last;
3609 state.setMode('zoom');
3610 state.globalSelectionSyncStop();
3611 state.globalSelectionSyncDelay();
3612 state.dygraph_user_action = true;
3613 state.dygraph_force_zoom = true;
3614 state.updateChartPanOrZoom(after, before);
3615 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3618 NETDATA.dygraphSetSelection = function(state, t) {
3619 if(typeof state.dygraph_instance !== 'undefined') {
3620 var r = state.calculateRowForTime(t);
3622 state.dygraph_instance.setSelection(r);
3624 state.dygraph_instance.clearSelection();
3625 state.legendShowUndefined();
3632 NETDATA.dygraphClearSelection = function(state, t) {
3633 if(typeof state.dygraph_instance !== 'undefined') {
3634 state.dygraph_instance.clearSelection();
3639 NETDATA.dygraphSmoothInitialize = function(callback) {
3641 url: NETDATA.dygraph_smooth_js,
3646 NETDATA.dygraph.smooth = true;
3647 smoothPlotter.smoothing = 0.3;
3650 NETDATA.dygraph.smooth = false;
3652 .always(function() {
3653 if(typeof callback === "function")
3658 NETDATA.dygraphInitialize = function(callback) {
3659 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3661 url: NETDATA.dygraph_js,
3666 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3669 NETDATA.chartLibraries.dygraph.enabled = false;
3670 NETDATA.error(100, NETDATA.dygraph_js);
3672 .always(function() {
3673 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3674 NETDATA.dygraphSmoothInitialize(callback);
3675 else if(typeof callback === "function")
3680 NETDATA.chartLibraries.dygraph.enabled = false;
3681 if(typeof callback === "function")
3686 NETDATA.dygraphChartUpdate = function(state, data) {
3687 var dygraph = state.dygraph_instance;
3689 if(typeof dygraph === 'undefined')
3690 return NETDATA.dygraphChartCreate(state, data);
3692 // when the chart is not visible, and hidden
3693 // if there is a window resize, dygraph detects
3694 // its element size as 0x0.
3695 // this will make it re-appear properly
3697 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3701 file: data.result.data,
3702 colors: state.chartColors(),
3703 labels: data.result.labels,
3704 labelsDivWidth: state.chartWidth() - 70,
3705 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3708 if(state.dygraph_force_zoom === true) {
3709 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3710 state.log('dygraphChartUpdate() forced zoom update');
3712 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3713 options.valueRange = null;
3714 options.isZoomedIgnoreProgrammaticZoom = true;
3715 state.dygraph_force_zoom = false;
3717 else if(state.current.name !== 'auto') {
3718 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3719 state.log('dygraphChartUpdate() loose update');
3722 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3723 state.log('dygraphChartUpdate() strict update');
3725 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3726 options.valueRange = null;
3727 options.isZoomedIgnoreProgrammaticZoom = true;
3730 if(state.dygraph_smooth_eligible === true) {
3731 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3732 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3733 NETDATA.dygraphChartCreate(state, data);
3738 dygraph.updateOptions(options);
3740 state.dygraph_last_rendered = new Date().getTime();
3744 NETDATA.dygraphChartCreate = function(state, data) {
3745 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3746 state.log('dygraphChartCreate()');
3748 var self = $(state.element);
3750 var chart_type = state.chart.chart_type;
3751 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3752 chart_type = self.data('dygraph-type') || chart_type;
3754 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3755 smooth = self.data('dygraph-smooth') || smooth;
3757 if(NETDATA.dygraph.smooth === false)
3760 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3761 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3763 state.dygraph_options = {
3764 colors: self.data('dygraph-colors') || state.chartColors(),
3766 // leave a few pixels empty on the right of the chart
3767 rightGap: self.data('dygraph-rightgap') || 5,
3768 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3769 showRoller: self.data('dygraph-showroller') || false,
3771 title: self.data('dygraph-title') || state.title,
3772 titleHeight: self.data('dygraph-titleheight') || 19,
3774 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3775 labels: data.result.labels,
3776 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3777 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3778 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3779 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3780 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3783 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3784 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3786 ylabel: state.units,
3787 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3789 // the function to plot the chart
3792 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3793 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3794 strokePattern: self.data('dygraph-strokepattern') || undefined,
3796 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3797 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3798 drawPoints: self.data('dygraph-drawpoints') || false,
3800 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3801 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3803 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3804 pointSize: self.data('dygraph-pointsize') || 1,
3806 // enabling this makes the chart with little square lines
3807 stepPlot: self.data('dygraph-stepplot') || false,
3809 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3810 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3811 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3813 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3814 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3815 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3816 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3818 drawAxis: self.data('dygraph-drawaxis') || true,
3819 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3820 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3821 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3823 drawGrid: self.data('dygraph-drawgrid') || true,
3824 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3825 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3826 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3827 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3828 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3830 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3831 sigFigs: self.data('dygraph-sigfigs') || null,
3832 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3833 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3835 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3836 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3837 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3839 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3840 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3844 ticker: Dygraph.dateTicker,
3845 axisLabelFormatter: function (d, gran) {
3846 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3848 valueFormatter: function (ms) {
3849 var d = new Date(ms);
3850 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3851 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3856 valueFormatter: function (x) {
3857 // we format legends with the state object
3858 // no need to do anything here
3859 // return (Math.round(x*100) / 100).toLocaleString();
3860 // return state.legendFormatValue(x);
3865 legendFormatter: function(data) {
3866 var elements = state.element_legend_childs;
3868 // if the hidden div is not there
3869 // we are not managing the legend
3870 if(elements.hidden === null) return;
3872 if (typeof data.x !== 'undefined') {
3873 state.legendSetDate(data.x);
3874 var i = data.series.length;
3876 var series = data.series[i];
3877 if(!series.isVisible) continue;
3878 state.legendSetLabelValue(series.label, series.y);
3884 drawCallback: function(dygraph, is_initial) {
3885 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3886 state.dygraph_user_action = false;
3888 var x_range = dygraph.xAxisRange();
3889 var after = Math.round(x_range[0]);
3890 var before = Math.round(x_range[1]);
3892 if(NETDATA.options.debug.dygraph === true)
3893 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3895 if(before <= state.netdata_last && after >= state.netdata_first)
3896 state.updateChartPanOrZoom(after, before);
3899 zoomCallback: function(minDate, maxDate, yRanges) {
3900 if(NETDATA.options.debug.dygraph === true)
3901 state.log('dygraphZoomCallback()');
3903 state.globalSelectionSyncStop();
3904 state.globalSelectionSyncDelay();
3905 state.setMode('zoom');
3907 // refresh it to the greatest possible zoom level
3908 state.dygraph_user_action = true;
3909 state.dygraph_force_zoom = true;
3910 state.updateChartPanOrZoom(minDate, maxDate);
3912 highlightCallback: function(event, x, points, row, seriesName) {
3913 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3914 state.log('dygraphHighlightCallback()');
3918 // there is a bug in dygraph when the chart is zoomed enough
3919 // the time it thinks is selected is wrong
3920 // here we calculate the time t based on the row number selected
3922 var t = state.data_after + row * state.data_update_every;
3923 // 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);
3925 state.globalSelectionSync(x);
3927 // fix legend zIndex using the internal structures of dygraph legend module
3928 // this works, but it is a hack!
3929 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3931 unhighlightCallback: function(event) {
3932 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3933 state.log('dygraphUnhighlightCallback()');
3935 state.unpauseChart();
3936 state.globalSelectionSyncStop();
3938 interactionModel : {
3939 mousedown: function(event, dygraph, context) {
3940 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3941 state.log('interactionModel.mousedown()');
3943 state.dygraph_user_action = true;
3944 state.globalSelectionSyncStop();
3946 if(NETDATA.options.debug.dygraph === true)
3947 state.log('dygraphMouseDown()');
3949 // Right-click should not initiate a zoom.
3950 if(event.button && event.button === 2) return;
3952 context.initializeMouseDown(event, dygraph, context);
3954 if(event.button && event.button === 1) {
3955 if (event.altKey || event.shiftKey) {
3956 state.setMode('pan');
3957 state.globalSelectionSyncDelay();
3958 Dygraph.startPan(event, dygraph, context);
3961 state.setMode('zoom');
3962 state.globalSelectionSyncDelay();
3963 Dygraph.startZoom(event, dygraph, context);
3967 if (event.altKey || event.shiftKey) {
3968 state.setMode('zoom');
3969 state.globalSelectionSyncDelay();
3970 Dygraph.startZoom(event, dygraph, context);
3973 state.setMode('pan');
3974 state.globalSelectionSyncDelay();
3975 Dygraph.startPan(event, dygraph, context);
3979 mousemove: function(event, dygraph, context) {
3980 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3981 state.log('interactionModel.mousemove()');
3983 if(context.isPanning) {
3984 state.dygraph_user_action = true;
3985 state.globalSelectionSyncStop();
3986 state.globalSelectionSyncDelay();
3987 state.setMode('pan');
3988 Dygraph.movePan(event, dygraph, context);
3990 else if(context.isZooming) {
3991 state.dygraph_user_action = true;
3992 state.globalSelectionSyncStop();
3993 state.globalSelectionSyncDelay();
3994 state.setMode('zoom');
3995 Dygraph.moveZoom(event, dygraph, context);
3998 mouseup: function(event, dygraph, context) {
3999 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4000 state.log('interactionModel.mouseup()');
4002 if (context.isPanning) {
4003 state.dygraph_user_action = true;
4004 state.globalSelectionSyncDelay();
4005 Dygraph.endPan(event, dygraph, context);
4007 else if (context.isZooming) {
4008 state.dygraph_user_action = true;
4009 state.globalSelectionSyncDelay();
4010 Dygraph.endZoom(event, dygraph, context);
4013 click: function(event, dygraph, context) {
4014 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4015 state.log('interactionModel.click()');
4017 event.preventDefault();
4019 dblclick: function(event, dygraph, context) {
4020 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4021 state.log('interactionModel.dblclick()');
4022 NETDATA.resetAllCharts(state);
4024 mousewheel: function(event, dygraph, context) {
4025 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4026 state.log('interactionModel.mousewheel()');
4028 // Take the offset of a mouse event on the dygraph canvas and
4029 // convert it to a pair of percentages from the bottom left.
4030 // (Not top left, bottom is where the lower value is.)
4031 function offsetToPercentage(g, offsetX, offsetY) {
4032 // This is calculating the pixel offset of the leftmost date.
4033 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4034 var yar0 = g.yAxisRange(0);
4036 // This is calculating the pixel of the higest value. (Top pixel)
4037 var yOffset = g.toDomCoords(null, yar0[1])[1];
4039 // x y w and h are relative to the corner of the drawing area,
4040 // so that the upper corner of the drawing area is (0, 0).
4041 var x = offsetX - xOffset;
4042 var y = offsetY - yOffset;
4044 // This is computing the rightmost pixel, effectively defining the
4046 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4048 // This is computing the lowest pixel, effectively defining the height.
4049 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4051 // Percentage from the left.
4052 var xPct = w === 0 ? 0 : (x / w);
4053 // Percentage from the top.
4054 var yPct = h === 0 ? 0 : (y / h);
4056 // The (1-) part below changes it from "% distance down from the top"
4057 // to "% distance up from the bottom".
4058 return [xPct, (1-yPct)];
4061 // Adjusts [x, y] toward each other by zoomInPercentage%
4062 // Split it so the left/bottom axis gets xBias/yBias of that change and
4063 // tight/top gets (1-xBias)/(1-yBias) of that change.
4065 // If a bias is missing it splits it down the middle.
4066 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4067 xBias = xBias || 0.5;
4068 yBias = yBias || 0.5;
4070 function adjustAxis(axis, zoomInPercentage, bias) {
4071 var delta = axis[1] - axis[0];
4072 var increment = delta * zoomInPercentage;
4073 var foo = [increment * bias, increment * (1-bias)];
4075 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4078 var yAxes = g.yAxisRanges();
4080 for (var i = 0; i < yAxes.length; i++) {
4081 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4084 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4087 if(event.altKey || event.shiftKey) {
4088 state.dygraph_user_action = true;
4090 state.globalSelectionSyncStop();
4091 state.globalSelectionSyncDelay();
4093 // http://dygraphs.com/gallery/interaction-api.js
4094 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4095 var percentage = normal / 50;
4097 if (!(event.offsetX && event.offsetY)){
4098 event.offsetX = event.layerX - event.target.offsetLeft;
4099 event.offsetY = event.layerY - event.target.offsetTop;
4102 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4103 var xPct = percentages[0];
4104 var yPct = percentages[1];
4106 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4108 var after = new_x_range[0];
4109 var before = new_x_range[1];
4111 var first = state.netdata_first + state.data_update_every;
4112 var last = state.netdata_last + state.data_update_every;
4115 after -= (before - last);
4122 state.setMode('zoom');
4123 if(state.updateChartPanOrZoom(after, before) === true)
4124 dygraph.updateOptions({ dateWindow: [ after, before ] });
4126 event.preventDefault();
4129 touchstart: function(event, dygraph, context) {
4130 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4131 state.log('interactionModel.touchstart()');
4133 state.dygraph_user_action = true;
4134 state.setMode('zoom');
4137 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4139 // we overwrite the touch directions at the end, to overwrite
4140 // the internal default of dygraphs
4141 context.touchDirections = { x: true, y: false };
4143 state.dygraph_last_touch_start = new Date().getTime();
4144 state.dygraph_last_touch_move = 0;
4146 if(typeof event.touches[0].pageX === 'number')
4147 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4149 state.dygraph_last_touch_page_x = 0;
4151 touchmove: function(event, dygraph, context) {
4152 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4153 state.log('interactionModel.touchmove()');
4155 state.dygraph_user_action = true;
4156 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4158 state.dygraph_last_touch_move = new Date().getTime();
4160 touchend: function(event, dygraph, context) {
4161 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4162 state.log('interactionModel.touchend()');
4164 state.dygraph_user_action = true;
4165 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4167 // if it didn't move, it is a selection
4168 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4169 // internal api of dygraphs
4170 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4171 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4172 if(NETDATA.dygraphSetSelection(state, t) === true)
4173 state.globalSelectionSync(t);
4176 // if it was double tap within double click time, reset the charts
4177 var now = new Date().getTime();
4178 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4179 if(state.dygraph_last_touch_move === 0) {
4180 var dt = now - state.dygraph_last_touch_end;
4181 if(dt <= NETDATA.options.current.double_click_speed)
4182 NETDATA.resetAllCharts(state);
4186 // remember the timestamp of the last touch end
4187 state.dygraph_last_touch_end = now;
4192 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4193 state.dygraph_options.drawGrid = false;
4194 state.dygraph_options.drawAxis = false;
4195 state.dygraph_options.title = undefined;
4196 state.dygraph_options.units = undefined;
4197 state.dygraph_options.ylabel = undefined;
4198 state.dygraph_options.yLabelWidth = 0;
4199 state.dygraph_options.labelsDivWidth = 120;
4200 state.dygraph_options.labelsDivStyles.width = '120px';
4201 state.dygraph_options.labelsSeparateLines = true;
4202 state.dygraph_options.rightGap = 0;
4205 if(smooth === true) {
4206 state.dygraph_smooth_eligible = true;
4208 if(NETDATA.options.current.smooth_plot === true)
4209 state.dygraph_options.plotter = smoothPlotter;
4211 else state.dygraph_smooth_eligible = false;
4213 state.dygraph_instance = new Dygraph(state.element_chart,
4214 data.result.data, state.dygraph_options);
4216 state.dygraph_force_zoom = false;
4217 state.dygraph_user_action = false;
4218 state.dygraph_last_rendered = new Date().getTime();
4222 // ----------------------------------------------------------------------------------------------------------------
4225 NETDATA.morrisInitialize = function(callback) {
4226 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4228 // morris requires raphael
4229 if(!NETDATA.chartLibraries.raphael.initialized) {
4230 if(NETDATA.chartLibraries.raphael.enabled) {
4231 NETDATA.raphaelInitialize(function() {
4232 NETDATA.morrisInitialize(callback);
4236 NETDATA.chartLibraries.morris.enabled = false;
4237 if(typeof callback === "function")
4242 NETDATA._loadCSS(NETDATA.morris_css);
4245 url: NETDATA.morris_js,
4250 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4253 NETDATA.chartLibraries.morris.enabled = false;
4254 NETDATA.error(100, NETDATA.morris_js);
4256 .always(function() {
4257 if(typeof callback === "function")
4263 NETDATA.chartLibraries.morris.enabled = false;
4264 if(typeof callback === "function")
4269 NETDATA.morrisChartUpdate = function(state, data) {
4270 state.morris_instance.setData(data.result.data);
4274 NETDATA.morrisChartCreate = function(state, data) {
4276 state.morris_options = {
4277 element: state.element_chart.id,
4278 data: data.result.data,
4280 ykeys: data.dimension_names,
4281 labels: data.dimension_names,
4287 continuousLine: false,
4288 behaveLikeLine: false
4291 if(state.chart.chart_type === 'line')
4292 state.morris_instance = new Morris.Line(state.morris_options);
4294 else if(state.chart.chart_type === 'area') {
4295 state.morris_options.behaveLikeLine = true;
4296 state.morris_instance = new Morris.Area(state.morris_options);
4299 state.morris_instance = new Morris.Area(state.morris_options);
4304 // ----------------------------------------------------------------------------------------------------------------
4307 NETDATA.raphaelInitialize = function(callback) {
4308 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4310 url: NETDATA.raphael_js,
4315 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4318 NETDATA.chartLibraries.raphael.enabled = false;
4319 NETDATA.error(100, NETDATA.raphael_js);
4321 .always(function() {
4322 if(typeof callback === "function")
4327 NETDATA.chartLibraries.raphael.enabled = false;
4328 if(typeof callback === "function")
4333 NETDATA.raphaelChartUpdate = function(state, data) {
4334 $(state.element_chart).raphael(data.result, {
4335 width: state.chartWidth(),
4336 height: state.chartHeight()
4342 NETDATA.raphaelChartCreate = function(state, data) {
4343 $(state.element_chart).raphael(data.result, {
4344 width: state.chartWidth(),
4345 height: state.chartHeight()
4351 // ----------------------------------------------------------------------------------------------------------------
4354 NETDATA.c3Initialize = function(callback) {
4355 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4358 if(!NETDATA.chartLibraries.d3.initialized) {
4359 if(NETDATA.chartLibraries.d3.enabled) {
4360 NETDATA.d3Initialize(function() {
4361 NETDATA.c3Initialize(callback);
4365 NETDATA.chartLibraries.c3.enabled = false;
4366 if(typeof callback === "function")
4371 NETDATA._loadCSS(NETDATA.c3_css);
4379 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4382 NETDATA.chartLibraries.c3.enabled = false;
4383 NETDATA.error(100, NETDATA.c3_js);
4385 .always(function() {
4386 if(typeof callback === "function")
4392 NETDATA.chartLibraries.c3.enabled = false;
4393 if(typeof callback === "function")
4398 NETDATA.c3ChartUpdate = function(state, data) {
4399 state.c3_instance.destroy();
4400 return NETDATA.c3ChartCreate(state, data);
4402 //state.c3_instance.load({
4403 // rows: data.result,
4410 NETDATA.c3ChartCreate = function(state, data) {
4412 state.element_chart.id = 'c3-' + state.uuid;
4413 // console.log('id = ' + state.element_chart.id);
4415 state.c3_instance = c3.generate({
4416 bindto: '#' + state.element_chart.id,
4418 width: state.chartWidth(),
4419 height: state.chartHeight()
4422 pattern: state.chartColors()
4427 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4433 format: function(x) {
4434 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4461 // console.log(state.c3_instance);
4466 // ----------------------------------------------------------------------------------------------------------------
4469 NETDATA.d3Initialize = function(callback) {
4470 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4477 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4480 NETDATA.chartLibraries.d3.enabled = false;
4481 NETDATA.error(100, NETDATA.d3_js);
4483 .always(function() {
4484 if(typeof callback === "function")
4489 NETDATA.chartLibraries.d3.enabled = false;
4490 if(typeof callback === "function")
4495 NETDATA.d3ChartUpdate = function(state, data) {
4499 NETDATA.d3ChartCreate = function(state, data) {
4503 // ----------------------------------------------------------------------------------------------------------------
4506 NETDATA.googleInitialize = function(callback) {
4507 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4509 url: NETDATA.google_js,
4514 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4515 google.load('visualization', '1.1', {
4516 'packages': ['corechart', 'controls'],
4517 'callback': callback
4521 NETDATA.chartLibraries.google.enabled = false;
4522 NETDATA.error(100, NETDATA.google_js);
4523 if(typeof callback === "function")
4528 NETDATA.chartLibraries.google.enabled = false;
4529 if(typeof callback === "function")
4534 NETDATA.googleChartUpdate = function(state, data) {
4535 var datatable = new google.visualization.DataTable(data.result);
4536 state.google_instance.draw(datatable, state.google_options);
4540 NETDATA.googleChartCreate = function(state, data) {
4541 var datatable = new google.visualization.DataTable(data.result);
4543 state.google_options = {
4544 colors: state.chartColors(),
4546 // do not set width, height - the chart resizes itself
4547 //width: state.chartWidth(),
4548 //height: state.chartHeight(),
4553 // title: "Time of Day",
4554 // format:'HH:mm:ss',
4555 viewWindowMode: 'maximized',
4567 viewWindowMode: 'pretty',
4582 focusTarget: 'category',
4589 titlePosition: 'out',
4600 curveType: 'function',
4605 switch(state.chart.chart_type) {
4607 state.google_options.vAxis.viewWindowMode = 'maximized';
4608 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4609 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4613 state.google_options.isStacked = true;
4614 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4615 state.google_options.vAxis.viewWindowMode = 'maximized';
4616 state.google_options.vAxis.minValue = null;
4617 state.google_options.vAxis.maxValue = null;
4618 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4623 state.google_options.lineWidth = 2;
4624 state.google_instance = new google.visualization.LineChart(state.element_chart);
4628 state.google_instance.draw(datatable, state.google_options);
4632 // ----------------------------------------------------------------------------------------------------------------
4634 NETDATA.percentFromValueMax = function(value, max) {
4635 if(value === null) value = 0;
4636 if(max < value) max = value;
4640 pcent = Math.round(value * 100 / max);
4641 if(pcent === 0 && value > 0) pcent = 1;
4647 // ----------------------------------------------------------------------------------------------------------------
4650 NETDATA.easypiechartInitialize = function(callback) {
4651 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4653 url: NETDATA.easypiechart_js,
4658 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4661 NETDATA.chartLibraries.easypiechart.enabled = false;
4662 NETDATA.error(100, NETDATA.easypiechart_js);
4664 .always(function() {
4665 if(typeof callback === "function")
4670 NETDATA.chartLibraries.easypiechart.enabled = false;
4671 if(typeof callback === "function")
4676 NETDATA.easypiechartClearSelection = function(state) {
4677 if(typeof state.easyPieChartEvent !== 'undefined') {
4678 if(state.easyPieChartEvent.timer !== null)
4679 clearTimeout(state.easyPieChartEvent.timer);
4681 state.easyPieChartEvent.timer = null;
4684 if(state.isAutoRefreshable() === true && state.data !== null) {
4685 NETDATA.easypiechartChartUpdate(state, state.data);
4688 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4689 state.easyPieChart_instance.update(0);
4691 state.easyPieChart_instance.enableAnimation();
4696 NETDATA.easypiechartSetSelection = function(state, t) {
4697 if(state.timeIsVisible(t) !== true)
4698 return NETDATA.easypiechartClearSelection(state);
4700 var slot = state.calculateRowForTime(t);
4701 if(slot < 0 || slot >= state.data.result.length)
4702 return NETDATA.easypiechartClearSelection(state);
4704 if(typeof state.easyPieChartEvent === 'undefined') {
4705 state.easyPieChartEvent = {
4712 var value = state.data.result[state.data.result.length - 1 - slot];
4713 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4714 var pcent = NETDATA.percentFromValueMax(value, max);
4716 state.easyPieChartEvent.value = value;
4717 state.easyPieChartEvent.pcent = pcent;
4718 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4720 if(state.easyPieChartEvent.timer === null) {
4721 state.easyPieChart_instance.disableAnimation();
4723 state.easyPieChartEvent.timer = setTimeout(function() {
4724 state.easyPieChartEvent.timer = null;
4725 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4726 }, NETDATA.options.current.charts_selection_animation_delay);
4732 NETDATA.easypiechartChartUpdate = function(state, data) {
4733 var value, max, pcent;
4735 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4741 value = data.result[0];
4742 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4743 pcent = NETDATA.percentFromValueMax(value, max);
4746 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4747 state.easyPieChart_instance.update(pcent);
4751 NETDATA.easypiechartChartCreate = function(state, data) {
4752 var self = $(state.element);
4753 var chart = $(state.element_chart);
4755 var value = data.result[0];
4756 var max = self.data('easypiechart-max-value') || null;
4757 var adjust = self.data('easypiechart-adjust') || null;
4761 state.easyPieChartMax = null;
4764 state.easyPieChartMax = max;
4766 var pcent = NETDATA.percentFromValueMax(value, max);
4768 chart.data('data-percent', pcent);
4772 case 'width': size = state.chartHeight(); break;
4773 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4774 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4776 default: size = state.chartWidth(); break;
4778 state.element.style.width = size + 'px';
4779 state.element.style.height = size + 'px';
4781 var stroke = Math.floor(size / 22);
4782 if(stroke < 3) stroke = 2;
4784 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4785 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4786 state.easyPieChartLabel = document.createElement('span');
4787 state.easyPieChartLabel.className = 'easyPieChartLabel';
4788 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4789 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4790 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4791 state.element_chart.appendChild(state.easyPieChartLabel);
4793 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4794 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4795 state.easyPieChartTitle = document.createElement('span');
4796 state.easyPieChartTitle.className = 'easyPieChartTitle';
4797 state.easyPieChartTitle.innerHTML = state.title;
4798 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4799 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4800 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4801 state.element_chart.appendChild(state.easyPieChartTitle);
4803 var unitfontsize = Math.round(titlefontsize * 0.9);
4804 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4805 state.easyPieChartUnits = document.createElement('span');
4806 state.easyPieChartUnits.className = 'easyPieChartUnits';
4807 state.easyPieChartUnits.innerHTML = state.units;
4808 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4809 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4810 state.element_chart.appendChild(state.easyPieChartUnits);
4812 chart.easyPieChart({
4813 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4814 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4815 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4816 scaleLength: self.data('easypiechart-scalelength') || 5,
4817 lineCap: self.data('easypiechart-linecap') || 'round',
4818 lineWidth: self.data('easypiechart-linewidth') || stroke,
4819 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4820 size: self.data('easypiechart-size') || size,
4821 rotate: self.data('easypiechart-rotate') || 0,
4822 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4823 easing: self.data('easypiechart-easing') || undefined
4826 // when we just re-create the chart
4827 // do not animate the first update
4829 if(typeof state.easyPieChart_instance !== 'undefined')
4832 state.easyPieChart_instance = chart.data('easyPieChart');
4833 if(animate === false) state.easyPieChart_instance.disableAnimation();
4834 state.easyPieChart_instance.update(pcent);
4835 if(animate === false) state.easyPieChart_instance.enableAnimation();
4839 // ----------------------------------------------------------------------------------------------------------------
4842 NETDATA.gaugeInitialize = function(callback) {
4843 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4845 url: NETDATA.gauge_js,
4850 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4853 NETDATA.chartLibraries.gauge.enabled = false;
4854 NETDATA.error(100, NETDATA.gauge_js);
4856 .always(function() {
4857 if(typeof callback === "function")
4862 NETDATA.chartLibraries.gauge.enabled = false;
4863 if(typeof callback === "function")
4868 NETDATA.gaugeAnimation = function(state, status) {
4871 if(typeof status === 'boolean' && status === false)
4873 else if(typeof status === 'number')
4876 state.gauge_instance.animationSpeed = speed;
4877 state.___gaugeOld__.speed = speed;
4880 NETDATA.gaugeSet = function(state, value, min, max) {
4881 if(typeof value !== 'number') value = 0;
4882 if(typeof min !== 'number') min = 0;
4883 if(typeof max !== 'number') max = 0;
4884 if(value > max) max = value;
4885 if(value < min) min = value;
4894 // gauge.js has an issue if the needle
4895 // is smaller than min or larger than max
4896 // when we set the new values
4897 // the needle will go crazy
4899 // to prevent it, we always feed it
4900 // with a percentage, so that the needle
4901 // is always between min and max
4902 var pcent = (value - min) * 100 / (max - min);
4904 // these should never happen
4905 if(pcent < 0) pcent = 0;
4906 if(pcent > 100) pcent = 100;
4908 state.gauge_instance.set(pcent);
4910 state.___gaugeOld__.value = value;
4911 state.___gaugeOld__.min = min;
4912 state.___gaugeOld__.max = max;
4915 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4916 if(state.___gaugeOld__.valueLabel !== value) {
4917 state.___gaugeOld__.valueLabel = value;
4918 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4920 if(state.___gaugeOld__.minLabel !== min) {
4921 state.___gaugeOld__.minLabel = min;
4922 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4924 if(state.___gaugeOld__.maxLabel !== max) {
4925 state.___gaugeOld__.maxLabel = max;
4926 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4930 NETDATA.gaugeClearSelection = function(state) {
4931 if(typeof state.gaugeEvent !== 'undefined') {
4932 if(state.gaugeEvent.timer !== null)
4933 clearTimeout(state.gaugeEvent.timer);
4935 state.gaugeEvent.timer = null;
4938 if(state.isAutoRefreshable() === true && state.data !== null) {
4939 NETDATA.gaugeChartUpdate(state, state.data);
4942 NETDATA.gaugeAnimation(state, false);
4943 NETDATA.gaugeSet(state, null, null, null);
4944 NETDATA.gaugeSetLabels(state, null, null, null);
4947 NETDATA.gaugeAnimation(state, true);
4951 NETDATA.gaugeSetSelection = function(state, t) {
4952 if(state.timeIsVisible(t) !== true)
4953 return NETDATA.gaugeClearSelection(state);
4955 var slot = state.calculateRowForTime(t);
4956 if(slot < 0 || slot >= state.data.result.length)
4957 return NETDATA.gaugeClearSelection(state);
4959 if(typeof state.gaugeEvent === 'undefined') {
4960 state.gaugeEvent = {
4968 var value = state.data.result[state.data.result.length - 1 - slot];
4969 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4972 state.gaugeEvent.value = value;
4973 state.gaugeEvent.max = max;
4974 state.gaugeEvent.min = min;
4975 NETDATA.gaugeSetLabels(state, value, min, max);
4977 if(state.gaugeEvent.timer === null) {
4978 NETDATA.gaugeAnimation(state, false);
4980 state.gaugeEvent.timer = setTimeout(function() {
4981 state.gaugeEvent.timer = null;
4982 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4983 }, NETDATA.options.current.charts_selection_animation_delay);
4989 NETDATA.gaugeChartUpdate = function(state, data) {
4990 var value, min, max;
4992 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4996 NETDATA.gaugeSetLabels(state, null, null, null);
4999 value = data.result[0];
5001 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5002 if(value > max) max = value;
5003 NETDATA.gaugeSetLabels(state, value, min, max);
5006 NETDATA.gaugeSet(state, value, min, max);
5010 NETDATA.gaugeChartCreate = function(state, data) {
5011 var self = $(state.element);
5012 // var chart = $(state.element_chart);
5014 var value = data.result[0];
5015 var max = self.data('gauge-max-value') || null;
5016 var adjust = self.data('gauge-adjust') || null;
5017 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5018 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5019 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5020 var stopColor = self.data('gauge-stop-color') || void 0;
5021 var generateGradient = self.data('gauge-generate-gradient') || false;
5025 state.gaugeMax = null;
5028 state.gaugeMax = max;
5030 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5032 // case 'width': width = height * ratio; break;
5034 // default: height = width / ratio; break;
5036 //state.element.style.width = width.toString() + 'px';
5037 //state.element.style.height = height.toString() + 'px';
5042 lines: 12, // The number of lines to draw
5043 angle: 0.15, // The length of each line
5044 lineWidth: 0.44, // 0.44 The line thickness
5046 length: 0.8, // 0.9 The radius of the inner circle
5047 strokeWidth: 0.035, // The rotation offset
5048 color: pointerColor // Fill color
5050 colorStart: startColor, // Colors
5051 colorStop: stopColor, // just experiment with them
5052 strokeColor: strokeColor, // to see which ones work best for you
5054 generateGradient: (generateGradient === true)?true:false,
5058 if (generateGradient.constructor === Array) {
5060 // data-gauge-generate-gradient="[0, 50, 100]"
5061 // data-gauge-gradient-percent-color-0="#FFFFFF"
5062 // data-gauge-gradient-percent-color-50="#999900"
5063 // data-gauge-gradient-percent-color-100="#000000"
5065 options.percentColors = new Array();
5066 var len = generateGradient.length;
5068 var pcent = generateGradient[len];
5069 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5070 if(color !== false) {
5071 var a = new Array();
5074 options.percentColors.unshift(a);
5077 if(options.percentColors.length === 0)
5078 delete options.percentColors;
5080 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5081 options.percentColors = [
5082 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5083 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5084 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5085 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5086 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5087 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5088 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5089 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5090 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5091 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5092 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5095 state.gauge_canvas = document.createElement('canvas');
5096 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5097 state.gauge_canvas.className = 'gaugeChart';
5098 state.gauge_canvas.width = width;
5099 state.gauge_canvas.height = height;
5100 state.element_chart.appendChild(state.gauge_canvas);
5102 var valuefontsize = Math.floor(height / 6);
5103 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5104 state.gaugeChartLabel = document.createElement('span');
5105 state.gaugeChartLabel.className = 'gaugeChartLabel';
5106 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5107 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5108 state.element_chart.appendChild(state.gaugeChartLabel);
5110 var titlefontsize = Math.round(valuefontsize / 2);
5112 state.gaugeChartTitle = document.createElement('span');
5113 state.gaugeChartTitle.className = 'gaugeChartTitle';
5114 state.gaugeChartTitle.innerHTML = state.title;
5115 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5116 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5117 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5118 state.element_chart.appendChild(state.gaugeChartTitle);
5120 var unitfontsize = Math.round(titlefontsize * 0.9);
5121 state.gaugeChartUnits = document.createElement('span');
5122 state.gaugeChartUnits.className = 'gaugeChartUnits';
5123 state.gaugeChartUnits.innerHTML = state.units;
5124 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5125 state.element_chart.appendChild(state.gaugeChartUnits);
5127 state.gaugeChartMin = document.createElement('span');
5128 state.gaugeChartMin.className = 'gaugeChartMin';
5129 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5130 state.element_chart.appendChild(state.gaugeChartMin);
5132 state.gaugeChartMax = document.createElement('span');
5133 state.gaugeChartMax.className = 'gaugeChartMax';
5134 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5135 state.element_chart.appendChild(state.gaugeChartMax);
5137 // when we just re-create the chart
5138 // do not animate the first update
5140 if(typeof state.gauge_instance !== 'undefined')
5143 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5145 state.___gaugeOld__ = {
5154 // we will always feed a percentage
5155 state.gauge_instance.minValue = 0;
5156 state.gauge_instance.maxValue = 100;
5158 NETDATA.gaugeAnimation(state, animate);
5159 NETDATA.gaugeSet(state, value, 0, max);
5160 NETDATA.gaugeSetLabels(state, value, 0, max);
5161 NETDATA.gaugeAnimation(state, true);
5165 // ----------------------------------------------------------------------------------------------------------------
5166 // Charts Libraries Registration
5168 NETDATA.chartLibraries = {
5170 initialize: NETDATA.dygraphInitialize,
5171 create: NETDATA.dygraphChartCreate,
5172 update: NETDATA.dygraphChartUpdate,
5173 resize: function(state) {
5174 if(typeof state.dygraph_instance.resize === 'function')
5175 state.dygraph_instance.resize();
5177 setSelection: NETDATA.dygraphSetSelection,
5178 clearSelection: NETDATA.dygraphClearSelection,
5179 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5182 format: function(state) { return 'json'; },
5183 options: function(state) { return 'ms|flip'; },
5184 legend: function(state) {
5185 if(this.isSparkline(state) === false)
5186 return 'right-side';
5190 autoresize: function(state) { return true; },
5191 max_updates_to_recreate: function(state) { return 5000; },
5192 track_colors: function(state) { return true; },
5193 pixels_per_point: function(state) {
5194 if(this.isSparkline(state) === false)
5200 isSparkline: function(state) {
5201 if(typeof state.dygraph_sparkline === 'undefined') {
5202 var t = $(state.element).data('dygraph-theme');
5203 if(t === 'sparkline')
5204 state.dygraph_sparkline = true;
5206 state.dygraph_sparkline = false;
5208 return state.dygraph_sparkline;
5212 initialize: NETDATA.sparklineInitialize,
5213 create: NETDATA.sparklineChartCreate,
5214 update: NETDATA.sparklineChartUpdate,
5216 setSelection: undefined, // function(state, t) { return true; },
5217 clearSelection: undefined, // function(state) { return true; },
5218 toolboxPanAndZoom: null,
5221 format: function(state) { return 'array'; },
5222 options: function(state) { return 'flip|abs'; },
5223 legend: function(state) { return null; },
5224 autoresize: function(state) { return false; },
5225 max_updates_to_recreate: function(state) { return 5000; },
5226 track_colors: function(state) { return false; },
5227 pixels_per_point: function(state) { return 3; }
5230 initialize: NETDATA.peityInitialize,
5231 create: NETDATA.peityChartCreate,
5232 update: NETDATA.peityChartUpdate,
5234 setSelection: undefined, // function(state, t) { return true; },
5235 clearSelection: undefined, // function(state) { return true; },
5236 toolboxPanAndZoom: null,
5239 format: function(state) { return 'ssvcomma'; },
5240 options: function(state) { return 'null2zero|flip|abs'; },
5241 legend: function(state) { return null; },
5242 autoresize: function(state) { return false; },
5243 max_updates_to_recreate: function(state) { return 5000; },
5244 track_colors: function(state) { return false; },
5245 pixels_per_point: function(state) { return 3; }
5248 initialize: NETDATA.morrisInitialize,
5249 create: NETDATA.morrisChartCreate,
5250 update: NETDATA.morrisChartUpdate,
5252 setSelection: undefined, // function(state, t) { return true; },
5253 clearSelection: undefined, // function(state) { return true; },
5254 toolboxPanAndZoom: null,
5257 format: function(state) { return 'json'; },
5258 options: function(state) { return 'objectrows|ms'; },
5259 legend: function(state) { return null; },
5260 autoresize: function(state) { return false; },
5261 max_updates_to_recreate: function(state) { return 50; },
5262 track_colors: function(state) { return false; },
5263 pixels_per_point: function(state) { return 15; }
5266 initialize: NETDATA.googleInitialize,
5267 create: NETDATA.googleChartCreate,
5268 update: NETDATA.googleChartUpdate,
5270 setSelection: undefined, //function(state, t) { return true; },
5271 clearSelection: undefined, //function(state) { return true; },
5272 toolboxPanAndZoom: null,
5275 format: function(state) { return 'datatable'; },
5276 options: function(state) { return ''; },
5277 legend: function(state) { return null; },
5278 autoresize: function(state) { return false; },
5279 max_updates_to_recreate: function(state) { return 300; },
5280 track_colors: function(state) { return false; },
5281 pixels_per_point: function(state) { return 4; }
5284 initialize: NETDATA.raphaelInitialize,
5285 create: NETDATA.raphaelChartCreate,
5286 update: NETDATA.raphaelChartUpdate,
5288 setSelection: undefined, // function(state, t) { return true; },
5289 clearSelection: undefined, // function(state) { return true; },
5290 toolboxPanAndZoom: null,
5293 format: function(state) { return 'json'; },
5294 options: function(state) { return ''; },
5295 legend: function(state) { return null; },
5296 autoresize: function(state) { return false; },
5297 max_updates_to_recreate: function(state) { return 5000; },
5298 track_colors: function(state) { return false; },
5299 pixels_per_point: function(state) { return 3; }
5302 initialize: NETDATA.c3Initialize,
5303 create: NETDATA.c3ChartCreate,
5304 update: NETDATA.c3ChartUpdate,
5306 setSelection: undefined, // function(state, t) { return true; },
5307 clearSelection: undefined, // function(state) { return true; },
5308 toolboxPanAndZoom: null,
5311 format: function(state) { return 'csvjsonarray'; },
5312 options: function(state) { return 'milliseconds'; },
5313 legend: function(state) { return null; },
5314 autoresize: function(state) { return false; },
5315 max_updates_to_recreate: function(state) { return 5000; },
5316 track_colors: function(state) { return false; },
5317 pixels_per_point: function(state) { return 15; }
5320 initialize: NETDATA.d3Initialize,
5321 create: NETDATA.d3ChartCreate,
5322 update: NETDATA.d3ChartUpdate,
5324 setSelection: undefined, // function(state, t) { return true; },
5325 clearSelection: undefined, // function(state) { return true; },
5326 toolboxPanAndZoom: null,
5329 format: function(state) { return 'json'; },
5330 options: function(state) { return ''; },
5331 legend: function(state) { return null; },
5332 autoresize: function(state) { return false; },
5333 max_updates_to_recreate: function(state) { return 5000; },
5334 track_colors: function(state) { return false; },
5335 pixels_per_point: function(state) { return 3; }
5338 initialize: NETDATA.easypiechartInitialize,
5339 create: NETDATA.easypiechartChartCreate,
5340 update: NETDATA.easypiechartChartUpdate,
5342 setSelection: NETDATA.easypiechartSetSelection,
5343 clearSelection: NETDATA.easypiechartClearSelection,
5344 toolboxPanAndZoom: null,
5347 format: function(state) { return 'array'; },
5348 options: function(state) { return 'absolute'; },
5349 legend: function(state) { return null; },
5350 autoresize: function(state) { return false; },
5351 max_updates_to_recreate: function(state) { return 5000; },
5352 track_colors: function(state) { return true; },
5353 pixels_per_point: function(state) { return 3; },
5357 initialize: NETDATA.gaugeInitialize,
5358 create: NETDATA.gaugeChartCreate,
5359 update: NETDATA.gaugeChartUpdate,
5361 setSelection: NETDATA.gaugeSetSelection,
5362 clearSelection: NETDATA.gaugeClearSelection,
5363 toolboxPanAndZoom: null,
5366 format: function(state) { return 'array'; },
5367 options: function(state) { return 'absolute'; },
5368 legend: function(state) { return null; },
5369 autoresize: function(state) { return false; },
5370 max_updates_to_recreate: function(state) { return 5000; },
5371 track_colors: function(state) { return true; },
5372 pixels_per_point: function(state) { return 3; },
5377 NETDATA.registerChartLibrary = function(library, url) {
5378 if(NETDATA.options.debug.libraries === true)
5379 console.log("registering chart library: " + library);
5381 NETDATA.chartLibraries[library].url = url;
5382 NETDATA.chartLibraries[library].initialized = true;
5383 NETDATA.chartLibraries[library].enabled = true;
5386 // ----------------------------------------------------------------------------------------------------------------
5387 // Load required JS libraries and CSS
5389 NETDATA.requiredJs = [
5391 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5392 isAlreadyLoaded: function() {
5393 // check if bootstrap is loaded
5394 if(typeof $().emulateTransitionEnd == 'function')
5397 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5405 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5406 isAlreadyLoaded: function() { return false; }
5409 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5410 isAlreadyLoaded: function() { return false; }
5414 NETDATA.requiredCSS = [
5416 url: NETDATA.themes.current.bootstrap_css,
5417 isAlreadyLoaded: function() {
5418 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5425 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5426 isAlreadyLoaded: function() { return false; }
5429 url: NETDATA.themes.current.dashboard_css,
5430 isAlreadyLoaded: function() { return false; }
5433 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5434 isAlreadyLoaded: function() { return false; }
5438 NETDATA.loadRequiredJs = function(index, callback) {
5439 if(index >= NETDATA.requiredJs.length) {
5440 if(typeof callback === 'function')
5445 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5446 NETDATA.loadRequiredJs(++index, callback);
5450 if(NETDATA.options.debug.main_loop === true)
5451 console.log('loading ' + NETDATA.requiredJs[index].url);
5454 url: NETDATA.requiredJs[index].url,
5458 .success(function() {
5459 if(NETDATA.options.debug.main_loop === true)
5460 console.log('loaded ' + NETDATA.requiredJs[index].url);
5462 NETDATA.loadRequiredJs(++index, callback);
5465 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5469 NETDATA.loadRequiredCSS = function(index) {
5470 if(index >= NETDATA.requiredCSS.length)
5473 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5474 NETDATA.loadRequiredCSS(++index);
5478 if(NETDATA.options.debug.main_loop === true)
5479 console.log('loading ' + NETDATA.requiredCSS[index].url);
5481 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5482 NETDATA.loadRequiredCSS(++index);
5486 // ----------------------------------------------------------------------------------------------------------------
5487 // Registry of netdata hosts
5489 NETDATA.registry = {
5496 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5499 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5501 NETDATA.registry.server = data.registry;
5502 NETDATA.registry.machine_guid = data.machine_guid;
5503 NETDATA.registry.hostname = data.hostname;
5505 NETDATA.registry.access(10, function (person_urls) {
5506 NETDATA.registry.parsePersonUrls(person_urls);
5513 parsePersonUrls: function(person_urls) {
5515 NETDATA.registry.urls = {};
5517 // sort based on the timestamp of the last access
5518 function pu_comparator_asc(a, b) {
5519 if (a[2] < b[2]) return -1;
5520 if (a[2] > b[2]) return 1;
5524 var apu = person_urls.sort(pu_comparator_asc);
5525 var len = apu.length;
5527 if(typeof NETDATA.registry.urls[apu[len][0]] === 'undefined') {
5528 NETDATA.registry.urls[apu[len][0]] = {
5531 last_t: apu[len][2],
5532 accesses: apu[len][3],
5534 alternate_urls: new Array()
5538 NETDATA.registry.urls[apu[len][0]].alternate_urls.push(apu[len][1]);
5542 if(typeof netdataRegistryCallback === 'function')
5543 netdataRegistryCallback(NETDATA.registry.urls);
5546 hello: function(host, callback) {
5547 // send HELLO to a netdata server:
5548 // 1. verifies the server is reachable
5549 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5551 url: host + '/api/v1/registry?action=hello',
5554 xhrFields: { withCredentials: true } // required for the cookie
5556 .done(function(data) {
5557 if(typeof data.status !== 'string' || data.status !== 'ok') {
5558 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5562 if(typeof callback === 'function')
5566 NETDATA.error(407, host);
5568 if(typeof callback === 'function')
5573 access: function(max_redirects, callback) {
5574 // send ACCESS to a netdata registry:
5575 // 1. it let it know we are accessing a netdata server (in the URL)
5576 // 2. it responds with a list of netdata servers we know
5577 // the registry identifies us using a cookie it sets the first time we access it
5579 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),
5582 xhrFields: { withCredentials: true } // required for the cookie
5584 .done(function(data) {
5585 var redirect = null;
5586 if(typeof data.registry === 'string')
5587 redirect = data.registry;
5589 if(typeof data.status !== 'string' || data.status !== 'ok')
5592 if(data === null && redirect !== null && max_redirects > 0) {
5593 NETDATA.registry.access(max_redirects - 1, callback);
5595 else if(data === null) {
5596 NETDATA.error(409, NETDATA.registry.server + ' response: ' + JSON.stringify(data));
5597 if(typeof callback === 'function')
5601 if(typeof callback === 'function')
5602 callback(data.urls);
5606 NETDATA.error(410, NETDATA.registry.server);
5607 if(typeof callback === 'function')
5613 // ----------------------------------------------------------------------------------------------------------------
5616 NETDATA.errorReset();
5617 NETDATA.loadRequiredCSS(0);
5619 NETDATA._loadjQuery(function() {
5620 NETDATA.loadRequiredJs(0, function() {
5621 if(typeof $().emulateTransitionEnd !== 'function') {
5622 // bootstrap is not available
5623 NETDATA.options.current.show_help = false;
5626 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5627 if(NETDATA.options.debug.main_loop === true)
5628 console.log('starting chart refresh thread');
5635 // window.NETDATA = NETDATA;
5636 // })(window, document);