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 = false;
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 abort_ajax_on_scroll: false,
305 setOptionCallback: function() { ; }
313 chart_data_url: false,
314 chart_errors: false, // FIXME
322 NETDATA.statistics = {
325 refreshes_active_max: 0
329 // ----------------------------------------------------------------------------------------------------------------
330 // local storage options
332 NETDATA.localStorage = {
335 callback: {} // only used for resetting back to defaults
338 NETDATA.localStorageGet = function(key, def, callback) {
341 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
342 NETDATA.localStorage.default[key.toString()] = def;
343 NETDATA.localStorage.callback[key.toString()] = callback;
346 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
348 // console.log('localStorage: loading "' + key.toString() + '"');
349 ret = localStorage.getItem(key.toString());
350 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
351 if(ret === null || ret === 'undefined') {
352 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
353 localStorage.setItem(key.toString(), JSON.stringify(def));
357 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
358 ret = JSON.parse(ret);
359 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
363 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
368 if(typeof ret === 'undefined' || ret === 'undefined') {
369 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
373 NETDATA.localStorage.current[key.toString()] = ret;
377 NETDATA.localStorageSet = function(key, value, callback) {
378 if(typeof value === 'undefined' || value === 'undefined') {
379 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
382 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
383 NETDATA.localStorage.default[key.toString()] = value;
384 NETDATA.localStorage.current[key.toString()] = value;
385 NETDATA.localStorage.callback[key.toString()] = callback;
388 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
389 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
391 localStorage.setItem(key.toString(), JSON.stringify(value));
394 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
398 NETDATA.localStorage.current[key.toString()] = value;
402 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
404 if(typeof obj[i] === 'object') {
405 //console.log('object ' + prefix + '.' + i.toString());
406 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
410 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
414 NETDATA.setOption = function(key, value) {
415 if(key.toString() === 'setOptionCallback') {
416 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
417 NETDATA.options.current[key.toString()] = value;
418 NETDATA.options.current.setOptionCallback();
421 else if(NETDATA.options.current[key.toString()] !== value) {
422 var name = 'options.' + key.toString();
424 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
425 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
427 //console.log(NETDATA.localStorage);
428 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
429 //console.log(NETDATA.options);
430 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
432 if(typeof NETDATA.options.current.setOptionCallback === 'function')
433 NETDATA.options.current.setOptionCallback();
439 NETDATA.getOption = function(key) {
440 return NETDATA.options.current[key.toString()];
443 // read settings from local storage
444 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
446 // always start with this option enabled.
447 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
449 NETDATA.resetOptions = function() {
450 for(var i in NETDATA.localStorage.default) {
451 var a = i.split('.');
453 if(a[0] === 'options') {
454 if(a[1] === 'setOptionCallback') continue;
455 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
456 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
458 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
460 else if(a[0] === 'chart_heights') {
461 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
462 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
468 // ----------------------------------------------------------------------------------------------------------------
470 if(NETDATA.options.debug.main_loop === true)
471 console.log('welcome to NETDATA');
473 NETDATA.onresize = function() {
474 NETDATA.options.last_resized = new Date().getTime();
478 NETDATA.onscroll = function() {
479 // console.log('onscroll');
481 NETDATA.options.last_page_scroll = new Date().getTime();
482 NETDATA.options.auto_refresher_stop_until = 0;
484 if(NETDATA.options.targets === null) return;
486 // when the user scrolls he sees that we have
487 // hidden all the not-visible charts
488 // using this little function we try to switch
489 // the charts back to visible quickly
490 var targets = NETDATA.options.targets;
491 var len = targets.length;
492 if(NETDATA.options.abort_ajax_on_scroll === true) {
494 if (targets[len]._updating === true) {
495 if (typeof targets[len].xhr !== 'undefined') {
496 targets[len].xhr.abort();
497 targets[len].running = false;
498 targets[len]._updating = false;
500 targets[len].isVisible();
506 targets[len].isVisible();
510 window.onresize = NETDATA.onresize;
511 window.onscroll = NETDATA.onscroll;
513 // ----------------------------------------------------------------------------------------------------------------
516 NETDATA.errorCodes = {
517 100: { message: "Cannot load chart library", alert: true },
518 101: { message: "Cannot load jQuery", alert: true },
519 402: { message: "Chart library not found", alert: false },
520 403: { message: "Chart library not enabled/is failed", alert: false },
521 404: { message: "Chart not found", alert: false },
522 405: { message: "Cannot download charts index from server", alert: true },
523 406: { message: "Invalid charts index downloaded from server", alert: true },
524 407: { message: "Cannot HELLO netdata server", alert: false },
525 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
526 409: { message: "Cannot ACCESS netdata registry", alert: false },
527 410: { message: "Netdata registry ACCESS failed", alert: false },
528 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
529 412: { message: "Netdata registry DELETE failed", alert: false },
530 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
531 414: { message: "Netdata registry SWITCH failed", alert: false },
532 415: { message: "Netdata alarms download failed", alert: false },
533 416: { message: "Netdata alarms log download failed", alert: false }
535 NETDATA.errorLast = {
541 NETDATA.error = function(code, msg) {
542 NETDATA.errorLast.code = code;
543 NETDATA.errorLast.message = msg;
544 NETDATA.errorLast.datetime = new Date().getTime();
546 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
549 if(typeof netdataErrorCallback === 'function') {
550 ret = netdataErrorCallback('system', code, msg);
553 if(ret && NETDATA.errorCodes[code].alert)
554 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
557 NETDATA.errorReset = function() {
558 NETDATA.errorLast.code = 0;
559 NETDATA.errorLast.message = "You are doing fine!";
560 NETDATA.errorLast.datetime = 0;
563 // ----------------------------------------------------------------------------------------------------------------
566 // When multiple charts need the same chart, we avoid downloading it
567 // multiple times (and having it in browser memory multiple time)
568 // by using this registry.
570 // Every time we download a chart definition, we save it here with .add()
571 // Then we try to get it back with .get(). If that fails, we download it.
573 NETDATA.chartRegistry = {
576 fixid: function(id) {
577 return id.replace(/:/g, "_").replace(/\//g, "_");
580 add: function(host, id, data) {
581 host = this.fixid(host);
584 if(typeof this.charts[host] === 'undefined')
585 this.charts[host] = {};
587 //console.log('added ' + host + '/' + id);
588 this.charts[host][id] = data;
591 get: function(host, id) {
592 host = this.fixid(host);
595 if(typeof this.charts[host] === 'undefined')
598 if(typeof this.charts[host][id] === 'undefined')
601 //console.log('cached ' + host + '/' + id);
602 return this.charts[host][id];
605 downloadAll: function(host, callback) {
606 while(host.slice(-1) === '/')
607 host = host.substring(0, host.length - 1);
612 url: host + '/api/v1/charts',
615 xhrFields: { withCredentials: true } // required for the cookie
617 .done(function(data) {
619 var h = NETDATA.chartRegistry.fixid(host);
620 self.charts[h] = data.charts;
622 else NETDATA.error(406, host + '/api/v1/charts');
624 if(typeof callback === 'function')
628 NETDATA.error(405, host + '/api/v1/charts');
630 if(typeof callback === 'function')
636 // ----------------------------------------------------------------------------------------------------------------
637 // Global Pan and Zoom on charts
639 // Using this structure are synchronize all the charts, so that
640 // when you pan or zoom one, all others are automatically refreshed
641 // to the same timespan.
643 NETDATA.globalPanAndZoom = {
644 seq: 0, // timestamp ms
645 // every time a chart is panned or zoomed
646 // we set the timestamp here
647 // then we use it as a sequence number
648 // to find if other charts are syncronized
651 master: null, // the master chart (state), to which all others
654 force_before_ms: null, // the timespan to sync all other charts
655 force_after_ms: null,
660 setMaster: function(state, after, before) {
661 if(NETDATA.options.current.sync_pan_and_zoom === false)
664 if(this.master !== null && this.master !== state)
665 this.master.resetChart(true, true);
667 var now = new Date().getTime();
670 this.force_after_ms = after;
671 this.force_before_ms = before;
672 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
674 if(typeof this.callback === 'function')
675 this.callback(true, after, before);
679 clearMaster: function() {
680 if(this.master !== null) {
681 var st = this.master;
688 this.force_after_ms = null;
689 this.force_before_ms = null;
690 NETDATA.options.auto_refresher_stop_until = 0;
692 if(typeof this.callback === 'function')
693 this.callback(false, 0, 0);
696 // is the given state the master of the global
697 // pan and zoom sync?
698 isMaster: function(state) {
699 if(this.master === state) return true;
703 // are we currently have a global pan and zoom sync?
704 isActive: function() {
705 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
709 // check if a chart, other than the master
710 // needs to be refreshed, due to the global pan and zoom
711 shouldBeAutoRefreshed: function(state) {
712 if(this.master === null || this.seq === 0)
715 //if(state.needsRecreation())
718 if(state.tm.pan_and_zoom_seq === this.seq)
725 // ----------------------------------------------------------------------------------------------------------------
726 // dimensions selection
729 // move color assignment to dimensions, here
731 dimensionStatus = function(parent, label, name_div, value_div, color) {
732 this.enabled = false;
733 this.parent = parent;
735 this.name_div = null;
736 this.value_div = null;
737 this.color = NETDATA.themes.current.foreground;
739 if(parent.selected_count > parent.unselected_count)
740 this.selected = true;
742 this.selected = false;
744 this.setOptions(name_div, value_div, color);
747 dimensionStatus.prototype.invalidate = function() {
748 this.name_div = null;
749 this.value_div = null;
750 this.enabled = false;
753 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
756 if(this.name_div != name_div) {
757 this.name_div = name_div;
758 this.name_div.title = this.label;
759 this.name_div.style.color = this.color;
760 if(this.selected === false)
761 this.name_div.className = 'netdata-legend-name not-selected';
763 this.name_div.className = 'netdata-legend-name selected';
766 if(this.value_div != value_div) {
767 this.value_div = value_div;
768 this.value_div.title = this.label;
769 this.value_div.style.color = this.color;
770 if(this.selected === false)
771 this.value_div.className = 'netdata-legend-value not-selected';
773 this.value_div.className = 'netdata-legend-value selected';
780 dimensionStatus.prototype.setHandler = function() {
781 if(this.enabled === false) return;
785 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
786 this.name_div.onclick = this.value_div.onclick = function(e) {
788 if(ds.isSelected()) {
790 if(e.shiftKey === true || e.ctrlKey === true) {
791 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
794 if(ds.parent.countSelected() === 0)
795 ds.parent.selectAll();
798 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
799 if(ds.parent.countSelected() === 1) {
800 ds.parent.selectAll();
803 ds.parent.selectNone();
809 // this is not selected
810 if(e.shiftKey === true || e.ctrlKey === true) {
811 // control or shift key is pressed -> select this too
815 // no key is pressed -> select only this
816 ds.parent.selectNone();
821 ds.parent.state.redrawChart();
825 dimensionStatus.prototype.select = function() {
826 if(this.enabled === false) return;
828 this.name_div.className = 'netdata-legend-name selected';
829 this.value_div.className = 'netdata-legend-value selected';
830 this.selected = true;
833 dimensionStatus.prototype.unselect = function() {
834 if(this.enabled === false) return;
836 this.name_div.className = 'netdata-legend-name not-selected';
837 this.value_div.className = 'netdata-legend-value hidden';
838 this.selected = false;
841 dimensionStatus.prototype.isSelected = function() {
842 return(this.enabled === true && this.selected === true);
845 // ----------------------------------------------------------------------------------------------------------------
847 dimensionsVisibility = function(state) {
850 this.dimensions = {};
851 this.selected_count = 0;
852 this.unselected_count = 0;
855 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
856 if(typeof this.dimensions[label] === 'undefined') {
858 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
861 this.dimensions[label].setOptions(name_div, value_div, color);
863 return this.dimensions[label];
866 dimensionsVisibility.prototype.dimensionGet = function(label) {
867 return this.dimensions[label];
870 dimensionsVisibility.prototype.invalidateAll = function() {
871 for(var d in this.dimensions)
872 this.dimensions[d].invalidate();
875 dimensionsVisibility.prototype.selectAll = function() {
876 for(var d in this.dimensions)
877 this.dimensions[d].select();
880 dimensionsVisibility.prototype.countSelected = function() {
882 for(var d in this.dimensions)
883 if(this.dimensions[d].isSelected()) i++;
888 dimensionsVisibility.prototype.selectNone = function() {
889 for(var d in this.dimensions)
890 this.dimensions[d].unselect();
893 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
894 var ret = new Array();
895 this.selected_count = 0;
896 this.unselected_count = 0;
898 for(var i = 0, len = array.length; i < len ; i++) {
899 var ds = this.dimensions[array[i]];
900 if(typeof ds === 'undefined') {
901 // console.log(array[i] + ' is not found');
906 if(ds.isSelected()) {
908 this.selected_count++;
912 this.unselected_count++;
916 if(this.selected_count === 0 && this.unselected_count !== 0) {
918 return this.selected2BooleanArray(array);
925 // ----------------------------------------------------------------------------------------------------------------
926 // global selection sync
928 NETDATA.globalSelectionSync = {
935 if(this.state !== null)
936 this.state.globalSelectionSyncStop();
940 if(this.state !== null) {
941 this.state.globalSelectionSyncDelay();
946 // ----------------------------------------------------------------------------------------------------------------
947 // Our state object, where all per-chart values are stored
949 chartState = function(element) {
950 var self = $(element);
951 this.element = element;
954 // all private functions should use 'that', instead of 'this'
958 * show an error instead of the chart
960 var error = function(msg) {
963 if(typeof netdataErrorCallback === 'function') {
964 ret = netdataErrorCallback('chart', that.id, msg);
968 that.element.innerHTML = that.id + ': ' + msg;
969 that.enabled = false;
970 that.current = that.pan;
974 // GUID - a unique identifier for the chart
975 this.uuid = NETDATA.guid();
977 // string - the name of chart
978 this.id = self.data('netdata');
980 // string - the key for localStorage settings
981 this.settings_id = self.data('id') || null;
983 // the user given dimensions of the element
984 this.width = self.data('width') || NETDATA.chartDefaults.width;
985 this.height = self.data('height') || NETDATA.chartDefaults.height;
987 if(this.settings_id !== null) {
988 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
989 // this is the callback that will be called
990 // if and when the user resets all localStorage variables
993 resizeChartToHeight(height);
997 // string - the netdata server URL, without any path
998 this.host = self.data('host') || NETDATA.chartDefaults.host;
1000 // make sure the host does not end with /
1001 // all netdata API requests use absolute paths
1002 while(this.host.slice(-1) === '/')
1003 this.host = this.host.substring(0, this.host.length - 1);
1005 // string - the grouping method requested by the user
1006 this.method = self.data('method') || NETDATA.chartDefaults.method;
1008 // the time-range requested by the user
1009 this.after = self.data('after') || NETDATA.chartDefaults.after;
1010 this.before = self.data('before') || NETDATA.chartDefaults.before;
1012 // the pixels per point requested by the user
1013 this.pixels_per_point = self.data('pixels-per-point') || 1;
1014 this.points = self.data('points') || null;
1016 // the dimensions requested by the user
1017 this.dimensions = self.data('dimensions') || null;
1019 // the chart library requested by the user
1020 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1022 // object - the chart library used
1023 this.library = null;
1027 this.colors_assigned = {};
1028 this.colors_available = null;
1030 // the element already created by the user
1031 this.element_message = null;
1033 // the element with the chart
1034 this.element_chart = null;
1036 // the element with the legend of the chart (if created by us)
1037 this.element_legend = null;
1038 this.element_legend_childs = {
1048 this.chart_url = null; // string - the url to download chart info
1049 this.chart = null; // object - the chart as downloaded from the server
1051 this.title = self.data('title') || null; // the title of the chart
1052 this.units = self.data('units') || null; // the units of the chart dimensions
1053 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1055 this.running = false; // boolean - true when the chart is being refreshed now
1056 this.validated = false; // boolean - has the chart been validated?
1057 this.enabled = true; // boolean - is the chart enabled for refresh?
1058 this.paused = false; // boolean - is the chart paused for any reason?
1059 this.selected = false; // boolean - is the chart shown a selection?
1060 this.debug = false; // boolean - console.log() debug info about this chart
1062 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1063 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1064 this.requested_after = null; // milliseconds - the timestamp of the request after param
1065 this.requested_before = null; // milliseconds - the timestamp of the request before param
1066 this.requested_padding = null;
1067 this.view_after = 0;
1068 this.view_before = 0;
1073 force_update_at: 0, // the timestamp to force the update at
1074 force_before_ms: null,
1075 force_after_ms: null
1080 force_update_at: 0, // the timestamp to force the update at
1081 force_before_ms: null,
1082 force_after_ms: null
1087 force_update_at: 0, // the timestamp to force the update at
1088 force_before_ms: null,
1089 force_after_ms: null
1092 // this is a pointer to one of the sub-classes below
1094 this.current = this.auto;
1096 // check the requested library is available
1097 // we don't initialize it here - it will be initialized when
1098 // this chart will be first used
1099 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1100 NETDATA.error(402, that.library_name);
1101 error('chart library "' + that.library_name + '" is not found');
1104 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1105 NETDATA.error(403, that.library_name);
1106 error('chart library "' + that.library_name + '" is not enabled');
1110 that.library = NETDATA.chartLibraries[that.library_name];
1112 // milliseconds - the time the last refresh took
1113 this.refresh_dt_ms = 0;
1115 // if we need to report the rendering speed
1116 // find the element that needs to be updated
1117 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1119 if(refresh_dt_element_name !== null)
1120 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1122 this.refresh_dt_element = null;
1124 this.dimensions_visibility = new dimensionsVisibility(this);
1126 this._updating = false;
1128 // ============================================================================================================
1129 // PRIVATE FUNCTIONS
1131 var createDOM = function() {
1132 if(that.enabled === false) return;
1134 if(that.element_message !== null) that.element_message.innerHTML = '';
1135 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1136 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1138 that.element.innerHTML = '';
1140 that.element_message = document.createElement('div');
1141 that.element_message.className = ' netdata-message hidden';
1142 that.element.appendChild(that.element_message);
1144 that.element_chart = document.createElement('div');
1145 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1146 that.element.appendChild(that.element_chart);
1148 if(that.hasLegend() === true) {
1149 that.element.className = "netdata-container-with-legend";
1150 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1152 that.element_legend = document.createElement('div');
1153 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1154 that.element.appendChild(that.element_legend);
1157 that.element.className = "netdata-container";
1158 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1160 that.element_legend = null;
1162 that.element_legend_childs.series = null;
1164 if(typeof(that.width) === 'string')
1165 $(that.element).css('width', that.width);
1166 else if(typeof(that.width) === 'number')
1167 $(that.element).css('width', that.width + 'px');
1169 if(typeof(that.library.aspect_ratio) === 'undefined') {
1170 if(typeof(that.height) === 'string')
1171 $(that.element).css('height', that.height);
1172 else if(typeof(that.height) === 'number')
1173 $(that.element).css('height', that.height + 'px');
1176 var w = that.element.offsetWidth;
1177 if(w === null || w === 0) {
1178 // the div is hidden
1179 // this will resize the chart when next viewed
1180 that.tm.last_resized = 0;
1183 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1186 if(NETDATA.chartDefaults.min_width !== null)
1187 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1189 that.tm.last_dom_created = new Date().getTime();
1195 * initialize state variables
1196 * destroy all (possibly) created state elements
1197 * create the basic DOM for a chart
1199 var init = function() {
1200 if(that.enabled === false) return;
1202 that.paused = false;
1203 that.selected = false;
1205 that.chart_created = false; // boolean - is the library.create() been called?
1206 that.updates_counter = 0; // numeric - the number of refreshes made so far
1207 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1208 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1211 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1212 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1213 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1215 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1216 last_updated: 0, // the timestamp the chart last updated with data
1217 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1219 // Used with NETDATA.globalPanAndZoom.seq
1220 last_visible_check: 0, // the time we last checked if it is visible
1221 last_resized: 0, // the time the chart was resized
1222 last_hidden: 0, // the time the chart was hidden
1223 last_unhidden: 0, // the time the chart was unhidden
1224 last_autorefreshed: 0 // the time the chart was last refreshed
1227 that.data = null; // the last data as downloaded from the netdata server
1228 that.data_url = 'invalid://'; // string - the last url used to update the chart
1229 that.data_points = 0; // number - the number of points returned from netdata
1230 that.data_after = 0; // milliseconds - the first timestamp of the data
1231 that.data_before = 0; // milliseconds - the last timestamp of the data
1232 that.data_update_every = 0; // milliseconds - the frequency to update the data
1234 that.tm.last_initialized = new Date().getTime();
1237 that.setMode('auto');
1240 var maxMessageFontSize = function() {
1241 // normally we want a font size, as tall as the element
1242 var h = that.element_message.clientHeight;
1244 // but give it some air, 20% let's say, or 5 pixels min
1245 var lost = Math.max(h * 0.2, 5);
1248 // center the text, vertically
1249 var paddingTop = (lost - 5) / 2;
1251 // but check the width too
1252 // it should fit 10 characters in it
1253 var w = that.element_message.clientWidth / 10;
1255 paddingTop += (h - w) / 2;
1259 // and don't make it too huge
1260 // 5% of the screen size is good
1261 if(h > screen.height / 20) {
1262 paddingTop += (h - (screen.height / 20)) / 2;
1263 h = screen.height / 20;
1267 that.element_message.style.fontSize = h.toString() + 'px';
1268 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1271 var showMessage = function(msg) {
1272 that.element_message.className = 'netdata-message';
1273 that.element_message.innerHTML = msg;
1274 that.element_message.style.fontSize = 'x-small';
1275 that.element_message.style.paddingTop = '0px';
1276 that.___messageHidden___ = undefined;
1279 var showMessageIcon = function(icon) {
1280 that.element_message.innerHTML = icon;
1281 that.element_message.className = 'netdata-message icon';
1282 maxMessageFontSize();
1283 that.___messageHidden___ = undefined;
1286 var hideMessage = function() {
1287 if(typeof that.___messageHidden___ === 'undefined') {
1288 that.___messageHidden___ = true;
1289 that.element_message.className = 'netdata-message hidden';
1293 var showRendering = function() {
1295 if(that.chart !== null) {
1296 if(that.chart.chart_type === 'line')
1297 icon = '<i class="fa fa-line-chart"></i>';
1299 icon = '<i class="fa fa-area-chart"></i>';
1302 icon = '<i class="fa fa-area-chart"></i>';
1304 showMessageIcon(icon + ' netdata');
1307 var showLoading = function() {
1308 if(that.chart_created === false) {
1309 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1315 var isHidden = function() {
1316 if(typeof that.___chartIsHidden___ !== 'undefined')
1322 // hide the chart, when it is not visible - called from isVisible()
1323 var hideChart = function() {
1324 // hide it, if it is not already hidden
1325 if(isHidden() === true) return;
1327 if(that.chart_created === true) {
1328 if(NETDATA.options.current.destroy_on_hide === true) {
1329 // we should destroy it
1334 that.element_chart.style.display = 'none';
1335 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1336 that.tm.last_hidden = new Date().getTime();
1339 // This works, but I not sure there are no corner cases somewhere
1340 // so it is commented - if the user has memory issues he can
1341 // set Destroy on Hide for all charts
1342 // that.data = null;
1346 that.___chartIsHidden___ = true;
1349 // unhide the chart, when it is visible - called from isVisible()
1350 var unhideChart = function() {
1351 if(isHidden() === false) return;
1353 that.___chartIsHidden___ = undefined;
1354 that.updates_since_last_unhide = 0;
1356 if(that.chart_created === false) {
1357 // we need to re-initialize it, to show our background
1358 // logo in bootstrap tabs, until the chart loads
1362 that.tm.last_unhidden = new Date().getTime();
1363 that.element_chart.style.display = '';
1364 if(that.element_legend !== null) that.element_legend.style.display = '';
1370 var canBeRendered = function() {
1371 if(isHidden() === true || that.isVisible(true) === false)
1377 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1378 var callChartLibraryUpdateSafely = function(data) {
1381 if(canBeRendered() === false)
1384 if(NETDATA.options.debug.chart_errors === true)
1385 status = that.library.update(that, data);
1388 status = that.library.update(that, data);
1395 if(status === false) {
1396 error('chart failed to be updated as ' + that.library_name);
1403 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1404 var callChartLibraryCreateSafely = function(data) {
1407 if(canBeRendered() === false)
1410 if(NETDATA.options.debug.chart_errors === true)
1411 status = that.library.create(that, data);
1414 status = that.library.create(that, data);
1421 if(status === false) {
1422 error('chart failed to be created as ' + that.library_name);
1426 that.chart_created = true;
1427 that.updates_since_last_creation = 0;
1431 // ----------------------------------------------------------------------------------------------------------------
1434 // resizeChart() - private
1435 // to be called just before the chart library to make sure that
1436 // a properly sized dom is available
1437 var resizeChart = function() {
1438 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1439 if(that.chart_created === false) return;
1441 if(that.needsRecreation()) {
1444 else if(typeof that.library.resize === 'function') {
1445 that.library.resize(that);
1447 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1448 $(that.element_legend_childs.nano).nanoScroller();
1450 maxMessageFontSize();
1453 that.tm.last_resized = new Date().getTime();
1457 // this is the actual chart resize algorithm
1459 // - resize the entire container
1460 // - update the internal states
1461 // - resize the chart as the div changes height
1462 // - update the scrollbar of the legend
1463 var resizeChartToHeight = function(h) {
1465 that.element.style.height = h;
1467 if(that.settings_id !== null)
1468 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1470 var now = new Date().getTime();
1471 NETDATA.options.last_page_scroll = now;
1472 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1475 that.tm.last_resized = 0;
1479 this.resizeHandler = function(e) {
1482 if(typeof this.event_resize === 'undefined'
1483 || this.event_resize.chart_original_w === 'undefined'
1484 || this.event_resize.chart_original_h === 'undefined')
1485 this.event_resize = {
1486 chart_original_w: this.element.clientWidth,
1487 chart_original_h: this.element.clientHeight,
1491 if(e.type === 'touchstart') {
1492 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1493 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1496 this.event_resize.mouse_start_x = e.clientX;
1497 this.event_resize.mouse_start_y = e.clientY;
1500 this.event_resize.chart_start_w = this.element.clientWidth;
1501 this.event_resize.chart_start_h = this.element.clientHeight;
1502 this.event_resize.chart_last_w = this.element.clientWidth;
1503 this.event_resize.chart_last_h = this.element.clientHeight;
1505 var now = new Date().getTime();
1506 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1507 // double click / double tap event
1509 // the optimal height of the chart
1510 // showing the entire legend
1511 var optimal = this.event_resize.chart_last_h
1512 + this.element_legend_childs.content.scrollHeight
1513 - this.element_legend_childs.content.clientHeight;
1515 // if we are not optimal, be optimal
1516 if(this.event_resize.chart_last_h != optimal)
1517 resizeChartToHeight(optimal.toString() + 'px');
1519 // else if we do not have the original height
1520 // reset to the original height
1521 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1522 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1525 this.event_resize.last = now;
1527 // process movement event
1528 document.onmousemove =
1529 document.ontouchmove =
1530 this.element_legend_childs.resize_handler.onmousemove =
1531 this.element_legend_childs.resize_handler.ontouchmove =
1536 case 'mousemove': y = e.clientY; break;
1537 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1541 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1543 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1544 resizeChartToHeight(newH.toString() + 'px');
1545 that.event_resize.chart_last_h = newH;
1550 // process end event
1551 document.onmouseup =
1552 document.ontouchend =
1553 this.element_legend_childs.resize_handler.onmouseup =
1554 this.element_legend_childs.resize_handler.ontouchend =
1556 // remove all the hooks
1557 document.onmouseup =
1558 document.onmousemove =
1559 document.ontouchmove =
1560 document.ontouchend =
1561 that.element_legend_childs.resize_handler.onmousemove =
1562 that.element_legend_childs.resize_handler.ontouchmove =
1563 that.element_legend_childs.resize_handler.onmouseout =
1564 that.element_legend_childs.resize_handler.onmouseup =
1565 that.element_legend_childs.resize_handler.ontouchend =
1568 // allow auto-refreshes
1569 NETDATA.options.auto_refresher_stop_until = 0;
1575 var noDataToShow = function() {
1576 showMessageIcon('<i class="fa fa-warning"></i> empty');
1577 that.legendUpdateDOM();
1578 that.tm.last_autorefreshed = new Date().getTime();
1579 // that.data_update_every = 30 * 1000;
1580 //that.element_chart.style.display = 'none';
1581 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1582 //that.___chartIsHidden___ = true;
1585 // ============================================================================================================
1588 this.error = function(msg) {
1592 this.setMode = function(m) {
1593 if(this.current !== null && this.current.name === m) return;
1596 this.current = this.auto;
1597 else if(m === 'pan')
1598 this.current = this.pan;
1599 else if(m === 'zoom')
1600 this.current = this.zoom;
1602 this.current = this.auto;
1604 this.current.force_update_at = 0;
1605 this.current.force_before_ms = null;
1606 this.current.force_after_ms = null;
1608 this.tm.last_mode_switch = new Date().getTime();
1611 // ----------------------------------------------------------------------------------------------------------------
1612 // global selection sync
1614 // prevent to global selection sync for some time
1615 this.globalSelectionSyncDelay = function(ms) {
1616 if(NETDATA.options.current.sync_selection === false)
1619 if(typeof ms === 'number')
1620 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1622 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1625 // can we globally apply selection sync?
1626 this.globalSelectionSyncAbility = function() {
1627 if(NETDATA.options.current.sync_selection === false)
1630 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1636 this.globalSelectionSyncIsMaster = function() {
1637 if(NETDATA.globalSelectionSync.state === this)
1643 // this chart is the master of the global selection sync
1644 this.globalSelectionSyncBeMaster = function() {
1646 if(this.globalSelectionSyncIsMaster()) {
1647 if(this.debug === true)
1648 this.log('sync: I am the master already.');
1653 if(NETDATA.globalSelectionSync.state) {
1654 if(this.debug === true)
1655 this.log('sync: I am not the sync master. Resetting global sync.');
1657 this.globalSelectionSyncStop();
1660 // become the master
1661 if(this.debug === true)
1662 this.log('sync: becoming sync master.');
1664 this.selected = true;
1665 NETDATA.globalSelectionSync.state = this;
1667 // find the all slaves
1668 var targets = NETDATA.options.targets;
1669 var len = targets.length;
1674 if(this.debug === true)
1675 st.log('sync: not adding me to sync');
1677 else if(st.globalSelectionSyncIsEligible()) {
1678 if(this.debug === true)
1679 st.log('sync: adding to sync as slave');
1681 st.globalSelectionSyncBeSlave();
1685 // this.globalSelectionSyncDelay(100);
1688 // can the chart participate to the global selection sync as a slave?
1689 this.globalSelectionSyncIsEligible = function() {
1690 if(this.enabled === true
1691 && this.library !== null
1692 && typeof this.library.setSelection === 'function'
1693 && this.isVisible() === true
1694 && this.chart_created === true)
1700 // this chart becomes a slave of the global selection sync
1701 this.globalSelectionSyncBeSlave = function() {
1702 if(NETDATA.globalSelectionSync.state !== this)
1703 NETDATA.globalSelectionSync.slaves.push(this);
1706 // sync all the visible charts to the given time
1707 // this is to be called from the chart libraries
1708 this.globalSelectionSync = function(t) {
1709 if(this.globalSelectionSyncAbility() === false) {
1710 if(this.debug === true)
1711 this.log('sync: cannot sync (yet?).');
1716 if(this.globalSelectionSyncIsMaster() === false) {
1717 if(this.debug === true)
1718 this.log('sync: trying to be sync master.');
1720 this.globalSelectionSyncBeMaster();
1722 if(this.globalSelectionSyncAbility() === false) {
1723 if(this.debug === true)
1724 this.log('sync: cannot sync (yet?).');
1730 NETDATA.globalSelectionSync.last_t = t;
1731 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1736 // stop syncing all charts to the given time
1737 this.globalSelectionSyncStop = function() {
1738 if(NETDATA.globalSelectionSync.slaves.length) {
1739 if(this.debug === true)
1740 this.log('sync: cleaning up...');
1742 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1744 if(that.debug === true)
1745 st.log('sync: not adding me to sync stop');
1748 if(that.debug === true)
1749 st.log('sync: removed slave from sync');
1751 st.clearSelection();
1755 NETDATA.globalSelectionSync.last_t = 0;
1756 NETDATA.globalSelectionSync.slaves = [];
1757 NETDATA.globalSelectionSync.state = null;
1760 this.clearSelection();
1763 this.setSelection = function(t) {
1764 if(typeof this.library.setSelection === 'function') {
1765 if(this.library.setSelection(this, t) === true)
1766 this.selected = true;
1768 this.selected = false;
1770 else this.selected = true;
1772 if(this.selected === true && this.debug === true)
1773 this.log('selection set to ' + t.toString());
1775 return this.selected;
1778 this.clearSelection = function() {
1779 if(this.selected === true) {
1780 if(typeof this.library.clearSelection === 'function') {
1781 if(this.library.clearSelection(this) === true)
1782 this.selected = false;
1784 this.selected = true;
1786 else this.selected = false;
1788 if(this.selected === false && this.debug === true)
1789 this.log('selection cleared');
1794 return this.selected;
1797 // find if a timestamp (ms) is shown in the current chart
1798 this.timeIsVisible = function(t) {
1799 if(t >= this.data_after && t <= this.data_before)
1804 this.calculateRowForTime = function(t) {
1805 if(this.timeIsVisible(t) === false) return -1;
1806 return Math.floor((t - this.data_after) / this.data_update_every);
1809 // ----------------------------------------------------------------------------------------------------------------
1812 this.log = function(msg) {
1813 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1816 this.pauseChart = function() {
1817 if(this.paused === false) {
1818 if(this.debug === true)
1819 this.log('pauseChart()');
1825 this.unpauseChart = function() {
1826 if(this.paused === true) {
1827 if(this.debug === true)
1828 this.log('unpauseChart()');
1830 this.paused = false;
1834 this.resetChart = function(dont_clear_master, dont_update) {
1835 if(this.debug === true)
1836 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1838 if(typeof dont_clear_master === 'undefined')
1839 dont_clear_master = false;
1841 if(typeof dont_update === 'undefined')
1842 dont_update = false;
1844 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1845 if(this.debug === true)
1846 this.log('resetChart() diverting to clearMaster().');
1847 // this will call us back with master === true
1848 NETDATA.globalPanAndZoom.clearMaster();
1852 this.clearSelection();
1854 this.tm.pan_and_zoom_seq = 0;
1856 this.setMode('auto');
1857 this.current.force_update_at = 0;
1858 this.current.force_before_ms = null;
1859 this.current.force_after_ms = null;
1860 this.tm.last_autorefreshed = 0;
1861 this.paused = false;
1862 this.selected = false;
1863 this.enabled = true;
1864 // this.debug = false;
1866 // do not update the chart here
1867 // or the chart will flip-flop when it is the master
1868 // of a selection sync and another chart becomes
1871 if(dont_update !== true && this.isVisible() === true) {
1876 this.updateChartPanOrZoom = function(after, before) {
1877 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1880 if(this.debug === true)
1883 if(before < after) {
1884 if(this.debug === true)
1885 this.log(logme + 'flipped parameters, rejecting it.');
1890 if(typeof this.fixed_min_duration === 'undefined')
1891 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1893 var min_duration = this.fixed_min_duration;
1894 var current_duration = Math.round(this.view_before - this.view_after);
1896 // round the numbers
1897 after = Math.round(after);
1898 before = Math.round(before);
1900 // align them to update_every
1901 // stretching them further away
1902 after -= after % this.data_update_every;
1903 before += this.data_update_every - (before % this.data_update_every);
1905 // the final wanted duration
1906 var wanted_duration = before - after;
1908 // to allow panning, accept just a point below our minimum
1909 if((current_duration - this.data_update_every) < min_duration)
1910 min_duration = current_duration - this.data_update_every;
1912 // we do it, but we adjust to minimum size and return false
1913 // when the wanted size is below the current and the minimum
1915 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1916 if(this.debug === true)
1917 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1919 min_duration = this.fixed_min_duration;
1921 var dt = (min_duration - wanted_duration) / 2;
1924 wanted_duration = before - after;
1928 var tolerance = this.data_update_every * 2;
1929 var movement = Math.abs(before - this.view_before);
1931 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1932 if(this.debug === true)
1933 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);
1937 if(this.current.name === 'auto') {
1938 this.log(logme + 'caller called me with mode: ' + this.current.name);
1939 this.setMode('pan');
1942 if(this.debug === true)
1943 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);
1945 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1946 this.current.force_after_ms = after;
1947 this.current.force_before_ms = before;
1948 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1952 this.legendFormatValue = function(value) {
1953 if(value === null || value === 'undefined') return '-';
1954 if(typeof value !== 'number') return value;
1956 var abs = Math.abs(value);
1957 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1958 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1959 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1960 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1961 return (Math.round(value * 10000) / 10000).toLocaleString();
1964 this.legendSetLabelValue = function(label, value) {
1965 var series = this.element_legend_childs.series[label];
1966 if(typeof series === 'undefined') return;
1967 if(series.value === null && series.user === null) return;
1969 // if the value has not changed, skip DOM update
1970 //if(series.last === value) return;
1973 if(typeof value === 'number') {
1974 var v = Math.abs(value);
1975 s = r = this.legendFormatValue(value);
1977 if(typeof series.last === 'number') {
1978 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1979 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1980 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1982 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1987 series.last = value;
1990 if(series.value !== null) series.value.innerHTML = s;
1991 if(series.user !== null) series.user.innerHTML = r;
1994 this.legendSetDate = function(ms) {
1995 if(typeof ms !== 'number') {
1996 this.legendShowUndefined();
2000 var d = new Date(ms);
2002 if(this.element_legend_childs.title_date)
2003 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2005 if(this.element_legend_childs.title_time)
2006 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2008 if(this.element_legend_childs.title_units)
2009 this.element_legend_childs.title_units.innerHTML = this.units;
2012 this.legendShowUndefined = function() {
2013 if(this.element_legend_childs.title_date)
2014 this.element_legend_childs.title_date.innerHTML = ' ';
2016 if(this.element_legend_childs.title_time)
2017 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2019 if(this.element_legend_childs.title_units)
2020 this.element_legend_childs.title_units.innerHTML = ' ';
2022 if(this.data && this.element_legend_childs.series !== null) {
2023 var labels = this.data.dimension_names;
2024 var i = labels.length;
2026 var label = labels[i];
2028 if(typeof label === 'undefined') continue;
2029 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2030 this.legendSetLabelValue(label, null);
2035 this.legendShowLatestValues = function() {
2036 if(this.chart === null) return;
2037 if(this.selected) return;
2039 if(this.data === null || this.element_legend_childs.series === null) {
2040 this.legendShowUndefined();
2044 var show_undefined = true;
2045 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2046 show_undefined = false;
2048 if(show_undefined) {
2049 this.legendShowUndefined();
2053 this.legendSetDate(this.view_before);
2055 var labels = this.data.dimension_names;
2056 var i = labels.length;
2058 var label = labels[i];
2060 if(typeof label === 'undefined') continue;
2061 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2064 this.legendSetLabelValue(label, null);
2066 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2070 this.legendReset = function() {
2071 this.legendShowLatestValues();
2074 // this should be called just ONCE per dimension per chart
2075 this._chartDimensionColor = function(label) {
2076 if(this.colors === null) this.chartColors();
2078 if(typeof this.colors_assigned[label] === 'undefined') {
2079 if(this.colors_available.length === 0) {
2080 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2081 this.colors_available.push(NETDATA.themes.current.colors[i]);
2084 this.colors_assigned[label] = this.colors_available.shift();
2086 if(this.debug === true)
2087 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2090 if(this.debug === true)
2091 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2094 this.colors.push(this.colors_assigned[label]);
2095 return this.colors_assigned[label];
2098 this.chartColors = function() {
2099 if(this.colors !== null) return this.colors;
2101 this.colors = new Array();
2102 this.colors_available = new Array();
2105 var c = $(this.element).data('colors');
2106 // this.log('read colors: ' + c);
2107 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2108 if(typeof c !== 'string') {
2109 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2116 for(i = 0, len = c.length; i < len ; i++) {
2118 this.colors_available.push(c[i]);
2119 // this.log('adding color: ' + c[i]);
2125 // push all the standard colors too
2126 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2127 this.colors_available.push(NETDATA.themes.current.colors[i]);
2132 this.legendUpdateDOM = function() {
2135 // check that the legend DOM is up to date for the downloaded dimensions
2136 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2137 // this.log('the legend does not have any series - requesting legend update');
2140 else if(this.data === null) {
2141 // this.log('the chart does not have any data - requesting legend update');
2144 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2148 var labels = this.data.dimension_names.toString();
2149 if(labels !== this.element_legend_childs.series.labels_key) {
2152 if(this.debug === true)
2153 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2157 if(needed === false) {
2158 // make sure colors available
2161 // do we have to update the current values?
2162 // we do this, only when the visible chart is current
2163 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2164 if(this.debug === true)
2165 this.log('chart is in latest position... updating values on legend...');
2167 //var labels = this.data.dimension_names;
2168 //var i = labels.length;
2170 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2174 if(this.colors === null) {
2175 // this is the first time we update the chart
2176 // let's assign colors to all dimensions
2177 if(this.library.track_colors() === true)
2178 for(var dim in this.chart.dimensions)
2179 this._chartDimensionColor(this.chart.dimensions[dim].name);
2181 // we will re-generate the colors for the chart
2182 // based on the selected dimensions
2185 if(this.debug === true)
2186 this.log('updating Legend DOM');
2188 // mark all dimensions as invalid
2189 this.dimensions_visibility.invalidateAll();
2191 var genLabel = function(state, parent, dim, name, count) {
2192 var color = state._chartDimensionColor(name);
2194 var user_element = null;
2195 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2196 if(user_id !== null) {
2197 user_element = document.getElementById(user_id) || null;
2198 if(user_element === null)
2199 state.log('Cannot find element with id: ' + user_id);
2202 state.element_legend_childs.series[name] = {
2203 name: document.createElement('span'),
2204 value: document.createElement('span'),
2209 var label = state.element_legend_childs.series[name];
2211 // create the dimension visibility tracking for this label
2212 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2214 var rgb = NETDATA.colorHex2Rgb(color);
2215 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2216 + state.chart.chart_type
2217 + '" style="background-color: '
2218 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2219 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2221 var text = document.createTextNode(' ' + name);
2222 label.name.appendChild(text);
2225 parent.appendChild(document.createElement('br'));
2227 parent.appendChild(label.name);
2228 parent.appendChild(label.value);
2231 var content = document.createElement('div');
2233 if(this.hasLegend()) {
2234 this.element_legend_childs = {
2236 resize_handler: document.createElement('div'),
2237 toolbox: document.createElement('div'),
2238 toolbox_left: document.createElement('div'),
2239 toolbox_right: document.createElement('div'),
2240 toolbox_reset: document.createElement('div'),
2241 toolbox_zoomin: document.createElement('div'),
2242 toolbox_zoomout: document.createElement('div'),
2243 toolbox_volume: document.createElement('div'),
2244 title_date: document.createElement('span'),
2245 title_time: document.createElement('span'),
2246 title_units: document.createElement('span'),
2247 nano: document.createElement('div'),
2249 paneClass: 'netdata-legend-series-pane',
2250 sliderClass: 'netdata-legend-series-slider',
2251 contentClass: 'netdata-legend-series-content',
2252 enabledClass: '__enabled',
2253 flashedClass: '__flashed',
2254 activeClass: '__active',
2256 alwaysVisible: true,
2262 this.element_legend.innerHTML = '';
2264 if(this.library.toolboxPanAndZoom !== null) {
2266 function get_pan_and_zoom_step(event) {
2268 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2270 else if (event.shiftKey)
2271 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2273 else if (event.altKey)
2274 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2277 return NETDATA.options.current.pan_and_zoom_factor;
2280 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2281 this.element.appendChild(this.element_legend_childs.toolbox);
2283 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2284 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2285 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2286 this.element_legend_childs.toolbox_left.onclick = function(e) {
2289 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2290 var before = that.view_before - step;
2291 var after = that.view_after - step;
2292 if(after >= that.netdata_first)
2293 that.library.toolboxPanAndZoom(that, after, before);
2295 if(NETDATA.options.current.show_help === true)
2296 $(this.element_legend_childs.toolbox_left).popover({
2301 placement: 'bottom',
2302 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2304 content: 'Pan the chart to the 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>'
2308 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2309 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2310 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2311 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2313 NETDATA.resetAllCharts(that);
2315 if(NETDATA.options.current.show_help === true)
2316 $(this.element_legend_childs.toolbox_reset).popover({
2321 placement: 'bottom',
2322 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2323 title: 'Chart Reset',
2324 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>'
2327 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2328 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2329 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2330 this.element_legend_childs.toolbox_right.onclick = function(e) {
2332 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2333 var before = that.view_before + step;
2334 var after = that.view_after + step;
2335 if(before <= that.netdata_last)
2336 that.library.toolboxPanAndZoom(that, after, before);
2338 if(NETDATA.options.current.show_help === true)
2339 $(this.element_legend_childs.toolbox_right).popover({
2344 placement: 'bottom',
2345 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2347 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>'
2351 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2352 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2353 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2354 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2356 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2357 var before = that.view_before - dt;
2358 var after = that.view_after + dt;
2359 that.library.toolboxPanAndZoom(that, after, before);
2361 if(NETDATA.options.current.show_help === true)
2362 $(this.element_legend_childs.toolbox_zoomin).popover({
2367 placement: 'bottom',
2368 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2369 title: 'Chart Zoom In',
2370 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>'
2373 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2374 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2375 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2376 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2378 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);
2379 var before = that.view_before + dt;
2380 var after = that.view_after - dt;
2382 that.library.toolboxPanAndZoom(that, after, before);
2384 if(NETDATA.options.current.show_help === true)
2385 $(this.element_legend_childs.toolbox_zoomout).popover({
2390 placement: 'bottom',
2391 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2392 title: 'Chart Zoom Out',
2393 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>'
2396 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2397 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2398 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2399 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2400 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2401 //e.preventDefault();
2402 //alert('clicked toolbox_volume on ' + that.id);
2406 this.element_legend_childs.toolbox = null;
2407 this.element_legend_childs.toolbox_left = null;
2408 this.element_legend_childs.toolbox_reset = null;
2409 this.element_legend_childs.toolbox_right = null;
2410 this.element_legend_childs.toolbox_zoomin = null;
2411 this.element_legend_childs.toolbox_zoomout = null;
2412 this.element_legend_childs.toolbox_volume = null;
2415 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2416 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2417 this.element.appendChild(this.element_legend_childs.resize_handler);
2418 if(NETDATA.options.current.show_help === true)
2419 $(this.element_legend_childs.resize_handler).popover({
2424 placement: 'bottom',
2425 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2426 title: 'Chart Resize',
2427 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>'
2431 this.element_legend_childs.resize_handler.onmousedown =
2433 that.resizeHandler(e);
2437 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2438 that.resizeHandler(e);
2441 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2442 this.element_legend.appendChild(this.element_legend_childs.title_date);
2444 this.element_legend.appendChild(document.createElement('br'));
2446 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2447 this.element_legend.appendChild(this.element_legend_childs.title_time);
2449 this.element_legend.appendChild(document.createElement('br'));
2451 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2452 this.element_legend.appendChild(this.element_legend_childs.title_units);
2454 this.element_legend.appendChild(document.createElement('br'));
2456 this.element_legend_childs.nano.className = 'netdata-legend-series';
2457 this.element_legend.appendChild(this.element_legend_childs.nano);
2459 content.className = 'netdata-legend-series-content';
2460 this.element_legend_childs.nano.appendChild(content);
2462 if(NETDATA.options.current.show_help === true)
2463 $(content).popover({
2468 placement: 'bottom',
2469 title: 'Chart Legend',
2470 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2471 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>'
2475 this.element_legend_childs = {
2477 resize_handler: null,
2480 toolbox_right: null,
2481 toolbox_reset: null,
2482 toolbox_zoomin: null,
2483 toolbox_zoomout: null,
2484 toolbox_volume: null,
2495 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2496 if(this.debug === true)
2497 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2499 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2500 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2504 var tmp = new Array();
2505 for(var dim in this.chart.dimensions) {
2506 tmp.push(this.chart.dimensions[dim].name);
2507 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2509 this.element_legend_childs.series.labels_key = tmp.toString();
2510 if(this.debug === true)
2511 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2514 // create a hidden div to be used for hidding
2515 // the original legend of the chart library
2516 var el = document.createElement('div');
2517 if(this.element_legend !== null)
2518 this.element_legend.appendChild(el);
2519 el.style.display = 'none';
2521 this.element_legend_childs.hidden = document.createElement('div');
2522 el.appendChild(this.element_legend_childs.hidden);
2524 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2525 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2527 this.legendShowLatestValues();
2530 this.hasLegend = function() {
2531 if(typeof this.___hasLegendCache___ !== 'undefined')
2532 return this.___hasLegendCache___;
2535 if(this.library && this.library.legend(this) === 'right-side') {
2536 var legend = $(this.element).data('legend') || 'yes';
2537 if(legend === 'yes') leg = true;
2540 this.___hasLegendCache___ = leg;
2544 this.legendWidth = function() {
2545 return (this.hasLegend())?140:0;
2548 this.legendHeight = function() {
2549 return $(this.element).height();
2552 this.chartWidth = function() {
2553 return $(this.element).width() - this.legendWidth();
2556 this.chartHeight = function() {
2557 return $(this.element).height();
2560 this.chartPixelsPerPoint = function() {
2561 // force an options provided detail
2562 var px = this.pixels_per_point;
2564 if(this.library && px < this.library.pixels_per_point(this))
2565 px = this.library.pixels_per_point(this);
2567 if(px < NETDATA.options.current.pixels_per_point)
2568 px = NETDATA.options.current.pixels_per_point;
2573 this.needsRecreation = function() {
2575 this.chart_created === true
2577 && this.library.autoresize() === false
2578 && this.tm.last_resized < NETDATA.options.last_resized
2582 this.chartURL = function() {
2583 var after, before, points_multiplier = 1;
2584 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2585 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2587 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2588 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2589 this.view_after = after * 1000;
2590 this.view_before = before * 1000;
2592 this.requested_padding = null;
2593 points_multiplier = 1;
2595 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2596 this.tm.pan_and_zoom_seq = 0;
2598 before = Math.round(this.current.force_before_ms / 1000);
2599 after = Math.round(this.current.force_after_ms / 1000);
2600 this.view_after = after * 1000;
2601 this.view_before = before * 1000;
2603 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2604 this.requested_padding = Math.round((before - after) / 2);
2605 after -= this.requested_padding;
2606 before += this.requested_padding;
2607 this.requested_padding *= 1000;
2608 points_multiplier = 2;
2611 this.current.force_before_ms = null;
2612 this.current.force_after_ms = null;
2615 this.tm.pan_and_zoom_seq = 0;
2617 before = this.before;
2619 this.view_after = after * 1000;
2620 this.view_before = before * 1000;
2622 this.requested_padding = null;
2623 points_multiplier = 1;
2626 this.requested_after = after * 1000;
2627 this.requested_before = before * 1000;
2629 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2631 // build the data URL
2632 this.data_url = this.host + this.chart.data_url;
2633 this.data_url += "&format=" + this.library.format();
2634 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2635 this.data_url += "&group=" + this.method;
2636 this.data_url += "&options=" + this.library.options(this);
2637 this.data_url += '|jsonwrap';
2639 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2640 this.data_url += '|nonzero';
2642 if(this.append_options !== null)
2643 this.data_url += '|' + this.append_options.toString();
2646 this.data_url += "&after=" + after.toString();
2649 this.data_url += "&before=" + before.toString();
2652 this.data_url += "&dimensions=" + this.dimensions;
2654 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2655 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2658 this.redrawChart = function() {
2659 if(this.data !== null)
2660 this.updateChartWithData(this.data);
2663 this.updateChartWithData = function(data) {
2664 if(this.debug === true)
2665 this.log('updateChartWithData() called.');
2667 // this may force the chart to be re-created
2671 this.updates_counter++;
2672 this.updates_since_last_unhide++;
2673 this.updates_since_last_creation++;
2675 var started = new Date().getTime();
2677 // if the result is JSON, find the latest update-every
2678 this.data_update_every = data.view_update_every * 1000;
2679 this.data_after = data.after * 1000;
2680 this.data_before = data.before * 1000;
2681 this.netdata_first = data.first_entry * 1000;
2682 this.netdata_last = data.last_entry * 1000;
2683 this.data_points = data.points;
2686 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2687 if(this.view_after < this.data_after) {
2688 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2689 this.view_after = this.data_after;
2692 if(this.view_before > this.data_before) {
2693 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2694 this.view_before = this.data_before;
2698 this.view_after = this.data_after;
2699 this.view_before = this.data_before;
2702 if(this.debug === true) {
2703 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2705 if(this.current.force_after_ms)
2706 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2708 this.log('STATUS: forced : unset');
2710 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2711 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2712 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2713 this.log('STATUS: points : ' + (this.data_points).toString());
2716 if(this.data_points === 0) {
2721 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2722 if(this.debug === true)
2723 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2725 this.chart_created = false;
2728 // check and update the legend
2729 this.legendUpdateDOM();
2731 if(this.chart_created === true
2732 && typeof this.library.update === 'function') {
2734 if(this.debug === true)
2735 this.log('updating chart...');
2737 if(callChartLibraryUpdateSafely(data) === false)
2741 if(this.debug === true)
2742 this.log('creating chart...');
2744 if(callChartLibraryCreateSafely(data) === false)
2748 this.legendShowLatestValues();
2749 if(this.selected === true)
2750 NETDATA.globalSelectionSync.stop();
2752 // update the performance counters
2753 var now = new Date().getTime();
2754 this.tm.last_updated = now;
2756 // don't update last_autorefreshed if this chart is
2757 // forced to be updated with global PanAndZoom
2758 if(NETDATA.globalPanAndZoom.isActive())
2759 this.tm.last_autorefreshed = 0;
2761 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2762 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2764 this.tm.last_autorefreshed = now;
2767 this.refresh_dt_ms = now - started;
2768 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2770 if(this.refresh_dt_element !== null)
2771 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2774 this.updateChart = function(callback) {
2775 if(this.debug === true)
2776 this.log('updateChart() called.');
2778 if(this._updating === true) {
2779 if(this.debug === true)
2780 this.log('I am already updating...');
2782 if(typeof callback === 'function') callback();
2786 // due to late initialization of charts and libraries
2787 // we need to check this too
2788 if(this.enabled === false) {
2789 if(this.debug === true)
2790 this.log('I am not enabled');
2792 if(typeof callback === 'function') callback();
2796 if(canBeRendered() === false) {
2797 if(typeof callback === 'function') callback();
2801 if(this.chart === null) {
2802 this.getChart(function() { that.updateChart(callback); });
2806 if(this.library.initialized === false) {
2807 if(this.library.enabled === true) {
2808 this.library.initialize(function() { that.updateChart(callback); });
2812 error('chart library "' + this.library_name + '" is not available.');
2813 if(typeof callback === 'function') callback();
2818 this.clearSelection();
2821 if(this.debug === true)
2822 this.log('updating from ' + this.data_url);
2824 NETDATA.statistics.refreshes_total++;
2825 NETDATA.statistics.refreshes_active++;
2827 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2828 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2830 this._updating = true;
2832 this.xhr = $.ajax( {
2836 xhrFields: { withCredentials: true } // required for the cookie
2838 .done(function(data) {
2839 that.xhr = undefined;
2841 if(that.debug === true)
2842 that.log('data received. updating chart.');
2844 that.updateChartWithData(data);
2846 .fail(function(msg) {
2847 that.xhr = undefined;
2849 if(msg.statusText !== 'abort')
2850 error('data download failed for url: ' + that.data_url);
2852 .always(function() {
2853 that.xhr = undefined;
2855 NETDATA.statistics.refreshes_active--;
2856 that._updating = false;
2857 if(typeof callback === 'function') callback();
2863 this.isVisible = function(nocache) {
2864 if(typeof nocache === 'undefined')
2867 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2869 // caching - we do not evaluate the charts visibility
2870 // if the page has not been scrolled since the last check
2871 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2872 return this.___isVisible___;
2874 this.tm.last_visible_check = new Date().getTime();
2876 var wh = window.innerHeight;
2877 var x = this.element.getBoundingClientRect();
2881 if(x.width === 0 || x.height === 0) {
2883 this.___isVisible___ = false;
2884 return this.___isVisible___;
2887 if(x.top < 0 && -x.top > x.height) {
2888 // the chart is entirely above
2889 ret = -x.top - x.height;
2891 else if(x.top > wh) {
2892 // the chart is entirely below
2896 if(ret > tolerance) {
2897 // the chart is too far
2900 this.___isVisible___ = false;
2901 return this.___isVisible___;
2904 // the chart is inside or very close
2907 this.___isVisible___ = true;
2908 return this.___isVisible___;
2912 this.isAutoRefreshable = function() {
2913 return (this.current.autorefresh);
2916 this.canBeAutoRefreshed = function() {
2917 var now = new Date().getTime();
2919 if(this.running === true) {
2920 if(this.debug === true)
2921 this.log('I am already running');
2926 if(this.enabled === false) {
2927 if(this.debug === true)
2928 this.log('I am not enabled');
2933 if(this.library === null || this.library.enabled === false) {
2934 error('charting library "' + this.library_name + '" is not available');
2935 if(this.debug === true)
2936 this.log('My chart library ' + this.library_name + ' is not available');
2941 if(this.isVisible() === false) {
2942 if(NETDATA.options.debug.visibility === true || this.debug === true)
2943 this.log('I am not visible');
2948 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2949 if(this.debug === true)
2950 this.log('timed force update detected - allowing this update');
2952 this.current.force_update_at = 0;
2956 if(this.isAutoRefreshable() === true) {
2957 // allow the first update, even if the page is not visible
2958 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2959 if(NETDATA.options.debug.focus === true || this.debug === true)
2960 this.log('canBeAutoRefreshed(): page does not have focus');
2965 if(this.needsRecreation() === true) {
2966 if(this.debug === true)
2967 this.log('canBeAutoRefreshed(): needs re-creation.');
2972 // options valid only for autoRefresh()
2973 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2974 if(NETDATA.globalPanAndZoom.isActive()) {
2975 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2976 if(this.debug === true)
2977 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2982 if(this.debug === true)
2983 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2989 if(this.selected === true) {
2990 if(this.debug === true)
2991 this.log('canBeAutoRefreshed(): I have a selection in place.');
2996 if(this.paused === true) {
2997 if(this.debug === true)
2998 this.log('canBeAutoRefreshed(): I am paused.');
3003 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3004 if(this.debug === true)
3005 this.log('canBeAutoRefreshed(): It is time to update me.');
3015 this.autoRefresh = function(callback) {
3016 if(this.canBeAutoRefreshed() === true && this.running === false) {
3019 state.running = true;
3020 state.updateChart(function() {
3021 state.running = false;
3023 if(typeof callback !== 'undefined')
3028 if(typeof callback !== 'undefined')
3033 this._defaultsFromDownloadedChart = function(chart) {
3035 this.chart_url = chart.url;
3036 this.data_update_every = chart.update_every * 1000;
3037 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3038 this.tm.last_info_downloaded = new Date().getTime();
3040 if(this.title === null)
3041 this.title = chart.title;
3043 if(this.units === null)
3044 this.units = chart.units;
3047 // fetch the chart description from the netdata server
3048 this.getChart = function(callback) {
3049 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3051 this._defaultsFromDownloadedChart(this.chart);
3052 if(typeof callback === 'function') callback();
3055 this.chart_url = "/api/v1/chart?chart=" + this.id;
3057 if(this.debug === true)
3058 this.log('downloading ' + this.chart_url);
3061 url: this.host + this.chart_url,
3064 xhrFields: { withCredentials: true } // required for the cookie
3066 .done(function(chart) {
3067 chart.url = that.chart_url;
3068 that._defaultsFromDownloadedChart(chart);
3069 NETDATA.chartRegistry.add(that.host, that.id, chart);
3072 NETDATA.error(404, that.chart_url);
3073 error('chart not found on url "' + that.chart_url + '"');
3075 .always(function() {
3076 if(typeof callback === 'function') callback();
3081 // ============================================================================================================
3087 NETDATA.resetAllCharts = function(state) {
3088 // first clear the global selection sync
3089 // to make sure no chart is in selected state
3090 state.globalSelectionSyncStop();
3092 // there are 2 possibilities here
3093 // a. state is the global Pan and Zoom master
3094 // b. state is not the global Pan and Zoom master
3096 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3099 // clear the global Pan and Zoom
3100 // this will also refresh the master
3101 // and unblock any charts currently mirroring the master
3102 NETDATA.globalPanAndZoom.clearMaster();
3104 // if we were not the master, reset our status too
3105 // this is required because most probably the mouse
3106 // is over this chart, blocking it from auto-refreshing
3107 if(master === false && (state.paused === true || state.selected === true))
3111 // get or create a chart state, given a DOM element
3112 NETDATA.chartState = function(element) {
3113 var state = $(element).data('netdata-state-object') || null;
3114 if(state === null) {
3115 state = new chartState(element);
3116 $(element).data('netdata-state-object', state);
3121 // ----------------------------------------------------------------------------------------------------------------
3122 // Library functions
3124 // Load a script without jquery
3125 // This is used to load jquery - after it is loaded, we use jquery
3126 NETDATA._loadjQuery = function(callback) {
3127 if(typeof jQuery === 'undefined') {
3128 if(NETDATA.options.debug.main_loop === true)
3129 console.log('loading ' + NETDATA.jQuery);
3131 var script = document.createElement('script');
3132 script.type = 'text/javascript';
3133 script.async = true;
3134 script.src = NETDATA.jQuery;
3136 // script.onabort = onError;
3137 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3138 if(typeof callback === "function")
3139 script.onload = callback;
3141 var s = document.getElementsByTagName('script')[0];
3142 s.parentNode.insertBefore(script, s);
3144 else if(typeof callback === "function")
3148 NETDATA._loadCSS = function(filename) {
3149 // don't use jQuery here
3150 // styles are loaded before jQuery
3151 // to eliminate showing an unstyled page to the user
3153 var fileref = document.createElement("link");
3154 fileref.setAttribute("rel", "stylesheet");
3155 fileref.setAttribute("type", "text/css");
3156 fileref.setAttribute("href", filename);
3158 if (typeof fileref !== 'undefined')
3159 document.getElementsByTagName("head")[0].appendChild(fileref);
3162 NETDATA.colorHex2Rgb = function(hex) {
3163 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3164 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3165 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3166 return r + r + g + g + b + b;
3169 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3171 r: parseInt(result[1], 16),
3172 g: parseInt(result[2], 16),
3173 b: parseInt(result[3], 16)
3177 NETDATA.colorLuminance = function(hex, lum) {
3178 // validate hex string
3179 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3181 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3185 // convert to decimal and change luminosity
3186 var rgb = "#", c, i;
3187 for (i = 0; i < 3; i++) {
3188 c = parseInt(hex.substr(i*2,2), 16);
3189 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3190 rgb += ("00"+c).substr(c.length);
3196 NETDATA.guid = function() {
3198 return Math.floor((1 + Math.random()) * 0x10000)
3203 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3206 NETDATA.zeropad = function(x) {
3207 if(x > -10 && x < 10) return '0' + x.toString();
3208 else return x.toString();
3211 // user function to signal us the DOM has been
3213 NETDATA.updatedDom = function() {
3214 NETDATA.options.updated_dom = true;
3217 NETDATA.ready = function(callback) {
3218 NETDATA.options.pauseCallback = callback;
3221 NETDATA.pause = function(callback) {
3222 if(NETDATA.options.pause === true)
3225 NETDATA.options.pauseCallback = callback;
3228 NETDATA.unpause = function() {
3229 NETDATA.options.pauseCallback = null;
3230 NETDATA.options.updated_dom = true;
3231 NETDATA.options.pause = false;
3234 // ----------------------------------------------------------------------------------------------------------------
3236 // this is purely sequencial charts refresher
3237 // it is meant to be autonomous
3238 NETDATA.chartRefresherNoParallel = function(index) {
3239 if(NETDATA.options.debug.mail_loop === true)
3240 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3242 if(NETDATA.options.updated_dom === true) {
3243 // the dom has been updated
3244 // get the dom parts again
3245 NETDATA.parseDom(NETDATA.chartRefresher);
3248 if(index >= NETDATA.options.targets.length) {
3249 if(NETDATA.options.debug.main_loop === true)
3250 console.log('waiting to restart main loop...');
3252 NETDATA.options.auto_refresher_fast_weight = 0;
3254 setTimeout(function() {
3255 NETDATA.chartRefresher();
3256 }, NETDATA.options.current.idle_between_loops);
3259 var state = NETDATA.options.targets[index];
3261 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3262 if(NETDATA.options.debug.main_loop === true)
3263 console.log('fast rendering...');
3265 state.autoRefresh(function() {
3266 NETDATA.chartRefresherNoParallel(++index);
3270 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3271 NETDATA.options.auto_refresher_fast_weight = 0;
3273 setTimeout(function() {
3274 state.autoRefresh(function() {
3275 NETDATA.chartRefresherNoParallel(++index);
3277 }, NETDATA.options.current.idle_between_charts);
3282 // this is part of the parallel refresher
3283 // its cause is to refresh sequencially all the charts
3284 // that depend on chart library initialization
3285 // it will call the parallel refresher back
3286 // as soon as it sees a chart that its chart library
3288 NETDATA.chartRefresher_uninitialized = function() {
3289 if(NETDATA.options.updated_dom === true) {
3290 // the dom has been updated
3291 // get the dom parts again
3292 NETDATA.parseDom(NETDATA.chartRefresher);
3296 if(NETDATA.options.sequencial.length === 0)
3297 NETDATA.chartRefresher();
3299 var state = NETDATA.options.sequencial.pop();
3300 if(state.library.initialized === true)
3301 NETDATA.chartRefresher();
3303 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3307 NETDATA.chartRefresherWaitTime = function() {
3308 return NETDATA.options.current.idle_parallel_loops;
3311 // the default refresher
3312 // it will create 2 sets of charts:
3313 // - the ones that can be refreshed in parallel
3314 // - the ones that depend on something else
3315 // the first set will be executed in parallel
3316 // the second will be given to NETDATA.chartRefresher_uninitialized()
3317 NETDATA.chartRefresher = function() {
3318 // console.log('auto-refresher...');
3320 if(NETDATA.options.pause === true) {
3321 // console.log('auto-refresher is paused');
3322 setTimeout(NETDATA.chartRefresher,
3323 NETDATA.chartRefresherWaitTime());
3327 if(typeof NETDATA.options.pauseCallback === 'function') {
3328 // console.log('auto-refresher is calling pauseCallback');
3329 NETDATA.options.pause = true;
3330 NETDATA.options.pauseCallback();
3331 NETDATA.chartRefresher();
3335 if(NETDATA.options.current.parallel_refresher === false) {
3336 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3337 NETDATA.chartRefresherNoParallel(0);
3341 if(NETDATA.options.updated_dom === true) {
3342 // the dom has been updated
3343 // get the dom parts again
3344 // console.log('auto-refresher is calling parseDom()');
3345 NETDATA.parseDom(NETDATA.chartRefresher);
3349 var parallel = new Array();
3350 var targets = NETDATA.options.targets;
3351 var len = targets.length;
3354 state = targets[len];
3355 if(state.isVisible() === false || state.running === true)
3358 if(state.library.initialized === false) {
3359 if(state.library.enabled === true) {
3360 state.library.initialize(NETDATA.chartRefresher);
3364 state.error('chart library "' + state.library_name + '" is not enabled.');
3368 parallel.unshift(state);
3371 if(parallel.length > 0) {
3372 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3373 // this will execute the jobs in parallel
3374 $(parallel).each(function() {
3379 // console.log('auto-refresher nothing to do');
3382 // run the next refresh iteration
3383 setTimeout(NETDATA.chartRefresher,
3384 NETDATA.chartRefresherWaitTime());
3387 NETDATA.parseDom = function(callback) {
3388 NETDATA.options.last_page_scroll = new Date().getTime();
3389 NETDATA.options.updated_dom = false;
3391 var targets = $('div[data-netdata]'); //.filter(':visible');
3393 if(NETDATA.options.debug.main_loop === true)
3394 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3396 NETDATA.options.targets = new Array();
3397 var len = targets.length;
3399 // the initialization will take care of sizing
3400 // and the "loading..." message
3401 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3404 if(typeof callback === 'function') callback();
3407 // this is the main function - where everything starts
3408 NETDATA.start = function() {
3409 // this should be called only once
3411 NETDATA.options.page_is_visible = true;
3413 $(window).blur(function() {
3414 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3415 NETDATA.options.page_is_visible = false;
3416 if(NETDATA.options.debug.focus === true)
3417 console.log('Lost Focus!');
3421 $(window).focus(function() {
3422 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3423 NETDATA.options.page_is_visible = true;
3424 if(NETDATA.options.debug.focus === true)
3425 console.log('Focus restored!');
3429 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3430 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3431 NETDATA.options.page_is_visible = false;
3432 if(NETDATA.options.debug.focus === true)
3433 console.log('Document has no focus!');
3437 // bootstrap tab switching
3438 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3440 // bootstrap modal switching
3441 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3442 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3444 // bootstrap collapse switching
3445 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3446 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3448 NETDATA.parseDom(NETDATA.chartRefresher);
3450 // Alarms initialization
3451 setTimeout(NETDATA.alarms.init, 1000);
3453 // Registry initialization
3454 setTimeout(NETDATA.registry.init, 1500);
3457 // ----------------------------------------------------------------------------------------------------------------
3460 NETDATA.peityInitialize = function(callback) {
3461 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3463 url: NETDATA.peity_js,
3466 xhrFields: { withCredentials: true } // required for the cookie
3469 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3472 NETDATA.chartLibraries.peity.enabled = false;
3473 NETDATA.error(100, NETDATA.peity_js);
3475 .always(function() {
3476 if(typeof callback === "function")
3481 NETDATA.chartLibraries.peity.enabled = false;
3482 if(typeof callback === "function")
3487 NETDATA.peityChartUpdate = function(state, data) {
3488 state.peity_instance.innerHTML = data.result;
3490 if(state.peity_options.stroke !== state.chartColors()[0]) {
3491 state.peity_options.stroke = state.chartColors()[0];
3492 if(state.chart.chart_type === 'line')
3493 state.peity_options.fill = NETDATA.themes.current.background;
3495 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3498 $(state.peity_instance).peity('line', state.peity_options);
3502 NETDATA.peityChartCreate = function(state, data) {
3503 state.peity_instance = document.createElement('div');
3504 state.element_chart.appendChild(state.peity_instance);
3506 var self = $(state.element);
3507 state.peity_options = {
3508 stroke: NETDATA.themes.current.foreground,
3509 strokeWidth: self.data('peity-strokewidth') || 1,
3510 width: state.chartWidth(),
3511 height: state.chartHeight(),
3512 fill: NETDATA.themes.current.foreground
3515 NETDATA.peityChartUpdate(state, data);
3519 // ----------------------------------------------------------------------------------------------------------------
3522 NETDATA.sparklineInitialize = function(callback) {
3523 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3525 url: NETDATA.sparkline_js,
3528 xhrFields: { withCredentials: true } // required for the cookie
3531 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3534 NETDATA.chartLibraries.sparkline.enabled = false;
3535 NETDATA.error(100, NETDATA.sparkline_js);
3537 .always(function() {
3538 if(typeof callback === "function")
3543 NETDATA.chartLibraries.sparkline.enabled = false;
3544 if(typeof callback === "function")
3549 NETDATA.sparklineChartUpdate = function(state, data) {
3550 state.sparkline_options.width = state.chartWidth();
3551 state.sparkline_options.height = state.chartHeight();
3553 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3557 NETDATA.sparklineChartCreate = function(state, data) {
3558 var self = $(state.element);
3559 var type = self.data('sparkline-type') || 'line';
3560 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3561 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3562 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3563 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3564 var composite = self.data('sparkline-composite') || undefined;
3565 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3566 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3567 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3568 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3569 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3570 var spotColor = self.data('sparkline-spotcolor') || undefined;
3571 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3572 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3573 var spotRadius = self.data('sparkline-spotradius') || undefined;
3574 var valueSpots = self.data('sparkline-valuespots') || undefined;
3575 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3576 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3577 var lineWidth = self.data('sparkline-linewidth') || undefined;
3578 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3579 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3580 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3581 var xvalues = self.data('sparkline-xvalues') || undefined;
3582 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3583 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3584 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3585 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3586 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3587 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3588 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3589 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3590 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3591 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3592 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3593 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3594 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3595 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3596 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3597 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3598 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3599 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3600 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3601 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3602 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3603 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3605 if(spotColor === 'disable') spotColor='';
3606 if(minSpotColor === 'disable') minSpotColor='';
3607 if(maxSpotColor === 'disable') maxSpotColor='';
3609 state.sparkline_options = {
3611 lineColor: lineColor,
3612 fillColor: fillColor,
3613 chartRangeMin: chartRangeMin,
3614 chartRangeMax: chartRangeMax,
3615 composite: composite,
3616 enableTagOptions: enableTagOptions,
3617 tagOptionPrefix: tagOptionPrefix,
3618 tagValuesAttribute: tagValuesAttribute,
3619 disableHiddenCheck: disableHiddenCheck,
3620 defaultPixelsPerValue: defaultPixelsPerValue,
3621 spotColor: spotColor,
3622 minSpotColor: minSpotColor,
3623 maxSpotColor: maxSpotColor,
3624 spotRadius: spotRadius,
3625 valueSpots: valueSpots,
3626 highlightSpotColor: highlightSpotColor,
3627 highlightLineColor: highlightLineColor,
3628 lineWidth: lineWidth,
3629 normalRangeMin: normalRangeMin,
3630 normalRangeMax: normalRangeMax,
3631 drawNormalOnTop: drawNormalOnTop,
3633 chartRangeClip: chartRangeClip,
3634 chartRangeMinX: chartRangeMinX,
3635 chartRangeMaxX: chartRangeMaxX,
3636 disableInteraction: disableInteraction,
3637 disableTooltips: disableTooltips,
3638 disableHighlight: disableHighlight,
3639 highlightLighten: highlightLighten,
3640 highlightColor: highlightColor,
3641 tooltipContainer: tooltipContainer,
3642 tooltipClassname: tooltipClassname,
3643 tooltipChartTitle: state.title,
3644 tooltipFormat: tooltipFormat,
3645 tooltipPrefix: tooltipPrefix,
3646 tooltipSuffix: tooltipSuffix,
3647 tooltipSkipNull: tooltipSkipNull,
3648 tooltipValueLookups: tooltipValueLookups,
3649 tooltipFormatFieldlist: tooltipFormatFieldlist,
3650 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3651 numberFormatter: numberFormatter,
3652 numberDigitGroupSep: numberDigitGroupSep,
3653 numberDecimalMark: numberDecimalMark,
3654 numberDigitGroupCount: numberDigitGroupCount,
3655 animatedZooms: animatedZooms,
3656 width: state.chartWidth(),
3657 height: state.chartHeight()
3660 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3664 // ----------------------------------------------------------------------------------------------------------------
3671 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3672 if(after < state.netdata_first)
3673 after = state.netdata_first;
3675 if(before > state.netdata_last)
3676 before = state.netdata_last;
3678 state.setMode('zoom');
3679 state.globalSelectionSyncStop();
3680 state.globalSelectionSyncDelay();
3681 state.dygraph_user_action = true;
3682 state.dygraph_force_zoom = true;
3683 state.updateChartPanOrZoom(after, before);
3684 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3687 NETDATA.dygraphSetSelection = function(state, t) {
3688 if(typeof state.dygraph_instance !== 'undefined') {
3689 var r = state.calculateRowForTime(t);
3691 state.dygraph_instance.setSelection(r);
3693 state.dygraph_instance.clearSelection();
3694 state.legendShowUndefined();
3701 NETDATA.dygraphClearSelection = function(state, t) {
3702 if(typeof state.dygraph_instance !== 'undefined') {
3703 state.dygraph_instance.clearSelection();
3708 NETDATA.dygraphSmoothInitialize = function(callback) {
3710 url: NETDATA.dygraph_smooth_js,
3713 xhrFields: { withCredentials: true } // required for the cookie
3716 NETDATA.dygraph.smooth = true;
3717 smoothPlotter.smoothing = 0.3;
3720 NETDATA.dygraph.smooth = false;
3722 .always(function() {
3723 if(typeof callback === "function")
3728 NETDATA.dygraphInitialize = function(callback) {
3729 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3731 url: NETDATA.dygraph_js,
3734 xhrFields: { withCredentials: true } // required for the cookie
3737 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3740 NETDATA.chartLibraries.dygraph.enabled = false;
3741 NETDATA.error(100, NETDATA.dygraph_js);
3743 .always(function() {
3744 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3745 NETDATA.dygraphSmoothInitialize(callback);
3746 else if(typeof callback === "function")
3751 NETDATA.chartLibraries.dygraph.enabled = false;
3752 if(typeof callback === "function")
3757 NETDATA.dygraphChartUpdate = function(state, data) {
3758 var dygraph = state.dygraph_instance;
3760 if(typeof dygraph === 'undefined')
3761 return NETDATA.dygraphChartCreate(state, data);
3763 // when the chart is not visible, and hidden
3764 // if there is a window resize, dygraph detects
3765 // its element size as 0x0.
3766 // this will make it re-appear properly
3768 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3772 file: data.result.data,
3773 colors: state.chartColors(),
3774 labels: data.result.labels,
3775 labelsDivWidth: state.chartWidth() - 70,
3776 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3779 if(state.dygraph_force_zoom === true) {
3780 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3781 state.log('dygraphChartUpdate() forced zoom update');
3783 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3784 options.valueRange = state.dygraph_options.valueRange;
3785 options.isZoomedIgnoreProgrammaticZoom = true;
3786 state.dygraph_force_zoom = false;
3788 else if(state.current.name !== 'auto') {
3789 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3790 state.log('dygraphChartUpdate() loose update');
3792 options.valueRange = state.dygraph_options.valueRange;
3795 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3796 state.log('dygraphChartUpdate() strict update');
3798 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3799 options.valueRange = state.dygraph_options.valueRange;
3800 options.isZoomedIgnoreProgrammaticZoom = true;
3803 if(state.dygraph_smooth_eligible === true) {
3804 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3805 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3806 NETDATA.dygraphChartCreate(state, data);
3811 dygraph.updateOptions(options);
3813 state.dygraph_last_rendered = new Date().getTime();
3817 NETDATA.dygraphChartCreate = function(state, data) {
3818 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3819 state.log('dygraphChartCreate()');
3821 var self = $(state.element);
3823 var chart_type = state.chart.chart_type;
3824 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3825 chart_type = self.data('dygraph-type') || chart_type;
3827 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3828 smooth = self.data('dygraph-smooth') || smooth;
3830 if(NETDATA.dygraph.smooth === false)
3833 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3834 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3836 state.dygraph_options = {
3837 colors: self.data('dygraph-colors') || state.chartColors(),
3839 // leave a few pixels empty on the right of the chart
3840 rightGap: self.data('dygraph-rightgap') || 5,
3841 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3842 showRoller: self.data('dygraph-showroller') || false,
3844 title: self.data('dygraph-title') || state.title,
3845 titleHeight: self.data('dygraph-titleheight') || 19,
3847 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3848 labels: data.result.labels,
3849 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3850 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3851 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3852 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3853 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3856 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3857 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3859 includeZero: self.data('dygraph-includezero') || false,
3860 xRangePad: self.data('dygraph-xrangepad') || 0,
3861 yRangePad: self.data('dygraph-yrangepad') || 1,
3863 valueRange: self.data('dygraph-valuerange') || null,
3865 ylabel: state.units,
3866 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3868 // the function to plot the chart
3871 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3872 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3873 strokePattern: self.data('dygraph-strokepattern') || undefined,
3875 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3876 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3877 drawPoints: self.data('dygraph-drawpoints') || false,
3879 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3880 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3882 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3883 pointSize: self.data('dygraph-pointsize') || 1,
3885 // enabling this makes the chart with little square lines
3886 stepPlot: self.data('dygraph-stepplot') || false,
3888 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3889 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3890 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3892 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3893 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3894 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3895 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3897 drawAxis: self.data('dygraph-drawaxis') || true,
3898 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3899 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3900 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3902 drawGrid: self.data('dygraph-drawgrid') || true,
3903 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3904 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3905 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3906 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3907 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3909 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3910 sigFigs: self.data('dygraph-sigfigs') || null,
3911 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3912 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3914 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3915 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3916 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3918 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3919 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3923 ticker: Dygraph.dateTicker,
3924 axisLabelFormatter: function (d, gran) {
3925 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3927 valueFormatter: function (ms) {
3928 var d = new Date(ms);
3929 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3930 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3935 valueFormatter: function (x) {
3936 // we format legends with the state object
3937 // no need to do anything here
3938 // return (Math.round(x*100) / 100).toLocaleString();
3939 // return state.legendFormatValue(x);
3944 legendFormatter: function(data) {
3945 var elements = state.element_legend_childs;
3947 // if the hidden div is not there
3948 // we are not managing the legend
3949 if(elements.hidden === null) return;
3951 if (typeof data.x !== 'undefined') {
3952 state.legendSetDate(data.x);
3953 var i = data.series.length;
3955 var series = data.series[i];
3956 if(!series.isVisible) continue;
3957 state.legendSetLabelValue(series.label, series.y);
3963 drawCallback: function(dygraph, is_initial) {
3964 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3965 state.dygraph_user_action = false;
3967 var x_range = dygraph.xAxisRange();
3968 var after = Math.round(x_range[0]);
3969 var before = Math.round(x_range[1]);
3971 if(NETDATA.options.debug.dygraph === true)
3972 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3974 if(before <= state.netdata_last && after >= state.netdata_first)
3975 state.updateChartPanOrZoom(after, before);
3978 zoomCallback: function(minDate, maxDate, yRanges) {
3979 if(NETDATA.options.debug.dygraph === true)
3980 state.log('dygraphZoomCallback()');
3982 state.globalSelectionSyncStop();
3983 state.globalSelectionSyncDelay();
3984 state.setMode('zoom');
3986 // refresh it to the greatest possible zoom level
3987 state.dygraph_user_action = true;
3988 state.dygraph_force_zoom = true;
3989 state.updateChartPanOrZoom(minDate, maxDate);
3991 highlightCallback: function(event, x, points, row, seriesName) {
3992 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3993 state.log('dygraphHighlightCallback()');
3997 // there is a bug in dygraph when the chart is zoomed enough
3998 // the time it thinks is selected is wrong
3999 // here we calculate the time t based on the row number selected
4001 var t = state.data_after + row * state.data_update_every;
4002 // 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);
4004 state.globalSelectionSync(x);
4006 // fix legend zIndex using the internal structures of dygraph legend module
4007 // this works, but it is a hack!
4008 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4010 unhighlightCallback: function(event) {
4011 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4012 state.log('dygraphUnhighlightCallback()');
4014 state.unpauseChart();
4015 state.globalSelectionSyncStop();
4017 interactionModel : {
4018 mousedown: function(event, dygraph, context) {
4019 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4020 state.log('interactionModel.mousedown()');
4022 state.dygraph_user_action = true;
4023 state.globalSelectionSyncStop();
4025 if(NETDATA.options.debug.dygraph === true)
4026 state.log('dygraphMouseDown()');
4028 // Right-click should not initiate a zoom.
4029 if(event.button && event.button === 2) return;
4031 context.initializeMouseDown(event, dygraph, context);
4033 if(event.button && event.button === 1) {
4034 if (event.altKey || event.shiftKey) {
4035 state.setMode('pan');
4036 state.globalSelectionSyncDelay();
4037 Dygraph.startPan(event, dygraph, context);
4040 state.setMode('zoom');
4041 state.globalSelectionSyncDelay();
4042 Dygraph.startZoom(event, dygraph, context);
4046 if (event.altKey || event.shiftKey) {
4047 state.setMode('zoom');
4048 state.globalSelectionSyncDelay();
4049 Dygraph.startZoom(event, dygraph, context);
4052 state.setMode('pan');
4053 state.globalSelectionSyncDelay();
4054 Dygraph.startPan(event, dygraph, context);
4058 mousemove: function(event, dygraph, context) {
4059 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4060 state.log('interactionModel.mousemove()');
4062 if(context.isPanning) {
4063 state.dygraph_user_action = true;
4064 state.globalSelectionSyncStop();
4065 state.globalSelectionSyncDelay();
4066 state.setMode('pan');
4067 Dygraph.movePan(event, dygraph, context);
4069 else if(context.isZooming) {
4070 state.dygraph_user_action = true;
4071 state.globalSelectionSyncStop();
4072 state.globalSelectionSyncDelay();
4073 state.setMode('zoom');
4074 Dygraph.moveZoom(event, dygraph, context);
4077 mouseup: function(event, dygraph, context) {
4078 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4079 state.log('interactionModel.mouseup()');
4081 if (context.isPanning) {
4082 state.dygraph_user_action = true;
4083 state.globalSelectionSyncDelay();
4084 Dygraph.endPan(event, dygraph, context);
4086 else if (context.isZooming) {
4087 state.dygraph_user_action = true;
4088 state.globalSelectionSyncDelay();
4089 Dygraph.endZoom(event, dygraph, context);
4092 click: function(event, dygraph, context) {
4093 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4094 state.log('interactionModel.click()');
4096 event.preventDefault();
4098 dblclick: function(event, dygraph, context) {
4099 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4100 state.log('interactionModel.dblclick()');
4101 NETDATA.resetAllCharts(state);
4103 mousewheel: function(event, dygraph, context) {
4104 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4105 state.log('interactionModel.mousewheel()');
4107 // Take the offset of a mouse event on the dygraph canvas and
4108 // convert it to a pair of percentages from the bottom left.
4109 // (Not top left, bottom is where the lower value is.)
4110 function offsetToPercentage(g, offsetX, offsetY) {
4111 // This is calculating the pixel offset of the leftmost date.
4112 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4113 var yar0 = g.yAxisRange(0);
4115 // This is calculating the pixel of the higest value. (Top pixel)
4116 var yOffset = g.toDomCoords(null, yar0[1])[1];
4118 // x y w and h are relative to the corner of the drawing area,
4119 // so that the upper corner of the drawing area is (0, 0).
4120 var x = offsetX - xOffset;
4121 var y = offsetY - yOffset;
4123 // This is computing the rightmost pixel, effectively defining the
4125 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4127 // This is computing the lowest pixel, effectively defining the height.
4128 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4130 // Percentage from the left.
4131 var xPct = w === 0 ? 0 : (x / w);
4132 // Percentage from the top.
4133 var yPct = h === 0 ? 0 : (y / h);
4135 // The (1-) part below changes it from "% distance down from the top"
4136 // to "% distance up from the bottom".
4137 return [xPct, (1-yPct)];
4140 // Adjusts [x, y] toward each other by zoomInPercentage%
4141 // Split it so the left/bottom axis gets xBias/yBias of that change and
4142 // tight/top gets (1-xBias)/(1-yBias) of that change.
4144 // If a bias is missing it splits it down the middle.
4145 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4146 xBias = xBias || 0.5;
4147 yBias = yBias || 0.5;
4149 function adjustAxis(axis, zoomInPercentage, bias) {
4150 var delta = axis[1] - axis[0];
4151 var increment = delta * zoomInPercentage;
4152 var foo = [increment * bias, increment * (1-bias)];
4154 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4157 var yAxes = g.yAxisRanges();
4159 for (var i = 0; i < yAxes.length; i++) {
4160 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4163 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4166 if(event.altKey || event.shiftKey) {
4167 state.dygraph_user_action = true;
4169 state.globalSelectionSyncStop();
4170 state.globalSelectionSyncDelay();
4172 // http://dygraphs.com/gallery/interaction-api.js
4173 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4174 var percentage = normal / 50;
4176 if (!(event.offsetX && event.offsetY)){
4177 event.offsetX = event.layerX - event.target.offsetLeft;
4178 event.offsetY = event.layerY - event.target.offsetTop;
4181 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4182 var xPct = percentages[0];
4183 var yPct = percentages[1];
4185 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4187 var after = new_x_range[0];
4188 var before = new_x_range[1];
4190 var first = state.netdata_first + state.data_update_every;
4191 var last = state.netdata_last + state.data_update_every;
4194 after -= (before - last);
4201 state.setMode('zoom');
4202 if(state.updateChartPanOrZoom(after, before) === true)
4203 dygraph.updateOptions({ dateWindow: [ after, before ] });
4205 event.preventDefault();
4208 touchstart: function(event, dygraph, context) {
4209 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4210 state.log('interactionModel.touchstart()');
4212 state.dygraph_user_action = true;
4213 state.setMode('zoom');
4216 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4218 // we overwrite the touch directions at the end, to overwrite
4219 // the internal default of dygraphs
4220 context.touchDirections = { x: true, y: false };
4222 state.dygraph_last_touch_start = new Date().getTime();
4223 state.dygraph_last_touch_move = 0;
4225 if(typeof event.touches[0].pageX === 'number')
4226 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4228 state.dygraph_last_touch_page_x = 0;
4230 touchmove: function(event, dygraph, context) {
4231 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4232 state.log('interactionModel.touchmove()');
4234 state.dygraph_user_action = true;
4235 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4237 state.dygraph_last_touch_move = new Date().getTime();
4239 touchend: function(event, dygraph, context) {
4240 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4241 state.log('interactionModel.touchend()');
4243 state.dygraph_user_action = true;
4244 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4246 // if it didn't move, it is a selection
4247 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4248 // internal api of dygraphs
4249 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4250 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4251 if(NETDATA.dygraphSetSelection(state, t) === true)
4252 state.globalSelectionSync(t);
4255 // if it was double tap within double click time, reset the charts
4256 var now = new Date().getTime();
4257 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4258 if(state.dygraph_last_touch_move === 0) {
4259 var dt = now - state.dygraph_last_touch_end;
4260 if(dt <= NETDATA.options.current.double_click_speed)
4261 NETDATA.resetAllCharts(state);
4265 // remember the timestamp of the last touch end
4266 state.dygraph_last_touch_end = now;
4271 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4272 state.dygraph_options.drawGrid = false;
4273 state.dygraph_options.drawAxis = false;
4274 state.dygraph_options.title = undefined;
4275 state.dygraph_options.units = undefined;
4276 state.dygraph_options.ylabel = undefined;
4277 state.dygraph_options.yLabelWidth = 0;
4278 state.dygraph_options.labelsDivWidth = 120;
4279 state.dygraph_options.labelsDivStyles.width = '120px';
4280 state.dygraph_options.labelsSeparateLines = true;
4281 state.dygraph_options.rightGap = 0;
4282 state.dygraph_options.yRangePad = 1;
4285 if(smooth === true) {
4286 state.dygraph_smooth_eligible = true;
4288 if(NETDATA.options.current.smooth_plot === true)
4289 state.dygraph_options.plotter = smoothPlotter;
4291 else state.dygraph_smooth_eligible = false;
4293 state.dygraph_instance = new Dygraph(state.element_chart,
4294 data.result.data, state.dygraph_options);
4296 state.dygraph_force_zoom = false;
4297 state.dygraph_user_action = false;
4298 state.dygraph_last_rendered = new Date().getTime();
4302 // ----------------------------------------------------------------------------------------------------------------
4305 NETDATA.morrisInitialize = function(callback) {
4306 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4308 // morris requires raphael
4309 if(!NETDATA.chartLibraries.raphael.initialized) {
4310 if(NETDATA.chartLibraries.raphael.enabled) {
4311 NETDATA.raphaelInitialize(function() {
4312 NETDATA.morrisInitialize(callback);
4316 NETDATA.chartLibraries.morris.enabled = false;
4317 if(typeof callback === "function")
4322 NETDATA._loadCSS(NETDATA.morris_css);
4325 url: NETDATA.morris_js,
4328 xhrFields: { withCredentials: true } // required for the cookie
4331 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4334 NETDATA.chartLibraries.morris.enabled = false;
4335 NETDATA.error(100, NETDATA.morris_js);
4337 .always(function() {
4338 if(typeof callback === "function")
4344 NETDATA.chartLibraries.morris.enabled = false;
4345 if(typeof callback === "function")
4350 NETDATA.morrisChartUpdate = function(state, data) {
4351 state.morris_instance.setData(data.result.data);
4355 NETDATA.morrisChartCreate = function(state, data) {
4357 state.morris_options = {
4358 element: state.element_chart.id,
4359 data: data.result.data,
4361 ykeys: data.dimension_names,
4362 labels: data.dimension_names,
4368 continuousLine: false,
4369 behaveLikeLine: false
4372 if(state.chart.chart_type === 'line')
4373 state.morris_instance = new Morris.Line(state.morris_options);
4375 else if(state.chart.chart_type === 'area') {
4376 state.morris_options.behaveLikeLine = true;
4377 state.morris_instance = new Morris.Area(state.morris_options);
4380 state.morris_instance = new Morris.Area(state.morris_options);
4385 // ----------------------------------------------------------------------------------------------------------------
4388 NETDATA.raphaelInitialize = function(callback) {
4389 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4391 url: NETDATA.raphael_js,
4394 xhrFields: { withCredentials: true } // required for the cookie
4397 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4400 NETDATA.chartLibraries.raphael.enabled = false;
4401 NETDATA.error(100, NETDATA.raphael_js);
4403 .always(function() {
4404 if(typeof callback === "function")
4409 NETDATA.chartLibraries.raphael.enabled = false;
4410 if(typeof callback === "function")
4415 NETDATA.raphaelChartUpdate = function(state, data) {
4416 $(state.element_chart).raphael(data.result, {
4417 width: state.chartWidth(),
4418 height: state.chartHeight()
4424 NETDATA.raphaelChartCreate = function(state, data) {
4425 $(state.element_chart).raphael(data.result, {
4426 width: state.chartWidth(),
4427 height: state.chartHeight()
4433 // ----------------------------------------------------------------------------------------------------------------
4436 NETDATA.c3Initialize = function(callback) {
4437 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4440 if(!NETDATA.chartLibraries.d3.initialized) {
4441 if(NETDATA.chartLibraries.d3.enabled) {
4442 NETDATA.d3Initialize(function() {
4443 NETDATA.c3Initialize(callback);
4447 NETDATA.chartLibraries.c3.enabled = false;
4448 if(typeof callback === "function")
4453 NETDATA._loadCSS(NETDATA.c3_css);
4459 xhrFields: { withCredentials: true } // required for the cookie
4462 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4465 NETDATA.chartLibraries.c3.enabled = false;
4466 NETDATA.error(100, NETDATA.c3_js);
4468 .always(function() {
4469 if(typeof callback === "function")
4475 NETDATA.chartLibraries.c3.enabled = false;
4476 if(typeof callback === "function")
4481 NETDATA.c3ChartUpdate = function(state, data) {
4482 state.c3_instance.destroy();
4483 return NETDATA.c3ChartCreate(state, data);
4485 //state.c3_instance.load({
4486 // rows: data.result,
4493 NETDATA.c3ChartCreate = function(state, data) {
4495 state.element_chart.id = 'c3-' + state.uuid;
4496 // console.log('id = ' + state.element_chart.id);
4498 state.c3_instance = c3.generate({
4499 bindto: '#' + state.element_chart.id,
4501 width: state.chartWidth(),
4502 height: state.chartHeight()
4505 pattern: state.chartColors()
4510 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4516 format: function(x) {
4517 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4544 // console.log(state.c3_instance);
4549 // ----------------------------------------------------------------------------------------------------------------
4552 NETDATA.d3Initialize = function(callback) {
4553 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4558 xhrFields: { withCredentials: true } // required for the cookie
4561 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4564 NETDATA.chartLibraries.d3.enabled = false;
4565 NETDATA.error(100, NETDATA.d3_js);
4567 .always(function() {
4568 if(typeof callback === "function")
4573 NETDATA.chartLibraries.d3.enabled = false;
4574 if(typeof callback === "function")
4579 NETDATA.d3ChartUpdate = function(state, data) {
4583 NETDATA.d3ChartCreate = function(state, data) {
4587 // ----------------------------------------------------------------------------------------------------------------
4590 NETDATA.googleInitialize = function(callback) {
4591 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4593 url: NETDATA.google_js,
4596 xhrFields: { withCredentials: true } // required for the cookie
4599 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4600 google.load('visualization', '1.1', {
4601 'packages': ['corechart', 'controls'],
4602 'callback': callback
4606 NETDATA.chartLibraries.google.enabled = false;
4607 NETDATA.error(100, NETDATA.google_js);
4608 if(typeof callback === "function")
4613 NETDATA.chartLibraries.google.enabled = false;
4614 if(typeof callback === "function")
4619 NETDATA.googleChartUpdate = function(state, data) {
4620 var datatable = new google.visualization.DataTable(data.result);
4621 state.google_instance.draw(datatable, state.google_options);
4625 NETDATA.googleChartCreate = function(state, data) {
4626 var datatable = new google.visualization.DataTable(data.result);
4628 state.google_options = {
4629 colors: state.chartColors(),
4631 // do not set width, height - the chart resizes itself
4632 //width: state.chartWidth(),
4633 //height: state.chartHeight(),
4638 // title: "Time of Day",
4639 // format:'HH:mm:ss',
4640 viewWindowMode: 'maximized',
4652 viewWindowMode: 'pretty',
4667 focusTarget: 'category',
4674 titlePosition: 'out',
4685 curveType: 'function',
4690 switch(state.chart.chart_type) {
4692 state.google_options.vAxis.viewWindowMode = 'maximized';
4693 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4694 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4698 state.google_options.isStacked = true;
4699 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4700 state.google_options.vAxis.viewWindowMode = 'maximized';
4701 state.google_options.vAxis.minValue = null;
4702 state.google_options.vAxis.maxValue = null;
4703 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4708 state.google_options.lineWidth = 2;
4709 state.google_instance = new google.visualization.LineChart(state.element_chart);
4713 state.google_instance.draw(datatable, state.google_options);
4717 // ----------------------------------------------------------------------------------------------------------------
4719 NETDATA.percentFromValueMax = function(value, max) {
4720 if(value === null) value = 0;
4721 if(max < value) max = value;
4725 pcent = Math.round(value * 100 / max);
4726 if(pcent === 0 && value > 0) pcent = 1;
4732 // ----------------------------------------------------------------------------------------------------------------
4735 NETDATA.easypiechartInitialize = function(callback) {
4736 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4738 url: NETDATA.easypiechart_js,
4741 xhrFields: { withCredentials: true } // required for the cookie
4744 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4747 NETDATA.chartLibraries.easypiechart.enabled = false;
4748 NETDATA.error(100, NETDATA.easypiechart_js);
4750 .always(function() {
4751 if(typeof callback === "function")
4756 NETDATA.chartLibraries.easypiechart.enabled = false;
4757 if(typeof callback === "function")
4762 NETDATA.easypiechartClearSelection = function(state) {
4763 if(typeof state.easyPieChartEvent !== 'undefined') {
4764 if(state.easyPieChartEvent.timer !== null)
4765 clearTimeout(state.easyPieChartEvent.timer);
4767 state.easyPieChartEvent.timer = null;
4770 if(state.isAutoRefreshable() === true && state.data !== null) {
4771 NETDATA.easypiechartChartUpdate(state, state.data);
4774 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4775 state.easyPieChart_instance.update(0);
4777 state.easyPieChart_instance.enableAnimation();
4782 NETDATA.easypiechartSetSelection = function(state, t) {
4783 if(state.timeIsVisible(t) !== true)
4784 return NETDATA.easypiechartClearSelection(state);
4786 var slot = state.calculateRowForTime(t);
4787 if(slot < 0 || slot >= state.data.result.length)
4788 return NETDATA.easypiechartClearSelection(state);
4790 if(typeof state.easyPieChartEvent === 'undefined') {
4791 state.easyPieChartEvent = {
4798 var value = state.data.result[state.data.result.length - 1 - slot];
4799 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4800 var pcent = NETDATA.percentFromValueMax(value, max);
4802 state.easyPieChartEvent.value = value;
4803 state.easyPieChartEvent.pcent = pcent;
4804 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4806 if(state.easyPieChartEvent.timer === null) {
4807 state.easyPieChart_instance.disableAnimation();
4809 state.easyPieChartEvent.timer = setTimeout(function() {
4810 state.easyPieChartEvent.timer = null;
4811 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4812 }, NETDATA.options.current.charts_selection_animation_delay);
4818 NETDATA.easypiechartChartUpdate = function(state, data) {
4819 var value, max, pcent;
4821 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4827 value = data.result[0];
4828 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4829 pcent = NETDATA.percentFromValueMax(value, max);
4832 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4833 state.easyPieChart_instance.update(pcent);
4837 NETDATA.easypiechartChartCreate = function(state, data) {
4838 var self = $(state.element);
4839 var chart = $(state.element_chart);
4841 var value = data.result[0];
4842 var max = self.data('easypiechart-max-value') || null;
4843 var adjust = self.data('easypiechart-adjust') || null;
4847 state.easyPieChartMax = null;
4850 state.easyPieChartMax = max;
4852 var pcent = NETDATA.percentFromValueMax(value, max);
4854 chart.data('data-percent', pcent);
4858 case 'width': size = state.chartHeight(); break;
4859 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4860 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4862 default: size = state.chartWidth(); break;
4864 state.element.style.width = size + 'px';
4865 state.element.style.height = size + 'px';
4867 var stroke = Math.floor(size / 22);
4868 if(stroke < 3) stroke = 2;
4870 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4871 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4872 state.easyPieChartLabel = document.createElement('span');
4873 state.easyPieChartLabel.className = 'easyPieChartLabel';
4874 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4875 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4876 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4877 state.element_chart.appendChild(state.easyPieChartLabel);
4879 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4880 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4881 state.easyPieChartTitle = document.createElement('span');
4882 state.easyPieChartTitle.className = 'easyPieChartTitle';
4883 state.easyPieChartTitle.innerHTML = state.title;
4884 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4885 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4886 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4887 state.element_chart.appendChild(state.easyPieChartTitle);
4889 var unitfontsize = Math.round(titlefontsize * 0.9);
4890 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4891 state.easyPieChartUnits = document.createElement('span');
4892 state.easyPieChartUnits.className = 'easyPieChartUnits';
4893 state.easyPieChartUnits.innerHTML = state.units;
4894 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4895 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4896 state.element_chart.appendChild(state.easyPieChartUnits);
4898 chart.easyPieChart({
4899 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4900 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4901 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4902 scaleLength: self.data('easypiechart-scalelength') || 5,
4903 lineCap: self.data('easypiechart-linecap') || 'round',
4904 lineWidth: self.data('easypiechart-linewidth') || stroke,
4905 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4906 size: self.data('easypiechart-size') || size,
4907 rotate: self.data('easypiechart-rotate') || 0,
4908 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4909 easing: self.data('easypiechart-easing') || undefined
4912 // when we just re-create the chart
4913 // do not animate the first update
4915 if(typeof state.easyPieChart_instance !== 'undefined')
4918 state.easyPieChart_instance = chart.data('easyPieChart');
4919 if(animate === false) state.easyPieChart_instance.disableAnimation();
4920 state.easyPieChart_instance.update(pcent);
4921 if(animate === false) state.easyPieChart_instance.enableAnimation();
4925 // ----------------------------------------------------------------------------------------------------------------
4928 NETDATA.gaugeInitialize = function(callback) {
4929 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4931 url: NETDATA.gauge_js,
4934 xhrFields: { withCredentials: true } // required for the cookie
4937 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4940 NETDATA.chartLibraries.gauge.enabled = false;
4941 NETDATA.error(100, NETDATA.gauge_js);
4943 .always(function() {
4944 if(typeof callback === "function")
4949 NETDATA.chartLibraries.gauge.enabled = false;
4950 if(typeof callback === "function")
4955 NETDATA.gaugeAnimation = function(state, status) {
4958 if(typeof status === 'boolean' && status === false)
4960 else if(typeof status === 'number')
4963 state.gauge_instance.animationSpeed = speed;
4964 state.___gaugeOld__.speed = speed;
4967 NETDATA.gaugeSet = function(state, value, min, max) {
4968 if(typeof value !== 'number') value = 0;
4969 if(typeof min !== 'number') min = 0;
4970 if(typeof max !== 'number') max = 0;
4971 if(value > max) max = value;
4972 if(value < min) min = value;
4981 // gauge.js has an issue if the needle
4982 // is smaller than min or larger than max
4983 // when we set the new values
4984 // the needle will go crazy
4986 // to prevent it, we always feed it
4987 // with a percentage, so that the needle
4988 // is always between min and max
4989 var pcent = (value - min) * 100 / (max - min);
4991 // these should never happen
4992 if(pcent < 0) pcent = 0;
4993 if(pcent > 100) pcent = 100;
4995 state.gauge_instance.set(pcent);
4997 state.___gaugeOld__.value = value;
4998 state.___gaugeOld__.min = min;
4999 state.___gaugeOld__.max = max;
5002 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5003 if(state.___gaugeOld__.valueLabel !== value) {
5004 state.___gaugeOld__.valueLabel = value;
5005 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5007 if(state.___gaugeOld__.minLabel !== min) {
5008 state.___gaugeOld__.minLabel = min;
5009 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5011 if(state.___gaugeOld__.maxLabel !== max) {
5012 state.___gaugeOld__.maxLabel = max;
5013 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5017 NETDATA.gaugeClearSelection = function(state) {
5018 if(typeof state.gaugeEvent !== 'undefined') {
5019 if(state.gaugeEvent.timer !== null)
5020 clearTimeout(state.gaugeEvent.timer);
5022 state.gaugeEvent.timer = null;
5025 if(state.isAutoRefreshable() === true && state.data !== null) {
5026 NETDATA.gaugeChartUpdate(state, state.data);
5029 NETDATA.gaugeAnimation(state, false);
5030 NETDATA.gaugeSet(state, null, null, null);
5031 NETDATA.gaugeSetLabels(state, null, null, null);
5034 NETDATA.gaugeAnimation(state, true);
5038 NETDATA.gaugeSetSelection = function(state, t) {
5039 if(state.timeIsVisible(t) !== true)
5040 return NETDATA.gaugeClearSelection(state);
5042 var slot = state.calculateRowForTime(t);
5043 if(slot < 0 || slot >= state.data.result.length)
5044 return NETDATA.gaugeClearSelection(state);
5046 if(typeof state.gaugeEvent === 'undefined') {
5047 state.gaugeEvent = {
5055 var value = state.data.result[state.data.result.length - 1 - slot];
5056 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5059 state.gaugeEvent.value = value;
5060 state.gaugeEvent.max = max;
5061 state.gaugeEvent.min = min;
5062 NETDATA.gaugeSetLabels(state, value, min, max);
5064 if(state.gaugeEvent.timer === null) {
5065 NETDATA.gaugeAnimation(state, false);
5067 state.gaugeEvent.timer = setTimeout(function() {
5068 state.gaugeEvent.timer = null;
5069 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5070 }, NETDATA.options.current.charts_selection_animation_delay);
5076 NETDATA.gaugeChartUpdate = function(state, data) {
5077 var value, min, max;
5079 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5083 NETDATA.gaugeSetLabels(state, null, null, null);
5086 value = data.result[0];
5088 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5089 if(value > max) max = value;
5090 NETDATA.gaugeSetLabels(state, value, min, max);
5093 NETDATA.gaugeSet(state, value, min, max);
5097 NETDATA.gaugeChartCreate = function(state, data) {
5098 var self = $(state.element);
5099 // var chart = $(state.element_chart);
5101 var value = data.result[0];
5102 var max = self.data('gauge-max-value') || null;
5103 var adjust = self.data('gauge-adjust') || null;
5104 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5105 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5106 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5107 var stopColor = self.data('gauge-stop-color') || void 0;
5108 var generateGradient = self.data('gauge-generate-gradient') || false;
5112 state.gaugeMax = null;
5115 state.gaugeMax = max;
5117 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5119 // case 'width': width = height * ratio; break;
5121 // default: height = width / ratio; break;
5123 //state.element.style.width = width.toString() + 'px';
5124 //state.element.style.height = height.toString() + 'px';
5129 lines: 12, // The number of lines to draw
5130 angle: 0.15, // The length of each line
5131 lineWidth: 0.44, // 0.44 The line thickness
5133 length: 0.8, // 0.9 The radius of the inner circle
5134 strokeWidth: 0.035, // The rotation offset
5135 color: pointerColor // Fill color
5137 colorStart: startColor, // Colors
5138 colorStop: stopColor, // just experiment with them
5139 strokeColor: strokeColor, // to see which ones work best for you
5141 generateGradient: (generateGradient === true)?true:false,
5145 if (generateGradient.constructor === Array) {
5147 // data-gauge-generate-gradient="[0, 50, 100]"
5148 // data-gauge-gradient-percent-color-0="#FFFFFF"
5149 // data-gauge-gradient-percent-color-50="#999900"
5150 // data-gauge-gradient-percent-color-100="#000000"
5152 options.percentColors = new Array();
5153 var len = generateGradient.length;
5155 var pcent = generateGradient[len];
5156 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5157 if(color !== false) {
5158 var a = new Array();
5161 options.percentColors.unshift(a);
5164 if(options.percentColors.length === 0)
5165 delete options.percentColors;
5167 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5168 options.percentColors = [
5169 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5170 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5171 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5172 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5173 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5174 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5175 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5176 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5177 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5178 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5179 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5182 state.gauge_canvas = document.createElement('canvas');
5183 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5184 state.gauge_canvas.className = 'gaugeChart';
5185 state.gauge_canvas.width = width;
5186 state.gauge_canvas.height = height;
5187 state.element_chart.appendChild(state.gauge_canvas);
5189 var valuefontsize = Math.floor(height / 6);
5190 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5191 state.gaugeChartLabel = document.createElement('span');
5192 state.gaugeChartLabel.className = 'gaugeChartLabel';
5193 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5194 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5195 state.element_chart.appendChild(state.gaugeChartLabel);
5197 var titlefontsize = Math.round(valuefontsize / 2);
5199 state.gaugeChartTitle = document.createElement('span');
5200 state.gaugeChartTitle.className = 'gaugeChartTitle';
5201 state.gaugeChartTitle.innerHTML = state.title;
5202 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5203 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5204 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5205 state.element_chart.appendChild(state.gaugeChartTitle);
5207 var unitfontsize = Math.round(titlefontsize * 0.9);
5208 state.gaugeChartUnits = document.createElement('span');
5209 state.gaugeChartUnits.className = 'gaugeChartUnits';
5210 state.gaugeChartUnits.innerHTML = state.units;
5211 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5212 state.element_chart.appendChild(state.gaugeChartUnits);
5214 state.gaugeChartMin = document.createElement('span');
5215 state.gaugeChartMin.className = 'gaugeChartMin';
5216 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5217 state.element_chart.appendChild(state.gaugeChartMin);
5219 state.gaugeChartMax = document.createElement('span');
5220 state.gaugeChartMax.className = 'gaugeChartMax';
5221 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5222 state.element_chart.appendChild(state.gaugeChartMax);
5224 // when we just re-create the chart
5225 // do not animate the first update
5227 if(typeof state.gauge_instance !== 'undefined')
5230 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5232 state.___gaugeOld__ = {
5241 // we will always feed a percentage
5242 state.gauge_instance.minValue = 0;
5243 state.gauge_instance.maxValue = 100;
5245 NETDATA.gaugeAnimation(state, animate);
5246 NETDATA.gaugeSet(state, value, 0, max);
5247 NETDATA.gaugeSetLabels(state, value, 0, max);
5248 NETDATA.gaugeAnimation(state, true);
5252 // ----------------------------------------------------------------------------------------------------------------
5253 // Charts Libraries Registration
5255 NETDATA.chartLibraries = {
5257 initialize: NETDATA.dygraphInitialize,
5258 create: NETDATA.dygraphChartCreate,
5259 update: NETDATA.dygraphChartUpdate,
5260 resize: function(state) {
5261 if(typeof state.dygraph_instance.resize === 'function')
5262 state.dygraph_instance.resize();
5264 setSelection: NETDATA.dygraphSetSelection,
5265 clearSelection: NETDATA.dygraphClearSelection,
5266 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5269 format: function(state) { return 'json'; },
5270 options: function(state) { return 'ms|flip'; },
5271 legend: function(state) {
5272 if(this.isSparkline(state) === false)
5273 return 'right-side';
5277 autoresize: function(state) { return true; },
5278 max_updates_to_recreate: function(state) { return 5000; },
5279 track_colors: function(state) { return true; },
5280 pixels_per_point: function(state) {
5281 if(this.isSparkline(state) === false)
5287 isSparkline: function(state) {
5288 if(typeof state.dygraph_sparkline === 'undefined') {
5289 var t = $(state.element).data('dygraph-theme');
5290 if(t === 'sparkline')
5291 state.dygraph_sparkline = true;
5293 state.dygraph_sparkline = false;
5295 return state.dygraph_sparkline;
5299 initialize: NETDATA.sparklineInitialize,
5300 create: NETDATA.sparklineChartCreate,
5301 update: NETDATA.sparklineChartUpdate,
5303 setSelection: undefined, // function(state, t) { return true; },
5304 clearSelection: undefined, // function(state) { return true; },
5305 toolboxPanAndZoom: null,
5308 format: function(state) { return 'array'; },
5309 options: function(state) { return 'flip|abs'; },
5310 legend: function(state) { return null; },
5311 autoresize: function(state) { return false; },
5312 max_updates_to_recreate: function(state) { return 5000; },
5313 track_colors: function(state) { return false; },
5314 pixels_per_point: function(state) { return 3; }
5317 initialize: NETDATA.peityInitialize,
5318 create: NETDATA.peityChartCreate,
5319 update: NETDATA.peityChartUpdate,
5321 setSelection: undefined, // function(state, t) { return true; },
5322 clearSelection: undefined, // function(state) { return true; },
5323 toolboxPanAndZoom: null,
5326 format: function(state) { return 'ssvcomma'; },
5327 options: function(state) { return 'null2zero|flip|abs'; },
5328 legend: function(state) { return null; },
5329 autoresize: function(state) { return false; },
5330 max_updates_to_recreate: function(state) { return 5000; },
5331 track_colors: function(state) { return false; },
5332 pixels_per_point: function(state) { return 3; }
5335 initialize: NETDATA.morrisInitialize,
5336 create: NETDATA.morrisChartCreate,
5337 update: NETDATA.morrisChartUpdate,
5339 setSelection: undefined, // function(state, t) { return true; },
5340 clearSelection: undefined, // function(state) { return true; },
5341 toolboxPanAndZoom: null,
5344 format: function(state) { return 'json'; },
5345 options: function(state) { return 'objectrows|ms'; },
5346 legend: function(state) { return null; },
5347 autoresize: function(state) { return false; },
5348 max_updates_to_recreate: function(state) { return 50; },
5349 track_colors: function(state) { return false; },
5350 pixels_per_point: function(state) { return 15; }
5353 initialize: NETDATA.googleInitialize,
5354 create: NETDATA.googleChartCreate,
5355 update: NETDATA.googleChartUpdate,
5357 setSelection: undefined, //function(state, t) { return true; },
5358 clearSelection: undefined, //function(state) { return true; },
5359 toolboxPanAndZoom: null,
5362 format: function(state) { return 'datatable'; },
5363 options: function(state) { return ''; },
5364 legend: function(state) { return null; },
5365 autoresize: function(state) { return false; },
5366 max_updates_to_recreate: function(state) { return 300; },
5367 track_colors: function(state) { return false; },
5368 pixels_per_point: function(state) { return 4; }
5371 initialize: NETDATA.raphaelInitialize,
5372 create: NETDATA.raphaelChartCreate,
5373 update: NETDATA.raphaelChartUpdate,
5375 setSelection: undefined, // function(state, t) { return true; },
5376 clearSelection: undefined, // function(state) { return true; },
5377 toolboxPanAndZoom: null,
5380 format: function(state) { return 'json'; },
5381 options: function(state) { return ''; },
5382 legend: function(state) { return null; },
5383 autoresize: function(state) { return false; },
5384 max_updates_to_recreate: function(state) { return 5000; },
5385 track_colors: function(state) { return false; },
5386 pixels_per_point: function(state) { return 3; }
5389 initialize: NETDATA.c3Initialize,
5390 create: NETDATA.c3ChartCreate,
5391 update: NETDATA.c3ChartUpdate,
5393 setSelection: undefined, // function(state, t) { return true; },
5394 clearSelection: undefined, // function(state) { return true; },
5395 toolboxPanAndZoom: null,
5398 format: function(state) { return 'csvjsonarray'; },
5399 options: function(state) { return 'milliseconds'; },
5400 legend: function(state) { return null; },
5401 autoresize: function(state) { return false; },
5402 max_updates_to_recreate: function(state) { return 5000; },
5403 track_colors: function(state) { return false; },
5404 pixels_per_point: function(state) { return 15; }
5407 initialize: NETDATA.d3Initialize,
5408 create: NETDATA.d3ChartCreate,
5409 update: NETDATA.d3ChartUpdate,
5411 setSelection: undefined, // function(state, t) { return true; },
5412 clearSelection: undefined, // function(state) { return true; },
5413 toolboxPanAndZoom: null,
5416 format: function(state) { return 'json'; },
5417 options: function(state) { return ''; },
5418 legend: function(state) { return null; },
5419 autoresize: function(state) { return false; },
5420 max_updates_to_recreate: function(state) { return 5000; },
5421 track_colors: function(state) { return false; },
5422 pixels_per_point: function(state) { return 3; }
5425 initialize: NETDATA.easypiechartInitialize,
5426 create: NETDATA.easypiechartChartCreate,
5427 update: NETDATA.easypiechartChartUpdate,
5429 setSelection: NETDATA.easypiechartSetSelection,
5430 clearSelection: NETDATA.easypiechartClearSelection,
5431 toolboxPanAndZoom: null,
5434 format: function(state) { return 'array'; },
5435 options: function(state) { return 'absolute'; },
5436 legend: function(state) { return null; },
5437 autoresize: function(state) { return false; },
5438 max_updates_to_recreate: function(state) { return 5000; },
5439 track_colors: function(state) { return true; },
5440 pixels_per_point: function(state) { return 3; },
5444 initialize: NETDATA.gaugeInitialize,
5445 create: NETDATA.gaugeChartCreate,
5446 update: NETDATA.gaugeChartUpdate,
5448 setSelection: NETDATA.gaugeSetSelection,
5449 clearSelection: NETDATA.gaugeClearSelection,
5450 toolboxPanAndZoom: null,
5453 format: function(state) { return 'array'; },
5454 options: function(state) { return 'absolute'; },
5455 legend: function(state) { return null; },
5456 autoresize: function(state) { return false; },
5457 max_updates_to_recreate: function(state) { return 5000; },
5458 track_colors: function(state) { return true; },
5459 pixels_per_point: function(state) { return 3; },
5464 NETDATA.registerChartLibrary = function(library, url) {
5465 if(NETDATA.options.debug.libraries === true)
5466 console.log("registering chart library: " + library);
5468 NETDATA.chartLibraries[library].url = url;
5469 NETDATA.chartLibraries[library].initialized = true;
5470 NETDATA.chartLibraries[library].enabled = true;
5473 // ----------------------------------------------------------------------------------------------------------------
5474 // Load required JS libraries and CSS
5476 NETDATA.requiredJs = [
5478 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5479 isAlreadyLoaded: function() {
5480 // check if bootstrap is loaded
5481 if(typeof $().emulateTransitionEnd == 'function')
5484 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5492 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5493 isAlreadyLoaded: function() { return false; }
5496 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5497 isAlreadyLoaded: function() { return false; }
5501 NETDATA.requiredCSS = [
5503 url: NETDATA.themes.current.bootstrap_css,
5504 isAlreadyLoaded: function() {
5505 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5512 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5513 isAlreadyLoaded: function() { return false; }
5516 url: NETDATA.themes.current.dashboard_css,
5517 isAlreadyLoaded: function() { return false; }
5520 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5521 isAlreadyLoaded: function() { return false; }
5525 NETDATA.loadRequiredJs = function(index, callback) {
5526 if(index >= NETDATA.requiredJs.length) {
5527 if(typeof callback === 'function')
5532 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5533 NETDATA.loadRequiredJs(++index, callback);
5537 if(NETDATA.options.debug.main_loop === true)
5538 console.log('loading ' + NETDATA.requiredJs[index].url);
5541 url: NETDATA.requiredJs[index].url,
5544 xhrFields: { withCredentials: true } // required for the cookie
5546 .success(function() {
5547 if(NETDATA.options.debug.main_loop === true)
5548 console.log('loaded ' + NETDATA.requiredJs[index].url);
5550 NETDATA.loadRequiredJs(++index, callback);
5553 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5557 NETDATA.loadRequiredCSS = function(index) {
5558 if(index >= NETDATA.requiredCSS.length)
5561 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5562 NETDATA.loadRequiredCSS(++index);
5566 if(NETDATA.options.debug.main_loop === true)
5567 console.log('loading ' + NETDATA.requiredCSS[index].url);
5569 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5570 NETDATA.loadRequiredCSS(++index);
5574 // ----------------------------------------------------------------------------------------------------------------
5575 // Registry of netdata hosts
5578 notifications: false, // when true, the browser supports notifications (may not be granted though)
5579 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5580 notifications_shown: new Array(),
5582 server: null, // the server to connect to for fetching alarms
5583 current: null, // the list of raised alarms
5584 notified: { alarms: {} }, // the list of raised alarms for which we have shown notifications
5586 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5588 notify: function(entry) {
5589 // console.log('alarm ' + entry.unique_id);
5591 if(entry.updated === true) {
5592 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5596 var name = entry.name.replace(/_/g, ' ');
5597 var title = name + ' = ' + ((entry.value === null)?'NaN':Math.floor(entry.value)).toString() + ' ' + entry.units;
5598 var body = entry.info + ' of ' + entry.chart + ' (' + entry.family + ')';
5599 var tag = entry.alarm_id;
5600 var icon = 'images/seo-performance-128.png';
5601 var interaction = false;
5604 switch(entry.status) {
5605 case 'UNINITIALIZED':
5606 // console.log('alarm ' + entry.unique_id + ' is UNINITIALIZED');
5610 // console.log('alarm ' + entry.unique_id + ' is UNDEFINED');
5614 if(NETDATA.alarms.last_notification_id === 0) {
5615 // console.log('alarm ' + entry.unique_id + ' is not current');
5618 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5619 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5622 title = entry.name + ' back to normal';
5623 icon = 'images/check-mark-2-128-green.png'
5624 interaction = false;
5628 icon = 'images/alert-128-orange.png';
5629 interaction = false;
5633 icon = 'images/alert-128-red.png'
5638 console.log('invalid alarm status ' + entry.status);
5642 // cleanup old notifications with the same alarm_id as this one
5643 var len = NETDATA.alarms.notifications_shown.length;
5645 var n = NETDATA.alarms.notifications_shown[len];
5646 if(n.data.alarm_id === entry.alarm_id) {
5647 // console.log('removing old alarm ' + n.data.unique_id);
5649 // close the notification
5652 // remove it from the array
5653 NETDATA.alarms.notifications_shown.splice(len, 1);
5654 len = NETDATA.alarms.notifications_shown.length;
5658 // show this notification
5659 // console.log('new notification: ' + title);
5660 var n = new Notification(title, {
5663 requireInteraction: interaction,
5664 icon: NETDATA.serverDefault + icon,
5669 NETDATA.alarms.notifications_shown.push(n);
5670 // console.log(entry);
5673 notifyAll: function() {
5674 NETDATA.alarms.notified = NETDATA.alarms.current;
5676 // console.log('FETCHING ALARM LOG');
5677 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5678 // console.log('ALARM LOG FETCHED');
5680 if(data === null || typeof data !== 'object') {
5681 console.log('invalid alarms log response');
5685 if(data.length === 0) {
5686 console.log('received empty alarm log');
5690 data.sort(function(a, b) {
5691 if(a.unique_id > b.unique_id) return -1;
5692 if(a.unique_id < b.unique_id) return 1;
5696 var len = data.length;
5698 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5699 NETDATA.alarms.notify(data[len]);
5703 NETDATA.alarms.last_notification_id = data[0].unique_id;
5707 check_notifications: function() {
5708 // returns true if we should fire 1+ notifications
5710 if(NETDATA.alarms.notifications !== true) {
5711 // console.log('notifications not available');
5715 if(Notification.permission !== 'granted') {
5716 // console.log('notifications not granted');
5720 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5721 // console.log('can do alarms');
5723 var current = NETDATA.alarms.current.alarms;
5724 var old = NETDATA.alarms.notified.alarms;
5727 if(Object.keys(current).length !== Object.keys(old).length) {
5728 // console.log('alarm count differs');
5734 // console.log('new alarm detected: ' + x);
5738 if(current[x].status !== old[x].status) {
5739 // console.log('alarm changed state: ' + x);
5745 //console.log('alarms did not change');
5747 // else console.log('cannot process alarms');
5752 get: function(what, callback) {
5754 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5757 xhrFields: { withCredentials: true } // required for the cookie
5759 .done(function(data) {
5760 if(typeof callback === 'function')
5764 NETDATA.error(415, NETDATA.alarms.server);
5766 if(typeof callback === 'function')
5771 update_forever: function() {
5772 NETDATA.alarms.get('active', function(data) {
5775 NETDATA.alarms.current = data;
5777 if(NETDATA.alarms.check_notifications() === true) {
5778 NETDATA.alarms.notifyAll();
5781 if (typeof NETDATA.alarms.callback === 'function') {
5782 NETDATA.alarms.callback(data);
5786 setTimeout(NETDATA.alarms.update_forever, 10000);
5790 get_log: function(last_id, callback) {
5792 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5795 xhrFields: { withCredentials: true } // required for the cookie
5797 .done(function(data) {
5798 if(typeof callback === 'function')
5802 NETDATA.error(416, NETDATA.alarms.server);
5804 if(typeof callback === 'function')
5810 var host = NETDATA.serverDefault;
5811 while(host.slice(-1) === '/')
5812 host = host.substring(0, host.length - 1);
5813 NETDATA.alarms.server = host;
5815 if(netdataShowAlarms === true) {
5816 NETDATA.alarms.update_forever();
5818 if('Notification' in window) {
5819 // console.log('notifications available');
5820 NETDATA.alarms.notifications = true;
5822 if(Notification.permission === 'default')
5823 Notification.requestPermission();
5829 // ----------------------------------------------------------------------------------------------------------------
5830 // Registry of netdata hosts
5832 NETDATA.registry = {
5833 server: null, // the netdata registry server
5834 person_guid: null, // the unique ID of this browser / user
5835 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5836 hostname: null, // the hostname of the netdata server that served dashboard.js
5837 machines: null, // the user's other URLs
5838 machines_array: null, // the user's other URLs in an array
5841 parsePersonUrls: function(person_urls) {
5842 // console.log(person_urls);
5843 NETDATA.registry.person_urls = person_urls;
5846 NETDATA.registry.machines = {};
5847 NETDATA.registry.machines_array = new Array();
5849 var now = new Date().getTime();
5850 var apu = person_urls;
5853 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5854 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5860 accesses: apu[i][3],
5862 alternate_urls: new Array()
5864 obj.alternate_urls.push(apu[i][1]);
5866 NETDATA.registry.machines[apu[i][0]] = obj;
5867 NETDATA.registry.machines_array.push(obj);
5870 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5872 var pu = NETDATA.registry.machines[apu[i][0]];
5873 if(pu.last_t < apu[i][2]) {
5875 pu.last_t = apu[i][2];
5876 pu.name = apu[i][4];
5878 pu.accesses += apu[i][3];
5879 pu.alternate_urls.push(apu[i][1]);
5884 if(typeof netdataRegistryCallback === 'function')
5885 netdataRegistryCallback(NETDATA.registry.machines_array);
5889 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5892 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5894 NETDATA.registry.server = data.registry;
5895 NETDATA.registry.machine_guid = data.machine_guid;
5896 NETDATA.registry.hostname = data.hostname;
5898 NETDATA.registry.access(2, function (person_urls) {
5899 NETDATA.registry.parsePersonUrls(person_urls);
5906 hello: function(host, callback) {
5907 while(host.slice(-1) === '/')
5908 host = host.substring(0, host.length - 1);
5910 // send HELLO to a netdata server:
5911 // 1. verifies the server is reachable
5912 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5914 url: host + '/api/v1/registry?action=hello',
5917 xhrFields: { withCredentials: true } // required for the cookie
5919 .done(function(data) {
5920 if(typeof data.status !== 'string' || data.status !== 'ok') {
5921 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5925 if(typeof callback === 'function')
5929 NETDATA.error(407, host);
5931 if(typeof callback === 'function')
5936 access: function(max_redirects, callback) {
5937 // send ACCESS to a netdata registry:
5938 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5939 // 2. it responds with a list of netdata servers we know
5940 // the registry identifies us using a cookie it sets the first time we access it
5941 // the registry may respond with a redirect URL to send us to another registry
5943 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),
5946 xhrFields: { withCredentials: true } // required for the cookie
5948 .done(function(data) {
5949 var redirect = null;
5950 if(typeof data.registry === 'string')
5951 redirect = data.registry;
5953 if(typeof data.status !== 'string' || data.status !== 'ok') {
5954 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5959 if(redirect !== null && max_redirects > 0) {
5960 NETDATA.registry.server = redirect;
5961 NETDATA.registry.access(max_redirects - 1, callback);
5964 if(typeof callback === 'function')
5969 if(typeof data.person_guid === 'string')
5970 NETDATA.registry.person_guid = data.person_guid;
5972 if(typeof callback === 'function')
5973 callback(data.urls);
5977 NETDATA.error(410, NETDATA.registry.server);
5979 if(typeof callback === 'function')
5984 delete: function(delete_url, callback) {
5985 // send DELETE to a netdata registry:
5987 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),
5990 xhrFields: { withCredentials: true } // required for the cookie
5992 .done(function(data) {
5993 if(typeof data.status !== 'string' || data.status !== 'ok') {
5994 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5998 if(typeof callback === 'function')
6002 NETDATA.error(412, NETDATA.registry.server);
6004 if(typeof callback === 'function')
6009 switch: function(new_person_guid, callback) {
6012 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,
6015 xhrFields: { withCredentials: true } // required for the cookie
6017 .done(function(data) {
6018 if(typeof data.status !== 'string' || data.status !== 'ok') {
6019 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6023 if(typeof callback === 'function')
6027 NETDATA.error(414, NETDATA.registry.server);
6029 if(typeof callback === 'function')
6035 // ----------------------------------------------------------------------------------------------------------------
6038 NETDATA.errorReset();
6039 NETDATA.loadRequiredCSS(0);
6041 NETDATA._loadjQuery(function() {
6042 NETDATA.loadRequiredJs(0, function() {
6043 if(typeof $().emulateTransitionEnd !== 'function') {
6044 // bootstrap is not available
6045 NETDATA.options.current.show_help = false;
6048 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6049 if(NETDATA.options.debug.main_loop === true)
6050 console.log('starting chart refresh thread');
6057 // window.NETDATA = NETDATA;
6058 // })(window, document);