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 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
33 var NETDATA = window.NETDATA || {};
35 (function(window, document, undefined) {
36 // ------------------------------------------------------------------------
37 // compatibility fixes
39 // fix IE issue with console
40 if(!window.console) { window.console = { log: function(){} }; }
42 // if string.endsWith is not defined, define it
43 if(typeof String.prototype.endsWith !== 'function') {
44 String.prototype.endsWith = function(s) {
45 if(s.length > this.length) return false;
46 return this.slice(-s.length) === s;
50 // if string.startsWith is not defined, define it
51 if(typeof String.prototype.startsWith !== 'function') {
52 String.prototype.startsWith = function(s) {
53 if(s.length > this.length) return false;
54 return this.slice(s.length) === s;
58 NETDATA.name2id = function(s) {
67 // ----------------------------------------------------------------------------------------------------------------
68 // Detect the netdata server
70 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
71 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
72 NETDATA._scriptSource = function() {
75 if(typeof document.currentScript !== 'undefined') {
76 script = document.currentScript;
79 var all_scripts = document.getElementsByTagName('script');
80 script = all_scripts[all_scripts.length - 1];
83 if (typeof script.getAttribute.length !== 'undefined')
86 script = script.getAttribute('src', -1);
91 if(typeof netdataServer !== 'undefined')
92 NETDATA.serverDefault = netdataServer;
94 var s = NETDATA._scriptSource();
95 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97 console.log('WARNING: Cannot detect the URL of the netdata server.');
98 NETDATA.serverDefault = null;
102 if(NETDATA.serverDefault === null)
103 NETDATA.serverDefault = '';
104 else if(NETDATA.serverDefault.slice(-1) !== '/')
105 NETDATA.serverDefault += '/';
107 // default URLs for all the external files we need
108 // make them RELATIVE so that the whole thing can also be
109 // installed under a web server
110 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
111 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
112 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
113 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
114 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
115 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
116 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
117 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
118 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
119 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
120 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
121 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
122 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
123 NETDATA.google_js = 'https://www.google.com/jsapi';
127 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
128 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161226-1',
129 background: '#FFFFFF',
130 foreground: '#000000',
133 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
134 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
135 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
136 '#329262', '#3B3EAC' ],
137 easypiechart_track: '#f0f0f0',
138 easypiechart_scale: '#dfe0e0',
139 gauge_pointer: '#C0C0C0',
140 gauge_stroke: '#F0F0F0',
141 gauge_gradient: false
144 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161218-2',
145 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161226-1',
146 background: '#272b30',
147 foreground: '#C8C8C8',
150 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
151 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
152 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
153 '#a6a479', '#a66da8' ],
155 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
156 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
157 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
158 '#329262', '#3B3EFF' ],
159 easypiechart_track: '#373b40',
160 easypiechart_scale: '#373b40',
161 gauge_pointer: '#474b50',
162 gauge_stroke: '#373b40',
163 gauge_gradient: false
167 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
168 NETDATA.themes.current = NETDATA.themes[netdataTheme];
170 NETDATA.themes.current = NETDATA.themes.white;
172 NETDATA.colors = NETDATA.themes.current.colors;
174 // these are the colors Google Charts are using
175 // we have them here to attempt emulate their look and feel on the other chart libraries
176 // http://there4.io/2012/05/02/google-chart-color-list/
177 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
178 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
179 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181 // an alternative set
182 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
183 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
184 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186 if(typeof netdataShowHelp === 'undefined')
187 netdataShowHelp = true;
189 if(typeof netdataShowAlarms === 'undefined')
190 netdataShowAlarms = false;
192 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
193 netdataRegistryAfterMs = 1500;
195 if(typeof netdataRegistry === 'undefined') {
196 // backward compatibility
197 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
198 netdataRegistry = true;
200 netdataRegistry = false;
202 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
203 netdataRegistry = true;
205 // ----------------------------------------------------------------------------------------------------------------
206 // the defaults for all charts
208 // if the user does not specify any of these, the following will be used
210 NETDATA.chartDefaults = {
211 host: NETDATA.serverDefault, // the server to get data from
212 width: '100%', // the chart width - can be null
213 height: '100%', // the chart height - can be null
214 min_width: null, // the chart minimum width - can be null
215 library: 'dygraph', // the graphing library to use
216 method: 'average', // the grouping method
217 before: 0, // panning
218 after: -600, // panning
219 pixels_per_point: 1, // the detail of the chart
220 fill_luminance: 0.8 // luminance of colors in solit areas
223 // ----------------------------------------------------------------------------------------------------------------
227 pauseCallback: null, // a callback when we are really paused
229 pause: false, // when enabled we don't auto-refresh the charts
231 targets: null, // an array of all the state objects that are
232 // currently active (independently of their
233 // viewport visibility)
235 updated_dom: true, // when true, the DOM has been updated with
236 // new elements we have to check.
238 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
239 // rendering charts continiously.
240 // used with .current.fast_render_timeframe
242 page_is_visible: true, // when true, this page is visible
244 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
245 // auto-refresher for some time (when a chart is
246 // performing pan or zoom, we need to stop refreshing
247 // all other charts, to have the maximum speed for
248 // rendering the chart that is panned or zoomed).
249 // Used with .current.global_pan_sync_time
251 last_resized: Date.now(), // the timestamp of the last resize request
253 last_page_scroll: 0, // the timestamp the last time the page was scrolled
255 // the current profile
256 // we may have many...
258 pixels_per_point: 1, // the minimum pixels per point for all charts
259 // increase this to speed javascript up
260 // each chart library has its own limit too
261 // the max of this and the chart library is used
262 // the final is calculated every time, so a change
263 // here will have immediate effect on the next chart
266 idle_between_charts: 100, // ms - how much time to wait between chart updates
268 fast_render_timeframe: 200, // ms - render continously until this time of continious
269 // rendering has been reached
270 // this setting is used to make it render e.g. 10
271 // charts at once, sleep idle_between_charts time
272 // and continue for another 10 charts.
274 idle_between_loops: 500, // ms - if all charts have been updated, wait this
275 // time before starting again.
277 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
279 idle_lost_focus: 500, // ms - when the window does not have focus, check
280 // if focus has been regained, every this time
282 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
283 // autorefreshing of charts is paused for this amount
286 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
287 // of time before setting up synchronized selections
290 sync_selection: true, // enable or disable selection sync
292 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
294 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
296 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298 update_only_visible: true, // enable or disable visibility management
300 parallel_refresher: true, // enable parallel refresh of charts
302 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304 destroy_on_hide: false, // destroy charts when they are not visible
306 show_help: netdataShowHelp, // when enabled the charts will show some help
307 show_help_delay_show_ms: 500,
308 show_help_delay_hide_ms: 0,
310 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
313 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
315 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
317 smooth_plot: true, // enable smooth plot, where possible
319 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321 color_fill_opacity_line: 1.0,
322 color_fill_opacity_area: 0.2,
323 color_fill_opacity_stacked: 0.8,
325 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
326 pan_and_zoom_factor_multiplier_control: 2.0,
327 pan_and_zoom_factor_multiplier_shift: 3.0,
328 pan_and_zoom_factor_multiplier_alt: 4.0,
330 abort_ajax_on_scroll: false, // kill pending ajax page scroll
331 async_on_scroll: false, // sync/async onscroll handler
332 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
336 setOptionCallback: function() { ; }
344 chart_data_url: false,
345 chart_errors: false, // FIXME
353 NETDATA.statistics = {
356 refreshes_active_max: 0
360 // ----------------------------------------------------------------------------------------------------------------
361 // local storage options
363 NETDATA.localStorage = {
366 callback: {} // only used for resetting back to defaults
369 NETDATA.localStorageGet = function(key, def, callback) {
372 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
373 NETDATA.localStorage.default[key.toString()] = def;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
379 // console.log('localStorage: loading "' + key.toString() + '"');
380 ret = localStorage.getItem(key.toString());
381 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
382 if(ret === null || ret === 'undefined') {
383 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
384 localStorage.setItem(key.toString(), JSON.stringify(def));
388 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
389 ret = JSON.parse(ret);
390 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
394 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
399 if(typeof ret === 'undefined' || ret === 'undefined') {
400 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
404 NETDATA.localStorage.current[key.toString()] = ret;
408 NETDATA.localStorageSet = function(key, value, callback) {
409 if(typeof value === 'undefined' || value === 'undefined') {
410 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
413 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
414 NETDATA.localStorage.default[key.toString()] = value;
415 NETDATA.localStorage.current[key.toString()] = value;
416 NETDATA.localStorage.callback[key.toString()] = callback;
419 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
420 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(value));
425 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
429 NETDATA.localStorage.current[key.toString()] = value;
433 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
435 if(typeof obj[i] === 'object') {
436 //console.log('object ' + prefix + '.' + i.toString());
437 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
441 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
445 NETDATA.setOption = function(key, value) {
446 if(key.toString() === 'setOptionCallback') {
447 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
448 NETDATA.options.current[key.toString()] = value;
449 NETDATA.options.current.setOptionCallback();
452 else if(NETDATA.options.current[key.toString()] !== value) {
453 var name = 'options.' + key.toString();
455 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
456 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
458 //console.log(NETDATA.localStorage);
459 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
460 //console.log(NETDATA.options);
461 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
463 if(typeof NETDATA.options.current.setOptionCallback === 'function')
464 NETDATA.options.current.setOptionCallback();
470 NETDATA.getOption = function(key) {
471 return NETDATA.options.current[key.toString()];
474 // read settings from local storage
475 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
477 // always start with this option enabled.
478 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
480 NETDATA.resetOptions = function() {
481 for(var i in NETDATA.localStorage.default) {
482 var a = i.split('.');
484 if(a[0] === 'options') {
485 if(a[1] === 'setOptionCallback') continue;
486 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
487 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
489 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
491 else if(a[0] === 'chart_heights') {
492 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
493 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
499 // ----------------------------------------------------------------------------------------------------------------
501 if(NETDATA.options.debug.main_loop === true)
502 console.log('welcome to NETDATA');
504 NETDATA.onresize = function() {
505 NETDATA.options.last_resized = Date.now();
509 NETDATA.onscroll_updater_count = 0;
510 NETDATA.onscroll_updater_running = false;
511 NETDATA.onscroll_updater_last_run = 0;
512 NETDATA.onscroll_updater_watchdog = null;
513 NETDATA.onscroll_updater_max_duration = 0;
514 NETDATA.onscroll_updater_above_threshold_count = 0;
515 NETDATA.onscroll_updater = function() {
516 NETDATA.onscroll_updater_running = true;
517 NETDATA.onscroll_updater_count++;
518 var start = Date.now();
520 var targets = NETDATA.options.targets;
521 var len = targets.length;
523 // when the user scrolls he sees that we have
524 // hidden all the not-visible charts
525 // using this little function we try to switch
526 // the charts back to visible quickly
529 if(NETDATA.options.abort_ajax_on_scroll === true) {
530 // we have to cancel pending requests too
533 if (targets[len]._updating === true) {
534 if (typeof targets[len].xhr !== 'undefined') {
535 targets[len].xhr.abort();
536 targets[len].running = false;
537 targets[len]._updating = false;
539 targets[len].isVisible();
544 // just find which chart is visible
547 targets[len].isVisible();
550 var end = Date.now();
551 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
553 if(NETDATA.options.current.async_on_scroll === false) {
554 var dt = end - start;
555 if(dt > NETDATA.onscroll_updater_max_duration) {
556 // console.log('max onscroll event handler duration increased to ' + dt);
557 NETDATA.onscroll_updater_max_duration = dt;
560 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
561 // console.log('slow: ' + dt);
562 NETDATA.onscroll_updater_above_threshold_count++;
564 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
565 NETDATA.setOption('async_on_scroll', true);
566 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
571 NETDATA.onscroll_updater_last_run = start;
572 NETDATA.onscroll_updater_running = false;
575 NETDATA.onscroll = function() {
576 // console.log('onscroll');
578 NETDATA.options.last_page_scroll = Date.now();
579 NETDATA.options.auto_refresher_stop_until = 0;
581 if(NETDATA.options.targets === null) return;
583 if(NETDATA.options.current.async_on_scroll === true) {
585 if(NETDATA.onscroll_updater_running === false) {
586 NETDATA.onscroll_updater_running = true;
587 setTimeout(NETDATA.onscroll_updater, 0);
590 if(NETDATA.onscroll_updater_watchdog !== null)
591 clearTimeout(NETDATA.onscroll_updater_watchdog);
593 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
594 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
595 // console.log('watchdog');
596 NETDATA.onscroll_updater();
599 NETDATA.onscroll_updater_watchdog = null;
605 NETDATA.onscroll_updater();
609 window.onresize = NETDATA.onresize;
610 window.onscroll = NETDATA.onscroll;
612 // ----------------------------------------------------------------------------------------------------------------
615 NETDATA.errorCodes = {
616 100: { message: "Cannot load chart library", alert: true },
617 101: { message: "Cannot load jQuery", alert: true },
618 402: { message: "Chart library not found", alert: false },
619 403: { message: "Chart library not enabled/is failed", alert: false },
620 404: { message: "Chart not found", alert: false },
621 405: { message: "Cannot download charts index from server", alert: true },
622 406: { message: "Invalid charts index downloaded from server", alert: true },
623 407: { message: "Cannot HELLO netdata server", alert: false },
624 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
625 409: { message: "Cannot ACCESS netdata registry", alert: false },
626 410: { message: "Netdata registry ACCESS failed", alert: false },
627 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
628 412: { message: "Netdata registry DELETE failed", alert: false },
629 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
630 414: { message: "Netdata registry SWITCH failed", alert: false },
631 415: { message: "Netdata alarms download failed", alert: false },
632 416: { message: "Netdata alarms log download failed", alert: false },
633 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
634 418: { message: "Netdata registry SEARCH failed", alert: false }
636 NETDATA.errorLast = {
642 NETDATA.error = function(code, msg) {
643 NETDATA.errorLast.code = code;
644 NETDATA.errorLast.message = msg;
645 NETDATA.errorLast.datetime = Date.now();
647 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
650 if(typeof netdataErrorCallback === 'function') {
651 ret = netdataErrorCallback('system', code, msg);
654 if(ret && NETDATA.errorCodes[code].alert)
655 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
658 NETDATA.errorReset = function() {
659 NETDATA.errorLast.code = 0;
660 NETDATA.errorLast.message = "You are doing fine!";
661 NETDATA.errorLast.datetime = 0;
664 // ----------------------------------------------------------------------------------------------------------------
665 // commonMin & commonMax
667 NETDATA.commonMin = {
671 get: function(state) {
672 if(typeof state.__commonMin === 'undefined') {
673 // get the commonMin setting
674 var self = $(state.element);
675 state.__commonMin = self.data('common-min') || null;
678 var min = state.data.min;
679 var name = state.__commonMin;
682 // we don't need commonMin
683 //state.log('no need for commonMin');
687 var t = this.keys[name];
688 if(typeof t === 'undefined') {
690 this.keys[name] = {};
694 var uuid = state.uuid;
695 if(typeof t[uuid] !== 'undefined') {
696 if(t[uuid] === min) {
697 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
698 return this.latest[name];
700 else if(min < this.latest[name]) {
701 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
703 this.latest[name] = min;
711 // find the common min
714 if(t[i] < m) m = t[i];
716 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
717 this.latest[name] = m;
722 NETDATA.commonMax = {
726 get: function(state) {
727 if(typeof state.__commonMax === 'undefined') {
728 // get the commonMax setting
729 var self = $(state.element);
730 state.__commonMax = self.data('common-max') || null;
733 var max = state.data.max;
734 var name = state.__commonMax;
737 // we don't need commonMax
738 //state.log('no need for commonMax');
742 var t = this.keys[name];
743 if(typeof t === 'undefined') {
745 this.keys[name] = {};
749 var uuid = state.uuid;
750 if(typeof t[uuid] !== 'undefined') {
751 if(t[uuid] === max) {
752 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
753 return this.latest[name];
755 else if(max > this.latest[name]) {
756 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
758 this.latest[name] = max;
766 // find the common max
769 if(t[i] > m) m = t[i];
771 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
772 this.latest[name] = m;
777 // ----------------------------------------------------------------------------------------------------------------
780 // When multiple charts need the same chart, we avoid downloading it
781 // multiple times (and having it in browser memory multiple time)
782 // by using this registry.
784 // Every time we download a chart definition, we save it here with .add()
785 // Then we try to get it back with .get(). If that fails, we download it.
787 NETDATA.chartRegistry = {
790 fixid: function(id) {
791 return id.replace(/:/g, "_").replace(/\//g, "_");
794 add: function(host, id, data) {
795 host = this.fixid(host);
798 if(typeof this.charts[host] === 'undefined')
799 this.charts[host] = {};
801 //console.log('added ' + host + '/' + id);
802 this.charts[host][id] = data;
805 get: function(host, id) {
806 host = this.fixid(host);
809 if(typeof this.charts[host] === 'undefined')
812 if(typeof this.charts[host][id] === 'undefined')
815 //console.log('cached ' + host + '/' + id);
816 return this.charts[host][id];
819 downloadAll: function(host, callback) {
820 while(host.slice(-1) === '/')
821 host = host.substring(0, host.length - 1);
826 url: host + '/api/v1/charts',
829 xhrFields: { withCredentials: true } // required for the cookie
831 .done(function(data) {
833 var h = NETDATA.chartRegistry.fixid(host);
834 self.charts[h] = data.charts;
836 else NETDATA.error(406, host + '/api/v1/charts');
838 if(typeof callback === 'function')
842 NETDATA.error(405, host + '/api/v1/charts');
844 if(typeof callback === 'function')
850 // ----------------------------------------------------------------------------------------------------------------
851 // Global Pan and Zoom on charts
853 // Using this structure are synchronize all the charts, so that
854 // when you pan or zoom one, all others are automatically refreshed
855 // to the same timespan.
857 NETDATA.globalPanAndZoom = {
858 seq: 0, // timestamp ms
859 // every time a chart is panned or zoomed
860 // we set the timestamp here
861 // then we use it as a sequence number
862 // to find if other charts are syncronized
865 master: null, // the master chart (state), to which all others
868 force_before_ms: null, // the timespan to sync all other charts
869 force_after_ms: null,
874 setMaster: function(state, after, before) {
875 if(NETDATA.options.current.sync_pan_and_zoom === false)
878 if(this.master !== null && this.master !== state)
879 this.master.resetChart(true, true);
881 var now = Date.now();
884 this.force_after_ms = after;
885 this.force_before_ms = before;
886 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
888 if(typeof this.callback === 'function')
889 this.callback(true, after, before);
893 clearMaster: function() {
894 if(this.master !== null) {
895 var st = this.master;
902 this.force_after_ms = null;
903 this.force_before_ms = null;
904 NETDATA.options.auto_refresher_stop_until = 0;
906 if(typeof this.callback === 'function')
907 this.callback(false, 0, 0);
910 // is the given state the master of the global
911 // pan and zoom sync?
912 isMaster: function(state) {
913 if(this.master === state) return true;
917 // are we currently have a global pan and zoom sync?
918 isActive: function() {
919 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
923 // check if a chart, other than the master
924 // needs to be refreshed, due to the global pan and zoom
925 shouldBeAutoRefreshed: function(state) {
926 if(this.master === null || this.seq === 0)
929 //if(state.needsRecreation())
932 if(state.tm.pan_and_zoom_seq === this.seq)
939 // ----------------------------------------------------------------------------------------------------------------
940 // dimensions selection
943 // move color assignment to dimensions, here
945 dimensionStatus = function(parent, label, name_div, value_div, color) {
946 this.enabled = false;
947 this.parent = parent;
949 this.name_div = null;
950 this.value_div = null;
951 this.color = NETDATA.themes.current.foreground;
953 if(parent.unselected_count === 0)
954 this.selected = true;
956 this.selected = false;
958 this.setOptions(name_div, value_div, color);
961 dimensionStatus.prototype.invalidate = function() {
962 this.name_div = null;
963 this.value_div = null;
964 this.enabled = false;
967 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
970 if(this.name_div != name_div) {
971 this.name_div = name_div;
972 this.name_div.title = this.label;
973 this.name_div.style.color = this.color;
974 if(this.selected === false)
975 this.name_div.className = 'netdata-legend-name not-selected';
977 this.name_div.className = 'netdata-legend-name selected';
980 if(this.value_div != value_div) {
981 this.value_div = value_div;
982 this.value_div.title = this.label;
983 this.value_div.style.color = this.color;
984 if(this.selected === false)
985 this.value_div.className = 'netdata-legend-value not-selected';
987 this.value_div.className = 'netdata-legend-value selected';
994 dimensionStatus.prototype.setHandler = function() {
995 if(this.enabled === false) return;
999 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1000 this.name_div.onclick = this.value_div.onclick = function(e) {
1002 if(ds.isSelected()) {
1004 if(e.shiftKey === true || e.ctrlKey === true) {
1005 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1008 if(ds.parent.countSelected() === 0)
1009 ds.parent.selectAll();
1012 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1013 if(ds.parent.countSelected() === 1) {
1014 ds.parent.selectAll();
1017 ds.parent.selectNone();
1023 // this is not selected
1024 if(e.shiftKey === true || e.ctrlKey === true) {
1025 // control or shift key is pressed -> select this too
1029 // no key is pressed -> select only this
1030 ds.parent.selectNone();
1035 ds.parent.state.redrawChart();
1039 dimensionStatus.prototype.select = function() {
1040 if(this.enabled === false) return;
1042 this.name_div.className = 'netdata-legend-name selected';
1043 this.value_div.className = 'netdata-legend-value selected';
1044 this.selected = true;
1047 dimensionStatus.prototype.unselect = function() {
1048 if(this.enabled === false) return;
1050 this.name_div.className = 'netdata-legend-name not-selected';
1051 this.value_div.className = 'netdata-legend-value hidden';
1052 this.selected = false;
1055 dimensionStatus.prototype.isSelected = function() {
1056 return(this.enabled === true && this.selected === true);
1059 // ----------------------------------------------------------------------------------------------------------------
1061 dimensionsVisibility = function(state) {
1064 this.dimensions = {};
1065 this.selected_count = 0;
1066 this.unselected_count = 0;
1069 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1070 if(typeof this.dimensions[label] === 'undefined') {
1072 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1075 this.dimensions[label].setOptions(name_div, value_div, color);
1077 return this.dimensions[label];
1080 dimensionsVisibility.prototype.dimensionGet = function(label) {
1081 return this.dimensions[label];
1084 dimensionsVisibility.prototype.invalidateAll = function() {
1085 for(var d in this.dimensions)
1086 this.dimensions[d].invalidate();
1089 dimensionsVisibility.prototype.selectAll = function() {
1090 for(var d in this.dimensions)
1091 this.dimensions[d].select();
1094 dimensionsVisibility.prototype.countSelected = function() {
1096 for(var d in this.dimensions)
1097 if(this.dimensions[d].isSelected()) i++;
1102 dimensionsVisibility.prototype.selectNone = function() {
1103 for(var d in this.dimensions)
1104 this.dimensions[d].unselect();
1107 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1108 var ret = new Array();
1109 this.selected_count = 0;
1110 this.unselected_count = 0;
1112 var len = array.length;
1114 var ds = this.dimensions[array[len]];
1115 if(typeof ds === 'undefined') {
1116 // console.log(array[i] + ' is not found');
1119 else if(ds.isSelected()) {
1121 this.selected_count++;
1125 this.unselected_count++;
1129 if(this.selected_count === 0 && this.unselected_count !== 0) {
1131 return this.selected2BooleanArray(array);
1138 // ----------------------------------------------------------------------------------------------------------------
1139 // global selection sync
1141 NETDATA.globalSelectionSync = {
1143 dont_sync_before: 0,
1148 if(this.state !== null)
1149 this.state.globalSelectionSyncStop();
1153 if(this.state !== null) {
1154 this.state.globalSelectionSyncDelay();
1159 // ----------------------------------------------------------------------------------------------------------------
1160 // Our state object, where all per-chart values are stored
1162 chartState = function(element) {
1163 var self = $(element);
1164 this.element = element;
1167 // all private functions should use 'that', instead of 'this'
1170 /* error() - private
1171 * show an error instead of the chart
1173 var error = function(msg) {
1176 if(typeof netdataErrorCallback === 'function') {
1177 ret = netdataErrorCallback('chart', that.id, msg);
1181 that.element.innerHTML = that.id + ': ' + msg;
1182 that.enabled = false;
1183 that.current = that.pan;
1187 // GUID - a unique identifier for the chart
1188 this.uuid = NETDATA.guid();
1190 // string - the name of chart
1191 this.id = self.data('netdata');
1193 // string - the key for localStorage settings
1194 this.settings_id = self.data('id') || null;
1196 // the user given dimensions of the element
1197 this.width = self.data('width') || NETDATA.chartDefaults.width;
1198 this.height = self.data('height') || NETDATA.chartDefaults.height;
1199 this.height_original = this.height;
1201 if(this.settings_id !== null) {
1202 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1203 // this is the callback that will be called
1204 // if and when the user resets all localStorage variables
1205 // to their defaults
1207 resizeChartToHeight(height);
1211 // string - the netdata server URL, without any path
1212 this.host = self.data('host') || NETDATA.chartDefaults.host;
1214 // make sure the host does not end with /
1215 // all netdata API requests use absolute paths
1216 while(this.host.slice(-1) === '/')
1217 this.host = this.host.substring(0, this.host.length - 1);
1219 // string - the grouping method requested by the user
1220 this.method = self.data('method') || NETDATA.chartDefaults.method;
1222 // the time-range requested by the user
1223 this.after = self.data('after') || NETDATA.chartDefaults.after;
1224 this.before = self.data('before') || NETDATA.chartDefaults.before;
1226 // the pixels per point requested by the user
1227 this.pixels_per_point = self.data('pixels-per-point') || 1;
1228 this.points = self.data('points') || null;
1230 // the dimensions requested by the user
1231 this.dimensions = self.data('dimensions') || null;
1233 // the chart library requested by the user
1234 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1236 // how many retries we have made to load chart data from the server
1237 this.retries_on_data_failures = 0;
1239 // object - the chart library used
1240 this.library = null;
1244 this.colors_assigned = {};
1245 this.colors_available = null;
1247 // the element already created by the user
1248 this.element_message = null;
1250 // the element with the chart
1251 this.element_chart = null;
1253 // the element with the legend of the chart (if created by us)
1254 this.element_legend = null;
1255 this.element_legend_childs = {
1260 perfect_scroller: null, // the container to apply perfect scroller to
1264 this.chart_url = null; // string - the url to download chart info
1265 this.chart = null; // object - the chart as downloaded from the server
1267 this.title = self.data('title') || null; // the title of the chart
1268 this.units = self.data('units') || null; // the units of the chart dimensions
1269 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1270 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1272 this.running = false; // boolean - true when the chart is being refreshed now
1273 this.validated = false; // boolean - has the chart been validated?
1274 this.enabled = true; // boolean - is the chart enabled for refresh?
1275 this.paused = false; // boolean - is the chart paused for any reason?
1276 this.selected = false; // boolean - is the chart shown a selection?
1277 this.debug = false; // boolean - console.log() debug info about this chart
1279 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1280 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1281 this.requested_after = null; // milliseconds - the timestamp of the request after param
1282 this.requested_before = null; // milliseconds - the timestamp of the request before param
1283 this.requested_padding = null;
1284 this.view_after = 0;
1285 this.view_before = 0;
1287 this.value_decimal_detail = -1;
1289 var d = self.data('decimal-digits');
1290 if(typeof d === 'number') {
1291 this.value_decimal_detail = 1;
1293 this.value_decimal_detail *= 10;
1300 force_update_at: 0, // the timestamp to force the update at
1301 force_before_ms: null,
1302 force_after_ms: null
1307 force_update_at: 0, // the timestamp to force the update at
1308 force_before_ms: null,
1309 force_after_ms: null
1314 force_update_at: 0, // the timestamp to force the update at
1315 force_before_ms: null,
1316 force_after_ms: null
1319 // this is a pointer to one of the sub-classes below
1321 this.current = this.auto;
1323 // check the requested library is available
1324 // we don't initialize it here - it will be initialized when
1325 // this chart will be first used
1326 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1327 NETDATA.error(402, that.library_name);
1328 error('chart library "' + that.library_name + '" is not found');
1331 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1332 NETDATA.error(403, that.library_name);
1333 error('chart library "' + that.library_name + '" is not enabled');
1337 that.library = NETDATA.chartLibraries[that.library_name];
1339 // milliseconds - the time the last refresh took
1340 this.refresh_dt_ms = 0;
1342 // if we need to report the rendering speed
1343 // find the element that needs to be updated
1344 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1346 if(refresh_dt_element_name !== null)
1347 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1349 this.refresh_dt_element = null;
1351 this.dimensions_visibility = new dimensionsVisibility(this);
1353 this._updating = false;
1355 // ============================================================================================================
1356 // PRIVATE FUNCTIONS
1358 var createDOM = function() {
1359 if(that.enabled === false) return;
1361 if(that.element_message !== null) that.element_message.innerHTML = '';
1362 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1363 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1365 that.element.innerHTML = '';
1367 that.element_message = document.createElement('div');
1368 that.element_message.className = ' netdata-message hidden';
1369 that.element.appendChild(that.element_message);
1371 that.element_chart = document.createElement('div');
1372 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1373 that.element.appendChild(that.element_chart);
1375 if(that.hasLegend() === true) {
1376 that.element.className = "netdata-container-with-legend";
1377 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1379 that.element_legend = document.createElement('div');
1380 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1381 that.element.appendChild(that.element_legend);
1384 that.element.className = "netdata-container";
1385 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1387 that.element_legend = null;
1389 that.element_legend_childs.series = null;
1391 if(typeof(that.width) === 'string')
1392 $(that.element).css('width', that.width);
1393 else if(typeof(that.width) === 'number')
1394 $(that.element).css('width', that.width + 'px');
1396 if(typeof(that.library.aspect_ratio) === 'undefined') {
1397 if(typeof(that.height) === 'string')
1398 $(that.element).css('height', that.height);
1399 else if(typeof(that.height) === 'number')
1400 $(that.element).css('height', that.height + 'px');
1403 var w = that.element.offsetWidth;
1404 if(w === null || w === 0) {
1405 // the div is hidden
1406 // this will resize the chart when next viewed
1407 that.tm.last_resized = 0;
1410 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1413 if(NETDATA.chartDefaults.min_width !== null)
1414 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1416 that.tm.last_dom_created = Date.now();
1422 * initialize state variables
1423 * destroy all (possibly) created state elements
1424 * create the basic DOM for a chart
1426 var init = function() {
1427 if(that.enabled === false) return;
1429 that.paused = false;
1430 that.selected = false;
1432 that.chart_created = false; // boolean - is the library.create() been called?
1433 that.updates_counter = 0; // numeric - the number of refreshes made so far
1434 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1435 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1438 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1439 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1440 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1442 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1443 last_updated: 0, // the timestamp the chart last updated with data
1444 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1446 // Used with NETDATA.globalPanAndZoom.seq
1447 last_visible_check: 0, // the time we last checked if it is visible
1448 last_resized: 0, // the time the chart was resized
1449 last_hidden: 0, // the time the chart was hidden
1450 last_unhidden: 0, // the time the chart was unhidden
1451 last_autorefreshed: 0 // the time the chart was last refreshed
1454 that.data = null; // the last data as downloaded from the netdata server
1455 that.data_url = 'invalid://'; // string - the last url used to update the chart
1456 that.data_points = 0; // number - the number of points returned from netdata
1457 that.data_after = 0; // milliseconds - the first timestamp of the data
1458 that.data_before = 0; // milliseconds - the last timestamp of the data
1459 that.data_update_every = 0; // milliseconds - the frequency to update the data
1461 that.tm.last_initialized = Date.now();
1464 that.setMode('auto');
1467 var maxMessageFontSize = function() {
1468 // normally we want a font size, as tall as the element
1469 var h = that.element_message.clientHeight;
1471 // but give it some air, 20% let's say, or 5 pixels min
1472 var lost = Math.max(h * 0.2, 5);
1475 // center the text, vertically
1476 var paddingTop = (lost - 5) / 2;
1478 // but check the width too
1479 // it should fit 10 characters in it
1480 var w = that.element_message.clientWidth / 10;
1482 paddingTop += (h - w) / 2;
1486 // and don't make it too huge
1487 // 5% of the screen size is good
1488 if(h > screen.height / 20) {
1489 paddingTop += (h - (screen.height / 20)) / 2;
1490 h = screen.height / 20;
1494 that.element_message.style.fontSize = h.toString() + 'px';
1495 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1498 var showMessage = function(msg) {
1499 that.element_message.className = 'netdata-message';
1500 that.element_message.innerHTML = msg;
1501 that.element_message.style.fontSize = 'x-small';
1502 that.element_message.style.paddingTop = '0px';
1503 that.___messageHidden___ = undefined;
1506 var showMessageIcon = function(icon) {
1507 that.element_message.innerHTML = icon;
1508 that.element_message.className = 'netdata-message icon';
1509 maxMessageFontSize();
1510 that.___messageHidden___ = undefined;
1513 var hideMessage = function() {
1514 if(typeof that.___messageHidden___ === 'undefined') {
1515 that.___messageHidden___ = true;
1516 that.element_message.className = 'netdata-message hidden';
1520 var showRendering = function() {
1522 if(that.chart !== null) {
1523 if(that.chart.chart_type === 'line')
1524 icon = '<i class="fa fa-line-chart"></i>';
1526 icon = '<i class="fa fa-area-chart"></i>';
1529 icon = '<i class="fa fa-area-chart"></i>';
1531 showMessageIcon(icon + ' netdata');
1534 var showLoading = function() {
1535 if(that.chart_created === false) {
1536 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1542 var isHidden = function() {
1543 if(typeof that.___chartIsHidden___ !== 'undefined')
1549 // hide the chart, when it is not visible - called from isVisible()
1550 var hideChart = function() {
1551 // hide it, if it is not already hidden
1552 if(isHidden() === true) return;
1554 if(that.chart_created === true) {
1555 if(NETDATA.options.current.destroy_on_hide === true) {
1556 // we should destroy it
1561 that.element_chart.style.display = 'none';
1562 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1563 that.tm.last_hidden = Date.now();
1566 // This works, but I not sure there are no corner cases somewhere
1567 // so it is commented - if the user has memory issues he can
1568 // set Destroy on Hide for all charts
1569 // that.data = null;
1573 that.___chartIsHidden___ = true;
1576 // unhide the chart, when it is visible - called from isVisible()
1577 var unhideChart = function() {
1578 if(isHidden() === false) return;
1580 that.___chartIsHidden___ = undefined;
1581 that.updates_since_last_unhide = 0;
1583 if(that.chart_created === false) {
1584 // we need to re-initialize it, to show our background
1585 // logo in bootstrap tabs, until the chart loads
1589 that.tm.last_unhidden = Date.now();
1590 that.element_chart.style.display = '';
1591 if(that.element_legend !== null) that.element_legend.style.display = '';
1597 var canBeRendered = function() {
1598 if(isHidden() === true || that.isVisible(true) === false)
1604 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1605 var callChartLibraryUpdateSafely = function(data) {
1608 if(canBeRendered() === false)
1611 if(NETDATA.options.debug.chart_errors === true)
1612 status = that.library.update(that, data);
1615 status = that.library.update(that, data);
1622 if(status === false) {
1623 error('chart failed to be updated as ' + that.library_name);
1630 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1631 var callChartLibraryCreateSafely = function(data) {
1634 if(canBeRendered() === false)
1637 if(NETDATA.options.debug.chart_errors === true)
1638 status = that.library.create(that, data);
1641 status = that.library.create(that, data);
1648 if(status === false) {
1649 error('chart failed to be created as ' + that.library_name);
1653 that.chart_created = true;
1654 that.updates_since_last_creation = 0;
1658 // ----------------------------------------------------------------------------------------------------------------
1661 // resizeChart() - private
1662 // to be called just before the chart library to make sure that
1663 // a properly sized dom is available
1664 var resizeChart = function() {
1665 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1666 if(that.chart_created === false) return;
1668 if(that.needsRecreation()) {
1671 else if(typeof that.library.resize === 'function') {
1672 that.library.resize(that);
1674 if(that.element_legend_childs.perfect_scroller !== null)
1675 Ps.update(that.element_legend_childs.perfect_scroller);
1677 maxMessageFontSize();
1680 that.tm.last_resized = Date.now();
1684 // this is the actual chart resize algorithm
1686 // - resize the entire container
1687 // - update the internal states
1688 // - resize the chart as the div changes height
1689 // - update the scrollbar of the legend
1690 var resizeChartToHeight = function(h) {
1692 that.element.style.height = h;
1694 if(that.settings_id !== null)
1695 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1697 var now = Date.now();
1698 NETDATA.options.last_page_scroll = now;
1699 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1702 that.tm.last_resized = 0;
1706 this.resizeHandler = function(e) {
1709 if(typeof this.event_resize === 'undefined'
1710 || this.event_resize.chart_original_w === 'undefined'
1711 || this.event_resize.chart_original_h === 'undefined')
1712 this.event_resize = {
1713 chart_original_w: this.element.clientWidth,
1714 chart_original_h: this.element.clientHeight,
1718 if(e.type === 'touchstart') {
1719 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1720 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1723 this.event_resize.mouse_start_x = e.clientX;
1724 this.event_resize.mouse_start_y = e.clientY;
1727 this.event_resize.chart_start_w = this.element.clientWidth;
1728 this.event_resize.chart_start_h = this.element.clientHeight;
1729 this.event_resize.chart_last_w = this.element.clientWidth;
1730 this.event_resize.chart_last_h = this.element.clientHeight;
1732 var now = Date.now();
1733 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller != null) {
1734 // double click / double tap event
1736 // console.dir(this.element_legend_childs.content);
1737 // console.dir(this.element_legend_childs.perfect_scroller);
1739 // the optimal height of the chart
1740 // showing the entire legend
1741 var optimal = this.event_resize.chart_last_h
1742 + this.element_legend_childs.perfect_scroller.scrollHeight
1743 - this.element_legend_childs.perfect_scroller.clientHeight;
1745 // if we are not optimal, be optimal
1746 if(this.event_resize.chart_last_h != optimal) {
1747 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1748 resizeChartToHeight(optimal.toString() + 'px');
1751 // else if the current height is not the original/saved height
1752 // reset to the original/saved height
1753 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h) {
1754 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1755 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1758 // else if the current height is not the internal default height
1759 // reset to the internal default height
1760 else if((this.event_resize.chart_last_h.toString() + 'px') != this.height_original) {
1761 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1762 resizeChartToHeight(this.height_original.toString());
1765 // else if the current height is not the firstchild's clientheight
1767 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1768 var parent_rect = this.element.getBoundingClientRect();
1769 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1770 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1772 // console.log(parent_rect);
1773 // console.log(content_rect);
1774 // console.log(wanted);
1776 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1777 if(this.event_resize.chart_last_h != wanted)
1778 resizeChartToHeight(wanted.toString() + 'px');
1782 this.event_resize.last = now;
1784 // process movement event
1785 document.onmousemove =
1786 document.ontouchmove =
1787 this.element_legend_childs.resize_handler.onmousemove =
1788 this.element_legend_childs.resize_handler.ontouchmove =
1793 case 'mousemove': y = e.clientY; break;
1794 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1798 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1800 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1801 resizeChartToHeight(newH.toString() + 'px');
1802 that.event_resize.chart_last_h = newH;
1807 // process end event
1808 document.onmouseup =
1809 document.ontouchend =
1810 this.element_legend_childs.resize_handler.onmouseup =
1811 this.element_legend_childs.resize_handler.ontouchend =
1813 // remove all the hooks
1814 document.onmouseup =
1815 document.onmousemove =
1816 document.ontouchmove =
1817 document.ontouchend =
1818 that.element_legend_childs.resize_handler.onmousemove =
1819 that.element_legend_childs.resize_handler.ontouchmove =
1820 that.element_legend_childs.resize_handler.onmouseout =
1821 that.element_legend_childs.resize_handler.onmouseup =
1822 that.element_legend_childs.resize_handler.ontouchend =
1825 // allow auto-refreshes
1826 NETDATA.options.auto_refresher_stop_until = 0;
1832 var noDataToShow = function() {
1833 showMessageIcon('<i class="fa fa-warning"></i> empty');
1834 that.legendUpdateDOM();
1835 that.tm.last_autorefreshed = Date.now();
1836 // that.data_update_every = 30 * 1000;
1837 //that.element_chart.style.display = 'none';
1838 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1839 //that.___chartIsHidden___ = true;
1842 // ============================================================================================================
1845 this.error = function(msg) {
1849 this.setMode = function(m) {
1850 if(this.current !== null && this.current.name === m) return;
1853 this.current = this.auto;
1854 else if(m === 'pan')
1855 this.current = this.pan;
1856 else if(m === 'zoom')
1857 this.current = this.zoom;
1859 this.current = this.auto;
1861 this.current.force_update_at = 0;
1862 this.current.force_before_ms = null;
1863 this.current.force_after_ms = null;
1865 this.tm.last_mode_switch = Date.now();
1868 // ----------------------------------------------------------------------------------------------------------------
1869 // global selection sync
1871 // prevent to global selection sync for some time
1872 this.globalSelectionSyncDelay = function(ms) {
1873 if(NETDATA.options.current.sync_selection === false)
1876 if(typeof ms === 'number')
1877 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1879 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1882 // can we globally apply selection sync?
1883 this.globalSelectionSyncAbility = function() {
1884 if(NETDATA.options.current.sync_selection === false)
1887 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1893 this.globalSelectionSyncIsMaster = function() {
1894 if(NETDATA.globalSelectionSync.state === this)
1900 // this chart is the master of the global selection sync
1901 this.globalSelectionSyncBeMaster = function() {
1903 if(this.globalSelectionSyncIsMaster()) {
1904 if(this.debug === true)
1905 this.log('sync: I am the master already.');
1910 if(NETDATA.globalSelectionSync.state) {
1911 if(this.debug === true)
1912 this.log('sync: I am not the sync master. Resetting global sync.');
1914 this.globalSelectionSyncStop();
1917 // become the master
1918 if(this.debug === true)
1919 this.log('sync: becoming sync master.');
1921 this.selected = true;
1922 NETDATA.globalSelectionSync.state = this;
1924 // find the all slaves
1925 var targets = NETDATA.options.targets;
1926 var len = targets.length;
1931 if(this.debug === true)
1932 st.log('sync: not adding me to sync');
1934 else if(st.globalSelectionSyncIsEligible()) {
1935 if(this.debug === true)
1936 st.log('sync: adding to sync as slave');
1938 st.globalSelectionSyncBeSlave();
1942 // this.globalSelectionSyncDelay(100);
1945 // can the chart participate to the global selection sync as a slave?
1946 this.globalSelectionSyncIsEligible = function() {
1947 if(this.enabled === true
1948 && this.library !== null
1949 && typeof this.library.setSelection === 'function'
1950 && this.isVisible() === true
1951 && this.chart_created === true)
1957 // this chart becomes a slave of the global selection sync
1958 this.globalSelectionSyncBeSlave = function() {
1959 if(NETDATA.globalSelectionSync.state !== this)
1960 NETDATA.globalSelectionSync.slaves.push(this);
1963 // sync all the visible charts to the given time
1964 // this is to be called from the chart libraries
1965 this.globalSelectionSync = function(t) {
1966 if(this.globalSelectionSyncAbility() === false) {
1967 if(this.debug === true)
1968 this.log('sync: cannot sync (yet?).');
1973 if(this.globalSelectionSyncIsMaster() === false) {
1974 if(this.debug === true)
1975 this.log('sync: trying to be sync master.');
1977 this.globalSelectionSyncBeMaster();
1979 if(this.globalSelectionSyncAbility() === false) {
1980 if(this.debug === true)
1981 this.log('sync: cannot sync (yet?).');
1987 NETDATA.globalSelectionSync.last_t = t;
1988 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1993 // stop syncing all charts to the given time
1994 this.globalSelectionSyncStop = function() {
1995 if(NETDATA.globalSelectionSync.slaves.length) {
1996 if(this.debug === true)
1997 this.log('sync: cleaning up...');
1999 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2001 if(that.debug === true)
2002 st.log('sync: not adding me to sync stop');
2005 if(that.debug === true)
2006 st.log('sync: removed slave from sync');
2008 st.clearSelection();
2012 NETDATA.globalSelectionSync.last_t = 0;
2013 NETDATA.globalSelectionSync.slaves = [];
2014 NETDATA.globalSelectionSync.state = null;
2017 this.clearSelection();
2020 this.setSelection = function(t) {
2021 if(typeof this.library.setSelection === 'function') {
2022 if(this.library.setSelection(this, t) === true)
2023 this.selected = true;
2025 this.selected = false;
2027 else this.selected = true;
2029 if(this.selected === true && this.debug === true)
2030 this.log('selection set to ' + t.toString());
2032 return this.selected;
2035 this.clearSelection = function() {
2036 if(this.selected === true) {
2037 if(typeof this.library.clearSelection === 'function') {
2038 if(this.library.clearSelection(this) === true)
2039 this.selected = false;
2041 this.selected = true;
2043 else this.selected = false;
2045 if(this.selected === false && this.debug === true)
2046 this.log('selection cleared');
2051 return this.selected;
2054 // find if a timestamp (ms) is shown in the current chart
2055 this.timeIsVisible = function(t) {
2056 if(t >= this.data_after && t <= this.data_before)
2061 this.calculateRowForTime = function(t) {
2062 if(this.timeIsVisible(t) === false) return -1;
2063 return Math.floor((t - this.data_after) / this.data_update_every);
2066 // ----------------------------------------------------------------------------------------------------------------
2069 this.log = function(msg) {
2070 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2073 this.pauseChart = function() {
2074 if(this.paused === false) {
2075 if(this.debug === true)
2076 this.log('pauseChart()');
2082 this.unpauseChart = function() {
2083 if(this.paused === true) {
2084 if(this.debug === true)
2085 this.log('unpauseChart()');
2087 this.paused = false;
2091 this.resetChart = function(dont_clear_master, dont_update) {
2092 if(this.debug === true)
2093 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2095 if(typeof dont_clear_master === 'undefined')
2096 dont_clear_master = false;
2098 if(typeof dont_update === 'undefined')
2099 dont_update = false;
2101 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2102 if(this.debug === true)
2103 this.log('resetChart() diverting to clearMaster().');
2104 // this will call us back with master === true
2105 NETDATA.globalPanAndZoom.clearMaster();
2109 this.clearSelection();
2111 this.tm.pan_and_zoom_seq = 0;
2113 this.setMode('auto');
2114 this.current.force_update_at = 0;
2115 this.current.force_before_ms = null;
2116 this.current.force_after_ms = null;
2117 this.tm.last_autorefreshed = 0;
2118 this.paused = false;
2119 this.selected = false;
2120 this.enabled = true;
2121 // this.debug = false;
2123 // do not update the chart here
2124 // or the chart will flip-flop when it is the master
2125 // of a selection sync and another chart becomes
2128 if(dont_update !== true && this.isVisible() === true) {
2133 this.updateChartPanOrZoom = function(after, before) {
2134 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2137 if(this.debug === true)
2140 if(before < after) {
2141 if(this.debug === true)
2142 this.log(logme + 'flipped parameters, rejecting it.');
2147 if(typeof this.fixed_min_duration === 'undefined')
2148 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2150 var min_duration = this.fixed_min_duration;
2151 var current_duration = Math.round(this.view_before - this.view_after);
2153 // round the numbers
2154 after = Math.round(after);
2155 before = Math.round(before);
2157 // align them to update_every
2158 // stretching them further away
2159 after -= after % this.data_update_every;
2160 before += this.data_update_every - (before % this.data_update_every);
2162 // the final wanted duration
2163 var wanted_duration = before - after;
2165 // to allow panning, accept just a point below our minimum
2166 if((current_duration - this.data_update_every) < min_duration)
2167 min_duration = current_duration - this.data_update_every;
2169 // we do it, but we adjust to minimum size and return false
2170 // when the wanted size is below the current and the minimum
2172 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2173 if(this.debug === true)
2174 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2176 min_duration = this.fixed_min_duration;
2178 var dt = (min_duration - wanted_duration) / 2;
2181 wanted_duration = before - after;
2185 var tolerance = this.data_update_every * 2;
2186 var movement = Math.abs(before - this.view_before);
2188 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2189 if(this.debug === true)
2190 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);
2194 if(this.current.name === 'auto') {
2195 this.log(logme + 'caller called me with mode: ' + this.current.name);
2196 this.setMode('pan');
2199 if(this.debug === true)
2200 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);
2202 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2203 this.current.force_after_ms = after;
2204 this.current.force_before_ms = before;
2205 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2209 this.legendFormatValue = function(value) {
2210 if(value === null || value === 'undefined') return '-';
2211 if(typeof value !== 'number') return value;
2213 if(this.value_decimal_detail !== -1)
2214 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2216 var abs = Math.abs(value);
2217 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2218 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2219 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2220 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2221 return (Math.round(value * 10000) / 10000).toLocaleString();
2224 this.legendSetLabelValue = function(label, value) {
2225 var series = this.element_legend_childs.series[label];
2226 if(typeof series === 'undefined') return;
2227 if(series.value === null && series.user === null) return;
2230 // this slows down firefox and edge significantly
2231 // since it requires to use innerHTML(), instead of innerText()
2233 // if the value has not changed, skip DOM update
2234 //if(series.last === value) return;
2237 if(typeof value === 'number') {
2238 var v = Math.abs(value);
2239 s = r = this.legendFormatValue(value);
2241 if(typeof series.last === 'number') {
2242 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2243 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2244 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2246 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2256 series.last = value;
2260 var s = this.legendFormatValue(value);
2262 // caching: do not update the update to show the same value again
2263 if(s === series.last_shown_value) return;
2264 series.last_shown_value = s;
2266 if(series.value !== null) series.value.innerText = s;
2267 if(series.user !== null) series.user.innerText = s;
2270 this.__legendSetDateString = function(date) {
2271 if(date !== this.__last_shown_legend_date) {
2272 this.element_legend_childs.title_date.innerText = date;
2273 this.__last_shown_legend_date = date;
2277 this.__legendSetTimeString = function(time) {
2278 if(time !== this.__last_shown_legend_time) {
2279 this.element_legend_childs.title_time.innerText = time;
2280 this.__last_shown_legend_time = time;
2284 this.__legendSetUnitsString = function(units) {
2285 if(units !== this.__last_shown_legend_units) {
2286 this.element_legend_childs.title_units.innerText = units;
2287 this.__last_shown_legend_units = units;
2291 this.legendSetDate = function(ms) {
2292 if(typeof ms !== 'number') {
2293 this.legendShowUndefined();
2297 var d = new Date(ms);
2299 if(this.element_legend_childs.title_date)
2300 this.__legendSetDateString(d.toLocaleDateString());
2302 if(this.element_legend_childs.title_time)
2303 this.__legendSetTimeString(d.toLocaleTimeString());
2305 if(this.element_legend_childs.title_units)
2306 this.__legendSetUnitsString(this.units)
2309 this.legendShowUndefined = function() {
2310 if(this.element_legend_childs.title_date)
2311 this.__legendSetDateString(' ');
2313 if(this.element_legend_childs.title_time)
2314 this.__legendSetTimeString(this.chart.name);
2316 if(this.element_legend_childs.title_units)
2317 this.__legendSetUnitsString(' ')
2319 if(this.data && this.element_legend_childs.series !== null) {
2320 var labels = this.data.dimension_names;
2321 var i = labels.length;
2323 var label = labels[i];
2325 if(typeof label === 'undefined') continue;
2326 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2327 this.legendSetLabelValue(label, null);
2332 this.legendShowLatestValues = function() {
2333 if(this.chart === null) return;
2334 if(this.selected) return;
2336 if(this.data === null || this.element_legend_childs.series === null) {
2337 this.legendShowUndefined();
2341 var show_undefined = true;
2342 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2343 show_undefined = false;
2345 if(show_undefined) {
2346 this.legendShowUndefined();
2350 this.legendSetDate(this.view_before);
2352 var labels = this.data.dimension_names;
2353 var i = labels.length;
2355 var label = labels[i];
2357 if(typeof label === 'undefined') continue;
2358 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2361 this.legendSetLabelValue(label, null);
2363 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2367 this.legendReset = function() {
2368 this.legendShowLatestValues();
2371 // this should be called just ONCE per dimension per chart
2372 this._chartDimensionColor = function(label) {
2373 if(this.colors === null) this.chartColors();
2375 if(typeof this.colors_assigned[label] === 'undefined') {
2376 if(this.colors_available.length === 0) {
2377 var len = NETDATA.themes.current.colors.length;
2379 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2382 this.colors_assigned[label] = this.colors_available.shift();
2384 if(this.debug === true)
2385 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2388 if(this.debug === true)
2389 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2392 this.colors.push(this.colors_assigned[label]);
2393 return this.colors_assigned[label];
2396 this.chartColors = function() {
2397 if(this.colors !== null) return this.colors;
2399 this.colors = new Array();
2400 this.colors_available = new Array();
2402 // add the standard colors
2403 var len = NETDATA.themes.current.colors.length;
2405 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2407 // add the user supplied colors
2408 var c = $(this.element).data('colors');
2409 // this.log('read colors: ' + c);
2410 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2411 if(typeof c !== 'string') {
2412 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2422 this.colors_available.unshift(c[len]);
2423 // this.log('adding color: ' + c[len]);
2432 this.legendUpdateDOM = function() {
2435 // check that the legend DOM is up to date for the downloaded dimensions
2436 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2437 // this.log('the legend does not have any series - requesting legend update');
2440 else if(this.data === null) {
2441 // this.log('the chart does not have any data - requesting legend update');
2444 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2448 var labels = this.data.dimension_names.toString();
2449 if(labels !== this.element_legend_childs.series.labels_key) {
2452 if(this.debug === true)
2453 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2457 if(needed === false) {
2458 // make sure colors available
2461 // do we have to update the current values?
2462 // we do this, only when the visible chart is current
2463 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2464 if(this.debug === true)
2465 this.log('chart is in latest position... updating values on legend...');
2467 //var labels = this.data.dimension_names;
2468 //var i = labels.length;
2470 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2474 if(this.colors === null) {
2475 // this is the first time we update the chart
2476 // let's assign colors to all dimensions
2477 if(this.library.track_colors() === true)
2478 for(var dim in this.chart.dimensions)
2479 this._chartDimensionColor(this.chart.dimensions[dim].name);
2481 // we will re-generate the colors for the chart
2482 // based on the selected dimensions
2485 if(this.debug === true)
2486 this.log('updating Legend DOM');
2488 // mark all dimensions as invalid
2489 this.dimensions_visibility.invalidateAll();
2491 var genLabel = function(state, parent, dim, name, count) {
2492 var color = state._chartDimensionColor(name);
2494 var user_element = null;
2495 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2496 if(user_id === null)
2497 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2498 if(user_id !== null) {
2499 user_element = document.getElementById(user_id) || null;
2500 if (user_element === null)
2501 state.log('Cannot find element with id: ' + user_id);
2504 state.element_legend_childs.series[name] = {
2505 name: document.createElement('span'),
2506 value: document.createElement('span'),
2509 last_shown_value: null
2512 var label = state.element_legend_childs.series[name];
2514 // create the dimension visibility tracking for this label
2515 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2517 var rgb = NETDATA.colorHex2Rgb(color);
2518 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2519 + state.chart.chart_type
2520 + '" style="background-color: '
2521 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2522 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2524 var text = document.createTextNode(' ' + name);
2525 label.name.appendChild(text);
2528 parent.appendChild(document.createElement('br'));
2530 parent.appendChild(label.name);
2531 parent.appendChild(label.value);
2534 var content = document.createElement('div');
2536 if(this.hasLegend()) {
2537 this.element_legend_childs = {
2539 resize_handler: document.createElement('div'),
2540 toolbox: document.createElement('div'),
2541 toolbox_left: document.createElement('div'),
2542 toolbox_right: document.createElement('div'),
2543 toolbox_reset: document.createElement('div'),
2544 toolbox_zoomin: document.createElement('div'),
2545 toolbox_zoomout: document.createElement('div'),
2546 toolbox_volume: document.createElement('div'),
2547 title_date: document.createElement('span'),
2548 title_time: document.createElement('span'),
2549 title_units: document.createElement('span'),
2550 perfect_scroller: document.createElement('div'),
2554 this.element_legend.innerHTML = '';
2556 if(this.library.toolboxPanAndZoom !== null) {
2558 function get_pan_and_zoom_step(event) {
2560 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2562 else if (event.shiftKey)
2563 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2565 else if (event.altKey)
2566 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2569 return NETDATA.options.current.pan_and_zoom_factor;
2572 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2573 this.element.appendChild(this.element_legend_childs.toolbox);
2575 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2576 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2577 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2578 this.element_legend_childs.toolbox_left.onclick = function(e) {
2581 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2582 var before = that.view_before - step;
2583 var after = that.view_after - step;
2584 if(after >= that.netdata_first)
2585 that.library.toolboxPanAndZoom(that, after, before);
2587 if(NETDATA.options.current.show_help === true)
2588 $(this.element_legend_childs.toolbox_left).popover({
2593 placement: 'bottom',
2594 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2596 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>'
2600 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2601 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2602 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2603 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2605 NETDATA.resetAllCharts(that);
2607 if(NETDATA.options.current.show_help === true)
2608 $(this.element_legend_childs.toolbox_reset).popover({
2613 placement: 'bottom',
2614 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2615 title: 'Chart Reset',
2616 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>'
2619 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2620 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2621 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2622 this.element_legend_childs.toolbox_right.onclick = function(e) {
2624 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2625 var before = that.view_before + step;
2626 var after = that.view_after + step;
2627 if(before <= that.netdata_last)
2628 that.library.toolboxPanAndZoom(that, after, before);
2630 if(NETDATA.options.current.show_help === true)
2631 $(this.element_legend_childs.toolbox_right).popover({
2636 placement: 'bottom',
2637 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2639 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>'
2643 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2644 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2645 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2646 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2648 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2649 var before = that.view_before - dt;
2650 var after = that.view_after + dt;
2651 that.library.toolboxPanAndZoom(that, after, before);
2653 if(NETDATA.options.current.show_help === true)
2654 $(this.element_legend_childs.toolbox_zoomin).popover({
2659 placement: 'bottom',
2660 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2661 title: 'Chart Zoom In',
2662 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>'
2665 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2666 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2667 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2668 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2670 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);
2671 var before = that.view_before + dt;
2672 var after = that.view_after - dt;
2674 that.library.toolboxPanAndZoom(that, after, before);
2676 if(NETDATA.options.current.show_help === true)
2677 $(this.element_legend_childs.toolbox_zoomout).popover({
2682 placement: 'bottom',
2683 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2684 title: 'Chart Zoom Out',
2685 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>'
2688 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2689 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2690 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2691 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2692 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2693 //e.preventDefault();
2694 //alert('clicked toolbox_volume on ' + that.id);
2698 this.element_legend_childs.toolbox = null;
2699 this.element_legend_childs.toolbox_left = null;
2700 this.element_legend_childs.toolbox_reset = null;
2701 this.element_legend_childs.toolbox_right = null;
2702 this.element_legend_childs.toolbox_zoomin = null;
2703 this.element_legend_childs.toolbox_zoomout = null;
2704 this.element_legend_childs.toolbox_volume = null;
2707 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2708 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2709 this.element.appendChild(this.element_legend_childs.resize_handler);
2710 if(NETDATA.options.current.show_help === true)
2711 $(this.element_legend_childs.resize_handler).popover({
2716 placement: 'bottom',
2717 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2718 title: 'Chart Resize',
2719 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>'
2723 this.element_legend_childs.resize_handler.onmousedown =
2725 that.resizeHandler(e);
2729 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2730 that.resizeHandler(e);
2733 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2734 this.element_legend.appendChild(this.element_legend_childs.title_date);
2736 this.element_legend.appendChild(document.createElement('br'));
2738 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2739 this.element_legend.appendChild(this.element_legend_childs.title_time);
2741 this.element_legend.appendChild(document.createElement('br'));
2743 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2744 this.element_legend.appendChild(this.element_legend_childs.title_units);
2746 this.element_legend.appendChild(document.createElement('br'));
2748 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2749 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2751 content.className = 'netdata-legend-series-content';
2752 this.element_legend_childs.perfect_scroller.appendChild(content);
2754 if(NETDATA.options.current.show_help === true)
2755 $(content).popover({
2760 placement: 'bottom',
2761 title: 'Chart Legend',
2762 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2763 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>'
2767 this.element_legend_childs = {
2769 resize_handler: null,
2772 toolbox_right: null,
2773 toolbox_reset: null,
2774 toolbox_zoomin: null,
2775 toolbox_zoomout: null,
2776 toolbox_volume: null,
2780 perfect_scroller: null,
2786 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2787 if(this.debug === true)
2788 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2790 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2791 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2795 var tmp = new Array();
2796 for(var dim in this.chart.dimensions) {
2797 tmp.push(this.chart.dimensions[dim].name);
2798 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2800 this.element_legend_childs.series.labels_key = tmp.toString();
2801 if(this.debug === true)
2802 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2805 // create a hidden div to be used for hidding
2806 // the original legend of the chart library
2807 var el = document.createElement('div');
2808 if(this.element_legend !== null)
2809 this.element_legend.appendChild(el);
2810 el.style.display = 'none';
2812 this.element_legend_childs.hidden = document.createElement('div');
2813 el.appendChild(this.element_legend_childs.hidden);
2815 if(this.element_legend_childs.perfect_scroller !== null) {
2816 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2818 wheelPropagation: true,
2819 swipePropagation: true,
2820 minScrollbarLength: null,
2821 maxScrollbarLength: null,
2822 useBothWheelAxes: false,
2823 suppressScrollX: true,
2824 suppressScrollY: false,
2825 scrollXMarginOffset: 0,
2826 scrollYMarginOffset: 0,
2829 Ps.update(this.element_legend_childs.perfect_scroller);
2832 this.legendShowLatestValues();
2835 this.hasLegend = function() {
2836 if(typeof this.___hasLegendCache___ !== 'undefined')
2837 return this.___hasLegendCache___;
2840 if(this.library && this.library.legend(this) === 'right-side') {
2841 var legend = $(this.element).data('legend') || 'yes';
2842 if(legend === 'yes') leg = true;
2845 this.___hasLegendCache___ = leg;
2849 this.legendWidth = function() {
2850 return (this.hasLegend())?140:0;
2853 this.legendHeight = function() {
2854 return $(this.element).height();
2857 this.chartWidth = function() {
2858 return $(this.element).width() - this.legendWidth();
2861 this.chartHeight = function() {
2862 return $(this.element).height();
2865 this.chartPixelsPerPoint = function() {
2866 // force an options provided detail
2867 var px = this.pixels_per_point;
2869 if(this.library && px < this.library.pixels_per_point(this))
2870 px = this.library.pixels_per_point(this);
2872 if(px < NETDATA.options.current.pixels_per_point)
2873 px = NETDATA.options.current.pixels_per_point;
2878 this.needsRecreation = function() {
2880 this.chart_created === true
2882 && this.library.autoresize() === false
2883 && this.tm.last_resized < NETDATA.options.last_resized
2887 this.chartURL = function() {
2888 var after, before, points_multiplier = 1;
2889 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2890 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2892 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2893 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2894 this.view_after = after * 1000;
2895 this.view_before = before * 1000;
2897 this.requested_padding = null;
2898 points_multiplier = 1;
2900 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2901 this.tm.pan_and_zoom_seq = 0;
2903 before = Math.round(this.current.force_before_ms / 1000);
2904 after = Math.round(this.current.force_after_ms / 1000);
2905 this.view_after = after * 1000;
2906 this.view_before = before * 1000;
2908 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2909 this.requested_padding = Math.round((before - after) / 2);
2910 after -= this.requested_padding;
2911 before += this.requested_padding;
2912 this.requested_padding *= 1000;
2913 points_multiplier = 2;
2916 this.current.force_before_ms = null;
2917 this.current.force_after_ms = null;
2920 this.tm.pan_and_zoom_seq = 0;
2922 before = this.before;
2924 this.view_after = after * 1000;
2925 this.view_before = before * 1000;
2927 this.requested_padding = null;
2928 points_multiplier = 1;
2931 this.requested_after = after * 1000;
2932 this.requested_before = before * 1000;
2934 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2936 // build the data URL
2937 this.data_url = this.host + this.chart.data_url;
2938 this.data_url += "&format=" + this.library.format();
2939 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2940 this.data_url += "&group=" + this.method;
2942 if(this.override_options !== null)
2943 this.data_url += "&options=" + this.override_options.toString();
2945 this.data_url += "&options=" + this.library.options(this);
2947 this.data_url += '|jsonwrap';
2949 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2950 this.data_url += '|nonzero';
2952 if(this.append_options !== null)
2953 this.data_url += '|' + this.append_options.toString();
2956 this.data_url += "&after=" + after.toString();
2959 this.data_url += "&before=" + before.toString();
2962 this.data_url += "&dimensions=" + this.dimensions;
2964 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2965 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2968 this.redrawChart = function() {
2969 if(this.data !== null)
2970 this.updateChartWithData(this.data);
2973 this.updateChartWithData = function(data) {
2974 if(this.debug === true)
2975 this.log('updateChartWithData() called.');
2977 // this may force the chart to be re-created
2981 this.updates_counter++;
2982 this.updates_since_last_unhide++;
2983 this.updates_since_last_creation++;
2985 var started = Date.now();
2987 // if the result is JSON, find the latest update-every
2988 this.data_update_every = data.view_update_every * 1000;
2989 this.data_after = data.after * 1000;
2990 this.data_before = data.before * 1000;
2991 this.netdata_first = data.first_entry * 1000;
2992 this.netdata_last = data.last_entry * 1000;
2993 this.data_points = data.points;
2996 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2997 if(this.view_after < this.data_after) {
2998 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2999 this.view_after = this.data_after;
3002 if(this.view_before > this.data_before) {
3003 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3004 this.view_before = this.data_before;
3008 this.view_after = this.data_after;
3009 this.view_before = this.data_before;
3012 if(this.debug === true) {
3013 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3015 if(this.current.force_after_ms)
3016 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3018 this.log('STATUS: forced : unset');
3020 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3021 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3022 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3023 this.log('STATUS: points : ' + (this.data_points).toString());
3026 if(this.data_points === 0) {
3031 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3032 if(this.debug === true)
3033 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3035 this.chart_created = false;
3038 // check and update the legend
3039 this.legendUpdateDOM();
3041 if(this.chart_created === true
3042 && typeof this.library.update === 'function') {
3044 if(this.debug === true)
3045 this.log('updating chart...');
3047 if(callChartLibraryUpdateSafely(data) === false)
3051 if(this.debug === true)
3052 this.log('creating chart...');
3054 if(callChartLibraryCreateSafely(data) === false)
3058 this.legendShowLatestValues();
3059 if(this.selected === true)
3060 NETDATA.globalSelectionSync.stop();
3062 // update the performance counters
3063 var now = Date.now();
3064 this.tm.last_updated = now;
3066 // don't update last_autorefreshed if this chart is
3067 // forced to be updated with global PanAndZoom
3068 if(NETDATA.globalPanAndZoom.isActive())
3069 this.tm.last_autorefreshed = 0;
3071 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3072 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3074 this.tm.last_autorefreshed = now;
3077 this.refresh_dt_ms = now - started;
3078 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3080 if(this.refresh_dt_element !== null)
3081 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3084 this.updateChart = function(callback) {
3085 if(this.debug === true)
3086 this.log('updateChart() called.');
3088 if(this._updating === true) {
3089 if(this.debug === true)
3090 this.log('I am already updating...');
3092 if(typeof callback === 'function') callback();
3096 // due to late initialization of charts and libraries
3097 // we need to check this too
3098 if(this.enabled === false) {
3099 if(this.debug === true)
3100 this.log('I am not enabled');
3102 if(typeof callback === 'function') callback();
3106 if(canBeRendered() === false) {
3107 if(typeof callback === 'function') callback();
3111 if(this.chart === null) {
3112 this.getChart(function() { that.updateChart(callback); });
3116 if(this.library.initialized === false) {
3117 if(this.library.enabled === true) {
3118 this.library.initialize(function() { that.updateChart(callback); });
3122 error('chart library "' + this.library_name + '" is not available.');
3123 if(typeof callback === 'function') callback();
3128 this.clearSelection();
3131 if(this.debug === true)
3132 this.log('updating from ' + this.data_url);
3134 NETDATA.statistics.refreshes_total++;
3135 NETDATA.statistics.refreshes_active++;
3137 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3138 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3140 this._updating = true;
3142 this.xhr = $.ajax( {
3147 'Cache-Control': 'no-cache, no-store',
3148 'Pragma': 'no-cache'
3150 xhrFields: { withCredentials: true } // required for the cookie
3152 .done(function(data) {
3153 that.xhr = undefined;
3154 that.retries_on_data_failures = 0;
3156 if(that.debug === true)
3157 that.log('data received. updating chart.');
3159 that.updateChartWithData(data);
3161 .fail(function(msg) {
3162 that.xhr = undefined;
3164 if(msg.statusText !== 'abort') {
3165 that.retries_on_data_failures++;
3166 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3167 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3168 that.retries_on_data_failures = 0;
3169 error('data download failed for url: ' + that.data_url);
3172 that.tm.last_autorefreshed = Date.now();
3173 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3177 .always(function() {
3178 that.xhr = undefined;
3180 NETDATA.statistics.refreshes_active--;
3181 that._updating = false;
3182 if(typeof callback === 'function') callback();
3188 this.isVisible = function(nocache) {
3189 if(typeof nocache === 'undefined')
3192 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3194 // caching - we do not evaluate the charts visibility
3195 // if the page has not been scrolled since the last check
3196 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3197 return this.___isVisible___;
3199 this.tm.last_visible_check = Date.now();
3201 var wh = window.innerHeight;
3202 var x = this.element.getBoundingClientRect();
3206 if(x.width === 0 || x.height === 0) {
3208 this.___isVisible___ = false;
3209 return this.___isVisible___;
3212 if(x.top < 0 && -x.top > x.height) {
3213 // the chart is entirely above
3214 ret = -x.top - x.height;
3216 else if(x.top > wh) {
3217 // the chart is entirely below
3221 if(ret > tolerance) {
3222 // the chart is too far
3225 this.___isVisible___ = false;
3226 return this.___isVisible___;
3229 // the chart is inside or very close
3232 this.___isVisible___ = true;
3233 return this.___isVisible___;
3237 this.isAutoRefreshable = function() {
3238 return (this.current.autorefresh);
3241 this.canBeAutoRefreshed = function() {
3242 var now = Date.now();
3244 if(this.running === true) {
3245 if(this.debug === true)
3246 this.log('I am already running');
3251 if(this.enabled === false) {
3252 if(this.debug === true)
3253 this.log('I am not enabled');
3258 if(this.library === null || this.library.enabled === false) {
3259 error('charting library "' + this.library_name + '" is not available');
3260 if(this.debug === true)
3261 this.log('My chart library ' + this.library_name + ' is not available');
3266 if(this.isVisible() === false) {
3267 if(NETDATA.options.debug.visibility === true || this.debug === true)
3268 this.log('I am not visible');
3273 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3274 if(this.debug === true)
3275 this.log('timed force update detected - allowing this update');
3277 this.current.force_update_at = 0;
3281 if(this.isAutoRefreshable() === true) {
3282 // allow the first update, even if the page is not visible
3283 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3284 if(NETDATA.options.debug.focus === true || this.debug === true)
3285 this.log('canBeAutoRefreshed(): page does not have focus');
3290 if(this.needsRecreation() === true) {
3291 if(this.debug === true)
3292 this.log('canBeAutoRefreshed(): needs re-creation.');
3297 // options valid only for autoRefresh()
3298 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3299 if(NETDATA.globalPanAndZoom.isActive()) {
3300 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3301 if(this.debug === true)
3302 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3307 if(this.debug === true)
3308 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3314 if(this.selected === true) {
3315 if(this.debug === true)
3316 this.log('canBeAutoRefreshed(): I have a selection in place.');
3321 if(this.paused === true) {
3322 if(this.debug === true)
3323 this.log('canBeAutoRefreshed(): I am paused.');
3328 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3329 if(this.debug === true)
3330 this.log('canBeAutoRefreshed(): It is time to update me.');
3340 this.autoRefresh = function(callback) {
3341 if(this.canBeAutoRefreshed() === true && this.running === false) {
3344 state.running = true;
3345 state.updateChart(function() {
3346 state.running = false;
3348 if(typeof callback !== 'undefined')
3353 if(typeof callback !== 'undefined')
3358 this._defaultsFromDownloadedChart = function(chart) {
3360 this.chart_url = chart.url;
3361 this.data_update_every = chart.update_every * 1000;
3362 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3363 this.tm.last_info_downloaded = Date.now();
3365 if(this.title === null)
3366 this.title = chart.title;
3368 if(this.units === null)
3369 this.units = chart.units;
3372 // fetch the chart description from the netdata server
3373 this.getChart = function(callback) {
3374 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3376 this._defaultsFromDownloadedChart(this.chart);
3377 if(typeof callback === 'function') callback();
3380 this.chart_url = "/api/v1/chart?chart=" + this.id;
3382 if(this.debug === true)
3383 this.log('downloading ' + this.chart_url);
3386 url: this.host + this.chart_url,
3389 xhrFields: { withCredentials: true } // required for the cookie
3391 .done(function(chart) {
3392 chart.url = that.chart_url;
3393 that._defaultsFromDownloadedChart(chart);
3394 NETDATA.chartRegistry.add(that.host, that.id, chart);
3397 NETDATA.error(404, that.chart_url);
3398 error('chart not found on url "' + that.chart_url + '"');
3400 .always(function() {
3401 if(typeof callback === 'function') callback();
3406 // ============================================================================================================
3412 NETDATA.resetAllCharts = function(state) {
3413 // first clear the global selection sync
3414 // to make sure no chart is in selected state
3415 state.globalSelectionSyncStop();
3417 // there are 2 possibilities here
3418 // a. state is the global Pan and Zoom master
3419 // b. state is not the global Pan and Zoom master
3421 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3424 // clear the global Pan and Zoom
3425 // this will also refresh the master
3426 // and unblock any charts currently mirroring the master
3427 NETDATA.globalPanAndZoom.clearMaster();
3429 // if we were not the master, reset our status too
3430 // this is required because most probably the mouse
3431 // is over this chart, blocking it from auto-refreshing
3432 if(master === false && (state.paused === true || state.selected === true))
3436 // get or create a chart state, given a DOM element
3437 NETDATA.chartState = function(element) {
3438 var state = $(element).data('netdata-state-object') || null;
3439 if(state === null) {
3440 state = new chartState(element);
3441 $(element).data('netdata-state-object', state);
3446 // ----------------------------------------------------------------------------------------------------------------
3447 // Library functions
3449 // Load a script without jquery
3450 // This is used to load jquery - after it is loaded, we use jquery
3451 NETDATA._loadjQuery = function(callback) {
3452 if(typeof jQuery === 'undefined') {
3453 if(NETDATA.options.debug.main_loop === true)
3454 console.log('loading ' + NETDATA.jQuery);
3456 var script = document.createElement('script');
3457 script.type = 'text/javascript';
3458 script.async = true;
3459 script.src = NETDATA.jQuery;
3461 // script.onabort = onError;
3462 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3463 if(typeof callback === "function")
3464 script.onload = callback;
3466 var s = document.getElementsByTagName('script')[0];
3467 s.parentNode.insertBefore(script, s);
3469 else if(typeof callback === "function")
3473 NETDATA._loadCSS = function(filename) {
3474 // don't use jQuery here
3475 // styles are loaded before jQuery
3476 // to eliminate showing an unstyled page to the user
3478 var fileref = document.createElement("link");
3479 fileref.setAttribute("rel", "stylesheet");
3480 fileref.setAttribute("type", "text/css");
3481 fileref.setAttribute("href", filename);
3483 if (typeof fileref !== 'undefined')
3484 document.getElementsByTagName("head")[0].appendChild(fileref);
3487 NETDATA.colorHex2Rgb = function(hex) {
3488 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3489 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3490 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3491 return r + r + g + g + b + b;
3494 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3496 r: parseInt(result[1], 16),
3497 g: parseInt(result[2], 16),
3498 b: parseInt(result[3], 16)
3502 NETDATA.colorLuminance = function(hex, lum) {
3503 // validate hex string
3504 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3506 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3510 // convert to decimal and change luminosity
3511 var rgb = "#", c, i;
3512 for (i = 0; i < 3; i++) {
3513 c = parseInt(hex.substr(i*2,2), 16);
3514 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3515 rgb += ("00"+c).substr(c.length);
3521 NETDATA.guid = function() {
3523 return Math.floor((1 + Math.random()) * 0x10000)
3528 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3531 NETDATA.zeropad = function(x) {
3532 if(x > -10 && x < 10) return '0' + x.toString();
3533 else return x.toString();
3536 // user function to signal us the DOM has been
3538 NETDATA.updatedDom = function() {
3539 NETDATA.options.updated_dom = true;
3542 NETDATA.ready = function(callback) {
3543 NETDATA.options.pauseCallback = callback;
3546 NETDATA.pause = function(callback) {
3547 if(NETDATA.options.pause === true)
3550 NETDATA.options.pauseCallback = callback;
3553 NETDATA.unpause = function() {
3554 NETDATA.options.pauseCallback = null;
3555 NETDATA.options.updated_dom = true;
3556 NETDATA.options.pause = false;
3559 // ----------------------------------------------------------------------------------------------------------------
3561 // this is purely sequencial charts refresher
3562 // it is meant to be autonomous
3563 NETDATA.chartRefresherNoParallel = function(index) {
3564 if(NETDATA.options.debug.mail_loop === true)
3565 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3567 if(NETDATA.options.updated_dom === true) {
3568 // the dom has been updated
3569 // get the dom parts again
3570 NETDATA.parseDom(NETDATA.chartRefresher);
3573 if(index >= NETDATA.options.targets.length) {
3574 if(NETDATA.options.debug.main_loop === true)
3575 console.log('waiting to restart main loop...');
3577 NETDATA.options.auto_refresher_fast_weight = 0;
3579 setTimeout(function() {
3580 NETDATA.chartRefresher();
3581 }, NETDATA.options.current.idle_between_loops);
3584 var state = NETDATA.options.targets[index];
3586 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3587 if(NETDATA.options.debug.main_loop === true)
3588 console.log('fast rendering...');
3590 state.autoRefresh(function() {
3591 NETDATA.chartRefresherNoParallel(++index);
3595 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3596 NETDATA.options.auto_refresher_fast_weight = 0;
3598 setTimeout(function() {
3599 state.autoRefresh(function() {
3600 NETDATA.chartRefresherNoParallel(++index);
3602 }, NETDATA.options.current.idle_between_charts);
3607 // this is part of the parallel refresher
3608 // its cause is to refresh sequencially all the charts
3609 // that depend on chart library initialization
3610 // it will call the parallel refresher back
3611 // as soon as it sees a chart that its chart library
3613 NETDATA.chartRefresher_uninitialized = function() {
3614 if(NETDATA.options.updated_dom === true) {
3615 // the dom has been updated
3616 // get the dom parts again
3617 NETDATA.parseDom(NETDATA.chartRefresher);
3621 if(NETDATA.options.sequencial.length === 0)
3622 NETDATA.chartRefresher();
3624 var state = NETDATA.options.sequencial.pop();
3625 if(state.library.initialized === true)
3626 NETDATA.chartRefresher();
3628 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3632 NETDATA.chartRefresherWaitTime = function() {
3633 return NETDATA.options.current.idle_parallel_loops;
3636 // the default refresher
3637 // it will create 2 sets of charts:
3638 // - the ones that can be refreshed in parallel
3639 // - the ones that depend on something else
3640 // the first set will be executed in parallel
3641 // the second will be given to NETDATA.chartRefresher_uninitialized()
3642 NETDATA.chartRefresher = function() {
3643 // console.log('auto-refresher...');
3645 if(NETDATA.options.pause === true) {
3646 // console.log('auto-refresher is paused');
3647 setTimeout(NETDATA.chartRefresher,
3648 NETDATA.chartRefresherWaitTime());
3652 if(typeof NETDATA.options.pauseCallback === 'function') {
3653 // console.log('auto-refresher is calling pauseCallback');
3654 NETDATA.options.pause = true;
3655 NETDATA.options.pauseCallback();
3656 NETDATA.chartRefresher();
3660 if(NETDATA.options.current.parallel_refresher === false) {
3661 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3662 NETDATA.chartRefresherNoParallel(0);
3666 if(NETDATA.options.updated_dom === true) {
3667 // the dom has been updated
3668 // get the dom parts again
3669 // console.log('auto-refresher is calling parseDom()');
3670 NETDATA.parseDom(NETDATA.chartRefresher);
3674 var parallel = new Array();
3675 var targets = NETDATA.options.targets;
3676 var len = targets.length;
3679 state = targets[len];
3680 if(state.isVisible() === false || state.running === true)
3683 if(state.library.initialized === false) {
3684 if(state.library.enabled === true) {
3685 state.library.initialize(NETDATA.chartRefresher);
3689 state.error('chart library "' + state.library_name + '" is not enabled.');
3693 parallel.unshift(state);
3696 if(parallel.length > 0) {
3697 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3698 // this will execute the jobs in parallel
3699 $(parallel).each(function() {
3704 // console.log('auto-refresher nothing to do');
3707 // run the next refresh iteration
3708 setTimeout(NETDATA.chartRefresher,
3709 NETDATA.chartRefresherWaitTime());
3712 NETDATA.parseDom = function(callback) {
3713 NETDATA.options.last_page_scroll = Date.now();
3714 NETDATA.options.updated_dom = false;
3716 var targets = $('div[data-netdata]'); //.filter(':visible');
3718 if(NETDATA.options.debug.main_loop === true)
3719 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3721 NETDATA.options.targets = new Array();
3722 var len = targets.length;
3724 // the initialization will take care of sizing
3725 // and the "loading..." message
3726 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3729 if(typeof callback === 'function') callback();
3732 // this is the main function - where everything starts
3733 NETDATA.start = function() {
3734 // this should be called only once
3736 NETDATA.options.page_is_visible = true;
3738 $(window).blur(function() {
3739 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3740 NETDATA.options.page_is_visible = false;
3741 if(NETDATA.options.debug.focus === true)
3742 console.log('Lost Focus!');
3746 $(window).focus(function() {
3747 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3748 NETDATA.options.page_is_visible = true;
3749 if(NETDATA.options.debug.focus === true)
3750 console.log('Focus restored!');
3754 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3755 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3756 NETDATA.options.page_is_visible = false;
3757 if(NETDATA.options.debug.focus === true)
3758 console.log('Document has no focus!');
3762 // bootstrap tab switching
3763 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3765 // bootstrap modal switching
3766 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3767 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3769 // bootstrap collapse switching
3770 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3771 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3773 NETDATA.parseDom(NETDATA.chartRefresher);
3775 // Alarms initialization
3776 setTimeout(NETDATA.alarms.init, 1000);
3778 // Registry initialization
3779 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3781 if(typeof netdataCallback === 'function')
3785 // ----------------------------------------------------------------------------------------------------------------
3788 NETDATA.peityInitialize = function(callback) {
3789 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3791 url: NETDATA.peity_js,
3794 xhrFields: { withCredentials: true } // required for the cookie
3797 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3800 NETDATA.chartLibraries.peity.enabled = false;
3801 NETDATA.error(100, NETDATA.peity_js);
3803 .always(function() {
3804 if(typeof callback === "function")
3809 NETDATA.chartLibraries.peity.enabled = false;
3810 if(typeof callback === "function")
3815 NETDATA.peityChartUpdate = function(state, data) {
3816 state.peity_instance.innerHTML = data.result;
3818 if(state.peity_options.stroke !== state.chartColors()[0]) {
3819 state.peity_options.stroke = state.chartColors()[0];
3820 if(state.chart.chart_type === 'line')
3821 state.peity_options.fill = NETDATA.themes.current.background;
3823 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3826 $(state.peity_instance).peity('line', state.peity_options);
3830 NETDATA.peityChartCreate = function(state, data) {
3831 state.peity_instance = document.createElement('div');
3832 state.element_chart.appendChild(state.peity_instance);
3834 var self = $(state.element);
3835 state.peity_options = {
3836 stroke: NETDATA.themes.current.foreground,
3837 strokeWidth: self.data('peity-strokewidth') || 1,
3838 width: state.chartWidth(),
3839 height: state.chartHeight(),
3840 fill: NETDATA.themes.current.foreground
3843 NETDATA.peityChartUpdate(state, data);
3847 // ----------------------------------------------------------------------------------------------------------------
3850 NETDATA.sparklineInitialize = function(callback) {
3851 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3853 url: NETDATA.sparkline_js,
3856 xhrFields: { withCredentials: true } // required for the cookie
3859 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3862 NETDATA.chartLibraries.sparkline.enabled = false;
3863 NETDATA.error(100, NETDATA.sparkline_js);
3865 .always(function() {
3866 if(typeof callback === "function")
3871 NETDATA.chartLibraries.sparkline.enabled = false;
3872 if(typeof callback === "function")
3877 NETDATA.sparklineChartUpdate = function(state, data) {
3878 state.sparkline_options.width = state.chartWidth();
3879 state.sparkline_options.height = state.chartHeight();
3881 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3885 NETDATA.sparklineChartCreate = function(state, data) {
3886 var self = $(state.element);
3887 var type = self.data('sparkline-type') || 'line';
3888 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3889 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3890 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3891 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3892 var composite = self.data('sparkline-composite') || undefined;
3893 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3894 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3895 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3896 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3897 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3898 var spotColor = self.data('sparkline-spotcolor') || undefined;
3899 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3900 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3901 var spotRadius = self.data('sparkline-spotradius') || undefined;
3902 var valueSpots = self.data('sparkline-valuespots') || undefined;
3903 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3904 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3905 var lineWidth = self.data('sparkline-linewidth') || undefined;
3906 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3907 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3908 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3909 var xvalues = self.data('sparkline-xvalues') || undefined;
3910 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3911 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3912 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3913 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3914 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3915 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3916 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3917 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3918 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3919 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3920 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3921 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3922 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3923 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3924 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3925 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3926 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3927 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3928 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3929 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3930 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3931 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3933 if(spotColor === 'disable') spotColor='';
3934 if(minSpotColor === 'disable') minSpotColor='';
3935 if(maxSpotColor === 'disable') maxSpotColor='';
3937 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3939 state.sparkline_options = {
3941 lineColor: lineColor,
3942 fillColor: fillColor,
3943 chartRangeMin: chartRangeMin,
3944 chartRangeMax: chartRangeMax,
3945 composite: composite,
3946 enableTagOptions: enableTagOptions,
3947 tagOptionPrefix: tagOptionPrefix,
3948 tagValuesAttribute: tagValuesAttribute,
3949 disableHiddenCheck: disableHiddenCheck,
3950 defaultPixelsPerValue: defaultPixelsPerValue,
3951 spotColor: spotColor,
3952 minSpotColor: minSpotColor,
3953 maxSpotColor: maxSpotColor,
3954 spotRadius: spotRadius,
3955 valueSpots: valueSpots,
3956 highlightSpotColor: highlightSpotColor,
3957 highlightLineColor: highlightLineColor,
3958 lineWidth: lineWidth,
3959 normalRangeMin: normalRangeMin,
3960 normalRangeMax: normalRangeMax,
3961 drawNormalOnTop: drawNormalOnTop,
3963 chartRangeClip: chartRangeClip,
3964 chartRangeMinX: chartRangeMinX,
3965 chartRangeMaxX: chartRangeMaxX,
3966 disableInteraction: disableInteraction,
3967 disableTooltips: disableTooltips,
3968 disableHighlight: disableHighlight,
3969 highlightLighten: highlightLighten,
3970 highlightColor: highlightColor,
3971 tooltipContainer: tooltipContainer,
3972 tooltipClassname: tooltipClassname,
3973 tooltipChartTitle: state.title,
3974 tooltipFormat: tooltipFormat,
3975 tooltipPrefix: tooltipPrefix,
3976 tooltipSuffix: tooltipSuffix,
3977 tooltipSkipNull: tooltipSkipNull,
3978 tooltipValueLookups: tooltipValueLookups,
3979 tooltipFormatFieldlist: tooltipFormatFieldlist,
3980 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3981 numberFormatter: numberFormatter,
3982 numberDigitGroupSep: numberDigitGroupSep,
3983 numberDecimalMark: numberDecimalMark,
3984 numberDigitGroupCount: numberDigitGroupCount,
3985 animatedZooms: animatedZooms,
3986 width: state.chartWidth(),
3987 height: state.chartHeight()
3990 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3994 // ----------------------------------------------------------------------------------------------------------------
4001 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4002 if(after < state.netdata_first)
4003 after = state.netdata_first;
4005 if(before > state.netdata_last)
4006 before = state.netdata_last;
4008 state.setMode('zoom');
4009 state.globalSelectionSyncStop();
4010 state.globalSelectionSyncDelay();
4011 state.dygraph_user_action = true;
4012 state.dygraph_force_zoom = true;
4013 state.updateChartPanOrZoom(after, before);
4014 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4017 NETDATA.dygraphSetSelection = function(state, t) {
4018 if(typeof state.dygraph_instance !== 'undefined') {
4019 var r = state.calculateRowForTime(t);
4021 state.dygraph_instance.setSelection(r);
4023 state.dygraph_instance.clearSelection();
4024 state.legendShowUndefined();
4031 NETDATA.dygraphClearSelection = function(state, t) {
4032 if(typeof state.dygraph_instance !== 'undefined') {
4033 state.dygraph_instance.clearSelection();
4038 NETDATA.dygraphSmoothInitialize = function(callback) {
4040 url: NETDATA.dygraph_smooth_js,
4043 xhrFields: { withCredentials: true } // required for the cookie
4046 NETDATA.dygraph.smooth = true;
4047 smoothPlotter.smoothing = 0.3;
4050 NETDATA.dygraph.smooth = false;
4052 .always(function() {
4053 if(typeof callback === "function")
4058 NETDATA.dygraphInitialize = function(callback) {
4059 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4061 url: NETDATA.dygraph_js,
4064 xhrFields: { withCredentials: true } // required for the cookie
4067 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4070 NETDATA.chartLibraries.dygraph.enabled = false;
4071 NETDATA.error(100, NETDATA.dygraph_js);
4073 .always(function() {
4074 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4075 NETDATA.dygraphSmoothInitialize(callback);
4076 else if(typeof callback === "function")
4081 NETDATA.chartLibraries.dygraph.enabled = false;
4082 if(typeof callback === "function")
4087 NETDATA.dygraphChartUpdate = function(state, data) {
4088 var dygraph = state.dygraph_instance;
4090 if(typeof dygraph === 'undefined')
4091 return NETDATA.dygraphChartCreate(state, data);
4093 // when the chart is not visible, and hidden
4094 // if there is a window resize, dygraph detects
4095 // its element size as 0x0.
4096 // this will make it re-appear properly
4098 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4102 file: data.result.data,
4103 colors: state.chartColors(),
4104 labels: data.result.labels,
4105 labelsDivWidth: state.chartWidth() - 70,
4106 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4109 if(state.dygraph_force_zoom === true) {
4110 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4111 state.log('dygraphChartUpdate() forced zoom update');
4113 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4114 options.isZoomedIgnoreProgrammaticZoom = true;
4115 state.dygraph_force_zoom = false;
4117 else if(state.current.name !== 'auto') {
4118 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4119 state.log('dygraphChartUpdate() loose update');
4122 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4123 state.log('dygraphChartUpdate() strict update');
4125 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4126 options.isZoomedIgnoreProgrammaticZoom = true;
4129 options.valueRange = state.dygraph_options.valueRange;
4131 var oldMax = null, oldMin = null;
4132 if(state.__commonMin !== null) {
4133 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4134 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4136 if(state.__commonMax !== null) {
4137 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4138 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4141 if(state.dygraph_smooth_eligible === true) {
4142 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4143 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4144 NETDATA.dygraphChartCreate(state, data);
4149 dygraph.updateOptions(options);
4152 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4153 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4154 options.valueRange[0] = NETDATA.commonMin.get(state);
4157 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4158 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4159 options.valueRange[1] = NETDATA.commonMax.get(state);
4163 if(redraw === true) {
4164 // state.log('forcing redraw to adapt to common- min/max');
4165 dygraph.updateOptions(options);
4168 state.dygraph_last_rendered = Date.now();
4172 NETDATA.dygraphChartCreate = function(state, data) {
4173 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4174 state.log('dygraphChartCreate()');
4176 var self = $(state.element);
4178 var chart_type = state.chart.chart_type;
4179 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4180 chart_type = self.data('dygraph-type') || chart_type;
4182 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4183 smooth = self.data('dygraph-smooth') || smooth;
4185 if(NETDATA.dygraph.smooth === false)
4188 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4189 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4191 state.dygraph_options = {
4192 colors: self.data('dygraph-colors') || state.chartColors(),
4194 // leave a few pixels empty on the right of the chart
4195 rightGap: self.data('dygraph-rightgap') || 5,
4196 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4197 showRoller: self.data('dygraph-showroller') || false,
4199 title: self.data('dygraph-title') || state.title,
4200 titleHeight: self.data('dygraph-titleheight') || 19,
4202 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4203 labels: data.result.labels,
4204 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4205 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4206 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4207 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4208 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4211 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4212 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4214 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4215 xRangePad: self.data('dygraph-xrangepad') || 0,
4216 yRangePad: self.data('dygraph-yrangepad') || 1,
4218 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4220 ylabel: state.units,
4221 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4223 // the function to plot the chart
4226 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4227 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4228 strokePattern: self.data('dygraph-strokepattern') || undefined,
4230 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4231 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4232 drawPoints: self.data('dygraph-drawpoints') || false,
4234 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4235 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4237 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4238 pointSize: self.data('dygraph-pointsize') || 1,
4240 // enabling this makes the chart with little square lines
4241 stepPlot: self.data('dygraph-stepplot') || false,
4243 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4244 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4245 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4247 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4248 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4249 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4250 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4252 drawAxis: self.data('dygraph-drawaxis') || true,
4253 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4254 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4255 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4257 drawGrid: self.data('dygraph-drawgrid') || true,
4258 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4259 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4260 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4262 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4263 sigFigs: self.data('dygraph-sigfigs') || null,
4264 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4265 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4267 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4268 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4269 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4271 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4272 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4276 ticker: Dygraph.dateTicker,
4277 axisLabelFormatter: function (d, gran) {
4278 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4280 valueFormatter: function (ms) {
4281 //var d = new Date(ms);
4282 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4284 // no need to return anything here
4291 valueFormatter: function (x) {
4292 // we format legends with the state object
4293 // no need to do anything here
4294 // return (Math.round(x*100) / 100).toLocaleString();
4295 // return state.legendFormatValue(x);
4300 legendFormatter: function(data) {
4301 var elements = state.element_legend_childs;
4303 // if the hidden div is not there
4304 // we are not managing the legend
4305 if(elements.hidden === null) return;
4307 if (typeof data.x !== 'undefined') {
4308 state.legendSetDate(data.x);
4309 var i = data.series.length;
4311 var series = data.series[i];
4312 if(series.isVisible === true)
4313 state.legendSetLabelValue(series.label, series.y);
4319 drawCallback: function(dygraph, is_initial) {
4320 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4321 state.dygraph_user_action = false;
4323 var x_range = dygraph.xAxisRange();
4324 var after = Math.round(x_range[0]);
4325 var before = Math.round(x_range[1]);
4327 if(NETDATA.options.debug.dygraph === true)
4328 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4330 if(before <= state.netdata_last && after >= state.netdata_first)
4331 state.updateChartPanOrZoom(after, before);
4334 zoomCallback: function(minDate, maxDate, yRanges) {
4335 if(NETDATA.options.debug.dygraph === true)
4336 state.log('dygraphZoomCallback()');
4338 state.globalSelectionSyncStop();
4339 state.globalSelectionSyncDelay();
4340 state.setMode('zoom');
4342 // refresh it to the greatest possible zoom level
4343 state.dygraph_user_action = true;
4344 state.dygraph_force_zoom = true;
4345 state.updateChartPanOrZoom(minDate, maxDate);
4347 highlightCallback: function(event, x, points, row, seriesName) {
4348 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4349 state.log('dygraphHighlightCallback()');
4353 // there is a bug in dygraph when the chart is zoomed enough
4354 // the time it thinks is selected is wrong
4355 // here we calculate the time t based on the row number selected
4357 var t = state.data_after + row * state.data_update_every;
4358 // 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);
4360 state.globalSelectionSync(x);
4362 // fix legend zIndex using the internal structures of dygraph legend module
4363 // this works, but it is a hack!
4364 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4366 unhighlightCallback: function(event) {
4367 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4368 state.log('dygraphUnhighlightCallback()');
4370 state.unpauseChart();
4371 state.globalSelectionSyncStop();
4373 interactionModel : {
4374 mousedown: function(event, dygraph, context) {
4375 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4376 state.log('interactionModel.mousedown()');
4378 state.dygraph_user_action = true;
4379 state.globalSelectionSyncStop();
4381 if(NETDATA.options.debug.dygraph === true)
4382 state.log('dygraphMouseDown()');
4384 // Right-click should not initiate a zoom.
4385 if(event.button && event.button === 2) return;
4387 context.initializeMouseDown(event, dygraph, context);
4389 if(event.button && event.button === 1) {
4390 if (event.altKey || event.shiftKey) {
4391 state.setMode('pan');
4392 state.globalSelectionSyncDelay();
4393 Dygraph.startPan(event, dygraph, context);
4396 state.setMode('zoom');
4397 state.globalSelectionSyncDelay();
4398 Dygraph.startZoom(event, dygraph, context);
4402 if (event.altKey || event.shiftKey) {
4403 state.setMode('zoom');
4404 state.globalSelectionSyncDelay();
4405 Dygraph.startZoom(event, dygraph, context);
4408 state.setMode('pan');
4409 state.globalSelectionSyncDelay();
4410 Dygraph.startPan(event, dygraph, context);
4414 mousemove: function(event, dygraph, context) {
4415 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4416 state.log('interactionModel.mousemove()');
4418 if(context.isPanning) {
4419 state.dygraph_user_action = true;
4420 state.globalSelectionSyncStop();
4421 state.globalSelectionSyncDelay();
4422 state.setMode('pan');
4423 context.is2DPan = false;
4424 Dygraph.movePan(event, dygraph, context);
4426 else if(context.isZooming) {
4427 state.dygraph_user_action = true;
4428 state.globalSelectionSyncStop();
4429 state.globalSelectionSyncDelay();
4430 state.setMode('zoom');
4431 Dygraph.moveZoom(event, dygraph, context);
4434 mouseup: function(event, dygraph, context) {
4435 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4436 state.log('interactionModel.mouseup()');
4438 if (context.isPanning) {
4439 state.dygraph_user_action = true;
4440 state.globalSelectionSyncDelay();
4441 Dygraph.endPan(event, dygraph, context);
4443 else if (context.isZooming) {
4444 state.dygraph_user_action = true;
4445 state.globalSelectionSyncDelay();
4446 Dygraph.endZoom(event, dygraph, context);
4449 click: function(event, dygraph, context) {
4450 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4451 state.log('interactionModel.click()');
4453 event.preventDefault();
4455 dblclick: function(event, dygraph, context) {
4456 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4457 state.log('interactionModel.dblclick()');
4458 NETDATA.resetAllCharts(state);
4460 wheel: function(event, dygraph, context) {
4461 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4462 state.log('interactionModel.wheel()');
4464 // Take the offset of a mouse event on the dygraph canvas and
4465 // convert it to a pair of percentages from the bottom left.
4466 // (Not top left, bottom is where the lower value is.)
4467 function offsetToPercentage(g, offsetX, offsetY) {
4468 // This is calculating the pixel offset of the leftmost date.
4469 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4470 var yar0 = g.yAxisRange(0);
4472 // This is calculating the pixel of the higest value. (Top pixel)
4473 var yOffset = g.toDomCoords(null, yar0[1])[1];
4475 // x y w and h are relative to the corner of the drawing area,
4476 // so that the upper corner of the drawing area is (0, 0).
4477 var x = offsetX - xOffset;
4478 var y = offsetY - yOffset;
4480 // This is computing the rightmost pixel, effectively defining the
4482 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4484 // This is computing the lowest pixel, effectively defining the height.
4485 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4487 // Percentage from the left.
4488 var xPct = w === 0 ? 0 : (x / w);
4489 // Percentage from the top.
4490 var yPct = h === 0 ? 0 : (y / h);
4492 // The (1-) part below changes it from "% distance down from the top"
4493 // to "% distance up from the bottom".
4494 return [xPct, (1-yPct)];
4497 // Adjusts [x, y] toward each other by zoomInPercentage%
4498 // Split it so the left/bottom axis gets xBias/yBias of that change and
4499 // tight/top gets (1-xBias)/(1-yBias) of that change.
4501 // If a bias is missing it splits it down the middle.
4502 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4503 xBias = xBias || 0.5;
4504 yBias = yBias || 0.5;
4506 function adjustAxis(axis, zoomInPercentage, bias) {
4507 var delta = axis[1] - axis[0];
4508 var increment = delta * zoomInPercentage;
4509 var foo = [increment * bias, increment * (1-bias)];
4511 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4514 var yAxes = g.yAxisRanges();
4516 for (var i = 0; i < yAxes.length; i++) {
4517 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4520 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4523 if(event.altKey || event.shiftKey) {
4524 state.dygraph_user_action = true;
4526 state.globalSelectionSyncStop();
4527 state.globalSelectionSyncDelay();
4529 // http://dygraphs.com/gallery/interaction-api.js
4531 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4533 normal_def = event.wheelDelta / 40;
4536 normal_def = event.deltaY * -1.2;
4538 var normal = (event.detail) ? event.detail * -1 : normal_def;
4539 var percentage = normal / 50;
4541 if (!(event.offsetX && event.offsetY)){
4542 event.offsetX = event.layerX - event.target.offsetLeft;
4543 event.offsetY = event.layerY - event.target.offsetTop;
4546 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4547 var xPct = percentages[0];
4548 var yPct = percentages[1];
4550 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4551 var after = new_x_range[0];
4552 var before = new_x_range[1];
4554 var first = state.netdata_first + state.data_update_every;
4555 var last = state.netdata_last + state.data_update_every;
4558 after -= (before - last);
4565 state.setMode('zoom');
4566 if(state.updateChartPanOrZoom(after, before) === true)
4567 dygraph.updateOptions({ dateWindow: [ after, before ] });
4569 event.preventDefault();
4572 touchstart: function(event, dygraph, context) {
4573 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4574 state.log('interactionModel.touchstart()');
4576 state.dygraph_user_action = true;
4577 state.setMode('zoom');
4580 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4582 // we overwrite the touch directions at the end, to overwrite
4583 // the internal default of dygraphs
4584 context.touchDirections = { x: true, y: false };
4586 state.dygraph_last_touch_start = Date.now();
4587 state.dygraph_last_touch_move = 0;
4589 if(typeof event.touches[0].pageX === 'number')
4590 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4592 state.dygraph_last_touch_page_x = 0;
4594 touchmove: function(event, dygraph, context) {
4595 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4596 state.log('interactionModel.touchmove()');
4598 state.dygraph_user_action = true;
4599 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4601 state.dygraph_last_touch_move = Date.now();
4603 touchend: function(event, dygraph, context) {
4604 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4605 state.log('interactionModel.touchend()');
4607 state.dygraph_user_action = true;
4608 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4610 // if it didn't move, it is a selection
4611 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4612 // internal api of dygraphs
4613 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4614 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4615 if(NETDATA.dygraphSetSelection(state, t) === true)
4616 state.globalSelectionSync(t);
4619 // if it was double tap within double click time, reset the charts
4620 var now = Date.now();
4621 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4622 if(state.dygraph_last_touch_move === 0) {
4623 var dt = now - state.dygraph_last_touch_end;
4624 if(dt <= NETDATA.options.current.double_click_speed)
4625 NETDATA.resetAllCharts(state);
4629 // remember the timestamp of the last touch end
4630 state.dygraph_last_touch_end = now;
4635 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4636 state.dygraph_options.drawGrid = false;
4637 state.dygraph_options.drawAxis = false;
4638 state.dygraph_options.title = undefined;
4639 state.dygraph_options.ylabel = undefined;
4640 state.dygraph_options.yLabelWidth = 0;
4641 state.dygraph_options.labelsDivWidth = 120;
4642 state.dygraph_options.labelsDivStyles.width = '120px';
4643 state.dygraph_options.labelsSeparateLines = true;
4644 state.dygraph_options.rightGap = 0;
4645 state.dygraph_options.yRangePad = 1;
4648 if(smooth === true) {
4649 state.dygraph_smooth_eligible = true;
4651 if(NETDATA.options.current.smooth_plot === true)
4652 state.dygraph_options.plotter = smoothPlotter;
4654 else state.dygraph_smooth_eligible = false;
4656 state.dygraph_instance = new Dygraph(state.element_chart,
4657 data.result.data, state.dygraph_options);
4659 state.dygraph_force_zoom = false;
4660 state.dygraph_user_action = false;
4661 state.dygraph_last_rendered = Date.now();
4663 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4664 state.__commonMin = self.data('common-min') || null;
4665 state.__commonMax = self.data('common-max') || null;
4668 state.log('incompatible version of dygraphs detected');
4669 state.__commonMin = null;
4670 state.__commonMax = null;
4676 // ----------------------------------------------------------------------------------------------------------------
4679 NETDATA.morrisInitialize = function(callback) {
4680 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4682 // morris requires raphael
4683 if(!NETDATA.chartLibraries.raphael.initialized) {
4684 if(NETDATA.chartLibraries.raphael.enabled) {
4685 NETDATA.raphaelInitialize(function() {
4686 NETDATA.morrisInitialize(callback);
4690 NETDATA.chartLibraries.morris.enabled = false;
4691 if(typeof callback === "function")
4696 NETDATA._loadCSS(NETDATA.morris_css);
4699 url: NETDATA.morris_js,
4702 xhrFields: { withCredentials: true } // required for the cookie
4705 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4708 NETDATA.chartLibraries.morris.enabled = false;
4709 NETDATA.error(100, NETDATA.morris_js);
4711 .always(function() {
4712 if(typeof callback === "function")
4718 NETDATA.chartLibraries.morris.enabled = false;
4719 if(typeof callback === "function")
4724 NETDATA.morrisChartUpdate = function(state, data) {
4725 state.morris_instance.setData(data.result.data);
4729 NETDATA.morrisChartCreate = function(state, data) {
4731 state.morris_options = {
4732 element: state.element_chart.id,
4733 data: data.result.data,
4735 ykeys: data.dimension_names,
4736 labels: data.dimension_names,
4742 continuousLine: false,
4743 behaveLikeLine: false
4746 if(state.chart.chart_type === 'line')
4747 state.morris_instance = new Morris.Line(state.morris_options);
4749 else if(state.chart.chart_type === 'area') {
4750 state.morris_options.behaveLikeLine = true;
4751 state.morris_instance = new Morris.Area(state.morris_options);
4754 state.morris_instance = new Morris.Area(state.morris_options);
4759 // ----------------------------------------------------------------------------------------------------------------
4762 NETDATA.raphaelInitialize = function(callback) {
4763 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4765 url: NETDATA.raphael_js,
4768 xhrFields: { withCredentials: true } // required for the cookie
4771 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4774 NETDATA.chartLibraries.raphael.enabled = false;
4775 NETDATA.error(100, NETDATA.raphael_js);
4777 .always(function() {
4778 if(typeof callback === "function")
4783 NETDATA.chartLibraries.raphael.enabled = false;
4784 if(typeof callback === "function")
4789 NETDATA.raphaelChartUpdate = function(state, data) {
4790 $(state.element_chart).raphael(data.result, {
4791 width: state.chartWidth(),
4792 height: state.chartHeight()
4798 NETDATA.raphaelChartCreate = function(state, data) {
4799 $(state.element_chart).raphael(data.result, {
4800 width: state.chartWidth(),
4801 height: state.chartHeight()
4807 // ----------------------------------------------------------------------------------------------------------------
4810 NETDATA.c3Initialize = function(callback) {
4811 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4814 if(!NETDATA.chartLibraries.d3.initialized) {
4815 if(NETDATA.chartLibraries.d3.enabled) {
4816 NETDATA.d3Initialize(function() {
4817 NETDATA.c3Initialize(callback);
4821 NETDATA.chartLibraries.c3.enabled = false;
4822 if(typeof callback === "function")
4827 NETDATA._loadCSS(NETDATA.c3_css);
4833 xhrFields: { withCredentials: true } // required for the cookie
4836 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4839 NETDATA.chartLibraries.c3.enabled = false;
4840 NETDATA.error(100, NETDATA.c3_js);
4842 .always(function() {
4843 if(typeof callback === "function")
4849 NETDATA.chartLibraries.c3.enabled = false;
4850 if(typeof callback === "function")
4855 NETDATA.c3ChartUpdate = function(state, data) {
4856 state.c3_instance.destroy();
4857 return NETDATA.c3ChartCreate(state, data);
4859 //state.c3_instance.load({
4860 // rows: data.result,
4867 NETDATA.c3ChartCreate = function(state, data) {
4869 state.element_chart.id = 'c3-' + state.uuid;
4870 // console.log('id = ' + state.element_chart.id);
4872 state.c3_instance = c3.generate({
4873 bindto: '#' + state.element_chart.id,
4875 width: state.chartWidth(),
4876 height: state.chartHeight()
4879 pattern: state.chartColors()
4884 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4890 format: function(x) {
4891 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4918 // console.log(state.c3_instance);
4923 // ----------------------------------------------------------------------------------------------------------------
4926 NETDATA.d3Initialize = function(callback) {
4927 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4932 xhrFields: { withCredentials: true } // required for the cookie
4935 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4938 NETDATA.chartLibraries.d3.enabled = false;
4939 NETDATA.error(100, NETDATA.d3_js);
4941 .always(function() {
4942 if(typeof callback === "function")
4947 NETDATA.chartLibraries.d3.enabled = false;
4948 if(typeof callback === "function")
4953 NETDATA.d3ChartUpdate = function(state, data) {
4957 NETDATA.d3ChartCreate = function(state, data) {
4961 // ----------------------------------------------------------------------------------------------------------------
4964 NETDATA.googleInitialize = function(callback) {
4965 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4967 url: NETDATA.google_js,
4970 xhrFields: { withCredentials: true } // required for the cookie
4973 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4974 google.load('visualization', '1.1', {
4975 'packages': ['corechart', 'controls'],
4976 'callback': callback
4980 NETDATA.chartLibraries.google.enabled = false;
4981 NETDATA.error(100, NETDATA.google_js);
4982 if(typeof callback === "function")
4987 NETDATA.chartLibraries.google.enabled = false;
4988 if(typeof callback === "function")
4993 NETDATA.googleChartUpdate = function(state, data) {
4994 var datatable = new google.visualization.DataTable(data.result);
4995 state.google_instance.draw(datatable, state.google_options);
4999 NETDATA.googleChartCreate = function(state, data) {
5000 var datatable = new google.visualization.DataTable(data.result);
5002 state.google_options = {
5003 colors: state.chartColors(),
5005 // do not set width, height - the chart resizes itself
5006 //width: state.chartWidth(),
5007 //height: state.chartHeight(),
5012 // title: "Time of Day",
5013 // format:'HH:mm:ss',
5014 viewWindowMode: 'maximized',
5026 viewWindowMode: 'pretty',
5041 focusTarget: 'category',
5048 titlePosition: 'out',
5059 curveType: 'function',
5064 switch(state.chart.chart_type) {
5066 state.google_options.vAxis.viewWindowMode = 'maximized';
5067 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5068 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5072 state.google_options.isStacked = true;
5073 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5074 state.google_options.vAxis.viewWindowMode = 'maximized';
5075 state.google_options.vAxis.minValue = null;
5076 state.google_options.vAxis.maxValue = null;
5077 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5082 state.google_options.lineWidth = 2;
5083 state.google_instance = new google.visualization.LineChart(state.element_chart);
5087 state.google_instance.draw(datatable, state.google_options);
5091 // ----------------------------------------------------------------------------------------------------------------
5093 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5094 if(typeof value !== 'number') value = 0;
5095 if(typeof min !== 'number') min = 0;
5096 if(typeof max !== 'number') max = 0;
5098 if(min > value) min = value;
5099 if(max < value) max = value;
5101 // make sure it is zero based
5102 if(min > 0) min = 0;
5103 if(max < 0) max = 0;
5108 pcent = Math.round(value * 100 / max);
5109 if(pcent === 0) pcent = 0.1;
5113 pcent = Math.round(-value * 100 / min);
5114 if(pcent === 0) pcent = -0.1;
5120 // ----------------------------------------------------------------------------------------------------------------
5123 NETDATA.easypiechartInitialize = function(callback) {
5124 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5126 url: NETDATA.easypiechart_js,
5129 xhrFields: { withCredentials: true } // required for the cookie
5132 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5135 NETDATA.chartLibraries.easypiechart.enabled = false;
5136 NETDATA.error(100, NETDATA.easypiechart_js);
5138 .always(function() {
5139 if(typeof callback === "function")
5144 NETDATA.chartLibraries.easypiechart.enabled = false;
5145 if(typeof callback === "function")
5150 NETDATA.easypiechartClearSelection = function(state) {
5151 if(typeof state.easyPieChartEvent !== 'undefined') {
5152 if(state.easyPieChartEvent.timer !== null)
5153 clearTimeout(state.easyPieChartEvent.timer);
5155 state.easyPieChartEvent.timer = null;
5158 if(state.isAutoRefreshable() === true && state.data !== null) {
5159 NETDATA.easypiechartChartUpdate(state, state.data);
5162 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5163 state.easyPieChart_instance.update(0);
5165 state.easyPieChart_instance.enableAnimation();
5170 NETDATA.easypiechartSetSelection = function(state, t) {
5171 if(state.timeIsVisible(t) !== true)
5172 return NETDATA.easypiechartClearSelection(state);
5174 var slot = state.calculateRowForTime(t);
5175 if(slot < 0 || slot >= state.data.result.length)
5176 return NETDATA.easypiechartClearSelection(state);
5178 if(typeof state.easyPieChartEvent === 'undefined') {
5179 state.easyPieChartEvent = {
5186 var value = state.data.result[state.data.result.length - 1 - slot];
5187 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5188 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5189 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5191 state.easyPieChartEvent.value = value;
5192 state.easyPieChartEvent.pcent = pcent;
5193 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5195 if(state.easyPieChartEvent.timer === null) {
5196 state.easyPieChart_instance.disableAnimation();
5198 state.easyPieChartEvent.timer = setTimeout(function() {
5199 state.easyPieChartEvent.timer = null;
5200 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5201 }, NETDATA.options.current.charts_selection_animation_delay);
5207 NETDATA.easypiechartChartUpdate = function(state, data) {
5208 var value, min, max, pcent;
5210 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5215 value = data.result[0];
5216 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5217 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5218 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5221 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5222 state.easyPieChart_instance.update(pcent);
5226 NETDATA.easypiechartChartCreate = function(state, data) {
5227 var self = $(state.element);
5228 var chart = $(state.element_chart);
5230 var value = data.result[0];
5231 var min = self.data('easypiechart-min-value') || null;
5232 var max = self.data('easypiechart-max-value') || null;
5233 var adjust = self.data('easypiechart-adjust') || null;
5236 min = NETDATA.commonMin.get(state);
5237 state.easyPieChartMin = null;
5240 state.easyPieChartMin = min;
5243 max = NETDATA.commonMax.get(state);
5244 state.easyPieChartMax = null;
5247 state.easyPieChartMax = max;
5249 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5251 chart.data('data-percent', pcent);
5255 case 'width': size = state.chartHeight(); break;
5256 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5257 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5259 default: size = state.chartWidth(); break;
5261 state.element.style.width = size + 'px';
5262 state.element.style.height = size + 'px';
5264 var stroke = Math.floor(size / 22);
5265 if(stroke < 3) stroke = 2;
5267 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5268 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5269 state.easyPieChartLabel = document.createElement('span');
5270 state.easyPieChartLabel.className = 'easyPieChartLabel';
5271 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5272 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5273 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5274 state.element_chart.appendChild(state.easyPieChartLabel);
5276 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5277 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5278 state.easyPieChartTitle = document.createElement('span');
5279 state.easyPieChartTitle.className = 'easyPieChartTitle';
5280 state.easyPieChartTitle.innerText = state.title;
5281 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5282 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5283 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5284 state.element_chart.appendChild(state.easyPieChartTitle);
5286 var unitfontsize = Math.round(titlefontsize * 0.9);
5287 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5288 state.easyPieChartUnits = document.createElement('span');
5289 state.easyPieChartUnits.className = 'easyPieChartUnits';
5290 state.easyPieChartUnits.innerText = state.units;
5291 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5292 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5293 state.element_chart.appendChild(state.easyPieChartUnits);
5295 var barColor = self.data('easypiechart-barcolor');
5296 if(typeof barColor === 'undefined' || barColor === null)
5297 barColor = state.chartColors()[0];
5299 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5300 var tmp = eval(barColor);
5301 if(typeof tmp === 'function')
5305 chart.easyPieChart({
5307 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5308 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5309 scaleLength: self.data('easypiechart-scalelength') || 5,
5310 lineCap: self.data('easypiechart-linecap') || 'round',
5311 lineWidth: self.data('easypiechart-linewidth') || stroke,
5312 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5313 size: self.data('easypiechart-size') || size,
5314 rotate: self.data('easypiechart-rotate') || 0,
5315 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5316 easing: self.data('easypiechart-easing') || undefined
5319 // when we just re-create the chart
5320 // do not animate the first update
5322 if(typeof state.easyPieChart_instance !== 'undefined')
5325 state.easyPieChart_instance = chart.data('easyPieChart');
5326 if(animate === false) state.easyPieChart_instance.disableAnimation();
5327 state.easyPieChart_instance.update(pcent);
5328 if(animate === false) state.easyPieChart_instance.enableAnimation();
5332 // ----------------------------------------------------------------------------------------------------------------
5335 NETDATA.gaugeInitialize = function(callback) {
5336 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5338 url: NETDATA.gauge_js,
5341 xhrFields: { withCredentials: true } // required for the cookie
5344 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5347 NETDATA.chartLibraries.gauge.enabled = false;
5348 NETDATA.error(100, NETDATA.gauge_js);
5350 .always(function() {
5351 if(typeof callback === "function")
5356 NETDATA.chartLibraries.gauge.enabled = false;
5357 if(typeof callback === "function")
5362 NETDATA.gaugeAnimation = function(state, status) {
5365 if(typeof status === 'boolean' && status === false)
5367 else if(typeof status === 'number')
5370 // console.log('gauge speed ' + speed);
5371 state.gauge_instance.animationSpeed = speed;
5372 state.___gaugeOld__.speed = speed;
5375 NETDATA.gaugeSet = function(state, value, min, max) {
5376 if(typeof value !== 'number') value = 0;
5377 if(typeof min !== 'number') min = 0;
5378 if(typeof max !== 'number') max = 0;
5379 if(value > max) max = value;
5380 if(value < min) min = value;
5389 // gauge.js has an issue if the needle
5390 // is smaller than min or larger than max
5391 // when we set the new values
5392 // the needle will go crazy
5394 // to prevent it, we always feed it
5395 // with a percentage, so that the needle
5396 // is always between min and max
5397 var pcent = (value - min) * 100 / (max - min);
5399 // these should never happen
5400 if(pcent < 0) pcent = 0;
5401 if(pcent > 100) pcent = 100;
5403 state.gauge_instance.set(pcent);
5404 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5406 state.___gaugeOld__.value = value;
5407 state.___gaugeOld__.min = min;
5408 state.___gaugeOld__.max = max;
5411 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5412 if(state.___gaugeOld__.valueLabel !== value) {
5413 state.___gaugeOld__.valueLabel = value;
5414 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5416 if(state.___gaugeOld__.minLabel !== min) {
5417 state.___gaugeOld__.minLabel = min;
5418 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5420 if(state.___gaugeOld__.maxLabel !== max) {
5421 state.___gaugeOld__.maxLabel = max;
5422 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5426 NETDATA.gaugeClearSelection = function(state) {
5427 if(typeof state.gaugeEvent !== 'undefined') {
5428 if(state.gaugeEvent.timer !== null)
5429 clearTimeout(state.gaugeEvent.timer);
5431 state.gaugeEvent.timer = null;
5434 if(state.isAutoRefreshable() === true && state.data !== null) {
5435 NETDATA.gaugeChartUpdate(state, state.data);
5438 NETDATA.gaugeAnimation(state, false);
5439 NETDATA.gaugeSet(state, null, null, null);
5440 NETDATA.gaugeSetLabels(state, null, null, null);
5443 NETDATA.gaugeAnimation(state, true);
5447 NETDATA.gaugeSetSelection = function(state, t) {
5448 if(state.timeIsVisible(t) !== true)
5449 return NETDATA.gaugeClearSelection(state);
5451 var slot = state.calculateRowForTime(t);
5452 if(slot < 0 || slot >= state.data.result.length)
5453 return NETDATA.gaugeClearSelection(state);
5455 if(typeof state.gaugeEvent === 'undefined') {
5456 state.gaugeEvent = {
5464 var value = state.data.result[state.data.result.length - 1 - slot];
5465 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5466 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5468 // make sure it is zero based
5469 if(min > 0) min = 0;
5470 if(max < 0) max = 0;
5472 state.gaugeEvent.value = value;
5473 state.gaugeEvent.min = min;
5474 state.gaugeEvent.max = max;
5475 NETDATA.gaugeSetLabels(state, value, min, max);
5477 if(state.gaugeEvent.timer === null) {
5478 NETDATA.gaugeAnimation(state, false);
5480 state.gaugeEvent.timer = setTimeout(function() {
5481 state.gaugeEvent.timer = null;
5482 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5483 }, NETDATA.options.current.charts_selection_animation_delay);
5489 NETDATA.gaugeChartUpdate = function(state, data) {
5490 var value, min, max;
5492 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5496 NETDATA.gaugeSetLabels(state, null, null, null);
5499 value = data.result[0];
5500 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5501 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5502 if(value < min) min = value;
5503 if(value > max) max = value;
5505 // make sure it is zero based
5506 if(min > 0) min = 0;
5507 if(max < 0) max = 0;
5509 NETDATA.gaugeSetLabels(state, value, min, max);
5512 NETDATA.gaugeSet(state, value, min, max);
5516 NETDATA.gaugeChartCreate = function(state, data) {
5517 var self = $(state.element);
5518 // var chart = $(state.element_chart);
5520 var value = data.result[0];
5521 var min = self.data('gauge-min-value') || null;
5522 var max = self.data('gauge-max-value') || null;
5523 var adjust = self.data('gauge-adjust') || null;
5524 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5525 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5526 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5527 var stopColor = self.data('gauge-stop-color') || void 0;
5528 var generateGradient = self.data('gauge-generate-gradient') || false;
5531 min = NETDATA.commonMin.get(state);
5532 state.gaugeMin = null;
5535 state.gaugeMin = min;
5538 max = NETDATA.commonMax.get(state);
5539 state.gaugeMax = null;
5542 state.gaugeMax = max;
5544 // make sure it is zero based
5545 if(min > 0) min = 0;
5546 if(max < 0) max = 0;
5548 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5550 // case 'width': width = height * ratio; break;
5552 // default: height = width / ratio; break;
5554 //state.element.style.width = width.toString() + 'px';
5555 //state.element.style.height = height.toString() + 'px';
5560 lines: 12, // The number of lines to draw
5561 angle: 0.15, // The length of each line
5562 lineWidth: 0.44, // 0.44 The line thickness
5564 length: 0.8, // 0.9 The radius of the inner circle
5565 strokeWidth: 0.035, // The rotation offset
5566 color: pointerColor // Fill color
5568 colorStart: startColor, // Colors
5569 colorStop: stopColor, // just experiment with them
5570 strokeColor: strokeColor, // to see which ones work best for you
5572 generateGradient: (generateGradient === true)?true:false,
5576 if (generateGradient.constructor === Array) {
5578 // data-gauge-generate-gradient="[0, 50, 100]"
5579 // data-gauge-gradient-percent-color-0="#FFFFFF"
5580 // data-gauge-gradient-percent-color-50="#999900"
5581 // data-gauge-gradient-percent-color-100="#000000"
5583 options.percentColors = new Array();
5584 var len = generateGradient.length;
5586 var pcent = generateGradient[len];
5587 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5588 if(color !== false) {
5589 var a = new Array();
5592 options.percentColors.unshift(a);
5595 if(options.percentColors.length === 0)
5596 delete options.percentColors;
5598 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5599 options.percentColors = [
5600 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5601 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5602 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5603 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5604 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5605 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5606 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5607 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5608 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5609 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5610 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5613 state.gauge_canvas = document.createElement('canvas');
5614 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5615 state.gauge_canvas.className = 'gaugeChart';
5616 state.gauge_canvas.width = width;
5617 state.gauge_canvas.height = height;
5618 state.element_chart.appendChild(state.gauge_canvas);
5620 var valuefontsize = Math.floor(height / 6);
5621 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5622 state.gaugeChartLabel = document.createElement('span');
5623 state.gaugeChartLabel.className = 'gaugeChartLabel';
5624 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5625 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5626 state.element_chart.appendChild(state.gaugeChartLabel);
5628 var titlefontsize = Math.round(valuefontsize / 2);
5630 state.gaugeChartTitle = document.createElement('span');
5631 state.gaugeChartTitle.className = 'gaugeChartTitle';
5632 state.gaugeChartTitle.innerText = state.title;
5633 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5634 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5635 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5636 state.element_chart.appendChild(state.gaugeChartTitle);
5638 var unitfontsize = Math.round(titlefontsize * 0.9);
5639 state.gaugeChartUnits = document.createElement('span');
5640 state.gaugeChartUnits.className = 'gaugeChartUnits';
5641 state.gaugeChartUnits.innerText = state.units;
5642 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5643 state.element_chart.appendChild(state.gaugeChartUnits);
5645 state.gaugeChartMin = document.createElement('span');
5646 state.gaugeChartMin.className = 'gaugeChartMin';
5647 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5648 state.element_chart.appendChild(state.gaugeChartMin);
5650 state.gaugeChartMax = document.createElement('span');
5651 state.gaugeChartMax.className = 'gaugeChartMax';
5652 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5653 state.element_chart.appendChild(state.gaugeChartMax);
5655 // when we just re-create the chart
5656 // do not animate the first update
5658 if(typeof state.gauge_instance !== 'undefined')
5661 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5663 state.___gaugeOld__ = {
5672 // we will always feed a percentage
5673 state.gauge_instance.minValue = 0;
5674 state.gauge_instance.maxValue = 100;
5676 NETDATA.gaugeAnimation(state, animate);
5677 NETDATA.gaugeSet(state, value, min, max);
5678 NETDATA.gaugeSetLabels(state, value, min, max);
5679 NETDATA.gaugeAnimation(state, true);
5683 // ----------------------------------------------------------------------------------------------------------------
5684 // Charts Libraries Registration
5686 NETDATA.chartLibraries = {
5688 initialize: NETDATA.dygraphInitialize,
5689 create: NETDATA.dygraphChartCreate,
5690 update: NETDATA.dygraphChartUpdate,
5691 resize: function(state) {
5692 if(typeof state.dygraph_instance.resize === 'function')
5693 state.dygraph_instance.resize();
5695 setSelection: NETDATA.dygraphSetSelection,
5696 clearSelection: NETDATA.dygraphClearSelection,
5697 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5700 format: function(state) { return 'json'; },
5701 options: function(state) { return 'ms|flip'; },
5702 legend: function(state) {
5703 if(this.isSparkline(state) === false)
5704 return 'right-side';
5708 autoresize: function(state) { return true; },
5709 max_updates_to_recreate: function(state) { return 5000; },
5710 track_colors: function(state) { return true; },
5711 pixels_per_point: function(state) {
5712 if(this.isSparkline(state) === false)
5718 isSparkline: function(state) {
5719 if(typeof state.dygraph_sparkline === 'undefined') {
5720 var t = $(state.element).data('dygraph-theme');
5721 if(t === 'sparkline')
5722 state.dygraph_sparkline = true;
5724 state.dygraph_sparkline = false;
5726 return state.dygraph_sparkline;
5730 initialize: NETDATA.sparklineInitialize,
5731 create: NETDATA.sparklineChartCreate,
5732 update: NETDATA.sparklineChartUpdate,
5734 setSelection: undefined, // function(state, t) { return true; },
5735 clearSelection: undefined, // function(state) { return true; },
5736 toolboxPanAndZoom: null,
5739 format: function(state) { return 'array'; },
5740 options: function(state) { return 'flip|abs'; },
5741 legend: function(state) { return null; },
5742 autoresize: function(state) { return false; },
5743 max_updates_to_recreate: function(state) { return 5000; },
5744 track_colors: function(state) { return false; },
5745 pixels_per_point: function(state) { return 3; }
5748 initialize: NETDATA.peityInitialize,
5749 create: NETDATA.peityChartCreate,
5750 update: NETDATA.peityChartUpdate,
5752 setSelection: undefined, // function(state, t) { return true; },
5753 clearSelection: undefined, // function(state) { return true; },
5754 toolboxPanAndZoom: null,
5757 format: function(state) { return 'ssvcomma'; },
5758 options: function(state) { return 'null2zero|flip|abs'; },
5759 legend: function(state) { return null; },
5760 autoresize: function(state) { return false; },
5761 max_updates_to_recreate: function(state) { return 5000; },
5762 track_colors: function(state) { return false; },
5763 pixels_per_point: function(state) { return 3; }
5766 initialize: NETDATA.morrisInitialize,
5767 create: NETDATA.morrisChartCreate,
5768 update: NETDATA.morrisChartUpdate,
5770 setSelection: undefined, // function(state, t) { return true; },
5771 clearSelection: undefined, // function(state) { return true; },
5772 toolboxPanAndZoom: null,
5775 format: function(state) { return 'json'; },
5776 options: function(state) { return 'objectrows|ms'; },
5777 legend: function(state) { return null; },
5778 autoresize: function(state) { return false; },
5779 max_updates_to_recreate: function(state) { return 50; },
5780 track_colors: function(state) { return false; },
5781 pixels_per_point: function(state) { return 15; }
5784 initialize: NETDATA.googleInitialize,
5785 create: NETDATA.googleChartCreate,
5786 update: NETDATA.googleChartUpdate,
5788 setSelection: undefined, //function(state, t) { return true; },
5789 clearSelection: undefined, //function(state) { return true; },
5790 toolboxPanAndZoom: null,
5793 format: function(state) { return 'datatable'; },
5794 options: function(state) { return ''; },
5795 legend: function(state) { return null; },
5796 autoresize: function(state) { return false; },
5797 max_updates_to_recreate: function(state) { return 300; },
5798 track_colors: function(state) { return false; },
5799 pixels_per_point: function(state) { return 4; }
5802 initialize: NETDATA.raphaelInitialize,
5803 create: NETDATA.raphaelChartCreate,
5804 update: NETDATA.raphaelChartUpdate,
5806 setSelection: undefined, // function(state, t) { return true; },
5807 clearSelection: undefined, // function(state) { return true; },
5808 toolboxPanAndZoom: null,
5811 format: function(state) { return 'json'; },
5812 options: function(state) { return ''; },
5813 legend: function(state) { return null; },
5814 autoresize: function(state) { return false; },
5815 max_updates_to_recreate: function(state) { return 5000; },
5816 track_colors: function(state) { return false; },
5817 pixels_per_point: function(state) { return 3; }
5820 initialize: NETDATA.c3Initialize,
5821 create: NETDATA.c3ChartCreate,
5822 update: NETDATA.c3ChartUpdate,
5824 setSelection: undefined, // function(state, t) { return true; },
5825 clearSelection: undefined, // function(state) { return true; },
5826 toolboxPanAndZoom: null,
5829 format: function(state) { return 'csvjsonarray'; },
5830 options: function(state) { return 'milliseconds'; },
5831 legend: function(state) { return null; },
5832 autoresize: function(state) { return false; },
5833 max_updates_to_recreate: function(state) { return 5000; },
5834 track_colors: function(state) { return false; },
5835 pixels_per_point: function(state) { return 15; }
5838 initialize: NETDATA.d3Initialize,
5839 create: NETDATA.d3ChartCreate,
5840 update: NETDATA.d3ChartUpdate,
5842 setSelection: undefined, // function(state, t) { return true; },
5843 clearSelection: undefined, // function(state) { return true; },
5844 toolboxPanAndZoom: null,
5847 format: function(state) { return 'json'; },
5848 options: function(state) { return ''; },
5849 legend: function(state) { return null; },
5850 autoresize: function(state) { return false; },
5851 max_updates_to_recreate: function(state) { return 5000; },
5852 track_colors: function(state) { return false; },
5853 pixels_per_point: function(state) { return 3; }
5856 initialize: NETDATA.easypiechartInitialize,
5857 create: NETDATA.easypiechartChartCreate,
5858 update: NETDATA.easypiechartChartUpdate,
5860 setSelection: NETDATA.easypiechartSetSelection,
5861 clearSelection: NETDATA.easypiechartClearSelection,
5862 toolboxPanAndZoom: null,
5865 format: function(state) { return 'array'; },
5866 options: function(state) { return 'absolute'; },
5867 legend: function(state) { return null; },
5868 autoresize: function(state) { return false; },
5869 max_updates_to_recreate: function(state) { return 5000; },
5870 track_colors: function(state) { return true; },
5871 pixels_per_point: function(state) { return 3; },
5875 initialize: NETDATA.gaugeInitialize,
5876 create: NETDATA.gaugeChartCreate,
5877 update: NETDATA.gaugeChartUpdate,
5879 setSelection: NETDATA.gaugeSetSelection,
5880 clearSelection: NETDATA.gaugeClearSelection,
5881 toolboxPanAndZoom: null,
5884 format: function(state) { return 'array'; },
5885 options: function(state) { return 'absolute'; },
5886 legend: function(state) { return null; },
5887 autoresize: function(state) { return false; },
5888 max_updates_to_recreate: function(state) { return 5000; },
5889 track_colors: function(state) { return true; },
5890 pixels_per_point: function(state) { return 3; },
5895 NETDATA.registerChartLibrary = function(library, url) {
5896 if(NETDATA.options.debug.libraries === true)
5897 console.log("registering chart library: " + library);
5899 NETDATA.chartLibraries[library].url = url;
5900 NETDATA.chartLibraries[library].initialized = true;
5901 NETDATA.chartLibraries[library].enabled = true;
5904 // ----------------------------------------------------------------------------------------------------------------
5905 // Load required JS libraries and CSS
5907 NETDATA.requiredJs = [
5909 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5911 isAlreadyLoaded: function() {
5912 // check if bootstrap is loaded
5913 if(typeof $().emulateTransitionEnd == 'function')
5916 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5924 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5925 isAlreadyLoaded: function() { return false; }
5929 NETDATA.requiredCSS = [
5931 url: NETDATA.themes.current.bootstrap_css,
5932 isAlreadyLoaded: function() {
5933 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5940 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5941 isAlreadyLoaded: function() { return false; }
5944 url: NETDATA.themes.current.dashboard_css,
5945 isAlreadyLoaded: function() { return false; }
5949 NETDATA.loadedRequiredJs = 0;
5950 NETDATA.loadRequiredJs = function(index, callback) {
5951 if(index >= NETDATA.requiredJs.length) {
5952 if(typeof callback === 'function')
5957 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5958 NETDATA.loadedRequiredJs++;
5959 NETDATA.loadRequiredJs(++index, callback);
5963 if(NETDATA.options.debug.main_loop === true)
5964 console.log('loading ' + NETDATA.requiredJs[index].url);
5967 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5971 url: NETDATA.requiredJs[index].url,
5974 xhrFields: { withCredentials: true } // required for the cookie
5977 if(NETDATA.options.debug.main_loop === true)
5978 console.log('loaded ' + NETDATA.requiredJs[index].url);
5981 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5983 .always(function() {
5984 NETDATA.loadedRequiredJs++;
5987 NETDATA.loadRequiredJs(++index, callback);
5991 NETDATA.loadRequiredJs(++index, callback);
5994 NETDATA.loadRequiredCSS = function(index) {
5995 if(index >= NETDATA.requiredCSS.length)
5998 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5999 NETDATA.loadRequiredCSS(++index);
6003 if(NETDATA.options.debug.main_loop === true)
6004 console.log('loading ' + NETDATA.requiredCSS[index].url);
6006 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6007 NETDATA.loadRequiredCSS(++index);
6011 // ----------------------------------------------------------------------------------------------------------------
6012 // Registry of netdata hosts
6015 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6016 chart_div_offset: 100, // give that space above the chart when scrolling to it
6017 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6018 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6020 ms_penalty: 0, // the time penalty of the next alarm
6021 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6022 // if alarms are shown faster than: one per 500ms
6024 notifications: false, // when true, the browser supports notifications (may not be granted though)
6025 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6026 first_notification_id: 0, // the id of the first alarm_log entry for this session
6027 // this is used to prevent CLEAR notifications for past events
6028 // notifications_shown: new Array(),
6030 server: null, // the server to connect to for fetching alarms
6031 current: null, // the list of raised alarms - updated in the background
6032 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6034 notify: function(entry) {
6035 // console.log('alarm ' + entry.unique_id);
6037 if(entry.updated === true) {
6038 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6042 var value = entry.value;
6043 if(NETDATA.alarms.current !== null) {
6044 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6045 if(typeof t !== 'undefined' && entry.status == t.status)
6049 var name = entry.name.replace(/_/g, ' ');
6050 var status = entry.status.toLowerCase();
6051 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6052 var tag = entry.alarm_id;
6053 var icon = 'images/seo-performance-128.png';
6054 var interaction = false;
6058 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6060 switch(entry.status) {
6068 case 'UNINITIALIZED':
6072 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6073 // console.log('alarm ' + entry.unique_id + ' is not current');
6076 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6077 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6080 title = name + ' back to normal';
6081 icon = 'images/check-mark-2-128-green.png'
6082 interaction = false;
6086 if(entry.old_status === 'CRITICAL')
6087 status = 'demoted to ' + entry.status.toLowerCase();
6089 icon = 'images/alert-128-orange.png';
6090 interaction = false;
6094 if(entry.old_status === 'WARNING')
6095 status = 'escalated to ' + entry.status.toLowerCase();
6097 icon = 'images/alert-128-red.png'
6102 console.log('invalid alarm status ' + entry.status);
6107 // cleanup old notifications with the same alarm_id as this one
6108 // FIXME: it does not seem to work on any web browser!
6109 var len = NETDATA.alarms.notifications_shown.length;
6111 var n = NETDATA.alarms.notifications_shown[len];
6112 if(n.data.alarm_id === entry.alarm_id) {
6113 console.log('removing old alarm ' + n.data.unique_id);
6115 // close the notification
6118 // remove it from the array
6119 NETDATA.alarms.notifications_shown.splice(len, 1);
6120 len = NETDATA.alarms.notifications_shown.length;
6127 setTimeout(function() {
6128 // show this notification
6129 // console.log('new notification: ' + title);
6130 var n = new Notification(title, {
6131 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6133 requireInteraction: interaction,
6134 icon: NETDATA.serverDefault + icon,
6138 n.onclick = function(event) {
6139 event.preventDefault();
6140 NETDATA.alarms.onclick(event.target.data);
6144 // NETDATA.alarms.notifications_shown.push(n);
6145 // console.log(entry);
6146 }, NETDATA.alarms.ms_penalty);
6148 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6152 scrollToChart: function(chart_id) {
6153 if(typeof chart_id === 'string') {
6154 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6155 if(typeof offset !== 'undefined') {
6156 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6163 scrollToAlarm: function(alarm) {
6164 if(typeof alarm === 'object') {
6165 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6167 if(ret === true && NETDATA.options.page_is_visible === false)
6169 // 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.');
6174 notifyAll: function() {
6175 // console.log('FETCHING ALARM LOG');
6176 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6177 // console.log('ALARM LOG FETCHED');
6179 if(data === null || typeof data !== 'object') {
6180 console.log('invalid alarms log response');
6184 if(data.length === 0) {
6185 console.log('received empty alarm log');
6189 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6191 data.sort(function(a, b) {
6192 if(a.unique_id > b.unique_id) return -1;
6193 if(a.unique_id < b.unique_id) return 1;
6197 NETDATA.alarms.ms_penalty = 0;
6199 var len = data.length;
6201 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6202 NETDATA.alarms.notify(data[len]);
6205 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6208 NETDATA.alarms.last_notification_id = data[0].unique_id;
6209 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6210 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6214 check_notifications: function() {
6215 // returns true if we should fire 1+ notifications
6217 if(NETDATA.alarms.notifications !== true) {
6218 // console.log('notifications not available');
6222 if(Notification.permission !== 'granted') {
6223 // console.log('notifications not granted');
6227 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6228 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6230 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6231 // console.log('new alarms detected');
6234 //else console.log('no new alarms');
6236 // else console.log('cannot process alarms');
6241 get: function(what, callback) {
6243 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6247 'Cache-Control': 'no-cache, no-store',
6248 'Pragma': 'no-cache'
6250 xhrFields: { withCredentials: true } // required for the cookie
6252 .done(function(data) {
6253 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6254 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6256 if(typeof callback === 'function')
6260 NETDATA.error(415, NETDATA.alarms.server);
6262 if(typeof callback === 'function')
6267 update_forever: function() {
6268 NETDATA.alarms.get('active', function(data) {
6270 NETDATA.alarms.current = data;
6272 if(NETDATA.alarms.check_notifications() === true) {
6273 NETDATA.alarms.notifyAll();
6276 if (typeof NETDATA.alarms.callback === 'function') {
6277 NETDATA.alarms.callback(data);
6280 // Health monitoring is disabled on this netdata
6281 if(data.status === false) return;
6284 setTimeout(NETDATA.alarms.update_forever, 10000);
6288 get_log: function(last_id, callback) {
6289 // console.log('fetching all log after ' + last_id.toString());
6291 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6295 'Cache-Control': 'no-cache, no-store',
6296 'Pragma': 'no-cache'
6298 xhrFields: { withCredentials: true } // required for the cookie
6300 .done(function(data) {
6301 if(typeof callback === 'function')
6305 NETDATA.error(416, NETDATA.alarms.server);
6307 if(typeof callback === 'function')
6313 var host = NETDATA.serverDefault;
6314 while(host.slice(-1) === '/')
6315 host = host.substring(0, host.length - 1);
6316 NETDATA.alarms.server = host;
6318 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6320 if(NETDATA.alarms.onclick === null)
6321 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6323 if(netdataShowAlarms === true) {
6324 NETDATA.alarms.update_forever();
6326 if('Notification' in window) {
6327 // console.log('notifications available');
6328 NETDATA.alarms.notifications = true;
6330 if(Notification.permission === 'default')
6331 Notification.requestPermission();
6337 // ----------------------------------------------------------------------------------------------------------------
6338 // Registry of netdata hosts
6340 NETDATA.registry = {
6341 server: null, // the netdata registry server
6342 person_guid: null, // the unique ID of this browser / user
6343 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6344 hostname: null, // the hostname of the netdata server that served dashboard.js
6345 machines: null, // the user's other URLs
6346 machines_array: null, // the user's other URLs in an array
6349 parsePersonUrls: function(person_urls) {
6350 // console.log(person_urls);
6351 NETDATA.registry.person_urls = person_urls;
6354 NETDATA.registry.machines = {};
6355 NETDATA.registry.machines_array = new Array();
6357 var now = Date.now();
6358 var apu = person_urls;
6361 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6362 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6368 accesses: apu[i][3],
6370 alternate_urls: new Array()
6372 obj.alternate_urls.push(apu[i][1]);
6374 NETDATA.registry.machines[apu[i][0]] = obj;
6375 NETDATA.registry.machines_array.push(obj);
6378 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6380 var pu = NETDATA.registry.machines[apu[i][0]];
6381 if(pu.last_t < apu[i][2]) {
6383 pu.last_t = apu[i][2];
6384 pu.name = apu[i][4];
6386 pu.accesses += apu[i][3];
6387 pu.alternate_urls.push(apu[i][1]);
6392 if(typeof netdataRegistryCallback === 'function')
6393 netdataRegistryCallback(NETDATA.registry.machines_array);
6397 if(netdataRegistry !== true) return;
6399 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6401 NETDATA.registry.server = data.registry;
6402 NETDATA.registry.machine_guid = data.machine_guid;
6403 NETDATA.registry.hostname = data.hostname;
6405 NETDATA.registry.access(2, function (person_urls) {
6406 NETDATA.registry.parsePersonUrls(person_urls);
6413 hello: function(host, callback) {
6414 while(host.slice(-1) === '/')
6415 host = host.substring(0, host.length - 1);
6417 // send HELLO to a netdata server:
6418 // 1. verifies the server is reachable
6419 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6421 url: host + '/api/v1/registry?action=hello',
6425 'Cache-Control': 'no-cache, no-store',
6426 'Pragma': 'no-cache'
6428 xhrFields: { withCredentials: true } // required for the cookie
6430 .done(function(data) {
6431 if(typeof data.status !== 'string' || data.status !== 'ok') {
6432 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6436 if(typeof callback === 'function')
6440 NETDATA.error(407, host);
6442 if(typeof callback === 'function')
6447 access: function(max_redirects, callback) {
6448 // send ACCESS to a netdata registry:
6449 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6450 // 2. it responds with a list of netdata servers we know
6451 // the registry identifies us using a cookie it sets the first time we access it
6452 // the registry may respond with a redirect URL to send us to another registry
6454 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),
6458 'Cache-Control': 'no-cache, no-store',
6459 'Pragma': 'no-cache'
6461 xhrFields: { withCredentials: true } // required for the cookie
6463 .done(function(data) {
6464 var redirect = null;
6465 if(typeof data.registry === 'string')
6466 redirect = data.registry;
6468 if(typeof data.status !== 'string' || data.status !== 'ok') {
6469 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6474 if(redirect !== null && max_redirects > 0) {
6475 NETDATA.registry.server = redirect;
6476 NETDATA.registry.access(max_redirects - 1, callback);
6479 if(typeof callback === 'function')
6484 if(typeof data.person_guid === 'string')
6485 NETDATA.registry.person_guid = data.person_guid;
6487 if(typeof callback === 'function')
6488 callback(data.urls);
6492 NETDATA.error(410, NETDATA.registry.server);
6494 if(typeof callback === 'function')
6499 delete: function(delete_url, callback) {
6500 // send DELETE to a netdata registry:
6502 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),
6506 'Cache-Control': 'no-cache, no-store',
6507 'Pragma': 'no-cache'
6509 xhrFields: { withCredentials: true } // required for the cookie
6511 .done(function(data) {
6512 if(typeof data.status !== 'string' || data.status !== 'ok') {
6513 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6517 if(typeof callback === 'function')
6521 NETDATA.error(412, NETDATA.registry.server);
6523 if(typeof callback === 'function')
6528 search: function(machine_guid, callback) {
6529 // SEARCH for the URLs of a machine:
6531 url: NETDATA.registry.server + '/api/v1/registry?action=search&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&for=' + machine_guid,
6535 'Cache-Control': 'no-cache, no-store',
6536 'Pragma': 'no-cache'
6538 xhrFields: { withCredentials: true } // required for the cookie
6540 .done(function(data) {
6541 if(typeof data.status !== 'string' || data.status !== 'ok') {
6542 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6546 if(typeof callback === 'function')
6550 NETDATA.error(418, NETDATA.registry.server);
6552 if(typeof callback === 'function')
6557 switch: function(new_person_guid, callback) {
6560 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,
6564 'Cache-Control': 'no-cache, no-store',
6565 'Pragma': 'no-cache'
6567 xhrFields: { withCredentials: true } // required for the cookie
6569 .done(function(data) {
6570 if(typeof data.status !== 'string' || data.status !== 'ok') {
6571 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6575 if(typeof callback === 'function')
6579 NETDATA.error(414, NETDATA.registry.server);
6581 if(typeof callback === 'function')
6587 // ----------------------------------------------------------------------------------------------------------------
6590 if(typeof netdataPrepCallback === 'function')
6591 netdataPrepCallback();
6593 NETDATA.errorReset();
6594 NETDATA.loadRequiredCSS(0);
6596 NETDATA._loadjQuery(function() {
6597 NETDATA.loadRequiredJs(0, function() {
6598 if(typeof $().emulateTransitionEnd !== 'function') {
6599 // bootstrap is not available
6600 NETDATA.options.current.show_help = false;
6603 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6604 if(NETDATA.options.debug.main_loop === true)
6605 console.log('starting chart refresh thread');
6611 })(window, document);