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;
1200 if(this.settings_id !== null) {
1201 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1202 // this is the callback that will be called
1203 // if and when the user resets all localStorage variables
1204 // to their defaults
1206 resizeChartToHeight(height);
1210 // string - the netdata server URL, without any path
1211 this.host = self.data('host') || NETDATA.chartDefaults.host;
1213 // make sure the host does not end with /
1214 // all netdata API requests use absolute paths
1215 while(this.host.slice(-1) === '/')
1216 this.host = this.host.substring(0, this.host.length - 1);
1218 // string - the grouping method requested by the user
1219 this.method = self.data('method') || NETDATA.chartDefaults.method;
1221 // the time-range requested by the user
1222 this.after = self.data('after') || NETDATA.chartDefaults.after;
1223 this.before = self.data('before') || NETDATA.chartDefaults.before;
1225 // the pixels per point requested by the user
1226 this.pixels_per_point = self.data('pixels-per-point') || 1;
1227 this.points = self.data('points') || null;
1229 // the dimensions requested by the user
1230 this.dimensions = self.data('dimensions') || null;
1232 // the chart library requested by the user
1233 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1235 // how many retries we have made to load chart data from the server
1236 this.retries_on_data_failures = 0;
1238 // object - the chart library used
1239 this.library = null;
1243 this.colors_assigned = {};
1244 this.colors_available = null;
1246 // the element already created by the user
1247 this.element_message = null;
1249 // the element with the chart
1250 this.element_chart = null;
1252 // the element with the legend of the chart (if created by us)
1253 this.element_legend = null;
1254 this.element_legend_childs = {
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.nano !== null && that.element_legend_childs.nano_options !== null)
1675 $(that.element_legend_childs.nano).nanoScroller();
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) {
1734 // double click / double tap event
1736 // the optimal height of the chart
1737 // showing the entire legend
1738 var optimal = this.event_resize.chart_last_h
1739 + this.element_legend_childs.content.scrollHeight
1740 - this.element_legend_childs.content.clientHeight;
1742 // if we are not optimal, be optimal
1743 if(this.event_resize.chart_last_h != optimal)
1744 resizeChartToHeight(optimal.toString() + 'px');
1746 // else if we do not have the original height
1747 // reset to the original height
1748 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1749 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1752 this.event_resize.last = now;
1754 // process movement event
1755 document.onmousemove =
1756 document.ontouchmove =
1757 this.element_legend_childs.resize_handler.onmousemove =
1758 this.element_legend_childs.resize_handler.ontouchmove =
1763 case 'mousemove': y = e.clientY; break;
1764 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1768 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1770 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1771 resizeChartToHeight(newH.toString() + 'px');
1772 that.event_resize.chart_last_h = newH;
1777 // process end event
1778 document.onmouseup =
1779 document.ontouchend =
1780 this.element_legend_childs.resize_handler.onmouseup =
1781 this.element_legend_childs.resize_handler.ontouchend =
1783 // remove all the hooks
1784 document.onmouseup =
1785 document.onmousemove =
1786 document.ontouchmove =
1787 document.ontouchend =
1788 that.element_legend_childs.resize_handler.onmousemove =
1789 that.element_legend_childs.resize_handler.ontouchmove =
1790 that.element_legend_childs.resize_handler.onmouseout =
1791 that.element_legend_childs.resize_handler.onmouseup =
1792 that.element_legend_childs.resize_handler.ontouchend =
1795 // allow auto-refreshes
1796 NETDATA.options.auto_refresher_stop_until = 0;
1802 var noDataToShow = function() {
1803 showMessageIcon('<i class="fa fa-warning"></i> empty');
1804 that.legendUpdateDOM();
1805 that.tm.last_autorefreshed = Date.now();
1806 // that.data_update_every = 30 * 1000;
1807 //that.element_chart.style.display = 'none';
1808 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1809 //that.___chartIsHidden___ = true;
1812 // ============================================================================================================
1815 this.error = function(msg) {
1819 this.setMode = function(m) {
1820 if(this.current !== null && this.current.name === m) return;
1823 this.current = this.auto;
1824 else if(m === 'pan')
1825 this.current = this.pan;
1826 else if(m === 'zoom')
1827 this.current = this.zoom;
1829 this.current = this.auto;
1831 this.current.force_update_at = 0;
1832 this.current.force_before_ms = null;
1833 this.current.force_after_ms = null;
1835 this.tm.last_mode_switch = Date.now();
1838 // ----------------------------------------------------------------------------------------------------------------
1839 // global selection sync
1841 // prevent to global selection sync for some time
1842 this.globalSelectionSyncDelay = function(ms) {
1843 if(NETDATA.options.current.sync_selection === false)
1846 if(typeof ms === 'number')
1847 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1849 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1852 // can we globally apply selection sync?
1853 this.globalSelectionSyncAbility = function() {
1854 if(NETDATA.options.current.sync_selection === false)
1857 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1863 this.globalSelectionSyncIsMaster = function() {
1864 if(NETDATA.globalSelectionSync.state === this)
1870 // this chart is the master of the global selection sync
1871 this.globalSelectionSyncBeMaster = function() {
1873 if(this.globalSelectionSyncIsMaster()) {
1874 if(this.debug === true)
1875 this.log('sync: I am the master already.');
1880 if(NETDATA.globalSelectionSync.state) {
1881 if(this.debug === true)
1882 this.log('sync: I am not the sync master. Resetting global sync.');
1884 this.globalSelectionSyncStop();
1887 // become the master
1888 if(this.debug === true)
1889 this.log('sync: becoming sync master.');
1891 this.selected = true;
1892 NETDATA.globalSelectionSync.state = this;
1894 // find the all slaves
1895 var targets = NETDATA.options.targets;
1896 var len = targets.length;
1901 if(this.debug === true)
1902 st.log('sync: not adding me to sync');
1904 else if(st.globalSelectionSyncIsEligible()) {
1905 if(this.debug === true)
1906 st.log('sync: adding to sync as slave');
1908 st.globalSelectionSyncBeSlave();
1912 // this.globalSelectionSyncDelay(100);
1915 // can the chart participate to the global selection sync as a slave?
1916 this.globalSelectionSyncIsEligible = function() {
1917 if(this.enabled === true
1918 && this.library !== null
1919 && typeof this.library.setSelection === 'function'
1920 && this.isVisible() === true
1921 && this.chart_created === true)
1927 // this chart becomes a slave of the global selection sync
1928 this.globalSelectionSyncBeSlave = function() {
1929 if(NETDATA.globalSelectionSync.state !== this)
1930 NETDATA.globalSelectionSync.slaves.push(this);
1933 // sync all the visible charts to the given time
1934 // this is to be called from the chart libraries
1935 this.globalSelectionSync = function(t) {
1936 if(this.globalSelectionSyncAbility() === false) {
1937 if(this.debug === true)
1938 this.log('sync: cannot sync (yet?).');
1943 if(this.globalSelectionSyncIsMaster() === false) {
1944 if(this.debug === true)
1945 this.log('sync: trying to be sync master.');
1947 this.globalSelectionSyncBeMaster();
1949 if(this.globalSelectionSyncAbility() === false) {
1950 if(this.debug === true)
1951 this.log('sync: cannot sync (yet?).');
1957 NETDATA.globalSelectionSync.last_t = t;
1958 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1963 // stop syncing all charts to the given time
1964 this.globalSelectionSyncStop = function() {
1965 if(NETDATA.globalSelectionSync.slaves.length) {
1966 if(this.debug === true)
1967 this.log('sync: cleaning up...');
1969 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1971 if(that.debug === true)
1972 st.log('sync: not adding me to sync stop');
1975 if(that.debug === true)
1976 st.log('sync: removed slave from sync');
1978 st.clearSelection();
1982 NETDATA.globalSelectionSync.last_t = 0;
1983 NETDATA.globalSelectionSync.slaves = [];
1984 NETDATA.globalSelectionSync.state = null;
1987 this.clearSelection();
1990 this.setSelection = function(t) {
1991 if(typeof this.library.setSelection === 'function') {
1992 if(this.library.setSelection(this, t) === true)
1993 this.selected = true;
1995 this.selected = false;
1997 else this.selected = true;
1999 if(this.selected === true && this.debug === true)
2000 this.log('selection set to ' + t.toString());
2002 return this.selected;
2005 this.clearSelection = function() {
2006 if(this.selected === true) {
2007 if(typeof this.library.clearSelection === 'function') {
2008 if(this.library.clearSelection(this) === true)
2009 this.selected = false;
2011 this.selected = true;
2013 else this.selected = false;
2015 if(this.selected === false && this.debug === true)
2016 this.log('selection cleared');
2021 return this.selected;
2024 // find if a timestamp (ms) is shown in the current chart
2025 this.timeIsVisible = function(t) {
2026 if(t >= this.data_after && t <= this.data_before)
2031 this.calculateRowForTime = function(t) {
2032 if(this.timeIsVisible(t) === false) return -1;
2033 return Math.floor((t - this.data_after) / this.data_update_every);
2036 // ----------------------------------------------------------------------------------------------------------------
2039 this.log = function(msg) {
2040 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2043 this.pauseChart = function() {
2044 if(this.paused === false) {
2045 if(this.debug === true)
2046 this.log('pauseChart()');
2052 this.unpauseChart = function() {
2053 if(this.paused === true) {
2054 if(this.debug === true)
2055 this.log('unpauseChart()');
2057 this.paused = false;
2061 this.resetChart = function(dont_clear_master, dont_update) {
2062 if(this.debug === true)
2063 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2065 if(typeof dont_clear_master === 'undefined')
2066 dont_clear_master = false;
2068 if(typeof dont_update === 'undefined')
2069 dont_update = false;
2071 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2072 if(this.debug === true)
2073 this.log('resetChart() diverting to clearMaster().');
2074 // this will call us back with master === true
2075 NETDATA.globalPanAndZoom.clearMaster();
2079 this.clearSelection();
2081 this.tm.pan_and_zoom_seq = 0;
2083 this.setMode('auto');
2084 this.current.force_update_at = 0;
2085 this.current.force_before_ms = null;
2086 this.current.force_after_ms = null;
2087 this.tm.last_autorefreshed = 0;
2088 this.paused = false;
2089 this.selected = false;
2090 this.enabled = true;
2091 // this.debug = false;
2093 // do not update the chart here
2094 // or the chart will flip-flop when it is the master
2095 // of a selection sync and another chart becomes
2098 if(dont_update !== true && this.isVisible() === true) {
2103 this.updateChartPanOrZoom = function(after, before) {
2104 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2107 if(this.debug === true)
2110 if(before < after) {
2111 if(this.debug === true)
2112 this.log(logme + 'flipped parameters, rejecting it.');
2117 if(typeof this.fixed_min_duration === 'undefined')
2118 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2120 var min_duration = this.fixed_min_duration;
2121 var current_duration = Math.round(this.view_before - this.view_after);
2123 // round the numbers
2124 after = Math.round(after);
2125 before = Math.round(before);
2127 // align them to update_every
2128 // stretching them further away
2129 after -= after % this.data_update_every;
2130 before += this.data_update_every - (before % this.data_update_every);
2132 // the final wanted duration
2133 var wanted_duration = before - after;
2135 // to allow panning, accept just a point below our minimum
2136 if((current_duration - this.data_update_every) < min_duration)
2137 min_duration = current_duration - this.data_update_every;
2139 // we do it, but we adjust to minimum size and return false
2140 // when the wanted size is below the current and the minimum
2142 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2143 if(this.debug === true)
2144 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2146 min_duration = this.fixed_min_duration;
2148 var dt = (min_duration - wanted_duration) / 2;
2151 wanted_duration = before - after;
2155 var tolerance = this.data_update_every * 2;
2156 var movement = Math.abs(before - this.view_before);
2158 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2159 if(this.debug === true)
2160 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);
2164 if(this.current.name === 'auto') {
2165 this.log(logme + 'caller called me with mode: ' + this.current.name);
2166 this.setMode('pan');
2169 if(this.debug === true)
2170 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);
2172 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2173 this.current.force_after_ms = after;
2174 this.current.force_before_ms = before;
2175 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2179 this.legendFormatValue = function(value) {
2180 if(value === null || value === 'undefined') return '-';
2181 if(typeof value !== 'number') return value;
2183 if(this.value_decimal_detail !== -1)
2184 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2186 var abs = Math.abs(value);
2187 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2188 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2189 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2190 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2191 return (Math.round(value * 10000) / 10000).toLocaleString();
2194 this.legendSetLabelValue = function(label, value) {
2195 var series = this.element_legend_childs.series[label];
2196 if(typeof series === 'undefined') return;
2197 if(series.value === null && series.user === null) return;
2200 // this slows down firefox and edge significantly
2201 // since it requires to use innerHTML(), instead of innerText()
2203 // if the value has not changed, skip DOM update
2204 //if(series.last === value) return;
2207 if(typeof value === 'number') {
2208 var v = Math.abs(value);
2209 s = r = this.legendFormatValue(value);
2211 if(typeof series.last === 'number') {
2212 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2213 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2214 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2216 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2226 series.last = value;
2230 var s = this.legendFormatValue(value);
2232 // caching: do not update the update to show the same value again
2233 if(s === series.last_shown_value) return;
2234 series.last_shown_value = s;
2236 if(series.value !== null) series.value.innerText = s;
2237 if(series.user !== null) series.user.innerText = s;
2240 this.__legendSetDateString = function(date) {
2241 if(date !== this.__last_shown_legend_date) {
2242 this.element_legend_childs.title_date.innerText = date;
2243 this.__last_shown_legend_date = date;
2247 this.__legendSetTimeString = function(time) {
2248 if(time !== this.__last_shown_legend_time) {
2249 this.element_legend_childs.title_time.innerText = time;
2250 this.__last_shown_legend_time = time;
2254 this.__legendSetUnitsString = function(units) {
2255 if(units !== this.__last_shown_legend_units) {
2256 this.element_legend_childs.title_units.innerText = units;
2257 this.__last_shown_legend_units = units;
2261 this.legendSetDate = function(ms) {
2262 if(typeof ms !== 'number') {
2263 this.legendShowUndefined();
2267 var d = new Date(ms);
2269 if(this.element_legend_childs.title_date)
2270 this.__legendSetDateString(d.toLocaleDateString());
2272 if(this.element_legend_childs.title_time)
2273 this.__legendSetTimeString(d.toLocaleTimeString());
2275 if(this.element_legend_childs.title_units)
2276 this.__legendSetUnitsString(this.units)
2279 this.legendShowUndefined = function() {
2280 if(this.element_legend_childs.title_date)
2281 this.__legendSetDateString(' ');
2283 if(this.element_legend_childs.title_time)
2284 this.__legendSetTimeString(this.chart.name);
2286 if(this.element_legend_childs.title_units)
2287 this.__legendSetUnitsString(' ')
2289 if(this.data && this.element_legend_childs.series !== null) {
2290 var labels = this.data.dimension_names;
2291 var i = labels.length;
2293 var label = labels[i];
2295 if(typeof label === 'undefined') continue;
2296 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2297 this.legendSetLabelValue(label, null);
2302 this.legendShowLatestValues = function() {
2303 if(this.chart === null) return;
2304 if(this.selected) return;
2306 if(this.data === null || this.element_legend_childs.series === null) {
2307 this.legendShowUndefined();
2311 var show_undefined = true;
2312 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2313 show_undefined = false;
2315 if(show_undefined) {
2316 this.legendShowUndefined();
2320 this.legendSetDate(this.view_before);
2322 var labels = this.data.dimension_names;
2323 var i = labels.length;
2325 var label = labels[i];
2327 if(typeof label === 'undefined') continue;
2328 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2331 this.legendSetLabelValue(label, null);
2333 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2337 this.legendReset = function() {
2338 this.legendShowLatestValues();
2341 // this should be called just ONCE per dimension per chart
2342 this._chartDimensionColor = function(label) {
2343 if(this.colors === null) this.chartColors();
2345 if(typeof this.colors_assigned[label] === 'undefined') {
2346 if(this.colors_available.length === 0) {
2347 var len = NETDATA.themes.current.colors.length;
2349 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2352 this.colors_assigned[label] = this.colors_available.shift();
2354 if(this.debug === true)
2355 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2358 if(this.debug === true)
2359 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2362 this.colors.push(this.colors_assigned[label]);
2363 return this.colors_assigned[label];
2366 this.chartColors = function() {
2367 if(this.colors !== null) return this.colors;
2369 this.colors = new Array();
2370 this.colors_available = new Array();
2372 // add the standard colors
2373 var len = NETDATA.themes.current.colors.length;
2375 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2377 // add the user supplied colors
2378 var c = $(this.element).data('colors');
2379 // this.log('read colors: ' + c);
2380 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2381 if(typeof c !== 'string') {
2382 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2392 this.colors_available.unshift(c[len]);
2393 // this.log('adding color: ' + c[len]);
2402 this.legendUpdateDOM = function() {
2405 // check that the legend DOM is up to date for the downloaded dimensions
2406 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2407 // this.log('the legend does not have any series - requesting legend update');
2410 else if(this.data === null) {
2411 // this.log('the chart does not have any data - requesting legend update');
2414 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2418 var labels = this.data.dimension_names.toString();
2419 if(labels !== this.element_legend_childs.series.labels_key) {
2422 if(this.debug === true)
2423 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2427 if(needed === false) {
2428 // make sure colors available
2431 // do we have to update the current values?
2432 // we do this, only when the visible chart is current
2433 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2434 if(this.debug === true)
2435 this.log('chart is in latest position... updating values on legend...');
2437 //var labels = this.data.dimension_names;
2438 //var i = labels.length;
2440 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2444 if(this.colors === null) {
2445 // this is the first time we update the chart
2446 // let's assign colors to all dimensions
2447 if(this.library.track_colors() === true)
2448 for(var dim in this.chart.dimensions)
2449 this._chartDimensionColor(this.chart.dimensions[dim].name);
2451 // we will re-generate the colors for the chart
2452 // based on the selected dimensions
2455 if(this.debug === true)
2456 this.log('updating Legend DOM');
2458 // mark all dimensions as invalid
2459 this.dimensions_visibility.invalidateAll();
2461 var genLabel = function(state, parent, dim, name, count) {
2462 var color = state._chartDimensionColor(name);
2464 var user_element = null;
2465 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2466 if(user_id === null)
2467 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2468 if(user_id !== null) {
2469 user_element = document.getElementById(user_id) || null;
2470 if (user_element === null)
2471 state.log('Cannot find element with id: ' + user_id);
2474 state.element_legend_childs.series[name] = {
2475 name: document.createElement('span'),
2476 value: document.createElement('span'),
2479 last_shown_value: null
2482 var label = state.element_legend_childs.series[name];
2484 // create the dimension visibility tracking for this label
2485 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2487 var rgb = NETDATA.colorHex2Rgb(color);
2488 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2489 + state.chart.chart_type
2490 + '" style="background-color: '
2491 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2492 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2494 var text = document.createTextNode(' ' + name);
2495 label.name.appendChild(text);
2498 parent.appendChild(document.createElement('br'));
2500 parent.appendChild(label.name);
2501 parent.appendChild(label.value);
2504 var content = document.createElement('div');
2506 if(this.hasLegend()) {
2507 this.element_legend_childs = {
2509 resize_handler: document.createElement('div'),
2510 toolbox: document.createElement('div'),
2511 toolbox_left: document.createElement('div'),
2512 toolbox_right: document.createElement('div'),
2513 toolbox_reset: document.createElement('div'),
2514 toolbox_zoomin: document.createElement('div'),
2515 toolbox_zoomout: document.createElement('div'),
2516 toolbox_volume: document.createElement('div'),
2517 title_date: document.createElement('span'),
2518 title_time: document.createElement('span'),
2519 title_units: document.createElement('span'),
2520 nano: document.createElement('div'),
2522 paneClass: 'netdata-legend-series-pane',
2523 sliderClass: 'netdata-legend-series-slider',
2524 contentClass: 'netdata-legend-series-content',
2525 enabledClass: '__enabled',
2526 flashedClass: '__flashed',
2527 activeClass: '__active',
2529 alwaysVisible: true,
2535 this.element_legend.innerHTML = '';
2537 if(this.library.toolboxPanAndZoom !== null) {
2539 function get_pan_and_zoom_step(event) {
2541 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2543 else if (event.shiftKey)
2544 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2546 else if (event.altKey)
2547 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2550 return NETDATA.options.current.pan_and_zoom_factor;
2553 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2554 this.element.appendChild(this.element_legend_childs.toolbox);
2556 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2557 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2558 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2559 this.element_legend_childs.toolbox_left.onclick = function(e) {
2562 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2563 var before = that.view_before - step;
2564 var after = that.view_after - step;
2565 if(after >= that.netdata_first)
2566 that.library.toolboxPanAndZoom(that, after, before);
2568 if(NETDATA.options.current.show_help === true)
2569 $(this.element_legend_childs.toolbox_left).popover({
2574 placement: 'bottom',
2575 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2577 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>'
2581 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2582 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2583 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2584 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2586 NETDATA.resetAllCharts(that);
2588 if(NETDATA.options.current.show_help === true)
2589 $(this.element_legend_childs.toolbox_reset).popover({
2594 placement: 'bottom',
2595 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2596 title: 'Chart Reset',
2597 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>'
2600 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2601 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2602 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2603 this.element_legend_childs.toolbox_right.onclick = function(e) {
2605 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2606 var before = that.view_before + step;
2607 var after = that.view_after + step;
2608 if(before <= that.netdata_last)
2609 that.library.toolboxPanAndZoom(that, after, before);
2611 if(NETDATA.options.current.show_help === true)
2612 $(this.element_legend_childs.toolbox_right).popover({
2617 placement: 'bottom',
2618 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2620 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>'
2624 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2625 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2626 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2627 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2629 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2630 var before = that.view_before - dt;
2631 var after = that.view_after + dt;
2632 that.library.toolboxPanAndZoom(that, after, before);
2634 if(NETDATA.options.current.show_help === true)
2635 $(this.element_legend_childs.toolbox_zoomin).popover({
2640 placement: 'bottom',
2641 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2642 title: 'Chart Zoom In',
2643 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>'
2646 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2647 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2648 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2649 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2651 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);
2652 var before = that.view_before + dt;
2653 var after = that.view_after - dt;
2655 that.library.toolboxPanAndZoom(that, after, before);
2657 if(NETDATA.options.current.show_help === true)
2658 $(this.element_legend_childs.toolbox_zoomout).popover({
2663 placement: 'bottom',
2664 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2665 title: 'Chart Zoom Out',
2666 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>'
2669 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2670 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2671 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2672 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2673 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2674 //e.preventDefault();
2675 //alert('clicked toolbox_volume on ' + that.id);
2679 this.element_legend_childs.toolbox = null;
2680 this.element_legend_childs.toolbox_left = null;
2681 this.element_legend_childs.toolbox_reset = null;
2682 this.element_legend_childs.toolbox_right = null;
2683 this.element_legend_childs.toolbox_zoomin = null;
2684 this.element_legend_childs.toolbox_zoomout = null;
2685 this.element_legend_childs.toolbox_volume = null;
2688 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2689 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2690 this.element.appendChild(this.element_legend_childs.resize_handler);
2691 if(NETDATA.options.current.show_help === true)
2692 $(this.element_legend_childs.resize_handler).popover({
2697 placement: 'bottom',
2698 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2699 title: 'Chart Resize',
2700 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>'
2704 this.element_legend_childs.resize_handler.onmousedown =
2706 that.resizeHandler(e);
2710 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2711 that.resizeHandler(e);
2714 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2715 this.element_legend.appendChild(this.element_legend_childs.title_date);
2717 this.element_legend.appendChild(document.createElement('br'));
2719 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2720 this.element_legend.appendChild(this.element_legend_childs.title_time);
2722 this.element_legend.appendChild(document.createElement('br'));
2724 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2725 this.element_legend.appendChild(this.element_legend_childs.title_units);
2727 this.element_legend.appendChild(document.createElement('br'));
2729 this.element_legend_childs.nano.className = 'netdata-legend-series';
2730 this.element_legend.appendChild(this.element_legend_childs.nano);
2732 content.className = 'netdata-legend-series-content';
2733 this.element_legend_childs.nano.appendChild(content);
2735 if(NETDATA.options.current.show_help === true)
2736 $(content).popover({
2741 placement: 'bottom',
2742 title: 'Chart Legend',
2743 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2744 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>'
2748 this.element_legend_childs = {
2750 resize_handler: null,
2753 toolbox_right: null,
2754 toolbox_reset: null,
2755 toolbox_zoomin: null,
2756 toolbox_zoomout: null,
2757 toolbox_volume: null,
2768 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2769 if(this.debug === true)
2770 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2772 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2773 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2777 var tmp = new Array();
2778 for(var dim in this.chart.dimensions) {
2779 tmp.push(this.chart.dimensions[dim].name);
2780 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2782 this.element_legend_childs.series.labels_key = tmp.toString();
2783 if(this.debug === true)
2784 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2787 // create a hidden div to be used for hidding
2788 // the original legend of the chart library
2789 var el = document.createElement('div');
2790 if(this.element_legend !== null)
2791 this.element_legend.appendChild(el);
2792 el.style.display = 'none';
2794 this.element_legend_childs.hidden = document.createElement('div');
2795 el.appendChild(this.element_legend_childs.hidden);
2797 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2798 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2800 this.legendShowLatestValues();
2803 this.hasLegend = function() {
2804 if(typeof this.___hasLegendCache___ !== 'undefined')
2805 return this.___hasLegendCache___;
2808 if(this.library && this.library.legend(this) === 'right-side') {
2809 var legend = $(this.element).data('legend') || 'yes';
2810 if(legend === 'yes') leg = true;
2813 this.___hasLegendCache___ = leg;
2817 this.legendWidth = function() {
2818 return (this.hasLegend())?140:0;
2821 this.legendHeight = function() {
2822 return $(this.element).height();
2825 this.chartWidth = function() {
2826 return $(this.element).width() - this.legendWidth();
2829 this.chartHeight = function() {
2830 return $(this.element).height();
2833 this.chartPixelsPerPoint = function() {
2834 // force an options provided detail
2835 var px = this.pixels_per_point;
2837 if(this.library && px < this.library.pixels_per_point(this))
2838 px = this.library.pixels_per_point(this);
2840 if(px < NETDATA.options.current.pixels_per_point)
2841 px = NETDATA.options.current.pixels_per_point;
2846 this.needsRecreation = function() {
2848 this.chart_created === true
2850 && this.library.autoresize() === false
2851 && this.tm.last_resized < NETDATA.options.last_resized
2855 this.chartURL = function() {
2856 var after, before, points_multiplier = 1;
2857 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2858 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2860 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2861 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2862 this.view_after = after * 1000;
2863 this.view_before = before * 1000;
2865 this.requested_padding = null;
2866 points_multiplier = 1;
2868 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2869 this.tm.pan_and_zoom_seq = 0;
2871 before = Math.round(this.current.force_before_ms / 1000);
2872 after = Math.round(this.current.force_after_ms / 1000);
2873 this.view_after = after * 1000;
2874 this.view_before = before * 1000;
2876 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2877 this.requested_padding = Math.round((before - after) / 2);
2878 after -= this.requested_padding;
2879 before += this.requested_padding;
2880 this.requested_padding *= 1000;
2881 points_multiplier = 2;
2884 this.current.force_before_ms = null;
2885 this.current.force_after_ms = null;
2888 this.tm.pan_and_zoom_seq = 0;
2890 before = this.before;
2892 this.view_after = after * 1000;
2893 this.view_before = before * 1000;
2895 this.requested_padding = null;
2896 points_multiplier = 1;
2899 this.requested_after = after * 1000;
2900 this.requested_before = before * 1000;
2902 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2904 // build the data URL
2905 this.data_url = this.host + this.chart.data_url;
2906 this.data_url += "&format=" + this.library.format();
2907 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2908 this.data_url += "&group=" + this.method;
2910 if(this.override_options !== null)
2911 this.data_url += "&options=" + this.override_options.toString();
2913 this.data_url += "&options=" + this.library.options(this);
2915 this.data_url += '|jsonwrap';
2917 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2918 this.data_url += '|nonzero';
2920 if(this.append_options !== null)
2921 this.data_url += '|' + this.append_options.toString();
2924 this.data_url += "&after=" + after.toString();
2927 this.data_url += "&before=" + before.toString();
2930 this.data_url += "&dimensions=" + this.dimensions;
2932 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2933 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2936 this.redrawChart = function() {
2937 if(this.data !== null)
2938 this.updateChartWithData(this.data);
2941 this.updateChartWithData = function(data) {
2942 if(this.debug === true)
2943 this.log('updateChartWithData() called.');
2945 // this may force the chart to be re-created
2949 this.updates_counter++;
2950 this.updates_since_last_unhide++;
2951 this.updates_since_last_creation++;
2953 var started = Date.now();
2955 // if the result is JSON, find the latest update-every
2956 this.data_update_every = data.view_update_every * 1000;
2957 this.data_after = data.after * 1000;
2958 this.data_before = data.before * 1000;
2959 this.netdata_first = data.first_entry * 1000;
2960 this.netdata_last = data.last_entry * 1000;
2961 this.data_points = data.points;
2964 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2965 if(this.view_after < this.data_after) {
2966 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2967 this.view_after = this.data_after;
2970 if(this.view_before > this.data_before) {
2971 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2972 this.view_before = this.data_before;
2976 this.view_after = this.data_after;
2977 this.view_before = this.data_before;
2980 if(this.debug === true) {
2981 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2983 if(this.current.force_after_ms)
2984 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2986 this.log('STATUS: forced : unset');
2988 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2989 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2990 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2991 this.log('STATUS: points : ' + (this.data_points).toString());
2994 if(this.data_points === 0) {
2999 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3000 if(this.debug === true)
3001 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3003 this.chart_created = false;
3006 // check and update the legend
3007 this.legendUpdateDOM();
3009 if(this.chart_created === true
3010 && typeof this.library.update === 'function') {
3012 if(this.debug === true)
3013 this.log('updating chart...');
3015 if(callChartLibraryUpdateSafely(data) === false)
3019 if(this.debug === true)
3020 this.log('creating chart...');
3022 if(callChartLibraryCreateSafely(data) === false)
3026 this.legendShowLatestValues();
3027 if(this.selected === true)
3028 NETDATA.globalSelectionSync.stop();
3030 // update the performance counters
3031 var now = Date.now();
3032 this.tm.last_updated = now;
3034 // don't update last_autorefreshed if this chart is
3035 // forced to be updated with global PanAndZoom
3036 if(NETDATA.globalPanAndZoom.isActive())
3037 this.tm.last_autorefreshed = 0;
3039 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3040 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3042 this.tm.last_autorefreshed = now;
3045 this.refresh_dt_ms = now - started;
3046 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3048 if(this.refresh_dt_element !== null)
3049 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3052 this.updateChart = function(callback) {
3053 if(this.debug === true)
3054 this.log('updateChart() called.');
3056 if(this._updating === true) {
3057 if(this.debug === true)
3058 this.log('I am already updating...');
3060 if(typeof callback === 'function') callback();
3064 // due to late initialization of charts and libraries
3065 // we need to check this too
3066 if(this.enabled === false) {
3067 if(this.debug === true)
3068 this.log('I am not enabled');
3070 if(typeof callback === 'function') callback();
3074 if(canBeRendered() === false) {
3075 if(typeof callback === 'function') callback();
3079 if(this.chart === null) {
3080 this.getChart(function() { that.updateChart(callback); });
3084 if(this.library.initialized === false) {
3085 if(this.library.enabled === true) {
3086 this.library.initialize(function() { that.updateChart(callback); });
3090 error('chart library "' + this.library_name + '" is not available.');
3091 if(typeof callback === 'function') callback();
3096 this.clearSelection();
3099 if(this.debug === true)
3100 this.log('updating from ' + this.data_url);
3102 NETDATA.statistics.refreshes_total++;
3103 NETDATA.statistics.refreshes_active++;
3105 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3106 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3108 this._updating = true;
3110 this.xhr = $.ajax( {
3115 'Cache-Control': 'no-cache, no-store',
3116 'Pragma': 'no-cache'
3118 xhrFields: { withCredentials: true } // required for the cookie
3120 .done(function(data) {
3121 that.xhr = undefined;
3122 that.retries_on_data_failures = 0;
3124 if(that.debug === true)
3125 that.log('data received. updating chart.');
3127 that.updateChartWithData(data);
3129 .fail(function(msg) {
3130 that.xhr = undefined;
3132 if(msg.statusText !== 'abort') {
3133 that.retries_on_data_failures++;
3134 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3135 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3136 that.retries_on_data_failures = 0;
3137 error('data download failed for url: ' + that.data_url);
3140 that.tm.last_autorefreshed = Date.now();
3141 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3145 .always(function() {
3146 that.xhr = undefined;
3148 NETDATA.statistics.refreshes_active--;
3149 that._updating = false;
3150 if(typeof callback === 'function') callback();
3156 this.isVisible = function(nocache) {
3157 if(typeof nocache === 'undefined')
3160 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3162 // caching - we do not evaluate the charts visibility
3163 // if the page has not been scrolled since the last check
3164 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3165 return this.___isVisible___;
3167 this.tm.last_visible_check = Date.now();
3169 var wh = window.innerHeight;
3170 var x = this.element.getBoundingClientRect();
3174 if(x.width === 0 || x.height === 0) {
3176 this.___isVisible___ = false;
3177 return this.___isVisible___;
3180 if(x.top < 0 && -x.top > x.height) {
3181 // the chart is entirely above
3182 ret = -x.top - x.height;
3184 else if(x.top > wh) {
3185 // the chart is entirely below
3189 if(ret > tolerance) {
3190 // the chart is too far
3193 this.___isVisible___ = false;
3194 return this.___isVisible___;
3197 // the chart is inside or very close
3200 this.___isVisible___ = true;
3201 return this.___isVisible___;
3205 this.isAutoRefreshable = function() {
3206 return (this.current.autorefresh);
3209 this.canBeAutoRefreshed = function() {
3210 var now = Date.now();
3212 if(this.running === true) {
3213 if(this.debug === true)
3214 this.log('I am already running');
3219 if(this.enabled === false) {
3220 if(this.debug === true)
3221 this.log('I am not enabled');
3226 if(this.library === null || this.library.enabled === false) {
3227 error('charting library "' + this.library_name + '" is not available');
3228 if(this.debug === true)
3229 this.log('My chart library ' + this.library_name + ' is not available');
3234 if(this.isVisible() === false) {
3235 if(NETDATA.options.debug.visibility === true || this.debug === true)
3236 this.log('I am not visible');
3241 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3242 if(this.debug === true)
3243 this.log('timed force update detected - allowing this update');
3245 this.current.force_update_at = 0;
3249 if(this.isAutoRefreshable() === true) {
3250 // allow the first update, even if the page is not visible
3251 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3252 if(NETDATA.options.debug.focus === true || this.debug === true)
3253 this.log('canBeAutoRefreshed(): page does not have focus');
3258 if(this.needsRecreation() === true) {
3259 if(this.debug === true)
3260 this.log('canBeAutoRefreshed(): needs re-creation.');
3265 // options valid only for autoRefresh()
3266 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3267 if(NETDATA.globalPanAndZoom.isActive()) {
3268 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3269 if(this.debug === true)
3270 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3275 if(this.debug === true)
3276 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3282 if(this.selected === true) {
3283 if(this.debug === true)
3284 this.log('canBeAutoRefreshed(): I have a selection in place.');
3289 if(this.paused === true) {
3290 if(this.debug === true)
3291 this.log('canBeAutoRefreshed(): I am paused.');
3296 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3297 if(this.debug === true)
3298 this.log('canBeAutoRefreshed(): It is time to update me.');
3308 this.autoRefresh = function(callback) {
3309 if(this.canBeAutoRefreshed() === true && this.running === false) {
3312 state.running = true;
3313 state.updateChart(function() {
3314 state.running = false;
3316 if(typeof callback !== 'undefined')
3321 if(typeof callback !== 'undefined')
3326 this._defaultsFromDownloadedChart = function(chart) {
3328 this.chart_url = chart.url;
3329 this.data_update_every = chart.update_every * 1000;
3330 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3331 this.tm.last_info_downloaded = Date.now();
3333 if(this.title === null)
3334 this.title = chart.title;
3336 if(this.units === null)
3337 this.units = chart.units;
3340 // fetch the chart description from the netdata server
3341 this.getChart = function(callback) {
3342 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3344 this._defaultsFromDownloadedChart(this.chart);
3345 if(typeof callback === 'function') callback();
3348 this.chart_url = "/api/v1/chart?chart=" + this.id;
3350 if(this.debug === true)
3351 this.log('downloading ' + this.chart_url);
3354 url: this.host + this.chart_url,
3357 xhrFields: { withCredentials: true } // required for the cookie
3359 .done(function(chart) {
3360 chart.url = that.chart_url;
3361 that._defaultsFromDownloadedChart(chart);
3362 NETDATA.chartRegistry.add(that.host, that.id, chart);
3365 NETDATA.error(404, that.chart_url);
3366 error('chart not found on url "' + that.chart_url + '"');
3368 .always(function() {
3369 if(typeof callback === 'function') callback();
3374 // ============================================================================================================
3380 NETDATA.resetAllCharts = function(state) {
3381 // first clear the global selection sync
3382 // to make sure no chart is in selected state
3383 state.globalSelectionSyncStop();
3385 // there are 2 possibilities here
3386 // a. state is the global Pan and Zoom master
3387 // b. state is not the global Pan and Zoom master
3389 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3392 // clear the global Pan and Zoom
3393 // this will also refresh the master
3394 // and unblock any charts currently mirroring the master
3395 NETDATA.globalPanAndZoom.clearMaster();
3397 // if we were not the master, reset our status too
3398 // this is required because most probably the mouse
3399 // is over this chart, blocking it from auto-refreshing
3400 if(master === false && (state.paused === true || state.selected === true))
3404 // get or create a chart state, given a DOM element
3405 NETDATA.chartState = function(element) {
3406 var state = $(element).data('netdata-state-object') || null;
3407 if(state === null) {
3408 state = new chartState(element);
3409 $(element).data('netdata-state-object', state);
3414 // ----------------------------------------------------------------------------------------------------------------
3415 // Library functions
3417 // Load a script without jquery
3418 // This is used to load jquery - after it is loaded, we use jquery
3419 NETDATA._loadjQuery = function(callback) {
3420 if(typeof jQuery === 'undefined') {
3421 if(NETDATA.options.debug.main_loop === true)
3422 console.log('loading ' + NETDATA.jQuery);
3424 var script = document.createElement('script');
3425 script.type = 'text/javascript';
3426 script.async = true;
3427 script.src = NETDATA.jQuery;
3429 // script.onabort = onError;
3430 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3431 if(typeof callback === "function")
3432 script.onload = callback;
3434 var s = document.getElementsByTagName('script')[0];
3435 s.parentNode.insertBefore(script, s);
3437 else if(typeof callback === "function")
3441 NETDATA._loadCSS = function(filename) {
3442 // don't use jQuery here
3443 // styles are loaded before jQuery
3444 // to eliminate showing an unstyled page to the user
3446 var fileref = document.createElement("link");
3447 fileref.setAttribute("rel", "stylesheet");
3448 fileref.setAttribute("type", "text/css");
3449 fileref.setAttribute("href", filename);
3451 if (typeof fileref !== 'undefined')
3452 document.getElementsByTagName("head")[0].appendChild(fileref);
3455 NETDATA.colorHex2Rgb = function(hex) {
3456 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3457 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3458 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3459 return r + r + g + g + b + b;
3462 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3464 r: parseInt(result[1], 16),
3465 g: parseInt(result[2], 16),
3466 b: parseInt(result[3], 16)
3470 NETDATA.colorLuminance = function(hex, lum) {
3471 // validate hex string
3472 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3474 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3478 // convert to decimal and change luminosity
3479 var rgb = "#", c, i;
3480 for (i = 0; i < 3; i++) {
3481 c = parseInt(hex.substr(i*2,2), 16);
3482 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3483 rgb += ("00"+c).substr(c.length);
3489 NETDATA.guid = function() {
3491 return Math.floor((1 + Math.random()) * 0x10000)
3496 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3499 NETDATA.zeropad = function(x) {
3500 if(x > -10 && x < 10) return '0' + x.toString();
3501 else return x.toString();
3504 // user function to signal us the DOM has been
3506 NETDATA.updatedDom = function() {
3507 NETDATA.options.updated_dom = true;
3510 NETDATA.ready = function(callback) {
3511 NETDATA.options.pauseCallback = callback;
3514 NETDATA.pause = function(callback) {
3515 if(NETDATA.options.pause === true)
3518 NETDATA.options.pauseCallback = callback;
3521 NETDATA.unpause = function() {
3522 NETDATA.options.pauseCallback = null;
3523 NETDATA.options.updated_dom = true;
3524 NETDATA.options.pause = false;
3527 // ----------------------------------------------------------------------------------------------------------------
3529 // this is purely sequencial charts refresher
3530 // it is meant to be autonomous
3531 NETDATA.chartRefresherNoParallel = function(index) {
3532 if(NETDATA.options.debug.mail_loop === true)
3533 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3535 if(NETDATA.options.updated_dom === true) {
3536 // the dom has been updated
3537 // get the dom parts again
3538 NETDATA.parseDom(NETDATA.chartRefresher);
3541 if(index >= NETDATA.options.targets.length) {
3542 if(NETDATA.options.debug.main_loop === true)
3543 console.log('waiting to restart main loop...');
3545 NETDATA.options.auto_refresher_fast_weight = 0;
3547 setTimeout(function() {
3548 NETDATA.chartRefresher();
3549 }, NETDATA.options.current.idle_between_loops);
3552 var state = NETDATA.options.targets[index];
3554 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3555 if(NETDATA.options.debug.main_loop === true)
3556 console.log('fast rendering...');
3558 state.autoRefresh(function() {
3559 NETDATA.chartRefresherNoParallel(++index);
3563 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3564 NETDATA.options.auto_refresher_fast_weight = 0;
3566 setTimeout(function() {
3567 state.autoRefresh(function() {
3568 NETDATA.chartRefresherNoParallel(++index);
3570 }, NETDATA.options.current.idle_between_charts);
3575 // this is part of the parallel refresher
3576 // its cause is to refresh sequencially all the charts
3577 // that depend on chart library initialization
3578 // it will call the parallel refresher back
3579 // as soon as it sees a chart that its chart library
3581 NETDATA.chartRefresher_uninitialized = function() {
3582 if(NETDATA.options.updated_dom === true) {
3583 // the dom has been updated
3584 // get the dom parts again
3585 NETDATA.parseDom(NETDATA.chartRefresher);
3589 if(NETDATA.options.sequencial.length === 0)
3590 NETDATA.chartRefresher();
3592 var state = NETDATA.options.sequencial.pop();
3593 if(state.library.initialized === true)
3594 NETDATA.chartRefresher();
3596 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3600 NETDATA.chartRefresherWaitTime = function() {
3601 return NETDATA.options.current.idle_parallel_loops;
3604 // the default refresher
3605 // it will create 2 sets of charts:
3606 // - the ones that can be refreshed in parallel
3607 // - the ones that depend on something else
3608 // the first set will be executed in parallel
3609 // the second will be given to NETDATA.chartRefresher_uninitialized()
3610 NETDATA.chartRefresher = function() {
3611 // console.log('auto-refresher...');
3613 if(NETDATA.options.pause === true) {
3614 // console.log('auto-refresher is paused');
3615 setTimeout(NETDATA.chartRefresher,
3616 NETDATA.chartRefresherWaitTime());
3620 if(typeof NETDATA.options.pauseCallback === 'function') {
3621 // console.log('auto-refresher is calling pauseCallback');
3622 NETDATA.options.pause = true;
3623 NETDATA.options.pauseCallback();
3624 NETDATA.chartRefresher();
3628 if(NETDATA.options.current.parallel_refresher === false) {
3629 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3630 NETDATA.chartRefresherNoParallel(0);
3634 if(NETDATA.options.updated_dom === true) {
3635 // the dom has been updated
3636 // get the dom parts again
3637 // console.log('auto-refresher is calling parseDom()');
3638 NETDATA.parseDom(NETDATA.chartRefresher);
3642 var parallel = new Array();
3643 var targets = NETDATA.options.targets;
3644 var len = targets.length;
3647 state = targets[len];
3648 if(state.isVisible() === false || state.running === true)
3651 if(state.library.initialized === false) {
3652 if(state.library.enabled === true) {
3653 state.library.initialize(NETDATA.chartRefresher);
3657 state.error('chart library "' + state.library_name + '" is not enabled.');
3661 parallel.unshift(state);
3664 if(parallel.length > 0) {
3665 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3666 // this will execute the jobs in parallel
3667 $(parallel).each(function() {
3672 // console.log('auto-refresher nothing to do');
3675 // run the next refresh iteration
3676 setTimeout(NETDATA.chartRefresher,
3677 NETDATA.chartRefresherWaitTime());
3680 NETDATA.parseDom = function(callback) {
3681 NETDATA.options.last_page_scroll = Date.now();
3682 NETDATA.options.updated_dom = false;
3684 var targets = $('div[data-netdata]'); //.filter(':visible');
3686 if(NETDATA.options.debug.main_loop === true)
3687 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3689 NETDATA.options.targets = new Array();
3690 var len = targets.length;
3692 // the initialization will take care of sizing
3693 // and the "loading..." message
3694 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3697 if(typeof callback === 'function') callback();
3700 // this is the main function - where everything starts
3701 NETDATA.start = function() {
3702 // this should be called only once
3704 NETDATA.options.page_is_visible = true;
3706 $(window).blur(function() {
3707 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3708 NETDATA.options.page_is_visible = false;
3709 if(NETDATA.options.debug.focus === true)
3710 console.log('Lost Focus!');
3714 $(window).focus(function() {
3715 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3716 NETDATA.options.page_is_visible = true;
3717 if(NETDATA.options.debug.focus === true)
3718 console.log('Focus restored!');
3722 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3723 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3724 NETDATA.options.page_is_visible = false;
3725 if(NETDATA.options.debug.focus === true)
3726 console.log('Document has no focus!');
3730 // bootstrap tab switching
3731 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3733 // bootstrap modal switching
3734 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3735 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3737 // bootstrap collapse switching
3738 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3739 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3741 NETDATA.parseDom(NETDATA.chartRefresher);
3743 // Alarms initialization
3744 setTimeout(NETDATA.alarms.init, 1000);
3746 // Registry initialization
3747 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3749 if(typeof netdataCallback === 'function')
3753 // ----------------------------------------------------------------------------------------------------------------
3756 NETDATA.peityInitialize = function(callback) {
3757 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3759 url: NETDATA.peity_js,
3762 xhrFields: { withCredentials: true } // required for the cookie
3765 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3768 NETDATA.chartLibraries.peity.enabled = false;
3769 NETDATA.error(100, NETDATA.peity_js);
3771 .always(function() {
3772 if(typeof callback === "function")
3777 NETDATA.chartLibraries.peity.enabled = false;
3778 if(typeof callback === "function")
3783 NETDATA.peityChartUpdate = function(state, data) {
3784 state.peity_instance.innerHTML = data.result;
3786 if(state.peity_options.stroke !== state.chartColors()[0]) {
3787 state.peity_options.stroke = state.chartColors()[0];
3788 if(state.chart.chart_type === 'line')
3789 state.peity_options.fill = NETDATA.themes.current.background;
3791 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3794 $(state.peity_instance).peity('line', state.peity_options);
3798 NETDATA.peityChartCreate = function(state, data) {
3799 state.peity_instance = document.createElement('div');
3800 state.element_chart.appendChild(state.peity_instance);
3802 var self = $(state.element);
3803 state.peity_options = {
3804 stroke: NETDATA.themes.current.foreground,
3805 strokeWidth: self.data('peity-strokewidth') || 1,
3806 width: state.chartWidth(),
3807 height: state.chartHeight(),
3808 fill: NETDATA.themes.current.foreground
3811 NETDATA.peityChartUpdate(state, data);
3815 // ----------------------------------------------------------------------------------------------------------------
3818 NETDATA.sparklineInitialize = function(callback) {
3819 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3821 url: NETDATA.sparkline_js,
3824 xhrFields: { withCredentials: true } // required for the cookie
3827 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3830 NETDATA.chartLibraries.sparkline.enabled = false;
3831 NETDATA.error(100, NETDATA.sparkline_js);
3833 .always(function() {
3834 if(typeof callback === "function")
3839 NETDATA.chartLibraries.sparkline.enabled = false;
3840 if(typeof callback === "function")
3845 NETDATA.sparklineChartUpdate = function(state, data) {
3846 state.sparkline_options.width = state.chartWidth();
3847 state.sparkline_options.height = state.chartHeight();
3849 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3853 NETDATA.sparklineChartCreate = function(state, data) {
3854 var self = $(state.element);
3855 var type = self.data('sparkline-type') || 'line';
3856 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3857 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3858 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3859 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3860 var composite = self.data('sparkline-composite') || undefined;
3861 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3862 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3863 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3864 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3865 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3866 var spotColor = self.data('sparkline-spotcolor') || undefined;
3867 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3868 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3869 var spotRadius = self.data('sparkline-spotradius') || undefined;
3870 var valueSpots = self.data('sparkline-valuespots') || undefined;
3871 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3872 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3873 var lineWidth = self.data('sparkline-linewidth') || undefined;
3874 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3875 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3876 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3877 var xvalues = self.data('sparkline-xvalues') || undefined;
3878 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3879 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3880 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3881 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3882 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3883 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3884 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3885 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3886 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3887 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3888 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3889 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3890 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3891 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3892 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3893 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3894 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3895 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3896 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3897 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3898 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3899 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3901 if(spotColor === 'disable') spotColor='';
3902 if(minSpotColor === 'disable') minSpotColor='';
3903 if(maxSpotColor === 'disable') maxSpotColor='';
3905 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3907 state.sparkline_options = {
3909 lineColor: lineColor,
3910 fillColor: fillColor,
3911 chartRangeMin: chartRangeMin,
3912 chartRangeMax: chartRangeMax,
3913 composite: composite,
3914 enableTagOptions: enableTagOptions,
3915 tagOptionPrefix: tagOptionPrefix,
3916 tagValuesAttribute: tagValuesAttribute,
3917 disableHiddenCheck: disableHiddenCheck,
3918 defaultPixelsPerValue: defaultPixelsPerValue,
3919 spotColor: spotColor,
3920 minSpotColor: minSpotColor,
3921 maxSpotColor: maxSpotColor,
3922 spotRadius: spotRadius,
3923 valueSpots: valueSpots,
3924 highlightSpotColor: highlightSpotColor,
3925 highlightLineColor: highlightLineColor,
3926 lineWidth: lineWidth,
3927 normalRangeMin: normalRangeMin,
3928 normalRangeMax: normalRangeMax,
3929 drawNormalOnTop: drawNormalOnTop,
3931 chartRangeClip: chartRangeClip,
3932 chartRangeMinX: chartRangeMinX,
3933 chartRangeMaxX: chartRangeMaxX,
3934 disableInteraction: disableInteraction,
3935 disableTooltips: disableTooltips,
3936 disableHighlight: disableHighlight,
3937 highlightLighten: highlightLighten,
3938 highlightColor: highlightColor,
3939 tooltipContainer: tooltipContainer,
3940 tooltipClassname: tooltipClassname,
3941 tooltipChartTitle: state.title,
3942 tooltipFormat: tooltipFormat,
3943 tooltipPrefix: tooltipPrefix,
3944 tooltipSuffix: tooltipSuffix,
3945 tooltipSkipNull: tooltipSkipNull,
3946 tooltipValueLookups: tooltipValueLookups,
3947 tooltipFormatFieldlist: tooltipFormatFieldlist,
3948 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3949 numberFormatter: numberFormatter,
3950 numberDigitGroupSep: numberDigitGroupSep,
3951 numberDecimalMark: numberDecimalMark,
3952 numberDigitGroupCount: numberDigitGroupCount,
3953 animatedZooms: animatedZooms,
3954 width: state.chartWidth(),
3955 height: state.chartHeight()
3958 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3962 // ----------------------------------------------------------------------------------------------------------------
3969 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3970 if(after < state.netdata_first)
3971 after = state.netdata_first;
3973 if(before > state.netdata_last)
3974 before = state.netdata_last;
3976 state.setMode('zoom');
3977 state.globalSelectionSyncStop();
3978 state.globalSelectionSyncDelay();
3979 state.dygraph_user_action = true;
3980 state.dygraph_force_zoom = true;
3981 state.updateChartPanOrZoom(after, before);
3982 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3985 NETDATA.dygraphSetSelection = function(state, t) {
3986 if(typeof state.dygraph_instance !== 'undefined') {
3987 var r = state.calculateRowForTime(t);
3989 state.dygraph_instance.setSelection(r);
3991 state.dygraph_instance.clearSelection();
3992 state.legendShowUndefined();
3999 NETDATA.dygraphClearSelection = function(state, t) {
4000 if(typeof state.dygraph_instance !== 'undefined') {
4001 state.dygraph_instance.clearSelection();
4006 NETDATA.dygraphSmoothInitialize = function(callback) {
4008 url: NETDATA.dygraph_smooth_js,
4011 xhrFields: { withCredentials: true } // required for the cookie
4014 NETDATA.dygraph.smooth = true;
4015 smoothPlotter.smoothing = 0.3;
4018 NETDATA.dygraph.smooth = false;
4020 .always(function() {
4021 if(typeof callback === "function")
4026 NETDATA.dygraphInitialize = function(callback) {
4027 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4029 url: NETDATA.dygraph_js,
4032 xhrFields: { withCredentials: true } // required for the cookie
4035 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4038 NETDATA.chartLibraries.dygraph.enabled = false;
4039 NETDATA.error(100, NETDATA.dygraph_js);
4041 .always(function() {
4042 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4043 NETDATA.dygraphSmoothInitialize(callback);
4044 else if(typeof callback === "function")
4049 NETDATA.chartLibraries.dygraph.enabled = false;
4050 if(typeof callback === "function")
4055 NETDATA.dygraphChartUpdate = function(state, data) {
4056 var dygraph = state.dygraph_instance;
4058 if(typeof dygraph === 'undefined')
4059 return NETDATA.dygraphChartCreate(state, data);
4061 // when the chart is not visible, and hidden
4062 // if there is a window resize, dygraph detects
4063 // its element size as 0x0.
4064 // this will make it re-appear properly
4066 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4070 file: data.result.data,
4071 colors: state.chartColors(),
4072 labels: data.result.labels,
4073 labelsDivWidth: state.chartWidth() - 70,
4074 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4077 if(state.dygraph_force_zoom === true) {
4078 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4079 state.log('dygraphChartUpdate() forced zoom update');
4081 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4082 options.isZoomedIgnoreProgrammaticZoom = true;
4083 state.dygraph_force_zoom = false;
4085 else if(state.current.name !== 'auto') {
4086 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4087 state.log('dygraphChartUpdate() loose update');
4090 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4091 state.log('dygraphChartUpdate() strict update');
4093 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4094 options.isZoomedIgnoreProgrammaticZoom = true;
4097 options.valueRange = state.dygraph_options.valueRange;
4099 var oldMax = null, oldMin = null;
4100 if(state.__commonMin !== null) {
4101 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4102 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4104 if(state.__commonMax !== null) {
4105 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4106 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4109 if(state.dygraph_smooth_eligible === true) {
4110 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4111 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4112 NETDATA.dygraphChartCreate(state, data);
4117 dygraph.updateOptions(options);
4120 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4121 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4122 options.valueRange[0] = NETDATA.commonMin.get(state);
4125 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4126 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4127 options.valueRange[1] = NETDATA.commonMax.get(state);
4131 if(redraw === true) {
4132 // state.log('forcing redraw to adapt to common- min/max');
4133 dygraph.updateOptions(options);
4136 state.dygraph_last_rendered = Date.now();
4140 NETDATA.dygraphChartCreate = function(state, data) {
4141 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4142 state.log('dygraphChartCreate()');
4144 var self = $(state.element);
4146 var chart_type = state.chart.chart_type;
4147 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4148 chart_type = self.data('dygraph-type') || chart_type;
4150 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4151 smooth = self.data('dygraph-smooth') || smooth;
4153 if(NETDATA.dygraph.smooth === false)
4156 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4157 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4159 state.dygraph_options = {
4160 colors: self.data('dygraph-colors') || state.chartColors(),
4162 // leave a few pixels empty on the right of the chart
4163 rightGap: self.data('dygraph-rightgap') || 5,
4164 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4165 showRoller: self.data('dygraph-showroller') || false,
4167 title: self.data('dygraph-title') || state.title,
4168 titleHeight: self.data('dygraph-titleheight') || 19,
4170 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4171 labels: data.result.labels,
4172 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4173 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4174 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4175 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4176 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4179 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4180 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4182 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4183 xRangePad: self.data('dygraph-xrangepad') || 0,
4184 yRangePad: self.data('dygraph-yrangepad') || 1,
4186 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4188 ylabel: state.units,
4189 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4191 // the function to plot the chart
4194 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4195 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4196 strokePattern: self.data('dygraph-strokepattern') || undefined,
4198 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4199 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4200 drawPoints: self.data('dygraph-drawpoints') || false,
4202 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4203 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4205 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4206 pointSize: self.data('dygraph-pointsize') || 1,
4208 // enabling this makes the chart with little square lines
4209 stepPlot: self.data('dygraph-stepplot') || false,
4211 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4212 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4213 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4215 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4216 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4217 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4218 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4220 drawAxis: self.data('dygraph-drawaxis') || true,
4221 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4222 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4223 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4225 drawGrid: self.data('dygraph-drawgrid') || true,
4226 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4227 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4228 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4230 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4231 sigFigs: self.data('dygraph-sigfigs') || null,
4232 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4233 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4235 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4236 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4237 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4239 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4240 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4244 ticker: Dygraph.dateTicker,
4245 axisLabelFormatter: function (d, gran) {
4246 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4248 valueFormatter: function (ms) {
4249 //var d = new Date(ms);
4250 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4252 // no need to return anything here
4259 valueFormatter: function (x) {
4260 // we format legends with the state object
4261 // no need to do anything here
4262 // return (Math.round(x*100) / 100).toLocaleString();
4263 // return state.legendFormatValue(x);
4268 legendFormatter: function(data) {
4269 var elements = state.element_legend_childs;
4271 // if the hidden div is not there
4272 // we are not managing the legend
4273 if(elements.hidden === null) return;
4275 if (typeof data.x !== 'undefined') {
4276 state.legendSetDate(data.x);
4277 var i = data.series.length;
4279 var series = data.series[i];
4280 if(series.isVisible === true)
4281 state.legendSetLabelValue(series.label, series.y);
4287 drawCallback: function(dygraph, is_initial) {
4288 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4289 state.dygraph_user_action = false;
4291 var x_range = dygraph.xAxisRange();
4292 var after = Math.round(x_range[0]);
4293 var before = Math.round(x_range[1]);
4295 if(NETDATA.options.debug.dygraph === true)
4296 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4298 if(before <= state.netdata_last && after >= state.netdata_first)
4299 state.updateChartPanOrZoom(after, before);
4302 zoomCallback: function(minDate, maxDate, yRanges) {
4303 if(NETDATA.options.debug.dygraph === true)
4304 state.log('dygraphZoomCallback()');
4306 state.globalSelectionSyncStop();
4307 state.globalSelectionSyncDelay();
4308 state.setMode('zoom');
4310 // refresh it to the greatest possible zoom level
4311 state.dygraph_user_action = true;
4312 state.dygraph_force_zoom = true;
4313 state.updateChartPanOrZoom(minDate, maxDate);
4315 highlightCallback: function(event, x, points, row, seriesName) {
4316 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4317 state.log('dygraphHighlightCallback()');
4321 // there is a bug in dygraph when the chart is zoomed enough
4322 // the time it thinks is selected is wrong
4323 // here we calculate the time t based on the row number selected
4325 var t = state.data_after + row * state.data_update_every;
4326 // 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);
4328 state.globalSelectionSync(x);
4330 // fix legend zIndex using the internal structures of dygraph legend module
4331 // this works, but it is a hack!
4332 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4334 unhighlightCallback: function(event) {
4335 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4336 state.log('dygraphUnhighlightCallback()');
4338 state.unpauseChart();
4339 state.globalSelectionSyncStop();
4341 interactionModel : {
4342 mousedown: function(event, dygraph, context) {
4343 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4344 state.log('interactionModel.mousedown()');
4346 state.dygraph_user_action = true;
4347 state.globalSelectionSyncStop();
4349 if(NETDATA.options.debug.dygraph === true)
4350 state.log('dygraphMouseDown()');
4352 // Right-click should not initiate a zoom.
4353 if(event.button && event.button === 2) return;
4355 context.initializeMouseDown(event, dygraph, context);
4357 if(event.button && event.button === 1) {
4358 if (event.altKey || event.shiftKey) {
4359 state.setMode('pan');
4360 state.globalSelectionSyncDelay();
4361 Dygraph.startPan(event, dygraph, context);
4364 state.setMode('zoom');
4365 state.globalSelectionSyncDelay();
4366 Dygraph.startZoom(event, dygraph, context);
4370 if (event.altKey || event.shiftKey) {
4371 state.setMode('zoom');
4372 state.globalSelectionSyncDelay();
4373 Dygraph.startZoom(event, dygraph, context);
4376 state.setMode('pan');
4377 state.globalSelectionSyncDelay();
4378 Dygraph.startPan(event, dygraph, context);
4382 mousemove: function(event, dygraph, context) {
4383 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4384 state.log('interactionModel.mousemove()');
4386 if(context.isPanning) {
4387 state.dygraph_user_action = true;
4388 state.globalSelectionSyncStop();
4389 state.globalSelectionSyncDelay();
4390 state.setMode('pan');
4391 context.is2DPan = false;
4392 Dygraph.movePan(event, dygraph, context);
4394 else if(context.isZooming) {
4395 state.dygraph_user_action = true;
4396 state.globalSelectionSyncStop();
4397 state.globalSelectionSyncDelay();
4398 state.setMode('zoom');
4399 Dygraph.moveZoom(event, dygraph, context);
4402 mouseup: function(event, dygraph, context) {
4403 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4404 state.log('interactionModel.mouseup()');
4406 if (context.isPanning) {
4407 state.dygraph_user_action = true;
4408 state.globalSelectionSyncDelay();
4409 Dygraph.endPan(event, dygraph, context);
4411 else if (context.isZooming) {
4412 state.dygraph_user_action = true;
4413 state.globalSelectionSyncDelay();
4414 Dygraph.endZoom(event, dygraph, context);
4417 click: function(event, dygraph, context) {
4418 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4419 state.log('interactionModel.click()');
4421 event.preventDefault();
4423 dblclick: function(event, dygraph, context) {
4424 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4425 state.log('interactionModel.dblclick()');
4426 NETDATA.resetAllCharts(state);
4428 wheel: function(event, dygraph, context) {
4429 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4430 state.log('interactionModel.wheel()');
4432 // Take the offset of a mouse event on the dygraph canvas and
4433 // convert it to a pair of percentages from the bottom left.
4434 // (Not top left, bottom is where the lower value is.)
4435 function offsetToPercentage(g, offsetX, offsetY) {
4436 // This is calculating the pixel offset of the leftmost date.
4437 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4438 var yar0 = g.yAxisRange(0);
4440 // This is calculating the pixel of the higest value. (Top pixel)
4441 var yOffset = g.toDomCoords(null, yar0[1])[1];
4443 // x y w and h are relative to the corner of the drawing area,
4444 // so that the upper corner of the drawing area is (0, 0).
4445 var x = offsetX - xOffset;
4446 var y = offsetY - yOffset;
4448 // This is computing the rightmost pixel, effectively defining the
4450 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4452 // This is computing the lowest pixel, effectively defining the height.
4453 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4455 // Percentage from the left.
4456 var xPct = w === 0 ? 0 : (x / w);
4457 // Percentage from the top.
4458 var yPct = h === 0 ? 0 : (y / h);
4460 // The (1-) part below changes it from "% distance down from the top"
4461 // to "% distance up from the bottom".
4462 return [xPct, (1-yPct)];
4465 // Adjusts [x, y] toward each other by zoomInPercentage%
4466 // Split it so the left/bottom axis gets xBias/yBias of that change and
4467 // tight/top gets (1-xBias)/(1-yBias) of that change.
4469 // If a bias is missing it splits it down the middle.
4470 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4471 xBias = xBias || 0.5;
4472 yBias = yBias || 0.5;
4474 function adjustAxis(axis, zoomInPercentage, bias) {
4475 var delta = axis[1] - axis[0];
4476 var increment = delta * zoomInPercentage;
4477 var foo = [increment * bias, increment * (1-bias)];
4479 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4482 var yAxes = g.yAxisRanges();
4484 for (var i = 0; i < yAxes.length; i++) {
4485 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4488 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4491 if(event.altKey || event.shiftKey) {
4492 state.dygraph_user_action = true;
4494 state.globalSelectionSyncStop();
4495 state.globalSelectionSyncDelay();
4497 // http://dygraphs.com/gallery/interaction-api.js
4499 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4501 normal_def = event.wheelDelta / 40;
4504 normal_def = event.deltaY * -1.2;
4506 var normal = (event.detail) ? event.detail * -1 : normal_def;
4507 var percentage = normal / 50;
4509 if (!(event.offsetX && event.offsetY)){
4510 event.offsetX = event.layerX - event.target.offsetLeft;
4511 event.offsetY = event.layerY - event.target.offsetTop;
4514 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4515 var xPct = percentages[0];
4516 var yPct = percentages[1];
4518 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4519 var after = new_x_range[0];
4520 var before = new_x_range[1];
4522 var first = state.netdata_first + state.data_update_every;
4523 var last = state.netdata_last + state.data_update_every;
4526 after -= (before - last);
4533 state.setMode('zoom');
4534 if(state.updateChartPanOrZoom(after, before) === true)
4535 dygraph.updateOptions({ dateWindow: [ after, before ] });
4537 event.preventDefault();
4540 touchstart: function(event, dygraph, context) {
4541 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4542 state.log('interactionModel.touchstart()');
4544 state.dygraph_user_action = true;
4545 state.setMode('zoom');
4548 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4550 // we overwrite the touch directions at the end, to overwrite
4551 // the internal default of dygraphs
4552 context.touchDirections = { x: true, y: false };
4554 state.dygraph_last_touch_start = Date.now();
4555 state.dygraph_last_touch_move = 0;
4557 if(typeof event.touches[0].pageX === 'number')
4558 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4560 state.dygraph_last_touch_page_x = 0;
4562 touchmove: function(event, dygraph, context) {
4563 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4564 state.log('interactionModel.touchmove()');
4566 state.dygraph_user_action = true;
4567 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4569 state.dygraph_last_touch_move = Date.now();
4571 touchend: function(event, dygraph, context) {
4572 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4573 state.log('interactionModel.touchend()');
4575 state.dygraph_user_action = true;
4576 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4578 // if it didn't move, it is a selection
4579 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4580 // internal api of dygraphs
4581 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4582 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4583 if(NETDATA.dygraphSetSelection(state, t) === true)
4584 state.globalSelectionSync(t);
4587 // if it was double tap within double click time, reset the charts
4588 var now = Date.now();
4589 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4590 if(state.dygraph_last_touch_move === 0) {
4591 var dt = now - state.dygraph_last_touch_end;
4592 if(dt <= NETDATA.options.current.double_click_speed)
4593 NETDATA.resetAllCharts(state);
4597 // remember the timestamp of the last touch end
4598 state.dygraph_last_touch_end = now;
4603 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4604 state.dygraph_options.drawGrid = false;
4605 state.dygraph_options.drawAxis = false;
4606 state.dygraph_options.title = undefined;
4607 state.dygraph_options.ylabel = undefined;
4608 state.dygraph_options.yLabelWidth = 0;
4609 state.dygraph_options.labelsDivWidth = 120;
4610 state.dygraph_options.labelsDivStyles.width = '120px';
4611 state.dygraph_options.labelsSeparateLines = true;
4612 state.dygraph_options.rightGap = 0;
4613 state.dygraph_options.yRangePad = 1;
4616 if(smooth === true) {
4617 state.dygraph_smooth_eligible = true;
4619 if(NETDATA.options.current.smooth_plot === true)
4620 state.dygraph_options.plotter = smoothPlotter;
4622 else state.dygraph_smooth_eligible = false;
4624 state.dygraph_instance = new Dygraph(state.element_chart,
4625 data.result.data, state.dygraph_options);
4627 state.dygraph_force_zoom = false;
4628 state.dygraph_user_action = false;
4629 state.dygraph_last_rendered = Date.now();
4631 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4632 state.__commonMin = self.data('common-min') || null;
4633 state.__commonMax = self.data('common-max') || null;
4636 state.log('incompatible version of dygraphs detected');
4637 state.__commonMin = null;
4638 state.__commonMax = null;
4644 // ----------------------------------------------------------------------------------------------------------------
4647 NETDATA.morrisInitialize = function(callback) {
4648 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4650 // morris requires raphael
4651 if(!NETDATA.chartLibraries.raphael.initialized) {
4652 if(NETDATA.chartLibraries.raphael.enabled) {
4653 NETDATA.raphaelInitialize(function() {
4654 NETDATA.morrisInitialize(callback);
4658 NETDATA.chartLibraries.morris.enabled = false;
4659 if(typeof callback === "function")
4664 NETDATA._loadCSS(NETDATA.morris_css);
4667 url: NETDATA.morris_js,
4670 xhrFields: { withCredentials: true } // required for the cookie
4673 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4676 NETDATA.chartLibraries.morris.enabled = false;
4677 NETDATA.error(100, NETDATA.morris_js);
4679 .always(function() {
4680 if(typeof callback === "function")
4686 NETDATA.chartLibraries.morris.enabled = false;
4687 if(typeof callback === "function")
4692 NETDATA.morrisChartUpdate = function(state, data) {
4693 state.morris_instance.setData(data.result.data);
4697 NETDATA.morrisChartCreate = function(state, data) {
4699 state.morris_options = {
4700 element: state.element_chart.id,
4701 data: data.result.data,
4703 ykeys: data.dimension_names,
4704 labels: data.dimension_names,
4710 continuousLine: false,
4711 behaveLikeLine: false
4714 if(state.chart.chart_type === 'line')
4715 state.morris_instance = new Morris.Line(state.morris_options);
4717 else if(state.chart.chart_type === 'area') {
4718 state.morris_options.behaveLikeLine = true;
4719 state.morris_instance = new Morris.Area(state.morris_options);
4722 state.morris_instance = new Morris.Area(state.morris_options);
4727 // ----------------------------------------------------------------------------------------------------------------
4730 NETDATA.raphaelInitialize = function(callback) {
4731 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4733 url: NETDATA.raphael_js,
4736 xhrFields: { withCredentials: true } // required for the cookie
4739 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4742 NETDATA.chartLibraries.raphael.enabled = false;
4743 NETDATA.error(100, NETDATA.raphael_js);
4745 .always(function() {
4746 if(typeof callback === "function")
4751 NETDATA.chartLibraries.raphael.enabled = false;
4752 if(typeof callback === "function")
4757 NETDATA.raphaelChartUpdate = function(state, data) {
4758 $(state.element_chart).raphael(data.result, {
4759 width: state.chartWidth(),
4760 height: state.chartHeight()
4766 NETDATA.raphaelChartCreate = function(state, data) {
4767 $(state.element_chart).raphael(data.result, {
4768 width: state.chartWidth(),
4769 height: state.chartHeight()
4775 // ----------------------------------------------------------------------------------------------------------------
4778 NETDATA.c3Initialize = function(callback) {
4779 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4782 if(!NETDATA.chartLibraries.d3.initialized) {
4783 if(NETDATA.chartLibraries.d3.enabled) {
4784 NETDATA.d3Initialize(function() {
4785 NETDATA.c3Initialize(callback);
4789 NETDATA.chartLibraries.c3.enabled = false;
4790 if(typeof callback === "function")
4795 NETDATA._loadCSS(NETDATA.c3_css);
4801 xhrFields: { withCredentials: true } // required for the cookie
4804 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4807 NETDATA.chartLibraries.c3.enabled = false;
4808 NETDATA.error(100, NETDATA.c3_js);
4810 .always(function() {
4811 if(typeof callback === "function")
4817 NETDATA.chartLibraries.c3.enabled = false;
4818 if(typeof callback === "function")
4823 NETDATA.c3ChartUpdate = function(state, data) {
4824 state.c3_instance.destroy();
4825 return NETDATA.c3ChartCreate(state, data);
4827 //state.c3_instance.load({
4828 // rows: data.result,
4835 NETDATA.c3ChartCreate = function(state, data) {
4837 state.element_chart.id = 'c3-' + state.uuid;
4838 // console.log('id = ' + state.element_chart.id);
4840 state.c3_instance = c3.generate({
4841 bindto: '#' + state.element_chart.id,
4843 width: state.chartWidth(),
4844 height: state.chartHeight()
4847 pattern: state.chartColors()
4852 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4858 format: function(x) {
4859 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4886 // console.log(state.c3_instance);
4891 // ----------------------------------------------------------------------------------------------------------------
4894 NETDATA.d3Initialize = function(callback) {
4895 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4900 xhrFields: { withCredentials: true } // required for the cookie
4903 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4906 NETDATA.chartLibraries.d3.enabled = false;
4907 NETDATA.error(100, NETDATA.d3_js);
4909 .always(function() {
4910 if(typeof callback === "function")
4915 NETDATA.chartLibraries.d3.enabled = false;
4916 if(typeof callback === "function")
4921 NETDATA.d3ChartUpdate = function(state, data) {
4925 NETDATA.d3ChartCreate = function(state, data) {
4929 // ----------------------------------------------------------------------------------------------------------------
4932 NETDATA.googleInitialize = function(callback) {
4933 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4935 url: NETDATA.google_js,
4938 xhrFields: { withCredentials: true } // required for the cookie
4941 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4942 google.load('visualization', '1.1', {
4943 'packages': ['corechart', 'controls'],
4944 'callback': callback
4948 NETDATA.chartLibraries.google.enabled = false;
4949 NETDATA.error(100, NETDATA.google_js);
4950 if(typeof callback === "function")
4955 NETDATA.chartLibraries.google.enabled = false;
4956 if(typeof callback === "function")
4961 NETDATA.googleChartUpdate = function(state, data) {
4962 var datatable = new google.visualization.DataTable(data.result);
4963 state.google_instance.draw(datatable, state.google_options);
4967 NETDATA.googleChartCreate = function(state, data) {
4968 var datatable = new google.visualization.DataTable(data.result);
4970 state.google_options = {
4971 colors: state.chartColors(),
4973 // do not set width, height - the chart resizes itself
4974 //width: state.chartWidth(),
4975 //height: state.chartHeight(),
4980 // title: "Time of Day",
4981 // format:'HH:mm:ss',
4982 viewWindowMode: 'maximized',
4994 viewWindowMode: 'pretty',
5009 focusTarget: 'category',
5016 titlePosition: 'out',
5027 curveType: 'function',
5032 switch(state.chart.chart_type) {
5034 state.google_options.vAxis.viewWindowMode = 'maximized';
5035 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5036 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5040 state.google_options.isStacked = true;
5041 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5042 state.google_options.vAxis.viewWindowMode = 'maximized';
5043 state.google_options.vAxis.minValue = null;
5044 state.google_options.vAxis.maxValue = null;
5045 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5050 state.google_options.lineWidth = 2;
5051 state.google_instance = new google.visualization.LineChart(state.element_chart);
5055 state.google_instance.draw(datatable, state.google_options);
5059 // ----------------------------------------------------------------------------------------------------------------
5061 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5062 if(typeof value !== 'number') value = 0;
5063 if(typeof min !== 'number') min = 0;
5064 if(typeof max !== 'number') max = 0;
5066 if(min > value) min = value;
5067 if(max < value) max = value;
5069 // make sure it is zero based
5070 if(min > 0) min = 0;
5071 if(max < 0) max = 0;
5076 pcent = Math.round(value * 100 / max);
5077 if(pcent === 0) pcent = 0.1;
5081 pcent = Math.round(-value * 100 / min);
5082 if(pcent === 0) pcent = -0.1;
5088 // ----------------------------------------------------------------------------------------------------------------
5091 NETDATA.easypiechartInitialize = function(callback) {
5092 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5094 url: NETDATA.easypiechart_js,
5097 xhrFields: { withCredentials: true } // required for the cookie
5100 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5103 NETDATA.chartLibraries.easypiechart.enabled = false;
5104 NETDATA.error(100, NETDATA.easypiechart_js);
5106 .always(function() {
5107 if(typeof callback === "function")
5112 NETDATA.chartLibraries.easypiechart.enabled = false;
5113 if(typeof callback === "function")
5118 NETDATA.easypiechartClearSelection = function(state) {
5119 if(typeof state.easyPieChartEvent !== 'undefined') {
5120 if(state.easyPieChartEvent.timer !== null)
5121 clearTimeout(state.easyPieChartEvent.timer);
5123 state.easyPieChartEvent.timer = null;
5126 if(state.isAutoRefreshable() === true && state.data !== null) {
5127 NETDATA.easypiechartChartUpdate(state, state.data);
5130 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5131 state.easyPieChart_instance.update(0);
5133 state.easyPieChart_instance.enableAnimation();
5138 NETDATA.easypiechartSetSelection = function(state, t) {
5139 if(state.timeIsVisible(t) !== true)
5140 return NETDATA.easypiechartClearSelection(state);
5142 var slot = state.calculateRowForTime(t);
5143 if(slot < 0 || slot >= state.data.result.length)
5144 return NETDATA.easypiechartClearSelection(state);
5146 if(typeof state.easyPieChartEvent === 'undefined') {
5147 state.easyPieChartEvent = {
5154 var value = state.data.result[state.data.result.length - 1 - slot];
5155 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5156 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5157 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5159 state.easyPieChartEvent.value = value;
5160 state.easyPieChartEvent.pcent = pcent;
5161 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5163 if(state.easyPieChartEvent.timer === null) {
5164 state.easyPieChart_instance.disableAnimation();
5166 state.easyPieChartEvent.timer = setTimeout(function() {
5167 state.easyPieChartEvent.timer = null;
5168 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5169 }, NETDATA.options.current.charts_selection_animation_delay);
5175 NETDATA.easypiechartChartUpdate = function(state, data) {
5176 var value, min, max, pcent;
5178 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5183 value = data.result[0];
5184 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5185 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5186 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5189 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5190 state.easyPieChart_instance.update(pcent);
5194 NETDATA.easypiechartChartCreate = function(state, data) {
5195 var self = $(state.element);
5196 var chart = $(state.element_chart);
5198 var value = data.result[0];
5199 var min = self.data('easypiechart-min-value') || null;
5200 var max = self.data('easypiechart-max-value') || null;
5201 var adjust = self.data('easypiechart-adjust') || null;
5204 min = NETDATA.commonMin.get(state);
5205 state.easyPieChartMin = null;
5208 state.easyPieChartMin = min;
5211 max = NETDATA.commonMax.get(state);
5212 state.easyPieChartMax = null;
5215 state.easyPieChartMax = max;
5217 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5219 chart.data('data-percent', pcent);
5223 case 'width': size = state.chartHeight(); break;
5224 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5225 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5227 default: size = state.chartWidth(); break;
5229 state.element.style.width = size + 'px';
5230 state.element.style.height = size + 'px';
5232 var stroke = Math.floor(size / 22);
5233 if(stroke < 3) stroke = 2;
5235 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5236 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5237 state.easyPieChartLabel = document.createElement('span');
5238 state.easyPieChartLabel.className = 'easyPieChartLabel';
5239 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5240 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5241 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5242 state.element_chart.appendChild(state.easyPieChartLabel);
5244 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5245 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5246 state.easyPieChartTitle = document.createElement('span');
5247 state.easyPieChartTitle.className = 'easyPieChartTitle';
5248 state.easyPieChartTitle.innerText = state.title;
5249 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5250 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5251 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5252 state.element_chart.appendChild(state.easyPieChartTitle);
5254 var unitfontsize = Math.round(titlefontsize * 0.9);
5255 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5256 state.easyPieChartUnits = document.createElement('span');
5257 state.easyPieChartUnits.className = 'easyPieChartUnits';
5258 state.easyPieChartUnits.innerText = state.units;
5259 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5260 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5261 state.element_chart.appendChild(state.easyPieChartUnits);
5263 var barColor = self.data('easypiechart-barcolor');
5264 if(typeof barColor === 'undefined' || barColor === null)
5265 barColor = state.chartColors()[0];
5267 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5268 var tmp = eval(barColor);
5269 if(typeof tmp === 'function')
5273 chart.easyPieChart({
5275 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5276 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5277 scaleLength: self.data('easypiechart-scalelength') || 5,
5278 lineCap: self.data('easypiechart-linecap') || 'round',
5279 lineWidth: self.data('easypiechart-linewidth') || stroke,
5280 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5281 size: self.data('easypiechart-size') || size,
5282 rotate: self.data('easypiechart-rotate') || 0,
5283 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5284 easing: self.data('easypiechart-easing') || undefined
5287 // when we just re-create the chart
5288 // do not animate the first update
5290 if(typeof state.easyPieChart_instance !== 'undefined')
5293 state.easyPieChart_instance = chart.data('easyPieChart');
5294 if(animate === false) state.easyPieChart_instance.disableAnimation();
5295 state.easyPieChart_instance.update(pcent);
5296 if(animate === false) state.easyPieChart_instance.enableAnimation();
5300 // ----------------------------------------------------------------------------------------------------------------
5303 NETDATA.gaugeInitialize = function(callback) {
5304 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5306 url: NETDATA.gauge_js,
5309 xhrFields: { withCredentials: true } // required for the cookie
5312 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5315 NETDATA.chartLibraries.gauge.enabled = false;
5316 NETDATA.error(100, NETDATA.gauge_js);
5318 .always(function() {
5319 if(typeof callback === "function")
5324 NETDATA.chartLibraries.gauge.enabled = false;
5325 if(typeof callback === "function")
5330 NETDATA.gaugeAnimation = function(state, status) {
5333 if(typeof status === 'boolean' && status === false)
5335 else if(typeof status === 'number')
5338 // console.log('gauge speed ' + speed);
5339 state.gauge_instance.animationSpeed = speed;
5340 state.___gaugeOld__.speed = speed;
5343 NETDATA.gaugeSet = function(state, value, min, max) {
5344 if(typeof value !== 'number') value = 0;
5345 if(typeof min !== 'number') min = 0;
5346 if(typeof max !== 'number') max = 0;
5347 if(value > max) max = value;
5348 if(value < min) min = value;
5357 // gauge.js has an issue if the needle
5358 // is smaller than min or larger than max
5359 // when we set the new values
5360 // the needle will go crazy
5362 // to prevent it, we always feed it
5363 // with a percentage, so that the needle
5364 // is always between min and max
5365 var pcent = (value - min) * 100 / (max - min);
5367 // these should never happen
5368 if(pcent < 0) pcent = 0;
5369 if(pcent > 100) pcent = 100;
5371 state.gauge_instance.set(pcent);
5372 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5374 state.___gaugeOld__.value = value;
5375 state.___gaugeOld__.min = min;
5376 state.___gaugeOld__.max = max;
5379 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5380 if(state.___gaugeOld__.valueLabel !== value) {
5381 state.___gaugeOld__.valueLabel = value;
5382 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5384 if(state.___gaugeOld__.minLabel !== min) {
5385 state.___gaugeOld__.minLabel = min;
5386 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5388 if(state.___gaugeOld__.maxLabel !== max) {
5389 state.___gaugeOld__.maxLabel = max;
5390 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5394 NETDATA.gaugeClearSelection = function(state) {
5395 if(typeof state.gaugeEvent !== 'undefined') {
5396 if(state.gaugeEvent.timer !== null)
5397 clearTimeout(state.gaugeEvent.timer);
5399 state.gaugeEvent.timer = null;
5402 if(state.isAutoRefreshable() === true && state.data !== null) {
5403 NETDATA.gaugeChartUpdate(state, state.data);
5406 NETDATA.gaugeAnimation(state, false);
5407 NETDATA.gaugeSet(state, null, null, null);
5408 NETDATA.gaugeSetLabels(state, null, null, null);
5411 NETDATA.gaugeAnimation(state, true);
5415 NETDATA.gaugeSetSelection = function(state, t) {
5416 if(state.timeIsVisible(t) !== true)
5417 return NETDATA.gaugeClearSelection(state);
5419 var slot = state.calculateRowForTime(t);
5420 if(slot < 0 || slot >= state.data.result.length)
5421 return NETDATA.gaugeClearSelection(state);
5423 if(typeof state.gaugeEvent === 'undefined') {
5424 state.gaugeEvent = {
5432 var value = state.data.result[state.data.result.length - 1 - slot];
5433 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5434 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5436 // make sure it is zero based
5437 if(min > 0) min = 0;
5438 if(max < 0) max = 0;
5440 state.gaugeEvent.value = value;
5441 state.gaugeEvent.min = min;
5442 state.gaugeEvent.max = max;
5443 NETDATA.gaugeSetLabels(state, value, min, max);
5445 if(state.gaugeEvent.timer === null) {
5446 NETDATA.gaugeAnimation(state, false);
5448 state.gaugeEvent.timer = setTimeout(function() {
5449 state.gaugeEvent.timer = null;
5450 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5451 }, NETDATA.options.current.charts_selection_animation_delay);
5457 NETDATA.gaugeChartUpdate = function(state, data) {
5458 var value, min, max;
5460 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5464 NETDATA.gaugeSetLabels(state, null, null, null);
5467 value = data.result[0];
5468 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5469 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5470 if(value < min) min = value;
5471 if(value > max) max = value;
5473 // make sure it is zero based
5474 if(min > 0) min = 0;
5475 if(max < 0) max = 0;
5477 NETDATA.gaugeSetLabels(state, value, min, max);
5480 NETDATA.gaugeSet(state, value, min, max);
5484 NETDATA.gaugeChartCreate = function(state, data) {
5485 var self = $(state.element);
5486 // var chart = $(state.element_chart);
5488 var value = data.result[0];
5489 var min = self.data('gauge-min-value') || null;
5490 var max = self.data('gauge-max-value') || null;
5491 var adjust = self.data('gauge-adjust') || null;
5492 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5493 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5494 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5495 var stopColor = self.data('gauge-stop-color') || void 0;
5496 var generateGradient = self.data('gauge-generate-gradient') || false;
5499 min = NETDATA.commonMin.get(state);
5500 state.gaugeMin = null;
5503 state.gaugeMin = min;
5506 max = NETDATA.commonMax.get(state);
5507 state.gaugeMax = null;
5510 state.gaugeMax = max;
5512 // make sure it is zero based
5513 if(min > 0) min = 0;
5514 if(max < 0) max = 0;
5516 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5518 // case 'width': width = height * ratio; break;
5520 // default: height = width / ratio; break;
5522 //state.element.style.width = width.toString() + 'px';
5523 //state.element.style.height = height.toString() + 'px';
5528 lines: 12, // The number of lines to draw
5529 angle: 0.15, // The length of each line
5530 lineWidth: 0.44, // 0.44 The line thickness
5532 length: 0.8, // 0.9 The radius of the inner circle
5533 strokeWidth: 0.035, // The rotation offset
5534 color: pointerColor // Fill color
5536 colorStart: startColor, // Colors
5537 colorStop: stopColor, // just experiment with them
5538 strokeColor: strokeColor, // to see which ones work best for you
5540 generateGradient: (generateGradient === true)?true:false,
5544 if (generateGradient.constructor === Array) {
5546 // data-gauge-generate-gradient="[0, 50, 100]"
5547 // data-gauge-gradient-percent-color-0="#FFFFFF"
5548 // data-gauge-gradient-percent-color-50="#999900"
5549 // data-gauge-gradient-percent-color-100="#000000"
5551 options.percentColors = new Array();
5552 var len = generateGradient.length;
5554 var pcent = generateGradient[len];
5555 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5556 if(color !== false) {
5557 var a = new Array();
5560 options.percentColors.unshift(a);
5563 if(options.percentColors.length === 0)
5564 delete options.percentColors;
5566 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5567 options.percentColors = [
5568 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5569 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5570 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5571 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5572 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5573 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5574 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5575 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5576 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5577 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5578 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5581 state.gauge_canvas = document.createElement('canvas');
5582 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5583 state.gauge_canvas.className = 'gaugeChart';
5584 state.gauge_canvas.width = width;
5585 state.gauge_canvas.height = height;
5586 state.element_chart.appendChild(state.gauge_canvas);
5588 var valuefontsize = Math.floor(height / 6);
5589 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5590 state.gaugeChartLabel = document.createElement('span');
5591 state.gaugeChartLabel.className = 'gaugeChartLabel';
5592 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5593 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5594 state.element_chart.appendChild(state.gaugeChartLabel);
5596 var titlefontsize = Math.round(valuefontsize / 2);
5598 state.gaugeChartTitle = document.createElement('span');
5599 state.gaugeChartTitle.className = 'gaugeChartTitle';
5600 state.gaugeChartTitle.innerText = state.title;
5601 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5602 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5603 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5604 state.element_chart.appendChild(state.gaugeChartTitle);
5606 var unitfontsize = Math.round(titlefontsize * 0.9);
5607 state.gaugeChartUnits = document.createElement('span');
5608 state.gaugeChartUnits.className = 'gaugeChartUnits';
5609 state.gaugeChartUnits.innerText = state.units;
5610 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5611 state.element_chart.appendChild(state.gaugeChartUnits);
5613 state.gaugeChartMin = document.createElement('span');
5614 state.gaugeChartMin.className = 'gaugeChartMin';
5615 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5616 state.element_chart.appendChild(state.gaugeChartMin);
5618 state.gaugeChartMax = document.createElement('span');
5619 state.gaugeChartMax.className = 'gaugeChartMax';
5620 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5621 state.element_chart.appendChild(state.gaugeChartMax);
5623 // when we just re-create the chart
5624 // do not animate the first update
5626 if(typeof state.gauge_instance !== 'undefined')
5629 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5631 state.___gaugeOld__ = {
5640 // we will always feed a percentage
5641 state.gauge_instance.minValue = 0;
5642 state.gauge_instance.maxValue = 100;
5644 NETDATA.gaugeAnimation(state, animate);
5645 NETDATA.gaugeSet(state, value, min, max);
5646 NETDATA.gaugeSetLabels(state, value, min, max);
5647 NETDATA.gaugeAnimation(state, true);
5651 // ----------------------------------------------------------------------------------------------------------------
5652 // Charts Libraries Registration
5654 NETDATA.chartLibraries = {
5656 initialize: NETDATA.dygraphInitialize,
5657 create: NETDATA.dygraphChartCreate,
5658 update: NETDATA.dygraphChartUpdate,
5659 resize: function(state) {
5660 if(typeof state.dygraph_instance.resize === 'function')
5661 state.dygraph_instance.resize();
5663 setSelection: NETDATA.dygraphSetSelection,
5664 clearSelection: NETDATA.dygraphClearSelection,
5665 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5668 format: function(state) { return 'json'; },
5669 options: function(state) { return 'ms|flip'; },
5670 legend: function(state) {
5671 if(this.isSparkline(state) === false)
5672 return 'right-side';
5676 autoresize: function(state) { return true; },
5677 max_updates_to_recreate: function(state) { return 5000; },
5678 track_colors: function(state) { return true; },
5679 pixels_per_point: function(state) {
5680 if(this.isSparkline(state) === false)
5686 isSparkline: function(state) {
5687 if(typeof state.dygraph_sparkline === 'undefined') {
5688 var t = $(state.element).data('dygraph-theme');
5689 if(t === 'sparkline')
5690 state.dygraph_sparkline = true;
5692 state.dygraph_sparkline = false;
5694 return state.dygraph_sparkline;
5698 initialize: NETDATA.sparklineInitialize,
5699 create: NETDATA.sparklineChartCreate,
5700 update: NETDATA.sparklineChartUpdate,
5702 setSelection: undefined, // function(state, t) { return true; },
5703 clearSelection: undefined, // function(state) { return true; },
5704 toolboxPanAndZoom: null,
5707 format: function(state) { return 'array'; },
5708 options: function(state) { return 'flip|abs'; },
5709 legend: function(state) { return null; },
5710 autoresize: function(state) { return false; },
5711 max_updates_to_recreate: function(state) { return 5000; },
5712 track_colors: function(state) { return false; },
5713 pixels_per_point: function(state) { return 3; }
5716 initialize: NETDATA.peityInitialize,
5717 create: NETDATA.peityChartCreate,
5718 update: NETDATA.peityChartUpdate,
5720 setSelection: undefined, // function(state, t) { return true; },
5721 clearSelection: undefined, // function(state) { return true; },
5722 toolboxPanAndZoom: null,
5725 format: function(state) { return 'ssvcomma'; },
5726 options: function(state) { return 'null2zero|flip|abs'; },
5727 legend: function(state) { return null; },
5728 autoresize: function(state) { return false; },
5729 max_updates_to_recreate: function(state) { return 5000; },
5730 track_colors: function(state) { return false; },
5731 pixels_per_point: function(state) { return 3; }
5734 initialize: NETDATA.morrisInitialize,
5735 create: NETDATA.morrisChartCreate,
5736 update: NETDATA.morrisChartUpdate,
5738 setSelection: undefined, // function(state, t) { return true; },
5739 clearSelection: undefined, // function(state) { return true; },
5740 toolboxPanAndZoom: null,
5743 format: function(state) { return 'json'; },
5744 options: function(state) { return 'objectrows|ms'; },
5745 legend: function(state) { return null; },
5746 autoresize: function(state) { return false; },
5747 max_updates_to_recreate: function(state) { return 50; },
5748 track_colors: function(state) { return false; },
5749 pixels_per_point: function(state) { return 15; }
5752 initialize: NETDATA.googleInitialize,
5753 create: NETDATA.googleChartCreate,
5754 update: NETDATA.googleChartUpdate,
5756 setSelection: undefined, //function(state, t) { return true; },
5757 clearSelection: undefined, //function(state) { return true; },
5758 toolboxPanAndZoom: null,
5761 format: function(state) { return 'datatable'; },
5762 options: function(state) { return ''; },
5763 legend: function(state) { return null; },
5764 autoresize: function(state) { return false; },
5765 max_updates_to_recreate: function(state) { return 300; },
5766 track_colors: function(state) { return false; },
5767 pixels_per_point: function(state) { return 4; }
5770 initialize: NETDATA.raphaelInitialize,
5771 create: NETDATA.raphaelChartCreate,
5772 update: NETDATA.raphaelChartUpdate,
5774 setSelection: undefined, // function(state, t) { return true; },
5775 clearSelection: undefined, // function(state) { return true; },
5776 toolboxPanAndZoom: null,
5779 format: function(state) { return 'json'; },
5780 options: function(state) { return ''; },
5781 legend: function(state) { return null; },
5782 autoresize: function(state) { return false; },
5783 max_updates_to_recreate: function(state) { return 5000; },
5784 track_colors: function(state) { return false; },
5785 pixels_per_point: function(state) { return 3; }
5788 initialize: NETDATA.c3Initialize,
5789 create: NETDATA.c3ChartCreate,
5790 update: NETDATA.c3ChartUpdate,
5792 setSelection: undefined, // function(state, t) { return true; },
5793 clearSelection: undefined, // function(state) { return true; },
5794 toolboxPanAndZoom: null,
5797 format: function(state) { return 'csvjsonarray'; },
5798 options: function(state) { return 'milliseconds'; },
5799 legend: function(state) { return null; },
5800 autoresize: function(state) { return false; },
5801 max_updates_to_recreate: function(state) { return 5000; },
5802 track_colors: function(state) { return false; },
5803 pixels_per_point: function(state) { return 15; }
5806 initialize: NETDATA.d3Initialize,
5807 create: NETDATA.d3ChartCreate,
5808 update: NETDATA.d3ChartUpdate,
5810 setSelection: undefined, // function(state, t) { return true; },
5811 clearSelection: undefined, // function(state) { return true; },
5812 toolboxPanAndZoom: null,
5815 format: function(state) { return 'json'; },
5816 options: function(state) { return ''; },
5817 legend: function(state) { return null; },
5818 autoresize: function(state) { return false; },
5819 max_updates_to_recreate: function(state) { return 5000; },
5820 track_colors: function(state) { return false; },
5821 pixels_per_point: function(state) { return 3; }
5824 initialize: NETDATA.easypiechartInitialize,
5825 create: NETDATA.easypiechartChartCreate,
5826 update: NETDATA.easypiechartChartUpdate,
5828 setSelection: NETDATA.easypiechartSetSelection,
5829 clearSelection: NETDATA.easypiechartClearSelection,
5830 toolboxPanAndZoom: null,
5833 format: function(state) { return 'array'; },
5834 options: function(state) { return 'absolute'; },
5835 legend: function(state) { return null; },
5836 autoresize: function(state) { return false; },
5837 max_updates_to_recreate: function(state) { return 5000; },
5838 track_colors: function(state) { return true; },
5839 pixels_per_point: function(state) { return 3; },
5843 initialize: NETDATA.gaugeInitialize,
5844 create: NETDATA.gaugeChartCreate,
5845 update: NETDATA.gaugeChartUpdate,
5847 setSelection: NETDATA.gaugeSetSelection,
5848 clearSelection: NETDATA.gaugeClearSelection,
5849 toolboxPanAndZoom: null,
5852 format: function(state) { return 'array'; },
5853 options: function(state) { return 'absolute'; },
5854 legend: function(state) { return null; },
5855 autoresize: function(state) { return false; },
5856 max_updates_to_recreate: function(state) { return 5000; },
5857 track_colors: function(state) { return true; },
5858 pixels_per_point: function(state) { return 3; },
5863 NETDATA.registerChartLibrary = function(library, url) {
5864 if(NETDATA.options.debug.libraries === true)
5865 console.log("registering chart library: " + library);
5867 NETDATA.chartLibraries[library].url = url;
5868 NETDATA.chartLibraries[library].initialized = true;
5869 NETDATA.chartLibraries[library].enabled = true;
5872 // ----------------------------------------------------------------------------------------------------------------
5873 // Load required JS libraries and CSS
5875 NETDATA.requiredJs = [
5877 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5879 isAlreadyLoaded: function() {
5880 // check if bootstrap is loaded
5881 if(typeof $().emulateTransitionEnd == 'function')
5884 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5892 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5893 isAlreadyLoaded: function() { return false; }
5897 NETDATA.requiredCSS = [
5899 url: NETDATA.themes.current.bootstrap_css,
5900 isAlreadyLoaded: function() {
5901 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5908 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5909 isAlreadyLoaded: function() { return false; }
5912 url: NETDATA.themes.current.dashboard_css,
5913 isAlreadyLoaded: function() { return false; }
5917 NETDATA.loadedRequiredJs = 0;
5918 NETDATA.loadRequiredJs = function(index, callback) {
5919 if(index >= NETDATA.requiredJs.length) {
5920 if(typeof callback === 'function')
5925 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5926 NETDATA.loadedRequiredJs++;
5927 NETDATA.loadRequiredJs(++index, callback);
5931 if(NETDATA.options.debug.main_loop === true)
5932 console.log('loading ' + NETDATA.requiredJs[index].url);
5935 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5939 url: NETDATA.requiredJs[index].url,
5942 xhrFields: { withCredentials: true } // required for the cookie
5945 if(NETDATA.options.debug.main_loop === true)
5946 console.log('loaded ' + NETDATA.requiredJs[index].url);
5949 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5951 .always(function() {
5952 NETDATA.loadedRequiredJs++;
5955 NETDATA.loadRequiredJs(++index, callback);
5959 NETDATA.loadRequiredJs(++index, callback);
5962 NETDATA.loadRequiredCSS = function(index) {
5963 if(index >= NETDATA.requiredCSS.length)
5966 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5967 NETDATA.loadRequiredCSS(++index);
5971 if(NETDATA.options.debug.main_loop === true)
5972 console.log('loading ' + NETDATA.requiredCSS[index].url);
5974 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5975 NETDATA.loadRequiredCSS(++index);
5979 // ----------------------------------------------------------------------------------------------------------------
5980 // Registry of netdata hosts
5983 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5984 chart_div_offset: 100, // give that space above the chart when scrolling to it
5985 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5986 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5988 ms_penalty: 0, // the time penalty of the next alarm
5989 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5990 // if alarms are shown faster than: one per 500ms
5992 notifications: false, // when true, the browser supports notifications (may not be granted though)
5993 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5994 first_notification_id: 0, // the id of the first alarm_log entry for this session
5995 // this is used to prevent CLEAR notifications for past events
5996 // notifications_shown: new Array(),
5998 server: null, // the server to connect to for fetching alarms
5999 current: null, // the list of raised alarms - updated in the background
6000 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6002 notify: function(entry) {
6003 // console.log('alarm ' + entry.unique_id);
6005 if(entry.updated === true) {
6006 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6010 var value = entry.value;
6011 if(NETDATA.alarms.current !== null) {
6012 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6013 if(typeof t !== 'undefined' && entry.status == t.status)
6017 var name = entry.name.replace(/_/g, ' ');
6018 var status = entry.status.toLowerCase();
6019 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6020 var tag = entry.alarm_id;
6021 var icon = 'images/seo-performance-128.png';
6022 var interaction = false;
6026 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6028 switch(entry.status) {
6036 case 'UNINITIALIZED':
6040 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6041 // console.log('alarm ' + entry.unique_id + ' is not current');
6044 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6045 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6048 title = name + ' back to normal';
6049 icon = 'images/check-mark-2-128-green.png'
6050 interaction = false;
6054 if(entry.old_status === 'CRITICAL')
6055 status = 'demoted to ' + entry.status.toLowerCase();
6057 icon = 'images/alert-128-orange.png';
6058 interaction = false;
6062 if(entry.old_status === 'WARNING')
6063 status = 'escalated to ' + entry.status.toLowerCase();
6065 icon = 'images/alert-128-red.png'
6070 console.log('invalid alarm status ' + entry.status);
6075 // cleanup old notifications with the same alarm_id as this one
6076 // FIXME: it does not seem to work on any web browser!
6077 var len = NETDATA.alarms.notifications_shown.length;
6079 var n = NETDATA.alarms.notifications_shown[len];
6080 if(n.data.alarm_id === entry.alarm_id) {
6081 console.log('removing old alarm ' + n.data.unique_id);
6083 // close the notification
6086 // remove it from the array
6087 NETDATA.alarms.notifications_shown.splice(len, 1);
6088 len = NETDATA.alarms.notifications_shown.length;
6095 setTimeout(function() {
6096 // show this notification
6097 // console.log('new notification: ' + title);
6098 var n = new Notification(title, {
6099 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6101 requireInteraction: interaction,
6102 icon: NETDATA.serverDefault + icon,
6106 n.onclick = function(event) {
6107 event.preventDefault();
6108 NETDATA.alarms.onclick(event.target.data);
6112 // NETDATA.alarms.notifications_shown.push(n);
6113 // console.log(entry);
6114 }, NETDATA.alarms.ms_penalty);
6116 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6120 scrollToChart: function(chart_id) {
6121 if(typeof chart_id === 'string') {
6122 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6123 if(typeof offset !== 'undefined') {
6124 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6131 scrollToAlarm: function(alarm) {
6132 if(typeof alarm === 'object') {
6133 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6135 if(ret === true && NETDATA.options.page_is_visible === false)
6137 // 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.');
6142 notifyAll: function() {
6143 // console.log('FETCHING ALARM LOG');
6144 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6145 // console.log('ALARM LOG FETCHED');
6147 if(data === null || typeof data !== 'object') {
6148 console.log('invalid alarms log response');
6152 if(data.length === 0) {
6153 console.log('received empty alarm log');
6157 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6159 data.sort(function(a, b) {
6160 if(a.unique_id > b.unique_id) return -1;
6161 if(a.unique_id < b.unique_id) return 1;
6165 NETDATA.alarms.ms_penalty = 0;
6167 var len = data.length;
6169 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6170 NETDATA.alarms.notify(data[len]);
6173 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6176 NETDATA.alarms.last_notification_id = data[0].unique_id;
6177 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6178 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6182 check_notifications: function() {
6183 // returns true if we should fire 1+ notifications
6185 if(NETDATA.alarms.notifications !== true) {
6186 // console.log('notifications not available');
6190 if(Notification.permission !== 'granted') {
6191 // console.log('notifications not granted');
6195 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6196 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6198 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6199 // console.log('new alarms detected');
6202 //else console.log('no new alarms');
6204 // else console.log('cannot process alarms');
6209 get: function(what, callback) {
6211 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6215 'Cache-Control': 'no-cache, no-store',
6216 'Pragma': 'no-cache'
6218 xhrFields: { withCredentials: true } // required for the cookie
6220 .done(function(data) {
6221 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6222 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6224 if(typeof callback === 'function')
6228 NETDATA.error(415, NETDATA.alarms.server);
6230 if(typeof callback === 'function')
6235 update_forever: function() {
6236 NETDATA.alarms.get('active', function(data) {
6238 NETDATA.alarms.current = data;
6240 if(NETDATA.alarms.check_notifications() === true) {
6241 NETDATA.alarms.notifyAll();
6244 if (typeof NETDATA.alarms.callback === 'function') {
6245 NETDATA.alarms.callback(data);
6248 // Health monitoring is disabled on this netdata
6249 if(data.status === false) return;
6252 setTimeout(NETDATA.alarms.update_forever, 10000);
6256 get_log: function(last_id, callback) {
6257 // console.log('fetching all log after ' + last_id.toString());
6259 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6263 'Cache-Control': 'no-cache, no-store',
6264 'Pragma': 'no-cache'
6266 xhrFields: { withCredentials: true } // required for the cookie
6268 .done(function(data) {
6269 if(typeof callback === 'function')
6273 NETDATA.error(416, NETDATA.alarms.server);
6275 if(typeof callback === 'function')
6281 var host = NETDATA.serverDefault;
6282 while(host.slice(-1) === '/')
6283 host = host.substring(0, host.length - 1);
6284 NETDATA.alarms.server = host;
6286 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6288 if(NETDATA.alarms.onclick === null)
6289 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6291 if(netdataShowAlarms === true) {
6292 NETDATA.alarms.update_forever();
6294 if('Notification' in window) {
6295 // console.log('notifications available');
6296 NETDATA.alarms.notifications = true;
6298 if(Notification.permission === 'default')
6299 Notification.requestPermission();
6305 // ----------------------------------------------------------------------------------------------------------------
6306 // Registry of netdata hosts
6308 NETDATA.registry = {
6309 server: null, // the netdata registry server
6310 person_guid: null, // the unique ID of this browser / user
6311 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6312 hostname: null, // the hostname of the netdata server that served dashboard.js
6313 machines: null, // the user's other URLs
6314 machines_array: null, // the user's other URLs in an array
6317 parsePersonUrls: function(person_urls) {
6318 // console.log(person_urls);
6319 NETDATA.registry.person_urls = person_urls;
6322 NETDATA.registry.machines = {};
6323 NETDATA.registry.machines_array = new Array();
6325 var now = Date.now();
6326 var apu = person_urls;
6329 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6330 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6336 accesses: apu[i][3],
6338 alternate_urls: new Array()
6340 obj.alternate_urls.push(apu[i][1]);
6342 NETDATA.registry.machines[apu[i][0]] = obj;
6343 NETDATA.registry.machines_array.push(obj);
6346 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6348 var pu = NETDATA.registry.machines[apu[i][0]];
6349 if(pu.last_t < apu[i][2]) {
6351 pu.last_t = apu[i][2];
6352 pu.name = apu[i][4];
6354 pu.accesses += apu[i][3];
6355 pu.alternate_urls.push(apu[i][1]);
6360 if(typeof netdataRegistryCallback === 'function')
6361 netdataRegistryCallback(NETDATA.registry.machines_array);
6365 if(netdataRegistry !== true) return;
6367 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6369 NETDATA.registry.server = data.registry;
6370 NETDATA.registry.machine_guid = data.machine_guid;
6371 NETDATA.registry.hostname = data.hostname;
6373 NETDATA.registry.access(2, function (person_urls) {
6374 NETDATA.registry.parsePersonUrls(person_urls);
6381 hello: function(host, callback) {
6382 while(host.slice(-1) === '/')
6383 host = host.substring(0, host.length - 1);
6385 // send HELLO to a netdata server:
6386 // 1. verifies the server is reachable
6387 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6389 url: host + '/api/v1/registry?action=hello',
6393 'Cache-Control': 'no-cache, no-store',
6394 'Pragma': 'no-cache'
6396 xhrFields: { withCredentials: true } // required for the cookie
6398 .done(function(data) {
6399 if(typeof data.status !== 'string' || data.status !== 'ok') {
6400 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6404 if(typeof callback === 'function')
6408 NETDATA.error(407, host);
6410 if(typeof callback === 'function')
6415 access: function(max_redirects, callback) {
6416 // send ACCESS to a netdata registry:
6417 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6418 // 2. it responds with a list of netdata servers we know
6419 // the registry identifies us using a cookie it sets the first time we access it
6420 // the registry may respond with a redirect URL to send us to another registry
6422 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),
6426 'Cache-Control': 'no-cache, no-store',
6427 'Pragma': 'no-cache'
6429 xhrFields: { withCredentials: true } // required for the cookie
6431 .done(function(data) {
6432 var redirect = null;
6433 if(typeof data.registry === 'string')
6434 redirect = data.registry;
6436 if(typeof data.status !== 'string' || data.status !== 'ok') {
6437 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6442 if(redirect !== null && max_redirects > 0) {
6443 NETDATA.registry.server = redirect;
6444 NETDATA.registry.access(max_redirects - 1, callback);
6447 if(typeof callback === 'function')
6452 if(typeof data.person_guid === 'string')
6453 NETDATA.registry.person_guid = data.person_guid;
6455 if(typeof callback === 'function')
6456 callback(data.urls);
6460 NETDATA.error(410, NETDATA.registry.server);
6462 if(typeof callback === 'function')
6467 delete: function(delete_url, callback) {
6468 // send DELETE to a netdata registry:
6470 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),
6474 'Cache-Control': 'no-cache, no-store',
6475 'Pragma': 'no-cache'
6477 xhrFields: { withCredentials: true } // required for the cookie
6479 .done(function(data) {
6480 if(typeof data.status !== 'string' || data.status !== 'ok') {
6481 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6485 if(typeof callback === 'function')
6489 NETDATA.error(412, NETDATA.registry.server);
6491 if(typeof callback === 'function')
6496 search: function(machine_guid, callback) {
6497 // SEARCH for the URLs of a machine:
6499 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,
6503 'Cache-Control': 'no-cache, no-store',
6504 'Pragma': 'no-cache'
6506 xhrFields: { withCredentials: true } // required for the cookie
6508 .done(function(data) {
6509 if(typeof data.status !== 'string' || data.status !== 'ok') {
6510 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6514 if(typeof callback === 'function')
6518 NETDATA.error(418, NETDATA.registry.server);
6520 if(typeof callback === 'function')
6525 switch: function(new_person_guid, callback) {
6528 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,
6532 'Cache-Control': 'no-cache, no-store',
6533 'Pragma': 'no-cache'
6535 xhrFields: { withCredentials: true } // required for the cookie
6537 .done(function(data) {
6538 if(typeof data.status !== 'string' || data.status !== 'ok') {
6539 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6543 if(typeof callback === 'function')
6547 NETDATA.error(414, NETDATA.registry.server);
6549 if(typeof callback === 'function')
6555 // ----------------------------------------------------------------------------------------------------------------
6558 if(typeof netdataPrepCallback === 'function')
6559 netdataPrepCallback();
6561 NETDATA.errorReset();
6562 NETDATA.loadRequiredCSS(0);
6564 NETDATA._loadjQuery(function() {
6565 NETDATA.loadRequiredJs(0, function() {
6566 if(typeof $().emulateTransitionEnd !== 'function') {
6567 // bootstrap is not available
6568 NETDATA.options.current.show_help = false;
6571 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6572 if(NETDATA.options.debug.main_loop === true)
6573 console.log('starting chart refresh thread');
6579 })(window, document);