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 netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // You can also set the default netdata server, using the following.
24 // When this variable is not set, we assume the page is hosted on your
25 // netdata server already.
26 // var netdataServer = "http://yourhost:19999"; // set your NetData server
28 //(function(window, document, undefined) {
30 // ------------------------------------------------------------------------
31 // compatibility fixes
33 // fix IE issue with console
34 if(!window.console) { window.console = { log: function(){} }; }
36 // if string.endsWith is not defined, define it
37 if(typeof String.prototype.endsWith !== 'function') {
38 String.prototype.endsWith = function(s) {
39 if(s.length > this.length) return false;
40 return this.slice(-s.length) === s;
44 // if string.startsWith is not defined, define it
45 if(typeof String.prototype.startsWith !== 'function') {
46 String.prototype.startsWith = function(s) {
47 if(s.length > this.length) return false;
48 return this.slice(s.length) === s;
53 var NETDATA = window.NETDATA || {};
55 NETDATA.name2id = function(s) {
64 // ----------------------------------------------------------------------------------------------------------------
65 // Detect the netdata server
67 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
68 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
69 NETDATA._scriptSource = function() {
72 if(typeof document.currentScript !== 'undefined') {
73 script = document.currentScript;
76 var all_scripts = document.getElementsByTagName('script');
77 script = all_scripts[all_scripts.length - 1];
80 if (typeof script.getAttribute.length !== 'undefined')
83 script = script.getAttribute('src', -1);
88 if(typeof netdataServer !== 'undefined')
89 NETDATA.serverDefault = netdataServer;
91 var s = NETDATA._scriptSource();
92 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
94 console.log('WARNING: Cannot detect the URL of the netdata server.');
95 NETDATA.serverDefault = null;
99 if(NETDATA.serverDefault === null)
100 NETDATA.serverDefault = '';
101 else if(NETDATA.serverDefault.slice(-1) !== '/')
102 NETDATA.serverDefault += '/';
104 // default URLs for all the external files we need
105 // make them RELATIVE so that the whole thing can also be
106 // installed under a web server
107 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
108 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
109 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
110 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
111 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
112 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
113 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
114 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
115 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
116 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
117 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
118 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
119 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
120 NETDATA.google_js = 'https://www.google.com/jsapi';
124 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
125 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
126 background: '#FFFFFF',
127 foreground: '#000000',
130 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
131 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
132 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
133 '#329262', '#3B3EAC' ],
134 easypiechart_track: '#f0f0f0',
135 easypiechart_scale: '#dfe0e0',
136 gauge_pointer: '#C0C0C0',
137 gauge_stroke: '#F0F0F0',
138 gauge_gradient: false
141 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
142 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
143 background: '#272b30',
144 foreground: '#C8C8C8',
147 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
148 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
149 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
150 '#a6a479', '#a66da8' ],
152 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
153 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
154 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
155 '#329262', '#3B3EFF' ],
156 easypiechart_track: '#373b40',
157 easypiechart_scale: '#373b40',
158 gauge_pointer: '#474b50',
159 gauge_stroke: '#373b40',
160 gauge_gradient: false
164 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
165 NETDATA.themes.current = NETDATA.themes[netdataTheme];
167 NETDATA.themes.current = NETDATA.themes.white;
169 NETDATA.colors = NETDATA.themes.current.colors;
171 // these are the colors Google Charts are using
172 // we have them here to attempt emulate their look and feel on the other chart libraries
173 // http://there4.io/2012/05/02/google-chart-color-list/
174 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
175 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
176 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
178 // an alternative set
179 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
180 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
181 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
183 if(typeof netdataShowHelp === 'undefined')
184 netdataShowHelp = true;
186 if(typeof netdataShowAlarms === 'undefined')
187 netdataShowAlarms = false;
189 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
190 netdataRegistryAfterMs = 1500;
192 if(typeof netdataRegistry === 'undefined') {
193 // backward compatibility
194 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
195 netdataRegistry = true;
197 netdataRegistry = false;
199 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
200 netdataRegistry = true;
202 // ----------------------------------------------------------------------------------------------------------------
203 // the defaults for all charts
205 // if the user does not specify any of these, the following will be used
207 NETDATA.chartDefaults = {
208 host: NETDATA.serverDefault, // the server to get data from
209 width: '100%', // the chart width - can be null
210 height: '100%', // the chart height - can be null
211 min_width: null, // the chart minimum width - can be null
212 library: 'dygraph', // the graphing library to use
213 method: 'average', // the grouping method
214 before: 0, // panning
215 after: -600, // panning
216 pixels_per_point: 1, // the detail of the chart
217 fill_luminance: 0.8 // luminance of colors in solit areas
220 // ----------------------------------------------------------------------------------------------------------------
224 pauseCallback: null, // a callback when we are really paused
226 pause: false, // when enabled we don't auto-refresh the charts
228 targets: null, // an array of all the state objects that are
229 // currently active (independently of their
230 // viewport visibility)
232 updated_dom: true, // when true, the DOM has been updated with
233 // new elements we have to check.
235 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
236 // rendering charts continiously.
237 // used with .current.fast_render_timeframe
239 page_is_visible: true, // when true, this page is visible
241 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
242 // auto-refresher for some time (when a chart is
243 // performing pan or zoom, we need to stop refreshing
244 // all other charts, to have the maximum speed for
245 // rendering the chart that is panned or zoomed).
246 // Used with .current.global_pan_sync_time
248 last_resized: new Date().getTime(), // the timestamp of the last resize request
250 last_page_scroll: 0, // the timestamp the last time the page was scrolled
252 // the current profile
253 // we may have many...
255 pixels_per_point: 1, // the minimum pixels per point for all charts
256 // increase this to speed javascript up
257 // each chart library has its own limit too
258 // the max of this and the chart library is used
259 // the final is calculated every time, so a change
260 // here will have immediate effect on the next chart
263 idle_between_charts: 100, // ms - how much time to wait between chart updates
265 fast_render_timeframe: 200, // ms - render continously until this time of continious
266 // rendering has been reached
267 // this setting is used to make it render e.g. 10
268 // charts at once, sleep idle_between_charts time
269 // and continue for another 10 charts.
271 idle_between_loops: 500, // ms - if all charts have been updated, wait this
272 // time before starting again.
274 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
276 idle_lost_focus: 500, // ms - when the window does not have focus, check
277 // if focus has been regained, every this time
279 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
280 // autorefreshing of charts is paused for this amount
283 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
284 // of time before setting up synchronized selections
287 sync_selection: true, // enable or disable selection sync
289 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
291 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
293 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
295 update_only_visible: true, // enable or disable visibility management
297 parallel_refresher: true, // enable parallel refresh of charts
299 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
301 destroy_on_hide: false, // destroy charts when they are not visible
303 show_help: netdataShowHelp, // when enabled the charts will show some help
304 show_help_delay_show_ms: 500,
305 show_help_delay_hide_ms: 0,
307 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
309 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
310 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
312 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
314 smooth_plot: true, // enable smooth plot, where possible
316 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
318 color_fill_opacity_line: 1.0,
319 color_fill_opacity_area: 0.2,
320 color_fill_opacity_stacked: 0.8,
322 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
323 pan_and_zoom_factor_multiplier_control: 2.0,
324 pan_and_zoom_factor_multiplier_shift: 3.0,
325 pan_and_zoom_factor_multiplier_alt: 4.0,
327 abort_ajax_on_scroll: false,
329 setOptionCallback: function() { ; }
337 chart_data_url: false,
338 chart_errors: false, // FIXME
346 NETDATA.statistics = {
349 refreshes_active_max: 0
353 // ----------------------------------------------------------------------------------------------------------------
354 // local storage options
356 NETDATA.localStorage = {
359 callback: {} // only used for resetting back to defaults
362 NETDATA.localStorageGet = function(key, def, callback) {
365 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
366 NETDATA.localStorage.default[key.toString()] = def;
367 NETDATA.localStorage.callback[key.toString()] = callback;
370 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
372 // console.log('localStorage: loading "' + key.toString() + '"');
373 ret = localStorage.getItem(key.toString());
374 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
375 if(ret === null || ret === 'undefined') {
376 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
377 localStorage.setItem(key.toString(), JSON.stringify(def));
381 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
382 ret = JSON.parse(ret);
383 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
387 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
392 if(typeof ret === 'undefined' || ret === 'undefined') {
393 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
397 NETDATA.localStorage.current[key.toString()] = ret;
401 NETDATA.localStorageSet = function(key, value, callback) {
402 if(typeof value === 'undefined' || value === 'undefined') {
403 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
406 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
407 NETDATA.localStorage.default[key.toString()] = value;
408 NETDATA.localStorage.current[key.toString()] = value;
409 NETDATA.localStorage.callback[key.toString()] = callback;
412 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
413 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
415 localStorage.setItem(key.toString(), JSON.stringify(value));
418 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
422 NETDATA.localStorage.current[key.toString()] = value;
426 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
428 if(typeof obj[i] === 'object') {
429 //console.log('object ' + prefix + '.' + i.toString());
430 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
434 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
438 NETDATA.setOption = function(key, value) {
439 if(key.toString() === 'setOptionCallback') {
440 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
441 NETDATA.options.current[key.toString()] = value;
442 NETDATA.options.current.setOptionCallback();
445 else if(NETDATA.options.current[key.toString()] !== value) {
446 var name = 'options.' + key.toString();
448 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
449 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
451 //console.log(NETDATA.localStorage);
452 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
453 //console.log(NETDATA.options);
454 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
456 if(typeof NETDATA.options.current.setOptionCallback === 'function')
457 NETDATA.options.current.setOptionCallback();
463 NETDATA.getOption = function(key) {
464 return NETDATA.options.current[key.toString()];
467 // read settings from local storage
468 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
470 // always start with this option enabled.
471 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
473 NETDATA.resetOptions = function() {
474 for(var i in NETDATA.localStorage.default) {
475 var a = i.split('.');
477 if(a[0] === 'options') {
478 if(a[1] === 'setOptionCallback') continue;
479 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
480 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
482 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
484 else if(a[0] === 'chart_heights') {
485 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
486 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
492 // ----------------------------------------------------------------------------------------------------------------
494 if(NETDATA.options.debug.main_loop === true)
495 console.log('welcome to NETDATA');
497 NETDATA.onresize = function() {
498 NETDATA.options.last_resized = new Date().getTime();
502 NETDATA.onscroll = function() {
503 // console.log('onscroll');
505 NETDATA.options.last_page_scroll = new Date().getTime();
506 NETDATA.options.auto_refresher_stop_until = 0;
508 if(NETDATA.options.targets === null) return;
510 // when the user scrolls he sees that we have
511 // hidden all the not-visible charts
512 // using this little function we try to switch
513 // the charts back to visible quickly
514 var targets = NETDATA.options.targets;
515 var len = targets.length;
516 if(NETDATA.options.abort_ajax_on_scroll === true) {
518 if (targets[len]._updating === true) {
519 if (typeof targets[len].xhr !== 'undefined') {
520 targets[len].xhr.abort();
521 targets[len].running = false;
522 targets[len]._updating = false;
524 targets[len].isVisible();
530 targets[len].isVisible();
534 window.onresize = NETDATA.onresize;
535 window.onscroll = NETDATA.onscroll;
537 // ----------------------------------------------------------------------------------------------------------------
540 NETDATA.errorCodes = {
541 100: { message: "Cannot load chart library", alert: true },
542 101: { message: "Cannot load jQuery", alert: true },
543 402: { message: "Chart library not found", alert: false },
544 403: { message: "Chart library not enabled/is failed", alert: false },
545 404: { message: "Chart not found", alert: false },
546 405: { message: "Cannot download charts index from server", alert: true },
547 406: { message: "Invalid charts index downloaded from server", alert: true },
548 407: { message: "Cannot HELLO netdata server", alert: false },
549 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
550 409: { message: "Cannot ACCESS netdata registry", alert: false },
551 410: { message: "Netdata registry ACCESS failed", alert: false },
552 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
553 412: { message: "Netdata registry DELETE failed", alert: false },
554 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
555 414: { message: "Netdata registry SWITCH failed", alert: false },
556 415: { message: "Netdata alarms download failed", alert: false },
557 416: { message: "Netdata alarms log download failed", alert: false }
559 NETDATA.errorLast = {
565 NETDATA.error = function(code, msg) {
566 NETDATA.errorLast.code = code;
567 NETDATA.errorLast.message = msg;
568 NETDATA.errorLast.datetime = new Date().getTime();
570 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
573 if(typeof netdataErrorCallback === 'function') {
574 ret = netdataErrorCallback('system', code, msg);
577 if(ret && NETDATA.errorCodes[code].alert)
578 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
581 NETDATA.errorReset = function() {
582 NETDATA.errorLast.code = 0;
583 NETDATA.errorLast.message = "You are doing fine!";
584 NETDATA.errorLast.datetime = 0;
587 // ----------------------------------------------------------------------------------------------------------------
590 // When multiple charts need the same chart, we avoid downloading it
591 // multiple times (and having it in browser memory multiple time)
592 // by using this registry.
594 // Every time we download a chart definition, we save it here with .add()
595 // Then we try to get it back with .get(). If that fails, we download it.
597 NETDATA.chartRegistry = {
600 fixid: function(id) {
601 return id.replace(/:/g, "_").replace(/\//g, "_");
604 add: function(host, id, data) {
605 host = this.fixid(host);
608 if(typeof this.charts[host] === 'undefined')
609 this.charts[host] = {};
611 //console.log('added ' + host + '/' + id);
612 this.charts[host][id] = data;
615 get: function(host, id) {
616 host = this.fixid(host);
619 if(typeof this.charts[host] === 'undefined')
622 if(typeof this.charts[host][id] === 'undefined')
625 //console.log('cached ' + host + '/' + id);
626 return this.charts[host][id];
629 downloadAll: function(host, callback) {
630 while(host.slice(-1) === '/')
631 host = host.substring(0, host.length - 1);
636 url: host + '/api/v1/charts',
639 xhrFields: { withCredentials: true } // required for the cookie
641 .done(function(data) {
643 var h = NETDATA.chartRegistry.fixid(host);
644 self.charts[h] = data.charts;
646 else NETDATA.error(406, host + '/api/v1/charts');
648 if(typeof callback === 'function')
652 NETDATA.error(405, host + '/api/v1/charts');
654 if(typeof callback === 'function')
660 // ----------------------------------------------------------------------------------------------------------------
661 // Global Pan and Zoom on charts
663 // Using this structure are synchronize all the charts, so that
664 // when you pan or zoom one, all others are automatically refreshed
665 // to the same timespan.
667 NETDATA.globalPanAndZoom = {
668 seq: 0, // timestamp ms
669 // every time a chart is panned or zoomed
670 // we set the timestamp here
671 // then we use it as a sequence number
672 // to find if other charts are syncronized
675 master: null, // the master chart (state), to which all others
678 force_before_ms: null, // the timespan to sync all other charts
679 force_after_ms: null,
684 setMaster: function(state, after, before) {
685 if(NETDATA.options.current.sync_pan_and_zoom === false)
688 if(this.master !== null && this.master !== state)
689 this.master.resetChart(true, true);
691 var now = new Date().getTime();
694 this.force_after_ms = after;
695 this.force_before_ms = before;
696 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
698 if(typeof this.callback === 'function')
699 this.callback(true, after, before);
703 clearMaster: function() {
704 if(this.master !== null) {
705 var st = this.master;
712 this.force_after_ms = null;
713 this.force_before_ms = null;
714 NETDATA.options.auto_refresher_stop_until = 0;
716 if(typeof this.callback === 'function')
717 this.callback(false, 0, 0);
720 // is the given state the master of the global
721 // pan and zoom sync?
722 isMaster: function(state) {
723 if(this.master === state) return true;
727 // are we currently have a global pan and zoom sync?
728 isActive: function() {
729 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
733 // check if a chart, other than the master
734 // needs to be refreshed, due to the global pan and zoom
735 shouldBeAutoRefreshed: function(state) {
736 if(this.master === null || this.seq === 0)
739 //if(state.needsRecreation())
742 if(state.tm.pan_and_zoom_seq === this.seq)
749 // ----------------------------------------------------------------------------------------------------------------
750 // dimensions selection
753 // move color assignment to dimensions, here
755 dimensionStatus = function(parent, label, name_div, value_div, color) {
756 this.enabled = false;
757 this.parent = parent;
759 this.name_div = null;
760 this.value_div = null;
761 this.color = NETDATA.themes.current.foreground;
763 if(parent.selected_count > parent.unselected_count)
764 this.selected = true;
766 this.selected = false;
768 this.setOptions(name_div, value_div, color);
771 dimensionStatus.prototype.invalidate = function() {
772 this.name_div = null;
773 this.value_div = null;
774 this.enabled = false;
777 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
780 if(this.name_div != name_div) {
781 this.name_div = name_div;
782 this.name_div.title = this.label;
783 this.name_div.style.color = this.color;
784 if(this.selected === false)
785 this.name_div.className = 'netdata-legend-name not-selected';
787 this.name_div.className = 'netdata-legend-name selected';
790 if(this.value_div != value_div) {
791 this.value_div = value_div;
792 this.value_div.title = this.label;
793 this.value_div.style.color = this.color;
794 if(this.selected === false)
795 this.value_div.className = 'netdata-legend-value not-selected';
797 this.value_div.className = 'netdata-legend-value selected';
804 dimensionStatus.prototype.setHandler = function() {
805 if(this.enabled === false) return;
809 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
810 this.name_div.onclick = this.value_div.onclick = function(e) {
812 if(ds.isSelected()) {
814 if(e.shiftKey === true || e.ctrlKey === true) {
815 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
818 if(ds.parent.countSelected() === 0)
819 ds.parent.selectAll();
822 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
823 if(ds.parent.countSelected() === 1) {
824 ds.parent.selectAll();
827 ds.parent.selectNone();
833 // this is not selected
834 if(e.shiftKey === true || e.ctrlKey === true) {
835 // control or shift key is pressed -> select this too
839 // no key is pressed -> select only this
840 ds.parent.selectNone();
845 ds.parent.state.redrawChart();
849 dimensionStatus.prototype.select = function() {
850 if(this.enabled === false) return;
852 this.name_div.className = 'netdata-legend-name selected';
853 this.value_div.className = 'netdata-legend-value selected';
854 this.selected = true;
857 dimensionStatus.prototype.unselect = function() {
858 if(this.enabled === false) return;
860 this.name_div.className = 'netdata-legend-name not-selected';
861 this.value_div.className = 'netdata-legend-value hidden';
862 this.selected = false;
865 dimensionStatus.prototype.isSelected = function() {
866 return(this.enabled === true && this.selected === true);
869 // ----------------------------------------------------------------------------------------------------------------
871 dimensionsVisibility = function(state) {
874 this.dimensions = {};
875 this.selected_count = 0;
876 this.unselected_count = 0;
879 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
880 if(typeof this.dimensions[label] === 'undefined') {
882 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
885 this.dimensions[label].setOptions(name_div, value_div, color);
887 return this.dimensions[label];
890 dimensionsVisibility.prototype.dimensionGet = function(label) {
891 return this.dimensions[label];
894 dimensionsVisibility.prototype.invalidateAll = function() {
895 for(var d in this.dimensions)
896 this.dimensions[d].invalidate();
899 dimensionsVisibility.prototype.selectAll = function() {
900 for(var d in this.dimensions)
901 this.dimensions[d].select();
904 dimensionsVisibility.prototype.countSelected = function() {
906 for(var d in this.dimensions)
907 if(this.dimensions[d].isSelected()) i++;
912 dimensionsVisibility.prototype.selectNone = function() {
913 for(var d in this.dimensions)
914 this.dimensions[d].unselect();
917 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
918 var ret = new Array();
919 this.selected_count = 0;
920 this.unselected_count = 0;
922 for(var i = 0, len = array.length; i < len ; i++) {
923 var ds = this.dimensions[array[i]];
924 if(typeof ds === 'undefined') {
925 // console.log(array[i] + ' is not found');
930 if(ds.isSelected()) {
932 this.selected_count++;
936 this.unselected_count++;
940 if(this.selected_count === 0 && this.unselected_count !== 0) {
942 return this.selected2BooleanArray(array);
949 // ----------------------------------------------------------------------------------------------------------------
950 // global selection sync
952 NETDATA.globalSelectionSync = {
959 if(this.state !== null)
960 this.state.globalSelectionSyncStop();
964 if(this.state !== null) {
965 this.state.globalSelectionSyncDelay();
970 // ----------------------------------------------------------------------------------------------------------------
971 // Our state object, where all per-chart values are stored
973 chartState = function(element) {
974 var self = $(element);
975 this.element = element;
978 // all private functions should use 'that', instead of 'this'
982 * show an error instead of the chart
984 var error = function(msg) {
987 if(typeof netdataErrorCallback === 'function') {
988 ret = netdataErrorCallback('chart', that.id, msg);
992 that.element.innerHTML = that.id + ': ' + msg;
993 that.enabled = false;
994 that.current = that.pan;
998 // GUID - a unique identifier for the chart
999 this.uuid = NETDATA.guid();
1001 // string - the name of chart
1002 this.id = self.data('netdata');
1004 // string - the key for localStorage settings
1005 this.settings_id = self.data('id') || null;
1007 // the user given dimensions of the element
1008 this.width = self.data('width') || NETDATA.chartDefaults.width;
1009 this.height = self.data('height') || NETDATA.chartDefaults.height;
1011 if(this.settings_id !== null) {
1012 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1013 // this is the callback that will be called
1014 // if and when the user resets all localStorage variables
1015 // to their defaults
1017 resizeChartToHeight(height);
1021 // string - the netdata server URL, without any path
1022 this.host = self.data('host') || NETDATA.chartDefaults.host;
1024 // make sure the host does not end with /
1025 // all netdata API requests use absolute paths
1026 while(this.host.slice(-1) === '/')
1027 this.host = this.host.substring(0, this.host.length - 1);
1029 // string - the grouping method requested by the user
1030 this.method = self.data('method') || NETDATA.chartDefaults.method;
1032 // the time-range requested by the user
1033 this.after = self.data('after') || NETDATA.chartDefaults.after;
1034 this.before = self.data('before') || NETDATA.chartDefaults.before;
1036 // the pixels per point requested by the user
1037 this.pixels_per_point = self.data('pixels-per-point') || 1;
1038 this.points = self.data('points') || null;
1040 // the dimensions requested by the user
1041 this.dimensions = self.data('dimensions') || null;
1043 // the chart library requested by the user
1044 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1046 // object - the chart library used
1047 this.library = null;
1051 this.colors_assigned = {};
1052 this.colors_available = null;
1054 // the element already created by the user
1055 this.element_message = null;
1057 // the element with the chart
1058 this.element_chart = null;
1060 // the element with the legend of the chart (if created by us)
1061 this.element_legend = null;
1062 this.element_legend_childs = {
1072 this.chart_url = null; // string - the url to download chart info
1073 this.chart = null; // object - the chart as downloaded from the server
1075 this.title = self.data('title') || null; // the title of the chart
1076 this.units = self.data('units') || null; // the units of the chart dimensions
1077 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1079 this.running = false; // boolean - true when the chart is being refreshed now
1080 this.validated = false; // boolean - has the chart been validated?
1081 this.enabled = true; // boolean - is the chart enabled for refresh?
1082 this.paused = false; // boolean - is the chart paused for any reason?
1083 this.selected = false; // boolean - is the chart shown a selection?
1084 this.debug = false; // boolean - console.log() debug info about this chart
1086 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1087 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1088 this.requested_after = null; // milliseconds - the timestamp of the request after param
1089 this.requested_before = null; // milliseconds - the timestamp of the request before param
1090 this.requested_padding = null;
1091 this.view_after = 0;
1092 this.view_before = 0;
1097 force_update_at: 0, // the timestamp to force the update at
1098 force_before_ms: null,
1099 force_after_ms: null
1104 force_update_at: 0, // the timestamp to force the update at
1105 force_before_ms: null,
1106 force_after_ms: null
1111 force_update_at: 0, // the timestamp to force the update at
1112 force_before_ms: null,
1113 force_after_ms: null
1116 // this is a pointer to one of the sub-classes below
1118 this.current = this.auto;
1120 // check the requested library is available
1121 // we don't initialize it here - it will be initialized when
1122 // this chart will be first used
1123 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1124 NETDATA.error(402, that.library_name);
1125 error('chart library "' + that.library_name + '" is not found');
1128 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1129 NETDATA.error(403, that.library_name);
1130 error('chart library "' + that.library_name + '" is not enabled');
1134 that.library = NETDATA.chartLibraries[that.library_name];
1136 // milliseconds - the time the last refresh took
1137 this.refresh_dt_ms = 0;
1139 // if we need to report the rendering speed
1140 // find the element that needs to be updated
1141 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1143 if(refresh_dt_element_name !== null)
1144 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1146 this.refresh_dt_element = null;
1148 this.dimensions_visibility = new dimensionsVisibility(this);
1150 this._updating = false;
1152 // ============================================================================================================
1153 // PRIVATE FUNCTIONS
1155 var createDOM = function() {
1156 if(that.enabled === false) return;
1158 if(that.element_message !== null) that.element_message.innerHTML = '';
1159 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1160 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1162 that.element.innerHTML = '';
1164 that.element_message = document.createElement('div');
1165 that.element_message.className = ' netdata-message hidden';
1166 that.element.appendChild(that.element_message);
1168 that.element_chart = document.createElement('div');
1169 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1170 that.element.appendChild(that.element_chart);
1172 if(that.hasLegend() === true) {
1173 that.element.className = "netdata-container-with-legend";
1174 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1176 that.element_legend = document.createElement('div');
1177 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1178 that.element.appendChild(that.element_legend);
1181 that.element.className = "netdata-container";
1182 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1184 that.element_legend = null;
1186 that.element_legend_childs.series = null;
1188 if(typeof(that.width) === 'string')
1189 $(that.element).css('width', that.width);
1190 else if(typeof(that.width) === 'number')
1191 $(that.element).css('width', that.width + 'px');
1193 if(typeof(that.library.aspect_ratio) === 'undefined') {
1194 if(typeof(that.height) === 'string')
1195 $(that.element).css('height', that.height);
1196 else if(typeof(that.height) === 'number')
1197 $(that.element).css('height', that.height + 'px');
1200 var w = that.element.offsetWidth;
1201 if(w === null || w === 0) {
1202 // the div is hidden
1203 // this will resize the chart when next viewed
1204 that.tm.last_resized = 0;
1207 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1210 if(NETDATA.chartDefaults.min_width !== null)
1211 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1213 that.tm.last_dom_created = new Date().getTime();
1219 * initialize state variables
1220 * destroy all (possibly) created state elements
1221 * create the basic DOM for a chart
1223 var init = function() {
1224 if(that.enabled === false) return;
1226 that.paused = false;
1227 that.selected = false;
1229 that.chart_created = false; // boolean - is the library.create() been called?
1230 that.updates_counter = 0; // numeric - the number of refreshes made so far
1231 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1232 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1235 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1236 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1237 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1239 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1240 last_updated: 0, // the timestamp the chart last updated with data
1241 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1243 // Used with NETDATA.globalPanAndZoom.seq
1244 last_visible_check: 0, // the time we last checked if it is visible
1245 last_resized: 0, // the time the chart was resized
1246 last_hidden: 0, // the time the chart was hidden
1247 last_unhidden: 0, // the time the chart was unhidden
1248 last_autorefreshed: 0 // the time the chart was last refreshed
1251 that.data = null; // the last data as downloaded from the netdata server
1252 that.data_url = 'invalid://'; // string - the last url used to update the chart
1253 that.data_points = 0; // number - the number of points returned from netdata
1254 that.data_after = 0; // milliseconds - the first timestamp of the data
1255 that.data_before = 0; // milliseconds - the last timestamp of the data
1256 that.data_update_every = 0; // milliseconds - the frequency to update the data
1258 that.tm.last_initialized = new Date().getTime();
1261 that.setMode('auto');
1264 var maxMessageFontSize = function() {
1265 // normally we want a font size, as tall as the element
1266 var h = that.element_message.clientHeight;
1268 // but give it some air, 20% let's say, or 5 pixels min
1269 var lost = Math.max(h * 0.2, 5);
1272 // center the text, vertically
1273 var paddingTop = (lost - 5) / 2;
1275 // but check the width too
1276 // it should fit 10 characters in it
1277 var w = that.element_message.clientWidth / 10;
1279 paddingTop += (h - w) / 2;
1283 // and don't make it too huge
1284 // 5% of the screen size is good
1285 if(h > screen.height / 20) {
1286 paddingTop += (h - (screen.height / 20)) / 2;
1287 h = screen.height / 20;
1291 that.element_message.style.fontSize = h.toString() + 'px';
1292 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1295 var showMessage = function(msg) {
1296 that.element_message.className = 'netdata-message';
1297 that.element_message.innerHTML = msg;
1298 that.element_message.style.fontSize = 'x-small';
1299 that.element_message.style.paddingTop = '0px';
1300 that.___messageHidden___ = undefined;
1303 var showMessageIcon = function(icon) {
1304 that.element_message.innerHTML = icon;
1305 that.element_message.className = 'netdata-message icon';
1306 maxMessageFontSize();
1307 that.___messageHidden___ = undefined;
1310 var hideMessage = function() {
1311 if(typeof that.___messageHidden___ === 'undefined') {
1312 that.___messageHidden___ = true;
1313 that.element_message.className = 'netdata-message hidden';
1317 var showRendering = function() {
1319 if(that.chart !== null) {
1320 if(that.chart.chart_type === 'line')
1321 icon = '<i class="fa fa-line-chart"></i>';
1323 icon = '<i class="fa fa-area-chart"></i>';
1326 icon = '<i class="fa fa-area-chart"></i>';
1328 showMessageIcon(icon + ' netdata');
1331 var showLoading = function() {
1332 if(that.chart_created === false) {
1333 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1339 var isHidden = function() {
1340 if(typeof that.___chartIsHidden___ !== 'undefined')
1346 // hide the chart, when it is not visible - called from isVisible()
1347 var hideChart = function() {
1348 // hide it, if it is not already hidden
1349 if(isHidden() === true) return;
1351 if(that.chart_created === true) {
1352 if(NETDATA.options.current.destroy_on_hide === true) {
1353 // we should destroy it
1358 that.element_chart.style.display = 'none';
1359 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1360 that.tm.last_hidden = new Date().getTime();
1363 // This works, but I not sure there are no corner cases somewhere
1364 // so it is commented - if the user has memory issues he can
1365 // set Destroy on Hide for all charts
1366 // that.data = null;
1370 that.___chartIsHidden___ = true;
1373 // unhide the chart, when it is visible - called from isVisible()
1374 var unhideChart = function() {
1375 if(isHidden() === false) return;
1377 that.___chartIsHidden___ = undefined;
1378 that.updates_since_last_unhide = 0;
1380 if(that.chart_created === false) {
1381 // we need to re-initialize it, to show our background
1382 // logo in bootstrap tabs, until the chart loads
1386 that.tm.last_unhidden = new Date().getTime();
1387 that.element_chart.style.display = '';
1388 if(that.element_legend !== null) that.element_legend.style.display = '';
1394 var canBeRendered = function() {
1395 if(isHidden() === true || that.isVisible(true) === false)
1401 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1402 var callChartLibraryUpdateSafely = function(data) {
1405 if(canBeRendered() === false)
1408 if(NETDATA.options.debug.chart_errors === true)
1409 status = that.library.update(that, data);
1412 status = that.library.update(that, data);
1419 if(status === false) {
1420 error('chart failed to be updated as ' + that.library_name);
1427 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1428 var callChartLibraryCreateSafely = function(data) {
1431 if(canBeRendered() === false)
1434 if(NETDATA.options.debug.chart_errors === true)
1435 status = that.library.create(that, data);
1438 status = that.library.create(that, data);
1445 if(status === false) {
1446 error('chart failed to be created as ' + that.library_name);
1450 that.chart_created = true;
1451 that.updates_since_last_creation = 0;
1455 // ----------------------------------------------------------------------------------------------------------------
1458 // resizeChart() - private
1459 // to be called just before the chart library to make sure that
1460 // a properly sized dom is available
1461 var resizeChart = function() {
1462 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1463 if(that.chart_created === false) return;
1465 if(that.needsRecreation()) {
1468 else if(typeof that.library.resize === 'function') {
1469 that.library.resize(that);
1471 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1472 $(that.element_legend_childs.nano).nanoScroller();
1474 maxMessageFontSize();
1477 that.tm.last_resized = new Date().getTime();
1481 // this is the actual chart resize algorithm
1483 // - resize the entire container
1484 // - update the internal states
1485 // - resize the chart as the div changes height
1486 // - update the scrollbar of the legend
1487 var resizeChartToHeight = function(h) {
1489 that.element.style.height = h;
1491 if(that.settings_id !== null)
1492 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1494 var now = new Date().getTime();
1495 NETDATA.options.last_page_scroll = now;
1496 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1499 that.tm.last_resized = 0;
1503 this.resizeHandler = function(e) {
1506 if(typeof this.event_resize === 'undefined'
1507 || this.event_resize.chart_original_w === 'undefined'
1508 || this.event_resize.chart_original_h === 'undefined')
1509 this.event_resize = {
1510 chart_original_w: this.element.clientWidth,
1511 chart_original_h: this.element.clientHeight,
1515 if(e.type === 'touchstart') {
1516 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1517 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1520 this.event_resize.mouse_start_x = e.clientX;
1521 this.event_resize.mouse_start_y = e.clientY;
1524 this.event_resize.chart_start_w = this.element.clientWidth;
1525 this.event_resize.chart_start_h = this.element.clientHeight;
1526 this.event_resize.chart_last_w = this.element.clientWidth;
1527 this.event_resize.chart_last_h = this.element.clientHeight;
1529 var now = new Date().getTime();
1530 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1531 // double click / double tap event
1533 // the optimal height of the chart
1534 // showing the entire legend
1535 var optimal = this.event_resize.chart_last_h
1536 + this.element_legend_childs.content.scrollHeight
1537 - this.element_legend_childs.content.clientHeight;
1539 // if we are not optimal, be optimal
1540 if(this.event_resize.chart_last_h != optimal)
1541 resizeChartToHeight(optimal.toString() + 'px');
1543 // else if we do not have the original height
1544 // reset to the original height
1545 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1546 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1549 this.event_resize.last = now;
1551 // process movement event
1552 document.onmousemove =
1553 document.ontouchmove =
1554 this.element_legend_childs.resize_handler.onmousemove =
1555 this.element_legend_childs.resize_handler.ontouchmove =
1560 case 'mousemove': y = e.clientY; break;
1561 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1565 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1567 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1568 resizeChartToHeight(newH.toString() + 'px');
1569 that.event_resize.chart_last_h = newH;
1574 // process end event
1575 document.onmouseup =
1576 document.ontouchend =
1577 this.element_legend_childs.resize_handler.onmouseup =
1578 this.element_legend_childs.resize_handler.ontouchend =
1580 // remove all the hooks
1581 document.onmouseup =
1582 document.onmousemove =
1583 document.ontouchmove =
1584 document.ontouchend =
1585 that.element_legend_childs.resize_handler.onmousemove =
1586 that.element_legend_childs.resize_handler.ontouchmove =
1587 that.element_legend_childs.resize_handler.onmouseout =
1588 that.element_legend_childs.resize_handler.onmouseup =
1589 that.element_legend_childs.resize_handler.ontouchend =
1592 // allow auto-refreshes
1593 NETDATA.options.auto_refresher_stop_until = 0;
1599 var noDataToShow = function() {
1600 showMessageIcon('<i class="fa fa-warning"></i> empty');
1601 that.legendUpdateDOM();
1602 that.tm.last_autorefreshed = new Date().getTime();
1603 // that.data_update_every = 30 * 1000;
1604 //that.element_chart.style.display = 'none';
1605 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1606 //that.___chartIsHidden___ = true;
1609 // ============================================================================================================
1612 this.error = function(msg) {
1616 this.setMode = function(m) {
1617 if(this.current !== null && this.current.name === m) return;
1620 this.current = this.auto;
1621 else if(m === 'pan')
1622 this.current = this.pan;
1623 else if(m === 'zoom')
1624 this.current = this.zoom;
1626 this.current = this.auto;
1628 this.current.force_update_at = 0;
1629 this.current.force_before_ms = null;
1630 this.current.force_after_ms = null;
1632 this.tm.last_mode_switch = new Date().getTime();
1635 // ----------------------------------------------------------------------------------------------------------------
1636 // global selection sync
1638 // prevent to global selection sync for some time
1639 this.globalSelectionSyncDelay = function(ms) {
1640 if(NETDATA.options.current.sync_selection === false)
1643 if(typeof ms === 'number')
1644 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1646 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1649 // can we globally apply selection sync?
1650 this.globalSelectionSyncAbility = function() {
1651 if(NETDATA.options.current.sync_selection === false)
1654 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1660 this.globalSelectionSyncIsMaster = function() {
1661 if(NETDATA.globalSelectionSync.state === this)
1667 // this chart is the master of the global selection sync
1668 this.globalSelectionSyncBeMaster = function() {
1670 if(this.globalSelectionSyncIsMaster()) {
1671 if(this.debug === true)
1672 this.log('sync: I am the master already.');
1677 if(NETDATA.globalSelectionSync.state) {
1678 if(this.debug === true)
1679 this.log('sync: I am not the sync master. Resetting global sync.');
1681 this.globalSelectionSyncStop();
1684 // become the master
1685 if(this.debug === true)
1686 this.log('sync: becoming sync master.');
1688 this.selected = true;
1689 NETDATA.globalSelectionSync.state = this;
1691 // find the all slaves
1692 var targets = NETDATA.options.targets;
1693 var len = targets.length;
1698 if(this.debug === true)
1699 st.log('sync: not adding me to sync');
1701 else if(st.globalSelectionSyncIsEligible()) {
1702 if(this.debug === true)
1703 st.log('sync: adding to sync as slave');
1705 st.globalSelectionSyncBeSlave();
1709 // this.globalSelectionSyncDelay(100);
1712 // can the chart participate to the global selection sync as a slave?
1713 this.globalSelectionSyncIsEligible = function() {
1714 if(this.enabled === true
1715 && this.library !== null
1716 && typeof this.library.setSelection === 'function'
1717 && this.isVisible() === true
1718 && this.chart_created === true)
1724 // this chart becomes a slave of the global selection sync
1725 this.globalSelectionSyncBeSlave = function() {
1726 if(NETDATA.globalSelectionSync.state !== this)
1727 NETDATA.globalSelectionSync.slaves.push(this);
1730 // sync all the visible charts to the given time
1731 // this is to be called from the chart libraries
1732 this.globalSelectionSync = function(t) {
1733 if(this.globalSelectionSyncAbility() === false) {
1734 if(this.debug === true)
1735 this.log('sync: cannot sync (yet?).');
1740 if(this.globalSelectionSyncIsMaster() === false) {
1741 if(this.debug === true)
1742 this.log('sync: trying to be sync master.');
1744 this.globalSelectionSyncBeMaster();
1746 if(this.globalSelectionSyncAbility() === false) {
1747 if(this.debug === true)
1748 this.log('sync: cannot sync (yet?).');
1754 NETDATA.globalSelectionSync.last_t = t;
1755 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1760 // stop syncing all charts to the given time
1761 this.globalSelectionSyncStop = function() {
1762 if(NETDATA.globalSelectionSync.slaves.length) {
1763 if(this.debug === true)
1764 this.log('sync: cleaning up...');
1766 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1768 if(that.debug === true)
1769 st.log('sync: not adding me to sync stop');
1772 if(that.debug === true)
1773 st.log('sync: removed slave from sync');
1775 st.clearSelection();
1779 NETDATA.globalSelectionSync.last_t = 0;
1780 NETDATA.globalSelectionSync.slaves = [];
1781 NETDATA.globalSelectionSync.state = null;
1784 this.clearSelection();
1787 this.setSelection = function(t) {
1788 if(typeof this.library.setSelection === 'function') {
1789 if(this.library.setSelection(this, t) === true)
1790 this.selected = true;
1792 this.selected = false;
1794 else this.selected = true;
1796 if(this.selected === true && this.debug === true)
1797 this.log('selection set to ' + t.toString());
1799 return this.selected;
1802 this.clearSelection = function() {
1803 if(this.selected === true) {
1804 if(typeof this.library.clearSelection === 'function') {
1805 if(this.library.clearSelection(this) === true)
1806 this.selected = false;
1808 this.selected = true;
1810 else this.selected = false;
1812 if(this.selected === false && this.debug === true)
1813 this.log('selection cleared');
1818 return this.selected;
1821 // find if a timestamp (ms) is shown in the current chart
1822 this.timeIsVisible = function(t) {
1823 if(t >= this.data_after && t <= this.data_before)
1828 this.calculateRowForTime = function(t) {
1829 if(this.timeIsVisible(t) === false) return -1;
1830 return Math.floor((t - this.data_after) / this.data_update_every);
1833 // ----------------------------------------------------------------------------------------------------------------
1836 this.log = function(msg) {
1837 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1840 this.pauseChart = function() {
1841 if(this.paused === false) {
1842 if(this.debug === true)
1843 this.log('pauseChart()');
1849 this.unpauseChart = function() {
1850 if(this.paused === true) {
1851 if(this.debug === true)
1852 this.log('unpauseChart()');
1854 this.paused = false;
1858 this.resetChart = function(dont_clear_master, dont_update) {
1859 if(this.debug === true)
1860 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1862 if(typeof dont_clear_master === 'undefined')
1863 dont_clear_master = false;
1865 if(typeof dont_update === 'undefined')
1866 dont_update = false;
1868 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1869 if(this.debug === true)
1870 this.log('resetChart() diverting to clearMaster().');
1871 // this will call us back with master === true
1872 NETDATA.globalPanAndZoom.clearMaster();
1876 this.clearSelection();
1878 this.tm.pan_and_zoom_seq = 0;
1880 this.setMode('auto');
1881 this.current.force_update_at = 0;
1882 this.current.force_before_ms = null;
1883 this.current.force_after_ms = null;
1884 this.tm.last_autorefreshed = 0;
1885 this.paused = false;
1886 this.selected = false;
1887 this.enabled = true;
1888 // this.debug = false;
1890 // do not update the chart here
1891 // or the chart will flip-flop when it is the master
1892 // of a selection sync and another chart becomes
1895 if(dont_update !== true && this.isVisible() === true) {
1900 this.updateChartPanOrZoom = function(after, before) {
1901 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1904 if(this.debug === true)
1907 if(before < after) {
1908 if(this.debug === true)
1909 this.log(logme + 'flipped parameters, rejecting it.');
1914 if(typeof this.fixed_min_duration === 'undefined')
1915 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1917 var min_duration = this.fixed_min_duration;
1918 var current_duration = Math.round(this.view_before - this.view_after);
1920 // round the numbers
1921 after = Math.round(after);
1922 before = Math.round(before);
1924 // align them to update_every
1925 // stretching them further away
1926 after -= after % this.data_update_every;
1927 before += this.data_update_every - (before % this.data_update_every);
1929 // the final wanted duration
1930 var wanted_duration = before - after;
1932 // to allow panning, accept just a point below our minimum
1933 if((current_duration - this.data_update_every) < min_duration)
1934 min_duration = current_duration - this.data_update_every;
1936 // we do it, but we adjust to minimum size and return false
1937 // when the wanted size is below the current and the minimum
1939 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1940 if(this.debug === true)
1941 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1943 min_duration = this.fixed_min_duration;
1945 var dt = (min_duration - wanted_duration) / 2;
1948 wanted_duration = before - after;
1952 var tolerance = this.data_update_every * 2;
1953 var movement = Math.abs(before - this.view_before);
1955 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1956 if(this.debug === true)
1957 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);
1961 if(this.current.name === 'auto') {
1962 this.log(logme + 'caller called me with mode: ' + this.current.name);
1963 this.setMode('pan');
1966 if(this.debug === true)
1967 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);
1969 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1970 this.current.force_after_ms = after;
1971 this.current.force_before_ms = before;
1972 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1976 this.legendFormatValue = function(value) {
1977 if(value === null || value === 'undefined') return '-';
1978 if(typeof value !== 'number') return value;
1980 var abs = Math.abs(value);
1981 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1982 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1983 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1984 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1985 return (Math.round(value * 10000) / 10000).toLocaleString();
1988 this.legendSetLabelValue = function(label, value) {
1989 var series = this.element_legend_childs.series[label];
1990 if(typeof series === 'undefined') return;
1991 if(series.value === null && series.user === null) return;
1993 // if the value has not changed, skip DOM update
1994 //if(series.last === value) return;
1997 if(typeof value === 'number') {
1998 var v = Math.abs(value);
1999 s = r = this.legendFormatValue(value);
2001 if(typeof series.last === 'number') {
2002 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2003 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2004 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2006 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2011 series.last = value;
2014 if(series.value !== null) series.value.innerHTML = s;
2015 if(series.user !== null) series.user.innerHTML = r;
2018 this.legendSetDate = function(ms) {
2019 if(typeof ms !== 'number') {
2020 this.legendShowUndefined();
2024 var d = new Date(ms);
2026 if(this.element_legend_childs.title_date)
2027 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2029 if(this.element_legend_childs.title_time)
2030 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2032 if(this.element_legend_childs.title_units)
2033 this.element_legend_childs.title_units.innerHTML = this.units;
2036 this.legendShowUndefined = function() {
2037 if(this.element_legend_childs.title_date)
2038 this.element_legend_childs.title_date.innerHTML = ' ';
2040 if(this.element_legend_childs.title_time)
2041 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2043 if(this.element_legend_childs.title_units)
2044 this.element_legend_childs.title_units.innerHTML = ' ';
2046 if(this.data && this.element_legend_childs.series !== null) {
2047 var labels = this.data.dimension_names;
2048 var i = labels.length;
2050 var label = labels[i];
2052 if(typeof label === 'undefined') continue;
2053 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2054 this.legendSetLabelValue(label, null);
2059 this.legendShowLatestValues = function() {
2060 if(this.chart === null) return;
2061 if(this.selected) return;
2063 if(this.data === null || this.element_legend_childs.series === null) {
2064 this.legendShowUndefined();
2068 var show_undefined = true;
2069 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2070 show_undefined = false;
2072 if(show_undefined) {
2073 this.legendShowUndefined();
2077 this.legendSetDate(this.view_before);
2079 var labels = this.data.dimension_names;
2080 var i = labels.length;
2082 var label = labels[i];
2084 if(typeof label === 'undefined') continue;
2085 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2088 this.legendSetLabelValue(label, null);
2090 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2094 this.legendReset = function() {
2095 this.legendShowLatestValues();
2098 // this should be called just ONCE per dimension per chart
2099 this._chartDimensionColor = function(label) {
2100 if(this.colors === null) this.chartColors();
2102 if(typeof this.colors_assigned[label] === 'undefined') {
2103 if(this.colors_available.length === 0) {
2104 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2105 this.colors_available.push(NETDATA.themes.current.colors[i]);
2108 this.colors_assigned[label] = this.colors_available.shift();
2110 if(this.debug === true)
2111 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2114 if(this.debug === true)
2115 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2118 this.colors.push(this.colors_assigned[label]);
2119 return this.colors_assigned[label];
2122 this.chartColors = function() {
2123 if(this.colors !== null) return this.colors;
2125 this.colors = new Array();
2126 this.colors_available = new Array();
2129 var c = $(this.element).data('colors');
2130 // this.log('read colors: ' + c);
2131 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2132 if(typeof c !== 'string') {
2133 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2140 for(i = 0, len = c.length; i < len ; i++) {
2142 this.colors_available.push(c[i]);
2143 // this.log('adding color: ' + c[i]);
2149 // push all the standard colors too
2150 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2151 this.colors_available.push(NETDATA.themes.current.colors[i]);
2156 this.legendUpdateDOM = function() {
2159 // check that the legend DOM is up to date for the downloaded dimensions
2160 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2161 // this.log('the legend does not have any series - requesting legend update');
2164 else if(this.data === null) {
2165 // this.log('the chart does not have any data - requesting legend update');
2168 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2172 var labels = this.data.dimension_names.toString();
2173 if(labels !== this.element_legend_childs.series.labels_key) {
2176 if(this.debug === true)
2177 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2181 if(needed === false) {
2182 // make sure colors available
2185 // do we have to update the current values?
2186 // we do this, only when the visible chart is current
2187 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2188 if(this.debug === true)
2189 this.log('chart is in latest position... updating values on legend...');
2191 //var labels = this.data.dimension_names;
2192 //var i = labels.length;
2194 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2198 if(this.colors === null) {
2199 // this is the first time we update the chart
2200 // let's assign colors to all dimensions
2201 if(this.library.track_colors() === true)
2202 for(var dim in this.chart.dimensions)
2203 this._chartDimensionColor(this.chart.dimensions[dim].name);
2205 // we will re-generate the colors for the chart
2206 // based on the selected dimensions
2209 if(this.debug === true)
2210 this.log('updating Legend DOM');
2212 // mark all dimensions as invalid
2213 this.dimensions_visibility.invalidateAll();
2215 var genLabel = function(state, parent, dim, name, count) {
2216 var color = state._chartDimensionColor(name);
2218 var user_element = null;
2219 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2220 if(user_id !== null) {
2221 user_element = document.getElementById(user_id) || null;
2222 if(user_element === null)
2223 state.log('Cannot find element with id: ' + user_id);
2226 state.element_legend_childs.series[name] = {
2227 name: document.createElement('span'),
2228 value: document.createElement('span'),
2233 var label = state.element_legend_childs.series[name];
2235 // create the dimension visibility tracking for this label
2236 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2238 var rgb = NETDATA.colorHex2Rgb(color);
2239 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2240 + state.chart.chart_type
2241 + '" style="background-color: '
2242 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2243 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2245 var text = document.createTextNode(' ' + name);
2246 label.name.appendChild(text);
2249 parent.appendChild(document.createElement('br'));
2251 parent.appendChild(label.name);
2252 parent.appendChild(label.value);
2255 var content = document.createElement('div');
2257 if(this.hasLegend()) {
2258 this.element_legend_childs = {
2260 resize_handler: document.createElement('div'),
2261 toolbox: document.createElement('div'),
2262 toolbox_left: document.createElement('div'),
2263 toolbox_right: document.createElement('div'),
2264 toolbox_reset: document.createElement('div'),
2265 toolbox_zoomin: document.createElement('div'),
2266 toolbox_zoomout: document.createElement('div'),
2267 toolbox_volume: document.createElement('div'),
2268 title_date: document.createElement('span'),
2269 title_time: document.createElement('span'),
2270 title_units: document.createElement('span'),
2271 nano: document.createElement('div'),
2273 paneClass: 'netdata-legend-series-pane',
2274 sliderClass: 'netdata-legend-series-slider',
2275 contentClass: 'netdata-legend-series-content',
2276 enabledClass: '__enabled',
2277 flashedClass: '__flashed',
2278 activeClass: '__active',
2280 alwaysVisible: true,
2286 this.element_legend.innerHTML = '';
2288 if(this.library.toolboxPanAndZoom !== null) {
2290 function get_pan_and_zoom_step(event) {
2292 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2294 else if (event.shiftKey)
2295 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2297 else if (event.altKey)
2298 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2301 return NETDATA.options.current.pan_and_zoom_factor;
2304 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2305 this.element.appendChild(this.element_legend_childs.toolbox);
2307 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2308 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2309 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2310 this.element_legend_childs.toolbox_left.onclick = function(e) {
2313 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2314 var before = that.view_before - step;
2315 var after = that.view_after - step;
2316 if(after >= that.netdata_first)
2317 that.library.toolboxPanAndZoom(that, after, before);
2319 if(NETDATA.options.current.show_help === true)
2320 $(this.element_legend_childs.toolbox_left).popover({
2325 placement: 'bottom',
2326 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2328 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>'
2332 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2333 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2334 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2335 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2337 NETDATA.resetAllCharts(that);
2339 if(NETDATA.options.current.show_help === true)
2340 $(this.element_legend_childs.toolbox_reset).popover({
2345 placement: 'bottom',
2346 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2347 title: 'Chart Reset',
2348 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>'
2351 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2352 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2353 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2354 this.element_legend_childs.toolbox_right.onclick = function(e) {
2356 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2357 var before = that.view_before + step;
2358 var after = that.view_after + step;
2359 if(before <= that.netdata_last)
2360 that.library.toolboxPanAndZoom(that, after, before);
2362 if(NETDATA.options.current.show_help === true)
2363 $(this.element_legend_childs.toolbox_right).popover({
2368 placement: 'bottom',
2369 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2371 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>'
2375 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2376 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2377 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2378 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2380 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2381 var before = that.view_before - dt;
2382 var after = that.view_after + dt;
2383 that.library.toolboxPanAndZoom(that, after, before);
2385 if(NETDATA.options.current.show_help === true)
2386 $(this.element_legend_childs.toolbox_zoomin).popover({
2391 placement: 'bottom',
2392 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2393 title: 'Chart Zoom In',
2394 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>'
2397 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2398 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2399 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2400 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2402 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);
2403 var before = that.view_before + dt;
2404 var after = that.view_after - dt;
2406 that.library.toolboxPanAndZoom(that, after, before);
2408 if(NETDATA.options.current.show_help === true)
2409 $(this.element_legend_childs.toolbox_zoomout).popover({
2414 placement: 'bottom',
2415 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2416 title: 'Chart Zoom Out',
2417 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>'
2420 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2421 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2422 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2423 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2424 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2425 //e.preventDefault();
2426 //alert('clicked toolbox_volume on ' + that.id);
2430 this.element_legend_childs.toolbox = null;
2431 this.element_legend_childs.toolbox_left = null;
2432 this.element_legend_childs.toolbox_reset = null;
2433 this.element_legend_childs.toolbox_right = null;
2434 this.element_legend_childs.toolbox_zoomin = null;
2435 this.element_legend_childs.toolbox_zoomout = null;
2436 this.element_legend_childs.toolbox_volume = null;
2439 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2440 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2441 this.element.appendChild(this.element_legend_childs.resize_handler);
2442 if(NETDATA.options.current.show_help === true)
2443 $(this.element_legend_childs.resize_handler).popover({
2448 placement: 'bottom',
2449 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2450 title: 'Chart Resize',
2451 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>'
2455 this.element_legend_childs.resize_handler.onmousedown =
2457 that.resizeHandler(e);
2461 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2462 that.resizeHandler(e);
2465 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2466 this.element_legend.appendChild(this.element_legend_childs.title_date);
2468 this.element_legend.appendChild(document.createElement('br'));
2470 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2471 this.element_legend.appendChild(this.element_legend_childs.title_time);
2473 this.element_legend.appendChild(document.createElement('br'));
2475 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2476 this.element_legend.appendChild(this.element_legend_childs.title_units);
2478 this.element_legend.appendChild(document.createElement('br'));
2480 this.element_legend_childs.nano.className = 'netdata-legend-series';
2481 this.element_legend.appendChild(this.element_legend_childs.nano);
2483 content.className = 'netdata-legend-series-content';
2484 this.element_legend_childs.nano.appendChild(content);
2486 if(NETDATA.options.current.show_help === true)
2487 $(content).popover({
2492 placement: 'bottom',
2493 title: 'Chart Legend',
2494 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2495 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>'
2499 this.element_legend_childs = {
2501 resize_handler: null,
2504 toolbox_right: null,
2505 toolbox_reset: null,
2506 toolbox_zoomin: null,
2507 toolbox_zoomout: null,
2508 toolbox_volume: null,
2519 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2520 if(this.debug === true)
2521 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2523 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2524 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2528 var tmp = new Array();
2529 for(var dim in this.chart.dimensions) {
2530 tmp.push(this.chart.dimensions[dim].name);
2531 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2533 this.element_legend_childs.series.labels_key = tmp.toString();
2534 if(this.debug === true)
2535 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2538 // create a hidden div to be used for hidding
2539 // the original legend of the chart library
2540 var el = document.createElement('div');
2541 if(this.element_legend !== null)
2542 this.element_legend.appendChild(el);
2543 el.style.display = 'none';
2545 this.element_legend_childs.hidden = document.createElement('div');
2546 el.appendChild(this.element_legend_childs.hidden);
2548 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2549 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2551 this.legendShowLatestValues();
2554 this.hasLegend = function() {
2555 if(typeof this.___hasLegendCache___ !== 'undefined')
2556 return this.___hasLegendCache___;
2559 if(this.library && this.library.legend(this) === 'right-side') {
2560 var legend = $(this.element).data('legend') || 'yes';
2561 if(legend === 'yes') leg = true;
2564 this.___hasLegendCache___ = leg;
2568 this.legendWidth = function() {
2569 return (this.hasLegend())?140:0;
2572 this.legendHeight = function() {
2573 return $(this.element).height();
2576 this.chartWidth = function() {
2577 return $(this.element).width() - this.legendWidth();
2580 this.chartHeight = function() {
2581 return $(this.element).height();
2584 this.chartPixelsPerPoint = function() {
2585 // force an options provided detail
2586 var px = this.pixels_per_point;
2588 if(this.library && px < this.library.pixels_per_point(this))
2589 px = this.library.pixels_per_point(this);
2591 if(px < NETDATA.options.current.pixels_per_point)
2592 px = NETDATA.options.current.pixels_per_point;
2597 this.needsRecreation = function() {
2599 this.chart_created === true
2601 && this.library.autoresize() === false
2602 && this.tm.last_resized < NETDATA.options.last_resized
2606 this.chartURL = function() {
2607 var after, before, points_multiplier = 1;
2608 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2609 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2611 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2612 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2613 this.view_after = after * 1000;
2614 this.view_before = before * 1000;
2616 this.requested_padding = null;
2617 points_multiplier = 1;
2619 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2620 this.tm.pan_and_zoom_seq = 0;
2622 before = Math.round(this.current.force_before_ms / 1000);
2623 after = Math.round(this.current.force_after_ms / 1000);
2624 this.view_after = after * 1000;
2625 this.view_before = before * 1000;
2627 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2628 this.requested_padding = Math.round((before - after) / 2);
2629 after -= this.requested_padding;
2630 before += this.requested_padding;
2631 this.requested_padding *= 1000;
2632 points_multiplier = 2;
2635 this.current.force_before_ms = null;
2636 this.current.force_after_ms = null;
2639 this.tm.pan_and_zoom_seq = 0;
2641 before = this.before;
2643 this.view_after = after * 1000;
2644 this.view_before = before * 1000;
2646 this.requested_padding = null;
2647 points_multiplier = 1;
2650 this.requested_after = after * 1000;
2651 this.requested_before = before * 1000;
2653 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2655 // build the data URL
2656 this.data_url = this.host + this.chart.data_url;
2657 this.data_url += "&format=" + this.library.format();
2658 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2659 this.data_url += "&group=" + this.method;
2660 this.data_url += "&options=" + this.library.options(this);
2661 this.data_url += '|jsonwrap';
2663 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2664 this.data_url += '|nonzero';
2666 if(this.append_options !== null)
2667 this.data_url += '|' + this.append_options.toString();
2670 this.data_url += "&after=" + after.toString();
2673 this.data_url += "&before=" + before.toString();
2676 this.data_url += "&dimensions=" + this.dimensions;
2678 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2679 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2682 this.redrawChart = function() {
2683 if(this.data !== null)
2684 this.updateChartWithData(this.data);
2687 this.updateChartWithData = function(data) {
2688 if(this.debug === true)
2689 this.log('updateChartWithData() called.');
2691 // this may force the chart to be re-created
2695 this.updates_counter++;
2696 this.updates_since_last_unhide++;
2697 this.updates_since_last_creation++;
2699 var started = new Date().getTime();
2701 // if the result is JSON, find the latest update-every
2702 this.data_update_every = data.view_update_every * 1000;
2703 this.data_after = data.after * 1000;
2704 this.data_before = data.before * 1000;
2705 this.netdata_first = data.first_entry * 1000;
2706 this.netdata_last = data.last_entry * 1000;
2707 this.data_points = data.points;
2710 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2711 if(this.view_after < this.data_after) {
2712 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2713 this.view_after = this.data_after;
2716 if(this.view_before > this.data_before) {
2717 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2718 this.view_before = this.data_before;
2722 this.view_after = this.data_after;
2723 this.view_before = this.data_before;
2726 if(this.debug === true) {
2727 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2729 if(this.current.force_after_ms)
2730 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2732 this.log('STATUS: forced : unset');
2734 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2735 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2736 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2737 this.log('STATUS: points : ' + (this.data_points).toString());
2740 if(this.data_points === 0) {
2745 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2746 if(this.debug === true)
2747 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2749 this.chart_created = false;
2752 // check and update the legend
2753 this.legendUpdateDOM();
2755 if(this.chart_created === true
2756 && typeof this.library.update === 'function') {
2758 if(this.debug === true)
2759 this.log('updating chart...');
2761 if(callChartLibraryUpdateSafely(data) === false)
2765 if(this.debug === true)
2766 this.log('creating chart...');
2768 if(callChartLibraryCreateSafely(data) === false)
2772 this.legendShowLatestValues();
2773 if(this.selected === true)
2774 NETDATA.globalSelectionSync.stop();
2776 // update the performance counters
2777 var now = new Date().getTime();
2778 this.tm.last_updated = now;
2780 // don't update last_autorefreshed if this chart is
2781 // forced to be updated with global PanAndZoom
2782 if(NETDATA.globalPanAndZoom.isActive())
2783 this.tm.last_autorefreshed = 0;
2785 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2786 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2788 this.tm.last_autorefreshed = now;
2791 this.refresh_dt_ms = now - started;
2792 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2794 if(this.refresh_dt_element !== null)
2795 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2798 this.updateChart = function(callback) {
2799 if(this.debug === true)
2800 this.log('updateChart() called.');
2802 if(this._updating === true) {
2803 if(this.debug === true)
2804 this.log('I am already updating...');
2806 if(typeof callback === 'function') callback();
2810 // due to late initialization of charts and libraries
2811 // we need to check this too
2812 if(this.enabled === false) {
2813 if(this.debug === true)
2814 this.log('I am not enabled');
2816 if(typeof callback === 'function') callback();
2820 if(canBeRendered() === false) {
2821 if(typeof callback === 'function') callback();
2825 if(this.chart === null) {
2826 this.getChart(function() { that.updateChart(callback); });
2830 if(this.library.initialized === false) {
2831 if(this.library.enabled === true) {
2832 this.library.initialize(function() { that.updateChart(callback); });
2836 error('chart library "' + this.library_name + '" is not available.');
2837 if(typeof callback === 'function') callback();
2842 this.clearSelection();
2845 if(this.debug === true)
2846 this.log('updating from ' + this.data_url);
2848 NETDATA.statistics.refreshes_total++;
2849 NETDATA.statistics.refreshes_active++;
2851 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2852 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2854 this._updating = true;
2856 this.xhr = $.ajax( {
2860 xhrFields: { withCredentials: true } // required for the cookie
2862 .done(function(data) {
2863 that.xhr = undefined;
2865 if(that.debug === true)
2866 that.log('data received. updating chart.');
2868 that.updateChartWithData(data);
2870 .fail(function(msg) {
2871 that.xhr = undefined;
2873 if(msg.statusText !== 'abort')
2874 error('data download failed for url: ' + that.data_url);
2876 .always(function() {
2877 that.xhr = undefined;
2879 NETDATA.statistics.refreshes_active--;
2880 that._updating = false;
2881 if(typeof callback === 'function') callback();
2887 this.isVisible = function(nocache) {
2888 if(typeof nocache === 'undefined')
2891 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2893 // caching - we do not evaluate the charts visibility
2894 // if the page has not been scrolled since the last check
2895 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2896 return this.___isVisible___;
2898 this.tm.last_visible_check = new Date().getTime();
2900 var wh = window.innerHeight;
2901 var x = this.element.getBoundingClientRect();
2905 if(x.width === 0 || x.height === 0) {
2907 this.___isVisible___ = false;
2908 return this.___isVisible___;
2911 if(x.top < 0 && -x.top > x.height) {
2912 // the chart is entirely above
2913 ret = -x.top - x.height;
2915 else if(x.top > wh) {
2916 // the chart is entirely below
2920 if(ret > tolerance) {
2921 // the chart is too far
2924 this.___isVisible___ = false;
2925 return this.___isVisible___;
2928 // the chart is inside or very close
2931 this.___isVisible___ = true;
2932 return this.___isVisible___;
2936 this.isAutoRefreshable = function() {
2937 return (this.current.autorefresh);
2940 this.canBeAutoRefreshed = function() {
2941 var now = new Date().getTime();
2943 if(this.running === true) {
2944 if(this.debug === true)
2945 this.log('I am already running');
2950 if(this.enabled === false) {
2951 if(this.debug === true)
2952 this.log('I am not enabled');
2957 if(this.library === null || this.library.enabled === false) {
2958 error('charting library "' + this.library_name + '" is not available');
2959 if(this.debug === true)
2960 this.log('My chart library ' + this.library_name + ' is not available');
2965 if(this.isVisible() === false) {
2966 if(NETDATA.options.debug.visibility === true || this.debug === true)
2967 this.log('I am not visible');
2972 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2973 if(this.debug === true)
2974 this.log('timed force update detected - allowing this update');
2976 this.current.force_update_at = 0;
2980 if(this.isAutoRefreshable() === true) {
2981 // allow the first update, even if the page is not visible
2982 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2983 if(NETDATA.options.debug.focus === true || this.debug === true)
2984 this.log('canBeAutoRefreshed(): page does not have focus');
2989 if(this.needsRecreation() === true) {
2990 if(this.debug === true)
2991 this.log('canBeAutoRefreshed(): needs re-creation.');
2996 // options valid only for autoRefresh()
2997 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2998 if(NETDATA.globalPanAndZoom.isActive()) {
2999 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3000 if(this.debug === true)
3001 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3006 if(this.debug === true)
3007 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3013 if(this.selected === true) {
3014 if(this.debug === true)
3015 this.log('canBeAutoRefreshed(): I have a selection in place.');
3020 if(this.paused === true) {
3021 if(this.debug === true)
3022 this.log('canBeAutoRefreshed(): I am paused.');
3027 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3028 if(this.debug === true)
3029 this.log('canBeAutoRefreshed(): It is time to update me.');
3039 this.autoRefresh = function(callback) {
3040 if(this.canBeAutoRefreshed() === true && this.running === false) {
3043 state.running = true;
3044 state.updateChart(function() {
3045 state.running = false;
3047 if(typeof callback !== 'undefined')
3052 if(typeof callback !== 'undefined')
3057 this._defaultsFromDownloadedChart = function(chart) {
3059 this.chart_url = chart.url;
3060 this.data_update_every = chart.update_every * 1000;
3061 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3062 this.tm.last_info_downloaded = new Date().getTime();
3064 if(this.title === null)
3065 this.title = chart.title;
3067 if(this.units === null)
3068 this.units = chart.units;
3071 // fetch the chart description from the netdata server
3072 this.getChart = function(callback) {
3073 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3075 this._defaultsFromDownloadedChart(this.chart);
3076 if(typeof callback === 'function') callback();
3079 this.chart_url = "/api/v1/chart?chart=" + this.id;
3081 if(this.debug === true)
3082 this.log('downloading ' + this.chart_url);
3085 url: this.host + this.chart_url,
3088 xhrFields: { withCredentials: true } // required for the cookie
3090 .done(function(chart) {
3091 chart.url = that.chart_url;
3092 that._defaultsFromDownloadedChart(chart);
3093 NETDATA.chartRegistry.add(that.host, that.id, chart);
3096 NETDATA.error(404, that.chart_url);
3097 error('chart not found on url "' + that.chart_url + '"');
3099 .always(function() {
3100 if(typeof callback === 'function') callback();
3105 // ============================================================================================================
3111 NETDATA.resetAllCharts = function(state) {
3112 // first clear the global selection sync
3113 // to make sure no chart is in selected state
3114 state.globalSelectionSyncStop();
3116 // there are 2 possibilities here
3117 // a. state is the global Pan and Zoom master
3118 // b. state is not the global Pan and Zoom master
3120 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3123 // clear the global Pan and Zoom
3124 // this will also refresh the master
3125 // and unblock any charts currently mirroring the master
3126 NETDATA.globalPanAndZoom.clearMaster();
3128 // if we were not the master, reset our status too
3129 // this is required because most probably the mouse
3130 // is over this chart, blocking it from auto-refreshing
3131 if(master === false && (state.paused === true || state.selected === true))
3135 // get or create a chart state, given a DOM element
3136 NETDATA.chartState = function(element) {
3137 var state = $(element).data('netdata-state-object') || null;
3138 if(state === null) {
3139 state = new chartState(element);
3140 $(element).data('netdata-state-object', state);
3145 // ----------------------------------------------------------------------------------------------------------------
3146 // Library functions
3148 // Load a script without jquery
3149 // This is used to load jquery - after it is loaded, we use jquery
3150 NETDATA._loadjQuery = function(callback) {
3151 if(typeof jQuery === 'undefined') {
3152 if(NETDATA.options.debug.main_loop === true)
3153 console.log('loading ' + NETDATA.jQuery);
3155 var script = document.createElement('script');
3156 script.type = 'text/javascript';
3157 script.async = true;
3158 script.src = NETDATA.jQuery;
3160 // script.onabort = onError;
3161 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3162 if(typeof callback === "function")
3163 script.onload = callback;
3165 var s = document.getElementsByTagName('script')[0];
3166 s.parentNode.insertBefore(script, s);
3168 else if(typeof callback === "function")
3172 NETDATA._loadCSS = function(filename) {
3173 // don't use jQuery here
3174 // styles are loaded before jQuery
3175 // to eliminate showing an unstyled page to the user
3177 var fileref = document.createElement("link");
3178 fileref.setAttribute("rel", "stylesheet");
3179 fileref.setAttribute("type", "text/css");
3180 fileref.setAttribute("href", filename);
3182 if (typeof fileref !== 'undefined')
3183 document.getElementsByTagName("head")[0].appendChild(fileref);
3186 NETDATA.colorHex2Rgb = function(hex) {
3187 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3188 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3189 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3190 return r + r + g + g + b + b;
3193 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3195 r: parseInt(result[1], 16),
3196 g: parseInt(result[2], 16),
3197 b: parseInt(result[3], 16)
3201 NETDATA.colorLuminance = function(hex, lum) {
3202 // validate hex string
3203 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3205 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3209 // convert to decimal and change luminosity
3210 var rgb = "#", c, i;
3211 for (i = 0; i < 3; i++) {
3212 c = parseInt(hex.substr(i*2,2), 16);
3213 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3214 rgb += ("00"+c).substr(c.length);
3220 NETDATA.guid = function() {
3222 return Math.floor((1 + Math.random()) * 0x10000)
3227 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3230 NETDATA.zeropad = function(x) {
3231 if(x > -10 && x < 10) return '0' + x.toString();
3232 else return x.toString();
3235 // user function to signal us the DOM has been
3237 NETDATA.updatedDom = function() {
3238 NETDATA.options.updated_dom = true;
3241 NETDATA.ready = function(callback) {
3242 NETDATA.options.pauseCallback = callback;
3245 NETDATA.pause = function(callback) {
3246 if(NETDATA.options.pause === true)
3249 NETDATA.options.pauseCallback = callback;
3252 NETDATA.unpause = function() {
3253 NETDATA.options.pauseCallback = null;
3254 NETDATA.options.updated_dom = true;
3255 NETDATA.options.pause = false;
3258 // ----------------------------------------------------------------------------------------------------------------
3260 // this is purely sequencial charts refresher
3261 // it is meant to be autonomous
3262 NETDATA.chartRefresherNoParallel = function(index) {
3263 if(NETDATA.options.debug.mail_loop === true)
3264 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3266 if(NETDATA.options.updated_dom === true) {
3267 // the dom has been updated
3268 // get the dom parts again
3269 NETDATA.parseDom(NETDATA.chartRefresher);
3272 if(index >= NETDATA.options.targets.length) {
3273 if(NETDATA.options.debug.main_loop === true)
3274 console.log('waiting to restart main loop...');
3276 NETDATA.options.auto_refresher_fast_weight = 0;
3278 setTimeout(function() {
3279 NETDATA.chartRefresher();
3280 }, NETDATA.options.current.idle_between_loops);
3283 var state = NETDATA.options.targets[index];
3285 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3286 if(NETDATA.options.debug.main_loop === true)
3287 console.log('fast rendering...');
3289 state.autoRefresh(function() {
3290 NETDATA.chartRefresherNoParallel(++index);
3294 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3295 NETDATA.options.auto_refresher_fast_weight = 0;
3297 setTimeout(function() {
3298 state.autoRefresh(function() {
3299 NETDATA.chartRefresherNoParallel(++index);
3301 }, NETDATA.options.current.idle_between_charts);
3306 // this is part of the parallel refresher
3307 // its cause is to refresh sequencially all the charts
3308 // that depend on chart library initialization
3309 // it will call the parallel refresher back
3310 // as soon as it sees a chart that its chart library
3312 NETDATA.chartRefresher_uninitialized = function() {
3313 if(NETDATA.options.updated_dom === true) {
3314 // the dom has been updated
3315 // get the dom parts again
3316 NETDATA.parseDom(NETDATA.chartRefresher);
3320 if(NETDATA.options.sequencial.length === 0)
3321 NETDATA.chartRefresher();
3323 var state = NETDATA.options.sequencial.pop();
3324 if(state.library.initialized === true)
3325 NETDATA.chartRefresher();
3327 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3331 NETDATA.chartRefresherWaitTime = function() {
3332 return NETDATA.options.current.idle_parallel_loops;
3335 // the default refresher
3336 // it will create 2 sets of charts:
3337 // - the ones that can be refreshed in parallel
3338 // - the ones that depend on something else
3339 // the first set will be executed in parallel
3340 // the second will be given to NETDATA.chartRefresher_uninitialized()
3341 NETDATA.chartRefresher = function() {
3342 // console.log('auto-refresher...');
3344 if(NETDATA.options.pause === true) {
3345 // console.log('auto-refresher is paused');
3346 setTimeout(NETDATA.chartRefresher,
3347 NETDATA.chartRefresherWaitTime());
3351 if(typeof NETDATA.options.pauseCallback === 'function') {
3352 // console.log('auto-refresher is calling pauseCallback');
3353 NETDATA.options.pause = true;
3354 NETDATA.options.pauseCallback();
3355 NETDATA.chartRefresher();
3359 if(NETDATA.options.current.parallel_refresher === false) {
3360 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3361 NETDATA.chartRefresherNoParallel(0);
3365 if(NETDATA.options.updated_dom === true) {
3366 // the dom has been updated
3367 // get the dom parts again
3368 // console.log('auto-refresher is calling parseDom()');
3369 NETDATA.parseDom(NETDATA.chartRefresher);
3373 var parallel = new Array();
3374 var targets = NETDATA.options.targets;
3375 var len = targets.length;
3378 state = targets[len];
3379 if(state.isVisible() === false || state.running === true)
3382 if(state.library.initialized === false) {
3383 if(state.library.enabled === true) {
3384 state.library.initialize(NETDATA.chartRefresher);
3388 state.error('chart library "' + state.library_name + '" is not enabled.');
3392 parallel.unshift(state);
3395 if(parallel.length > 0) {
3396 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3397 // this will execute the jobs in parallel
3398 $(parallel).each(function() {
3403 // console.log('auto-refresher nothing to do');
3406 // run the next refresh iteration
3407 setTimeout(NETDATA.chartRefresher,
3408 NETDATA.chartRefresherWaitTime());
3411 NETDATA.parseDom = function(callback) {
3412 NETDATA.options.last_page_scroll = new Date().getTime();
3413 NETDATA.options.updated_dom = false;
3415 var targets = $('div[data-netdata]'); //.filter(':visible');
3417 if(NETDATA.options.debug.main_loop === true)
3418 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3420 NETDATA.options.targets = new Array();
3421 var len = targets.length;
3423 // the initialization will take care of sizing
3424 // and the "loading..." message
3425 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3428 if(typeof callback === 'function') callback();
3431 // this is the main function - where everything starts
3432 NETDATA.start = function() {
3433 // this should be called only once
3435 NETDATA.options.page_is_visible = true;
3437 $(window).blur(function() {
3438 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3439 NETDATA.options.page_is_visible = false;
3440 if(NETDATA.options.debug.focus === true)
3441 console.log('Lost Focus!');
3445 $(window).focus(function() {
3446 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3447 NETDATA.options.page_is_visible = true;
3448 if(NETDATA.options.debug.focus === true)
3449 console.log('Focus restored!');
3453 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3454 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3455 NETDATA.options.page_is_visible = false;
3456 if(NETDATA.options.debug.focus === true)
3457 console.log('Document has no focus!');
3461 // bootstrap tab switching
3462 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3464 // bootstrap modal switching
3465 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3466 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3468 // bootstrap collapse switching
3469 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3470 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3472 NETDATA.parseDom(NETDATA.chartRefresher);
3474 // Alarms initialization
3475 setTimeout(NETDATA.alarms.init, 1000);
3477 // Registry initialization
3478 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3481 // ----------------------------------------------------------------------------------------------------------------
3484 NETDATA.peityInitialize = function(callback) {
3485 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3487 url: NETDATA.peity_js,
3490 xhrFields: { withCredentials: true } // required for the cookie
3493 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3496 NETDATA.chartLibraries.peity.enabled = false;
3497 NETDATA.error(100, NETDATA.peity_js);
3499 .always(function() {
3500 if(typeof callback === "function")
3505 NETDATA.chartLibraries.peity.enabled = false;
3506 if(typeof callback === "function")
3511 NETDATA.peityChartUpdate = function(state, data) {
3512 state.peity_instance.innerHTML = data.result;
3514 if(state.peity_options.stroke !== state.chartColors()[0]) {
3515 state.peity_options.stroke = state.chartColors()[0];
3516 if(state.chart.chart_type === 'line')
3517 state.peity_options.fill = NETDATA.themes.current.background;
3519 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3522 $(state.peity_instance).peity('line', state.peity_options);
3526 NETDATA.peityChartCreate = function(state, data) {
3527 state.peity_instance = document.createElement('div');
3528 state.element_chart.appendChild(state.peity_instance);
3530 var self = $(state.element);
3531 state.peity_options = {
3532 stroke: NETDATA.themes.current.foreground,
3533 strokeWidth: self.data('peity-strokewidth') || 1,
3534 width: state.chartWidth(),
3535 height: state.chartHeight(),
3536 fill: NETDATA.themes.current.foreground
3539 NETDATA.peityChartUpdate(state, data);
3543 // ----------------------------------------------------------------------------------------------------------------
3546 NETDATA.sparklineInitialize = function(callback) {
3547 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3549 url: NETDATA.sparkline_js,
3552 xhrFields: { withCredentials: true } // required for the cookie
3555 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3558 NETDATA.chartLibraries.sparkline.enabled = false;
3559 NETDATA.error(100, NETDATA.sparkline_js);
3561 .always(function() {
3562 if(typeof callback === "function")
3567 NETDATA.chartLibraries.sparkline.enabled = false;
3568 if(typeof callback === "function")
3573 NETDATA.sparklineChartUpdate = function(state, data) {
3574 state.sparkline_options.width = state.chartWidth();
3575 state.sparkline_options.height = state.chartHeight();
3577 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3581 NETDATA.sparklineChartCreate = function(state, data) {
3582 var self = $(state.element);
3583 var type = self.data('sparkline-type') || 'line';
3584 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3585 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3586 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3587 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3588 var composite = self.data('sparkline-composite') || undefined;
3589 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3590 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3591 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3592 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3593 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3594 var spotColor = self.data('sparkline-spotcolor') || undefined;
3595 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3596 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3597 var spotRadius = self.data('sparkline-spotradius') || undefined;
3598 var valueSpots = self.data('sparkline-valuespots') || undefined;
3599 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3600 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3601 var lineWidth = self.data('sparkline-linewidth') || undefined;
3602 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3603 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3604 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3605 var xvalues = self.data('sparkline-xvalues') || undefined;
3606 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3607 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3608 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3609 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3610 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3611 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3612 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3613 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3614 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3615 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3616 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3617 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3618 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3619 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3620 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3621 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3622 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3623 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3624 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3625 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3626 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3627 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3629 if(spotColor === 'disable') spotColor='';
3630 if(minSpotColor === 'disable') minSpotColor='';
3631 if(maxSpotColor === 'disable') maxSpotColor='';
3633 state.sparkline_options = {
3635 lineColor: lineColor,
3636 fillColor: fillColor,
3637 chartRangeMin: chartRangeMin,
3638 chartRangeMax: chartRangeMax,
3639 composite: composite,
3640 enableTagOptions: enableTagOptions,
3641 tagOptionPrefix: tagOptionPrefix,
3642 tagValuesAttribute: tagValuesAttribute,
3643 disableHiddenCheck: disableHiddenCheck,
3644 defaultPixelsPerValue: defaultPixelsPerValue,
3645 spotColor: spotColor,
3646 minSpotColor: minSpotColor,
3647 maxSpotColor: maxSpotColor,
3648 spotRadius: spotRadius,
3649 valueSpots: valueSpots,
3650 highlightSpotColor: highlightSpotColor,
3651 highlightLineColor: highlightLineColor,
3652 lineWidth: lineWidth,
3653 normalRangeMin: normalRangeMin,
3654 normalRangeMax: normalRangeMax,
3655 drawNormalOnTop: drawNormalOnTop,
3657 chartRangeClip: chartRangeClip,
3658 chartRangeMinX: chartRangeMinX,
3659 chartRangeMaxX: chartRangeMaxX,
3660 disableInteraction: disableInteraction,
3661 disableTooltips: disableTooltips,
3662 disableHighlight: disableHighlight,
3663 highlightLighten: highlightLighten,
3664 highlightColor: highlightColor,
3665 tooltipContainer: tooltipContainer,
3666 tooltipClassname: tooltipClassname,
3667 tooltipChartTitle: state.title,
3668 tooltipFormat: tooltipFormat,
3669 tooltipPrefix: tooltipPrefix,
3670 tooltipSuffix: tooltipSuffix,
3671 tooltipSkipNull: tooltipSkipNull,
3672 tooltipValueLookups: tooltipValueLookups,
3673 tooltipFormatFieldlist: tooltipFormatFieldlist,
3674 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3675 numberFormatter: numberFormatter,
3676 numberDigitGroupSep: numberDigitGroupSep,
3677 numberDecimalMark: numberDecimalMark,
3678 numberDigitGroupCount: numberDigitGroupCount,
3679 animatedZooms: animatedZooms,
3680 width: state.chartWidth(),
3681 height: state.chartHeight()
3684 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3688 // ----------------------------------------------------------------------------------------------------------------
3695 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3696 if(after < state.netdata_first)
3697 after = state.netdata_first;
3699 if(before > state.netdata_last)
3700 before = state.netdata_last;
3702 state.setMode('zoom');
3703 state.globalSelectionSyncStop();
3704 state.globalSelectionSyncDelay();
3705 state.dygraph_user_action = true;
3706 state.dygraph_force_zoom = true;
3707 state.updateChartPanOrZoom(after, before);
3708 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3711 NETDATA.dygraphSetSelection = function(state, t) {
3712 if(typeof state.dygraph_instance !== 'undefined') {
3713 var r = state.calculateRowForTime(t);
3715 state.dygraph_instance.setSelection(r);
3717 state.dygraph_instance.clearSelection();
3718 state.legendShowUndefined();
3725 NETDATA.dygraphClearSelection = function(state, t) {
3726 if(typeof state.dygraph_instance !== 'undefined') {
3727 state.dygraph_instance.clearSelection();
3732 NETDATA.dygraphSmoothInitialize = function(callback) {
3734 url: NETDATA.dygraph_smooth_js,
3737 xhrFields: { withCredentials: true } // required for the cookie
3740 NETDATA.dygraph.smooth = true;
3741 smoothPlotter.smoothing = 0.3;
3744 NETDATA.dygraph.smooth = false;
3746 .always(function() {
3747 if(typeof callback === "function")
3752 NETDATA.dygraphInitialize = function(callback) {
3753 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3755 url: NETDATA.dygraph_js,
3758 xhrFields: { withCredentials: true } // required for the cookie
3761 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3764 NETDATA.chartLibraries.dygraph.enabled = false;
3765 NETDATA.error(100, NETDATA.dygraph_js);
3767 .always(function() {
3768 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3769 NETDATA.dygraphSmoothInitialize(callback);
3770 else if(typeof callback === "function")
3775 NETDATA.chartLibraries.dygraph.enabled = false;
3776 if(typeof callback === "function")
3781 NETDATA.dygraphChartUpdate = function(state, data) {
3782 var dygraph = state.dygraph_instance;
3784 if(typeof dygraph === 'undefined')
3785 return NETDATA.dygraphChartCreate(state, data);
3787 // when the chart is not visible, and hidden
3788 // if there is a window resize, dygraph detects
3789 // its element size as 0x0.
3790 // this will make it re-appear properly
3792 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3796 file: data.result.data,
3797 colors: state.chartColors(),
3798 labels: data.result.labels,
3799 labelsDivWidth: state.chartWidth() - 70,
3800 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3803 if(state.dygraph_force_zoom === true) {
3804 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3805 state.log('dygraphChartUpdate() forced zoom update');
3807 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3808 options.valueRange = state.dygraph_options.valueRange;
3809 options.isZoomedIgnoreProgrammaticZoom = true;
3810 state.dygraph_force_zoom = false;
3812 else if(state.current.name !== 'auto') {
3813 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3814 state.log('dygraphChartUpdate() loose update');
3816 options.valueRange = state.dygraph_options.valueRange;
3819 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3820 state.log('dygraphChartUpdate() strict update');
3822 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3823 options.valueRange = state.dygraph_options.valueRange;
3824 options.isZoomedIgnoreProgrammaticZoom = true;
3827 if(state.dygraph_smooth_eligible === true) {
3828 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3829 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3830 NETDATA.dygraphChartCreate(state, data);
3835 dygraph.updateOptions(options);
3837 state.dygraph_last_rendered = new Date().getTime();
3841 NETDATA.dygraphChartCreate = function(state, data) {
3842 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3843 state.log('dygraphChartCreate()');
3845 var self = $(state.element);
3847 var chart_type = state.chart.chart_type;
3848 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3849 chart_type = self.data('dygraph-type') || chart_type;
3851 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3852 smooth = self.data('dygraph-smooth') || smooth;
3854 if(NETDATA.dygraph.smooth === false)
3857 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3858 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3860 state.dygraph_options = {
3861 colors: self.data('dygraph-colors') || state.chartColors(),
3863 // leave a few pixels empty on the right of the chart
3864 rightGap: self.data('dygraph-rightgap') || 5,
3865 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3866 showRoller: self.data('dygraph-showroller') || false,
3868 title: self.data('dygraph-title') || state.title,
3869 titleHeight: self.data('dygraph-titleheight') || 19,
3871 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3872 labels: data.result.labels,
3873 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3874 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3875 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3876 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3877 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3880 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3881 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3883 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
3884 xRangePad: self.data('dygraph-xrangepad') || 0,
3885 yRangePad: self.data('dygraph-yrangepad') || 1,
3887 valueRange: self.data('dygraph-valuerange') || null,
3889 ylabel: state.units,
3890 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3892 // the function to plot the chart
3895 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3896 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3897 strokePattern: self.data('dygraph-strokepattern') || undefined,
3899 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3900 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3901 drawPoints: self.data('dygraph-drawpoints') || false,
3903 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3904 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3906 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3907 pointSize: self.data('dygraph-pointsize') || 1,
3909 // enabling this makes the chart with little square lines
3910 stepPlot: self.data('dygraph-stepplot') || false,
3912 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3913 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3914 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3916 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3917 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3918 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3919 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3921 drawAxis: self.data('dygraph-drawaxis') || true,
3922 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3923 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3924 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3926 drawGrid: self.data('dygraph-drawgrid') || true,
3927 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3928 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3929 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3930 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3931 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3933 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3934 sigFigs: self.data('dygraph-sigfigs') || null,
3935 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3936 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3938 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3939 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3940 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3942 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3943 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3947 ticker: Dygraph.dateTicker,
3948 axisLabelFormatter: function (d, gran) {
3949 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3951 valueFormatter: function (ms) {
3952 var d = new Date(ms);
3953 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3954 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3959 valueFormatter: function (x) {
3960 // we format legends with the state object
3961 // no need to do anything here
3962 // return (Math.round(x*100) / 100).toLocaleString();
3963 // return state.legendFormatValue(x);
3968 legendFormatter: function(data) {
3969 var elements = state.element_legend_childs;
3971 // if the hidden div is not there
3972 // we are not managing the legend
3973 if(elements.hidden === null) return;
3975 if (typeof data.x !== 'undefined') {
3976 state.legendSetDate(data.x);
3977 var i = data.series.length;
3979 var series = data.series[i];
3980 if(!series.isVisible) continue;
3981 state.legendSetLabelValue(series.label, series.y);
3987 drawCallback: function(dygraph, is_initial) {
3988 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3989 state.dygraph_user_action = false;
3991 var x_range = dygraph.xAxisRange();
3992 var after = Math.round(x_range[0]);
3993 var before = Math.round(x_range[1]);
3995 if(NETDATA.options.debug.dygraph === true)
3996 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3998 if(before <= state.netdata_last && after >= state.netdata_first)
3999 state.updateChartPanOrZoom(after, before);
4002 zoomCallback: function(minDate, maxDate, yRanges) {
4003 if(NETDATA.options.debug.dygraph === true)
4004 state.log('dygraphZoomCallback()');
4006 state.globalSelectionSyncStop();
4007 state.globalSelectionSyncDelay();
4008 state.setMode('zoom');
4010 // refresh it to the greatest possible zoom level
4011 state.dygraph_user_action = true;
4012 state.dygraph_force_zoom = true;
4013 state.updateChartPanOrZoom(minDate, maxDate);
4015 highlightCallback: function(event, x, points, row, seriesName) {
4016 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4017 state.log('dygraphHighlightCallback()');
4021 // there is a bug in dygraph when the chart is zoomed enough
4022 // the time it thinks is selected is wrong
4023 // here we calculate the time t based on the row number selected
4025 var t = state.data_after + row * state.data_update_every;
4026 // 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);
4028 state.globalSelectionSync(x);
4030 // fix legend zIndex using the internal structures of dygraph legend module
4031 // this works, but it is a hack!
4032 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4034 unhighlightCallback: function(event) {
4035 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4036 state.log('dygraphUnhighlightCallback()');
4038 state.unpauseChart();
4039 state.globalSelectionSyncStop();
4041 interactionModel : {
4042 mousedown: function(event, dygraph, context) {
4043 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4044 state.log('interactionModel.mousedown()');
4046 state.dygraph_user_action = true;
4047 state.globalSelectionSyncStop();
4049 if(NETDATA.options.debug.dygraph === true)
4050 state.log('dygraphMouseDown()');
4052 // Right-click should not initiate a zoom.
4053 if(event.button && event.button === 2) return;
4055 context.initializeMouseDown(event, dygraph, context);
4057 if(event.button && event.button === 1) {
4058 if (event.altKey || event.shiftKey) {
4059 state.setMode('pan');
4060 state.globalSelectionSyncDelay();
4061 Dygraph.startPan(event, dygraph, context);
4064 state.setMode('zoom');
4065 state.globalSelectionSyncDelay();
4066 Dygraph.startZoom(event, dygraph, context);
4070 if (event.altKey || event.shiftKey) {
4071 state.setMode('zoom');
4072 state.globalSelectionSyncDelay();
4073 Dygraph.startZoom(event, dygraph, context);
4076 state.setMode('pan');
4077 state.globalSelectionSyncDelay();
4078 Dygraph.startPan(event, dygraph, context);
4082 mousemove: function(event, dygraph, context) {
4083 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4084 state.log('interactionModel.mousemove()');
4086 if(context.isPanning) {
4087 state.dygraph_user_action = true;
4088 state.globalSelectionSyncStop();
4089 state.globalSelectionSyncDelay();
4090 state.setMode('pan');
4091 Dygraph.movePan(event, dygraph, context);
4093 else if(context.isZooming) {
4094 state.dygraph_user_action = true;
4095 state.globalSelectionSyncStop();
4096 state.globalSelectionSyncDelay();
4097 state.setMode('zoom');
4098 Dygraph.moveZoom(event, dygraph, context);
4101 mouseup: function(event, dygraph, context) {
4102 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4103 state.log('interactionModel.mouseup()');
4105 if (context.isPanning) {
4106 state.dygraph_user_action = true;
4107 state.globalSelectionSyncDelay();
4108 Dygraph.endPan(event, dygraph, context);
4110 else if (context.isZooming) {
4111 state.dygraph_user_action = true;
4112 state.globalSelectionSyncDelay();
4113 Dygraph.endZoom(event, dygraph, context);
4116 click: function(event, dygraph, context) {
4117 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4118 state.log('interactionModel.click()');
4120 event.preventDefault();
4122 dblclick: function(event, dygraph, context) {
4123 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4124 state.log('interactionModel.dblclick()');
4125 NETDATA.resetAllCharts(state);
4127 mousewheel: function(event, dygraph, context) {
4128 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4129 state.log('interactionModel.mousewheel()');
4131 // Take the offset of a mouse event on the dygraph canvas and
4132 // convert it to a pair of percentages from the bottom left.
4133 // (Not top left, bottom is where the lower value is.)
4134 function offsetToPercentage(g, offsetX, offsetY) {
4135 // This is calculating the pixel offset of the leftmost date.
4136 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4137 var yar0 = g.yAxisRange(0);
4139 // This is calculating the pixel of the higest value. (Top pixel)
4140 var yOffset = g.toDomCoords(null, yar0[1])[1];
4142 // x y w and h are relative to the corner of the drawing area,
4143 // so that the upper corner of the drawing area is (0, 0).
4144 var x = offsetX - xOffset;
4145 var y = offsetY - yOffset;
4147 // This is computing the rightmost pixel, effectively defining the
4149 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4151 // This is computing the lowest pixel, effectively defining the height.
4152 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4154 // Percentage from the left.
4155 var xPct = w === 0 ? 0 : (x / w);
4156 // Percentage from the top.
4157 var yPct = h === 0 ? 0 : (y / h);
4159 // The (1-) part below changes it from "% distance down from the top"
4160 // to "% distance up from the bottom".
4161 return [xPct, (1-yPct)];
4164 // Adjusts [x, y] toward each other by zoomInPercentage%
4165 // Split it so the left/bottom axis gets xBias/yBias of that change and
4166 // tight/top gets (1-xBias)/(1-yBias) of that change.
4168 // If a bias is missing it splits it down the middle.
4169 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4170 xBias = xBias || 0.5;
4171 yBias = yBias || 0.5;
4173 function adjustAxis(axis, zoomInPercentage, bias) {
4174 var delta = axis[1] - axis[0];
4175 var increment = delta * zoomInPercentage;
4176 var foo = [increment * bias, increment * (1-bias)];
4178 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4181 var yAxes = g.yAxisRanges();
4183 for (var i = 0; i < yAxes.length; i++) {
4184 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4187 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4190 if(event.altKey || event.shiftKey) {
4191 state.dygraph_user_action = true;
4193 state.globalSelectionSyncStop();
4194 state.globalSelectionSyncDelay();
4196 // http://dygraphs.com/gallery/interaction-api.js
4197 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4198 var percentage = normal / 50;
4200 if (!(event.offsetX && event.offsetY)){
4201 event.offsetX = event.layerX - event.target.offsetLeft;
4202 event.offsetY = event.layerY - event.target.offsetTop;
4205 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4206 var xPct = percentages[0];
4207 var yPct = percentages[1];
4209 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4211 var after = new_x_range[0];
4212 var before = new_x_range[1];
4214 var first = state.netdata_first + state.data_update_every;
4215 var last = state.netdata_last + state.data_update_every;
4218 after -= (before - last);
4225 state.setMode('zoom');
4226 if(state.updateChartPanOrZoom(after, before) === true)
4227 dygraph.updateOptions({ dateWindow: [ after, before ] });
4229 event.preventDefault();
4232 touchstart: function(event, dygraph, context) {
4233 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4234 state.log('interactionModel.touchstart()');
4236 state.dygraph_user_action = true;
4237 state.setMode('zoom');
4240 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4242 // we overwrite the touch directions at the end, to overwrite
4243 // the internal default of dygraphs
4244 context.touchDirections = { x: true, y: false };
4246 state.dygraph_last_touch_start = new Date().getTime();
4247 state.dygraph_last_touch_move = 0;
4249 if(typeof event.touches[0].pageX === 'number')
4250 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4252 state.dygraph_last_touch_page_x = 0;
4254 touchmove: function(event, dygraph, context) {
4255 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4256 state.log('interactionModel.touchmove()');
4258 state.dygraph_user_action = true;
4259 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4261 state.dygraph_last_touch_move = new Date().getTime();
4263 touchend: function(event, dygraph, context) {
4264 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4265 state.log('interactionModel.touchend()');
4267 state.dygraph_user_action = true;
4268 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4270 // if it didn't move, it is a selection
4271 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4272 // internal api of dygraphs
4273 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4274 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4275 if(NETDATA.dygraphSetSelection(state, t) === true)
4276 state.globalSelectionSync(t);
4279 // if it was double tap within double click time, reset the charts
4280 var now = new Date().getTime();
4281 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4282 if(state.dygraph_last_touch_move === 0) {
4283 var dt = now - state.dygraph_last_touch_end;
4284 if(dt <= NETDATA.options.current.double_click_speed)
4285 NETDATA.resetAllCharts(state);
4289 // remember the timestamp of the last touch end
4290 state.dygraph_last_touch_end = now;
4295 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4296 state.dygraph_options.drawGrid = false;
4297 state.dygraph_options.drawAxis = false;
4298 state.dygraph_options.title = undefined;
4299 state.dygraph_options.units = undefined;
4300 state.dygraph_options.ylabel = undefined;
4301 state.dygraph_options.yLabelWidth = 0;
4302 state.dygraph_options.labelsDivWidth = 120;
4303 state.dygraph_options.labelsDivStyles.width = '120px';
4304 state.dygraph_options.labelsSeparateLines = true;
4305 state.dygraph_options.rightGap = 0;
4306 state.dygraph_options.yRangePad = 1;
4309 if(smooth === true) {
4310 state.dygraph_smooth_eligible = true;
4312 if(NETDATA.options.current.smooth_plot === true)
4313 state.dygraph_options.plotter = smoothPlotter;
4315 else state.dygraph_smooth_eligible = false;
4317 state.dygraph_instance = new Dygraph(state.element_chart,
4318 data.result.data, state.dygraph_options);
4320 state.dygraph_force_zoom = false;
4321 state.dygraph_user_action = false;
4322 state.dygraph_last_rendered = new Date().getTime();
4326 // ----------------------------------------------------------------------------------------------------------------
4329 NETDATA.morrisInitialize = function(callback) {
4330 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4332 // morris requires raphael
4333 if(!NETDATA.chartLibraries.raphael.initialized) {
4334 if(NETDATA.chartLibraries.raphael.enabled) {
4335 NETDATA.raphaelInitialize(function() {
4336 NETDATA.morrisInitialize(callback);
4340 NETDATA.chartLibraries.morris.enabled = false;
4341 if(typeof callback === "function")
4346 NETDATA._loadCSS(NETDATA.morris_css);
4349 url: NETDATA.morris_js,
4352 xhrFields: { withCredentials: true } // required for the cookie
4355 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4358 NETDATA.chartLibraries.morris.enabled = false;
4359 NETDATA.error(100, NETDATA.morris_js);
4361 .always(function() {
4362 if(typeof callback === "function")
4368 NETDATA.chartLibraries.morris.enabled = false;
4369 if(typeof callback === "function")
4374 NETDATA.morrisChartUpdate = function(state, data) {
4375 state.morris_instance.setData(data.result.data);
4379 NETDATA.morrisChartCreate = function(state, data) {
4381 state.morris_options = {
4382 element: state.element_chart.id,
4383 data: data.result.data,
4385 ykeys: data.dimension_names,
4386 labels: data.dimension_names,
4392 continuousLine: false,
4393 behaveLikeLine: false
4396 if(state.chart.chart_type === 'line')
4397 state.morris_instance = new Morris.Line(state.morris_options);
4399 else if(state.chart.chart_type === 'area') {
4400 state.morris_options.behaveLikeLine = true;
4401 state.morris_instance = new Morris.Area(state.morris_options);
4404 state.morris_instance = new Morris.Area(state.morris_options);
4409 // ----------------------------------------------------------------------------------------------------------------
4412 NETDATA.raphaelInitialize = function(callback) {
4413 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4415 url: NETDATA.raphael_js,
4418 xhrFields: { withCredentials: true } // required for the cookie
4421 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4424 NETDATA.chartLibraries.raphael.enabled = false;
4425 NETDATA.error(100, NETDATA.raphael_js);
4427 .always(function() {
4428 if(typeof callback === "function")
4433 NETDATA.chartLibraries.raphael.enabled = false;
4434 if(typeof callback === "function")
4439 NETDATA.raphaelChartUpdate = function(state, data) {
4440 $(state.element_chart).raphael(data.result, {
4441 width: state.chartWidth(),
4442 height: state.chartHeight()
4448 NETDATA.raphaelChartCreate = function(state, data) {
4449 $(state.element_chart).raphael(data.result, {
4450 width: state.chartWidth(),
4451 height: state.chartHeight()
4457 // ----------------------------------------------------------------------------------------------------------------
4460 NETDATA.c3Initialize = function(callback) {
4461 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4464 if(!NETDATA.chartLibraries.d3.initialized) {
4465 if(NETDATA.chartLibraries.d3.enabled) {
4466 NETDATA.d3Initialize(function() {
4467 NETDATA.c3Initialize(callback);
4471 NETDATA.chartLibraries.c3.enabled = false;
4472 if(typeof callback === "function")
4477 NETDATA._loadCSS(NETDATA.c3_css);
4483 xhrFields: { withCredentials: true } // required for the cookie
4486 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4489 NETDATA.chartLibraries.c3.enabled = false;
4490 NETDATA.error(100, NETDATA.c3_js);
4492 .always(function() {
4493 if(typeof callback === "function")
4499 NETDATA.chartLibraries.c3.enabled = false;
4500 if(typeof callback === "function")
4505 NETDATA.c3ChartUpdate = function(state, data) {
4506 state.c3_instance.destroy();
4507 return NETDATA.c3ChartCreate(state, data);
4509 //state.c3_instance.load({
4510 // rows: data.result,
4517 NETDATA.c3ChartCreate = function(state, data) {
4519 state.element_chart.id = 'c3-' + state.uuid;
4520 // console.log('id = ' + state.element_chart.id);
4522 state.c3_instance = c3.generate({
4523 bindto: '#' + state.element_chart.id,
4525 width: state.chartWidth(),
4526 height: state.chartHeight()
4529 pattern: state.chartColors()
4534 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4540 format: function(x) {
4541 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4568 // console.log(state.c3_instance);
4573 // ----------------------------------------------------------------------------------------------------------------
4576 NETDATA.d3Initialize = function(callback) {
4577 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4582 xhrFields: { withCredentials: true } // required for the cookie
4585 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4588 NETDATA.chartLibraries.d3.enabled = false;
4589 NETDATA.error(100, NETDATA.d3_js);
4591 .always(function() {
4592 if(typeof callback === "function")
4597 NETDATA.chartLibraries.d3.enabled = false;
4598 if(typeof callback === "function")
4603 NETDATA.d3ChartUpdate = function(state, data) {
4607 NETDATA.d3ChartCreate = function(state, data) {
4611 // ----------------------------------------------------------------------------------------------------------------
4614 NETDATA.googleInitialize = function(callback) {
4615 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4617 url: NETDATA.google_js,
4620 xhrFields: { withCredentials: true } // required for the cookie
4623 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4624 google.load('visualization', '1.1', {
4625 'packages': ['corechart', 'controls'],
4626 'callback': callback
4630 NETDATA.chartLibraries.google.enabled = false;
4631 NETDATA.error(100, NETDATA.google_js);
4632 if(typeof callback === "function")
4637 NETDATA.chartLibraries.google.enabled = false;
4638 if(typeof callback === "function")
4643 NETDATA.googleChartUpdate = function(state, data) {
4644 var datatable = new google.visualization.DataTable(data.result);
4645 state.google_instance.draw(datatable, state.google_options);
4649 NETDATA.googleChartCreate = function(state, data) {
4650 var datatable = new google.visualization.DataTable(data.result);
4652 state.google_options = {
4653 colors: state.chartColors(),
4655 // do not set width, height - the chart resizes itself
4656 //width: state.chartWidth(),
4657 //height: state.chartHeight(),
4662 // title: "Time of Day",
4663 // format:'HH:mm:ss',
4664 viewWindowMode: 'maximized',
4676 viewWindowMode: 'pretty',
4691 focusTarget: 'category',
4698 titlePosition: 'out',
4709 curveType: 'function',
4714 switch(state.chart.chart_type) {
4716 state.google_options.vAxis.viewWindowMode = 'maximized';
4717 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4718 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4722 state.google_options.isStacked = true;
4723 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4724 state.google_options.vAxis.viewWindowMode = 'maximized';
4725 state.google_options.vAxis.minValue = null;
4726 state.google_options.vAxis.maxValue = null;
4727 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4732 state.google_options.lineWidth = 2;
4733 state.google_instance = new google.visualization.LineChart(state.element_chart);
4737 state.google_instance.draw(datatable, state.google_options);
4741 // ----------------------------------------------------------------------------------------------------------------
4743 NETDATA.percentFromValueMax = function(value, max) {
4744 if(value === null) value = 0;
4745 if(max < value) max = value;
4749 pcent = Math.round(value * 100 / max);
4750 if(pcent === 0 && value > 0) pcent = 1;
4756 // ----------------------------------------------------------------------------------------------------------------
4759 NETDATA.easypiechartInitialize = function(callback) {
4760 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4762 url: NETDATA.easypiechart_js,
4765 xhrFields: { withCredentials: true } // required for the cookie
4768 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4771 NETDATA.chartLibraries.easypiechart.enabled = false;
4772 NETDATA.error(100, NETDATA.easypiechart_js);
4774 .always(function() {
4775 if(typeof callback === "function")
4780 NETDATA.chartLibraries.easypiechart.enabled = false;
4781 if(typeof callback === "function")
4786 NETDATA.easypiechartClearSelection = function(state) {
4787 if(typeof state.easyPieChartEvent !== 'undefined') {
4788 if(state.easyPieChartEvent.timer !== null)
4789 clearTimeout(state.easyPieChartEvent.timer);
4791 state.easyPieChartEvent.timer = null;
4794 if(state.isAutoRefreshable() === true && state.data !== null) {
4795 NETDATA.easypiechartChartUpdate(state, state.data);
4798 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4799 state.easyPieChart_instance.update(0);
4801 state.easyPieChart_instance.enableAnimation();
4806 NETDATA.easypiechartSetSelection = function(state, t) {
4807 if(state.timeIsVisible(t) !== true)
4808 return NETDATA.easypiechartClearSelection(state);
4810 var slot = state.calculateRowForTime(t);
4811 if(slot < 0 || slot >= state.data.result.length)
4812 return NETDATA.easypiechartClearSelection(state);
4814 if(typeof state.easyPieChartEvent === 'undefined') {
4815 state.easyPieChartEvent = {
4822 var value = state.data.result[state.data.result.length - 1 - slot];
4823 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4824 var pcent = NETDATA.percentFromValueMax(value, max);
4826 state.easyPieChartEvent.value = value;
4827 state.easyPieChartEvent.pcent = pcent;
4828 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4830 if(state.easyPieChartEvent.timer === null) {
4831 state.easyPieChart_instance.disableAnimation();
4833 state.easyPieChartEvent.timer = setTimeout(function() {
4834 state.easyPieChartEvent.timer = null;
4835 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4836 }, NETDATA.options.current.charts_selection_animation_delay);
4842 NETDATA.easypiechartChartUpdate = function(state, data) {
4843 var value, max, pcent;
4845 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4851 value = data.result[0];
4852 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4853 pcent = NETDATA.percentFromValueMax(value, max);
4856 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4857 state.easyPieChart_instance.update(pcent);
4861 NETDATA.easypiechartChartCreate = function(state, data) {
4862 var self = $(state.element);
4863 var chart = $(state.element_chart);
4865 var value = data.result[0];
4866 var max = self.data('easypiechart-max-value') || null;
4867 var adjust = self.data('easypiechart-adjust') || null;
4871 state.easyPieChartMax = null;
4874 state.easyPieChartMax = max;
4876 var pcent = NETDATA.percentFromValueMax(value, max);
4878 chart.data('data-percent', pcent);
4882 case 'width': size = state.chartHeight(); break;
4883 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4884 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4886 default: size = state.chartWidth(); break;
4888 state.element.style.width = size + 'px';
4889 state.element.style.height = size + 'px';
4891 var stroke = Math.floor(size / 22);
4892 if(stroke < 3) stroke = 2;
4894 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4895 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4896 state.easyPieChartLabel = document.createElement('span');
4897 state.easyPieChartLabel.className = 'easyPieChartLabel';
4898 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4899 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4900 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4901 state.element_chart.appendChild(state.easyPieChartLabel);
4903 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4904 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4905 state.easyPieChartTitle = document.createElement('span');
4906 state.easyPieChartTitle.className = 'easyPieChartTitle';
4907 state.easyPieChartTitle.innerHTML = state.title;
4908 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4909 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4910 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4911 state.element_chart.appendChild(state.easyPieChartTitle);
4913 var unitfontsize = Math.round(titlefontsize * 0.9);
4914 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4915 state.easyPieChartUnits = document.createElement('span');
4916 state.easyPieChartUnits.className = 'easyPieChartUnits';
4917 state.easyPieChartUnits.innerHTML = state.units;
4918 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4919 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4920 state.element_chart.appendChild(state.easyPieChartUnits);
4922 chart.easyPieChart({
4923 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4924 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4925 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4926 scaleLength: self.data('easypiechart-scalelength') || 5,
4927 lineCap: self.data('easypiechart-linecap') || 'round',
4928 lineWidth: self.data('easypiechart-linewidth') || stroke,
4929 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4930 size: self.data('easypiechart-size') || size,
4931 rotate: self.data('easypiechart-rotate') || 0,
4932 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4933 easing: self.data('easypiechart-easing') || undefined
4936 // when we just re-create the chart
4937 // do not animate the first update
4939 if(typeof state.easyPieChart_instance !== 'undefined')
4942 state.easyPieChart_instance = chart.data('easyPieChart');
4943 if(animate === false) state.easyPieChart_instance.disableAnimation();
4944 state.easyPieChart_instance.update(pcent);
4945 if(animate === false) state.easyPieChart_instance.enableAnimation();
4949 // ----------------------------------------------------------------------------------------------------------------
4952 NETDATA.gaugeInitialize = function(callback) {
4953 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4955 url: NETDATA.gauge_js,
4958 xhrFields: { withCredentials: true } // required for the cookie
4961 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4964 NETDATA.chartLibraries.gauge.enabled = false;
4965 NETDATA.error(100, NETDATA.gauge_js);
4967 .always(function() {
4968 if(typeof callback === "function")
4973 NETDATA.chartLibraries.gauge.enabled = false;
4974 if(typeof callback === "function")
4979 NETDATA.gaugeAnimation = function(state, status) {
4982 if(typeof status === 'boolean' && status === false)
4984 else if(typeof status === 'number')
4987 state.gauge_instance.animationSpeed = speed;
4988 state.___gaugeOld__.speed = speed;
4991 NETDATA.gaugeSet = function(state, value, min, max) {
4992 if(typeof value !== 'number') value = 0;
4993 if(typeof min !== 'number') min = 0;
4994 if(typeof max !== 'number') max = 0;
4995 if(value > max) max = value;
4996 if(value < min) min = value;
5005 // gauge.js has an issue if the needle
5006 // is smaller than min or larger than max
5007 // when we set the new values
5008 // the needle will go crazy
5010 // to prevent it, we always feed it
5011 // with a percentage, so that the needle
5012 // is always between min and max
5013 var pcent = (value - min) * 100 / (max - min);
5015 // these should never happen
5016 if(pcent < 0) pcent = 0;
5017 if(pcent > 100) pcent = 100;
5019 state.gauge_instance.set(pcent);
5021 state.___gaugeOld__.value = value;
5022 state.___gaugeOld__.min = min;
5023 state.___gaugeOld__.max = max;
5026 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5027 if(state.___gaugeOld__.valueLabel !== value) {
5028 state.___gaugeOld__.valueLabel = value;
5029 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5031 if(state.___gaugeOld__.minLabel !== min) {
5032 state.___gaugeOld__.minLabel = min;
5033 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5035 if(state.___gaugeOld__.maxLabel !== max) {
5036 state.___gaugeOld__.maxLabel = max;
5037 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5041 NETDATA.gaugeClearSelection = function(state) {
5042 if(typeof state.gaugeEvent !== 'undefined') {
5043 if(state.gaugeEvent.timer !== null)
5044 clearTimeout(state.gaugeEvent.timer);
5046 state.gaugeEvent.timer = null;
5049 if(state.isAutoRefreshable() === true && state.data !== null) {
5050 NETDATA.gaugeChartUpdate(state, state.data);
5053 NETDATA.gaugeAnimation(state, false);
5054 NETDATA.gaugeSet(state, null, null, null);
5055 NETDATA.gaugeSetLabels(state, null, null, null);
5058 NETDATA.gaugeAnimation(state, true);
5062 NETDATA.gaugeSetSelection = function(state, t) {
5063 if(state.timeIsVisible(t) !== true)
5064 return NETDATA.gaugeClearSelection(state);
5066 var slot = state.calculateRowForTime(t);
5067 if(slot < 0 || slot >= state.data.result.length)
5068 return NETDATA.gaugeClearSelection(state);
5070 if(typeof state.gaugeEvent === 'undefined') {
5071 state.gaugeEvent = {
5079 var value = state.data.result[state.data.result.length - 1 - slot];
5080 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5083 state.gaugeEvent.value = value;
5084 state.gaugeEvent.max = max;
5085 state.gaugeEvent.min = min;
5086 NETDATA.gaugeSetLabels(state, value, min, max);
5088 if(state.gaugeEvent.timer === null) {
5089 NETDATA.gaugeAnimation(state, false);
5091 state.gaugeEvent.timer = setTimeout(function() {
5092 state.gaugeEvent.timer = null;
5093 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5094 }, NETDATA.options.current.charts_selection_animation_delay);
5100 NETDATA.gaugeChartUpdate = function(state, data) {
5101 var value, min, max;
5103 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5107 NETDATA.gaugeSetLabels(state, null, null, null);
5110 value = data.result[0];
5112 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5113 if(value > max) max = value;
5114 NETDATA.gaugeSetLabels(state, value, min, max);
5117 NETDATA.gaugeSet(state, value, min, max);
5121 NETDATA.gaugeChartCreate = function(state, data) {
5122 var self = $(state.element);
5123 // var chart = $(state.element_chart);
5125 var value = data.result[0];
5126 var max = self.data('gauge-max-value') || null;
5127 var adjust = self.data('gauge-adjust') || null;
5128 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5129 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5130 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5131 var stopColor = self.data('gauge-stop-color') || void 0;
5132 var generateGradient = self.data('gauge-generate-gradient') || false;
5136 state.gaugeMax = null;
5139 state.gaugeMax = max;
5141 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5143 // case 'width': width = height * ratio; break;
5145 // default: height = width / ratio; break;
5147 //state.element.style.width = width.toString() + 'px';
5148 //state.element.style.height = height.toString() + 'px';
5153 lines: 12, // The number of lines to draw
5154 angle: 0.15, // The length of each line
5155 lineWidth: 0.44, // 0.44 The line thickness
5157 length: 0.8, // 0.9 The radius of the inner circle
5158 strokeWidth: 0.035, // The rotation offset
5159 color: pointerColor // Fill color
5161 colorStart: startColor, // Colors
5162 colorStop: stopColor, // just experiment with them
5163 strokeColor: strokeColor, // to see which ones work best for you
5165 generateGradient: (generateGradient === true)?true:false,
5169 if (generateGradient.constructor === Array) {
5171 // data-gauge-generate-gradient="[0, 50, 100]"
5172 // data-gauge-gradient-percent-color-0="#FFFFFF"
5173 // data-gauge-gradient-percent-color-50="#999900"
5174 // data-gauge-gradient-percent-color-100="#000000"
5176 options.percentColors = new Array();
5177 var len = generateGradient.length;
5179 var pcent = generateGradient[len];
5180 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5181 if(color !== false) {
5182 var a = new Array();
5185 options.percentColors.unshift(a);
5188 if(options.percentColors.length === 0)
5189 delete options.percentColors;
5191 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5192 options.percentColors = [
5193 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5194 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5195 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5196 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5197 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5198 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5199 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5200 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5201 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5202 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5203 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5206 state.gauge_canvas = document.createElement('canvas');
5207 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5208 state.gauge_canvas.className = 'gaugeChart';
5209 state.gauge_canvas.width = width;
5210 state.gauge_canvas.height = height;
5211 state.element_chart.appendChild(state.gauge_canvas);
5213 var valuefontsize = Math.floor(height / 6);
5214 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5215 state.gaugeChartLabel = document.createElement('span');
5216 state.gaugeChartLabel.className = 'gaugeChartLabel';
5217 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5218 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5219 state.element_chart.appendChild(state.gaugeChartLabel);
5221 var titlefontsize = Math.round(valuefontsize / 2);
5223 state.gaugeChartTitle = document.createElement('span');
5224 state.gaugeChartTitle.className = 'gaugeChartTitle';
5225 state.gaugeChartTitle.innerHTML = state.title;
5226 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5227 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5228 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5229 state.element_chart.appendChild(state.gaugeChartTitle);
5231 var unitfontsize = Math.round(titlefontsize * 0.9);
5232 state.gaugeChartUnits = document.createElement('span');
5233 state.gaugeChartUnits.className = 'gaugeChartUnits';
5234 state.gaugeChartUnits.innerHTML = state.units;
5235 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5236 state.element_chart.appendChild(state.gaugeChartUnits);
5238 state.gaugeChartMin = document.createElement('span');
5239 state.gaugeChartMin.className = 'gaugeChartMin';
5240 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5241 state.element_chart.appendChild(state.gaugeChartMin);
5243 state.gaugeChartMax = document.createElement('span');
5244 state.gaugeChartMax.className = 'gaugeChartMax';
5245 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5246 state.element_chart.appendChild(state.gaugeChartMax);
5248 // when we just re-create the chart
5249 // do not animate the first update
5251 if(typeof state.gauge_instance !== 'undefined')
5254 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5256 state.___gaugeOld__ = {
5265 // we will always feed a percentage
5266 state.gauge_instance.minValue = 0;
5267 state.gauge_instance.maxValue = 100;
5269 NETDATA.gaugeAnimation(state, animate);
5270 NETDATA.gaugeSet(state, value, 0, max);
5271 NETDATA.gaugeSetLabels(state, value, 0, max);
5272 NETDATA.gaugeAnimation(state, true);
5276 // ----------------------------------------------------------------------------------------------------------------
5277 // Charts Libraries Registration
5279 NETDATA.chartLibraries = {
5281 initialize: NETDATA.dygraphInitialize,
5282 create: NETDATA.dygraphChartCreate,
5283 update: NETDATA.dygraphChartUpdate,
5284 resize: function(state) {
5285 if(typeof state.dygraph_instance.resize === 'function')
5286 state.dygraph_instance.resize();
5288 setSelection: NETDATA.dygraphSetSelection,
5289 clearSelection: NETDATA.dygraphClearSelection,
5290 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5293 format: function(state) { return 'json'; },
5294 options: function(state) { return 'ms|flip'; },
5295 legend: function(state) {
5296 if(this.isSparkline(state) === false)
5297 return 'right-side';
5301 autoresize: function(state) { return true; },
5302 max_updates_to_recreate: function(state) { return 5000; },
5303 track_colors: function(state) { return true; },
5304 pixels_per_point: function(state) {
5305 if(this.isSparkline(state) === false)
5311 isSparkline: function(state) {
5312 if(typeof state.dygraph_sparkline === 'undefined') {
5313 var t = $(state.element).data('dygraph-theme');
5314 if(t === 'sparkline')
5315 state.dygraph_sparkline = true;
5317 state.dygraph_sparkline = false;
5319 return state.dygraph_sparkline;
5323 initialize: NETDATA.sparklineInitialize,
5324 create: NETDATA.sparklineChartCreate,
5325 update: NETDATA.sparklineChartUpdate,
5327 setSelection: undefined, // function(state, t) { return true; },
5328 clearSelection: undefined, // function(state) { return true; },
5329 toolboxPanAndZoom: null,
5332 format: function(state) { return 'array'; },
5333 options: function(state) { return 'flip|abs'; },
5334 legend: function(state) { return null; },
5335 autoresize: function(state) { return false; },
5336 max_updates_to_recreate: function(state) { return 5000; },
5337 track_colors: function(state) { return false; },
5338 pixels_per_point: function(state) { return 3; }
5341 initialize: NETDATA.peityInitialize,
5342 create: NETDATA.peityChartCreate,
5343 update: NETDATA.peityChartUpdate,
5345 setSelection: undefined, // function(state, t) { return true; },
5346 clearSelection: undefined, // function(state) { return true; },
5347 toolboxPanAndZoom: null,
5350 format: function(state) { return 'ssvcomma'; },
5351 options: function(state) { return 'null2zero|flip|abs'; },
5352 legend: function(state) { return null; },
5353 autoresize: function(state) { return false; },
5354 max_updates_to_recreate: function(state) { return 5000; },
5355 track_colors: function(state) { return false; },
5356 pixels_per_point: function(state) { return 3; }
5359 initialize: NETDATA.morrisInitialize,
5360 create: NETDATA.morrisChartCreate,
5361 update: NETDATA.morrisChartUpdate,
5363 setSelection: undefined, // function(state, t) { return true; },
5364 clearSelection: undefined, // function(state) { return true; },
5365 toolboxPanAndZoom: null,
5368 format: function(state) { return 'json'; },
5369 options: function(state) { return 'objectrows|ms'; },
5370 legend: function(state) { return null; },
5371 autoresize: function(state) { return false; },
5372 max_updates_to_recreate: function(state) { return 50; },
5373 track_colors: function(state) { return false; },
5374 pixels_per_point: function(state) { return 15; }
5377 initialize: NETDATA.googleInitialize,
5378 create: NETDATA.googleChartCreate,
5379 update: NETDATA.googleChartUpdate,
5381 setSelection: undefined, //function(state, t) { return true; },
5382 clearSelection: undefined, //function(state) { return true; },
5383 toolboxPanAndZoom: null,
5386 format: function(state) { return 'datatable'; },
5387 options: function(state) { return ''; },
5388 legend: function(state) { return null; },
5389 autoresize: function(state) { return false; },
5390 max_updates_to_recreate: function(state) { return 300; },
5391 track_colors: function(state) { return false; },
5392 pixels_per_point: function(state) { return 4; }
5395 initialize: NETDATA.raphaelInitialize,
5396 create: NETDATA.raphaelChartCreate,
5397 update: NETDATA.raphaelChartUpdate,
5399 setSelection: undefined, // function(state, t) { return true; },
5400 clearSelection: undefined, // function(state) { return true; },
5401 toolboxPanAndZoom: null,
5404 format: function(state) { return 'json'; },
5405 options: function(state) { return ''; },
5406 legend: function(state) { return null; },
5407 autoresize: function(state) { return false; },
5408 max_updates_to_recreate: function(state) { return 5000; },
5409 track_colors: function(state) { return false; },
5410 pixels_per_point: function(state) { return 3; }
5413 initialize: NETDATA.c3Initialize,
5414 create: NETDATA.c3ChartCreate,
5415 update: NETDATA.c3ChartUpdate,
5417 setSelection: undefined, // function(state, t) { return true; },
5418 clearSelection: undefined, // function(state) { return true; },
5419 toolboxPanAndZoom: null,
5422 format: function(state) { return 'csvjsonarray'; },
5423 options: function(state) { return 'milliseconds'; },
5424 legend: function(state) { return null; },
5425 autoresize: function(state) { return false; },
5426 max_updates_to_recreate: function(state) { return 5000; },
5427 track_colors: function(state) { return false; },
5428 pixels_per_point: function(state) { return 15; }
5431 initialize: NETDATA.d3Initialize,
5432 create: NETDATA.d3ChartCreate,
5433 update: NETDATA.d3ChartUpdate,
5435 setSelection: undefined, // function(state, t) { return true; },
5436 clearSelection: undefined, // function(state) { return true; },
5437 toolboxPanAndZoom: null,
5440 format: function(state) { return 'json'; },
5441 options: function(state) { return ''; },
5442 legend: function(state) { return null; },
5443 autoresize: function(state) { return false; },
5444 max_updates_to_recreate: function(state) { return 5000; },
5445 track_colors: function(state) { return false; },
5446 pixels_per_point: function(state) { return 3; }
5449 initialize: NETDATA.easypiechartInitialize,
5450 create: NETDATA.easypiechartChartCreate,
5451 update: NETDATA.easypiechartChartUpdate,
5453 setSelection: NETDATA.easypiechartSetSelection,
5454 clearSelection: NETDATA.easypiechartClearSelection,
5455 toolboxPanAndZoom: null,
5458 format: function(state) { return 'array'; },
5459 options: function(state) { return 'absolute'; },
5460 legend: function(state) { return null; },
5461 autoresize: function(state) { return false; },
5462 max_updates_to_recreate: function(state) { return 5000; },
5463 track_colors: function(state) { return true; },
5464 pixels_per_point: function(state) { return 3; },
5468 initialize: NETDATA.gaugeInitialize,
5469 create: NETDATA.gaugeChartCreate,
5470 update: NETDATA.gaugeChartUpdate,
5472 setSelection: NETDATA.gaugeSetSelection,
5473 clearSelection: NETDATA.gaugeClearSelection,
5474 toolboxPanAndZoom: null,
5477 format: function(state) { return 'array'; },
5478 options: function(state) { return 'absolute'; },
5479 legend: function(state) { return null; },
5480 autoresize: function(state) { return false; },
5481 max_updates_to_recreate: function(state) { return 5000; },
5482 track_colors: function(state) { return true; },
5483 pixels_per_point: function(state) { return 3; },
5488 NETDATA.registerChartLibrary = function(library, url) {
5489 if(NETDATA.options.debug.libraries === true)
5490 console.log("registering chart library: " + library);
5492 NETDATA.chartLibraries[library].url = url;
5493 NETDATA.chartLibraries[library].initialized = true;
5494 NETDATA.chartLibraries[library].enabled = true;
5497 // ----------------------------------------------------------------------------------------------------------------
5498 // Load required JS libraries and CSS
5500 NETDATA.requiredJs = [
5502 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5503 isAlreadyLoaded: function() {
5504 // check if bootstrap is loaded
5505 if(typeof $().emulateTransitionEnd == 'function')
5508 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5516 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5517 isAlreadyLoaded: function() { return false; }
5520 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5521 isAlreadyLoaded: function() { return false; }
5525 NETDATA.requiredCSS = [
5527 url: NETDATA.themes.current.bootstrap_css,
5528 isAlreadyLoaded: function() {
5529 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5536 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5537 isAlreadyLoaded: function() { return false; }
5540 url: NETDATA.themes.current.dashboard_css,
5541 isAlreadyLoaded: function() { return false; }
5544 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5545 isAlreadyLoaded: function() { return false; }
5549 NETDATA.loadRequiredJs = function(index, callback) {
5550 if(index >= NETDATA.requiredJs.length) {
5551 if(typeof callback === 'function')
5556 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5557 NETDATA.loadRequiredJs(++index, callback);
5561 if(NETDATA.options.debug.main_loop === true)
5562 console.log('loading ' + NETDATA.requiredJs[index].url);
5565 url: NETDATA.requiredJs[index].url,
5568 xhrFields: { withCredentials: true } // required for the cookie
5571 if(NETDATA.options.debug.main_loop === true)
5572 console.log('loaded ' + NETDATA.requiredJs[index].url);
5574 NETDATA.loadRequiredJs(++index, callback);
5577 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5581 NETDATA.loadRequiredCSS = function(index) {
5582 if(index >= NETDATA.requiredCSS.length)
5585 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5586 NETDATA.loadRequiredCSS(++index);
5590 if(NETDATA.options.debug.main_loop === true)
5591 console.log('loading ' + NETDATA.requiredCSS[index].url);
5593 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5594 NETDATA.loadRequiredCSS(++index);
5598 // ----------------------------------------------------------------------------------------------------------------
5599 // Registry of netdata hosts
5602 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5603 chart_div_offset: 100, // give that space above the chart when scrolling to it
5604 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5605 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5607 ms_penalty: 0, // the time penalty of the next alarm
5608 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5609 // if alarms are shown faster than: one per 500ms
5611 notifications: false, // when true, the browser supports notifications (may not be granted though)
5612 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5613 // notifications_shown: new Array(),
5615 server: null, // the server to connect to for fetching alarms
5616 current: null, // the list of raised alarms - updated in the background
5617 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5619 notify: function(entry) {
5620 // console.log('alarm ' + entry.unique_id);
5622 if(entry.updated === true) {
5623 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5627 var value = entry.value;
5628 if(NETDATA.alarms.current !== null) {
5629 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5630 if(typeof t !== 'undefined' && entry.status == t.status)
5634 var name = entry.name.replace(/_/g, ' ');
5635 var status = entry.status.toLowerCase();
5636 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5637 var tag = entry.alarm_id;
5638 var icon = 'images/seo-performance-128.png';
5639 var interaction = false;
5643 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5645 switch(entry.status) {
5653 case 'UNINITIALIZED':
5657 if(NETDATA.alarms.last_notification_id === 0) {
5658 // console.log('alarm ' + entry.unique_id + ' is not current');
5661 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5662 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5665 title = name + ' back to normal';
5666 icon = 'images/check-mark-2-128-green.png'
5667 interaction = false;
5671 if(entry.old_status === 'CRITICAL')
5672 status = 'demoted to ' + entry.status.toLowerCase();
5674 icon = 'images/alert-128-orange.png';
5675 interaction = false;
5679 if(entry.old_status === 'WARNING')
5680 status = 'escalated to ' + entry.status.toLowerCase();
5682 icon = 'images/alert-128-red.png'
5687 console.log('invalid alarm status ' + entry.status);
5692 // cleanup old notifications with the same alarm_id as this one
5693 // FIXME: it does not seem to work on any web browser!
5694 var len = NETDATA.alarms.notifications_shown.length;
5696 var n = NETDATA.alarms.notifications_shown[len];
5697 if(n.data.alarm_id === entry.alarm_id) {
5698 console.log('removing old alarm ' + n.data.unique_id);
5700 // close the notification
5703 // remove it from the array
5704 NETDATA.alarms.notifications_shown.splice(len, 1);
5705 len = NETDATA.alarms.notifications_shown.length;
5712 setTimeout(function() {
5713 // show this notification
5714 // console.log('new notification: ' + title);
5715 var n = new Notification(title, {
5716 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5718 requireInteraction: interaction,
5719 icon: NETDATA.serverDefault + icon,
5723 n.onclick = function(event) {
5724 event.preventDefault();
5725 NETDATA.alarms.onclick(event.target.data);
5729 // NETDATA.alarms.notifications_shown.push(n);
5730 // console.log(entry);
5731 }, NETDATA.alarms.ms_penalty);
5733 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5737 scrollToChart: function(chart_id) {
5738 if(typeof chart_id === 'string') {
5739 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5740 if(typeof offset !== 'undefined') {
5741 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5748 scrollToAlarm: function(alarm) {
5749 if(typeof alarm === 'object') {
5750 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5752 if(ret === true && NETDATA.options.page_is_visible === false)
5754 // alert('netdata dashboard will now scroll to chart: ' + alarm.chart + '\n\nThis alarm opened to bring the browser window in front of the screen. Click on the dashboard to prevent it from appearing again.');
5759 notifyAll: function() {
5760 // console.log('FETCHING ALARM LOG');
5761 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5762 // console.log('ALARM LOG FETCHED');
5764 if(data === null || typeof data !== 'object') {
5765 console.log('invalid alarms log response');
5769 if(data.length === 0) {
5770 console.log('received empty alarm log');
5774 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5776 data.sort(function(a, b) {
5777 if(a.unique_id > b.unique_id) return -1;
5778 if(a.unique_id < b.unique_id) return 1;
5782 NETDATA.alarms.ms_penalty = 0;
5784 var len = data.length;
5786 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5787 NETDATA.alarms.notify(data[len]);
5790 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5793 NETDATA.alarms.last_notification_id = data[0].unique_id;
5794 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5798 check_notifications: function() {
5799 // returns true if we should fire 1+ notifications
5801 if(NETDATA.alarms.notifications !== true) {
5802 // console.log('notifications not available');
5806 if(Notification.permission !== 'granted') {
5807 // console.log('notifications not granted');
5811 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5812 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5814 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5815 // console.log('new alarms detected');
5818 //else console.log('no new alarms');
5820 // else console.log('cannot process alarms');
5825 get: function(what, callback) {
5827 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5830 xhrFields: { withCredentials: true } // required for the cookie
5832 .done(function(data) {
5833 if(typeof callback === 'function')
5837 NETDATA.error(415, NETDATA.alarms.server);
5839 if(typeof callback === 'function')
5844 update_forever: function() {
5845 NETDATA.alarms.get('active', function(data) {
5847 NETDATA.alarms.current = data;
5849 if(NETDATA.alarms.check_notifications() === true) {
5850 NETDATA.alarms.notifyAll();
5853 if (typeof NETDATA.alarms.callback === 'function') {
5854 NETDATA.alarms.callback(data);
5857 // Health monitoring is disabled on this netdata
5858 if(data.status === false) return;
5861 setTimeout(NETDATA.alarms.update_forever, 10000);
5865 get_log: function(last_id, callback) {
5866 // console.log('fetching all log after ' + last_id.toString());
5868 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5871 xhrFields: { withCredentials: true } // required for the cookie
5873 .done(function(data) {
5874 if(typeof callback === 'function')
5878 NETDATA.error(416, NETDATA.alarms.server);
5880 if(typeof callback === 'function')
5886 var host = NETDATA.serverDefault;
5887 while(host.slice(-1) === '/')
5888 host = host.substring(0, host.length - 1);
5889 NETDATA.alarms.server = host;
5891 if(NETDATA.alarms.onclick === null)
5892 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5894 if(netdataShowAlarms === true) {
5895 NETDATA.alarms.update_forever();
5897 if('Notification' in window) {
5898 // console.log('notifications available');
5899 NETDATA.alarms.notifications = true;
5901 if(Notification.permission === 'default')
5902 Notification.requestPermission();
5908 // ----------------------------------------------------------------------------------------------------------------
5909 // Registry of netdata hosts
5911 NETDATA.registry = {
5912 server: null, // the netdata registry server
5913 person_guid: null, // the unique ID of this browser / user
5914 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5915 hostname: null, // the hostname of the netdata server that served dashboard.js
5916 machines: null, // the user's other URLs
5917 machines_array: null, // the user's other URLs in an array
5920 parsePersonUrls: function(person_urls) {
5921 // console.log(person_urls);
5922 NETDATA.registry.person_urls = person_urls;
5925 NETDATA.registry.machines = {};
5926 NETDATA.registry.machines_array = new Array();
5928 var now = new Date().getTime();
5929 var apu = person_urls;
5932 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5933 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5939 accesses: apu[i][3],
5941 alternate_urls: new Array()
5943 obj.alternate_urls.push(apu[i][1]);
5945 NETDATA.registry.machines[apu[i][0]] = obj;
5946 NETDATA.registry.machines_array.push(obj);
5949 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5951 var pu = NETDATA.registry.machines[apu[i][0]];
5952 if(pu.last_t < apu[i][2]) {
5954 pu.last_t = apu[i][2];
5955 pu.name = apu[i][4];
5957 pu.accesses += apu[i][3];
5958 pu.alternate_urls.push(apu[i][1]);
5963 if(typeof netdataRegistryCallback === 'function')
5964 netdataRegistryCallback(NETDATA.registry.machines_array);
5968 if(netdataRegistry !== true) return;
5970 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5972 NETDATA.registry.server = data.registry;
5973 NETDATA.registry.machine_guid = data.machine_guid;
5974 NETDATA.registry.hostname = data.hostname;
5976 NETDATA.registry.access(2, function (person_urls) {
5977 NETDATA.registry.parsePersonUrls(person_urls);
5984 hello: function(host, callback) {
5985 while(host.slice(-1) === '/')
5986 host = host.substring(0, host.length - 1);
5988 // send HELLO to a netdata server:
5989 // 1. verifies the server is reachable
5990 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5992 url: host + '/api/v1/registry?action=hello',
5995 xhrFields: { withCredentials: true } // required for the cookie
5997 .done(function(data) {
5998 if(typeof data.status !== 'string' || data.status !== 'ok') {
5999 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6003 if(typeof callback === 'function')
6007 NETDATA.error(407, host);
6009 if(typeof callback === 'function')
6014 access: function(max_redirects, callback) {
6015 // send ACCESS to a netdata registry:
6016 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6017 // 2. it responds with a list of netdata servers we know
6018 // the registry identifies us using a cookie it sets the first time we access it
6019 // the registry may respond with a redirect URL to send us to another registry
6021 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),
6024 xhrFields: { withCredentials: true } // required for the cookie
6026 .done(function(data) {
6027 var redirect = null;
6028 if(typeof data.registry === 'string')
6029 redirect = data.registry;
6031 if(typeof data.status !== 'string' || data.status !== 'ok') {
6032 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6037 if(redirect !== null && max_redirects > 0) {
6038 NETDATA.registry.server = redirect;
6039 NETDATA.registry.access(max_redirects - 1, callback);
6042 if(typeof callback === 'function')
6047 if(typeof data.person_guid === 'string')
6048 NETDATA.registry.person_guid = data.person_guid;
6050 if(typeof callback === 'function')
6051 callback(data.urls);
6055 NETDATA.error(410, NETDATA.registry.server);
6057 if(typeof callback === 'function')
6062 delete: function(delete_url, callback) {
6063 // send DELETE to a netdata registry:
6065 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),
6068 xhrFields: { withCredentials: true } // required for the cookie
6070 .done(function(data) {
6071 if(typeof data.status !== 'string' || data.status !== 'ok') {
6072 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6076 if(typeof callback === 'function')
6080 NETDATA.error(412, NETDATA.registry.server);
6082 if(typeof callback === 'function')
6087 switch: function(new_person_guid, callback) {
6090 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,
6093 xhrFields: { withCredentials: true } // required for the cookie
6095 .done(function(data) {
6096 if(typeof data.status !== 'string' || data.status !== 'ok') {
6097 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6101 if(typeof callback === 'function')
6105 NETDATA.error(414, NETDATA.registry.server);
6107 if(typeof callback === 'function')
6113 // ----------------------------------------------------------------------------------------------------------------
6116 NETDATA.errorReset();
6117 NETDATA.loadRequiredCSS(0);
6119 NETDATA._loadjQuery(function() {
6120 NETDATA.loadRequiredJs(0, function() {
6121 if(typeof $().emulateTransitionEnd !== 'function') {
6122 // bootstrap is not available
6123 NETDATA.options.current.show_help = false;
6126 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6127 if(NETDATA.options.debug.main_loop === true)
6128 console.log('starting chart refresh thread');
6135 // window.NETDATA = NETDATA;
6136 // })(window, document);