1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataNoRegistry = true; // Don't update the registry for this access
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = true; // enable/disable help
19 // var netdataShowAlarms = true; // enable/disable help
21 // You can also set the default netdata server, using the following.
22 // When this variable is not set, we assume the page is hosted on your
23 // netdata server already.
24 // var netdataServer = "http://yourhost:19999"; // set your NetData server
26 //(function(window, document, undefined) {
28 // ------------------------------------------------------------------------
29 // compatibility fixes
31 // fix IE issue with console
32 if(!window.console) { window.console = { log: function(){} }; }
34 // if string.endsWith is not defined, define it
35 if(typeof String.prototype.endsWith !== 'function') {
36 String.prototype.endsWith = function(s) {
37 if(s.length > this.length) return false;
38 return this.slice(-s.length) === s;
42 // if string.startsWith is not defined, define it
43 if(typeof String.prototype.startsWith !== 'function') {
44 String.prototype.startsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(s.length) === s;
51 var NETDATA = window.NETDATA || {};
53 // ----------------------------------------------------------------------------------------------------------------
54 // Detect the netdata server
56 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
57 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
58 NETDATA._scriptSource = function() {
61 if(typeof document.currentScript !== 'undefined') {
62 script = document.currentScript;
65 var all_scripts = document.getElementsByTagName('script');
66 script = all_scripts[all_scripts.length - 1];
69 if (typeof script.getAttribute.length !== 'undefined')
72 script = script.getAttribute('src', -1);
77 if(typeof netdataServer !== 'undefined')
78 NETDATA.serverDefault = netdataServer;
80 var s = NETDATA._scriptSource();
81 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
83 console.log('WARNING: Cannot detect the URL of the netdata server.');
84 NETDATA.serverDefault = null;
88 if(NETDATA.serverDefault === null)
89 NETDATA.serverDefault = '';
90 else if(NETDATA.serverDefault.slice(-1) !== '/')
91 NETDATA.serverDefault += '/';
93 // default URLs for all the external files we need
94 // make them RELATIVE so that the whole thing can also be
95 // installed under a web server
96 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
97 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
98 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
99 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
100 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
101 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
102 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
103 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
104 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
105 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
106 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
107 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
108 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
109 NETDATA.google_js = 'https://www.google.com/jsapi';
113 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
114 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
115 background: '#FFFFFF',
116 foreground: '#000000',
119 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
120 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
121 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
122 '#329262', '#3B3EAC' ],
123 easypiechart_track: '#f0f0f0',
124 easypiechart_scale: '#dfe0e0',
125 gauge_pointer: '#C0C0C0',
126 gauge_stroke: '#F0F0F0',
127 gauge_gradient: false
130 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
131 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
132 background: '#272b30',
133 foreground: '#C8C8C8',
136 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
137 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
138 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
139 '#a6a479', '#a66da8' ],
141 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
142 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
143 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
144 '#329262', '#3B3EFF' ],
145 easypiechart_track: '#373b40',
146 easypiechart_scale: '#373b40',
147 gauge_pointer: '#474b50',
148 gauge_stroke: '#373b40',
149 gauge_gradient: false
153 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
154 NETDATA.themes.current = NETDATA.themes[netdataTheme];
156 NETDATA.themes.current = NETDATA.themes.white;
158 if(typeof netdataShowHelp === 'undefined')
159 netdataShowHelp = true;
161 if(typeof netdataShowAlarms === 'undefined')
162 netdataShowAlarms = true;
164 NETDATA.colors = NETDATA.themes.current.colors;
166 // these are the colors Google Charts are using
167 // we have them here to attempt emulate their look and feel on the other chart libraries
168 // http://there4.io/2012/05/02/google-chart-color-list/
169 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
170 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
171 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
173 // an alternative set
174 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
175 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
176 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
178 // ----------------------------------------------------------------------------------------------------------------
179 // the defaults for all charts
181 // if the user does not specify any of these, the following will be used
183 NETDATA.chartDefaults = {
184 host: NETDATA.serverDefault, // the server to get data from
185 width: '100%', // the chart width - can be null
186 height: '100%', // the chart height - can be null
187 min_width: null, // the chart minimum width - can be null
188 library: 'dygraph', // the graphing library to use
189 method: 'average', // the grouping method
190 before: 0, // panning
191 after: -600, // panning
192 pixels_per_point: 1, // the detail of the chart
193 fill_luminance: 0.8 // luminance of colors in solit areas
196 // ----------------------------------------------------------------------------------------------------------------
200 pauseCallback: null, // a callback when we are really paused
202 pause: false, // when enabled we don't auto-refresh the charts
204 targets: null, // an array of all the state objects that are
205 // currently active (independently of their
206 // viewport visibility)
208 updated_dom: true, // when true, the DOM has been updated with
209 // new elements we have to check.
211 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
212 // rendering charts continiously.
213 // used with .current.fast_render_timeframe
215 page_is_visible: true, // when true, this page is visible
217 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
218 // auto-refresher for some time (when a chart is
219 // performing pan or zoom, we need to stop refreshing
220 // all other charts, to have the maximum speed for
221 // rendering the chart that is panned or zoomed).
222 // Used with .current.global_pan_sync_time
224 last_resized: new Date().getTime(), // the timestamp of the last resize request
226 last_page_scroll: 0, // the timestamp the last time the page was scrolled
228 // the current profile
229 // we may have many...
231 pixels_per_point: 1, // the minimum pixels per point for all charts
232 // increase this to speed javascript up
233 // each chart library has its own limit too
234 // the max of this and the chart library is used
235 // the final is calculated every time, so a change
236 // here will have immediate effect on the next chart
239 idle_between_charts: 100, // ms - how much time to wait between chart updates
241 fast_render_timeframe: 200, // ms - render continously until this time of continious
242 // rendering has been reached
243 // this setting is used to make it render e.g. 10
244 // charts at once, sleep idle_between_charts time
245 // and continue for another 10 charts.
247 idle_between_loops: 500, // ms - if all charts have been updated, wait this
248 // time before starting again.
250 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
252 idle_lost_focus: 500, // ms - when the window does not have focus, check
253 // if focus has been regained, every this time
255 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
256 // autorefreshing of charts is paused for this amount
259 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
260 // of time before setting up synchronized selections
263 sync_selection: true, // enable or disable selection sync
265 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
267 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
269 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
271 update_only_visible: true, // enable or disable visibility management
273 parallel_refresher: true, // enable parallel refresh of charts
275 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
277 destroy_on_hide: false, // destroy charts when they are not visible
279 show_help: netdataShowHelp, // when enabled the charts will show some help
280 show_help_delay_show_ms: 500,
281 show_help_delay_hide_ms: 0,
283 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
285 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
286 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
288 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
290 smooth_plot: true, // enable smooth plot, where possible
292 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
294 color_fill_opacity_line: 1.0,
295 color_fill_opacity_area: 0.2,
296 color_fill_opacity_stacked: 0.8,
298 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
299 pan_and_zoom_factor_multiplier_control: 2.0,
300 pan_and_zoom_factor_multiplier_shift: 3.0,
301 pan_and_zoom_factor_multiplier_alt: 4.0,
303 setOptionCallback: function() { ; }
311 chart_data_url: false,
312 chart_errors: false, // FIXME
320 NETDATA.statistics = {
323 refreshes_active_max: 0
327 // ----------------------------------------------------------------------------------------------------------------
328 // local storage options
330 NETDATA.localStorage = {
333 callback: {} // only used for resetting back to defaults
336 NETDATA.localStorageGet = function(key, def, callback) {
339 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
340 NETDATA.localStorage.default[key.toString()] = def;
341 NETDATA.localStorage.callback[key.toString()] = callback;
344 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
346 // console.log('localStorage: loading "' + key.toString() + '"');
347 ret = localStorage.getItem(key.toString());
348 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
349 if(ret === null || ret === 'undefined') {
350 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
351 localStorage.setItem(key.toString(), JSON.stringify(def));
355 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
356 ret = JSON.parse(ret);
357 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
361 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
366 if(typeof ret === 'undefined' || ret === 'undefined') {
367 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
371 NETDATA.localStorage.current[key.toString()] = ret;
375 NETDATA.localStorageSet = function(key, value, callback) {
376 if(typeof value === 'undefined' || value === 'undefined') {
377 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
380 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
381 NETDATA.localStorage.default[key.toString()] = value;
382 NETDATA.localStorage.current[key.toString()] = value;
383 NETDATA.localStorage.callback[key.toString()] = callback;
386 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
387 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
389 localStorage.setItem(key.toString(), JSON.stringify(value));
392 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
396 NETDATA.localStorage.current[key.toString()] = value;
400 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
402 if(typeof obj[i] === 'object') {
403 //console.log('object ' + prefix + '.' + i.toString());
404 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
408 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
412 NETDATA.setOption = function(key, value) {
413 if(key.toString() === 'setOptionCallback') {
414 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
415 NETDATA.options.current[key.toString()] = value;
416 NETDATA.options.current.setOptionCallback();
419 else if(NETDATA.options.current[key.toString()] !== value) {
420 var name = 'options.' + key.toString();
422 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
423 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
425 //console.log(NETDATA.localStorage);
426 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
427 //console.log(NETDATA.options);
428 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
430 if(typeof NETDATA.options.current.setOptionCallback === 'function')
431 NETDATA.options.current.setOptionCallback();
437 NETDATA.getOption = function(key) {
438 return NETDATA.options.current[key.toString()];
441 // read settings from local storage
442 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
444 // always start with this option enabled.
445 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
447 NETDATA.resetOptions = function() {
448 for(var i in NETDATA.localStorage.default) {
449 var a = i.split('.');
451 if(a[0] === 'options') {
452 if(a[1] === 'setOptionCallback') continue;
453 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
454 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
456 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
458 else if(a[0] === 'chart_heights') {
459 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
460 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
466 // ----------------------------------------------------------------------------------------------------------------
468 if(NETDATA.options.debug.main_loop === true)
469 console.log('welcome to NETDATA');
471 NETDATA.onresize = function() {
472 NETDATA.options.last_resized = new Date().getTime();
476 NETDATA.onscroll = function() {
477 // console.log('onscroll');
479 NETDATA.options.last_page_scroll = new Date().getTime();
480 NETDATA.options.auto_refresher_stop_until = 0;
482 if(NETDATA.options.targets === null) return;
484 // when the user scrolls he sees that we have
485 // hidden all the not-visible charts
486 // using this little function we try to switch
487 // the charts back to visible quickly
488 var targets = NETDATA.options.targets;
489 var len = targets.length;
491 if(targets[len]._updating === true) {
492 if (typeof targets[len].xhr !== 'undefined') {
493 targets[len].xhr.abort();
494 targets[len].running = false;
495 targets[len]._updating = false;
497 targets[len].isVisible();
502 window.onresize = NETDATA.onresize;
503 window.onscroll = NETDATA.onscroll;
505 // ----------------------------------------------------------------------------------------------------------------
508 NETDATA.errorCodes = {
509 100: { message: "Cannot load chart library", alert: true },
510 101: { message: "Cannot load jQuery", alert: true },
511 402: { message: "Chart library not found", alert: false },
512 403: { message: "Chart library not enabled/is failed", alert: false },
513 404: { message: "Chart not found", alert: false },
514 405: { message: "Cannot download charts index from server", alert: true },
515 406: { message: "Invalid charts index downloaded from server", alert: true },
516 407: { message: "Cannot HELLO netdata server", alert: false },
517 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
518 409: { message: "Cannot ACCESS netdata registry", alert: false },
519 410: { message: "Netdata registry ACCESS failed", alert: false },
520 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
521 412: { message: "Netdata registry DELETE failed", alert: false },
522 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
523 414: { message: "Netdata registry SWITCH failed", alert: false },
524 415: { message: "Netdata alarms download failed", alert: false },
525 416: { message: "Netdata alarms log download failed", alert: false }
527 NETDATA.errorLast = {
533 NETDATA.error = function(code, msg) {
534 NETDATA.errorLast.code = code;
535 NETDATA.errorLast.message = msg;
536 NETDATA.errorLast.datetime = new Date().getTime();
538 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
541 if(typeof netdataErrorCallback === 'function') {
542 ret = netdataErrorCallback('system', code, msg);
545 if(ret && NETDATA.errorCodes[code].alert)
546 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
549 NETDATA.errorReset = function() {
550 NETDATA.errorLast.code = 0;
551 NETDATA.errorLast.message = "You are doing fine!";
552 NETDATA.errorLast.datetime = 0;
555 // ----------------------------------------------------------------------------------------------------------------
558 // When multiple charts need the same chart, we avoid downloading it
559 // multiple times (and having it in browser memory multiple time)
560 // by using this registry.
562 // Every time we download a chart definition, we save it here with .add()
563 // Then we try to get it back with .get(). If that fails, we download it.
565 NETDATA.chartRegistry = {
568 fixid: function(id) {
569 return id.replace(/:/g, "_").replace(/\//g, "_");
572 add: function(host, id, data) {
573 host = this.fixid(host);
576 if(typeof this.charts[host] === 'undefined')
577 this.charts[host] = {};
579 //console.log('added ' + host + '/' + id);
580 this.charts[host][id] = data;
583 get: function(host, id) {
584 host = this.fixid(host);
587 if(typeof this.charts[host] === 'undefined')
590 if(typeof this.charts[host][id] === 'undefined')
593 //console.log('cached ' + host + '/' + id);
594 return this.charts[host][id];
597 downloadAll: function(host, callback) {
598 while(host.slice(-1) === '/')
599 host = host.substring(0, host.length - 1);
604 url: host + '/api/v1/charts',
607 xhrFields: { withCredentials: true } // required for the cookie
609 .done(function(data) {
611 var h = NETDATA.chartRegistry.fixid(host);
612 self.charts[h] = data.charts;
614 else NETDATA.error(406, host + '/api/v1/charts');
616 if(typeof callback === 'function')
620 NETDATA.error(405, host + '/api/v1/charts');
622 if(typeof callback === 'function')
628 // ----------------------------------------------------------------------------------------------------------------
629 // Global Pan and Zoom on charts
631 // Using this structure are synchronize all the charts, so that
632 // when you pan or zoom one, all others are automatically refreshed
633 // to the same timespan.
635 NETDATA.globalPanAndZoom = {
636 seq: 0, // timestamp ms
637 // every time a chart is panned or zoomed
638 // we set the timestamp here
639 // then we use it as a sequence number
640 // to find if other charts are syncronized
643 master: null, // the master chart (state), to which all others
646 force_before_ms: null, // the timespan to sync all other charts
647 force_after_ms: null,
652 setMaster: function(state, after, before) {
653 if(NETDATA.options.current.sync_pan_and_zoom === false)
656 if(this.master !== null && this.master !== state)
657 this.master.resetChart(true, true);
659 var now = new Date().getTime();
662 this.force_after_ms = after;
663 this.force_before_ms = before;
664 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
666 if(typeof this.callback === 'function')
667 this.callback(true, after, before);
671 clearMaster: function() {
672 if(this.master !== null) {
673 var st = this.master;
680 this.force_after_ms = null;
681 this.force_before_ms = null;
682 NETDATA.options.auto_refresher_stop_until = 0;
684 if(typeof this.callback === 'function')
685 this.callback(false, 0, 0);
688 // is the given state the master of the global
689 // pan and zoom sync?
690 isMaster: function(state) {
691 if(this.master === state) return true;
695 // are we currently have a global pan and zoom sync?
696 isActive: function() {
697 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
701 // check if a chart, other than the master
702 // needs to be refreshed, due to the global pan and zoom
703 shouldBeAutoRefreshed: function(state) {
704 if(this.master === null || this.seq === 0)
707 //if(state.needsRecreation())
710 if(state.tm.pan_and_zoom_seq === this.seq)
717 // ----------------------------------------------------------------------------------------------------------------
718 // dimensions selection
721 // move color assignment to dimensions, here
723 dimensionStatus = function(parent, label, name_div, value_div, color) {
724 this.enabled = false;
725 this.parent = parent;
727 this.name_div = null;
728 this.value_div = null;
729 this.color = NETDATA.themes.current.foreground;
731 if(parent.selected_count > parent.unselected_count)
732 this.selected = true;
734 this.selected = false;
736 this.setOptions(name_div, value_div, color);
739 dimensionStatus.prototype.invalidate = function() {
740 this.name_div = null;
741 this.value_div = null;
742 this.enabled = false;
745 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
748 if(this.name_div != name_div) {
749 this.name_div = name_div;
750 this.name_div.title = this.label;
751 this.name_div.style.color = this.color;
752 if(this.selected === false)
753 this.name_div.className = 'netdata-legend-name not-selected';
755 this.name_div.className = 'netdata-legend-name selected';
758 if(this.value_div != value_div) {
759 this.value_div = value_div;
760 this.value_div.title = this.label;
761 this.value_div.style.color = this.color;
762 if(this.selected === false)
763 this.value_div.className = 'netdata-legend-value not-selected';
765 this.value_div.className = 'netdata-legend-value selected';
772 dimensionStatus.prototype.setHandler = function() {
773 if(this.enabled === false) return;
777 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
778 this.name_div.onclick = this.value_div.onclick = function(e) {
780 if(ds.isSelected()) {
782 if(e.shiftKey === true || e.ctrlKey === true) {
783 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
786 if(ds.parent.countSelected() === 0)
787 ds.parent.selectAll();
790 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
791 if(ds.parent.countSelected() === 1) {
792 ds.parent.selectAll();
795 ds.parent.selectNone();
801 // this is not selected
802 if(e.shiftKey === true || e.ctrlKey === true) {
803 // control or shift key is pressed -> select this too
807 // no key is pressed -> select only this
808 ds.parent.selectNone();
813 ds.parent.state.redrawChart();
817 dimensionStatus.prototype.select = function() {
818 if(this.enabled === false) return;
820 this.name_div.className = 'netdata-legend-name selected';
821 this.value_div.className = 'netdata-legend-value selected';
822 this.selected = true;
825 dimensionStatus.prototype.unselect = function() {
826 if(this.enabled === false) return;
828 this.name_div.className = 'netdata-legend-name not-selected';
829 this.value_div.className = 'netdata-legend-value hidden';
830 this.selected = false;
833 dimensionStatus.prototype.isSelected = function() {
834 return(this.enabled === true && this.selected === true);
837 // ----------------------------------------------------------------------------------------------------------------
839 dimensionsVisibility = function(state) {
842 this.dimensions = {};
843 this.selected_count = 0;
844 this.unselected_count = 0;
847 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
848 if(typeof this.dimensions[label] === 'undefined') {
850 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
853 this.dimensions[label].setOptions(name_div, value_div, color);
855 return this.dimensions[label];
858 dimensionsVisibility.prototype.dimensionGet = function(label) {
859 return this.dimensions[label];
862 dimensionsVisibility.prototype.invalidateAll = function() {
863 for(var d in this.dimensions)
864 this.dimensions[d].invalidate();
867 dimensionsVisibility.prototype.selectAll = function() {
868 for(var d in this.dimensions)
869 this.dimensions[d].select();
872 dimensionsVisibility.prototype.countSelected = function() {
874 for(var d in this.dimensions)
875 if(this.dimensions[d].isSelected()) i++;
880 dimensionsVisibility.prototype.selectNone = function() {
881 for(var d in this.dimensions)
882 this.dimensions[d].unselect();
885 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
886 var ret = new Array();
887 this.selected_count = 0;
888 this.unselected_count = 0;
890 for(var i = 0, len = array.length; i < len ; i++) {
891 var ds = this.dimensions[array[i]];
892 if(typeof ds === 'undefined') {
893 // console.log(array[i] + ' is not found');
898 if(ds.isSelected()) {
900 this.selected_count++;
904 this.unselected_count++;
908 if(this.selected_count === 0 && this.unselected_count !== 0) {
910 return this.selected2BooleanArray(array);
917 // ----------------------------------------------------------------------------------------------------------------
918 // global selection sync
920 NETDATA.globalSelectionSync = {
927 if(this.state !== null)
928 this.state.globalSelectionSyncStop();
932 if(this.state !== null) {
933 this.state.globalSelectionSyncDelay();
938 // ----------------------------------------------------------------------------------------------------------------
939 // Our state object, where all per-chart values are stored
941 chartState = function(element) {
942 var self = $(element);
943 this.element = element;
946 // all private functions should use 'that', instead of 'this'
950 * show an error instead of the chart
952 var error = function(msg) {
955 if(typeof netdataErrorCallback === 'function') {
956 ret = netdataErrorCallback('chart', that.id, msg);
960 that.element.innerHTML = that.id + ': ' + msg;
961 that.enabled = false;
962 that.current = that.pan;
966 // GUID - a unique identifier for the chart
967 this.uuid = NETDATA.guid();
969 // string - the name of chart
970 this.id = self.data('netdata');
972 // string - the key for localStorage settings
973 this.settings_id = self.data('id') || null;
975 // the user given dimensions of the element
976 this.width = self.data('width') || NETDATA.chartDefaults.width;
977 this.height = self.data('height') || NETDATA.chartDefaults.height;
979 if(this.settings_id !== null) {
980 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
981 // this is the callback that will be called
982 // if and when the user resets all localStorage variables
985 resizeChartToHeight(height);
989 // string - the netdata server URL, without any path
990 this.host = self.data('host') || NETDATA.chartDefaults.host;
992 // make sure the host does not end with /
993 // all netdata API requests use absolute paths
994 while(this.host.slice(-1) === '/')
995 this.host = this.host.substring(0, this.host.length - 1);
997 // string - the grouping method requested by the user
998 this.method = self.data('method') || NETDATA.chartDefaults.method;
1000 // the time-range requested by the user
1001 this.after = self.data('after') || NETDATA.chartDefaults.after;
1002 this.before = self.data('before') || NETDATA.chartDefaults.before;
1004 // the pixels per point requested by the user
1005 this.pixels_per_point = self.data('pixels-per-point') || 1;
1006 this.points = self.data('points') || null;
1008 // the dimensions requested by the user
1009 this.dimensions = self.data('dimensions') || null;
1011 // the chart library requested by the user
1012 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1014 // object - the chart library used
1015 this.library = null;
1019 this.colors_assigned = {};
1020 this.colors_available = null;
1022 // the element already created by the user
1023 this.element_message = null;
1025 // the element with the chart
1026 this.element_chart = null;
1028 // the element with the legend of the chart (if created by us)
1029 this.element_legend = null;
1030 this.element_legend_childs = {
1040 this.chart_url = null; // string - the url to download chart info
1041 this.chart = null; // object - the chart as downloaded from the server
1043 this.title = self.data('title') || null; // the title of the chart
1044 this.units = self.data('units') || null; // the units of the chart dimensions
1045 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1047 this.running = false; // boolean - true when the chart is being refreshed now
1048 this.validated = false; // boolean - has the chart been validated?
1049 this.enabled = true; // boolean - is the chart enabled for refresh?
1050 this.paused = false; // boolean - is the chart paused for any reason?
1051 this.selected = false; // boolean - is the chart shown a selection?
1052 this.debug = false; // boolean - console.log() debug info about this chart
1054 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1055 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1056 this.requested_after = null; // milliseconds - the timestamp of the request after param
1057 this.requested_before = null; // milliseconds - the timestamp of the request before param
1058 this.requested_padding = null;
1059 this.view_after = 0;
1060 this.view_before = 0;
1065 force_update_at: 0, // the timestamp to force the update at
1066 force_before_ms: null,
1067 force_after_ms: null
1072 force_update_at: 0, // the timestamp to force the update at
1073 force_before_ms: null,
1074 force_after_ms: null
1079 force_update_at: 0, // the timestamp to force the update at
1080 force_before_ms: null,
1081 force_after_ms: null
1084 // this is a pointer to one of the sub-classes below
1086 this.current = this.auto;
1088 // check the requested library is available
1089 // we don't initialize it here - it will be initialized when
1090 // this chart will be first used
1091 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1092 NETDATA.error(402, that.library_name);
1093 error('chart library "' + that.library_name + '" is not found');
1096 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1097 NETDATA.error(403, that.library_name);
1098 error('chart library "' + that.library_name + '" is not enabled');
1102 that.library = NETDATA.chartLibraries[that.library_name];
1104 // milliseconds - the time the last refresh took
1105 this.refresh_dt_ms = 0;
1107 // if we need to report the rendering speed
1108 // find the element that needs to be updated
1109 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1111 if(refresh_dt_element_name !== null)
1112 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1114 this.refresh_dt_element = null;
1116 this.dimensions_visibility = new dimensionsVisibility(this);
1118 this._updating = false;
1120 // ============================================================================================================
1121 // PRIVATE FUNCTIONS
1123 var createDOM = function() {
1124 if(that.enabled === false) return;
1126 if(that.element_message !== null) that.element_message.innerHTML = '';
1127 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1128 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1130 that.element.innerHTML = '';
1132 that.element_message = document.createElement('div');
1133 that.element_message.className = ' netdata-message hidden';
1134 that.element.appendChild(that.element_message);
1136 that.element_chart = document.createElement('div');
1137 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1138 that.element.appendChild(that.element_chart);
1140 if(that.hasLegend() === true) {
1141 that.element.className = "netdata-container-with-legend";
1142 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1144 that.element_legend = document.createElement('div');
1145 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1146 that.element.appendChild(that.element_legend);
1149 that.element.className = "netdata-container";
1150 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1152 that.element_legend = null;
1154 that.element_legend_childs.series = null;
1156 if(typeof(that.width) === 'string')
1157 $(that.element).css('width', that.width);
1158 else if(typeof(that.width) === 'number')
1159 $(that.element).css('width', that.width + 'px');
1161 if(typeof(that.library.aspect_ratio) === 'undefined') {
1162 if(typeof(that.height) === 'string')
1163 $(that.element).css('height', that.height);
1164 else if(typeof(that.height) === 'number')
1165 $(that.element).css('height', that.height + 'px');
1168 var w = that.element.offsetWidth;
1169 if(w === null || w === 0) {
1170 // the div is hidden
1171 // this will resize the chart when next viewed
1172 that.tm.last_resized = 0;
1175 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1178 if(NETDATA.chartDefaults.min_width !== null)
1179 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1181 that.tm.last_dom_created = new Date().getTime();
1187 * initialize state variables
1188 * destroy all (possibly) created state elements
1189 * create the basic DOM for a chart
1191 var init = function() {
1192 if(that.enabled === false) return;
1194 that.paused = false;
1195 that.selected = false;
1197 that.chart_created = false; // boolean - is the library.create() been called?
1198 that.updates_counter = 0; // numeric - the number of refreshes made so far
1199 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1200 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1203 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1204 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1205 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1207 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1208 last_updated: 0, // the timestamp the chart last updated with data
1209 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1211 // Used with NETDATA.globalPanAndZoom.seq
1212 last_visible_check: 0, // the time we last checked if it is visible
1213 last_resized: 0, // the time the chart was resized
1214 last_hidden: 0, // the time the chart was hidden
1215 last_unhidden: 0, // the time the chart was unhidden
1216 last_autorefreshed: 0 // the time the chart was last refreshed
1219 that.data = null; // the last data as downloaded from the netdata server
1220 that.data_url = 'invalid://'; // string - the last url used to update the chart
1221 that.data_points = 0; // number - the number of points returned from netdata
1222 that.data_after = 0; // milliseconds - the first timestamp of the data
1223 that.data_before = 0; // milliseconds - the last timestamp of the data
1224 that.data_update_every = 0; // milliseconds - the frequency to update the data
1226 that.tm.last_initialized = new Date().getTime();
1229 that.setMode('auto');
1232 var maxMessageFontSize = function() {
1233 // normally we want a font size, as tall as the element
1234 var h = that.element_message.clientHeight;
1236 // but give it some air, 20% let's say, or 5 pixels min
1237 var lost = Math.max(h * 0.2, 5);
1240 // center the text, vertically
1241 var paddingTop = (lost - 5) / 2;
1243 // but check the width too
1244 // it should fit 10 characters in it
1245 var w = that.element_message.clientWidth / 10;
1247 paddingTop += (h - w) / 2;
1251 // and don't make it too huge
1252 // 5% of the screen size is good
1253 if(h > screen.height / 20) {
1254 paddingTop += (h - (screen.height / 20)) / 2;
1255 h = screen.height / 20;
1259 that.element_message.style.fontSize = h.toString() + 'px';
1260 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1263 var showMessage = function(msg) {
1264 that.element_message.className = 'netdata-message';
1265 that.element_message.innerHTML = msg;
1266 that.element_message.style.fontSize = 'x-small';
1267 that.element_message.style.paddingTop = '0px';
1268 that.___messageHidden___ = undefined;
1271 var showMessageIcon = function(icon) {
1272 that.element_message.innerHTML = icon;
1273 that.element_message.className = 'netdata-message icon';
1274 maxMessageFontSize();
1275 that.___messageHidden___ = undefined;
1278 var hideMessage = function() {
1279 if(typeof that.___messageHidden___ === 'undefined') {
1280 that.___messageHidden___ = true;
1281 that.element_message.className = 'netdata-message hidden';
1285 var showRendering = function() {
1287 if(that.chart !== null) {
1288 if(that.chart.chart_type === 'line')
1289 icon = '<i class="fa fa-line-chart"></i>';
1291 icon = '<i class="fa fa-area-chart"></i>';
1294 icon = '<i class="fa fa-area-chart"></i>';
1296 showMessageIcon(icon + ' netdata');
1299 var showLoading = function() {
1300 if(that.chart_created === false) {
1301 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1307 var isHidden = function() {
1308 if(typeof that.___chartIsHidden___ !== 'undefined')
1314 // hide the chart, when it is not visible - called from isVisible()
1315 var hideChart = function() {
1316 // hide it, if it is not already hidden
1317 if(isHidden() === true) return;
1319 if(that.chart_created === true) {
1320 if(NETDATA.options.current.destroy_on_hide === true) {
1321 // we should destroy it
1326 that.element_chart.style.display = 'none';
1327 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1328 that.tm.last_hidden = new Date().getTime();
1331 // This works, but I not sure there are no corner cases somewhere
1332 // so it is commented - if the user has memory issues he can
1333 // set Destroy on Hide for all charts
1334 // that.data = null;
1338 that.___chartIsHidden___ = true;
1341 // unhide the chart, when it is visible - called from isVisible()
1342 var unhideChart = function() {
1343 if(isHidden() === false) return;
1345 that.___chartIsHidden___ = undefined;
1346 that.updates_since_last_unhide = 0;
1348 if(that.chart_created === false) {
1349 // we need to re-initialize it, to show our background
1350 // logo in bootstrap tabs, until the chart loads
1354 that.tm.last_unhidden = new Date().getTime();
1355 that.element_chart.style.display = '';
1356 if(that.element_legend !== null) that.element_legend.style.display = '';
1362 var canBeRendered = function() {
1363 if(isHidden() === true || that.isVisible(true) === false)
1369 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1370 var callChartLibraryUpdateSafely = function(data) {
1373 if(canBeRendered() === false)
1376 if(NETDATA.options.debug.chart_errors === true)
1377 status = that.library.update(that, data);
1380 status = that.library.update(that, data);
1387 if(status === false) {
1388 error('chart failed to be updated as ' + that.library_name);
1395 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1396 var callChartLibraryCreateSafely = function(data) {
1399 if(canBeRendered() === false)
1402 if(NETDATA.options.debug.chart_errors === true)
1403 status = that.library.create(that, data);
1406 status = that.library.create(that, data);
1413 if(status === false) {
1414 error('chart failed to be created as ' + that.library_name);
1418 that.chart_created = true;
1419 that.updates_since_last_creation = 0;
1423 // ----------------------------------------------------------------------------------------------------------------
1426 // resizeChart() - private
1427 // to be called just before the chart library to make sure that
1428 // a properly sized dom is available
1429 var resizeChart = function() {
1430 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1431 if(that.chart_created === false) return;
1433 if(that.needsRecreation()) {
1436 else if(typeof that.library.resize === 'function') {
1437 that.library.resize(that);
1439 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1440 $(that.element_legend_childs.nano).nanoScroller();
1442 maxMessageFontSize();
1445 that.tm.last_resized = new Date().getTime();
1449 // this is the actual chart resize algorithm
1451 // - resize the entire container
1452 // - update the internal states
1453 // - resize the chart as the div changes height
1454 // - update the scrollbar of the legend
1455 var resizeChartToHeight = function(h) {
1457 that.element.style.height = h;
1459 if(that.settings_id !== null)
1460 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1462 var now = new Date().getTime();
1463 NETDATA.options.last_page_scroll = now;
1464 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1467 that.tm.last_resized = 0;
1471 this.resizeHandler = function(e) {
1474 if(typeof this.event_resize === 'undefined'
1475 || this.event_resize.chart_original_w === 'undefined'
1476 || this.event_resize.chart_original_h === 'undefined')
1477 this.event_resize = {
1478 chart_original_w: this.element.clientWidth,
1479 chart_original_h: this.element.clientHeight,
1483 if(e.type === 'touchstart') {
1484 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1485 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1488 this.event_resize.mouse_start_x = e.clientX;
1489 this.event_resize.mouse_start_y = e.clientY;
1492 this.event_resize.chart_start_w = this.element.clientWidth;
1493 this.event_resize.chart_start_h = this.element.clientHeight;
1494 this.event_resize.chart_last_w = this.element.clientWidth;
1495 this.event_resize.chart_last_h = this.element.clientHeight;
1497 var now = new Date().getTime();
1498 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1499 // double click / double tap event
1501 // the optimal height of the chart
1502 // showing the entire legend
1503 var optimal = this.event_resize.chart_last_h
1504 + this.element_legend_childs.content.scrollHeight
1505 - this.element_legend_childs.content.clientHeight;
1507 // if we are not optimal, be optimal
1508 if(this.event_resize.chart_last_h != optimal)
1509 resizeChartToHeight(optimal.toString() + 'px');
1511 // else if we do not have the original height
1512 // reset to the original height
1513 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1514 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1517 this.event_resize.last = now;
1519 // process movement event
1520 document.onmousemove =
1521 document.ontouchmove =
1522 this.element_legend_childs.resize_handler.onmousemove =
1523 this.element_legend_childs.resize_handler.ontouchmove =
1528 case 'mousemove': y = e.clientY; break;
1529 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1533 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1535 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1536 resizeChartToHeight(newH.toString() + 'px');
1537 that.event_resize.chart_last_h = newH;
1542 // process end event
1543 document.onmouseup =
1544 document.ontouchend =
1545 this.element_legend_childs.resize_handler.onmouseup =
1546 this.element_legend_childs.resize_handler.ontouchend =
1548 // remove all the hooks
1549 document.onmouseup =
1550 document.onmousemove =
1551 document.ontouchmove =
1552 document.ontouchend =
1553 that.element_legend_childs.resize_handler.onmousemove =
1554 that.element_legend_childs.resize_handler.ontouchmove =
1555 that.element_legend_childs.resize_handler.onmouseout =
1556 that.element_legend_childs.resize_handler.onmouseup =
1557 that.element_legend_childs.resize_handler.ontouchend =
1560 // allow auto-refreshes
1561 NETDATA.options.auto_refresher_stop_until = 0;
1567 var noDataToShow = function() {
1568 showMessageIcon('<i class="fa fa-warning"></i> empty');
1569 that.legendUpdateDOM();
1570 that.tm.last_autorefreshed = new Date().getTime();
1571 // that.data_update_every = 30 * 1000;
1572 //that.element_chart.style.display = 'none';
1573 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1574 //that.___chartIsHidden___ = true;
1577 // ============================================================================================================
1580 this.error = function(msg) {
1584 this.setMode = function(m) {
1585 if(this.current !== null && this.current.name === m) return;
1588 this.current = this.auto;
1589 else if(m === 'pan')
1590 this.current = this.pan;
1591 else if(m === 'zoom')
1592 this.current = this.zoom;
1594 this.current = this.auto;
1596 this.current.force_update_at = 0;
1597 this.current.force_before_ms = null;
1598 this.current.force_after_ms = null;
1600 this.tm.last_mode_switch = new Date().getTime();
1603 // ----------------------------------------------------------------------------------------------------------------
1604 // global selection sync
1606 // prevent to global selection sync for some time
1607 this.globalSelectionSyncDelay = function(ms) {
1608 if(NETDATA.options.current.sync_selection === false)
1611 if(typeof ms === 'number')
1612 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1614 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1617 // can we globally apply selection sync?
1618 this.globalSelectionSyncAbility = function() {
1619 if(NETDATA.options.current.sync_selection === false)
1622 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1628 this.globalSelectionSyncIsMaster = function() {
1629 if(NETDATA.globalSelectionSync.state === this)
1635 // this chart is the master of the global selection sync
1636 this.globalSelectionSyncBeMaster = function() {
1638 if(this.globalSelectionSyncIsMaster()) {
1639 if(this.debug === true)
1640 this.log('sync: I am the master already.');
1645 if(NETDATA.globalSelectionSync.state) {
1646 if(this.debug === true)
1647 this.log('sync: I am not the sync master. Resetting global sync.');
1649 this.globalSelectionSyncStop();
1652 // become the master
1653 if(this.debug === true)
1654 this.log('sync: becoming sync master.');
1656 this.selected = true;
1657 NETDATA.globalSelectionSync.state = this;
1659 // find the all slaves
1660 var targets = NETDATA.options.targets;
1661 var len = targets.length;
1666 if(this.debug === true)
1667 st.log('sync: not adding me to sync');
1669 else if(st.globalSelectionSyncIsEligible()) {
1670 if(this.debug === true)
1671 st.log('sync: adding to sync as slave');
1673 st.globalSelectionSyncBeSlave();
1677 // this.globalSelectionSyncDelay(100);
1680 // can the chart participate to the global selection sync as a slave?
1681 this.globalSelectionSyncIsEligible = function() {
1682 if(this.enabled === true
1683 && this.library !== null
1684 && typeof this.library.setSelection === 'function'
1685 && this.isVisible() === true
1686 && this.chart_created === true)
1692 // this chart becomes a slave of the global selection sync
1693 this.globalSelectionSyncBeSlave = function() {
1694 if(NETDATA.globalSelectionSync.state !== this)
1695 NETDATA.globalSelectionSync.slaves.push(this);
1698 // sync all the visible charts to the given time
1699 // this is to be called from the chart libraries
1700 this.globalSelectionSync = function(t) {
1701 if(this.globalSelectionSyncAbility() === false) {
1702 if(this.debug === true)
1703 this.log('sync: cannot sync (yet?).');
1708 if(this.globalSelectionSyncIsMaster() === false) {
1709 if(this.debug === true)
1710 this.log('sync: trying to be sync master.');
1712 this.globalSelectionSyncBeMaster();
1714 if(this.globalSelectionSyncAbility() === false) {
1715 if(this.debug === true)
1716 this.log('sync: cannot sync (yet?).');
1722 NETDATA.globalSelectionSync.last_t = t;
1723 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1728 // stop syncing all charts to the given time
1729 this.globalSelectionSyncStop = function() {
1730 if(NETDATA.globalSelectionSync.slaves.length) {
1731 if(this.debug === true)
1732 this.log('sync: cleaning up...');
1734 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1736 if(that.debug === true)
1737 st.log('sync: not adding me to sync stop');
1740 if(that.debug === true)
1741 st.log('sync: removed slave from sync');
1743 st.clearSelection();
1747 NETDATA.globalSelectionSync.last_t = 0;
1748 NETDATA.globalSelectionSync.slaves = [];
1749 NETDATA.globalSelectionSync.state = null;
1752 this.clearSelection();
1755 this.setSelection = function(t) {
1756 if(typeof this.library.setSelection === 'function') {
1757 if(this.library.setSelection(this, t) === true)
1758 this.selected = true;
1760 this.selected = false;
1762 else this.selected = true;
1764 if(this.selected === true && this.debug === true)
1765 this.log('selection set to ' + t.toString());
1767 return this.selected;
1770 this.clearSelection = function() {
1771 if(this.selected === true) {
1772 if(typeof this.library.clearSelection === 'function') {
1773 if(this.library.clearSelection(this) === true)
1774 this.selected = false;
1776 this.selected = true;
1778 else this.selected = false;
1780 if(this.selected === false && this.debug === true)
1781 this.log('selection cleared');
1786 return this.selected;
1789 // find if a timestamp (ms) is shown in the current chart
1790 this.timeIsVisible = function(t) {
1791 if(t >= this.data_after && t <= this.data_before)
1796 this.calculateRowForTime = function(t) {
1797 if(this.timeIsVisible(t) === false) return -1;
1798 return Math.floor((t - this.data_after) / this.data_update_every);
1801 // ----------------------------------------------------------------------------------------------------------------
1804 this.log = function(msg) {
1805 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1808 this.pauseChart = function() {
1809 if(this.paused === false) {
1810 if(this.debug === true)
1811 this.log('pauseChart()');
1817 this.unpauseChart = function() {
1818 if(this.paused === true) {
1819 if(this.debug === true)
1820 this.log('unpauseChart()');
1822 this.paused = false;
1826 this.resetChart = function(dont_clear_master, dont_update) {
1827 if(this.debug === true)
1828 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1830 if(typeof dont_clear_master === 'undefined')
1831 dont_clear_master = false;
1833 if(typeof dont_update === 'undefined')
1834 dont_update = false;
1836 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1837 if(this.debug === true)
1838 this.log('resetChart() diverting to clearMaster().');
1839 // this will call us back with master === true
1840 NETDATA.globalPanAndZoom.clearMaster();
1844 this.clearSelection();
1846 this.tm.pan_and_zoom_seq = 0;
1848 this.setMode('auto');
1849 this.current.force_update_at = 0;
1850 this.current.force_before_ms = null;
1851 this.current.force_after_ms = null;
1852 this.tm.last_autorefreshed = 0;
1853 this.paused = false;
1854 this.selected = false;
1855 this.enabled = true;
1856 // this.debug = false;
1858 // do not update the chart here
1859 // or the chart will flip-flop when it is the master
1860 // of a selection sync and another chart becomes
1863 if(dont_update !== true && this.isVisible() === true) {
1868 this.updateChartPanOrZoom = function(after, before) {
1869 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1872 if(this.debug === true)
1875 if(before < after) {
1876 if(this.debug === true)
1877 this.log(logme + 'flipped parameters, rejecting it.');
1882 if(typeof this.fixed_min_duration === 'undefined')
1883 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1885 var min_duration = this.fixed_min_duration;
1886 var current_duration = Math.round(this.view_before - this.view_after);
1888 // round the numbers
1889 after = Math.round(after);
1890 before = Math.round(before);
1892 // align them to update_every
1893 // stretching them further away
1894 after -= after % this.data_update_every;
1895 before += this.data_update_every - (before % this.data_update_every);
1897 // the final wanted duration
1898 var wanted_duration = before - after;
1900 // to allow panning, accept just a point below our minimum
1901 if((current_duration - this.data_update_every) < min_duration)
1902 min_duration = current_duration - this.data_update_every;
1904 // we do it, but we adjust to minimum size and return false
1905 // when the wanted size is below the current and the minimum
1907 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1908 if(this.debug === true)
1909 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1911 min_duration = this.fixed_min_duration;
1913 var dt = (min_duration - wanted_duration) / 2;
1916 wanted_duration = before - after;
1920 var tolerance = this.data_update_every * 2;
1921 var movement = Math.abs(before - this.view_before);
1923 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1924 if(this.debug === true)
1925 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);
1929 if(this.current.name === 'auto') {
1930 this.log(logme + 'caller called me with mode: ' + this.current.name);
1931 this.setMode('pan');
1934 if(this.debug === true)
1935 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);
1937 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1938 this.current.force_after_ms = after;
1939 this.current.force_before_ms = before;
1940 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1944 this.legendFormatValue = function(value) {
1945 if(value === null || value === 'undefined') return '-';
1946 if(typeof value !== 'number') return value;
1948 var abs = Math.abs(value);
1949 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1950 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1951 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1952 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1953 return (Math.round(value * 10000) / 10000).toLocaleString();
1956 this.legendSetLabelValue = function(label, value) {
1957 var series = this.element_legend_childs.series[label];
1958 if(typeof series === 'undefined') return;
1959 if(series.value === null && series.user === null) return;
1961 // if the value has not changed, skip DOM update
1962 //if(series.last === value) return;
1965 if(typeof value === 'number') {
1966 var v = Math.abs(value);
1967 s = r = this.legendFormatValue(value);
1969 if(typeof series.last === 'number') {
1970 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1971 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1972 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1974 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1979 series.last = value;
1982 if(series.value !== null) series.value.innerHTML = s;
1983 if(series.user !== null) series.user.innerHTML = r;
1986 this.legendSetDate = function(ms) {
1987 if(typeof ms !== 'number') {
1988 this.legendShowUndefined();
1992 var d = new Date(ms);
1994 if(this.element_legend_childs.title_date)
1995 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1997 if(this.element_legend_childs.title_time)
1998 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2000 if(this.element_legend_childs.title_units)
2001 this.element_legend_childs.title_units.innerHTML = this.units;
2004 this.legendShowUndefined = function() {
2005 if(this.element_legend_childs.title_date)
2006 this.element_legend_childs.title_date.innerHTML = ' ';
2008 if(this.element_legend_childs.title_time)
2009 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2011 if(this.element_legend_childs.title_units)
2012 this.element_legend_childs.title_units.innerHTML = ' ';
2014 if(this.data && this.element_legend_childs.series !== null) {
2015 var labels = this.data.dimension_names;
2016 var i = labels.length;
2018 var label = labels[i];
2020 if(typeof label === 'undefined') continue;
2021 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2022 this.legendSetLabelValue(label, null);
2027 this.legendShowLatestValues = function() {
2028 if(this.chart === null) return;
2029 if(this.selected) return;
2031 if(this.data === null || this.element_legend_childs.series === null) {
2032 this.legendShowUndefined();
2036 var show_undefined = true;
2037 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2038 show_undefined = false;
2040 if(show_undefined) {
2041 this.legendShowUndefined();
2045 this.legendSetDate(this.view_before);
2047 var labels = this.data.dimension_names;
2048 var i = labels.length;
2050 var label = labels[i];
2052 if(typeof label === 'undefined') continue;
2053 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2056 this.legendSetLabelValue(label, null);
2058 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2062 this.legendReset = function() {
2063 this.legendShowLatestValues();
2066 // this should be called just ONCE per dimension per chart
2067 this._chartDimensionColor = function(label) {
2068 if(this.colors === null) this.chartColors();
2070 if(typeof this.colors_assigned[label] === 'undefined') {
2071 if(this.colors_available.length === 0) {
2072 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2073 this.colors_available.push(NETDATA.themes.current.colors[i]);
2076 this.colors_assigned[label] = this.colors_available.shift();
2078 if(this.debug === true)
2079 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2082 if(this.debug === true)
2083 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2086 this.colors.push(this.colors_assigned[label]);
2087 return this.colors_assigned[label];
2090 this.chartColors = function() {
2091 if(this.colors !== null) return this.colors;
2093 this.colors = new Array();
2094 this.colors_available = new Array();
2097 var c = $(this.element).data('colors');
2098 // this.log('read colors: ' + c);
2099 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2100 if(typeof c !== 'string') {
2101 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2108 for(i = 0, len = c.length; i < len ; i++) {
2110 this.colors_available.push(c[i]);
2111 // this.log('adding color: ' + c[i]);
2117 // push all the standard colors too
2118 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2119 this.colors_available.push(NETDATA.themes.current.colors[i]);
2124 this.legendUpdateDOM = function() {
2127 // check that the legend DOM is up to date for the downloaded dimensions
2128 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2129 // this.log('the legend does not have any series - requesting legend update');
2132 else if(this.data === null) {
2133 // this.log('the chart does not have any data - requesting legend update');
2136 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2140 var labels = this.data.dimension_names.toString();
2141 if(labels !== this.element_legend_childs.series.labels_key) {
2144 if(this.debug === true)
2145 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2149 if(needed === false) {
2150 // make sure colors available
2153 // do we have to update the current values?
2154 // we do this, only when the visible chart is current
2155 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2156 if(this.debug === true)
2157 this.log('chart is in latest position... updating values on legend...');
2159 //var labels = this.data.dimension_names;
2160 //var i = labels.length;
2162 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2166 if(this.colors === null) {
2167 // this is the first time we update the chart
2168 // let's assign colors to all dimensions
2169 if(this.library.track_colors() === true)
2170 for(var dim in this.chart.dimensions)
2171 this._chartDimensionColor(this.chart.dimensions[dim].name);
2173 // we will re-generate the colors for the chart
2174 // based on the selected dimensions
2177 if(this.debug === true)
2178 this.log('updating Legend DOM');
2180 // mark all dimensions as invalid
2181 this.dimensions_visibility.invalidateAll();
2183 var genLabel = function(state, parent, dim, name, count) {
2184 var color = state._chartDimensionColor(name);
2186 var user_element = null;
2187 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2188 if(user_id !== null) {
2189 user_element = document.getElementById(user_id) || null;
2190 if(user_element === null)
2191 state.log('Cannot find element with id: ' + user_id);
2194 state.element_legend_childs.series[name] = {
2195 name: document.createElement('span'),
2196 value: document.createElement('span'),
2201 var label = state.element_legend_childs.series[name];
2203 // create the dimension visibility tracking for this label
2204 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2206 var rgb = NETDATA.colorHex2Rgb(color);
2207 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2208 + state.chart.chart_type
2209 + '" style="background-color: '
2210 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2211 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2213 var text = document.createTextNode(' ' + name);
2214 label.name.appendChild(text);
2217 parent.appendChild(document.createElement('br'));
2219 parent.appendChild(label.name);
2220 parent.appendChild(label.value);
2223 var content = document.createElement('div');
2225 if(this.hasLegend()) {
2226 this.element_legend_childs = {
2228 resize_handler: document.createElement('div'),
2229 toolbox: document.createElement('div'),
2230 toolbox_left: document.createElement('div'),
2231 toolbox_right: document.createElement('div'),
2232 toolbox_reset: document.createElement('div'),
2233 toolbox_zoomin: document.createElement('div'),
2234 toolbox_zoomout: document.createElement('div'),
2235 toolbox_volume: document.createElement('div'),
2236 title_date: document.createElement('span'),
2237 title_time: document.createElement('span'),
2238 title_units: document.createElement('span'),
2239 nano: document.createElement('div'),
2241 paneClass: 'netdata-legend-series-pane',
2242 sliderClass: 'netdata-legend-series-slider',
2243 contentClass: 'netdata-legend-series-content',
2244 enabledClass: '__enabled',
2245 flashedClass: '__flashed',
2246 activeClass: '__active',
2248 alwaysVisible: true,
2254 this.element_legend.innerHTML = '';
2256 if(this.library.toolboxPanAndZoom !== null) {
2258 function get_pan_and_zoom_step(event) {
2260 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2262 else if (event.shiftKey)
2263 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2265 else if (event.altKey)
2266 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2269 return NETDATA.options.current.pan_and_zoom_factor;
2272 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2273 this.element.appendChild(this.element_legend_childs.toolbox);
2275 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2276 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2277 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2278 this.element_legend_childs.toolbox_left.onclick = function(e) {
2281 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2282 var before = that.view_before - step;
2283 var after = that.view_after - step;
2284 if(after >= that.netdata_first)
2285 that.library.toolboxPanAndZoom(that, after, before);
2287 if(NETDATA.options.current.show_help === true)
2288 $(this.element_legend_childs.toolbox_left).popover({
2293 placement: 'bottom',
2294 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2296 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>'
2300 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2301 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2302 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2303 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2305 NETDATA.resetAllCharts(that);
2307 if(NETDATA.options.current.show_help === true)
2308 $(this.element_legend_childs.toolbox_reset).popover({
2313 placement: 'bottom',
2314 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2315 title: 'Chart Reset',
2316 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>'
2319 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2320 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2321 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2322 this.element_legend_childs.toolbox_right.onclick = function(e) {
2324 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2325 var before = that.view_before + step;
2326 var after = that.view_after + step;
2327 if(before <= that.netdata_last)
2328 that.library.toolboxPanAndZoom(that, after, before);
2330 if(NETDATA.options.current.show_help === true)
2331 $(this.element_legend_childs.toolbox_right).popover({
2336 placement: 'bottom',
2337 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2339 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>'
2343 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2344 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2345 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2346 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2348 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2349 var before = that.view_before - dt;
2350 var after = that.view_after + dt;
2351 that.library.toolboxPanAndZoom(that, after, before);
2353 if(NETDATA.options.current.show_help === true)
2354 $(this.element_legend_childs.toolbox_zoomin).popover({
2359 placement: 'bottom',
2360 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2361 title: 'Chart Zoom In',
2362 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>'
2365 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2366 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2367 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2368 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2370 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);
2371 var before = that.view_before + dt;
2372 var after = that.view_after - dt;
2374 that.library.toolboxPanAndZoom(that, after, before);
2376 if(NETDATA.options.current.show_help === true)
2377 $(this.element_legend_childs.toolbox_zoomout).popover({
2382 placement: 'bottom',
2383 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2384 title: 'Chart Zoom Out',
2385 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>'
2388 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2389 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2390 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2391 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2392 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2393 //e.preventDefault();
2394 //alert('clicked toolbox_volume on ' + that.id);
2398 this.element_legend_childs.toolbox = null;
2399 this.element_legend_childs.toolbox_left = null;
2400 this.element_legend_childs.toolbox_reset = null;
2401 this.element_legend_childs.toolbox_right = null;
2402 this.element_legend_childs.toolbox_zoomin = null;
2403 this.element_legend_childs.toolbox_zoomout = null;
2404 this.element_legend_childs.toolbox_volume = null;
2407 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2408 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2409 this.element.appendChild(this.element_legend_childs.resize_handler);
2410 if(NETDATA.options.current.show_help === true)
2411 $(this.element_legend_childs.resize_handler).popover({
2416 placement: 'bottom',
2417 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2418 title: 'Chart Resize',
2419 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>'
2423 this.element_legend_childs.resize_handler.onmousedown =
2425 that.resizeHandler(e);
2429 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2430 that.resizeHandler(e);
2433 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2434 this.element_legend.appendChild(this.element_legend_childs.title_date);
2436 this.element_legend.appendChild(document.createElement('br'));
2438 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2439 this.element_legend.appendChild(this.element_legend_childs.title_time);
2441 this.element_legend.appendChild(document.createElement('br'));
2443 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2444 this.element_legend.appendChild(this.element_legend_childs.title_units);
2446 this.element_legend.appendChild(document.createElement('br'));
2448 this.element_legend_childs.nano.className = 'netdata-legend-series';
2449 this.element_legend.appendChild(this.element_legend_childs.nano);
2451 content.className = 'netdata-legend-series-content';
2452 this.element_legend_childs.nano.appendChild(content);
2454 if(NETDATA.options.current.show_help === true)
2455 $(content).popover({
2460 placement: 'bottom',
2461 title: 'Chart Legend',
2462 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2463 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>'
2467 this.element_legend_childs = {
2469 resize_handler: null,
2472 toolbox_right: null,
2473 toolbox_reset: null,
2474 toolbox_zoomin: null,
2475 toolbox_zoomout: null,
2476 toolbox_volume: null,
2487 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2488 if(this.debug === true)
2489 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2491 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2492 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2496 var tmp = new Array();
2497 for(var dim in this.chart.dimensions) {
2498 tmp.push(this.chart.dimensions[dim].name);
2499 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2501 this.element_legend_childs.series.labels_key = tmp.toString();
2502 if(this.debug === true)
2503 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2506 // create a hidden div to be used for hidding
2507 // the original legend of the chart library
2508 var el = document.createElement('div');
2509 if(this.element_legend !== null)
2510 this.element_legend.appendChild(el);
2511 el.style.display = 'none';
2513 this.element_legend_childs.hidden = document.createElement('div');
2514 el.appendChild(this.element_legend_childs.hidden);
2516 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2517 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2519 this.legendShowLatestValues();
2522 this.hasLegend = function() {
2523 if(typeof this.___hasLegendCache___ !== 'undefined')
2524 return this.___hasLegendCache___;
2527 if(this.library && this.library.legend(this) === 'right-side') {
2528 var legend = $(this.element).data('legend') || 'yes';
2529 if(legend === 'yes') leg = true;
2532 this.___hasLegendCache___ = leg;
2536 this.legendWidth = function() {
2537 return (this.hasLegend())?140:0;
2540 this.legendHeight = function() {
2541 return $(this.element).height();
2544 this.chartWidth = function() {
2545 return $(this.element).width() - this.legendWidth();
2548 this.chartHeight = function() {
2549 return $(this.element).height();
2552 this.chartPixelsPerPoint = function() {
2553 // force an options provided detail
2554 var px = this.pixels_per_point;
2556 if(this.library && px < this.library.pixels_per_point(this))
2557 px = this.library.pixels_per_point(this);
2559 if(px < NETDATA.options.current.pixels_per_point)
2560 px = NETDATA.options.current.pixels_per_point;
2565 this.needsRecreation = function() {
2567 this.chart_created === true
2569 && this.library.autoresize() === false
2570 && this.tm.last_resized < NETDATA.options.last_resized
2574 this.chartURL = function() {
2575 var after, before, points_multiplier = 1;
2576 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2577 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2579 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2580 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2581 this.view_after = after * 1000;
2582 this.view_before = before * 1000;
2584 this.requested_padding = null;
2585 points_multiplier = 1;
2587 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2588 this.tm.pan_and_zoom_seq = 0;
2590 before = Math.round(this.current.force_before_ms / 1000);
2591 after = Math.round(this.current.force_after_ms / 1000);
2592 this.view_after = after * 1000;
2593 this.view_before = before * 1000;
2595 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2596 this.requested_padding = Math.round((before - after) / 2);
2597 after -= this.requested_padding;
2598 before += this.requested_padding;
2599 this.requested_padding *= 1000;
2600 points_multiplier = 2;
2603 this.current.force_before_ms = null;
2604 this.current.force_after_ms = null;
2607 this.tm.pan_and_zoom_seq = 0;
2609 before = this.before;
2611 this.view_after = after * 1000;
2612 this.view_before = before * 1000;
2614 this.requested_padding = null;
2615 points_multiplier = 1;
2618 this.requested_after = after * 1000;
2619 this.requested_before = before * 1000;
2621 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2623 // build the data URL
2624 this.data_url = this.host + this.chart.data_url;
2625 this.data_url += "&format=" + this.library.format();
2626 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2627 this.data_url += "&group=" + this.method;
2628 this.data_url += "&options=" + this.library.options(this);
2629 this.data_url += '|jsonwrap';
2631 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2632 this.data_url += '|nonzero';
2634 if(this.append_options !== null)
2635 this.data_url += '|' + this.append_options.toString();
2638 this.data_url += "&after=" + after.toString();
2641 this.data_url += "&before=" + before.toString();
2644 this.data_url += "&dimensions=" + this.dimensions;
2646 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2647 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2650 this.redrawChart = function() {
2651 if(this.data !== null)
2652 this.updateChartWithData(this.data);
2655 this.updateChartWithData = function(data) {
2656 if(this.debug === true)
2657 this.log('updateChartWithData() called.');
2659 // this may force the chart to be re-created
2663 this.updates_counter++;
2664 this.updates_since_last_unhide++;
2665 this.updates_since_last_creation++;
2667 var started = new Date().getTime();
2669 // if the result is JSON, find the latest update-every
2670 this.data_update_every = data.view_update_every * 1000;
2671 this.data_after = data.after * 1000;
2672 this.data_before = data.before * 1000;
2673 this.netdata_first = data.first_entry * 1000;
2674 this.netdata_last = data.last_entry * 1000;
2675 this.data_points = data.points;
2678 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2679 if(this.view_after < this.data_after) {
2680 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2681 this.view_after = this.data_after;
2684 if(this.view_before > this.data_before) {
2685 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2686 this.view_before = this.data_before;
2690 this.view_after = this.data_after;
2691 this.view_before = this.data_before;
2694 if(this.debug === true) {
2695 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2697 if(this.current.force_after_ms)
2698 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2700 this.log('STATUS: forced : unset');
2702 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2703 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2704 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2705 this.log('STATUS: points : ' + (this.data_points).toString());
2708 if(this.data_points === 0) {
2713 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2714 if(this.debug === true)
2715 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2717 this.chart_created = false;
2720 // check and update the legend
2721 this.legendUpdateDOM();
2723 if(this.chart_created === true
2724 && typeof this.library.update === 'function') {
2726 if(this.debug === true)
2727 this.log('updating chart...');
2729 if(callChartLibraryUpdateSafely(data) === false)
2733 if(this.debug === true)
2734 this.log('creating chart...');
2736 if(callChartLibraryCreateSafely(data) === false)
2740 this.legendShowLatestValues();
2741 if(this.selected === true)
2742 NETDATA.globalSelectionSync.stop();
2744 // update the performance counters
2745 var now = new Date().getTime();
2746 this.tm.last_updated = now;
2748 // don't update last_autorefreshed if this chart is
2749 // forced to be updated with global PanAndZoom
2750 if(NETDATA.globalPanAndZoom.isActive())
2751 this.tm.last_autorefreshed = 0;
2753 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2754 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2756 this.tm.last_autorefreshed = now;
2759 this.refresh_dt_ms = now - started;
2760 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2762 if(this.refresh_dt_element !== null)
2763 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2766 this.updateChart = function(callback) {
2767 if(this.debug === true)
2768 this.log('updateChart() called.');
2770 if(this._updating === true) {
2771 if(this.debug === true)
2772 this.log('I am already updating...');
2774 if(typeof callback === 'function') callback();
2778 // due to late initialization of charts and libraries
2779 // we need to check this too
2780 if(this.enabled === false) {
2781 if(this.debug === true)
2782 this.log('I am not enabled');
2784 if(typeof callback === 'function') callback();
2788 if(canBeRendered() === false) {
2789 if(typeof callback === 'function') callback();
2793 if(this.chart === null) {
2794 this.getChart(function() { that.updateChart(callback); });
2798 if(this.library.initialized === false) {
2799 if(this.library.enabled === true) {
2800 this.library.initialize(function() { that.updateChart(callback); });
2804 error('chart library "' + this.library_name + '" is not available.');
2805 if(typeof callback === 'function') callback();
2810 this.clearSelection();
2813 if(this.debug === true)
2814 this.log('updating from ' + this.data_url);
2816 NETDATA.statistics.refreshes_total++;
2817 NETDATA.statistics.refreshes_active++;
2819 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2820 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2822 this._updating = true;
2824 this.xhr = $.ajax( {
2828 xhrFields: { withCredentials: true } // required for the cookie
2830 .done(function(data) {
2831 that.xhr = undefined;
2833 if(that.debug === true)
2834 that.log('data received. updating chart.');
2836 that.updateChartWithData(data);
2838 .fail(function(msg) {
2839 that.xhr = undefined;
2841 if(msg.statusText !== 'abort')
2842 error('data download failed for url: ' + that.data_url);
2844 .always(function() {
2845 that.xhr = undefined;
2847 NETDATA.statistics.refreshes_active--;
2848 that._updating = false;
2849 if(typeof callback === 'function') callback();
2855 this.isVisible = function(nocache) {
2856 if(typeof nocache === 'undefined')
2859 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2861 // caching - we do not evaluate the charts visibility
2862 // if the page has not been scrolled since the last check
2863 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2864 return this.___isVisible___;
2866 this.tm.last_visible_check = new Date().getTime();
2868 var wh = window.innerHeight;
2869 var x = this.element.getBoundingClientRect();
2873 if(x.width === 0 || x.height === 0) {
2875 this.___isVisible___ = false;
2876 return this.___isVisible___;
2879 if(x.top < 0 && -x.top > x.height) {
2880 // the chart is entirely above
2881 ret = -x.top - x.height;
2883 else if(x.top > wh) {
2884 // the chart is entirely below
2888 if(ret > tolerance) {
2889 // the chart is too far
2892 this.___isVisible___ = false;
2893 return this.___isVisible___;
2896 // the chart is inside or very close
2899 this.___isVisible___ = true;
2900 return this.___isVisible___;
2904 this.isAutoRefreshable = function() {
2905 return (this.current.autorefresh);
2908 this.canBeAutoRefreshed = function() {
2909 var now = new Date().getTime();
2911 if(this.running === true) {
2912 if(this.debug === true)
2913 this.log('I am already running');
2918 if(this.enabled === false) {
2919 if(this.debug === true)
2920 this.log('I am not enabled');
2925 if(this.library === null || this.library.enabled === false) {
2926 error('charting library "' + this.library_name + '" is not available');
2927 if(this.debug === true)
2928 this.log('My chart library ' + this.library_name + ' is not available');
2933 if(this.isVisible() === false) {
2934 if(NETDATA.options.debug.visibility === true || this.debug === true)
2935 this.log('I am not visible');
2940 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2941 if(this.debug === true)
2942 this.log('timed force update detected - allowing this update');
2944 this.current.force_update_at = 0;
2948 if(this.isAutoRefreshable() === true) {
2949 // allow the first update, even if the page is not visible
2950 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2951 if(NETDATA.options.debug.focus === true || this.debug === true)
2952 this.log('canBeAutoRefreshed(): page does not have focus');
2957 if(this.needsRecreation() === true) {
2958 if(this.debug === true)
2959 this.log('canBeAutoRefreshed(): needs re-creation.');
2964 // options valid only for autoRefresh()
2965 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2966 if(NETDATA.globalPanAndZoom.isActive()) {
2967 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2968 if(this.debug === true)
2969 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2974 if(this.debug === true)
2975 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2981 if(this.selected === true) {
2982 if(this.debug === true)
2983 this.log('canBeAutoRefreshed(): I have a selection in place.');
2988 if(this.paused === true) {
2989 if(this.debug === true)
2990 this.log('canBeAutoRefreshed(): I am paused.');
2995 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2996 if(this.debug === true)
2997 this.log('canBeAutoRefreshed(): It is time to update me.');
3007 this.autoRefresh = function(callback) {
3008 if(this.canBeAutoRefreshed() === true && this.running === false) {
3011 state.running = true;
3012 state.updateChart(function() {
3013 state.running = false;
3015 if(typeof callback !== 'undefined')
3020 if(typeof callback !== 'undefined')
3025 this._defaultsFromDownloadedChart = function(chart) {
3027 this.chart_url = chart.url;
3028 this.data_update_every = chart.update_every * 1000;
3029 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3030 this.tm.last_info_downloaded = new Date().getTime();
3032 if(this.title === null)
3033 this.title = chart.title;
3035 if(this.units === null)
3036 this.units = chart.units;
3039 // fetch the chart description from the netdata server
3040 this.getChart = function(callback) {
3041 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3043 this._defaultsFromDownloadedChart(this.chart);
3044 if(typeof callback === 'function') callback();
3047 this.chart_url = "/api/v1/chart?chart=" + this.id;
3049 if(this.debug === true)
3050 this.log('downloading ' + this.chart_url);
3053 url: this.host + this.chart_url,
3056 xhrFields: { withCredentials: true } // required for the cookie
3058 .done(function(chart) {
3059 chart.url = that.chart_url;
3060 that._defaultsFromDownloadedChart(chart);
3061 NETDATA.chartRegistry.add(that.host, that.id, chart);
3064 NETDATA.error(404, that.chart_url);
3065 error('chart not found on url "' + that.chart_url + '"');
3067 .always(function() {
3068 if(typeof callback === 'function') callback();
3073 // ============================================================================================================
3079 NETDATA.resetAllCharts = function(state) {
3080 // first clear the global selection sync
3081 // to make sure no chart is in selected state
3082 state.globalSelectionSyncStop();
3084 // there are 2 possibilities here
3085 // a. state is the global Pan and Zoom master
3086 // b. state is not the global Pan and Zoom master
3088 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3091 // clear the global Pan and Zoom
3092 // this will also refresh the master
3093 // and unblock any charts currently mirroring the master
3094 NETDATA.globalPanAndZoom.clearMaster();
3096 // if we were not the master, reset our status too
3097 // this is required because most probably the mouse
3098 // is over this chart, blocking it from auto-refreshing
3099 if(master === false && (state.paused === true || state.selected === true))
3103 // get or create a chart state, given a DOM element
3104 NETDATA.chartState = function(element) {
3105 var state = $(element).data('netdata-state-object') || null;
3106 if(state === null) {
3107 state = new chartState(element);
3108 $(element).data('netdata-state-object', state);
3113 // ----------------------------------------------------------------------------------------------------------------
3114 // Library functions
3116 // Load a script without jquery
3117 // This is used to load jquery - after it is loaded, we use jquery
3118 NETDATA._loadjQuery = function(callback) {
3119 if(typeof jQuery === 'undefined') {
3120 if(NETDATA.options.debug.main_loop === true)
3121 console.log('loading ' + NETDATA.jQuery);
3123 var script = document.createElement('script');
3124 script.type = 'text/javascript';
3125 script.async = true;
3126 script.src = NETDATA.jQuery;
3128 // script.onabort = onError;
3129 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3130 if(typeof callback === "function")
3131 script.onload = callback;
3133 var s = document.getElementsByTagName('script')[0];
3134 s.parentNode.insertBefore(script, s);
3136 else if(typeof callback === "function")
3140 NETDATA._loadCSS = function(filename) {
3141 // don't use jQuery here
3142 // styles are loaded before jQuery
3143 // to eliminate showing an unstyled page to the user
3145 var fileref = document.createElement("link");
3146 fileref.setAttribute("rel", "stylesheet");
3147 fileref.setAttribute("type", "text/css");
3148 fileref.setAttribute("href", filename);
3150 if (typeof fileref !== 'undefined')
3151 document.getElementsByTagName("head")[0].appendChild(fileref);
3154 NETDATA.colorHex2Rgb = function(hex) {
3155 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3156 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3157 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3158 return r + r + g + g + b + b;
3161 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3163 r: parseInt(result[1], 16),
3164 g: parseInt(result[2], 16),
3165 b: parseInt(result[3], 16)
3169 NETDATA.colorLuminance = function(hex, lum) {
3170 // validate hex string
3171 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3173 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3177 // convert to decimal and change luminosity
3178 var rgb = "#", c, i;
3179 for (i = 0; i < 3; i++) {
3180 c = parseInt(hex.substr(i*2,2), 16);
3181 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3182 rgb += ("00"+c).substr(c.length);
3188 NETDATA.guid = function() {
3190 return Math.floor((1 + Math.random()) * 0x10000)
3195 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3198 NETDATA.zeropad = function(x) {
3199 if(x > -10 && x < 10) return '0' + x.toString();
3200 else return x.toString();
3203 // user function to signal us the DOM has been
3205 NETDATA.updatedDom = function() {
3206 NETDATA.options.updated_dom = true;
3209 NETDATA.ready = function(callback) {
3210 NETDATA.options.pauseCallback = callback;
3213 NETDATA.pause = function(callback) {
3214 if(NETDATA.options.pause === true)
3217 NETDATA.options.pauseCallback = callback;
3220 NETDATA.unpause = function() {
3221 NETDATA.options.pauseCallback = null;
3222 NETDATA.options.updated_dom = true;
3223 NETDATA.options.pause = false;
3226 // ----------------------------------------------------------------------------------------------------------------
3228 // this is purely sequencial charts refresher
3229 // it is meant to be autonomous
3230 NETDATA.chartRefresherNoParallel = function(index) {
3231 if(NETDATA.options.debug.mail_loop === true)
3232 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3234 if(NETDATA.options.updated_dom === true) {
3235 // the dom has been updated
3236 // get the dom parts again
3237 NETDATA.parseDom(NETDATA.chartRefresher);
3240 if(index >= NETDATA.options.targets.length) {
3241 if(NETDATA.options.debug.main_loop === true)
3242 console.log('waiting to restart main loop...');
3244 NETDATA.options.auto_refresher_fast_weight = 0;
3246 setTimeout(function() {
3247 NETDATA.chartRefresher();
3248 }, NETDATA.options.current.idle_between_loops);
3251 var state = NETDATA.options.targets[index];
3253 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3254 if(NETDATA.options.debug.main_loop === true)
3255 console.log('fast rendering...');
3257 state.autoRefresh(function() {
3258 NETDATA.chartRefresherNoParallel(++index);
3262 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3263 NETDATA.options.auto_refresher_fast_weight = 0;
3265 setTimeout(function() {
3266 state.autoRefresh(function() {
3267 NETDATA.chartRefresherNoParallel(++index);
3269 }, NETDATA.options.current.idle_between_charts);
3274 // this is part of the parallel refresher
3275 // its cause is to refresh sequencially all the charts
3276 // that depend on chart library initialization
3277 // it will call the parallel refresher back
3278 // as soon as it sees a chart that its chart library
3280 NETDATA.chartRefresher_uninitialized = function() {
3281 if(NETDATA.options.updated_dom === true) {
3282 // the dom has been updated
3283 // get the dom parts again
3284 NETDATA.parseDom(NETDATA.chartRefresher);
3288 if(NETDATA.options.sequencial.length === 0)
3289 NETDATA.chartRefresher();
3291 var state = NETDATA.options.sequencial.pop();
3292 if(state.library.initialized === true)
3293 NETDATA.chartRefresher();
3295 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3299 NETDATA.chartRefresherWaitTime = function() {
3300 return NETDATA.options.current.idle_parallel_loops;
3303 // the default refresher
3304 // it will create 2 sets of charts:
3305 // - the ones that can be refreshed in parallel
3306 // - the ones that depend on something else
3307 // the first set will be executed in parallel
3308 // the second will be given to NETDATA.chartRefresher_uninitialized()
3309 NETDATA.chartRefresher = function() {
3310 // console.log('auto-refresher...');
3312 if(NETDATA.options.pause === true) {
3313 // console.log('auto-refresher is paused');
3314 setTimeout(NETDATA.chartRefresher,
3315 NETDATA.chartRefresherWaitTime());
3319 if(typeof NETDATA.options.pauseCallback === 'function') {
3320 // console.log('auto-refresher is calling pauseCallback');
3321 NETDATA.options.pause = true;
3322 NETDATA.options.pauseCallback();
3323 NETDATA.chartRefresher();
3327 if(NETDATA.options.current.parallel_refresher === false) {
3328 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3329 NETDATA.chartRefresherNoParallel(0);
3333 if(NETDATA.options.updated_dom === true) {
3334 // the dom has been updated
3335 // get the dom parts again
3336 // console.log('auto-refresher is calling parseDom()');
3337 NETDATA.parseDom(NETDATA.chartRefresher);
3341 var parallel = new Array();
3342 var targets = NETDATA.options.targets;
3343 var len = targets.length;
3346 state = targets[len];
3347 if(state.isVisible() === false || state.running === true)
3350 if(state.library.initialized === false) {
3351 if(state.library.enabled === true) {
3352 state.library.initialize(NETDATA.chartRefresher);
3356 state.error('chart library "' + state.library_name + '" is not enabled.');
3360 parallel.unshift(state);
3363 if(parallel.length > 0) {
3364 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3365 // this will execute the jobs in parallel
3366 $(parallel).each(function() {
3371 console.log('auto-refresher nothing to do');
3374 // run the next refresh iteration
3375 setTimeout(NETDATA.chartRefresher,
3376 NETDATA.chartRefresherWaitTime());
3379 NETDATA.parseDom = function(callback) {
3380 NETDATA.options.last_page_scroll = new Date().getTime();
3381 NETDATA.options.updated_dom = false;
3383 var targets = $('div[data-netdata]'); //.filter(':visible');
3385 if(NETDATA.options.debug.main_loop === true)
3386 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3388 NETDATA.options.targets = new Array();
3389 var len = targets.length;
3391 // the initialization will take care of sizing
3392 // and the "loading..." message
3393 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3396 if(typeof callback === 'function') callback();
3399 // this is the main function - where everything starts
3400 NETDATA.start = function() {
3401 // this should be called only once
3403 NETDATA.options.page_is_visible = true;
3405 $(window).blur(function() {
3406 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3407 NETDATA.options.page_is_visible = false;
3408 if(NETDATA.options.debug.focus === true)
3409 console.log('Lost Focus!');
3413 $(window).focus(function() {
3414 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3415 NETDATA.options.page_is_visible = true;
3416 if(NETDATA.options.debug.focus === true)
3417 console.log('Focus restored!');
3421 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3422 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3423 NETDATA.options.page_is_visible = false;
3424 if(NETDATA.options.debug.focus === true)
3425 console.log('Document has no focus!');
3429 // bootstrap tab switching
3430 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3432 // bootstrap modal switching
3433 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3434 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3436 // bootstrap collapse switching
3437 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3438 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3440 NETDATA.parseDom(NETDATA.chartRefresher);
3442 // Alarms initialization
3443 if(netdataShowAlarms === true)
3444 setTimeout(NETDATA.alarms.init, 1000);
3446 // Registry initialization
3447 setTimeout(NETDATA.registry.init, 1500);
3450 // ----------------------------------------------------------------------------------------------------------------
3453 NETDATA.peityInitialize = function(callback) {
3454 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3456 url: NETDATA.peity_js,
3459 xhrFields: { withCredentials: true } // required for the cookie
3462 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3465 NETDATA.chartLibraries.peity.enabled = false;
3466 NETDATA.error(100, NETDATA.peity_js);
3468 .always(function() {
3469 if(typeof callback === "function")
3474 NETDATA.chartLibraries.peity.enabled = false;
3475 if(typeof callback === "function")
3480 NETDATA.peityChartUpdate = function(state, data) {
3481 state.peity_instance.innerHTML = data.result;
3483 if(state.peity_options.stroke !== state.chartColors()[0]) {
3484 state.peity_options.stroke = state.chartColors()[0];
3485 if(state.chart.chart_type === 'line')
3486 state.peity_options.fill = NETDATA.themes.current.background;
3488 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3491 $(state.peity_instance).peity('line', state.peity_options);
3495 NETDATA.peityChartCreate = function(state, data) {
3496 state.peity_instance = document.createElement('div');
3497 state.element_chart.appendChild(state.peity_instance);
3499 var self = $(state.element);
3500 state.peity_options = {
3501 stroke: NETDATA.themes.current.foreground,
3502 strokeWidth: self.data('peity-strokewidth') || 1,
3503 width: state.chartWidth(),
3504 height: state.chartHeight(),
3505 fill: NETDATA.themes.current.foreground
3508 NETDATA.peityChartUpdate(state, data);
3512 // ----------------------------------------------------------------------------------------------------------------
3515 NETDATA.sparklineInitialize = function(callback) {
3516 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3518 url: NETDATA.sparkline_js,
3521 xhrFields: { withCredentials: true } // required for the cookie
3524 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3527 NETDATA.chartLibraries.sparkline.enabled = false;
3528 NETDATA.error(100, NETDATA.sparkline_js);
3530 .always(function() {
3531 if(typeof callback === "function")
3536 NETDATA.chartLibraries.sparkline.enabled = false;
3537 if(typeof callback === "function")
3542 NETDATA.sparklineChartUpdate = function(state, data) {
3543 state.sparkline_options.width = state.chartWidth();
3544 state.sparkline_options.height = state.chartHeight();
3546 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3550 NETDATA.sparklineChartCreate = function(state, data) {
3551 var self = $(state.element);
3552 var type = self.data('sparkline-type') || 'line';
3553 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3554 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3555 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3556 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3557 var composite = self.data('sparkline-composite') || undefined;
3558 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3559 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3560 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3561 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3562 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3563 var spotColor = self.data('sparkline-spotcolor') || undefined;
3564 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3565 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3566 var spotRadius = self.data('sparkline-spotradius') || undefined;
3567 var valueSpots = self.data('sparkline-valuespots') || undefined;
3568 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3569 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3570 var lineWidth = self.data('sparkline-linewidth') || undefined;
3571 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3572 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3573 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3574 var xvalues = self.data('sparkline-xvalues') || undefined;
3575 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3576 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3577 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3578 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3579 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3580 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3581 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3582 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3583 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3584 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3585 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3586 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3587 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3588 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3589 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3590 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3591 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3592 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3593 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3594 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3595 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3596 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3598 if(spotColor === 'disable') spotColor='';
3599 if(minSpotColor === 'disable') minSpotColor='';
3600 if(maxSpotColor === 'disable') maxSpotColor='';
3602 state.sparkline_options = {
3604 lineColor: lineColor,
3605 fillColor: fillColor,
3606 chartRangeMin: chartRangeMin,
3607 chartRangeMax: chartRangeMax,
3608 composite: composite,
3609 enableTagOptions: enableTagOptions,
3610 tagOptionPrefix: tagOptionPrefix,
3611 tagValuesAttribute: tagValuesAttribute,
3612 disableHiddenCheck: disableHiddenCheck,
3613 defaultPixelsPerValue: defaultPixelsPerValue,
3614 spotColor: spotColor,
3615 minSpotColor: minSpotColor,
3616 maxSpotColor: maxSpotColor,
3617 spotRadius: spotRadius,
3618 valueSpots: valueSpots,
3619 highlightSpotColor: highlightSpotColor,
3620 highlightLineColor: highlightLineColor,
3621 lineWidth: lineWidth,
3622 normalRangeMin: normalRangeMin,
3623 normalRangeMax: normalRangeMax,
3624 drawNormalOnTop: drawNormalOnTop,
3626 chartRangeClip: chartRangeClip,
3627 chartRangeMinX: chartRangeMinX,
3628 chartRangeMaxX: chartRangeMaxX,
3629 disableInteraction: disableInteraction,
3630 disableTooltips: disableTooltips,
3631 disableHighlight: disableHighlight,
3632 highlightLighten: highlightLighten,
3633 highlightColor: highlightColor,
3634 tooltipContainer: tooltipContainer,
3635 tooltipClassname: tooltipClassname,
3636 tooltipChartTitle: state.title,
3637 tooltipFormat: tooltipFormat,
3638 tooltipPrefix: tooltipPrefix,
3639 tooltipSuffix: tooltipSuffix,
3640 tooltipSkipNull: tooltipSkipNull,
3641 tooltipValueLookups: tooltipValueLookups,
3642 tooltipFormatFieldlist: tooltipFormatFieldlist,
3643 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3644 numberFormatter: numberFormatter,
3645 numberDigitGroupSep: numberDigitGroupSep,
3646 numberDecimalMark: numberDecimalMark,
3647 numberDigitGroupCount: numberDigitGroupCount,
3648 animatedZooms: animatedZooms,
3649 width: state.chartWidth(),
3650 height: state.chartHeight()
3653 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3657 // ----------------------------------------------------------------------------------------------------------------
3664 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3665 if(after < state.netdata_first)
3666 after = state.netdata_first;
3668 if(before > state.netdata_last)
3669 before = state.netdata_last;
3671 state.setMode('zoom');
3672 state.globalSelectionSyncStop();
3673 state.globalSelectionSyncDelay();
3674 state.dygraph_user_action = true;
3675 state.dygraph_force_zoom = true;
3676 state.updateChartPanOrZoom(after, before);
3677 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3680 NETDATA.dygraphSetSelection = function(state, t) {
3681 if(typeof state.dygraph_instance !== 'undefined') {
3682 var r = state.calculateRowForTime(t);
3684 state.dygraph_instance.setSelection(r);
3686 state.dygraph_instance.clearSelection();
3687 state.legendShowUndefined();
3694 NETDATA.dygraphClearSelection = function(state, t) {
3695 if(typeof state.dygraph_instance !== 'undefined') {
3696 state.dygraph_instance.clearSelection();
3701 NETDATA.dygraphSmoothInitialize = function(callback) {
3703 url: NETDATA.dygraph_smooth_js,
3706 xhrFields: { withCredentials: true } // required for the cookie
3709 NETDATA.dygraph.smooth = true;
3710 smoothPlotter.smoothing = 0.3;
3713 NETDATA.dygraph.smooth = false;
3715 .always(function() {
3716 if(typeof callback === "function")
3721 NETDATA.dygraphInitialize = function(callback) {
3722 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3724 url: NETDATA.dygraph_js,
3727 xhrFields: { withCredentials: true } // required for the cookie
3730 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3733 NETDATA.chartLibraries.dygraph.enabled = false;
3734 NETDATA.error(100, NETDATA.dygraph_js);
3736 .always(function() {
3737 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3738 NETDATA.dygraphSmoothInitialize(callback);
3739 else if(typeof callback === "function")
3744 NETDATA.chartLibraries.dygraph.enabled = false;
3745 if(typeof callback === "function")
3750 NETDATA.dygraphChartUpdate = function(state, data) {
3751 var dygraph = state.dygraph_instance;
3753 if(typeof dygraph === 'undefined')
3754 return NETDATA.dygraphChartCreate(state, data);
3756 // when the chart is not visible, and hidden
3757 // if there is a window resize, dygraph detects
3758 // its element size as 0x0.
3759 // this will make it re-appear properly
3761 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3765 file: data.result.data,
3766 colors: state.chartColors(),
3767 labels: data.result.labels,
3768 labelsDivWidth: state.chartWidth() - 70,
3769 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3772 if(state.dygraph_force_zoom === true) {
3773 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3774 state.log('dygraphChartUpdate() forced zoom update');
3776 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3777 options.valueRange = state.dygraph_options.valueRange;
3778 options.isZoomedIgnoreProgrammaticZoom = true;
3779 state.dygraph_force_zoom = false;
3781 else if(state.current.name !== 'auto') {
3782 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3783 state.log('dygraphChartUpdate() loose update');
3785 options.valueRange = state.dygraph_options.valueRange;
3788 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3789 state.log('dygraphChartUpdate() strict update');
3791 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3792 options.valueRange = state.dygraph_options.valueRange;
3793 options.isZoomedIgnoreProgrammaticZoom = true;
3796 if(state.dygraph_smooth_eligible === true) {
3797 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3798 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3799 NETDATA.dygraphChartCreate(state, data);
3804 dygraph.updateOptions(options);
3806 state.dygraph_last_rendered = new Date().getTime();
3810 NETDATA.dygraphChartCreate = function(state, data) {
3811 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3812 state.log('dygraphChartCreate()');
3814 var self = $(state.element);
3816 var chart_type = state.chart.chart_type;
3817 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3818 chart_type = self.data('dygraph-type') || chart_type;
3820 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3821 smooth = self.data('dygraph-smooth') || smooth;
3823 if(NETDATA.dygraph.smooth === false)
3826 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3827 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3829 state.dygraph_options = {
3830 colors: self.data('dygraph-colors') || state.chartColors(),
3832 // leave a few pixels empty on the right of the chart
3833 rightGap: self.data('dygraph-rightgap') || 5,
3834 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3835 showRoller: self.data('dygraph-showroller') || false,
3837 title: self.data('dygraph-title') || state.title,
3838 titleHeight: self.data('dygraph-titleheight') || 19,
3840 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3841 labels: data.result.labels,
3842 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3843 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3844 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3845 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3846 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3849 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3850 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3852 includeZero: self.data('dygraph-includezero') || false,
3853 xRangePad: self.data('dygraph-xrangepad') || 0,
3854 yRangePad: self.data('dygraph-yrangepad') || 1,
3856 valueRange: self.data('dygraph-valuerange') || null,
3858 ylabel: state.units,
3859 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3861 // the function to plot the chart
3864 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3865 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3866 strokePattern: self.data('dygraph-strokepattern') || undefined,
3868 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3869 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3870 drawPoints: self.data('dygraph-drawpoints') || false,
3872 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3873 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3875 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3876 pointSize: self.data('dygraph-pointsize') || 1,
3878 // enabling this makes the chart with little square lines
3879 stepPlot: self.data('dygraph-stepplot') || false,
3881 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3882 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3883 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3885 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3886 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3887 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3888 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3890 drawAxis: self.data('dygraph-drawaxis') || true,
3891 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3892 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3893 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3895 drawGrid: self.data('dygraph-drawgrid') || true,
3896 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3897 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3898 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3899 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3900 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3902 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3903 sigFigs: self.data('dygraph-sigfigs') || null,
3904 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3905 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3907 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3908 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3909 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3911 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3912 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3916 ticker: Dygraph.dateTicker,
3917 axisLabelFormatter: function (d, gran) {
3918 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3920 valueFormatter: function (ms) {
3921 var d = new Date(ms);
3922 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3923 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3928 valueFormatter: function (x) {
3929 // we format legends with the state object
3930 // no need to do anything here
3931 // return (Math.round(x*100) / 100).toLocaleString();
3932 // return state.legendFormatValue(x);
3937 legendFormatter: function(data) {
3938 var elements = state.element_legend_childs;
3940 // if the hidden div is not there
3941 // we are not managing the legend
3942 if(elements.hidden === null) return;
3944 if (typeof data.x !== 'undefined') {
3945 state.legendSetDate(data.x);
3946 var i = data.series.length;
3948 var series = data.series[i];
3949 if(!series.isVisible) continue;
3950 state.legendSetLabelValue(series.label, series.y);
3956 drawCallback: function(dygraph, is_initial) {
3957 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3958 state.dygraph_user_action = false;
3960 var x_range = dygraph.xAxisRange();
3961 var after = Math.round(x_range[0]);
3962 var before = Math.round(x_range[1]);
3964 if(NETDATA.options.debug.dygraph === true)
3965 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3967 if(before <= state.netdata_last && after >= state.netdata_first)
3968 state.updateChartPanOrZoom(after, before);
3971 zoomCallback: function(minDate, maxDate, yRanges) {
3972 if(NETDATA.options.debug.dygraph === true)
3973 state.log('dygraphZoomCallback()');
3975 state.globalSelectionSyncStop();
3976 state.globalSelectionSyncDelay();
3977 state.setMode('zoom');
3979 // refresh it to the greatest possible zoom level
3980 state.dygraph_user_action = true;
3981 state.dygraph_force_zoom = true;
3982 state.updateChartPanOrZoom(minDate, maxDate);
3984 highlightCallback: function(event, x, points, row, seriesName) {
3985 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3986 state.log('dygraphHighlightCallback()');
3990 // there is a bug in dygraph when the chart is zoomed enough
3991 // the time it thinks is selected is wrong
3992 // here we calculate the time t based on the row number selected
3994 var t = state.data_after + row * state.data_update_every;
3995 // 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);
3997 state.globalSelectionSync(x);
3999 // fix legend zIndex using the internal structures of dygraph legend module
4000 // this works, but it is a hack!
4001 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4003 unhighlightCallback: function(event) {
4004 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4005 state.log('dygraphUnhighlightCallback()');
4007 state.unpauseChart();
4008 state.globalSelectionSyncStop();
4010 interactionModel : {
4011 mousedown: function(event, dygraph, context) {
4012 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4013 state.log('interactionModel.mousedown()');
4015 state.dygraph_user_action = true;
4016 state.globalSelectionSyncStop();
4018 if(NETDATA.options.debug.dygraph === true)
4019 state.log('dygraphMouseDown()');
4021 // Right-click should not initiate a zoom.
4022 if(event.button && event.button === 2) return;
4024 context.initializeMouseDown(event, dygraph, context);
4026 if(event.button && event.button === 1) {
4027 if (event.altKey || event.shiftKey) {
4028 state.setMode('pan');
4029 state.globalSelectionSyncDelay();
4030 Dygraph.startPan(event, dygraph, context);
4033 state.setMode('zoom');
4034 state.globalSelectionSyncDelay();
4035 Dygraph.startZoom(event, dygraph, context);
4039 if (event.altKey || event.shiftKey) {
4040 state.setMode('zoom');
4041 state.globalSelectionSyncDelay();
4042 Dygraph.startZoom(event, dygraph, context);
4045 state.setMode('pan');
4046 state.globalSelectionSyncDelay();
4047 Dygraph.startPan(event, dygraph, context);
4051 mousemove: function(event, dygraph, context) {
4052 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4053 state.log('interactionModel.mousemove()');
4055 if(context.isPanning) {
4056 state.dygraph_user_action = true;
4057 state.globalSelectionSyncStop();
4058 state.globalSelectionSyncDelay();
4059 state.setMode('pan');
4060 Dygraph.movePan(event, dygraph, context);
4062 else if(context.isZooming) {
4063 state.dygraph_user_action = true;
4064 state.globalSelectionSyncStop();
4065 state.globalSelectionSyncDelay();
4066 state.setMode('zoom');
4067 Dygraph.moveZoom(event, dygraph, context);
4070 mouseup: function(event, dygraph, context) {
4071 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4072 state.log('interactionModel.mouseup()');
4074 if (context.isPanning) {
4075 state.dygraph_user_action = true;
4076 state.globalSelectionSyncDelay();
4077 Dygraph.endPan(event, dygraph, context);
4079 else if (context.isZooming) {
4080 state.dygraph_user_action = true;
4081 state.globalSelectionSyncDelay();
4082 Dygraph.endZoom(event, dygraph, context);
4085 click: function(event, dygraph, context) {
4086 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4087 state.log('interactionModel.click()');
4089 event.preventDefault();
4091 dblclick: function(event, dygraph, context) {
4092 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4093 state.log('interactionModel.dblclick()');
4094 NETDATA.resetAllCharts(state);
4096 mousewheel: function(event, dygraph, context) {
4097 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4098 state.log('interactionModel.mousewheel()');
4100 // Take the offset of a mouse event on the dygraph canvas and
4101 // convert it to a pair of percentages from the bottom left.
4102 // (Not top left, bottom is where the lower value is.)
4103 function offsetToPercentage(g, offsetX, offsetY) {
4104 // This is calculating the pixel offset of the leftmost date.
4105 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4106 var yar0 = g.yAxisRange(0);
4108 // This is calculating the pixel of the higest value. (Top pixel)
4109 var yOffset = g.toDomCoords(null, yar0[1])[1];
4111 // x y w and h are relative to the corner of the drawing area,
4112 // so that the upper corner of the drawing area is (0, 0).
4113 var x = offsetX - xOffset;
4114 var y = offsetY - yOffset;
4116 // This is computing the rightmost pixel, effectively defining the
4118 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4120 // This is computing the lowest pixel, effectively defining the height.
4121 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4123 // Percentage from the left.
4124 var xPct = w === 0 ? 0 : (x / w);
4125 // Percentage from the top.
4126 var yPct = h === 0 ? 0 : (y / h);
4128 // The (1-) part below changes it from "% distance down from the top"
4129 // to "% distance up from the bottom".
4130 return [xPct, (1-yPct)];
4133 // Adjusts [x, y] toward each other by zoomInPercentage%
4134 // Split it so the left/bottom axis gets xBias/yBias of that change and
4135 // tight/top gets (1-xBias)/(1-yBias) of that change.
4137 // If a bias is missing it splits it down the middle.
4138 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4139 xBias = xBias || 0.5;
4140 yBias = yBias || 0.5;
4142 function adjustAxis(axis, zoomInPercentage, bias) {
4143 var delta = axis[1] - axis[0];
4144 var increment = delta * zoomInPercentage;
4145 var foo = [increment * bias, increment * (1-bias)];
4147 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4150 var yAxes = g.yAxisRanges();
4152 for (var i = 0; i < yAxes.length; i++) {
4153 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4156 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4159 if(event.altKey || event.shiftKey) {
4160 state.dygraph_user_action = true;
4162 state.globalSelectionSyncStop();
4163 state.globalSelectionSyncDelay();
4165 // http://dygraphs.com/gallery/interaction-api.js
4166 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4167 var percentage = normal / 50;
4169 if (!(event.offsetX && event.offsetY)){
4170 event.offsetX = event.layerX - event.target.offsetLeft;
4171 event.offsetY = event.layerY - event.target.offsetTop;
4174 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4175 var xPct = percentages[0];
4176 var yPct = percentages[1];
4178 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4180 var after = new_x_range[0];
4181 var before = new_x_range[1];
4183 var first = state.netdata_first + state.data_update_every;
4184 var last = state.netdata_last + state.data_update_every;
4187 after -= (before - last);
4194 state.setMode('zoom');
4195 if(state.updateChartPanOrZoom(after, before) === true)
4196 dygraph.updateOptions({ dateWindow: [ after, before ] });
4198 event.preventDefault();
4201 touchstart: function(event, dygraph, context) {
4202 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4203 state.log('interactionModel.touchstart()');
4205 state.dygraph_user_action = true;
4206 state.setMode('zoom');
4209 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4211 // we overwrite the touch directions at the end, to overwrite
4212 // the internal default of dygraphs
4213 context.touchDirections = { x: true, y: false };
4215 state.dygraph_last_touch_start = new Date().getTime();
4216 state.dygraph_last_touch_move = 0;
4218 if(typeof event.touches[0].pageX === 'number')
4219 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4221 state.dygraph_last_touch_page_x = 0;
4223 touchmove: function(event, dygraph, context) {
4224 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4225 state.log('interactionModel.touchmove()');
4227 state.dygraph_user_action = true;
4228 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4230 state.dygraph_last_touch_move = new Date().getTime();
4232 touchend: function(event, dygraph, context) {
4233 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4234 state.log('interactionModel.touchend()');
4236 state.dygraph_user_action = true;
4237 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4239 // if it didn't move, it is a selection
4240 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4241 // internal api of dygraphs
4242 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4243 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4244 if(NETDATA.dygraphSetSelection(state, t) === true)
4245 state.globalSelectionSync(t);
4248 // if it was double tap within double click time, reset the charts
4249 var now = new Date().getTime();
4250 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4251 if(state.dygraph_last_touch_move === 0) {
4252 var dt = now - state.dygraph_last_touch_end;
4253 if(dt <= NETDATA.options.current.double_click_speed)
4254 NETDATA.resetAllCharts(state);
4258 // remember the timestamp of the last touch end
4259 state.dygraph_last_touch_end = now;
4264 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4265 state.dygraph_options.drawGrid = false;
4266 state.dygraph_options.drawAxis = false;
4267 state.dygraph_options.title = undefined;
4268 state.dygraph_options.units = undefined;
4269 state.dygraph_options.ylabel = undefined;
4270 state.dygraph_options.yLabelWidth = 0;
4271 state.dygraph_options.labelsDivWidth = 120;
4272 state.dygraph_options.labelsDivStyles.width = '120px';
4273 state.dygraph_options.labelsSeparateLines = true;
4274 state.dygraph_options.rightGap = 0;
4275 state.dygraph_options.yRangePad = 1;
4278 if(smooth === true) {
4279 state.dygraph_smooth_eligible = true;
4281 if(NETDATA.options.current.smooth_plot === true)
4282 state.dygraph_options.plotter = smoothPlotter;
4284 else state.dygraph_smooth_eligible = false;
4286 state.dygraph_instance = new Dygraph(state.element_chart,
4287 data.result.data, state.dygraph_options);
4289 state.dygraph_force_zoom = false;
4290 state.dygraph_user_action = false;
4291 state.dygraph_last_rendered = new Date().getTime();
4295 // ----------------------------------------------------------------------------------------------------------------
4298 NETDATA.morrisInitialize = function(callback) {
4299 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4301 // morris requires raphael
4302 if(!NETDATA.chartLibraries.raphael.initialized) {
4303 if(NETDATA.chartLibraries.raphael.enabled) {
4304 NETDATA.raphaelInitialize(function() {
4305 NETDATA.morrisInitialize(callback);
4309 NETDATA.chartLibraries.morris.enabled = false;
4310 if(typeof callback === "function")
4315 NETDATA._loadCSS(NETDATA.morris_css);
4318 url: NETDATA.morris_js,
4321 xhrFields: { withCredentials: true } // required for the cookie
4324 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4327 NETDATA.chartLibraries.morris.enabled = false;
4328 NETDATA.error(100, NETDATA.morris_js);
4330 .always(function() {
4331 if(typeof callback === "function")
4337 NETDATA.chartLibraries.morris.enabled = false;
4338 if(typeof callback === "function")
4343 NETDATA.morrisChartUpdate = function(state, data) {
4344 state.morris_instance.setData(data.result.data);
4348 NETDATA.morrisChartCreate = function(state, data) {
4350 state.morris_options = {
4351 element: state.element_chart.id,
4352 data: data.result.data,
4354 ykeys: data.dimension_names,
4355 labels: data.dimension_names,
4361 continuousLine: false,
4362 behaveLikeLine: false
4365 if(state.chart.chart_type === 'line')
4366 state.morris_instance = new Morris.Line(state.morris_options);
4368 else if(state.chart.chart_type === 'area') {
4369 state.morris_options.behaveLikeLine = true;
4370 state.morris_instance = new Morris.Area(state.morris_options);
4373 state.morris_instance = new Morris.Area(state.morris_options);
4378 // ----------------------------------------------------------------------------------------------------------------
4381 NETDATA.raphaelInitialize = function(callback) {
4382 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4384 url: NETDATA.raphael_js,
4387 xhrFields: { withCredentials: true } // required for the cookie
4390 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4393 NETDATA.chartLibraries.raphael.enabled = false;
4394 NETDATA.error(100, NETDATA.raphael_js);
4396 .always(function() {
4397 if(typeof callback === "function")
4402 NETDATA.chartLibraries.raphael.enabled = false;
4403 if(typeof callback === "function")
4408 NETDATA.raphaelChartUpdate = function(state, data) {
4409 $(state.element_chart).raphael(data.result, {
4410 width: state.chartWidth(),
4411 height: state.chartHeight()
4417 NETDATA.raphaelChartCreate = function(state, data) {
4418 $(state.element_chart).raphael(data.result, {
4419 width: state.chartWidth(),
4420 height: state.chartHeight()
4426 // ----------------------------------------------------------------------------------------------------------------
4429 NETDATA.c3Initialize = function(callback) {
4430 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4433 if(!NETDATA.chartLibraries.d3.initialized) {
4434 if(NETDATA.chartLibraries.d3.enabled) {
4435 NETDATA.d3Initialize(function() {
4436 NETDATA.c3Initialize(callback);
4440 NETDATA.chartLibraries.c3.enabled = false;
4441 if(typeof callback === "function")
4446 NETDATA._loadCSS(NETDATA.c3_css);
4452 xhrFields: { withCredentials: true } // required for the cookie
4455 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4458 NETDATA.chartLibraries.c3.enabled = false;
4459 NETDATA.error(100, NETDATA.c3_js);
4461 .always(function() {
4462 if(typeof callback === "function")
4468 NETDATA.chartLibraries.c3.enabled = false;
4469 if(typeof callback === "function")
4474 NETDATA.c3ChartUpdate = function(state, data) {
4475 state.c3_instance.destroy();
4476 return NETDATA.c3ChartCreate(state, data);
4478 //state.c3_instance.load({
4479 // rows: data.result,
4486 NETDATA.c3ChartCreate = function(state, data) {
4488 state.element_chart.id = 'c3-' + state.uuid;
4489 // console.log('id = ' + state.element_chart.id);
4491 state.c3_instance = c3.generate({
4492 bindto: '#' + state.element_chart.id,
4494 width: state.chartWidth(),
4495 height: state.chartHeight()
4498 pattern: state.chartColors()
4503 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4509 format: function(x) {
4510 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4537 // console.log(state.c3_instance);
4542 // ----------------------------------------------------------------------------------------------------------------
4545 NETDATA.d3Initialize = function(callback) {
4546 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4551 xhrFields: { withCredentials: true } // required for the cookie
4554 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4557 NETDATA.chartLibraries.d3.enabled = false;
4558 NETDATA.error(100, NETDATA.d3_js);
4560 .always(function() {
4561 if(typeof callback === "function")
4566 NETDATA.chartLibraries.d3.enabled = false;
4567 if(typeof callback === "function")
4572 NETDATA.d3ChartUpdate = function(state, data) {
4576 NETDATA.d3ChartCreate = function(state, data) {
4580 // ----------------------------------------------------------------------------------------------------------------
4583 NETDATA.googleInitialize = function(callback) {
4584 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4586 url: NETDATA.google_js,
4589 xhrFields: { withCredentials: true } // required for the cookie
4592 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4593 google.load('visualization', '1.1', {
4594 'packages': ['corechart', 'controls'],
4595 'callback': callback
4599 NETDATA.chartLibraries.google.enabled = false;
4600 NETDATA.error(100, NETDATA.google_js);
4601 if(typeof callback === "function")
4606 NETDATA.chartLibraries.google.enabled = false;
4607 if(typeof callback === "function")
4612 NETDATA.googleChartUpdate = function(state, data) {
4613 var datatable = new google.visualization.DataTable(data.result);
4614 state.google_instance.draw(datatable, state.google_options);
4618 NETDATA.googleChartCreate = function(state, data) {
4619 var datatable = new google.visualization.DataTable(data.result);
4621 state.google_options = {
4622 colors: state.chartColors(),
4624 // do not set width, height - the chart resizes itself
4625 //width: state.chartWidth(),
4626 //height: state.chartHeight(),
4631 // title: "Time of Day",
4632 // format:'HH:mm:ss',
4633 viewWindowMode: 'maximized',
4645 viewWindowMode: 'pretty',
4660 focusTarget: 'category',
4667 titlePosition: 'out',
4678 curveType: 'function',
4683 switch(state.chart.chart_type) {
4685 state.google_options.vAxis.viewWindowMode = 'maximized';
4686 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4687 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4691 state.google_options.isStacked = true;
4692 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4693 state.google_options.vAxis.viewWindowMode = 'maximized';
4694 state.google_options.vAxis.minValue = null;
4695 state.google_options.vAxis.maxValue = null;
4696 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4701 state.google_options.lineWidth = 2;
4702 state.google_instance = new google.visualization.LineChart(state.element_chart);
4706 state.google_instance.draw(datatable, state.google_options);
4710 // ----------------------------------------------------------------------------------------------------------------
4712 NETDATA.percentFromValueMax = function(value, max) {
4713 if(value === null) value = 0;
4714 if(max < value) max = value;
4718 pcent = Math.round(value * 100 / max);
4719 if(pcent === 0 && value > 0) pcent = 1;
4725 // ----------------------------------------------------------------------------------------------------------------
4728 NETDATA.easypiechartInitialize = function(callback) {
4729 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4731 url: NETDATA.easypiechart_js,
4734 xhrFields: { withCredentials: true } // required for the cookie
4737 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4740 NETDATA.chartLibraries.easypiechart.enabled = false;
4741 NETDATA.error(100, NETDATA.easypiechart_js);
4743 .always(function() {
4744 if(typeof callback === "function")
4749 NETDATA.chartLibraries.easypiechart.enabled = false;
4750 if(typeof callback === "function")
4755 NETDATA.easypiechartClearSelection = function(state) {
4756 if(typeof state.easyPieChartEvent !== 'undefined') {
4757 if(state.easyPieChartEvent.timer !== null)
4758 clearTimeout(state.easyPieChartEvent.timer);
4760 state.easyPieChartEvent.timer = null;
4763 if(state.isAutoRefreshable() === true && state.data !== null) {
4764 NETDATA.easypiechartChartUpdate(state, state.data);
4767 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4768 state.easyPieChart_instance.update(0);
4770 state.easyPieChart_instance.enableAnimation();
4775 NETDATA.easypiechartSetSelection = function(state, t) {
4776 if(state.timeIsVisible(t) !== true)
4777 return NETDATA.easypiechartClearSelection(state);
4779 var slot = state.calculateRowForTime(t);
4780 if(slot < 0 || slot >= state.data.result.length)
4781 return NETDATA.easypiechartClearSelection(state);
4783 if(typeof state.easyPieChartEvent === 'undefined') {
4784 state.easyPieChartEvent = {
4791 var value = state.data.result[state.data.result.length - 1 - slot];
4792 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4793 var pcent = NETDATA.percentFromValueMax(value, max);
4795 state.easyPieChartEvent.value = value;
4796 state.easyPieChartEvent.pcent = pcent;
4797 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4799 if(state.easyPieChartEvent.timer === null) {
4800 state.easyPieChart_instance.disableAnimation();
4802 state.easyPieChartEvent.timer = setTimeout(function() {
4803 state.easyPieChartEvent.timer = null;
4804 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4805 }, NETDATA.options.current.charts_selection_animation_delay);
4811 NETDATA.easypiechartChartUpdate = function(state, data) {
4812 var value, max, pcent;
4814 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4820 value = data.result[0];
4821 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4822 pcent = NETDATA.percentFromValueMax(value, max);
4825 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4826 state.easyPieChart_instance.update(pcent);
4830 NETDATA.easypiechartChartCreate = function(state, data) {
4831 var self = $(state.element);
4832 var chart = $(state.element_chart);
4834 var value = data.result[0];
4835 var max = self.data('easypiechart-max-value') || null;
4836 var adjust = self.data('easypiechart-adjust') || null;
4840 state.easyPieChartMax = null;
4843 state.easyPieChartMax = max;
4845 var pcent = NETDATA.percentFromValueMax(value, max);
4847 chart.data('data-percent', pcent);
4851 case 'width': size = state.chartHeight(); break;
4852 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4853 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4855 default: size = state.chartWidth(); break;
4857 state.element.style.width = size + 'px';
4858 state.element.style.height = size + 'px';
4860 var stroke = Math.floor(size / 22);
4861 if(stroke < 3) stroke = 2;
4863 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4864 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4865 state.easyPieChartLabel = document.createElement('span');
4866 state.easyPieChartLabel.className = 'easyPieChartLabel';
4867 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4868 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4869 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4870 state.element_chart.appendChild(state.easyPieChartLabel);
4872 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4873 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4874 state.easyPieChartTitle = document.createElement('span');
4875 state.easyPieChartTitle.className = 'easyPieChartTitle';
4876 state.easyPieChartTitle.innerHTML = state.title;
4877 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4878 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4879 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4880 state.element_chart.appendChild(state.easyPieChartTitle);
4882 var unitfontsize = Math.round(titlefontsize * 0.9);
4883 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4884 state.easyPieChartUnits = document.createElement('span');
4885 state.easyPieChartUnits.className = 'easyPieChartUnits';
4886 state.easyPieChartUnits.innerHTML = state.units;
4887 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4888 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4889 state.element_chart.appendChild(state.easyPieChartUnits);
4891 chart.easyPieChart({
4892 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4893 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4894 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4895 scaleLength: self.data('easypiechart-scalelength') || 5,
4896 lineCap: self.data('easypiechart-linecap') || 'round',
4897 lineWidth: self.data('easypiechart-linewidth') || stroke,
4898 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4899 size: self.data('easypiechart-size') || size,
4900 rotate: self.data('easypiechart-rotate') || 0,
4901 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4902 easing: self.data('easypiechart-easing') || undefined
4905 // when we just re-create the chart
4906 // do not animate the first update
4908 if(typeof state.easyPieChart_instance !== 'undefined')
4911 state.easyPieChart_instance = chart.data('easyPieChart');
4912 if(animate === false) state.easyPieChart_instance.disableAnimation();
4913 state.easyPieChart_instance.update(pcent);
4914 if(animate === false) state.easyPieChart_instance.enableAnimation();
4918 // ----------------------------------------------------------------------------------------------------------------
4921 NETDATA.gaugeInitialize = function(callback) {
4922 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4924 url: NETDATA.gauge_js,
4927 xhrFields: { withCredentials: true } // required for the cookie
4930 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4933 NETDATA.chartLibraries.gauge.enabled = false;
4934 NETDATA.error(100, NETDATA.gauge_js);
4936 .always(function() {
4937 if(typeof callback === "function")
4942 NETDATA.chartLibraries.gauge.enabled = false;
4943 if(typeof callback === "function")
4948 NETDATA.gaugeAnimation = function(state, status) {
4951 if(typeof status === 'boolean' && status === false)
4953 else if(typeof status === 'number')
4956 state.gauge_instance.animationSpeed = speed;
4957 state.___gaugeOld__.speed = speed;
4960 NETDATA.gaugeSet = function(state, value, min, max) {
4961 if(typeof value !== 'number') value = 0;
4962 if(typeof min !== 'number') min = 0;
4963 if(typeof max !== 'number') max = 0;
4964 if(value > max) max = value;
4965 if(value < min) min = value;
4974 // gauge.js has an issue if the needle
4975 // is smaller than min or larger than max
4976 // when we set the new values
4977 // the needle will go crazy
4979 // to prevent it, we always feed it
4980 // with a percentage, so that the needle
4981 // is always between min and max
4982 var pcent = (value - min) * 100 / (max - min);
4984 // these should never happen
4985 if(pcent < 0) pcent = 0;
4986 if(pcent > 100) pcent = 100;
4988 state.gauge_instance.set(pcent);
4990 state.___gaugeOld__.value = value;
4991 state.___gaugeOld__.min = min;
4992 state.___gaugeOld__.max = max;
4995 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4996 if(state.___gaugeOld__.valueLabel !== value) {
4997 state.___gaugeOld__.valueLabel = value;
4998 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5000 if(state.___gaugeOld__.minLabel !== min) {
5001 state.___gaugeOld__.minLabel = min;
5002 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5004 if(state.___gaugeOld__.maxLabel !== max) {
5005 state.___gaugeOld__.maxLabel = max;
5006 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5010 NETDATA.gaugeClearSelection = function(state) {
5011 if(typeof state.gaugeEvent !== 'undefined') {
5012 if(state.gaugeEvent.timer !== null)
5013 clearTimeout(state.gaugeEvent.timer);
5015 state.gaugeEvent.timer = null;
5018 if(state.isAutoRefreshable() === true && state.data !== null) {
5019 NETDATA.gaugeChartUpdate(state, state.data);
5022 NETDATA.gaugeAnimation(state, false);
5023 NETDATA.gaugeSet(state, null, null, null);
5024 NETDATA.gaugeSetLabels(state, null, null, null);
5027 NETDATA.gaugeAnimation(state, true);
5031 NETDATA.gaugeSetSelection = function(state, t) {
5032 if(state.timeIsVisible(t) !== true)
5033 return NETDATA.gaugeClearSelection(state);
5035 var slot = state.calculateRowForTime(t);
5036 if(slot < 0 || slot >= state.data.result.length)
5037 return NETDATA.gaugeClearSelection(state);
5039 if(typeof state.gaugeEvent === 'undefined') {
5040 state.gaugeEvent = {
5048 var value = state.data.result[state.data.result.length - 1 - slot];
5049 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5052 state.gaugeEvent.value = value;
5053 state.gaugeEvent.max = max;
5054 state.gaugeEvent.min = min;
5055 NETDATA.gaugeSetLabels(state, value, min, max);
5057 if(state.gaugeEvent.timer === null) {
5058 NETDATA.gaugeAnimation(state, false);
5060 state.gaugeEvent.timer = setTimeout(function() {
5061 state.gaugeEvent.timer = null;
5062 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5063 }, NETDATA.options.current.charts_selection_animation_delay);
5069 NETDATA.gaugeChartUpdate = function(state, data) {
5070 var value, min, max;
5072 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5076 NETDATA.gaugeSetLabels(state, null, null, null);
5079 value = data.result[0];
5081 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5082 if(value > max) max = value;
5083 NETDATA.gaugeSetLabels(state, value, min, max);
5086 NETDATA.gaugeSet(state, value, min, max);
5090 NETDATA.gaugeChartCreate = function(state, data) {
5091 var self = $(state.element);
5092 // var chart = $(state.element_chart);
5094 var value = data.result[0];
5095 var max = self.data('gauge-max-value') || null;
5096 var adjust = self.data('gauge-adjust') || null;
5097 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5098 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5099 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5100 var stopColor = self.data('gauge-stop-color') || void 0;
5101 var generateGradient = self.data('gauge-generate-gradient') || false;
5105 state.gaugeMax = null;
5108 state.gaugeMax = max;
5110 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5112 // case 'width': width = height * ratio; break;
5114 // default: height = width / ratio; break;
5116 //state.element.style.width = width.toString() + 'px';
5117 //state.element.style.height = height.toString() + 'px';
5122 lines: 12, // The number of lines to draw
5123 angle: 0.15, // The length of each line
5124 lineWidth: 0.44, // 0.44 The line thickness
5126 length: 0.8, // 0.9 The radius of the inner circle
5127 strokeWidth: 0.035, // The rotation offset
5128 color: pointerColor // Fill color
5130 colorStart: startColor, // Colors
5131 colorStop: stopColor, // just experiment with them
5132 strokeColor: strokeColor, // to see which ones work best for you
5134 generateGradient: (generateGradient === true)?true:false,
5138 if (generateGradient.constructor === Array) {
5140 // data-gauge-generate-gradient="[0, 50, 100]"
5141 // data-gauge-gradient-percent-color-0="#FFFFFF"
5142 // data-gauge-gradient-percent-color-50="#999900"
5143 // data-gauge-gradient-percent-color-100="#000000"
5145 options.percentColors = new Array();
5146 var len = generateGradient.length;
5148 var pcent = generateGradient[len];
5149 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5150 if(color !== false) {
5151 var a = new Array();
5154 options.percentColors.unshift(a);
5157 if(options.percentColors.length === 0)
5158 delete options.percentColors;
5160 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5161 options.percentColors = [
5162 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5163 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5164 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5165 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5166 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5167 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5168 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5169 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5170 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5171 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5172 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5175 state.gauge_canvas = document.createElement('canvas');
5176 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5177 state.gauge_canvas.className = 'gaugeChart';
5178 state.gauge_canvas.width = width;
5179 state.gauge_canvas.height = height;
5180 state.element_chart.appendChild(state.gauge_canvas);
5182 var valuefontsize = Math.floor(height / 6);
5183 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5184 state.gaugeChartLabel = document.createElement('span');
5185 state.gaugeChartLabel.className = 'gaugeChartLabel';
5186 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5187 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5188 state.element_chart.appendChild(state.gaugeChartLabel);
5190 var titlefontsize = Math.round(valuefontsize / 2);
5192 state.gaugeChartTitle = document.createElement('span');
5193 state.gaugeChartTitle.className = 'gaugeChartTitle';
5194 state.gaugeChartTitle.innerHTML = state.title;
5195 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5196 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5197 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5198 state.element_chart.appendChild(state.gaugeChartTitle);
5200 var unitfontsize = Math.round(titlefontsize * 0.9);
5201 state.gaugeChartUnits = document.createElement('span');
5202 state.gaugeChartUnits.className = 'gaugeChartUnits';
5203 state.gaugeChartUnits.innerHTML = state.units;
5204 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5205 state.element_chart.appendChild(state.gaugeChartUnits);
5207 state.gaugeChartMin = document.createElement('span');
5208 state.gaugeChartMin.className = 'gaugeChartMin';
5209 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5210 state.element_chart.appendChild(state.gaugeChartMin);
5212 state.gaugeChartMax = document.createElement('span');
5213 state.gaugeChartMax.className = 'gaugeChartMax';
5214 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5215 state.element_chart.appendChild(state.gaugeChartMax);
5217 // when we just re-create the chart
5218 // do not animate the first update
5220 if(typeof state.gauge_instance !== 'undefined')
5223 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5225 state.___gaugeOld__ = {
5234 // we will always feed a percentage
5235 state.gauge_instance.minValue = 0;
5236 state.gauge_instance.maxValue = 100;
5238 NETDATA.gaugeAnimation(state, animate);
5239 NETDATA.gaugeSet(state, value, 0, max);
5240 NETDATA.gaugeSetLabels(state, value, 0, max);
5241 NETDATA.gaugeAnimation(state, true);
5245 // ----------------------------------------------------------------------------------------------------------------
5246 // Charts Libraries Registration
5248 NETDATA.chartLibraries = {
5250 initialize: NETDATA.dygraphInitialize,
5251 create: NETDATA.dygraphChartCreate,
5252 update: NETDATA.dygraphChartUpdate,
5253 resize: function(state) {
5254 if(typeof state.dygraph_instance.resize === 'function')
5255 state.dygraph_instance.resize();
5257 setSelection: NETDATA.dygraphSetSelection,
5258 clearSelection: NETDATA.dygraphClearSelection,
5259 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5262 format: function(state) { return 'json'; },
5263 options: function(state) { return 'ms|flip'; },
5264 legend: function(state) {
5265 if(this.isSparkline(state) === false)
5266 return 'right-side';
5270 autoresize: function(state) { return true; },
5271 max_updates_to_recreate: function(state) { return 5000; },
5272 track_colors: function(state) { return true; },
5273 pixels_per_point: function(state) {
5274 if(this.isSparkline(state) === false)
5280 isSparkline: function(state) {
5281 if(typeof state.dygraph_sparkline === 'undefined') {
5282 var t = $(state.element).data('dygraph-theme');
5283 if(t === 'sparkline')
5284 state.dygraph_sparkline = true;
5286 state.dygraph_sparkline = false;
5288 return state.dygraph_sparkline;
5292 initialize: NETDATA.sparklineInitialize,
5293 create: NETDATA.sparklineChartCreate,
5294 update: NETDATA.sparklineChartUpdate,
5296 setSelection: undefined, // function(state, t) { return true; },
5297 clearSelection: undefined, // function(state) { return true; },
5298 toolboxPanAndZoom: null,
5301 format: function(state) { return 'array'; },
5302 options: function(state) { return 'flip|abs'; },
5303 legend: function(state) { return null; },
5304 autoresize: function(state) { return false; },
5305 max_updates_to_recreate: function(state) { return 5000; },
5306 track_colors: function(state) { return false; },
5307 pixels_per_point: function(state) { return 3; }
5310 initialize: NETDATA.peityInitialize,
5311 create: NETDATA.peityChartCreate,
5312 update: NETDATA.peityChartUpdate,
5314 setSelection: undefined, // function(state, t) { return true; },
5315 clearSelection: undefined, // function(state) { return true; },
5316 toolboxPanAndZoom: null,
5319 format: function(state) { return 'ssvcomma'; },
5320 options: function(state) { return 'null2zero|flip|abs'; },
5321 legend: function(state) { return null; },
5322 autoresize: function(state) { return false; },
5323 max_updates_to_recreate: function(state) { return 5000; },
5324 track_colors: function(state) { return false; },
5325 pixels_per_point: function(state) { return 3; }
5328 initialize: NETDATA.morrisInitialize,
5329 create: NETDATA.morrisChartCreate,
5330 update: NETDATA.morrisChartUpdate,
5332 setSelection: undefined, // function(state, t) { return true; },
5333 clearSelection: undefined, // function(state) { return true; },
5334 toolboxPanAndZoom: null,
5337 format: function(state) { return 'json'; },
5338 options: function(state) { return 'objectrows|ms'; },
5339 legend: function(state) { return null; },
5340 autoresize: function(state) { return false; },
5341 max_updates_to_recreate: function(state) { return 50; },
5342 track_colors: function(state) { return false; },
5343 pixels_per_point: function(state) { return 15; }
5346 initialize: NETDATA.googleInitialize,
5347 create: NETDATA.googleChartCreate,
5348 update: NETDATA.googleChartUpdate,
5350 setSelection: undefined, //function(state, t) { return true; },
5351 clearSelection: undefined, //function(state) { return true; },
5352 toolboxPanAndZoom: null,
5355 format: function(state) { return 'datatable'; },
5356 options: function(state) { return ''; },
5357 legend: function(state) { return null; },
5358 autoresize: function(state) { return false; },
5359 max_updates_to_recreate: function(state) { return 300; },
5360 track_colors: function(state) { return false; },
5361 pixels_per_point: function(state) { return 4; }
5364 initialize: NETDATA.raphaelInitialize,
5365 create: NETDATA.raphaelChartCreate,
5366 update: NETDATA.raphaelChartUpdate,
5368 setSelection: undefined, // function(state, t) { return true; },
5369 clearSelection: undefined, // function(state) { return true; },
5370 toolboxPanAndZoom: null,
5373 format: function(state) { return 'json'; },
5374 options: function(state) { return ''; },
5375 legend: function(state) { return null; },
5376 autoresize: function(state) { return false; },
5377 max_updates_to_recreate: function(state) { return 5000; },
5378 track_colors: function(state) { return false; },
5379 pixels_per_point: function(state) { return 3; }
5382 initialize: NETDATA.c3Initialize,
5383 create: NETDATA.c3ChartCreate,
5384 update: NETDATA.c3ChartUpdate,
5386 setSelection: undefined, // function(state, t) { return true; },
5387 clearSelection: undefined, // function(state) { return true; },
5388 toolboxPanAndZoom: null,
5391 format: function(state) { return 'csvjsonarray'; },
5392 options: function(state) { return 'milliseconds'; },
5393 legend: function(state) { return null; },
5394 autoresize: function(state) { return false; },
5395 max_updates_to_recreate: function(state) { return 5000; },
5396 track_colors: function(state) { return false; },
5397 pixels_per_point: function(state) { return 15; }
5400 initialize: NETDATA.d3Initialize,
5401 create: NETDATA.d3ChartCreate,
5402 update: NETDATA.d3ChartUpdate,
5404 setSelection: undefined, // function(state, t) { return true; },
5405 clearSelection: undefined, // function(state) { return true; },
5406 toolboxPanAndZoom: null,
5409 format: function(state) { return 'json'; },
5410 options: function(state) { return ''; },
5411 legend: function(state) { return null; },
5412 autoresize: function(state) { return false; },
5413 max_updates_to_recreate: function(state) { return 5000; },
5414 track_colors: function(state) { return false; },
5415 pixels_per_point: function(state) { return 3; }
5418 initialize: NETDATA.easypiechartInitialize,
5419 create: NETDATA.easypiechartChartCreate,
5420 update: NETDATA.easypiechartChartUpdate,
5422 setSelection: NETDATA.easypiechartSetSelection,
5423 clearSelection: NETDATA.easypiechartClearSelection,
5424 toolboxPanAndZoom: null,
5427 format: function(state) { return 'array'; },
5428 options: function(state) { return 'absolute'; },
5429 legend: function(state) { return null; },
5430 autoresize: function(state) { return false; },
5431 max_updates_to_recreate: function(state) { return 5000; },
5432 track_colors: function(state) { return true; },
5433 pixels_per_point: function(state) { return 3; },
5437 initialize: NETDATA.gaugeInitialize,
5438 create: NETDATA.gaugeChartCreate,
5439 update: NETDATA.gaugeChartUpdate,
5441 setSelection: NETDATA.gaugeSetSelection,
5442 clearSelection: NETDATA.gaugeClearSelection,
5443 toolboxPanAndZoom: null,
5446 format: function(state) { return 'array'; },
5447 options: function(state) { return 'absolute'; },
5448 legend: function(state) { return null; },
5449 autoresize: function(state) { return false; },
5450 max_updates_to_recreate: function(state) { return 5000; },
5451 track_colors: function(state) { return true; },
5452 pixels_per_point: function(state) { return 3; },
5457 NETDATA.registerChartLibrary = function(library, url) {
5458 if(NETDATA.options.debug.libraries === true)
5459 console.log("registering chart library: " + library);
5461 NETDATA.chartLibraries[library].url = url;
5462 NETDATA.chartLibraries[library].initialized = true;
5463 NETDATA.chartLibraries[library].enabled = true;
5466 // ----------------------------------------------------------------------------------------------------------------
5467 // Load required JS libraries and CSS
5469 NETDATA.requiredJs = [
5471 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5472 isAlreadyLoaded: function() {
5473 // check if bootstrap is loaded
5474 if(typeof $().emulateTransitionEnd == 'function')
5477 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5485 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5486 isAlreadyLoaded: function() { return false; }
5489 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5490 isAlreadyLoaded: function() { return false; }
5494 NETDATA.requiredCSS = [
5496 url: NETDATA.themes.current.bootstrap_css,
5497 isAlreadyLoaded: function() {
5498 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5505 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5506 isAlreadyLoaded: function() { return false; }
5509 url: NETDATA.themes.current.dashboard_css,
5510 isAlreadyLoaded: function() { return false; }
5513 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5514 isAlreadyLoaded: function() { return false; }
5518 NETDATA.loadRequiredJs = function(index, callback) {
5519 if(index >= NETDATA.requiredJs.length) {
5520 if(typeof callback === 'function')
5525 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5526 NETDATA.loadRequiredJs(++index, callback);
5530 if(NETDATA.options.debug.main_loop === true)
5531 console.log('loading ' + NETDATA.requiredJs[index].url);
5534 url: NETDATA.requiredJs[index].url,
5537 xhrFields: { withCredentials: true } // required for the cookie
5539 .success(function() {
5540 if(NETDATA.options.debug.main_loop === true)
5541 console.log('loaded ' + NETDATA.requiredJs[index].url);
5543 NETDATA.loadRequiredJs(++index, callback);
5546 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5550 NETDATA.loadRequiredCSS = function(index) {
5551 if(index >= NETDATA.requiredCSS.length)
5554 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5555 NETDATA.loadRequiredCSS(++index);
5559 if(NETDATA.options.debug.main_loop === true)
5560 console.log('loading ' + NETDATA.requiredCSS[index].url);
5562 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5563 NETDATA.loadRequiredCSS(++index);
5567 // ----------------------------------------------------------------------------------------------------------------
5568 // Registry of netdata hosts
5574 get: function(what, callback) {
5576 url: NETDATA.serverDefault + '/api/v1/alarms?' + what.toString(),
5579 xhrFields: { withCredentials: true } // required for the cookie
5581 .done(function(data) {
5582 if(typeof callback === 'function')
5586 NETDATA.error(415, host);
5588 if(typeof callback === 'function')
5593 update_forever: function() {
5594 NETDATA.alarms.get('active', function(data) {
5596 NETDATA.alarms.current = data;
5598 if (typeof NETDATA.alarms.callback === 'function') {
5599 NETDATA.alarms.callback(data);
5603 setTimeout(NETDATA.alarms.update_forever, 10000);
5607 get_log: function(callback) {
5609 url: NETDATA.serverDefault + '/api/v1/alarm_log',
5612 xhrFields: { withCredentials: true } // required for the cookie
5614 .done(function(data) {
5615 if(typeof callback === 'function')
5619 NETDATA.error(416, host);
5621 if(typeof callback === 'function')
5627 NETDATA.alarms.update_forever();
5631 // ----------------------------------------------------------------------------------------------------------------
5632 // Registry of netdata hosts
5634 NETDATA.registry = {
5635 server: null, // the netdata registry server
5636 person_guid: null, // the unique ID of this browser / user
5637 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5638 hostname: null, // the hostname of the netdata server that served dashboard.js
5639 machines: null, // the user's other URLs
5640 machines_array: null, // the user's other URLs in an array
5643 parsePersonUrls: function(person_urls) {
5644 // console.log(person_urls);
5645 NETDATA.registry.person_urls = person_urls;
5648 NETDATA.registry.machines = {};
5649 NETDATA.registry.machines_array = new Array();
5651 var now = new Date().getTime();
5652 var apu = person_urls;
5655 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5656 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5662 accesses: apu[i][3],
5664 alternate_urls: new Array()
5666 obj.alternate_urls.push(apu[i][1]);
5668 NETDATA.registry.machines[apu[i][0]] = obj;
5669 NETDATA.registry.machines_array.push(obj);
5672 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5674 var pu = NETDATA.registry.machines[apu[i][0]];
5675 if(pu.last_t < apu[i][2]) {
5677 pu.last_t = apu[i][2];
5678 pu.name = apu[i][4];
5680 pu.accesses += apu[i][3];
5681 pu.alternate_urls.push(apu[i][1]);
5686 if(typeof netdataRegistryCallback === 'function')
5687 netdataRegistryCallback(NETDATA.registry.machines_array);
5691 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5694 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5696 NETDATA.registry.server = data.registry;
5697 NETDATA.registry.machine_guid = data.machine_guid;
5698 NETDATA.registry.hostname = data.hostname;
5700 NETDATA.registry.access(2, function (person_urls) {
5701 NETDATA.registry.parsePersonUrls(person_urls);
5708 hello: function(host, callback) {
5709 while(host.slice(-1) === '/')
5710 host = host.substring(0, host.length - 1);
5712 // send HELLO to a netdata server:
5713 // 1. verifies the server is reachable
5714 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5716 url: host + '/api/v1/registry?action=hello',
5719 xhrFields: { withCredentials: true } // required for the cookie
5721 .done(function(data) {
5722 if(typeof data.status !== 'string' || data.status !== 'ok') {
5723 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5727 if(typeof callback === 'function')
5731 NETDATA.error(407, host);
5733 if(typeof callback === 'function')
5738 access: function(max_redirects, callback) {
5739 // send ACCESS to a netdata registry:
5740 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5741 // 2. it responds with a list of netdata servers we know
5742 // the registry identifies us using a cookie it sets the first time we access it
5743 // the registry may respond with a redirect URL to send us to another registry
5745 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),
5748 xhrFields: { withCredentials: true } // required for the cookie
5750 .done(function(data) {
5751 var redirect = null;
5752 if(typeof data.registry === 'string')
5753 redirect = data.registry;
5755 if(typeof data.status !== 'string' || data.status !== 'ok') {
5756 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5761 if(redirect !== null && max_redirects > 0) {
5762 NETDATA.registry.server = redirect;
5763 NETDATA.registry.access(max_redirects - 1, callback);
5766 if(typeof callback === 'function')
5771 if(typeof data.person_guid === 'string')
5772 NETDATA.registry.person_guid = data.person_guid;
5774 if(typeof callback === 'function')
5775 callback(data.urls);
5779 NETDATA.error(410, NETDATA.registry.server);
5781 if(typeof callback === 'function')
5786 delete: function(delete_url, callback) {
5787 // send DELETE to a netdata registry:
5789 url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
5792 xhrFields: { withCredentials: true } // required for the cookie
5794 .done(function(data) {
5795 if(typeof data.status !== 'string' || data.status !== 'ok') {
5796 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5800 if(typeof callback === 'function')
5804 NETDATA.error(412, NETDATA.registry.server);
5806 if(typeof callback === 'function')
5811 switch: function(new_person_guid, callback) {
5814 url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
5817 xhrFields: { withCredentials: true } // required for the cookie
5819 .done(function(data) {
5820 if(typeof data.status !== 'string' || data.status !== 'ok') {
5821 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5825 if(typeof callback === 'function')
5829 NETDATA.error(414, NETDATA.registry.server);
5831 if(typeof callback === 'function')
5837 // ----------------------------------------------------------------------------------------------------------------
5840 NETDATA.errorReset();
5841 NETDATA.loadRequiredCSS(0);
5843 NETDATA._loadjQuery(function() {
5844 NETDATA.loadRequiredJs(0, function() {
5845 if(typeof $().emulateTransitionEnd !== 'function') {
5846 // bootstrap is not available
5847 NETDATA.options.current.show_help = false;
5850 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5851 if(NETDATA.options.debug.main_loop === true)
5852 console.log('starting chart refresh thread');
5859 // window.NETDATA = NETDATA;
5860 // })(window, document);