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) {
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?v20161229-2',
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?v20161229-1',
145 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
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 netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
199 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
200 netdataRegistry = true;
202 // ----------------------------------------------------------------------------------------------------------------
203 // the defaults for all charts
205 // if the user does not specify any of these, the following will be used
207 NETDATA.chartDefaults = {
208 host: NETDATA.serverDefault, // the server to get data from
209 width: '100%', // the chart width - can be null
210 height: '100%', // the chart height - can be null
211 min_width: null, // the chart minimum width - can be null
212 library: 'dygraph', // the graphing library to use
213 method: 'average', // the grouping method
214 before: 0, // panning
215 after: -600, // panning
216 pixels_per_point: 1, // the detail of the chart
217 fill_luminance: 0.8 // luminance of colors in solit areas
220 // ----------------------------------------------------------------------------------------------------------------
224 pauseCallback: null, // a callback when we are really paused
226 pause: false, // when enabled we don't auto-refresh the charts
228 targets: null, // an array of all the state objects that are
229 // currently active (independently of their
230 // viewport visibility)
232 updated_dom: true, // when true, the DOM has been updated with
233 // new elements we have to check.
235 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
236 // rendering charts continiously.
237 // used with .current.fast_render_timeframe
239 page_is_visible: true, // when true, this page is visible
241 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
242 // auto-refresher for some time (when a chart is
243 // performing pan or zoom, we need to stop refreshing
244 // all other charts, to have the maximum speed for
245 // rendering the chart that is panned or zoomed).
246 // Used with .current.global_pan_sync_time
248 last_resized: Date.now(), // the timestamp of the last resize request
250 last_page_scroll: 0, // the timestamp the last time the page was scrolled
252 // the current profile
253 // we may have many...
255 pixels_per_point: 1, // the minimum pixels per point for all charts
256 // increase this to speed javascript up
257 // each chart library has its own limit too
258 // the max of this and the chart library is used
259 // the final is calculated every time, so a change
260 // here will have immediate effect on the next chart
263 idle_between_charts: 100, // ms - how much time to wait between chart updates
265 fast_render_timeframe: 200, // ms - render continously until this time of continious
266 // rendering has been reached
267 // this setting is used to make it render e.g. 10
268 // charts at once, sleep idle_between_charts time
269 // and continue for another 10 charts.
271 idle_between_loops: 500, // ms - if all charts have been updated, wait this
272 // time before starting again.
274 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
276 idle_lost_focus: 500, // ms - when the window does not have focus, check
277 // if focus has been regained, every this time
279 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
280 // autorefreshing of charts is paused for this amount
283 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
284 // of time before setting up synchronized selections
287 sync_selection: true, // enable or disable selection sync
289 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
291 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
293 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
295 update_only_visible: true, // enable or disable visibility management
297 parallel_refresher: true, // enable parallel refresh of charts
299 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
301 destroy_on_hide: false, // destroy charts when they are not visible
303 show_help: netdataShowHelp, // when enabled the charts will show some help
304 show_help_delay_show_ms: 500,
305 show_help_delay_hide_ms: 0,
307 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
309 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
310 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
312 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
314 smooth_plot: true, // enable smooth plot, where possible
316 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
318 color_fill_opacity_line: 1.0,
319 color_fill_opacity_area: 0.2,
320 color_fill_opacity_stacked: 0.8,
322 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
323 pan_and_zoom_factor_multiplier_control: 2.0,
324 pan_and_zoom_factor_multiplier_shift: 3.0,
325 pan_and_zoom_factor_multiplier_alt: 4.0,
327 abort_ajax_on_scroll: false, // kill pending ajax page scroll
328 async_on_scroll: false, // sync/async onscroll handler
329 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
331 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
333 setOptionCallback: function() { }
341 chart_data_url: false,
342 chart_errors: false, // FIXME: remember to set it to false before merging
350 NETDATA.statistics = {
353 refreshes_active_max: 0
357 // ----------------------------------------------------------------------------------------------------------------
358 // local storage options
360 NETDATA.localStorage = {
363 callback: {} // only used for resetting back to defaults
366 NETDATA.localStorageGet = function(key, def, callback) {
369 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
370 NETDATA.localStorage.default[key.toString()] = def;
371 NETDATA.localStorage.callback[key.toString()] = callback;
374 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
376 // console.log('localStorage: loading "' + key.toString() + '"');
377 ret = localStorage.getItem(key.toString());
378 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
379 if(ret === null || ret === 'undefined') {
380 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
381 localStorage.setItem(key.toString(), JSON.stringify(def));
385 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
386 ret = JSON.parse(ret);
387 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
391 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
396 if(typeof ret === 'undefined' || ret === 'undefined') {
397 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
401 NETDATA.localStorage.current[key.toString()] = ret;
405 NETDATA.localStorageSet = function(key, value, callback) {
406 if(typeof value === 'undefined' || value === 'undefined') {
407 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
410 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
411 NETDATA.localStorage.default[key.toString()] = value;
412 NETDATA.localStorage.current[key.toString()] = value;
413 NETDATA.localStorage.callback[key.toString()] = callback;
416 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
417 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
419 localStorage.setItem(key.toString(), JSON.stringify(value));
422 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
426 NETDATA.localStorage.current[key.toString()] = value;
430 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
431 var keys = Object.keys(obj);
432 var len = keys.length;
436 if(typeof obj[i] === 'object') {
437 //console.log('object ' + prefix + '.' + i.toString());
438 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
442 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
446 NETDATA.setOption = function(key, value) {
447 if(key.toString() === 'setOptionCallback') {
448 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
449 NETDATA.options.current[key.toString()] = value;
450 NETDATA.options.current.setOptionCallback();
453 else if(NETDATA.options.current[key.toString()] !== value) {
454 var name = 'options.' + key.toString();
456 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
457 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
459 //console.log(NETDATA.localStorage);
460 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
461 //console.log(NETDATA.options);
462 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
464 if(typeof NETDATA.options.current.setOptionCallback === 'function')
465 NETDATA.options.current.setOptionCallback();
471 NETDATA.getOption = function(key) {
472 return NETDATA.options.current[key.toString()];
475 // read settings from local storage
476 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
478 // always start with this option enabled.
479 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
481 NETDATA.resetOptions = function() {
482 var keys = Object.keys(NETDATA.localStorage.default);
483 var len = keys.length;
486 var a = i.split('.');
488 if(a[0] === 'options') {
489 if(a[1] === 'setOptionCallback') continue;
490 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
491 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
493 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
495 else if(a[0] === 'chart_heights') {
496 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
497 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
503 // ----------------------------------------------------------------------------------------------------------------
505 if(NETDATA.options.debug.main_loop === true)
506 console.log('welcome to NETDATA');
508 NETDATA.onresizeCallback = null;
509 NETDATA.onresize = function() {
510 NETDATA.options.last_resized = Date.now();
513 if(typeof NETDATA.onresizeCallback === 'function')
514 NETDATA.onresizeCallback();
517 NETDATA.onscroll_updater_count = 0;
518 NETDATA.onscroll_updater_running = false;
519 NETDATA.onscroll_updater_last_run = 0;
520 NETDATA.onscroll_updater_watchdog = null;
521 NETDATA.onscroll_updater_max_duration = 0;
522 NETDATA.onscroll_updater_above_threshold_count = 0;
523 NETDATA.onscroll_updater = function() {
524 NETDATA.onscroll_updater_running = true;
525 NETDATA.onscroll_updater_count++;
526 var start = Date.now();
528 var targets = NETDATA.options.targets;
529 var len = targets.length;
531 // when the user scrolls he sees that we have
532 // hidden all the not-visible charts
533 // using this little function we try to switch
534 // the charts back to visible quickly
537 if(NETDATA.options.abort_ajax_on_scroll === true) {
538 // we have to cancel pending requests too
541 if (targets[len]._updating === true) {
542 if (typeof targets[len].xhr !== 'undefined') {
543 targets[len].xhr.abort();
544 targets[len].running = false;
545 targets[len]._updating = false;
547 targets[len].isVisible();
552 // just find which chart is visible
555 targets[len].isVisible();
558 var end = Date.now();
559 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
561 if(NETDATA.options.current.async_on_scroll === false) {
562 var dt = end - start;
563 if(dt > NETDATA.onscroll_updater_max_duration) {
564 // console.log('max onscroll event handler duration increased to ' + dt);
565 NETDATA.onscroll_updater_max_duration = dt;
568 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
569 // console.log('slow: ' + dt);
570 NETDATA.onscroll_updater_above_threshold_count++;
572 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
573 NETDATA.setOption('async_on_scroll', true);
574 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
579 NETDATA.onscroll_updater_last_run = start;
580 NETDATA.onscroll_updater_running = false;
583 NETDATA.onscroll = function() {
584 // console.log('onscroll');
586 NETDATA.options.last_page_scroll = Date.now();
587 NETDATA.options.auto_refresher_stop_until = 0;
589 if(NETDATA.options.targets === null) return;
591 if(NETDATA.options.current.async_on_scroll === true) {
593 if(NETDATA.onscroll_updater_running === false) {
594 NETDATA.onscroll_updater_running = true;
595 setTimeout(NETDATA.onscroll_updater, 0);
598 if(NETDATA.onscroll_updater_watchdog !== null)
599 clearTimeout(NETDATA.onscroll_updater_watchdog);
601 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
602 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
603 // console.log('watchdog');
604 NETDATA.onscroll_updater();
607 NETDATA.onscroll_updater_watchdog = null;
613 NETDATA.onscroll_updater();
617 window.onresize = NETDATA.onresize;
618 window.onscroll = NETDATA.onscroll;
620 // ----------------------------------------------------------------------------------------------------------------
623 NETDATA.errorCodes = {
624 100: { message: "Cannot load chart library", alert: true },
625 101: { message: "Cannot load jQuery", alert: true },
626 402: { message: "Chart library not found", alert: false },
627 403: { message: "Chart library not enabled/is failed", alert: false },
628 404: { message: "Chart not found", alert: false },
629 405: { message: "Cannot download charts index from server", alert: true },
630 406: { message: "Invalid charts index downloaded from server", alert: true },
631 407: { message: "Cannot HELLO netdata server", alert: false },
632 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
633 409: { message: "Cannot ACCESS netdata registry", alert: false },
634 410: { message: "Netdata registry ACCESS failed", alert: false },
635 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
636 412: { message: "Netdata registry DELETE failed", alert: false },
637 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
638 414: { message: "Netdata registry SWITCH failed", alert: false },
639 415: { message: "Netdata alarms download failed", alert: false },
640 416: { message: "Netdata alarms log download failed", alert: false },
641 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
642 418: { message: "Netdata registry SEARCH failed", alert: false }
644 NETDATA.errorLast = {
650 NETDATA.error = function(code, msg) {
651 NETDATA.errorLast.code = code;
652 NETDATA.errorLast.message = msg;
653 NETDATA.errorLast.datetime = Date.now();
655 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
658 if(typeof netdataErrorCallback === 'function') {
659 ret = netdataErrorCallback('system', code, msg);
662 if(ret && NETDATA.errorCodes[code].alert)
663 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
666 NETDATA.errorReset = function() {
667 NETDATA.errorLast.code = 0;
668 NETDATA.errorLast.message = "You are doing fine!";
669 NETDATA.errorLast.datetime = 0;
672 // ----------------------------------------------------------------------------------------------------------------
673 // commonMin & commonMax
675 NETDATA.commonMin = {
679 get: function(state) {
680 if(typeof state.__commonMin === 'undefined') {
681 // get the commonMin setting
682 var self = $(state.element);
683 state.__commonMin = self.data('common-min') || null;
686 var min = state.data.min;
687 var name = state.__commonMin;
690 // we don't need commonMin
691 //state.log('no need for commonMin');
695 var t = this.keys[name];
696 if(typeof t === 'undefined') {
698 this.keys[name] = {};
702 var uuid = state.uuid;
703 if(typeof t[uuid] !== 'undefined') {
704 if(t[uuid] === min) {
705 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
706 return this.latest[name];
708 else if(min < this.latest[name]) {
709 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
711 this.latest[name] = min;
719 // find the common min
722 if(t[i] < m) m = t[i];
724 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
725 this.latest[name] = m;
730 NETDATA.commonMax = {
734 get: function(state) {
735 if(typeof state.__commonMax === 'undefined') {
736 // get the commonMax setting
737 var self = $(state.element);
738 state.__commonMax = self.data('common-max') || null;
741 var max = state.data.max;
742 var name = state.__commonMax;
745 // we don't need commonMax
746 //state.log('no need for commonMax');
750 var t = this.keys[name];
751 if(typeof t === 'undefined') {
753 this.keys[name] = {};
757 var uuid = state.uuid;
758 if(typeof t[uuid] !== 'undefined') {
759 if(t[uuid] === max) {
760 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
761 return this.latest[name];
763 else if(max > this.latest[name]) {
764 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
766 this.latest[name] = max;
774 // find the common max
777 if(t[i] > m) m = t[i];
779 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
780 this.latest[name] = m;
785 // ----------------------------------------------------------------------------------------------------------------
788 // When multiple charts need the same chart, we avoid downloading it
789 // multiple times (and having it in browser memory multiple time)
790 // by using this registry.
792 // Every time we download a chart definition, we save it here with .add()
793 // Then we try to get it back with .get(). If that fails, we download it.
795 NETDATA.fixHost = function(host) {
796 while(host.slice(-1) === '/')
797 host = host.substring(0, host.length - 1);
802 NETDATA.chartRegistry = {
805 fixid: function(id) {
806 return id.replace(/:/g, "_").replace(/\//g, "_");
809 add: function(host, id, data) {
810 host = this.fixid(host);
813 if(typeof this.charts[host] === 'undefined')
814 this.charts[host] = {};
816 //console.log('added ' + host + '/' + id);
817 this.charts[host][id] = data;
820 get: function(host, id) {
821 host = this.fixid(host);
824 if(typeof this.charts[host] === 'undefined')
827 if(typeof this.charts[host][id] === 'undefined')
830 //console.log('cached ' + host + '/' + id);
831 return this.charts[host][id];
834 downloadAll: function(host, callback) {
835 host = NETDATA.fixHost(host);
840 url: host + '/api/v1/charts',
843 xhrFields: { withCredentials: true } // required for the cookie
845 .done(function(data) {
847 var h = NETDATA.chartRegistry.fixid(host);
848 self.charts[h] = data.charts;
850 else NETDATA.error(406, host + '/api/v1/charts');
852 if(typeof callback === 'function')
853 return callback(data);
856 NETDATA.error(405, host + '/api/v1/charts');
858 if(typeof callback === 'function')
859 return callback(null);
864 // ----------------------------------------------------------------------------------------------------------------
865 // Global Pan and Zoom on charts
867 // Using this structure are synchronize all the charts, so that
868 // when you pan or zoom one, all others are automatically refreshed
869 // to the same timespan.
871 NETDATA.globalPanAndZoom = {
872 seq: 0, // timestamp ms
873 // every time a chart is panned or zoomed
874 // we set the timestamp here
875 // then we use it as a sequence number
876 // to find if other charts are syncronized
879 master: null, // the master chart (state), to which all others
882 force_before_ms: null, // the timespan to sync all other charts
883 force_after_ms: null,
888 setMaster: function(state, after, before) {
889 if(NETDATA.options.current.sync_pan_and_zoom === false)
892 if(this.master !== null && this.master !== state)
893 this.master.resetChart(true, true);
895 var now = Date.now();
898 this.force_after_ms = after;
899 this.force_before_ms = before;
900 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
902 if(typeof this.callback === 'function')
903 this.callback(true, after, before);
907 clearMaster: function() {
908 if(this.master !== null) {
909 var st = this.master;
916 this.force_after_ms = null;
917 this.force_before_ms = null;
918 NETDATA.options.auto_refresher_stop_until = 0;
920 if(typeof this.callback === 'function')
921 this.callback(false, 0, 0);
924 // is the given state the master of the global
925 // pan and zoom sync?
926 isMaster: function(state) {
927 if(this.master === state) return true;
931 // are we currently have a global pan and zoom sync?
932 isActive: function() {
933 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
937 // check if a chart, other than the master
938 // needs to be refreshed, due to the global pan and zoom
939 shouldBeAutoRefreshed: function(state) {
940 if(this.master === null || this.seq === 0)
943 //if(state.needsRecreation())
946 if(state.tm.pan_and_zoom_seq === this.seq)
953 // ----------------------------------------------------------------------------------------------------------------
954 // dimensions selection
957 // move color assignment to dimensions, here
959 dimensionStatus = function(parent, label, name_div, value_div, color) {
960 this.enabled = false;
961 this.parent = parent;
963 this.name_div = null;
964 this.value_div = null;
965 this.color = NETDATA.themes.current.foreground;
967 if(parent.unselected_count === 0)
968 this.selected = true;
970 this.selected = false;
972 this.setOptions(name_div, value_div, color);
975 dimensionStatus.prototype.invalidate = function() {
976 this.name_div = null;
977 this.value_div = null;
978 this.enabled = false;
981 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
984 if(this.name_div !== name_div) {
985 this.name_div = name_div;
986 this.name_div.title = this.label;
987 this.name_div.style.color = this.color;
988 if(this.selected === false)
989 this.name_div.className = 'netdata-legend-name not-selected';
991 this.name_div.className = 'netdata-legend-name selected';
994 if(this.value_div !== value_div) {
995 this.value_div = value_div;
996 this.value_div.title = this.label;
997 this.value_div.style.color = this.color;
998 if(this.selected === false)
999 this.value_div.className = 'netdata-legend-value not-selected';
1001 this.value_div.className = 'netdata-legend-value selected';
1004 this.enabled = true;
1008 dimensionStatus.prototype.setHandler = function() {
1009 if(this.enabled === false) return;
1013 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1014 this.name_div.onclick = this.value_div.onclick = function(e) {
1016 if(ds.isSelected()) {
1018 if(e.shiftKey === true || e.ctrlKey === true) {
1019 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1022 if(ds.parent.countSelected() === 0)
1023 ds.parent.selectAll();
1026 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1027 if(ds.parent.countSelected() === 1) {
1028 ds.parent.selectAll();
1031 ds.parent.selectNone();
1037 // this is not selected
1038 if(e.shiftKey === true || e.ctrlKey === true) {
1039 // control or shift key is pressed -> select this too
1043 // no key is pressed -> select only this
1044 ds.parent.selectNone();
1049 ds.parent.state.redrawChart();
1053 dimensionStatus.prototype.select = function() {
1054 if(this.enabled === false) return;
1056 this.name_div.className = 'netdata-legend-name selected';
1057 this.value_div.className = 'netdata-legend-value selected';
1058 this.selected = true;
1061 dimensionStatus.prototype.unselect = function() {
1062 if(this.enabled === false) return;
1064 this.name_div.className = 'netdata-legend-name not-selected';
1065 this.value_div.className = 'netdata-legend-value hidden';
1066 this.selected = false;
1069 dimensionStatus.prototype.isSelected = function() {
1070 return(this.enabled === true && this.selected === true);
1073 // ----------------------------------------------------------------------------------------------------------------
1075 dimensionsVisibility = function(state) {
1078 this.dimensions = {};
1079 this.selected_count = 0;
1080 this.unselected_count = 0;
1083 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1084 if(typeof this.dimensions[label] === 'undefined') {
1086 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1089 this.dimensions[label].setOptions(name_div, value_div, color);
1091 return this.dimensions[label];
1094 dimensionsVisibility.prototype.dimensionGet = function(label) {
1095 return this.dimensions[label];
1098 dimensionsVisibility.prototype.invalidateAll = function() {
1099 var keys = Object.keys(this.dimensions);
1100 var len = keys.length;
1102 this.dimensions[keys[len]].invalidate();
1105 dimensionsVisibility.prototype.selectAll = function() {
1106 var keys = Object.keys(this.dimensions);
1107 var len = keys.length;
1109 this.dimensions[keys[len]].select();
1112 dimensionsVisibility.prototype.countSelected = function() {
1114 var keys = Object.keys(this.dimensions);
1115 var len = keys.length;
1117 if(this.dimensions[keys[len]].isSelected()) selected++;
1122 dimensionsVisibility.prototype.selectNone = function() {
1123 var keys = Object.keys(this.dimensions);
1124 var len = keys.length;
1126 this.dimensions[keys[len]].unselect();
1129 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1130 var ret = new Array();
1131 this.selected_count = 0;
1132 this.unselected_count = 0;
1134 var len = array.length;
1136 var ds = this.dimensions[array[len]];
1137 if(typeof ds === 'undefined') {
1138 // console.log(array[i] + ' is not found');
1141 else if(ds.isSelected()) {
1143 this.selected_count++;
1147 this.unselected_count++;
1151 if(this.selected_count === 0 && this.unselected_count !== 0) {
1153 return this.selected2BooleanArray(array);
1160 // ----------------------------------------------------------------------------------------------------------------
1161 // global selection sync
1163 NETDATA.globalSelectionSync = {
1165 dont_sync_before: 0,
1170 if(this.state !== null)
1171 this.state.globalSelectionSyncStop();
1175 if(this.state !== null) {
1176 this.state.globalSelectionSyncDelay();
1181 // ----------------------------------------------------------------------------------------------------------------
1182 // Our state object, where all per-chart values are stored
1184 chartState = function(element) {
1185 var self = $(element);
1186 this.element = element;
1189 // all private functions should use 'that', instead of 'this'
1192 /* error() - private
1193 * show an error instead of the chart
1195 var error = function(msg) {
1198 if(typeof netdataErrorCallback === 'function') {
1199 ret = netdataErrorCallback('chart', that.id, msg);
1203 that.element.innerHTML = that.id + ': ' + msg;
1204 that.enabled = false;
1205 that.current = that.pan;
1209 // GUID - a unique identifier for the chart
1210 this.uuid = NETDATA.guid();
1212 // string - the name of chart
1213 this.id = self.data('netdata');
1215 // string - the key for localStorage settings
1216 this.settings_id = self.data('id') || null;
1218 // the user given dimensions of the element
1219 this.width = self.data('width') || NETDATA.chartDefaults.width;
1220 this.height = self.data('height') || NETDATA.chartDefaults.height;
1221 this.height_original = this.height;
1223 if(this.settings_id !== null) {
1224 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1225 // this is the callback that will be called
1226 // if and when the user resets all localStorage variables
1227 // to their defaults
1229 resizeChartToHeight(height);
1233 // string - the netdata server URL, without any path
1234 this.host = self.data('host') || NETDATA.chartDefaults.host;
1236 // make sure the host does not end with /
1237 // all netdata API requests use absolute paths
1238 while(this.host.slice(-1) === '/')
1239 this.host = this.host.substring(0, this.host.length - 1);
1241 // string - the grouping method requested by the user
1242 this.method = self.data('method') || NETDATA.chartDefaults.method;
1244 // the time-range requested by the user
1245 this.after = self.data('after') || NETDATA.chartDefaults.after;
1246 this.before = self.data('before') || NETDATA.chartDefaults.before;
1248 // the pixels per point requested by the user
1249 this.pixels_per_point = self.data('pixels-per-point') || 1;
1250 this.points = self.data('points') || null;
1252 // the dimensions requested by the user
1253 this.dimensions = self.data('dimensions') || null;
1255 // the chart library requested by the user
1256 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1258 // how many retries we have made to load chart data from the server
1259 this.retries_on_data_failures = 0;
1261 // object - the chart library used
1262 this.library = null;
1266 this.colors_assigned = {};
1267 this.colors_available = null;
1269 // the element already created by the user
1270 this.element_message = null;
1272 // the element with the chart
1273 this.element_chart = null;
1275 // the element with the legend of the chart (if created by us)
1276 this.element_legend = null;
1277 this.element_legend_childs = {
1282 perfect_scroller: null, // the container to apply perfect scroller to
1286 this.chart_url = null; // string - the url to download chart info
1287 this.chart = null; // object - the chart as downloaded from the server
1289 this.title = self.data('title') || null; // the title of the chart
1290 this.units = self.data('units') || null; // the units of the chart dimensions
1291 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1292 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1294 this.running = false; // boolean - true when the chart is being refreshed now
1295 this.validated = false; // boolean - has the chart been validated?
1296 this.enabled = true; // boolean - is the chart enabled for refresh?
1297 this.paused = false; // boolean - is the chart paused for any reason?
1298 this.selected = false; // boolean - is the chart shown a selection?
1299 this.debug = false; // boolean - console.log() debug info about this chart
1301 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1302 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1303 this.requested_after = null; // milliseconds - the timestamp of the request after param
1304 this.requested_before = null; // milliseconds - the timestamp of the request before param
1305 this.requested_padding = null;
1306 this.view_after = 0;
1307 this.view_before = 0;
1309 this.value_decimal_detail = -1;
1310 var d = self.data('decimal-digits');
1311 if(typeof d === 'number') {
1312 this.value_decimal_detail = 1;
1314 this.value_decimal_detail *= 10;
1320 force_update_at: 0, // the timestamp to force the update at
1321 force_before_ms: null,
1322 force_after_ms: null
1327 force_update_at: 0, // the timestamp to force the update at
1328 force_before_ms: null,
1329 force_after_ms: null
1334 force_update_at: 0, // the timestamp to force the update at
1335 force_before_ms: null,
1336 force_after_ms: null
1339 // this is a pointer to one of the sub-classes below
1341 this.current = this.auto;
1343 // check the requested library is available
1344 // we don't initialize it here - it will be initialized when
1345 // this chart will be first used
1346 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1347 NETDATA.error(402, that.library_name);
1348 error('chart library "' + that.library_name + '" is not found');
1351 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1352 NETDATA.error(403, that.library_name);
1353 error('chart library "' + that.library_name + '" is not enabled');
1357 that.library = NETDATA.chartLibraries[that.library_name];
1359 // milliseconds - the time the last refresh took
1360 this.refresh_dt_ms = 0;
1362 // if we need to report the rendering speed
1363 // find the element that needs to be updated
1364 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1366 if(refresh_dt_element_name !== null)
1367 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1369 this.refresh_dt_element = null;
1371 this.dimensions_visibility = new dimensionsVisibility(this);
1373 this._updating = false;
1375 // ============================================================================================================
1376 // PRIVATE FUNCTIONS
1378 var createDOM = function() {
1379 if(that.enabled === false) return;
1381 if(that.element_message !== null) that.element_message.innerHTML = '';
1382 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1383 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1385 that.element.innerHTML = '';
1387 that.element_message = document.createElement('div');
1388 that.element_message.className = 'netdata-message icon hidden';
1389 that.element.appendChild(that.element_message);
1391 that.element_chart = document.createElement('div');
1392 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1393 that.element.appendChild(that.element_chart);
1395 if(that.hasLegend() === true) {
1396 that.element.className = "netdata-container-with-legend";
1397 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1399 that.element_legend = document.createElement('div');
1400 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1401 that.element.appendChild(that.element_legend);
1404 that.element.className = "netdata-container";
1405 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1407 that.element_legend = null;
1409 that.element_legend_childs.series = null;
1411 if(typeof(that.width) === 'string')
1412 $(that.element).css('width', that.width);
1413 else if(typeof(that.width) === 'number')
1414 $(that.element).css('width', that.width + 'px');
1416 if(typeof(that.library.aspect_ratio) === 'undefined') {
1417 if(typeof(that.height) === 'string')
1418 that.element.style.height = that.height;
1419 else if(typeof(that.height) === 'number')
1420 that.element.style.height = that.height.toString() + 'px';
1423 var w = that.element.offsetWidth;
1424 if(w === null || w === 0) {
1425 // the div is hidden
1426 // this will resize the chart when next viewed
1427 that.tm.last_resized = 0;
1430 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1433 if(NETDATA.chartDefaults.min_width !== null)
1434 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1436 that.tm.last_dom_created = Date.now();
1442 * initialize state variables
1443 * destroy all (possibly) created state elements
1444 * create the basic DOM for a chart
1446 var init = function() {
1447 if(that.enabled === false) return;
1449 that.paused = false;
1450 that.selected = false;
1452 that.chart_created = false; // boolean - is the library.create() been called?
1453 that.updates_counter = 0; // numeric - the number of refreshes made so far
1454 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1455 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1458 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1459 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1460 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1462 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1463 last_updated: 0, // the timestamp the chart last updated with data
1464 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1466 // Used with NETDATA.globalPanAndZoom.seq
1467 last_visible_check: 0, // the time we last checked if it is visible
1468 last_resized: 0, // the time the chart was resized
1469 last_hidden: 0, // the time the chart was hidden
1470 last_unhidden: 0, // the time the chart was unhidden
1471 last_autorefreshed: 0 // the time the chart was last refreshed
1474 that.data = null; // the last data as downloaded from the netdata server
1475 that.data_url = 'invalid://'; // string - the last url used to update the chart
1476 that.data_points = 0; // number - the number of points returned from netdata
1477 that.data_after = 0; // milliseconds - the first timestamp of the data
1478 that.data_before = 0; // milliseconds - the last timestamp of the data
1479 that.data_update_every = 0; // milliseconds - the frequency to update the data
1481 that.tm.last_initialized = Date.now();
1484 that.setMode('auto');
1487 var maxMessageFontSize = function() {
1488 var screenHeight = screen.height;
1489 var el = that.element;
1491 // normally we want a font size, as tall as the element
1492 var h = el.clientHeight;
1494 // but give it some air, 20% let's say, or 5 pixels min
1495 var lost = Math.max(h * 0.2, 5);
1498 // center the text, vertically
1499 var paddingTop = (lost - 5) / 2;
1501 // but check the width too
1502 // it should fit 10 characters in it
1503 var w = el.clientWidth / 10;
1505 paddingTop += (h - w) / 2;
1509 // and don't make it too huge
1510 // 5% of the screen size is good
1511 if(h > screenHeight / 20) {
1512 paddingTop += (h - (screenHeight / 20)) / 2;
1513 h = screenHeight / 20;
1517 that.element_message.style.fontSize = h.toString() + 'px';
1518 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1521 var showMessageIcon = function(icon) {
1522 that.element_message.innerHTML = icon;
1523 maxMessageFontSize();
1524 $(that.element_message).removeClass('hidden');
1525 that.___messageHidden___ = undefined;
1528 var hideMessage = function() {
1529 if(typeof that.___messageHidden___ === 'undefined') {
1530 that.___messageHidden___ = true;
1531 $(that.element_message).addClass('hidden');
1535 var showRendering = function() {
1537 if(that.chart !== null) {
1538 if(that.chart.chart_type === 'line')
1539 icon = '<i class="fa fa-line-chart"></i>';
1541 icon = '<i class="fa fa-area-chart"></i>';
1544 icon = '<i class="fa fa-area-chart"></i>';
1546 showMessageIcon(icon + ' netdata');
1549 var showLoading = function() {
1550 if(that.chart_created === false) {
1551 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1557 var isHidden = function() {
1558 if(typeof that.___chartIsHidden___ !== 'undefined')
1564 // hide the chart, when it is not visible - called from isVisible()
1565 var hideChart = function() {
1566 // hide it, if it is not already hidden
1567 if(isHidden() === true) return;
1569 if(that.chart_created === true) {
1570 if(NETDATA.options.current.destroy_on_hide === true) {
1571 // we should destroy it
1576 that.element_chart.style.display = 'none';
1577 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1578 that.tm.last_hidden = Date.now();
1581 // This works, but I not sure there are no corner cases somewhere
1582 // so it is commented - if the user has memory issues he can
1583 // set Destroy on Hide for all charts
1584 // that.data = null;
1588 that.___chartIsHidden___ = true;
1591 // unhide the chart, when it is visible - called from isVisible()
1592 var unhideChart = function() {
1593 if(isHidden() === false) return;
1595 that.___chartIsHidden___ = undefined;
1596 that.updates_since_last_unhide = 0;
1598 if(that.chart_created === false) {
1599 // we need to re-initialize it, to show our background
1600 // logo in bootstrap tabs, until the chart loads
1604 that.tm.last_unhidden = Date.now();
1605 that.element_chart.style.display = '';
1606 if(that.element_legend !== null) that.element_legend.style.display = '';
1612 var canBeRendered = function() {
1613 if(isHidden() === true || that.isVisible(true) === false)
1619 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1620 var callChartLibraryUpdateSafely = function(data) {
1623 if(canBeRendered() === false)
1626 if(NETDATA.options.debug.chart_errors === true)
1627 status = that.library.update(that, data);
1630 status = that.library.update(that, data);
1637 if(status === false) {
1638 error('chart failed to be updated as ' + that.library_name);
1645 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1646 var callChartLibraryCreateSafely = function(data) {
1649 if(canBeRendered() === false)
1652 if(NETDATA.options.debug.chart_errors === true)
1653 status = that.library.create(that, data);
1656 status = that.library.create(that, data);
1663 if(status === false) {
1664 error('chart failed to be created as ' + that.library_name);
1668 that.chart_created = true;
1669 that.updates_since_last_creation = 0;
1673 // ----------------------------------------------------------------------------------------------------------------
1676 // resizeChart() - private
1677 // to be called just before the chart library to make sure that
1678 // a properly sized dom is available
1679 var resizeChart = function() {
1680 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1681 if(that.chart_created === false) return;
1683 if(that.needsRecreation()) {
1686 else if(typeof that.library.resize === 'function') {
1687 that.library.resize(that);
1689 if(that.element_legend_childs.perfect_scroller !== null)
1690 Ps.update(that.element_legend_childs.perfect_scroller);
1692 maxMessageFontSize();
1695 that.tm.last_resized = Date.now();
1699 // this is the actual chart resize algorithm
1701 // - resize the entire container
1702 // - update the internal states
1703 // - resize the chart as the div changes height
1704 // - update the scrollbar of the legend
1705 var resizeChartToHeight = function(h) {
1707 that.element.style.height = h;
1709 if(that.settings_id !== null)
1710 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1712 var now = Date.now();
1713 NETDATA.options.last_page_scroll = now;
1714 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1717 that.tm.last_resized = 0;
1721 this.resizeHandler = function(e) {
1724 if(typeof this.event_resize === 'undefined'
1725 || this.event_resize.chart_original_w === 'undefined'
1726 || this.event_resize.chart_original_h === 'undefined')
1727 this.event_resize = {
1728 chart_original_w: this.element.clientWidth,
1729 chart_original_h: this.element.clientHeight,
1733 if(e.type === 'touchstart') {
1734 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1735 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1738 this.event_resize.mouse_start_x = e.clientX;
1739 this.event_resize.mouse_start_y = e.clientY;
1742 this.event_resize.chart_start_w = this.element.clientWidth;
1743 this.event_resize.chart_start_h = this.element.clientHeight;
1744 this.event_resize.chart_last_w = this.element.clientWidth;
1745 this.event_resize.chart_last_h = this.element.clientHeight;
1747 var now = Date.now();
1748 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1749 // double click / double tap event
1751 // console.dir(this.element_legend_childs.content);
1752 // console.dir(this.element_legend_childs.perfect_scroller);
1754 // the optimal height of the chart
1755 // showing the entire legend
1756 var optimal = this.event_resize.chart_last_h
1757 + this.element_legend_childs.perfect_scroller.scrollHeight
1758 - this.element_legend_childs.perfect_scroller.clientHeight;
1760 // if we are not optimal, be optimal
1761 if(this.event_resize.chart_last_h !== optimal) {
1762 // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1763 resizeChartToHeight(optimal.toString() + 'px');
1766 // else if the current height is not the original/saved height
1767 // reset to the original/saved height
1768 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1769 // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1770 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1773 // else if the current height is not the internal default height
1774 // reset to the internal default height
1775 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1776 // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1777 resizeChartToHeight(this.height_original.toString());
1780 // else if the current height is not the firstchild's clientheight
1782 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1783 var parent_rect = this.element.getBoundingClientRect();
1784 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1785 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1787 // console.log(parent_rect);
1788 // console.log(content_rect);
1789 // console.log(wanted);
1791 // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1792 if(this.event_resize.chart_last_h !== wanted)
1793 resizeChartToHeight(wanted.toString() + 'px');
1797 this.event_resize.last = now;
1799 // process movement event
1800 document.onmousemove =
1801 document.ontouchmove =
1802 this.element_legend_childs.resize_handler.onmousemove =
1803 this.element_legend_childs.resize_handler.ontouchmove =
1808 case 'mousemove': y = e.clientY; break;
1809 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1813 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1815 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1816 resizeChartToHeight(newH.toString() + 'px');
1817 that.event_resize.chart_last_h = newH;
1822 // process end event
1823 document.onmouseup =
1824 document.ontouchend =
1825 this.element_legend_childs.resize_handler.onmouseup =
1826 this.element_legend_childs.resize_handler.ontouchend =
1828 // remove all the hooks
1829 document.onmouseup =
1830 document.onmousemove =
1831 document.ontouchmove =
1832 document.ontouchend =
1833 that.element_legend_childs.resize_handler.onmousemove =
1834 that.element_legend_childs.resize_handler.ontouchmove =
1835 that.element_legend_childs.resize_handler.onmouseout =
1836 that.element_legend_childs.resize_handler.onmouseup =
1837 that.element_legend_childs.resize_handler.ontouchend =
1840 // allow auto-refreshes
1841 NETDATA.options.auto_refresher_stop_until = 0;
1847 var noDataToShow = function() {
1848 showMessageIcon('<i class="fa fa-warning"></i> empty');
1849 that.legendUpdateDOM();
1850 that.tm.last_autorefreshed = Date.now();
1851 // that.data_update_every = 30 * 1000;
1852 //that.element_chart.style.display = 'none';
1853 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1854 //that.___chartIsHidden___ = true;
1857 // ============================================================================================================
1860 this.error = function(msg) {
1864 this.setMode = function(m) {
1865 if(this.current !== null && this.current.name === m) return;
1868 this.current = this.auto;
1869 else if(m === 'pan')
1870 this.current = this.pan;
1871 else if(m === 'zoom')
1872 this.current = this.zoom;
1874 this.current = this.auto;
1876 this.current.force_update_at = 0;
1877 this.current.force_before_ms = null;
1878 this.current.force_after_ms = null;
1880 this.tm.last_mode_switch = Date.now();
1883 // ----------------------------------------------------------------------------------------------------------------
1884 // global selection sync
1886 // prevent to global selection sync for some time
1887 this.globalSelectionSyncDelay = function(ms) {
1888 if(NETDATA.options.current.sync_selection === false)
1891 if(typeof ms === 'number')
1892 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1894 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1897 // can we globally apply selection sync?
1898 this.globalSelectionSyncAbility = function() {
1899 if(NETDATA.options.current.sync_selection === false)
1902 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1908 this.globalSelectionSyncIsMaster = function() {
1909 return (NETDATA.globalSelectionSync.state === this);
1912 // this chart is the master of the global selection sync
1913 this.globalSelectionSyncBeMaster = function() {
1915 if(this.globalSelectionSyncIsMaster()) {
1916 if(this.debug === true)
1917 this.log('sync: I am the master already.');
1922 if(NETDATA.globalSelectionSync.state) {
1923 if(this.debug === true)
1924 this.log('sync: I am not the sync master. Resetting global sync.');
1926 this.globalSelectionSyncStop();
1929 // become the master
1930 if(this.debug === true)
1931 this.log('sync: becoming sync master.');
1933 this.selected = true;
1934 NETDATA.globalSelectionSync.state = this;
1936 // find the all slaves
1937 var targets = NETDATA.options.targets;
1938 var len = targets.length;
1943 if(this.debug === true)
1944 st.log('sync: not adding me to sync');
1946 else if(st.globalSelectionSyncIsEligible()) {
1947 if(this.debug === true)
1948 st.log('sync: adding to sync as slave');
1950 st.globalSelectionSyncBeSlave();
1954 // this.globalSelectionSyncDelay(100);
1957 // can the chart participate to the global selection sync as a slave?
1958 this.globalSelectionSyncIsEligible = function() {
1959 if(this.enabled === true
1960 && this.library !== null
1961 && typeof this.library.setSelection === 'function'
1962 && this.isVisible() === true
1963 && this.chart_created === true)
1969 // this chart becomes a slave of the global selection sync
1970 this.globalSelectionSyncBeSlave = function() {
1971 if(NETDATA.globalSelectionSync.state !== this)
1972 NETDATA.globalSelectionSync.slaves.push(this);
1975 // sync all the visible charts to the given time
1976 // this is to be called from the chart libraries
1977 this.globalSelectionSync = function(t) {
1978 if(this.globalSelectionSyncAbility() === false)
1981 if(this.globalSelectionSyncIsMaster() === false) {
1982 if(this.debug === true)
1983 this.log('sync: trying to be sync master.');
1985 this.globalSelectionSyncBeMaster();
1987 if(this.globalSelectionSyncAbility() === false)
1991 NETDATA.globalSelectionSync.last_t = t;
1992 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1997 // stop syncing all charts to the given time
1998 this.globalSelectionSyncStop = function() {
1999 if(NETDATA.globalSelectionSync.slaves.length) {
2000 if(this.debug === true)
2001 this.log('sync: cleaning up...');
2003 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2005 if(that.debug === true)
2006 st.log('sync: not adding me to sync stop');
2009 if(that.debug === true)
2010 st.log('sync: removed slave from sync');
2012 st.clearSelection();
2016 NETDATA.globalSelectionSync.last_t = 0;
2017 NETDATA.globalSelectionSync.slaves = [];
2018 NETDATA.globalSelectionSync.state = null;
2021 this.clearSelection();
2024 this.setSelection = function(t) {
2025 if(typeof this.library.setSelection === 'function') {
2026 if(this.library.setSelection(this, t) === true)
2027 this.selected = true;
2029 this.selected = false;
2031 else this.selected = true;
2033 if(this.selected === true && this.debug === true)
2034 this.log('selection set to ' + t.toString());
2036 return this.selected;
2039 this.clearSelection = function() {
2040 if(this.selected === true) {
2041 if(typeof this.library.clearSelection === 'function') {
2042 if(this.library.clearSelection(this) === true)
2043 this.selected = false;
2045 this.selected = true;
2047 else this.selected = false;
2049 if(this.selected === false && this.debug === true)
2050 this.log('selection cleared');
2055 return this.selected;
2058 // find if a timestamp (ms) is shown in the current chart
2059 this.timeIsVisible = function(t) {
2060 if(t >= this.data_after && t <= this.data_before)
2065 this.calculateRowForTime = function(t) {
2066 if(this.timeIsVisible(t) === false) return -1;
2067 return Math.floor((t - this.data_after) / this.data_update_every);
2070 // ----------------------------------------------------------------------------------------------------------------
2073 this.log = function(msg) {
2074 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2077 this.pauseChart = function() {
2078 if(this.paused === false) {
2079 if(this.debug === true)
2080 this.log('pauseChart()');
2086 this.unpauseChart = function() {
2087 if(this.paused === true) {
2088 if(this.debug === true)
2089 this.log('unpauseChart()');
2091 this.paused = false;
2095 this.resetChart = function(dont_clear_master, dont_update) {
2096 if(this.debug === true)
2097 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2099 if(typeof dont_clear_master === 'undefined')
2100 dont_clear_master = false;
2102 if(typeof dont_update === 'undefined')
2103 dont_update = false;
2105 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2106 if(this.debug === true)
2107 this.log('resetChart() diverting to clearMaster().');
2108 // this will call us back with master === true
2109 NETDATA.globalPanAndZoom.clearMaster();
2113 this.clearSelection();
2115 this.tm.pan_and_zoom_seq = 0;
2117 this.setMode('auto');
2118 this.current.force_update_at = 0;
2119 this.current.force_before_ms = null;
2120 this.current.force_after_ms = null;
2121 this.tm.last_autorefreshed = 0;
2122 this.paused = false;
2123 this.selected = false;
2124 this.enabled = true;
2125 // this.debug = false;
2127 // do not update the chart here
2128 // or the chart will flip-flop when it is the master
2129 // of a selection sync and another chart becomes
2132 if(dont_update !== true && this.isVisible() === true) {
2137 this.updateChartPanOrZoom = function(after, before) {
2138 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2141 if(this.debug === true)
2144 if(before < after) {
2145 if(this.debug === true)
2146 this.log(logme + 'flipped parameters, rejecting it.');
2151 if(typeof this.fixed_min_duration === 'undefined')
2152 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2154 var min_duration = this.fixed_min_duration;
2155 var current_duration = Math.round(this.view_before - this.view_after);
2157 // round the numbers
2158 after = Math.round(after);
2159 before = Math.round(before);
2161 // align them to update_every
2162 // stretching them further away
2163 after -= after % this.data_update_every;
2164 before += this.data_update_every - (before % this.data_update_every);
2166 // the final wanted duration
2167 var wanted_duration = before - after;
2169 // to allow panning, accept just a point below our minimum
2170 if((current_duration - this.data_update_every) < min_duration)
2171 min_duration = current_duration - this.data_update_every;
2173 // we do it, but we adjust to minimum size and return false
2174 // when the wanted size is below the current and the minimum
2176 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2177 if(this.debug === true)
2178 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2180 min_duration = this.fixed_min_duration;
2182 var dt = (min_duration - wanted_duration) / 2;
2185 wanted_duration = before - after;
2189 var tolerance = this.data_update_every * 2;
2190 var movement = Math.abs(before - this.view_before);
2192 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2193 if(this.debug === true)
2194 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);
2198 if(this.current.name === 'auto') {
2199 this.log(logme + 'caller called me with mode: ' + this.current.name);
2200 this.setMode('pan');
2203 if(this.debug === true)
2204 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);
2206 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2207 this.current.force_after_ms = after;
2208 this.current.force_before_ms = before;
2209 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2213 this.legendFormatValue = function(value) {
2214 if(value === null || value === 'undefined') return '-';
2215 if(typeof value !== 'number') return value;
2217 if(this.value_decimal_detail !== -1)
2218 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2220 var abs = Math.abs(value);
2221 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2222 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2223 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2224 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2225 return (Math.round(value * 10000) / 10000).toLocaleString();
2228 this.legendSetLabelValue = function(label, value) {
2229 var series = this.element_legend_childs.series[label];
2230 if(typeof series === 'undefined') return;
2231 if(series.value === null && series.user === null) return;
2234 // this slows down firefox and edge significantly
2235 // since it requires to use innerHTML(), instead of innerText()
2237 // if the value has not changed, skip DOM update
2238 //if(series.last === value) return;
2241 if(typeof value === 'number') {
2242 var v = Math.abs(value);
2243 s = r = this.legendFormatValue(value);
2245 if(typeof series.last === 'number') {
2246 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2247 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2248 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2250 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2260 series.last = value;
2264 var s = this.legendFormatValue(value);
2266 // caching: do not update the update to show the same value again
2267 if(s === series.last_shown_value) return;
2268 series.last_shown_value = s;
2270 if(series.value !== null) series.value.innerText = s;
2271 if(series.user !== null) series.user.innerText = s;
2274 this.__legendSetDateString = function(date) {
2275 if(date !== this.__last_shown_legend_date) {
2276 this.element_legend_childs.title_date.innerText = date;
2277 this.__last_shown_legend_date = date;
2281 this.__legendSetTimeString = function(time) {
2282 if(time !== this.__last_shown_legend_time) {
2283 this.element_legend_childs.title_time.innerText = time;
2284 this.__last_shown_legend_time = time;
2288 this.__legendSetUnitsString = function(units) {
2289 if(units !== this.__last_shown_legend_units) {
2290 this.element_legend_childs.title_units.innerText = units;
2291 this.__last_shown_legend_units = units;
2295 this.legendSetDate = function(ms) {
2296 if(typeof ms !== 'number') {
2297 this.legendShowUndefined();
2301 var d = new Date(ms);
2303 if(this.element_legend_childs.title_date)
2304 this.__legendSetDateString(d.toLocaleDateString());
2306 if(this.element_legend_childs.title_time)
2307 this.__legendSetTimeString(d.toLocaleTimeString());
2309 if(this.element_legend_childs.title_units)
2310 this.__legendSetUnitsString(this.units)
2313 this.legendShowUndefined = function() {
2314 if(this.element_legend_childs.title_date)
2315 this.__legendSetDateString(' ');
2317 if(this.element_legend_childs.title_time)
2318 this.__legendSetTimeString(this.chart.name);
2320 if(this.element_legend_childs.title_units)
2321 this.__legendSetUnitsString(' ')
2323 if(this.data && this.element_legend_childs.series !== null) {
2324 var labels = this.data.dimension_names;
2325 var i = labels.length;
2327 var label = labels[i];
2329 if(typeof label === 'undefined') continue;
2330 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2331 this.legendSetLabelValue(label, null);
2336 this.legendShowLatestValues = function() {
2337 if(this.chart === null) return;
2338 if(this.selected) return;
2340 if(this.data === null || this.element_legend_childs.series === null) {
2341 this.legendShowUndefined();
2345 var show_undefined = true;
2346 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2347 show_undefined = false;
2349 if(show_undefined) {
2350 this.legendShowUndefined();
2354 this.legendSetDate(this.view_before);
2356 var labels = this.data.dimension_names;
2357 var i = labels.length;
2359 var label = labels[i];
2361 if(typeof label === 'undefined') continue;
2362 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2365 this.legendSetLabelValue(label, null);
2367 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2371 this.legendReset = function() {
2372 this.legendShowLatestValues();
2375 // this should be called just ONCE per dimension per chart
2376 this._chartDimensionColor = function(label) {
2377 if(this.colors === null) this.chartColors();
2379 if(typeof this.colors_assigned[label] === 'undefined') {
2380 if(this.colors_available.length === 0) {
2381 var len = NETDATA.themes.current.colors.length;
2383 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2386 this.colors_assigned[label] = this.colors_available.shift();
2388 if(this.debug === true)
2389 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2392 if(this.debug === true)
2393 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2396 this.colors.push(this.colors_assigned[label]);
2397 return this.colors_assigned[label];
2400 this.chartColors = function() {
2401 if(this.colors !== null) return this.colors;
2403 this.colors = new Array();
2404 this.colors_available = new Array();
2406 // add the standard colors
2407 var len = NETDATA.themes.current.colors.length;
2409 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2411 // add the user supplied colors
2412 var c = $(this.element).data('colors');
2413 // this.log('read colors: ' + c);
2414 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2415 if(typeof c !== 'string') {
2416 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2426 this.colors_available.unshift(c[len]);
2427 // this.log('adding color: ' + c[len]);
2436 this.legendUpdateDOM = function() {
2437 var needed = false, dim, keys, len, i;
2439 // check that the legend DOM is up to date for the downloaded dimensions
2440 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2441 // this.log('the legend does not have any series - requesting legend update');
2444 else if(this.data === null) {
2445 // this.log('the chart does not have any data - requesting legend update');
2448 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2452 var labels = this.data.dimension_names.toString();
2453 if(labels !== this.element_legend_childs.series.labels_key) {
2456 if(this.debug === true)
2457 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2461 if(needed === false) {
2462 // make sure colors available
2465 // do we have to update the current values?
2466 // we do this, only when the visible chart is current
2467 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2468 if(this.debug === true)
2469 this.log('chart is in latest position... updating values on legend...');
2471 //var labels = this.data.dimension_names;
2472 //var i = labels.length;
2474 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2478 if(this.colors === null) {
2479 // this is the first time we update the chart
2480 // let's assign colors to all dimensions
2481 if(this.library.track_colors() === true) {
2482 keys = Object.keys(this.chart.dimensions);
2484 for(i = 0; i < len ;i++)
2485 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2488 // we will re-generate the colors for the chart
2489 // based on the selected dimensions
2492 if(this.debug === true)
2493 this.log('updating Legend DOM');
2495 // mark all dimensions as invalid
2496 this.dimensions_visibility.invalidateAll();
2498 var genLabel = function(state, parent, dim, name, count) {
2499 var color = state._chartDimensionColor(name);
2501 var user_element = null;
2502 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2503 if(user_id === null)
2504 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2505 if(user_id !== null) {
2506 user_element = document.getElementById(user_id) || null;
2507 if (user_element === null)
2508 state.log('Cannot find element with id: ' + user_id);
2511 state.element_legend_childs.series[name] = {
2512 name: document.createElement('span'),
2513 value: document.createElement('span'),
2516 last_shown_value: null
2519 var label = state.element_legend_childs.series[name];
2521 // create the dimension visibility tracking for this label
2522 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2524 var rgb = NETDATA.colorHex2Rgb(color);
2525 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2526 + state.chart.chart_type
2527 + '" style="background-color: '
2528 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2529 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2531 var text = document.createTextNode(' ' + name);
2532 label.name.appendChild(text);
2535 parent.appendChild(document.createElement('br'));
2537 parent.appendChild(label.name);
2538 parent.appendChild(label.value);
2541 var content = document.createElement('div');
2543 if(this.hasLegend()) {
2544 this.element_legend_childs = {
2546 resize_handler: document.createElement('div'),
2547 toolbox: document.createElement('div'),
2548 toolbox_left: document.createElement('div'),
2549 toolbox_right: document.createElement('div'),
2550 toolbox_reset: document.createElement('div'),
2551 toolbox_zoomin: document.createElement('div'),
2552 toolbox_zoomout: document.createElement('div'),
2553 toolbox_volume: document.createElement('div'),
2554 title_date: document.createElement('span'),
2555 title_time: document.createElement('span'),
2556 title_units: document.createElement('span'),
2557 perfect_scroller: document.createElement('div'),
2561 this.element_legend.innerHTML = '';
2563 if(this.library.toolboxPanAndZoom !== null) {
2565 var get_pan_and_zoom_step = function(event) {
2567 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2569 else if (event.shiftKey)
2570 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2572 else if (event.altKey)
2573 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2576 return NETDATA.options.current.pan_and_zoom_factor;
2579 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2580 this.element.appendChild(this.element_legend_childs.toolbox);
2582 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2583 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2584 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2585 this.element_legend_childs.toolbox_left.onclick = function(e) {
2588 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2589 var before = that.view_before - step;
2590 var after = that.view_after - step;
2591 if(after >= that.netdata_first)
2592 that.library.toolboxPanAndZoom(that, after, before);
2594 if(NETDATA.options.current.show_help === true)
2595 $(this.element_legend_childs.toolbox_left).popover({
2600 placement: 'bottom',
2601 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2603 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>'
2607 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2608 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2609 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2610 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2612 NETDATA.resetAllCharts(that);
2614 if(NETDATA.options.current.show_help === true)
2615 $(this.element_legend_childs.toolbox_reset).popover({
2620 placement: 'bottom',
2621 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2622 title: 'Chart Reset',
2623 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>'
2626 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2627 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2628 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2629 this.element_legend_childs.toolbox_right.onclick = function(e) {
2631 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2632 var before = that.view_before + step;
2633 var after = that.view_after + step;
2634 if(before <= that.netdata_last)
2635 that.library.toolboxPanAndZoom(that, after, before);
2637 if(NETDATA.options.current.show_help === true)
2638 $(this.element_legend_childs.toolbox_right).popover({
2643 placement: 'bottom',
2644 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2646 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>'
2650 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2651 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2652 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2653 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2655 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2656 var before = that.view_before - dt;
2657 var after = that.view_after + dt;
2658 that.library.toolboxPanAndZoom(that, after, before);
2660 if(NETDATA.options.current.show_help === true)
2661 $(this.element_legend_childs.toolbox_zoomin).popover({
2666 placement: 'bottom',
2667 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2668 title: 'Chart Zoom In',
2669 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>'
2672 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2673 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2674 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2675 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2677 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);
2678 var before = that.view_before + dt;
2679 var after = that.view_after - dt;
2681 that.library.toolboxPanAndZoom(that, after, before);
2683 if(NETDATA.options.current.show_help === true)
2684 $(this.element_legend_childs.toolbox_zoomout).popover({
2689 placement: 'bottom',
2690 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2691 title: 'Chart Zoom Out',
2692 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>'
2695 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2696 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2697 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2698 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2699 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2700 //e.preventDefault();
2701 //alert('clicked toolbox_volume on ' + that.id);
2705 this.element_legend_childs.toolbox = null;
2706 this.element_legend_childs.toolbox_left = null;
2707 this.element_legend_childs.toolbox_reset = null;
2708 this.element_legend_childs.toolbox_right = null;
2709 this.element_legend_childs.toolbox_zoomin = null;
2710 this.element_legend_childs.toolbox_zoomout = null;
2711 this.element_legend_childs.toolbox_volume = null;
2714 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2715 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2716 this.element.appendChild(this.element_legend_childs.resize_handler);
2717 if(NETDATA.options.current.show_help === true)
2718 $(this.element_legend_childs.resize_handler).popover({
2723 placement: 'bottom',
2724 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2725 title: 'Chart Resize',
2726 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>'
2730 this.element_legend_childs.resize_handler.onmousedown =
2732 that.resizeHandler(e);
2736 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2737 that.resizeHandler(e);
2740 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2741 this.element_legend.appendChild(this.element_legend_childs.title_date);
2743 this.element_legend.appendChild(document.createElement('br'));
2745 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2746 this.element_legend.appendChild(this.element_legend_childs.title_time);
2748 this.element_legend.appendChild(document.createElement('br'));
2750 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2751 this.element_legend.appendChild(this.element_legend_childs.title_units);
2753 this.element_legend.appendChild(document.createElement('br'));
2755 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2756 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2758 content.className = 'netdata-legend-series-content';
2759 this.element_legend_childs.perfect_scroller.appendChild(content);
2761 if(NETDATA.options.current.show_help === true)
2762 $(content).popover({
2767 placement: 'bottom',
2768 title: 'Chart Legend',
2769 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2770 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>'
2774 this.element_legend_childs = {
2776 resize_handler: null,
2779 toolbox_right: null,
2780 toolbox_reset: null,
2781 toolbox_zoomin: null,
2782 toolbox_zoomout: null,
2783 toolbox_volume: null,
2787 perfect_scroller: null,
2793 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2794 if(this.debug === true)
2795 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2797 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2798 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2802 var tmp = new Array();
2803 keys = Object.keys(this.chart.dimensions);
2804 for(i = 0, len = keys.length; i < len ;i++) {
2806 tmp.push(this.chart.dimensions[dim].name);
2807 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2809 this.element_legend_childs.series.labels_key = tmp.toString();
2810 if(this.debug === true)
2811 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2814 // create a hidden div to be used for hidding
2815 // the original legend of the chart library
2816 var el = document.createElement('div');
2817 if(this.element_legend !== null)
2818 this.element_legend.appendChild(el);
2819 el.style.display = 'none';
2821 this.element_legend_childs.hidden = document.createElement('div');
2822 el.appendChild(this.element_legend_childs.hidden);
2824 if(this.element_legend_childs.perfect_scroller !== null) {
2825 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2827 wheelPropagation: true,
2828 swipePropagation: true,
2829 minScrollbarLength: null,
2830 maxScrollbarLength: null,
2831 useBothWheelAxes: false,
2832 suppressScrollX: true,
2833 suppressScrollY: false,
2834 scrollXMarginOffset: 0,
2835 scrollYMarginOffset: 0,
2838 Ps.update(this.element_legend_childs.perfect_scroller);
2841 this.legendShowLatestValues();
2844 this.hasLegend = function() {
2845 if(typeof this.___hasLegendCache___ !== 'undefined')
2846 return this.___hasLegendCache___;
2849 if(this.library && this.library.legend(this) === 'right-side') {
2850 var legend = $(this.element).data('legend') || 'yes';
2851 if(legend === 'yes') leg = true;
2854 this.___hasLegendCache___ = leg;
2858 this.legendWidth = function() {
2859 return (this.hasLegend())?140:0;
2862 this.legendHeight = function() {
2863 return $(this.element).height();
2866 this.chartWidth = function() {
2867 return $(this.element).width() - this.legendWidth();
2870 this.chartHeight = function() {
2871 return $(this.element).height();
2874 this.chartPixelsPerPoint = function() {
2875 // force an options provided detail
2876 var px = this.pixels_per_point;
2878 if(this.library && px < this.library.pixels_per_point(this))
2879 px = this.library.pixels_per_point(this);
2881 if(px < NETDATA.options.current.pixels_per_point)
2882 px = NETDATA.options.current.pixels_per_point;
2887 this.needsRecreation = function() {
2889 this.chart_created === true
2891 && this.library.autoresize() === false
2892 && this.tm.last_resized < NETDATA.options.last_resized
2896 this.chartURL = function() {
2897 var after, before, points_multiplier = 1;
2898 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2899 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2901 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2902 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2903 this.view_after = after * 1000;
2904 this.view_before = before * 1000;
2906 this.requested_padding = null;
2907 points_multiplier = 1;
2909 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2910 this.tm.pan_and_zoom_seq = 0;
2912 before = Math.round(this.current.force_before_ms / 1000);
2913 after = Math.round(this.current.force_after_ms / 1000);
2914 this.view_after = after * 1000;
2915 this.view_before = before * 1000;
2917 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2918 this.requested_padding = Math.round((before - after) / 2);
2919 after -= this.requested_padding;
2920 before += this.requested_padding;
2921 this.requested_padding *= 1000;
2922 points_multiplier = 2;
2925 this.current.force_before_ms = null;
2926 this.current.force_after_ms = null;
2929 this.tm.pan_and_zoom_seq = 0;
2931 before = this.before;
2933 this.view_after = after * 1000;
2934 this.view_before = before * 1000;
2936 this.requested_padding = null;
2937 points_multiplier = 1;
2940 this.requested_after = after * 1000;
2941 this.requested_before = before * 1000;
2943 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2945 // build the data URL
2946 this.data_url = this.host + this.chart.data_url;
2947 this.data_url += "&format=" + this.library.format();
2948 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2949 this.data_url += "&group=" + this.method;
2951 if(this.override_options !== null)
2952 this.data_url += "&options=" + this.override_options.toString();
2954 this.data_url += "&options=" + this.library.options(this);
2956 this.data_url += '|jsonwrap';
2958 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2959 this.data_url += '|nonzero';
2961 if(this.append_options !== null)
2962 this.data_url += '|' + this.append_options.toString();
2965 this.data_url += "&after=" + after.toString();
2968 this.data_url += "&before=" + before.toString();
2971 this.data_url += "&dimensions=" + this.dimensions;
2973 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2974 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2977 this.redrawChart = function() {
2978 if(this.data !== null)
2979 this.updateChartWithData(this.data);
2982 this.updateChartWithData = function(data) {
2983 if(this.debug === true)
2984 this.log('updateChartWithData() called.');
2986 // this may force the chart to be re-created
2990 this.updates_counter++;
2991 this.updates_since_last_unhide++;
2992 this.updates_since_last_creation++;
2994 var started = Date.now();
2996 // if the result is JSON, find the latest update-every
2997 this.data_update_every = data.view_update_every * 1000;
2998 this.data_after = data.after * 1000;
2999 this.data_before = data.before * 1000;
3000 this.netdata_first = data.first_entry * 1000;
3001 this.netdata_last = data.last_entry * 1000;
3002 this.data_points = data.points;
3005 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3006 if(this.view_after < this.data_after) {
3007 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3008 this.view_after = this.data_after;
3011 if(this.view_before > this.data_before) {
3012 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3013 this.view_before = this.data_before;
3017 this.view_after = this.data_after;
3018 this.view_before = this.data_before;
3021 if(this.debug === true) {
3022 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3024 if(this.current.force_after_ms)
3025 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3027 this.log('STATUS: forced : unset');
3029 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3030 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3031 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3032 this.log('STATUS: points : ' + (this.data_points).toString());
3035 if(this.data_points === 0) {
3040 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3041 if(this.debug === true)
3042 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3044 this.chart_created = false;
3047 // check and update the legend
3048 this.legendUpdateDOM();
3050 if(this.chart_created === true
3051 && typeof this.library.update === 'function') {
3053 if(this.debug === true)
3054 this.log('updating chart...');
3056 if(callChartLibraryUpdateSafely(data) === false)
3060 if(this.debug === true)
3061 this.log('creating chart...');
3063 if(callChartLibraryCreateSafely(data) === false)
3067 this.legendShowLatestValues();
3068 if(this.selected === true)
3069 NETDATA.globalSelectionSync.stop();
3071 // update the performance counters
3072 var now = Date.now();
3073 this.tm.last_updated = now;
3075 // don't update last_autorefreshed if this chart is
3076 // forced to be updated with global PanAndZoom
3077 if(NETDATA.globalPanAndZoom.isActive())
3078 this.tm.last_autorefreshed = 0;
3080 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3081 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3083 this.tm.last_autorefreshed = now;
3086 this.refresh_dt_ms = now - started;
3087 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3089 if(this.refresh_dt_element !== null)
3090 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3093 this.updateChart = function(callback) {
3094 if(this.debug === true)
3095 this.log('updateChart() called.');
3097 if(this._updating === true) {
3098 if(this.debug === true)
3099 this.log('I am already updating...');
3101 if(typeof callback === 'function')
3107 // due to late initialization of charts and libraries
3108 // we need to check this too
3109 if(this.enabled === false) {
3110 if(this.debug === true)
3111 this.log('I am not enabled');
3113 if(typeof callback === 'function')
3119 if(canBeRendered() === false) {
3120 if(typeof callback === 'function')
3126 if(this.chart === null)
3127 return this.getChart(function() {
3128 return that.updateChart(callback);
3131 if(this.library.initialized === false) {
3132 if(this.library.enabled === true) {
3133 return this.library.initialize(function () {
3134 return that.updateChart(callback);
3138 error('chart library "' + this.library_name + '" is not available.');
3140 if(typeof callback === 'function')
3147 this.clearSelection();
3150 if(this.debug === true)
3151 this.log('updating from ' + this.data_url);
3153 NETDATA.statistics.refreshes_total++;
3154 NETDATA.statistics.refreshes_active++;
3156 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3157 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3159 this._updating = true;
3161 this.xhr = $.ajax( {
3166 'Cache-Control': 'no-cache, no-store',
3167 'Pragma': 'no-cache'
3169 xhrFields: { withCredentials: true } // required for the cookie
3171 .done(function(data) {
3172 that.xhr = undefined;
3173 that.retries_on_data_failures = 0;
3175 if(that.debug === true)
3176 that.log('data received. updating chart.');
3178 that.updateChartWithData(data);
3180 .fail(function(msg) {
3181 that.xhr = undefined;
3183 if(msg.statusText !== 'abort') {
3184 that.retries_on_data_failures++;
3185 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3186 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3187 that.retries_on_data_failures = 0;
3188 error('data download failed for url: ' + that.data_url);
3191 that.tm.last_autorefreshed = Date.now();
3192 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3196 .always(function() {
3197 that.xhr = undefined;
3199 NETDATA.statistics.refreshes_active--;
3200 that._updating = false;
3202 if(typeof callback === 'function')
3207 this.isVisible = function(nocache) {
3208 if(typeof nocache === 'undefined')
3211 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3213 // caching - we do not evaluate the charts visibility
3214 // if the page has not been scrolled since the last check
3215 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3216 return this.___isVisible___;
3218 this.tm.last_visible_check = Date.now();
3220 var wh = window.innerHeight;
3221 var x = this.element.getBoundingClientRect();
3225 if(x.width === 0 || x.height === 0) {
3227 this.___isVisible___ = false;
3228 return this.___isVisible___;
3231 if(x.top < 0 && -x.top > x.height) {
3232 // the chart is entirely above
3233 ret = -x.top - x.height;
3235 else if(x.top > wh) {
3236 // the chart is entirely below
3240 if(ret > tolerance) {
3241 // the chart is too far
3244 this.___isVisible___ = false;
3245 return this.___isVisible___;
3248 // the chart is inside or very close
3251 this.___isVisible___ = true;
3252 return this.___isVisible___;
3256 this.isAutoRefreshable = function() {
3257 return (this.current.autorefresh);
3260 this.canBeAutoRefreshed = function() {
3261 var now = Date.now();
3263 if(this.running === true) {
3264 if(this.debug === true)
3265 this.log('I am already running');
3270 if(this.enabled === false) {
3271 if(this.debug === true)
3272 this.log('I am not enabled');
3277 if(this.library === null || this.library.enabled === false) {
3278 error('charting library "' + this.library_name + '" is not available');
3279 if(this.debug === true)
3280 this.log('My chart library ' + this.library_name + ' is not available');
3285 if(this.isVisible() === false) {
3286 if(NETDATA.options.debug.visibility === true || this.debug === true)
3287 this.log('I am not visible');
3292 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3293 if(this.debug === true)
3294 this.log('timed force update detected - allowing this update');
3296 this.current.force_update_at = 0;
3300 if(this.isAutoRefreshable() === true) {
3301 // allow the first update, even if the page is not visible
3302 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3303 if(NETDATA.options.debug.focus === true || this.debug === true)
3304 this.log('canBeAutoRefreshed(): page does not have focus');
3309 if(this.needsRecreation() === true) {
3310 if(this.debug === true)
3311 this.log('canBeAutoRefreshed(): needs re-creation.');
3316 // options valid only for autoRefresh()
3317 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3318 if(NETDATA.globalPanAndZoom.isActive()) {
3319 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3320 if(this.debug === true)
3321 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3326 if(this.debug === true)
3327 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3333 if(this.selected === true) {
3334 if(this.debug === true)
3335 this.log('canBeAutoRefreshed(): I have a selection in place.');
3340 if(this.paused === true) {
3341 if(this.debug === true)
3342 this.log('canBeAutoRefreshed(): I am paused.');
3347 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3348 if(this.debug === true)
3349 this.log('canBeAutoRefreshed(): It is time to update me.');
3359 this.autoRefresh = function(callback) {
3360 if(this.canBeAutoRefreshed() === true && this.running === false) {
3363 state.running = true;
3364 state.updateChart(function() {
3365 state.running = false;
3367 if(typeof callback !== 'undefined')
3372 if(typeof callback !== 'undefined')
3377 this._defaultsFromDownloadedChart = function(chart) {
3379 this.chart_url = chart.url;
3380 this.data_update_every = chart.update_every * 1000;
3381 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3382 this.tm.last_info_downloaded = Date.now();
3384 if(this.title === null)
3385 this.title = chart.title;
3387 if(this.units === null)
3388 this.units = chart.units;
3391 // fetch the chart description from the netdata server
3392 this.getChart = function(callback) {
3393 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3395 this._defaultsFromDownloadedChart(this.chart);
3397 if(typeof callback === 'function')
3401 this.chart_url = "/api/v1/chart?chart=" + this.id;
3403 if(this.debug === true)
3404 this.log('downloading ' + this.chart_url);
3407 url: this.host + this.chart_url,
3410 xhrFields: { withCredentials: true } // required for the cookie
3412 .done(function(chart) {
3413 chart.url = that.chart_url;
3414 that._defaultsFromDownloadedChart(chart);
3415 NETDATA.chartRegistry.add(that.host, that.id, chart);
3418 NETDATA.error(404, that.chart_url);
3419 error('chart not found on url "' + that.chart_url + '"');
3421 .always(function() {
3422 if(typeof callback === 'function')
3428 // ============================================================================================================
3434 NETDATA.resetAllCharts = function(state) {
3435 // first clear the global selection sync
3436 // to make sure no chart is in selected state
3437 state.globalSelectionSyncStop();
3439 // there are 2 possibilities here
3440 // a. state is the global Pan and Zoom master
3441 // b. state is not the global Pan and Zoom master
3443 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3446 // clear the global Pan and Zoom
3447 // this will also refresh the master
3448 // and unblock any charts currently mirroring the master
3449 NETDATA.globalPanAndZoom.clearMaster();
3451 // if we were not the master, reset our status too
3452 // this is required because most probably the mouse
3453 // is over this chart, blocking it from auto-refreshing
3454 if(master === false && (state.paused === true || state.selected === true))
3458 // get or create a chart state, given a DOM element
3459 NETDATA.chartState = function(element) {
3460 var state = $(element).data('netdata-state-object') || null;
3461 if(state === null) {
3462 state = new chartState(element);
3463 $(element).data('netdata-state-object', state);
3468 // ----------------------------------------------------------------------------------------------------------------
3469 // Library functions
3471 // Load a script without jquery
3472 // This is used to load jquery - after it is loaded, we use jquery
3473 NETDATA._loadjQuery = function(callback) {
3474 if(typeof jQuery === 'undefined') {
3475 if(NETDATA.options.debug.main_loop === true)
3476 console.log('loading ' + NETDATA.jQuery);
3478 var script = document.createElement('script');
3479 script.type = 'text/javascript';
3480 script.async = true;
3481 script.src = NETDATA.jQuery;
3483 // script.onabort = onError;
3484 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3485 if(typeof callback === "function")
3486 script.onload = callback;
3488 var s = document.getElementsByTagName('script')[0];
3489 s.parentNode.insertBefore(script, s);
3491 else if(typeof callback === "function")
3495 NETDATA._loadCSS = function(filename) {
3496 // don't use jQuery here
3497 // styles are loaded before jQuery
3498 // to eliminate showing an unstyled page to the user
3500 var fileref = document.createElement("link");
3501 fileref.setAttribute("rel", "stylesheet");
3502 fileref.setAttribute("type", "text/css");
3503 fileref.setAttribute("href", filename);
3505 if (typeof fileref !== 'undefined')
3506 document.getElementsByTagName("head")[0].appendChild(fileref);
3509 NETDATA.colorHex2Rgb = function(hex) {
3510 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3511 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3512 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3513 return r + r + g + g + b + b;
3516 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3518 r: parseInt(result[1], 16),
3519 g: parseInt(result[2], 16),
3520 b: parseInt(result[3], 16)
3524 NETDATA.colorLuminance = function(hex, lum) {
3525 // validate hex string
3526 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3528 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3532 // convert to decimal and change luminosity
3533 var rgb = "#", c, i;
3534 for (i = 0; i < 3; i++) {
3535 c = parseInt(hex.substr(i*2,2), 16);
3536 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3537 rgb += ("00"+c).substr(c.length);
3543 NETDATA.guid = function() {
3545 return Math.floor((1 + Math.random()) * 0x10000)
3550 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3553 NETDATA.zeropad = function(x) {
3554 if(x > -10 && x < 10) return '0' + x.toString();
3555 else return x.toString();
3558 // user function to signal us the DOM has been
3560 NETDATA.updatedDom = function() {
3561 NETDATA.options.updated_dom = true;
3564 NETDATA.ready = function(callback) {
3565 NETDATA.options.pauseCallback = callback;
3568 NETDATA.pause = function(callback) {
3569 if(typeof callback === 'function') {
3570 if (NETDATA.options.pause === true)
3573 NETDATA.options.pauseCallback = callback;
3577 NETDATA.unpause = function() {
3578 NETDATA.options.pauseCallback = null;
3579 NETDATA.options.updated_dom = true;
3580 NETDATA.options.pause = false;
3583 // ----------------------------------------------------------------------------------------------------------------
3585 // this is purely sequencial charts refresher
3586 // it is meant to be autonomous
3587 NETDATA.chartRefresherNoParallel = function(index) {
3588 if(NETDATA.options.debug.mail_loop === true)
3589 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3591 if(NETDATA.options.updated_dom === true) {
3592 // the dom has been updated
3593 // get the dom parts again
3594 NETDATA.parseDom(NETDATA.chartRefresher);
3597 if(index >= NETDATA.options.targets.length) {
3598 if(NETDATA.options.debug.main_loop === true)
3599 console.log('waiting to restart main loop...');
3601 NETDATA.options.auto_refresher_fast_weight = 0;
3603 setTimeout(function() {
3604 NETDATA.chartRefresher();
3605 }, NETDATA.options.current.idle_between_loops);
3608 var state = NETDATA.options.targets[index];
3610 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3611 if(NETDATA.options.debug.main_loop === true)
3612 console.log('fast rendering...');
3614 state.autoRefresh(function() {
3615 NETDATA.chartRefresherNoParallel(++index);
3619 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3620 NETDATA.options.auto_refresher_fast_weight = 0;
3622 setTimeout(function() {
3623 state.autoRefresh(function() {
3624 NETDATA.chartRefresherNoParallel(++index);
3626 }, NETDATA.options.current.idle_between_charts);
3631 // this is part of the parallel refresher
3632 // its cause is to refresh sequencially all the charts
3633 // that depend on chart library initialization
3634 // it will call the parallel refresher back
3635 // as soon as it sees a chart that its chart library
3637 NETDATA.chartRefresher_uninitialized = function() {
3638 if(NETDATA.options.updated_dom === true) {
3639 // the dom has been updated
3640 // get the dom parts again
3641 NETDATA.parseDom(NETDATA.chartRefresher);
3645 if(NETDATA.options.sequencial.length === 0)
3646 NETDATA.chartRefresher();
3648 var state = NETDATA.options.sequencial.pop();
3649 if(state.library.initialized === true)
3650 NETDATA.chartRefresher();
3652 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3656 NETDATA.chartRefresherWaitTime = function() {
3657 return NETDATA.options.current.idle_parallel_loops;
3660 // the default refresher
3661 // it will create 2 sets of charts:
3662 // - the ones that can be refreshed in parallel
3663 // - the ones that depend on something else
3664 // the first set will be executed in parallel
3665 // the second will be given to NETDATA.chartRefresher_uninitialized()
3666 NETDATA.chartRefresher = function() {
3667 // console.log('auto-refresher...');
3669 if(NETDATA.options.pause === true) {
3670 // console.log('auto-refresher is paused');
3671 setTimeout(NETDATA.chartRefresher,
3672 NETDATA.chartRefresherWaitTime());
3676 if(typeof NETDATA.options.pauseCallback === 'function') {
3677 // console.log('auto-refresher is calling pauseCallback');
3678 NETDATA.options.pause = true;
3679 NETDATA.options.pauseCallback();
3680 NETDATA.chartRefresher();
3684 if(NETDATA.options.current.parallel_refresher === false) {
3685 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3686 NETDATA.chartRefresherNoParallel(0);
3690 if(NETDATA.options.updated_dom === true) {
3691 // the dom has been updated
3692 // get the dom parts again
3693 // console.log('auto-refresher is calling parseDom()');
3694 NETDATA.parseDom(NETDATA.chartRefresher);
3698 var parallel = new Array();
3699 var targets = NETDATA.options.targets;
3700 var len = targets.length;
3703 state = targets[len];
3704 if(state.isVisible() === false || state.running === true)
3707 if(state.library.initialized === false) {
3708 if(state.library.enabled === true) {
3709 state.library.initialize(NETDATA.chartRefresher);
3713 state.error('chart library "' + state.library_name + '" is not enabled.');
3717 parallel.unshift(state);
3720 if(parallel.length > 0) {
3721 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3722 // this will execute the jobs in parallel
3723 $(parallel).each(function() {
3728 // console.log('auto-refresher nothing to do');
3731 // run the next refresh iteration
3732 setTimeout(NETDATA.chartRefresher,
3733 NETDATA.chartRefresherWaitTime());
3736 NETDATA.parseDom = function(callback) {
3737 NETDATA.options.last_page_scroll = Date.now();
3738 NETDATA.options.updated_dom = false;
3740 var targets = $('div[data-netdata]'); //.filter(':visible');
3742 if(NETDATA.options.debug.main_loop === true)
3743 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3745 NETDATA.options.targets = new Array();
3746 var len = targets.length;
3748 // the initialization will take care of sizing
3749 // and the "loading..." message
3750 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3753 if(typeof callback === 'function')
3757 // this is the main function - where everything starts
3758 NETDATA.start = function() {
3759 // this should be called only once
3761 NETDATA.options.page_is_visible = true;
3763 $(window).blur(function() {
3764 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3765 NETDATA.options.page_is_visible = false;
3766 if(NETDATA.options.debug.focus === true)
3767 console.log('Lost Focus!');
3771 $(window).focus(function() {
3772 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3773 NETDATA.options.page_is_visible = true;
3774 if(NETDATA.options.debug.focus === true)
3775 console.log('Focus restored!');
3779 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3780 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3781 NETDATA.options.page_is_visible = false;
3782 if(NETDATA.options.debug.focus === true)
3783 console.log('Document has no focus!');
3787 // bootstrap tab switching
3788 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3790 // bootstrap modal switching
3791 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3792 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3794 // bootstrap collapse switching
3795 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3796 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3798 NETDATA.parseDom(NETDATA.chartRefresher);
3800 // Alarms initialization
3801 setTimeout(NETDATA.alarms.init, 1000);
3803 // Registry initialization
3804 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3806 if(typeof netdataCallback === 'function')
3810 // ----------------------------------------------------------------------------------------------------------------
3813 NETDATA.peityInitialize = function(callback) {
3814 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3816 url: NETDATA.peity_js,
3819 xhrFields: { withCredentials: true } // required for the cookie
3822 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3825 NETDATA.chartLibraries.peity.enabled = false;
3826 NETDATA.error(100, NETDATA.peity_js);
3828 .always(function() {
3829 if(typeof callback === "function")
3834 NETDATA.chartLibraries.peity.enabled = false;
3835 if(typeof callback === "function")
3840 NETDATA.peityChartUpdate = function(state, data) {
3841 state.peity_instance.innerHTML = data.result;
3843 if(state.peity_options.stroke !== state.chartColors()[0]) {
3844 state.peity_options.stroke = state.chartColors()[0];
3845 if(state.chart.chart_type === 'line')
3846 state.peity_options.fill = NETDATA.themes.current.background;
3848 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3851 $(state.peity_instance).peity('line', state.peity_options);
3855 NETDATA.peityChartCreate = function(state, data) {
3856 state.peity_instance = document.createElement('div');
3857 state.element_chart.appendChild(state.peity_instance);
3859 var self = $(state.element);
3860 state.peity_options = {
3861 stroke: NETDATA.themes.current.foreground,
3862 strokeWidth: self.data('peity-strokewidth') || 1,
3863 width: state.chartWidth(),
3864 height: state.chartHeight(),
3865 fill: NETDATA.themes.current.foreground
3868 NETDATA.peityChartUpdate(state, data);
3872 // ----------------------------------------------------------------------------------------------------------------
3875 NETDATA.sparklineInitialize = function(callback) {
3876 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3878 url: NETDATA.sparkline_js,
3881 xhrFields: { withCredentials: true } // required for the cookie
3884 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3887 NETDATA.chartLibraries.sparkline.enabled = false;
3888 NETDATA.error(100, NETDATA.sparkline_js);
3890 .always(function() {
3891 if(typeof callback === "function")
3896 NETDATA.chartLibraries.sparkline.enabled = false;
3897 if(typeof callback === "function")
3902 NETDATA.sparklineChartUpdate = function(state, data) {
3903 state.sparkline_options.width = state.chartWidth();
3904 state.sparkline_options.height = state.chartHeight();
3906 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3910 NETDATA.sparklineChartCreate = function(state, data) {
3911 var self = $(state.element);
3912 var type = self.data('sparkline-type') || 'line';
3913 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3914 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3915 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3916 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3917 var composite = self.data('sparkline-composite') || undefined;
3918 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3919 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3920 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3921 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3922 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3923 var spotColor = self.data('sparkline-spotcolor') || undefined;
3924 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3925 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3926 var spotRadius = self.data('sparkline-spotradius') || undefined;
3927 var valueSpots = self.data('sparkline-valuespots') || undefined;
3928 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3929 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3930 var lineWidth = self.data('sparkline-linewidth') || undefined;
3931 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3932 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3933 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3934 var xvalues = self.data('sparkline-xvalues') || undefined;
3935 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3936 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3937 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3938 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3939 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3940 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3941 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3942 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3943 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3944 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3945 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3946 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3947 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3948 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3949 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3950 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3951 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3952 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3953 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3954 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3955 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3956 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3958 if(spotColor === 'disable') spotColor='';
3959 if(minSpotColor === 'disable') minSpotColor='';
3960 if(maxSpotColor === 'disable') maxSpotColor='';
3962 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3964 state.sparkline_options = {
3966 lineColor: lineColor,
3967 fillColor: fillColor,
3968 chartRangeMin: chartRangeMin,
3969 chartRangeMax: chartRangeMax,
3970 composite: composite,
3971 enableTagOptions: enableTagOptions,
3972 tagOptionPrefix: tagOptionPrefix,
3973 tagValuesAttribute: tagValuesAttribute,
3974 disableHiddenCheck: disableHiddenCheck,
3975 defaultPixelsPerValue: defaultPixelsPerValue,
3976 spotColor: spotColor,
3977 minSpotColor: minSpotColor,
3978 maxSpotColor: maxSpotColor,
3979 spotRadius: spotRadius,
3980 valueSpots: valueSpots,
3981 highlightSpotColor: highlightSpotColor,
3982 highlightLineColor: highlightLineColor,
3983 lineWidth: lineWidth,
3984 normalRangeMin: normalRangeMin,
3985 normalRangeMax: normalRangeMax,
3986 drawNormalOnTop: drawNormalOnTop,
3988 chartRangeClip: chartRangeClip,
3989 chartRangeMinX: chartRangeMinX,
3990 chartRangeMaxX: chartRangeMaxX,
3991 disableInteraction: disableInteraction,
3992 disableTooltips: disableTooltips,
3993 disableHighlight: disableHighlight,
3994 highlightLighten: highlightLighten,
3995 highlightColor: highlightColor,
3996 tooltipContainer: tooltipContainer,
3997 tooltipClassname: tooltipClassname,
3998 tooltipChartTitle: state.title,
3999 tooltipFormat: tooltipFormat,
4000 tooltipPrefix: tooltipPrefix,
4001 tooltipSuffix: tooltipSuffix,
4002 tooltipSkipNull: tooltipSkipNull,
4003 tooltipValueLookups: tooltipValueLookups,
4004 tooltipFormatFieldlist: tooltipFormatFieldlist,
4005 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4006 numberFormatter: numberFormatter,
4007 numberDigitGroupSep: numberDigitGroupSep,
4008 numberDecimalMark: numberDecimalMark,
4009 numberDigitGroupCount: numberDigitGroupCount,
4010 animatedZooms: animatedZooms,
4011 width: state.chartWidth(),
4012 height: state.chartHeight()
4015 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4019 // ----------------------------------------------------------------------------------------------------------------
4026 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4027 if(after < state.netdata_first)
4028 after = state.netdata_first;
4030 if(before > state.netdata_last)
4031 before = state.netdata_last;
4033 state.setMode('zoom');
4034 state.globalSelectionSyncStop();
4035 state.globalSelectionSyncDelay();
4036 state.dygraph_user_action = true;
4037 state.dygraph_force_zoom = true;
4038 state.updateChartPanOrZoom(after, before);
4039 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4042 NETDATA.dygraphSetSelection = function(state, t) {
4043 if(typeof state.dygraph_instance !== 'undefined') {
4044 var r = state.calculateRowForTime(t);
4046 state.dygraph_instance.setSelection(r);
4048 state.dygraph_instance.clearSelection();
4049 state.legendShowUndefined();
4056 NETDATA.dygraphClearSelection = function(state, t) {
4057 if(typeof state.dygraph_instance !== 'undefined') {
4058 state.dygraph_instance.clearSelection();
4063 NETDATA.dygraphSmoothInitialize = function(callback) {
4065 url: NETDATA.dygraph_smooth_js,
4068 xhrFields: { withCredentials: true } // required for the cookie
4071 NETDATA.dygraph.smooth = true;
4072 smoothPlotter.smoothing = 0.3;
4075 NETDATA.dygraph.smooth = false;
4077 .always(function() {
4078 if(typeof callback === "function")
4083 NETDATA.dygraphInitialize = function(callback) {
4084 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4086 url: NETDATA.dygraph_js,
4089 xhrFields: { withCredentials: true } // required for the cookie
4092 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4095 NETDATA.chartLibraries.dygraph.enabled = false;
4096 NETDATA.error(100, NETDATA.dygraph_js);
4098 .always(function() {
4099 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4100 NETDATA.dygraphSmoothInitialize(callback);
4101 else if(typeof callback === "function")
4106 NETDATA.chartLibraries.dygraph.enabled = false;
4107 if(typeof callback === "function")
4112 NETDATA.dygraphChartUpdate = function(state, data) {
4113 var dygraph = state.dygraph_instance;
4115 if(typeof dygraph === 'undefined')
4116 return NETDATA.dygraphChartCreate(state, data);
4118 // when the chart is not visible, and hidden
4119 // if there is a window resize, dygraph detects
4120 // its element size as 0x0.
4121 // this will make it re-appear properly
4123 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4127 file: data.result.data,
4128 colors: state.chartColors(),
4129 labels: data.result.labels,
4130 labelsDivWidth: state.chartWidth() - 70,
4131 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4134 if(state.dygraph_force_zoom === true) {
4135 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4136 state.log('dygraphChartUpdate() forced zoom update');
4138 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4139 options.isZoomedIgnoreProgrammaticZoom = true;
4140 state.dygraph_force_zoom = false;
4142 else if(state.current.name !== 'auto') {
4143 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4144 state.log('dygraphChartUpdate() loose update');
4147 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4148 state.log('dygraphChartUpdate() strict update');
4150 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4151 options.isZoomedIgnoreProgrammaticZoom = true;
4154 options.valueRange = state.dygraph_options.valueRange;
4156 var oldMax = null, oldMin = null;
4157 if(state.__commonMin !== null) {
4158 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4159 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4161 if(state.__commonMax !== null) {
4162 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4163 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4166 if(state.dygraph_smooth_eligible === true) {
4167 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4168 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4169 NETDATA.dygraphChartCreate(state, data);
4174 dygraph.updateOptions(options);
4177 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4178 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4179 options.valueRange[0] = NETDATA.commonMin.get(state);
4182 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4183 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4184 options.valueRange[1] = NETDATA.commonMax.get(state);
4188 if(redraw === true) {
4189 // state.log('forcing redraw to adapt to common- min/max');
4190 dygraph.updateOptions(options);
4193 state.dygraph_last_rendered = Date.now();
4197 NETDATA.dygraphChartCreate = function(state, data) {
4198 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4199 state.log('dygraphChartCreate()');
4201 var self = $(state.element);
4203 var chart_type = state.chart.chart_type;
4204 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4205 chart_type = self.data('dygraph-type') || chart_type;
4207 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4208 smooth = self.data('dygraph-smooth') || smooth;
4210 if(NETDATA.dygraph.smooth === false)
4213 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4214 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4216 state.dygraph_options = {
4217 colors: self.data('dygraph-colors') || state.chartColors(),
4219 // leave a few pixels empty on the right of the chart
4220 rightGap: self.data('dygraph-rightgap') || 5,
4221 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4222 showRoller: self.data('dygraph-showroller') || false,
4224 title: self.data('dygraph-title') || state.title,
4225 titleHeight: self.data('dygraph-titleheight') || 19,
4227 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4228 labels: data.result.labels,
4229 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4230 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4231 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4232 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4233 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4236 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4237 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4239 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4240 xRangePad: self.data('dygraph-xrangepad') || 0,
4241 yRangePad: self.data('dygraph-yrangepad') || 1,
4243 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4245 ylabel: state.units,
4246 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4248 // the function to plot the chart
4251 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4252 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4253 strokePattern: self.data('dygraph-strokepattern') || undefined,
4255 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4256 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4257 drawPoints: self.data('dygraph-drawpoints') || false,
4259 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4260 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4262 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4263 pointSize: self.data('dygraph-pointsize') || 1,
4265 // enabling this makes the chart with little square lines
4266 stepPlot: self.data('dygraph-stepplot') || false,
4268 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4269 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4270 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4272 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4273 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4274 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4275 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4277 drawAxis: self.data('dygraph-drawaxis') || true,
4278 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4279 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4280 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4282 drawGrid: self.data('dygraph-drawgrid') || true,
4283 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4284 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4285 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4287 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4288 sigFigs: self.data('dygraph-sigfigs') || null,
4289 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4290 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4292 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4293 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4294 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4296 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4297 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4301 ticker: Dygraph.dateTicker,
4302 axisLabelFormatter: function (d, gran) {
4303 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4305 valueFormatter: function (ms) {
4306 //var d = new Date(ms);
4307 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4309 // no need to return anything here
4316 valueFormatter: function (x) {
4317 // we format legends with the state object
4318 // no need to do anything here
4319 // return (Math.round(x*100) / 100).toLocaleString();
4320 // return state.legendFormatValue(x);
4325 legendFormatter: function(data) {
4326 var elements = state.element_legend_childs;
4328 // if the hidden div is not there
4329 // we are not managing the legend
4330 if(elements.hidden === null) return;
4332 if (typeof data.x !== 'undefined') {
4333 state.legendSetDate(data.x);
4334 var i = data.series.length;
4336 var series = data.series[i];
4337 if(series.isVisible === true)
4338 state.legendSetLabelValue(series.label, series.y);
4340 state.legendSetLabelValue(series.label, null);
4346 drawCallback: function(dygraph, is_initial) {
4347 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4348 state.dygraph_user_action = false;
4350 var x_range = dygraph.xAxisRange();
4351 var after = Math.round(x_range[0]);
4352 var before = Math.round(x_range[1]);
4354 if(NETDATA.options.debug.dygraph === true)
4355 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4357 if(before <= state.netdata_last && after >= state.netdata_first)
4358 state.updateChartPanOrZoom(after, before);
4361 zoomCallback: function(minDate, maxDate, yRanges) {
4362 if(NETDATA.options.debug.dygraph === true)
4363 state.log('dygraphZoomCallback()');
4365 state.globalSelectionSyncStop();
4366 state.globalSelectionSyncDelay();
4367 state.setMode('zoom');
4369 // refresh it to the greatest possible zoom level
4370 state.dygraph_user_action = true;
4371 state.dygraph_force_zoom = true;
4372 state.updateChartPanOrZoom(minDate, maxDate);
4374 highlightCallback: function(event, x, points, row, seriesName) {
4375 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4376 state.log('dygraphHighlightCallback()');
4380 // there is a bug in dygraph when the chart is zoomed enough
4381 // the time it thinks is selected is wrong
4382 // here we calculate the time t based on the row number selected
4384 var t = state.data_after + row * state.data_update_every;
4385 // 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);
4387 state.globalSelectionSync(x);
4389 // fix legend zIndex using the internal structures of dygraph legend module
4390 // this works, but it is a hack!
4391 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4393 unhighlightCallback: function(event) {
4394 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4395 state.log('dygraphUnhighlightCallback()');
4397 state.unpauseChart();
4398 state.globalSelectionSyncStop();
4400 interactionModel : {
4401 mousedown: function(event, dygraph, context) {
4402 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4403 state.log('interactionModel.mousedown()');
4405 state.dygraph_user_action = true;
4406 state.globalSelectionSyncStop();
4408 if(NETDATA.options.debug.dygraph === true)
4409 state.log('dygraphMouseDown()');
4411 // Right-click should not initiate a zoom.
4412 if(event.button && event.button === 2) return;
4414 context.initializeMouseDown(event, dygraph, context);
4416 if(event.button && event.button === 1) {
4417 if (event.altKey || event.shiftKey) {
4418 state.setMode('pan');
4419 state.globalSelectionSyncDelay();
4420 Dygraph.startPan(event, dygraph, context);
4423 state.setMode('zoom');
4424 state.globalSelectionSyncDelay();
4425 Dygraph.startZoom(event, dygraph, context);
4429 if (event.altKey || event.shiftKey) {
4430 state.setMode('zoom');
4431 state.globalSelectionSyncDelay();
4432 Dygraph.startZoom(event, dygraph, context);
4435 state.setMode('pan');
4436 state.globalSelectionSyncDelay();
4437 Dygraph.startPan(event, dygraph, context);
4441 mousemove: function(event, dygraph, context) {
4442 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4443 state.log('interactionModel.mousemove()');
4445 if(context.isPanning) {
4446 state.dygraph_user_action = true;
4447 state.globalSelectionSyncStop();
4448 state.globalSelectionSyncDelay();
4449 state.setMode('pan');
4450 context.is2DPan = false;
4451 Dygraph.movePan(event, dygraph, context);
4453 else if(context.isZooming) {
4454 state.dygraph_user_action = true;
4455 state.globalSelectionSyncStop();
4456 state.globalSelectionSyncDelay();
4457 state.setMode('zoom');
4458 Dygraph.moveZoom(event, dygraph, context);
4461 mouseup: function(event, dygraph, context) {
4462 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4463 state.log('interactionModel.mouseup()');
4465 if (context.isPanning) {
4466 state.dygraph_user_action = true;
4467 state.globalSelectionSyncDelay();
4468 Dygraph.endPan(event, dygraph, context);
4470 else if (context.isZooming) {
4471 state.dygraph_user_action = true;
4472 state.globalSelectionSyncDelay();
4473 Dygraph.endZoom(event, dygraph, context);
4476 click: function(event, dygraph, context) {
4477 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4478 state.log('interactionModel.click()');
4480 event.preventDefault();
4482 dblclick: function(event, dygraph, context) {
4483 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4484 state.log('interactionModel.dblclick()');
4485 NETDATA.resetAllCharts(state);
4487 wheel: function(event, dygraph, context) {
4488 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4489 state.log('interactionModel.wheel()');
4491 // Take the offset of a mouse event on the dygraph canvas and
4492 // convert it to a pair of percentages from the bottom left.
4493 // (Not top left, bottom is where the lower value is.)
4494 function offsetToPercentage(g, offsetX, offsetY) {
4495 // This is calculating the pixel offset of the leftmost date.
4496 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4497 var yar0 = g.yAxisRange(0);
4499 // This is calculating the pixel of the higest value. (Top pixel)
4500 var yOffset = g.toDomCoords(null, yar0[1])[1];
4502 // x y w and h are relative to the corner of the drawing area,
4503 // so that the upper corner of the drawing area is (0, 0).
4504 var x = offsetX - xOffset;
4505 var y = offsetY - yOffset;
4507 // This is computing the rightmost pixel, effectively defining the
4509 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4511 // This is computing the lowest pixel, effectively defining the height.
4512 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4514 // Percentage from the left.
4515 var xPct = w === 0 ? 0 : (x / w);
4516 // Percentage from the top.
4517 var yPct = h === 0 ? 0 : (y / h);
4519 // The (1-) part below changes it from "% distance down from the top"
4520 // to "% distance up from the bottom".
4521 return [xPct, (1-yPct)];
4524 // Adjusts [x, y] toward each other by zoomInPercentage%
4525 // Split it so the left/bottom axis gets xBias/yBias of that change and
4526 // tight/top gets (1-xBias)/(1-yBias) of that change.
4528 // If a bias is missing it splits it down the middle.
4529 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4530 xBias = xBias || 0.5;
4531 yBias = yBias || 0.5;
4533 function adjustAxis(axis, zoomInPercentage, bias) {
4534 var delta = axis[1] - axis[0];
4535 var increment = delta * zoomInPercentage;
4536 var foo = [increment * bias, increment * (1-bias)];
4538 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4541 var yAxes = g.yAxisRanges();
4543 for (var i = 0; i < yAxes.length; i++) {
4544 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4547 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4550 if(event.altKey || event.shiftKey) {
4551 state.dygraph_user_action = true;
4553 state.globalSelectionSyncStop();
4554 state.globalSelectionSyncDelay();
4556 // http://dygraphs.com/gallery/interaction-api.js
4558 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4560 normal_def = event.wheelDelta / 40;
4563 normal_def = event.deltaY * -1.2;
4565 var normal = (event.detail) ? event.detail * -1 : normal_def;
4566 var percentage = normal / 50;
4568 if (!(event.offsetX && event.offsetY)){
4569 event.offsetX = event.layerX - event.target.offsetLeft;
4570 event.offsetY = event.layerY - event.target.offsetTop;
4573 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4574 var xPct = percentages[0];
4575 var yPct = percentages[1];
4577 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4578 var after = new_x_range[0];
4579 var before = new_x_range[1];
4581 var first = state.netdata_first + state.data_update_every;
4582 var last = state.netdata_last + state.data_update_every;
4585 after -= (before - last);
4592 state.setMode('zoom');
4593 if(state.updateChartPanOrZoom(after, before) === true)
4594 dygraph.updateOptions({ dateWindow: [ after, before ] });
4596 event.preventDefault();
4599 touchstart: function(event, dygraph, context) {
4600 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4601 state.log('interactionModel.touchstart()');
4603 state.dygraph_user_action = true;
4604 state.setMode('zoom');
4607 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4609 // we overwrite the touch directions at the end, to overwrite
4610 // the internal default of dygraphs
4611 context.touchDirections = { x: true, y: false };
4613 state.dygraph_last_touch_start = Date.now();
4614 state.dygraph_last_touch_move = 0;
4616 if(typeof event.touches[0].pageX === 'number')
4617 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4619 state.dygraph_last_touch_page_x = 0;
4621 touchmove: function(event, dygraph, context) {
4622 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4623 state.log('interactionModel.touchmove()');
4625 state.dygraph_user_action = true;
4626 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4628 state.dygraph_last_touch_move = Date.now();
4630 touchend: function(event, dygraph, context) {
4631 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4632 state.log('interactionModel.touchend()');
4634 state.dygraph_user_action = true;
4635 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4637 // if it didn't move, it is a selection
4638 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4639 // internal api of dygraphs
4640 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4641 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4642 if(NETDATA.dygraphSetSelection(state, t) === true)
4643 state.globalSelectionSync(t);
4646 // if it was double tap within double click time, reset the charts
4647 var now = Date.now();
4648 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4649 if(state.dygraph_last_touch_move === 0) {
4650 var dt = now - state.dygraph_last_touch_end;
4651 if(dt <= NETDATA.options.current.double_click_speed)
4652 NETDATA.resetAllCharts(state);
4656 // remember the timestamp of the last touch end
4657 state.dygraph_last_touch_end = now;
4662 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4663 state.dygraph_options.drawGrid = false;
4664 state.dygraph_options.drawAxis = false;
4665 state.dygraph_options.title = undefined;
4666 state.dygraph_options.ylabel = undefined;
4667 state.dygraph_options.yLabelWidth = 0;
4668 state.dygraph_options.labelsDivWidth = 120;
4669 state.dygraph_options.labelsDivStyles.width = '120px';
4670 state.dygraph_options.labelsSeparateLines = true;
4671 state.dygraph_options.rightGap = 0;
4672 state.dygraph_options.yRangePad = 1;
4675 if(smooth === true) {
4676 state.dygraph_smooth_eligible = true;
4678 if(NETDATA.options.current.smooth_plot === true)
4679 state.dygraph_options.plotter = smoothPlotter;
4681 else state.dygraph_smooth_eligible = false;
4683 state.dygraph_instance = new Dygraph(state.element_chart,
4684 data.result.data, state.dygraph_options);
4686 state.dygraph_force_zoom = false;
4687 state.dygraph_user_action = false;
4688 state.dygraph_last_rendered = Date.now();
4690 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4691 state.__commonMin = self.data('common-min') || null;
4692 state.__commonMax = self.data('common-max') || null;
4695 state.log('incompatible version of dygraphs detected');
4696 state.__commonMin = null;
4697 state.__commonMax = null;
4703 // ----------------------------------------------------------------------------------------------------------------
4706 NETDATA.morrisInitialize = function(callback) {
4707 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4709 // morris requires raphael
4710 if(!NETDATA.chartLibraries.raphael.initialized) {
4711 if(NETDATA.chartLibraries.raphael.enabled) {
4712 NETDATA.raphaelInitialize(function() {
4713 NETDATA.morrisInitialize(callback);
4717 NETDATA.chartLibraries.morris.enabled = false;
4718 if(typeof callback === "function")
4723 NETDATA._loadCSS(NETDATA.morris_css);
4726 url: NETDATA.morris_js,
4729 xhrFields: { withCredentials: true } // required for the cookie
4732 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4735 NETDATA.chartLibraries.morris.enabled = false;
4736 NETDATA.error(100, NETDATA.morris_js);
4738 .always(function() {
4739 if(typeof callback === "function")
4745 NETDATA.chartLibraries.morris.enabled = false;
4746 if(typeof callback === "function")
4751 NETDATA.morrisChartUpdate = function(state, data) {
4752 state.morris_instance.setData(data.result.data);
4756 NETDATA.morrisChartCreate = function(state, data) {
4758 state.morris_options = {
4759 element: state.element_chart.id,
4760 data: data.result.data,
4762 ykeys: data.dimension_names,
4763 labels: data.dimension_names,
4769 continuousLine: false,
4770 behaveLikeLine: false
4773 if(state.chart.chart_type === 'line')
4774 state.morris_instance = new Morris.Line(state.morris_options);
4776 else if(state.chart.chart_type === 'area') {
4777 state.morris_options.behaveLikeLine = true;
4778 state.morris_instance = new Morris.Area(state.morris_options);
4781 state.morris_instance = new Morris.Area(state.morris_options);
4786 // ----------------------------------------------------------------------------------------------------------------
4789 NETDATA.raphaelInitialize = function(callback) {
4790 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4792 url: NETDATA.raphael_js,
4795 xhrFields: { withCredentials: true } // required for the cookie
4798 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4801 NETDATA.chartLibraries.raphael.enabled = false;
4802 NETDATA.error(100, NETDATA.raphael_js);
4804 .always(function() {
4805 if(typeof callback === "function")
4810 NETDATA.chartLibraries.raphael.enabled = false;
4811 if(typeof callback === "function")
4816 NETDATA.raphaelChartUpdate = function(state, data) {
4817 $(state.element_chart).raphael(data.result, {
4818 width: state.chartWidth(),
4819 height: state.chartHeight()
4825 NETDATA.raphaelChartCreate = function(state, data) {
4826 $(state.element_chart).raphael(data.result, {
4827 width: state.chartWidth(),
4828 height: state.chartHeight()
4834 // ----------------------------------------------------------------------------------------------------------------
4837 NETDATA.c3Initialize = function(callback) {
4838 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4841 if(!NETDATA.chartLibraries.d3.initialized) {
4842 if(NETDATA.chartLibraries.d3.enabled) {
4843 NETDATA.d3Initialize(function() {
4844 NETDATA.c3Initialize(callback);
4848 NETDATA.chartLibraries.c3.enabled = false;
4849 if(typeof callback === "function")
4854 NETDATA._loadCSS(NETDATA.c3_css);
4860 xhrFields: { withCredentials: true } // required for the cookie
4863 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4866 NETDATA.chartLibraries.c3.enabled = false;
4867 NETDATA.error(100, NETDATA.c3_js);
4869 .always(function() {
4870 if(typeof callback === "function")
4876 NETDATA.chartLibraries.c3.enabled = false;
4877 if(typeof callback === "function")
4882 NETDATA.c3ChartUpdate = function(state, data) {
4883 state.c3_instance.destroy();
4884 return NETDATA.c3ChartCreate(state, data);
4886 //state.c3_instance.load({
4887 // rows: data.result,
4894 NETDATA.c3ChartCreate = function(state, data) {
4896 state.element_chart.id = 'c3-' + state.uuid;
4897 // console.log('id = ' + state.element_chart.id);
4899 state.c3_instance = c3.generate({
4900 bindto: '#' + state.element_chart.id,
4902 width: state.chartWidth(),
4903 height: state.chartHeight()
4906 pattern: state.chartColors()
4911 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4917 format: function(x) {
4918 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4945 // console.log(state.c3_instance);
4950 // ----------------------------------------------------------------------------------------------------------------
4953 NETDATA.d3Initialize = function(callback) {
4954 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4959 xhrFields: { withCredentials: true } // required for the cookie
4962 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4965 NETDATA.chartLibraries.d3.enabled = false;
4966 NETDATA.error(100, NETDATA.d3_js);
4968 .always(function() {
4969 if(typeof callback === "function")
4974 NETDATA.chartLibraries.d3.enabled = false;
4975 if(typeof callback === "function")
4980 NETDATA.d3ChartUpdate = function(state, data) {
4984 NETDATA.d3ChartCreate = function(state, data) {
4988 // ----------------------------------------------------------------------------------------------------------------
4991 NETDATA.googleInitialize = function(callback) {
4992 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4994 url: NETDATA.google_js,
4997 xhrFields: { withCredentials: true } // required for the cookie
5000 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5001 google.load('visualization', '1.1', {
5002 'packages': ['corechart', 'controls'],
5003 'callback': callback
5007 NETDATA.chartLibraries.google.enabled = false;
5008 NETDATA.error(100, NETDATA.google_js);
5009 if(typeof callback === "function")
5014 NETDATA.chartLibraries.google.enabled = false;
5015 if(typeof callback === "function")
5020 NETDATA.googleChartUpdate = function(state, data) {
5021 var datatable = new google.visualization.DataTable(data.result);
5022 state.google_instance.draw(datatable, state.google_options);
5026 NETDATA.googleChartCreate = function(state, data) {
5027 var datatable = new google.visualization.DataTable(data.result);
5029 state.google_options = {
5030 colors: state.chartColors(),
5032 // do not set width, height - the chart resizes itself
5033 //width: state.chartWidth(),
5034 //height: state.chartHeight(),
5039 // title: "Time of Day",
5040 // format:'HH:mm:ss',
5041 viewWindowMode: 'maximized',
5053 viewWindowMode: 'pretty',
5068 focusTarget: 'category',
5075 titlePosition: 'out',
5086 curveType: 'function',
5091 switch(state.chart.chart_type) {
5093 state.google_options.vAxis.viewWindowMode = 'maximized';
5094 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5095 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5099 state.google_options.isStacked = true;
5100 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5101 state.google_options.vAxis.viewWindowMode = 'maximized';
5102 state.google_options.vAxis.minValue = null;
5103 state.google_options.vAxis.maxValue = null;
5104 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5109 state.google_options.lineWidth = 2;
5110 state.google_instance = new google.visualization.LineChart(state.element_chart);
5114 state.google_instance.draw(datatable, state.google_options);
5118 // ----------------------------------------------------------------------------------------------------------------
5120 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5121 if(typeof value !== 'number') value = 0;
5122 if(typeof min !== 'number') min = 0;
5123 if(typeof max !== 'number') max = 0;
5125 if(min > value) min = value;
5126 if(max < value) max = value;
5128 // make sure it is zero based
5129 if(min > 0) min = 0;
5130 if(max < 0) max = 0;
5135 pcent = Math.round(value * 100 / max);
5136 if(pcent === 0) pcent = 0.1;
5140 pcent = Math.round(-value * 100 / min);
5141 if(pcent === 0) pcent = -0.1;
5147 // ----------------------------------------------------------------------------------------------------------------
5150 NETDATA.easypiechartInitialize = function(callback) {
5151 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5153 url: NETDATA.easypiechart_js,
5156 xhrFields: { withCredentials: true } // required for the cookie
5159 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5162 NETDATA.chartLibraries.easypiechart.enabled = false;
5163 NETDATA.error(100, NETDATA.easypiechart_js);
5165 .always(function() {
5166 if(typeof callback === "function")
5171 NETDATA.chartLibraries.easypiechart.enabled = false;
5172 if(typeof callback === "function")
5177 NETDATA.easypiechartClearSelection = function(state) {
5178 if(typeof state.easyPieChartEvent !== 'undefined') {
5179 if(state.easyPieChartEvent.timer !== null)
5180 clearTimeout(state.easyPieChartEvent.timer);
5182 state.easyPieChartEvent.timer = null;
5185 if(state.isAutoRefreshable() === true && state.data !== null) {
5186 NETDATA.easypiechartChartUpdate(state, state.data);
5189 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5190 state.easyPieChart_instance.update(0);
5192 state.easyPieChart_instance.enableAnimation();
5197 NETDATA.easypiechartSetSelection = function(state, t) {
5198 if(state.timeIsVisible(t) !== true)
5199 return NETDATA.easypiechartClearSelection(state);
5201 var slot = state.calculateRowForTime(t);
5202 if(slot < 0 || slot >= state.data.result.length)
5203 return NETDATA.easypiechartClearSelection(state);
5205 if(typeof state.easyPieChartEvent === 'undefined') {
5206 state.easyPieChartEvent = {
5213 var value = state.data.result[state.data.result.length - 1 - slot];
5214 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5215 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5216 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5218 state.easyPieChartEvent.value = value;
5219 state.easyPieChartEvent.pcent = pcent;
5220 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5222 if(state.easyPieChartEvent.timer === null) {
5223 state.easyPieChart_instance.disableAnimation();
5225 state.easyPieChartEvent.timer = setTimeout(function() {
5226 state.easyPieChartEvent.timer = null;
5227 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5228 }, NETDATA.options.current.charts_selection_animation_delay);
5234 NETDATA.easypiechartChartUpdate = function(state, data) {
5235 var value, min, max, pcent;
5237 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5242 value = data.result[0];
5243 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5244 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5245 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5248 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5249 state.easyPieChart_instance.update(pcent);
5253 NETDATA.easypiechartChartCreate = function(state, data) {
5254 var self = $(state.element);
5255 var chart = $(state.element_chart);
5257 var value = data.result[0];
5258 var min = self.data('easypiechart-min-value') || null;
5259 var max = self.data('easypiechart-max-value') || null;
5260 var adjust = self.data('easypiechart-adjust') || null;
5263 min = NETDATA.commonMin.get(state);
5264 state.easyPieChartMin = null;
5267 state.easyPieChartMin = min;
5270 max = NETDATA.commonMax.get(state);
5271 state.easyPieChartMax = null;
5274 state.easyPieChartMax = max;
5276 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5278 chart.data('data-percent', pcent);
5282 case 'width': size = state.chartHeight(); break;
5283 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5284 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5286 default: size = state.chartWidth(); break;
5288 state.element.style.width = size + 'px';
5289 state.element.style.height = size + 'px';
5291 var stroke = Math.floor(size / 22);
5292 if(stroke < 3) stroke = 2;
5294 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5295 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5296 state.easyPieChartLabel = document.createElement('span');
5297 state.easyPieChartLabel.className = 'easyPieChartLabel';
5298 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5299 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5300 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5301 state.element_chart.appendChild(state.easyPieChartLabel);
5303 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5304 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5305 state.easyPieChartTitle = document.createElement('span');
5306 state.easyPieChartTitle.className = 'easyPieChartTitle';
5307 state.easyPieChartTitle.innerText = state.title;
5308 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5309 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5310 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5311 state.element_chart.appendChild(state.easyPieChartTitle);
5313 var unitfontsize = Math.round(titlefontsize * 0.9);
5314 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5315 state.easyPieChartUnits = document.createElement('span');
5316 state.easyPieChartUnits.className = 'easyPieChartUnits';
5317 state.easyPieChartUnits.innerText = state.units;
5318 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5319 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5320 state.element_chart.appendChild(state.easyPieChartUnits);
5322 var barColor = self.data('easypiechart-barcolor');
5323 if(typeof barColor === 'undefined' || barColor === null)
5324 barColor = state.chartColors()[0];
5326 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5327 var tmp = eval(barColor);
5328 if(typeof tmp === 'function')
5332 chart.easyPieChart({
5334 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5335 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5336 scaleLength: self.data('easypiechart-scalelength') || 5,
5337 lineCap: self.data('easypiechart-linecap') || 'round',
5338 lineWidth: self.data('easypiechart-linewidth') || stroke,
5339 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5340 size: self.data('easypiechart-size') || size,
5341 rotate: self.data('easypiechart-rotate') || 0,
5342 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5343 easing: self.data('easypiechart-easing') || undefined
5346 // when we just re-create the chart
5347 // do not animate the first update
5349 if(typeof state.easyPieChart_instance !== 'undefined')
5352 state.easyPieChart_instance = chart.data('easyPieChart');
5353 if(animate === false) state.easyPieChart_instance.disableAnimation();
5354 state.easyPieChart_instance.update(pcent);
5355 if(animate === false) state.easyPieChart_instance.enableAnimation();
5359 // ----------------------------------------------------------------------------------------------------------------
5362 NETDATA.gaugeInitialize = function(callback) {
5363 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5365 url: NETDATA.gauge_js,
5368 xhrFields: { withCredentials: true } // required for the cookie
5371 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5374 NETDATA.chartLibraries.gauge.enabled = false;
5375 NETDATA.error(100, NETDATA.gauge_js);
5377 .always(function() {
5378 if(typeof callback === "function")
5383 NETDATA.chartLibraries.gauge.enabled = false;
5384 if(typeof callback === "function")
5389 NETDATA.gaugeAnimation = function(state, status) {
5392 if(typeof status === 'boolean' && status === false)
5394 else if(typeof status === 'number')
5397 // console.log('gauge speed ' + speed);
5398 state.gauge_instance.animationSpeed = speed;
5399 state.___gaugeOld__.speed = speed;
5402 NETDATA.gaugeSet = function(state, value, min, max) {
5403 if(typeof value !== 'number') value = 0;
5404 if(typeof min !== 'number') min = 0;
5405 if(typeof max !== 'number') max = 0;
5406 if(value > max) max = value;
5407 if(value < min) min = value;
5413 else if(min === max)
5416 // gauge.js has an issue if the needle
5417 // is smaller than min or larger than max
5418 // when we set the new values
5419 // the needle will go crazy
5421 // to prevent it, we always feed it
5422 // with a percentage, so that the needle
5423 // is always between min and max
5424 var pcent = (value - min) * 100 / (max - min);
5426 // these should never happen
5427 if(pcent < 0) pcent = 0;
5428 if(pcent > 100) pcent = 100;
5430 state.gauge_instance.set(pcent);
5431 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5433 state.___gaugeOld__.value = value;
5434 state.___gaugeOld__.min = min;
5435 state.___gaugeOld__.max = max;
5438 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5439 if(state.___gaugeOld__.valueLabel !== value) {
5440 state.___gaugeOld__.valueLabel = value;
5441 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5443 if(state.___gaugeOld__.minLabel !== min) {
5444 state.___gaugeOld__.minLabel = min;
5445 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5447 if(state.___gaugeOld__.maxLabel !== max) {
5448 state.___gaugeOld__.maxLabel = max;
5449 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5453 NETDATA.gaugeClearSelection = function(state) {
5454 if(typeof state.gaugeEvent !== 'undefined') {
5455 if(state.gaugeEvent.timer !== null)
5456 clearTimeout(state.gaugeEvent.timer);
5458 state.gaugeEvent.timer = null;
5461 if(state.isAutoRefreshable() === true && state.data !== null) {
5462 NETDATA.gaugeChartUpdate(state, state.data);
5465 NETDATA.gaugeAnimation(state, false);
5466 NETDATA.gaugeSet(state, null, null, null);
5467 NETDATA.gaugeSetLabels(state, null, null, null);
5470 NETDATA.gaugeAnimation(state, true);
5474 NETDATA.gaugeSetSelection = function(state, t) {
5475 if(state.timeIsVisible(t) !== true)
5476 return NETDATA.gaugeClearSelection(state);
5478 var slot = state.calculateRowForTime(t);
5479 if(slot < 0 || slot >= state.data.result.length)
5480 return NETDATA.gaugeClearSelection(state);
5482 if(typeof state.gaugeEvent === 'undefined') {
5483 state.gaugeEvent = {
5491 var value = state.data.result[state.data.result.length - 1 - slot];
5492 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5493 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5495 // make sure it is zero based
5496 if(min > 0) min = 0;
5497 if(max < 0) max = 0;
5499 state.gaugeEvent.value = value;
5500 state.gaugeEvent.min = min;
5501 state.gaugeEvent.max = max;
5502 NETDATA.gaugeSetLabels(state, value, min, max);
5504 if(state.gaugeEvent.timer === null) {
5505 NETDATA.gaugeAnimation(state, false);
5507 state.gaugeEvent.timer = setTimeout(function() {
5508 state.gaugeEvent.timer = null;
5509 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5510 }, NETDATA.options.current.charts_selection_animation_delay);
5516 NETDATA.gaugeChartUpdate = function(state, data) {
5517 var value, min, max;
5519 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5523 NETDATA.gaugeSetLabels(state, null, null, null);
5526 value = data.result[0];
5527 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5528 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5529 if(value < min) min = value;
5530 if(value > max) max = value;
5532 // make sure it is zero based
5533 if(min > 0) min = 0;
5534 if(max < 0) max = 0;
5536 NETDATA.gaugeSetLabels(state, value, min, max);
5539 NETDATA.gaugeSet(state, value, min, max);
5543 NETDATA.gaugeChartCreate = function(state, data) {
5544 var self = $(state.element);
5545 // var chart = $(state.element_chart);
5547 var value = data.result[0];
5548 var min = self.data('gauge-min-value') || null;
5549 var max = self.data('gauge-max-value') || null;
5550 var adjust = self.data('gauge-adjust') || null;
5551 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5552 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5553 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5554 var stopColor = self.data('gauge-stop-color') || void 0;
5555 var generateGradient = self.data('gauge-generate-gradient') || false;
5558 min = NETDATA.commonMin.get(state);
5559 state.gaugeMin = null;
5562 state.gaugeMin = min;
5565 max = NETDATA.commonMax.get(state);
5566 state.gaugeMax = null;
5569 state.gaugeMax = max;
5571 // make sure it is zero based
5572 if(min > 0) min = 0;
5573 if(max < 0) max = 0;
5575 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5577 // case 'width': width = height * ratio; break;
5579 // default: height = width / ratio; break;
5581 //state.element.style.width = width.toString() + 'px';
5582 //state.element.style.height = height.toString() + 'px';
5587 lines: 12, // The number of lines to draw
5588 angle: 0.15, // The length of each line
5589 lineWidth: 0.44, // 0.44 The line thickness
5591 length: 0.8, // 0.9 The radius of the inner circle
5592 strokeWidth: 0.035, // The rotation offset
5593 color: pointerColor // Fill color
5595 colorStart: startColor, // Colors
5596 colorStop: stopColor, // just experiment with them
5597 strokeColor: strokeColor, // to see which ones work best for you
5599 generateGradient: (generateGradient === true)?true:false,
5603 if (generateGradient.constructor === Array) {
5605 // data-gauge-generate-gradient="[0, 50, 100]"
5606 // data-gauge-gradient-percent-color-0="#FFFFFF"
5607 // data-gauge-gradient-percent-color-50="#999900"
5608 // data-gauge-gradient-percent-color-100="#000000"
5610 options.percentColors = new Array();
5611 var len = generateGradient.length;
5613 var pcent = generateGradient[len];
5614 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5615 if(color !== false) {
5616 var a = new Array();
5619 options.percentColors.unshift(a);
5622 if(options.percentColors.length === 0)
5623 delete options.percentColors;
5625 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5626 options.percentColors = [
5627 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5628 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5629 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5630 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5631 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5632 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5633 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5634 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5635 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5636 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5637 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5640 state.gauge_canvas = document.createElement('canvas');
5641 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5642 state.gauge_canvas.className = 'gaugeChart';
5643 state.gauge_canvas.width = width;
5644 state.gauge_canvas.height = height;
5645 state.element_chart.appendChild(state.gauge_canvas);
5647 var valuefontsize = Math.floor(height / 6);
5648 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5649 state.gaugeChartLabel = document.createElement('span');
5650 state.gaugeChartLabel.className = 'gaugeChartLabel';
5651 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5652 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5653 state.element_chart.appendChild(state.gaugeChartLabel);
5655 var titlefontsize = Math.round(valuefontsize / 2);
5657 state.gaugeChartTitle = document.createElement('span');
5658 state.gaugeChartTitle.className = 'gaugeChartTitle';
5659 state.gaugeChartTitle.innerText = state.title;
5660 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5661 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5662 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5663 state.element_chart.appendChild(state.gaugeChartTitle);
5665 var unitfontsize = Math.round(titlefontsize * 0.9);
5666 state.gaugeChartUnits = document.createElement('span');
5667 state.gaugeChartUnits.className = 'gaugeChartUnits';
5668 state.gaugeChartUnits.innerText = state.units;
5669 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5670 state.element_chart.appendChild(state.gaugeChartUnits);
5672 state.gaugeChartMin = document.createElement('span');
5673 state.gaugeChartMin.className = 'gaugeChartMin';
5674 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5675 state.element_chart.appendChild(state.gaugeChartMin);
5677 state.gaugeChartMax = document.createElement('span');
5678 state.gaugeChartMax.className = 'gaugeChartMax';
5679 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5680 state.element_chart.appendChild(state.gaugeChartMax);
5682 // when we just re-create the chart
5683 // do not animate the first update
5685 if(typeof state.gauge_instance !== 'undefined')
5688 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5690 state.___gaugeOld__ = {
5699 // we will always feed a percentage
5700 state.gauge_instance.minValue = 0;
5701 state.gauge_instance.maxValue = 100;
5703 NETDATA.gaugeAnimation(state, animate);
5704 NETDATA.gaugeSet(state, value, min, max);
5705 NETDATA.gaugeSetLabels(state, value, min, max);
5706 NETDATA.gaugeAnimation(state, true);
5710 // ----------------------------------------------------------------------------------------------------------------
5711 // Charts Libraries Registration
5713 NETDATA.chartLibraries = {
5715 initialize: NETDATA.dygraphInitialize,
5716 create: NETDATA.dygraphChartCreate,
5717 update: NETDATA.dygraphChartUpdate,
5718 resize: function(state) {
5719 if(typeof state.dygraph_instance.resize === 'function')
5720 state.dygraph_instance.resize();
5722 setSelection: NETDATA.dygraphSetSelection,
5723 clearSelection: NETDATA.dygraphClearSelection,
5724 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5727 format: function(state) { return 'json'; },
5728 options: function(state) { return 'ms|flip'; },
5729 legend: function(state) {
5730 if(this.isSparkline(state) === false)
5731 return 'right-side';
5735 autoresize: function(state) { return true; },
5736 max_updates_to_recreate: function(state) { return 5000; },
5737 track_colors: function(state) { return true; },
5738 pixels_per_point: function(state) {
5739 if(this.isSparkline(state) === false)
5745 isSparkline: function(state) {
5746 if(typeof state.dygraph_sparkline === 'undefined') {
5747 var t = $(state.element).data('dygraph-theme');
5748 if(t === 'sparkline')
5749 state.dygraph_sparkline = true;
5751 state.dygraph_sparkline = false;
5753 return state.dygraph_sparkline;
5757 initialize: NETDATA.sparklineInitialize,
5758 create: NETDATA.sparklineChartCreate,
5759 update: NETDATA.sparklineChartUpdate,
5761 setSelection: undefined, // function(state, t) { return true; },
5762 clearSelection: undefined, // function(state) { return true; },
5763 toolboxPanAndZoom: null,
5766 format: function(state) { return 'array'; },
5767 options: function(state) { return 'flip|abs'; },
5768 legend: function(state) { return null; },
5769 autoresize: function(state) { return false; },
5770 max_updates_to_recreate: function(state) { return 5000; },
5771 track_colors: function(state) { return false; },
5772 pixels_per_point: function(state) { return 3; }
5775 initialize: NETDATA.peityInitialize,
5776 create: NETDATA.peityChartCreate,
5777 update: NETDATA.peityChartUpdate,
5779 setSelection: undefined, // function(state, t) { return true; },
5780 clearSelection: undefined, // function(state) { return true; },
5781 toolboxPanAndZoom: null,
5784 format: function(state) { return 'ssvcomma'; },
5785 options: function(state) { return 'null2zero|flip|abs'; },
5786 legend: function(state) { return null; },
5787 autoresize: function(state) { return false; },
5788 max_updates_to_recreate: function(state) { return 5000; },
5789 track_colors: function(state) { return false; },
5790 pixels_per_point: function(state) { return 3; }
5793 initialize: NETDATA.morrisInitialize,
5794 create: NETDATA.morrisChartCreate,
5795 update: NETDATA.morrisChartUpdate,
5797 setSelection: undefined, // function(state, t) { return true; },
5798 clearSelection: undefined, // function(state) { return true; },
5799 toolboxPanAndZoom: null,
5802 format: function(state) { return 'json'; },
5803 options: function(state) { return 'objectrows|ms'; },
5804 legend: function(state) { return null; },
5805 autoresize: function(state) { return false; },
5806 max_updates_to_recreate: function(state) { return 50; },
5807 track_colors: function(state) { return false; },
5808 pixels_per_point: function(state) { return 15; }
5811 initialize: NETDATA.googleInitialize,
5812 create: NETDATA.googleChartCreate,
5813 update: NETDATA.googleChartUpdate,
5815 setSelection: undefined, //function(state, t) { return true; },
5816 clearSelection: undefined, //function(state) { return true; },
5817 toolboxPanAndZoom: null,
5820 format: function(state) { return 'datatable'; },
5821 options: function(state) { return ''; },
5822 legend: function(state) { return null; },
5823 autoresize: function(state) { return false; },
5824 max_updates_to_recreate: function(state) { return 300; },
5825 track_colors: function(state) { return false; },
5826 pixels_per_point: function(state) { return 4; }
5829 initialize: NETDATA.raphaelInitialize,
5830 create: NETDATA.raphaelChartCreate,
5831 update: NETDATA.raphaelChartUpdate,
5833 setSelection: undefined, // function(state, t) { return true; },
5834 clearSelection: undefined, // function(state) { return true; },
5835 toolboxPanAndZoom: null,
5838 format: function(state) { return 'json'; },
5839 options: function(state) { return ''; },
5840 legend: function(state) { return null; },
5841 autoresize: function(state) { return false; },
5842 max_updates_to_recreate: function(state) { return 5000; },
5843 track_colors: function(state) { return false; },
5844 pixels_per_point: function(state) { return 3; }
5847 initialize: NETDATA.c3Initialize,
5848 create: NETDATA.c3ChartCreate,
5849 update: NETDATA.c3ChartUpdate,
5851 setSelection: undefined, // function(state, t) { return true; },
5852 clearSelection: undefined, // function(state) { return true; },
5853 toolboxPanAndZoom: null,
5856 format: function(state) { return 'csvjsonarray'; },
5857 options: function(state) { return 'milliseconds'; },
5858 legend: function(state) { return null; },
5859 autoresize: function(state) { return false; },
5860 max_updates_to_recreate: function(state) { return 5000; },
5861 track_colors: function(state) { return false; },
5862 pixels_per_point: function(state) { return 15; }
5865 initialize: NETDATA.d3Initialize,
5866 create: NETDATA.d3ChartCreate,
5867 update: NETDATA.d3ChartUpdate,
5869 setSelection: undefined, // function(state, t) { return true; },
5870 clearSelection: undefined, // function(state) { return true; },
5871 toolboxPanAndZoom: null,
5874 format: function(state) { return 'json'; },
5875 options: function(state) { return ''; },
5876 legend: function(state) { return null; },
5877 autoresize: function(state) { return false; },
5878 max_updates_to_recreate: function(state) { return 5000; },
5879 track_colors: function(state) { return false; },
5880 pixels_per_point: function(state) { return 3; }
5883 initialize: NETDATA.easypiechartInitialize,
5884 create: NETDATA.easypiechartChartCreate,
5885 update: NETDATA.easypiechartChartUpdate,
5887 setSelection: NETDATA.easypiechartSetSelection,
5888 clearSelection: NETDATA.easypiechartClearSelection,
5889 toolboxPanAndZoom: null,
5892 format: function(state) { return 'array'; },
5893 options: function(state) { return 'absolute'; },
5894 legend: function(state) { return null; },
5895 autoresize: function(state) { return false; },
5896 max_updates_to_recreate: function(state) { return 5000; },
5897 track_colors: function(state) { return true; },
5898 pixels_per_point: function(state) { return 3; },
5902 initialize: NETDATA.gaugeInitialize,
5903 create: NETDATA.gaugeChartCreate,
5904 update: NETDATA.gaugeChartUpdate,
5906 setSelection: NETDATA.gaugeSetSelection,
5907 clearSelection: NETDATA.gaugeClearSelection,
5908 toolboxPanAndZoom: null,
5911 format: function(state) { return 'array'; },
5912 options: function(state) { return 'absolute'; },
5913 legend: function(state) { return null; },
5914 autoresize: function(state) { return false; },
5915 max_updates_to_recreate: function(state) { return 5000; },
5916 track_colors: function(state) { return true; },
5917 pixels_per_point: function(state) { return 3; },
5922 NETDATA.registerChartLibrary = function(library, url) {
5923 if(NETDATA.options.debug.libraries === true)
5924 console.log("registering chart library: " + library);
5926 NETDATA.chartLibraries[library].url = url;
5927 NETDATA.chartLibraries[library].initialized = true;
5928 NETDATA.chartLibraries[library].enabled = true;
5931 // ----------------------------------------------------------------------------------------------------------------
5932 // Load required JS libraries and CSS
5934 NETDATA.requiredJs = [
5936 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5938 isAlreadyLoaded: function() {
5939 // check if bootstrap is loaded
5940 if(typeof $().emulateTransitionEnd === 'function')
5943 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5951 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5952 isAlreadyLoaded: function() { return false; }
5956 NETDATA.requiredCSS = [
5958 url: NETDATA.themes.current.bootstrap_css,
5959 isAlreadyLoaded: function() {
5960 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5967 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5968 isAlreadyLoaded: function() { return false; }
5971 url: NETDATA.themes.current.dashboard_css,
5972 isAlreadyLoaded: function() { return false; }
5976 NETDATA.loadedRequiredJs = 0;
5977 NETDATA.loadRequiredJs = function(index, callback) {
5978 if(index >= NETDATA.requiredJs.length) {
5979 if(typeof callback === 'function')
5984 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5985 NETDATA.loadedRequiredJs++;
5986 NETDATA.loadRequiredJs(++index, callback);
5990 if(NETDATA.options.debug.main_loop === true)
5991 console.log('loading ' + NETDATA.requiredJs[index].url);
5994 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5998 url: NETDATA.requiredJs[index].url,
6001 xhrFields: { withCredentials: true } // required for the cookie
6004 if(NETDATA.options.debug.main_loop === true)
6005 console.log('loaded ' + NETDATA.requiredJs[index].url);
6008 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6010 .always(function() {
6011 NETDATA.loadedRequiredJs++;
6014 NETDATA.loadRequiredJs(++index, callback);
6018 NETDATA.loadRequiredJs(++index, callback);
6021 NETDATA.loadRequiredCSS = function(index) {
6022 if(index >= NETDATA.requiredCSS.length)
6025 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6026 NETDATA.loadRequiredCSS(++index);
6030 if(NETDATA.options.debug.main_loop === true)
6031 console.log('loading ' + NETDATA.requiredCSS[index].url);
6033 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6034 NETDATA.loadRequiredCSS(++index);
6038 // ----------------------------------------------------------------------------------------------------------------
6039 // Registry of netdata hosts
6042 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6043 chart_div_offset: 100, // give that space above the chart when scrolling to it
6044 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6045 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6047 ms_penalty: 0, // the time penalty of the next alarm
6048 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6049 // if alarms are shown faster than: one per 500ms
6051 notifications: false, // when true, the browser supports notifications (may not be granted though)
6052 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6053 first_notification_id: 0, // the id of the first alarm_log entry for this session
6054 // this is used to prevent CLEAR notifications for past events
6055 // notifications_shown: new Array(),
6057 server: null, // the server to connect to for fetching alarms
6058 current: null, // the list of raised alarms - updated in the background
6059 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6061 notify: function(entry) {
6062 // console.log('alarm ' + entry.unique_id);
6064 if(entry.updated === true) {
6065 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6069 var value = entry.value;
6070 if(NETDATA.alarms.current !== null) {
6071 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6072 if(typeof t !== 'undefined' && entry.status === t.status)
6076 var name = entry.name.replace(/_/g, ' ');
6077 var status = entry.status.toLowerCase();
6078 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6079 var tag = entry.alarm_id;
6080 var icon = 'images/seo-performance-128.png';
6081 var interaction = false;
6085 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6087 switch(entry.status) {
6095 case 'UNINITIALIZED':
6099 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6100 // console.log('alarm ' + entry.unique_id + ' is not current');
6103 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6104 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6107 title = name + ' back to normal';
6108 icon = 'images/check-mark-2-128-green.png'
6109 interaction = false;
6113 if(entry.old_status === 'CRITICAL')
6114 status = 'demoted to ' + entry.status.toLowerCase();
6116 icon = 'images/alert-128-orange.png';
6117 interaction = false;
6121 if(entry.old_status === 'WARNING')
6122 status = 'escalated to ' + entry.status.toLowerCase();
6124 icon = 'images/alert-128-red.png'
6129 console.log('invalid alarm status ' + entry.status);
6134 // cleanup old notifications with the same alarm_id as this one
6135 // FIXME: it does not seem to work on any web browser!
6136 var len = NETDATA.alarms.notifications_shown.length;
6138 var n = NETDATA.alarms.notifications_shown[len];
6139 if(n.data.alarm_id === entry.alarm_id) {
6140 console.log('removing old alarm ' + n.data.unique_id);
6142 // close the notification
6145 // remove it from the array
6146 NETDATA.alarms.notifications_shown.splice(len, 1);
6147 len = NETDATA.alarms.notifications_shown.length;
6154 setTimeout(function() {
6155 // show this notification
6156 // console.log('new notification: ' + title);
6157 var n = new Notification(title, {
6158 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6160 requireInteraction: interaction,
6161 icon: NETDATA.serverDefault + icon,
6165 n.onclick = function(event) {
6166 event.preventDefault();
6167 NETDATA.alarms.onclick(event.target.data);
6171 // NETDATA.alarms.notifications_shown.push(n);
6172 // console.log(entry);
6173 }, NETDATA.alarms.ms_penalty);
6175 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6179 scrollToChart: function(chart_id) {
6180 if(typeof chart_id === 'string') {
6181 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6182 if(typeof offset !== 'undefined') {
6183 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6190 scrollToAlarm: function(alarm) {
6191 if(typeof alarm === 'object') {
6192 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6194 if(ret === true && NETDATA.options.page_is_visible === false)
6196 // 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.');
6201 notifyAll: function() {
6202 // console.log('FETCHING ALARM LOG');
6203 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6204 // console.log('ALARM LOG FETCHED');
6206 if(data === null || typeof data !== 'object') {
6207 console.log('invalid alarms log response');
6211 if(data.length === 0) {
6212 console.log('received empty alarm log');
6216 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6218 data.sort(function(a, b) {
6219 if(a.unique_id > b.unique_id) return -1;
6220 if(a.unique_id < b.unique_id) return 1;
6224 NETDATA.alarms.ms_penalty = 0;
6226 var len = data.length;
6228 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6229 NETDATA.alarms.notify(data[len]);
6232 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6235 NETDATA.alarms.last_notification_id = data[0].unique_id;
6236 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6237 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6241 check_notifications: function() {
6242 // returns true if we should fire 1+ notifications
6244 if(NETDATA.alarms.notifications !== true) {
6245 // console.log('notifications not available');
6249 if(Notification.permission !== 'granted') {
6250 // console.log('notifications not granted');
6254 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6255 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6257 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6258 // console.log('new alarms detected');
6261 //else console.log('no new alarms');
6263 // else console.log('cannot process alarms');
6268 get: function(what, callback) {
6270 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6274 'Cache-Control': 'no-cache, no-store',
6275 'Pragma': 'no-cache'
6277 xhrFields: { withCredentials: true } // required for the cookie
6279 .done(function(data) {
6280 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6281 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6283 if(typeof callback === 'function')
6284 return callback(data);
6287 NETDATA.error(415, NETDATA.alarms.server);
6289 if(typeof callback === 'function')
6290 return callback(null);
6294 update_forever: function() {
6295 NETDATA.alarms.get('active', function(data) {
6297 NETDATA.alarms.current = data;
6299 if(NETDATA.alarms.check_notifications() === true) {
6300 NETDATA.alarms.notifyAll();
6303 if (typeof NETDATA.alarms.callback === 'function') {
6304 NETDATA.alarms.callback(data);
6307 // Health monitoring is disabled on this netdata
6308 if(data.status === false) return;
6311 setTimeout(NETDATA.alarms.update_forever, 10000);
6315 get_log: function(last_id, callback) {
6316 // console.log('fetching all log after ' + last_id.toString());
6318 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6322 'Cache-Control': 'no-cache, no-store',
6323 'Pragma': 'no-cache'
6325 xhrFields: { withCredentials: true } // required for the cookie
6327 .done(function(data) {
6328 if(typeof callback === 'function')
6329 return callback(data);
6332 NETDATA.error(416, NETDATA.alarms.server);
6334 if(typeof callback === 'function')
6335 return callback(null);
6340 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6342 NETDATA.alarms.last_notification_id =
6343 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6345 if(NETDATA.alarms.onclick === null)
6346 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6348 if(netdataShowAlarms === true) {
6349 NETDATA.alarms.update_forever();
6351 if('Notification' in window) {
6352 // console.log('notifications available');
6353 NETDATA.alarms.notifications = true;
6355 if(Notification.permission === 'default')
6356 Notification.requestPermission();
6362 // ----------------------------------------------------------------------------------------------------------------
6363 // Registry of netdata hosts
6365 NETDATA.registry = {
6366 server: null, // the netdata registry server
6367 person_guid: null, // the unique ID of this browser / user
6368 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6369 hostname: null, // the hostname of the netdata server that served dashboard.js
6370 machines: null, // the user's other URLs
6371 machines_array: null, // the user's other URLs in an array
6374 parsePersonUrls: function(person_urls) {
6375 // console.log(person_urls);
6376 NETDATA.registry.person_urls = person_urls;
6379 NETDATA.registry.machines = {};
6380 NETDATA.registry.machines_array = new Array();
6382 var now = Date.now();
6383 var apu = person_urls;
6386 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6387 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6393 accesses: apu[i][3],
6395 alternate_urls: new Array()
6397 obj.alternate_urls.push(apu[i][1]);
6399 NETDATA.registry.machines[apu[i][0]] = obj;
6400 NETDATA.registry.machines_array.push(obj);
6403 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6405 var pu = NETDATA.registry.machines[apu[i][0]];
6406 if(pu.last_t < apu[i][2]) {
6408 pu.last_t = apu[i][2];
6409 pu.name = apu[i][4];
6411 pu.accesses += apu[i][3];
6412 pu.alternate_urls.push(apu[i][1]);
6417 if(typeof netdataRegistryCallback === 'function')
6418 netdataRegistryCallback(NETDATA.registry.machines_array);
6422 if(netdataRegistry !== true) return;
6424 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6426 NETDATA.registry.server = data.registry;
6427 NETDATA.registry.machine_guid = data.machine_guid;
6428 NETDATA.registry.hostname = data.hostname;
6430 NETDATA.registry.access(2, function (person_urls) {
6431 NETDATA.registry.parsePersonUrls(person_urls);
6438 hello: function(host, callback) {
6439 host = NETDATA.fixHost(host);
6441 // send HELLO to a netdata server:
6442 // 1. verifies the server is reachable
6443 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6445 url: host + '/api/v1/registry?action=hello',
6449 'Cache-Control': 'no-cache, no-store',
6450 'Pragma': 'no-cache'
6452 xhrFields: { withCredentials: true } // required for the cookie
6454 .done(function(data) {
6455 if(typeof data.status !== 'string' || data.status !== 'ok') {
6456 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6460 if(typeof callback === 'function')
6461 return callback(data);
6464 NETDATA.error(407, host);
6466 if(typeof callback === 'function')
6467 return callback(null);
6471 access: function(max_redirects, callback) {
6472 // send ACCESS to a netdata registry:
6473 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6474 // 2. it responds with a list of netdata servers we know
6475 // the registry identifies us using a cookie it sets the first time we access it
6476 // the registry may respond with a redirect URL to send us to another registry
6478 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),
6482 'Cache-Control': 'no-cache, no-store',
6483 'Pragma': 'no-cache'
6485 xhrFields: { withCredentials: true } // required for the cookie
6487 .done(function(data) {
6488 var redirect = null;
6489 if(typeof data.registry === 'string')
6490 redirect = data.registry;
6492 if(typeof data.status !== 'string' || data.status !== 'ok') {
6493 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6498 if(redirect !== null && max_redirects > 0) {
6499 NETDATA.registry.server = redirect;
6500 NETDATA.registry.access(max_redirects - 1, callback);
6503 if(typeof callback === 'function')
6504 return callback(null);
6508 if(typeof data.person_guid === 'string')
6509 NETDATA.registry.person_guid = data.person_guid;
6511 if(typeof callback === 'function')
6512 return callback(data.urls);
6516 NETDATA.error(410, NETDATA.registry.server);
6518 if(typeof callback === 'function')
6519 return callback(null);
6523 delete: function(delete_url, callback) {
6524 // send DELETE to a netdata registry:
6526 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),
6530 'Cache-Control': 'no-cache, no-store',
6531 'Pragma': 'no-cache'
6533 xhrFields: { withCredentials: true } // required for the cookie
6535 .done(function(data) {
6536 if(typeof data.status !== 'string' || data.status !== 'ok') {
6537 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6541 if(typeof callback === 'function')
6542 return callback(data);
6545 NETDATA.error(412, NETDATA.registry.server);
6547 if(typeof callback === 'function')
6548 return callback(null);
6552 search: function(machine_guid, callback) {
6553 // SEARCH for the URLs of a machine:
6555 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,
6559 'Cache-Control': 'no-cache, no-store',
6560 'Pragma': 'no-cache'
6562 xhrFields: { withCredentials: true } // required for the cookie
6564 .done(function(data) {
6565 if(typeof data.status !== 'string' || data.status !== 'ok') {
6566 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6570 if(typeof callback === 'function')
6571 return callback(data);
6574 NETDATA.error(418, NETDATA.registry.server);
6576 if(typeof callback === 'function')
6577 return callback(null);
6581 switch: function(new_person_guid, callback) {
6584 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,
6588 'Cache-Control': 'no-cache, no-store',
6589 'Pragma': 'no-cache'
6591 xhrFields: { withCredentials: true } // required for the cookie
6593 .done(function(data) {
6594 if(typeof data.status !== 'string' || data.status !== 'ok') {
6595 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6599 if(typeof callback === 'function')
6600 return callback(data);
6603 NETDATA.error(414, NETDATA.registry.server);
6605 if(typeof callback === 'function')
6606 return callback(null);
6611 // ----------------------------------------------------------------------------------------------------------------
6614 if(typeof netdataPrepCallback === 'function')
6615 netdataPrepCallback();
6617 NETDATA.errorReset();
6618 NETDATA.loadRequiredCSS(0);
6620 NETDATA._loadjQuery(function() {
6621 NETDATA.loadRequiredJs(0, function() {
6622 if(typeof $().emulateTransitionEnd !== 'function') {
6623 // bootstrap is not available
6624 NETDATA.options.current.show_help = false;
6627 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6628 if(NETDATA.options.debug.main_loop === true)
6629 console.log('starting chart refresh thread');
6635 })(window, document);