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.localStorageTested = -1;
367 NETDATA.localStorageTest = function() {
368 if(NETDATA.localStorageTested !== -1)
369 return NETDATA.localStorageTested;
371 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
374 localStorage.setItem(test, test);
375 localStorage.removeItem(test);
376 NETDATA.localStorageTested = true;
379 NETDATA.localStorageTested = false;
383 NETDATA.localStorageTested = false;
385 return NETDATA.localStorageTested;
388 NETDATA.localStorageGet = function(key, def, callback) {
391 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
392 NETDATA.localStorage.default[key.toString()] = def;
393 NETDATA.localStorage.callback[key.toString()] = callback;
396 if(NETDATA.localStorageTest() === true) {
398 // console.log('localStorage: loading "' + key.toString() + '"');
399 ret = localStorage.getItem(key.toString());
400 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
401 if(ret === null || ret === 'undefined') {
402 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
403 localStorage.setItem(key.toString(), JSON.stringify(def));
407 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
408 ret = JSON.parse(ret);
409 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
413 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
418 if(typeof ret === 'undefined' || ret === 'undefined') {
419 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
423 NETDATA.localStorage.current[key.toString()] = ret;
427 NETDATA.localStorageSet = function(key, value, callback) {
428 if(typeof value === 'undefined' || value === 'undefined') {
429 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
432 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
433 NETDATA.localStorage.default[key.toString()] = value;
434 NETDATA.localStorage.current[key.toString()] = value;
435 NETDATA.localStorage.callback[key.toString()] = callback;
438 if(NETDATA.localStorageTest() === true) {
439 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
441 localStorage.setItem(key.toString(), JSON.stringify(value));
444 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
448 NETDATA.localStorage.current[key.toString()] = value;
452 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
453 var keys = Object.keys(obj);
454 var len = keys.length;
458 if(typeof obj[i] === 'object') {
459 //console.log('object ' + prefix + '.' + i.toString());
460 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
464 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
468 NETDATA.setOption = function(key, value) {
469 if(key.toString() === 'setOptionCallback') {
470 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
471 NETDATA.options.current[key.toString()] = value;
472 NETDATA.options.current.setOptionCallback();
475 else if(NETDATA.options.current[key.toString()] !== value) {
476 var name = 'options.' + key.toString();
478 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
479 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
481 //console.log(NETDATA.localStorage);
482 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
483 //console.log(NETDATA.options);
484 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
486 if(typeof NETDATA.options.current.setOptionCallback === 'function')
487 NETDATA.options.current.setOptionCallback();
493 NETDATA.getOption = function(key) {
494 return NETDATA.options.current[key.toString()];
497 // read settings from local storage
498 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
500 // always start with this option enabled.
501 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
503 NETDATA.resetOptions = function() {
504 var keys = Object.keys(NETDATA.localStorage.default);
505 var len = keys.length;
508 var a = i.split('.');
510 if(a[0] === 'options') {
511 if(a[1] === 'setOptionCallback') continue;
512 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
513 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
515 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
517 else if(a[0] === 'chart_heights') {
518 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
519 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
525 // ----------------------------------------------------------------------------------------------------------------
527 if(NETDATA.options.debug.main_loop === true)
528 console.log('welcome to NETDATA');
530 NETDATA.onresizeCallback = null;
531 NETDATA.onresize = function() {
532 NETDATA.options.last_resized = Date.now();
535 if(typeof NETDATA.onresizeCallback === 'function')
536 NETDATA.onresizeCallback();
539 NETDATA.onscroll_updater_count = 0;
540 NETDATA.onscroll_updater_running = false;
541 NETDATA.onscroll_updater_last_run = 0;
542 NETDATA.onscroll_updater_watchdog = null;
543 NETDATA.onscroll_updater_max_duration = 0;
544 NETDATA.onscroll_updater_above_threshold_count = 0;
545 NETDATA.onscroll_updater = function() {
546 NETDATA.onscroll_updater_running = true;
547 NETDATA.onscroll_updater_count++;
548 var start = Date.now();
550 var targets = NETDATA.options.targets;
551 var len = targets.length;
553 // when the user scrolls he sees that we have
554 // hidden all the not-visible charts
555 // using this little function we try to switch
556 // the charts back to visible quickly
559 if(NETDATA.options.abort_ajax_on_scroll === true) {
560 // we have to cancel pending requests too
563 if (targets[len]._updating === true) {
564 if (typeof targets[len].xhr !== 'undefined') {
565 targets[len].xhr.abort();
566 targets[len].running = false;
567 targets[len]._updating = false;
569 targets[len].isVisible();
574 // just find which chart is visible
577 targets[len].isVisible();
580 var end = Date.now();
581 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
583 if(NETDATA.options.current.async_on_scroll === false) {
584 var dt = end - start;
585 if(dt > NETDATA.onscroll_updater_max_duration) {
586 // console.log('max onscroll event handler duration increased to ' + dt);
587 NETDATA.onscroll_updater_max_duration = dt;
590 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
591 // console.log('slow: ' + dt);
592 NETDATA.onscroll_updater_above_threshold_count++;
594 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
595 NETDATA.setOption('async_on_scroll', true);
596 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
601 NETDATA.onscroll_updater_last_run = start;
602 NETDATA.onscroll_updater_running = false;
605 NETDATA.onscroll = function() {
606 // console.log('onscroll');
608 NETDATA.options.last_page_scroll = Date.now();
609 NETDATA.options.auto_refresher_stop_until = 0;
611 if(NETDATA.options.targets === null) return;
613 if(NETDATA.options.current.async_on_scroll === true) {
615 if(NETDATA.onscroll_updater_running === false) {
616 NETDATA.onscroll_updater_running = true;
617 setTimeout(NETDATA.onscroll_updater, 0);
620 if(NETDATA.onscroll_updater_watchdog !== null)
621 clearTimeout(NETDATA.onscroll_updater_watchdog);
623 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
624 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
625 // console.log('watchdog');
626 NETDATA.onscroll_updater();
629 NETDATA.onscroll_updater_watchdog = null;
635 NETDATA.onscroll_updater();
639 window.onresize = NETDATA.onresize;
640 window.onscroll = NETDATA.onscroll;
642 // ----------------------------------------------------------------------------------------------------------------
645 NETDATA.errorCodes = {
646 100: { message: "Cannot load chart library", alert: true },
647 101: { message: "Cannot load jQuery", alert: true },
648 402: { message: "Chart library not found", alert: false },
649 403: { message: "Chart library not enabled/is failed", alert: false },
650 404: { message: "Chart not found", alert: false },
651 405: { message: "Cannot download charts index from server", alert: true },
652 406: { message: "Invalid charts index downloaded from server", alert: true },
653 407: { message: "Cannot HELLO netdata server", alert: false },
654 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
655 409: { message: "Cannot ACCESS netdata registry", alert: false },
656 410: { message: "Netdata registry ACCESS failed", alert: false },
657 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
658 412: { message: "Netdata registry DELETE failed", alert: false },
659 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
660 414: { message: "Netdata registry SWITCH failed", alert: false },
661 415: { message: "Netdata alarms download failed", alert: false },
662 416: { message: "Netdata alarms log download failed", alert: false },
663 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
664 418: { message: "Netdata registry SEARCH failed", alert: false }
666 NETDATA.errorLast = {
672 NETDATA.error = function(code, msg) {
673 NETDATA.errorLast.code = code;
674 NETDATA.errorLast.message = msg;
675 NETDATA.errorLast.datetime = Date.now();
677 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
680 if(typeof netdataErrorCallback === 'function') {
681 ret = netdataErrorCallback('system', code, msg);
684 if(ret && NETDATA.errorCodes[code].alert)
685 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
688 NETDATA.errorReset = function() {
689 NETDATA.errorLast.code = 0;
690 NETDATA.errorLast.message = "You are doing fine!";
691 NETDATA.errorLast.datetime = 0;
694 // ----------------------------------------------------------------------------------------------------------------
695 // commonMin & commonMax
697 NETDATA.commonMin = {
701 get: function(state) {
702 if(typeof state.__commonMin === 'undefined') {
703 // get the commonMin setting
704 var self = $(state.element);
705 state.__commonMin = self.data('common-min') || null;
708 var min = state.data.min;
709 var name = state.__commonMin;
712 // we don't need commonMin
713 //state.log('no need for commonMin');
717 var t = this.keys[name];
718 if(typeof t === 'undefined') {
720 this.keys[name] = {};
724 var uuid = state.uuid;
725 if(typeof t[uuid] !== 'undefined') {
726 if(t[uuid] === min) {
727 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
728 return this.latest[name];
730 else if(min < this.latest[name]) {
731 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
733 this.latest[name] = min;
741 // find the common min
744 if(t[i] < m) m = t[i];
746 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
747 this.latest[name] = m;
752 NETDATA.commonMax = {
756 get: function(state) {
757 if(typeof state.__commonMax === 'undefined') {
758 // get the commonMax setting
759 var self = $(state.element);
760 state.__commonMax = self.data('common-max') || null;
763 var max = state.data.max;
764 var name = state.__commonMax;
767 // we don't need commonMax
768 //state.log('no need for commonMax');
772 var t = this.keys[name];
773 if(typeof t === 'undefined') {
775 this.keys[name] = {};
779 var uuid = state.uuid;
780 if(typeof t[uuid] !== 'undefined') {
781 if(t[uuid] === max) {
782 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
783 return this.latest[name];
785 else if(max > this.latest[name]) {
786 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
788 this.latest[name] = max;
796 // find the common max
799 if(t[i] > m) m = t[i];
801 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
802 this.latest[name] = m;
807 // ----------------------------------------------------------------------------------------------------------------
810 // When multiple charts need the same chart, we avoid downloading it
811 // multiple times (and having it in browser memory multiple time)
812 // by using this registry.
814 // Every time we download a chart definition, we save it here with .add()
815 // Then we try to get it back with .get(). If that fails, we download it.
817 NETDATA.fixHost = function(host) {
818 while(host.slice(-1) === '/')
819 host = host.substring(0, host.length - 1);
824 NETDATA.chartRegistry = {
827 fixid: function(id) {
828 return id.replace(/:/g, "_").replace(/\//g, "_");
831 add: function(host, id, data) {
832 host = this.fixid(host);
835 if(typeof this.charts[host] === 'undefined')
836 this.charts[host] = {};
838 //console.log('added ' + host + '/' + id);
839 this.charts[host][id] = data;
842 get: function(host, id) {
843 host = this.fixid(host);
846 if(typeof this.charts[host] === 'undefined')
849 if(typeof this.charts[host][id] === 'undefined')
852 //console.log('cached ' + host + '/' + id);
853 return this.charts[host][id];
856 downloadAll: function(host, callback) {
857 host = NETDATA.fixHost(host);
862 url: host + '/api/v1/charts',
865 xhrFields: { withCredentials: true } // required for the cookie
867 .done(function(data) {
869 var h = NETDATA.chartRegistry.fixid(host);
870 self.charts[h] = data.charts;
872 else NETDATA.error(406, host + '/api/v1/charts');
874 if(typeof callback === 'function')
875 return callback(data);
878 NETDATA.error(405, host + '/api/v1/charts');
880 if(typeof callback === 'function')
881 return callback(null);
886 // ----------------------------------------------------------------------------------------------------------------
887 // Global Pan and Zoom on charts
889 // Using this structure are synchronize all the charts, so that
890 // when you pan or zoom one, all others are automatically refreshed
891 // to the same timespan.
893 NETDATA.globalPanAndZoom = {
894 seq: 0, // timestamp ms
895 // every time a chart is panned or zoomed
896 // we set the timestamp here
897 // then we use it as a sequence number
898 // to find if other charts are syncronized
901 master: null, // the master chart (state), to which all others
904 force_before_ms: null, // the timespan to sync all other charts
905 force_after_ms: null,
910 setMaster: function(state, after, before) {
911 if(NETDATA.options.current.sync_pan_and_zoom === false)
914 if(this.master !== null && this.master !== state)
915 this.master.resetChart(true, true);
917 var now = Date.now();
920 this.force_after_ms = after;
921 this.force_before_ms = before;
922 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
924 if(typeof this.callback === 'function')
925 this.callback(true, after, before);
929 clearMaster: function() {
930 if(this.master !== null) {
931 var st = this.master;
938 this.force_after_ms = null;
939 this.force_before_ms = null;
940 NETDATA.options.auto_refresher_stop_until = 0;
942 if(typeof this.callback === 'function')
943 this.callback(false, 0, 0);
946 // is the given state the master of the global
947 // pan and zoom sync?
948 isMaster: function(state) {
949 if(this.master === state) return true;
953 // are we currently have a global pan and zoom sync?
954 isActive: function() {
955 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
959 // check if a chart, other than the master
960 // needs to be refreshed, due to the global pan and zoom
961 shouldBeAutoRefreshed: function(state) {
962 if(this.master === null || this.seq === 0)
965 //if(state.needsRecreation())
968 if(state.tm.pan_and_zoom_seq === this.seq)
975 // ----------------------------------------------------------------------------------------------------------------
976 // dimensions selection
979 // move color assignment to dimensions, here
981 dimensionStatus = function(parent, label, name_div, value_div, color) {
982 this.enabled = false;
983 this.parent = parent;
985 this.name_div = null;
986 this.value_div = null;
987 this.color = NETDATA.themes.current.foreground;
989 if(parent.unselected_count === 0)
990 this.selected = true;
992 this.selected = false;
994 this.setOptions(name_div, value_div, color);
997 dimensionStatus.prototype.invalidate = function() {
998 this.name_div = null;
999 this.value_div = null;
1000 this.enabled = false;
1003 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1006 if(this.name_div !== name_div) {
1007 this.name_div = name_div;
1008 this.name_div.title = this.label;
1009 this.name_div.style.color = this.color;
1010 if(this.selected === false)
1011 this.name_div.className = 'netdata-legend-name not-selected';
1013 this.name_div.className = 'netdata-legend-name selected';
1016 if(this.value_div !== value_div) {
1017 this.value_div = value_div;
1018 this.value_div.title = this.label;
1019 this.value_div.style.color = this.color;
1020 if(this.selected === false)
1021 this.value_div.className = 'netdata-legend-value not-selected';
1023 this.value_div.className = 'netdata-legend-value selected';
1026 this.enabled = true;
1030 dimensionStatus.prototype.setHandler = function() {
1031 if(this.enabled === false) return;
1035 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1036 this.name_div.onclick = this.value_div.onclick = function(e) {
1038 if(ds.isSelected()) {
1040 if(e.shiftKey === true || e.ctrlKey === true) {
1041 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1044 if(ds.parent.countSelected() === 0)
1045 ds.parent.selectAll();
1048 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1049 if(ds.parent.countSelected() === 1) {
1050 ds.parent.selectAll();
1053 ds.parent.selectNone();
1059 // this is not selected
1060 if(e.shiftKey === true || e.ctrlKey === true) {
1061 // control or shift key is pressed -> select this too
1065 // no key is pressed -> select only this
1066 ds.parent.selectNone();
1071 ds.parent.state.redrawChart();
1075 dimensionStatus.prototype.select = function() {
1076 if(this.enabled === false) return;
1078 this.name_div.className = 'netdata-legend-name selected';
1079 this.value_div.className = 'netdata-legend-value selected';
1080 this.selected = true;
1083 dimensionStatus.prototype.unselect = function() {
1084 if(this.enabled === false) return;
1086 this.name_div.className = 'netdata-legend-name not-selected';
1087 this.value_div.className = 'netdata-legend-value hidden';
1088 this.selected = false;
1091 dimensionStatus.prototype.isSelected = function() {
1092 return(this.enabled === true && this.selected === true);
1095 // ----------------------------------------------------------------------------------------------------------------
1097 dimensionsVisibility = function(state) {
1100 this.dimensions = {};
1101 this.selected_count = 0;
1102 this.unselected_count = 0;
1105 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1106 if(typeof this.dimensions[label] === 'undefined') {
1108 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1111 this.dimensions[label].setOptions(name_div, value_div, color);
1113 return this.dimensions[label];
1116 dimensionsVisibility.prototype.dimensionGet = function(label) {
1117 return this.dimensions[label];
1120 dimensionsVisibility.prototype.invalidateAll = function() {
1121 var keys = Object.keys(this.dimensions);
1122 var len = keys.length;
1124 this.dimensions[keys[len]].invalidate();
1127 dimensionsVisibility.prototype.selectAll = function() {
1128 var keys = Object.keys(this.dimensions);
1129 var len = keys.length;
1131 this.dimensions[keys[len]].select();
1134 dimensionsVisibility.prototype.countSelected = function() {
1136 var keys = Object.keys(this.dimensions);
1137 var len = keys.length;
1139 if(this.dimensions[keys[len]].isSelected()) selected++;
1144 dimensionsVisibility.prototype.selectNone = function() {
1145 var keys = Object.keys(this.dimensions);
1146 var len = keys.length;
1148 this.dimensions[keys[len]].unselect();
1151 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1152 var ret = new Array();
1153 this.selected_count = 0;
1154 this.unselected_count = 0;
1156 var len = array.length;
1158 var ds = this.dimensions[array[len]];
1159 if(typeof ds === 'undefined') {
1160 // console.log(array[i] + ' is not found');
1163 else if(ds.isSelected()) {
1165 this.selected_count++;
1169 this.unselected_count++;
1173 if(this.selected_count === 0 && this.unselected_count !== 0) {
1175 return this.selected2BooleanArray(array);
1182 // ----------------------------------------------------------------------------------------------------------------
1183 // global selection sync
1185 NETDATA.globalSelectionSync = {
1187 dont_sync_before: 0,
1192 if(this.state !== null)
1193 this.state.globalSelectionSyncStop();
1197 if(this.state !== null) {
1198 this.state.globalSelectionSyncDelay();
1203 // ----------------------------------------------------------------------------------------------------------------
1204 // Our state object, where all per-chart values are stored
1206 chartState = function(element) {
1207 var self = $(element);
1208 this.element = element;
1211 // all private functions should use 'that', instead of 'this'
1214 /* error() - private
1215 * show an error instead of the chart
1217 var error = function(msg) {
1220 if(typeof netdataErrorCallback === 'function') {
1221 ret = netdataErrorCallback('chart', that.id, msg);
1225 that.element.innerHTML = that.id + ': ' + msg;
1226 that.enabled = false;
1227 that.current = that.pan;
1231 // GUID - a unique identifier for the chart
1232 this.uuid = NETDATA.guid();
1234 // string - the name of chart
1235 this.id = self.data('netdata');
1237 // string - the key for localStorage settings
1238 this.settings_id = self.data('id') || null;
1240 // the user given dimensions of the element
1241 this.width = self.data('width') || NETDATA.chartDefaults.width;
1242 this.height = self.data('height') || NETDATA.chartDefaults.height;
1243 this.height_original = this.height;
1245 if(this.settings_id !== null) {
1246 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1247 // this is the callback that will be called
1248 // if and when the user resets all localStorage variables
1249 // to their defaults
1251 resizeChartToHeight(height);
1255 // string - the netdata server URL, without any path
1256 this.host = self.data('host') || NETDATA.chartDefaults.host;
1258 // make sure the host does not end with /
1259 // all netdata API requests use absolute paths
1260 while(this.host.slice(-1) === '/')
1261 this.host = this.host.substring(0, this.host.length - 1);
1263 // string - the grouping method requested by the user
1264 this.method = self.data('method') || NETDATA.chartDefaults.method;
1266 // the time-range requested by the user
1267 this.after = self.data('after') || NETDATA.chartDefaults.after;
1268 this.before = self.data('before') || NETDATA.chartDefaults.before;
1270 // the pixels per point requested by the user
1271 this.pixels_per_point = self.data('pixels-per-point') || 1;
1272 this.points = self.data('points') || null;
1274 // the dimensions requested by the user
1275 this.dimensions = self.data('dimensions') || null;
1277 // the chart library requested by the user
1278 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1280 // how many retries we have made to load chart data from the server
1281 this.retries_on_data_failures = 0;
1283 // object - the chart library used
1284 this.library = null;
1288 this.colors_assigned = {};
1289 this.colors_available = null;
1291 // the element already created by the user
1292 this.element_message = null;
1294 // the element with the chart
1295 this.element_chart = null;
1297 // the element with the legend of the chart (if created by us)
1298 this.element_legend = null;
1299 this.element_legend_childs = {
1304 perfect_scroller: null, // the container to apply perfect scroller to
1308 this.chart_url = null; // string - the url to download chart info
1309 this.chart = null; // object - the chart as downloaded from the server
1311 this.title = self.data('title') || null; // the title of the chart
1312 this.units = self.data('units') || null; // the units of the chart dimensions
1313 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1314 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1316 this.running = false; // boolean - true when the chart is being refreshed now
1317 this.validated = false; // boolean - has the chart been validated?
1318 this.enabled = true; // boolean - is the chart enabled for refresh?
1319 this.paused = false; // boolean - is the chart paused for any reason?
1320 this.selected = false; // boolean - is the chart shown a selection?
1321 this.debug = false; // boolean - console.log() debug info about this chart
1323 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1324 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1325 this.requested_after = null; // milliseconds - the timestamp of the request after param
1326 this.requested_before = null; // milliseconds - the timestamp of the request before param
1327 this.requested_padding = null;
1328 this.view_after = 0;
1329 this.view_before = 0;
1331 this.value_decimal_detail = -1;
1332 var d = self.data('decimal-digits');
1333 if(typeof d === 'number') {
1334 this.value_decimal_detail = 1;
1336 this.value_decimal_detail *= 10;
1342 force_update_at: 0, // the timestamp to force the update at
1343 force_before_ms: null,
1344 force_after_ms: null
1349 force_update_at: 0, // the timestamp to force the update at
1350 force_before_ms: null,
1351 force_after_ms: null
1356 force_update_at: 0, // the timestamp to force the update at
1357 force_before_ms: null,
1358 force_after_ms: null
1361 // this is a pointer to one of the sub-classes below
1363 this.current = this.auto;
1365 // check the requested library is available
1366 // we don't initialize it here - it will be initialized when
1367 // this chart will be first used
1368 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1369 NETDATA.error(402, that.library_name);
1370 error('chart library "' + that.library_name + '" is not found');
1373 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1374 NETDATA.error(403, that.library_name);
1375 error('chart library "' + that.library_name + '" is not enabled');
1379 that.library = NETDATA.chartLibraries[that.library_name];
1381 // milliseconds - the time the last refresh took
1382 this.refresh_dt_ms = 0;
1384 // if we need to report the rendering speed
1385 // find the element that needs to be updated
1386 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1388 if(refresh_dt_element_name !== null)
1389 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1391 this.refresh_dt_element = null;
1393 this.dimensions_visibility = new dimensionsVisibility(this);
1395 this._updating = false;
1397 // ============================================================================================================
1398 // PRIVATE FUNCTIONS
1400 var createDOM = function() {
1401 if(that.enabled === false) return;
1403 if(that.element_message !== null) that.element_message.innerHTML = '';
1404 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1405 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1407 that.element.innerHTML = '';
1409 that.element_message = document.createElement('div');
1410 that.element_message.className = 'netdata-message icon hidden';
1411 that.element.appendChild(that.element_message);
1413 that.element_chart = document.createElement('div');
1414 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1415 that.element.appendChild(that.element_chart);
1417 if(that.hasLegend() === true) {
1418 that.element.className = "netdata-container-with-legend";
1419 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1421 that.element_legend = document.createElement('div');
1422 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1423 that.element.appendChild(that.element_legend);
1426 that.element.className = "netdata-container";
1427 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1429 that.element_legend = null;
1431 that.element_legend_childs.series = null;
1433 if(typeof(that.width) === 'string')
1434 $(that.element).css('width', that.width);
1435 else if(typeof(that.width) === 'number')
1436 $(that.element).css('width', that.width + 'px');
1438 if(typeof(that.library.aspect_ratio) === 'undefined') {
1439 if(typeof(that.height) === 'string')
1440 that.element.style.height = that.height;
1441 else if(typeof(that.height) === 'number')
1442 that.element.style.height = that.height.toString() + 'px';
1445 var w = that.element.offsetWidth;
1446 if(w === null || w === 0) {
1447 // the div is hidden
1448 // this will resize the chart when next viewed
1449 that.tm.last_resized = 0;
1452 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1455 if(NETDATA.chartDefaults.min_width !== null)
1456 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1458 that.tm.last_dom_created = Date.now();
1464 * initialize state variables
1465 * destroy all (possibly) created state elements
1466 * create the basic DOM for a chart
1468 var init = function() {
1469 if(that.enabled === false) return;
1471 that.paused = false;
1472 that.selected = false;
1474 that.chart_created = false; // boolean - is the library.create() been called?
1475 that.updates_counter = 0; // numeric - the number of refreshes made so far
1476 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1477 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1480 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1481 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1482 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1484 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1485 last_updated: 0, // the timestamp the chart last updated with data
1486 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1488 // Used with NETDATA.globalPanAndZoom.seq
1489 last_visible_check: 0, // the time we last checked if it is visible
1490 last_resized: 0, // the time the chart was resized
1491 last_hidden: 0, // the time the chart was hidden
1492 last_unhidden: 0, // the time the chart was unhidden
1493 last_autorefreshed: 0 // the time the chart was last refreshed
1496 that.data = null; // the last data as downloaded from the netdata server
1497 that.data_url = 'invalid://'; // string - the last url used to update the chart
1498 that.data_points = 0; // number - the number of points returned from netdata
1499 that.data_after = 0; // milliseconds - the first timestamp of the data
1500 that.data_before = 0; // milliseconds - the last timestamp of the data
1501 that.data_update_every = 0; // milliseconds - the frequency to update the data
1503 that.tm.last_initialized = Date.now();
1506 that.setMode('auto');
1509 var maxMessageFontSize = function() {
1510 var screenHeight = screen.height;
1511 var el = that.element;
1513 // normally we want a font size, as tall as the element
1514 var h = el.clientHeight;
1516 // but give it some air, 20% let's say, or 5 pixels min
1517 var lost = Math.max(h * 0.2, 5);
1520 // center the text, vertically
1521 var paddingTop = (lost - 5) / 2;
1523 // but check the width too
1524 // it should fit 10 characters in it
1525 var w = el.clientWidth / 10;
1527 paddingTop += (h - w) / 2;
1531 // and don't make it too huge
1532 // 5% of the screen size is good
1533 if(h > screenHeight / 20) {
1534 paddingTop += (h - (screenHeight / 20)) / 2;
1535 h = screenHeight / 20;
1539 that.element_message.style.fontSize = h.toString() + 'px';
1540 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1543 var showMessageIcon = function(icon) {
1544 that.element_message.innerHTML = icon;
1545 maxMessageFontSize();
1546 $(that.element_message).removeClass('hidden');
1547 that.___messageHidden___ = undefined;
1550 var hideMessage = function() {
1551 if(typeof that.___messageHidden___ === 'undefined') {
1552 that.___messageHidden___ = true;
1553 $(that.element_message).addClass('hidden');
1557 var showRendering = function() {
1559 if(that.chart !== null) {
1560 if(that.chart.chart_type === 'line')
1561 icon = '<i class="fa fa-line-chart"></i>';
1563 icon = '<i class="fa fa-area-chart"></i>';
1566 icon = '<i class="fa fa-area-chart"></i>';
1568 showMessageIcon(icon + ' netdata');
1571 var showLoading = function() {
1572 if(that.chart_created === false) {
1573 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1579 var isHidden = function() {
1580 if(typeof that.___chartIsHidden___ !== 'undefined')
1586 // hide the chart, when it is not visible - called from isVisible()
1587 var hideChart = function() {
1588 // hide it, if it is not already hidden
1589 if(isHidden() === true) return;
1591 if(that.chart_created === true) {
1592 if(NETDATA.options.current.destroy_on_hide === true) {
1593 // we should destroy it
1598 that.element_chart.style.display = 'none';
1599 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1600 that.tm.last_hidden = Date.now();
1603 // This works, but I not sure there are no corner cases somewhere
1604 // so it is commented - if the user has memory issues he can
1605 // set Destroy on Hide for all charts
1606 // that.data = null;
1610 that.___chartIsHidden___ = true;
1613 // unhide the chart, when it is visible - called from isVisible()
1614 var unhideChart = function() {
1615 if(isHidden() === false) return;
1617 that.___chartIsHidden___ = undefined;
1618 that.updates_since_last_unhide = 0;
1620 if(that.chart_created === false) {
1621 // we need to re-initialize it, to show our background
1622 // logo in bootstrap tabs, until the chart loads
1626 that.tm.last_unhidden = Date.now();
1627 that.element_chart.style.display = '';
1628 if(that.element_legend !== null) that.element_legend.style.display = '';
1634 var canBeRendered = function() {
1635 if(isHidden() === true || that.isVisible(true) === false)
1641 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1642 var callChartLibraryUpdateSafely = function(data) {
1645 if(canBeRendered() === false)
1648 if(NETDATA.options.debug.chart_errors === true)
1649 status = that.library.update(that, data);
1652 status = that.library.update(that, data);
1659 if(status === false) {
1660 error('chart failed to be updated as ' + that.library_name);
1667 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1668 var callChartLibraryCreateSafely = function(data) {
1671 if(canBeRendered() === false)
1674 if(NETDATA.options.debug.chart_errors === true)
1675 status = that.library.create(that, data);
1678 status = that.library.create(that, data);
1685 if(status === false) {
1686 error('chart failed to be created as ' + that.library_name);
1690 that.chart_created = true;
1691 that.updates_since_last_creation = 0;
1695 // ----------------------------------------------------------------------------------------------------------------
1698 // resizeChart() - private
1699 // to be called just before the chart library to make sure that
1700 // a properly sized dom is available
1701 var resizeChart = function() {
1702 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1703 if(that.chart_created === false) return;
1705 if(that.needsRecreation()) {
1708 else if(typeof that.library.resize === 'function') {
1709 that.library.resize(that);
1711 if(that.element_legend_childs.perfect_scroller !== null)
1712 Ps.update(that.element_legend_childs.perfect_scroller);
1714 maxMessageFontSize();
1717 that.tm.last_resized = Date.now();
1721 // this is the actual chart resize algorithm
1723 // - resize the entire container
1724 // - update the internal states
1725 // - resize the chart as the div changes height
1726 // - update the scrollbar of the legend
1727 var resizeChartToHeight = function(h) {
1729 that.element.style.height = h;
1731 if(that.settings_id !== null)
1732 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1734 var now = Date.now();
1735 NETDATA.options.last_page_scroll = now;
1736 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1739 that.tm.last_resized = 0;
1743 this.resizeHandler = function(e) {
1746 if(typeof this.event_resize === 'undefined'
1747 || this.event_resize.chart_original_w === 'undefined'
1748 || this.event_resize.chart_original_h === 'undefined')
1749 this.event_resize = {
1750 chart_original_w: this.element.clientWidth,
1751 chart_original_h: this.element.clientHeight,
1755 if(e.type === 'touchstart') {
1756 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1757 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1760 this.event_resize.mouse_start_x = e.clientX;
1761 this.event_resize.mouse_start_y = e.clientY;
1764 this.event_resize.chart_start_w = this.element.clientWidth;
1765 this.event_resize.chart_start_h = this.element.clientHeight;
1766 this.event_resize.chart_last_w = this.element.clientWidth;
1767 this.event_resize.chart_last_h = this.element.clientHeight;
1769 var now = Date.now();
1770 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1771 // double click / double tap event
1773 // console.dir(this.element_legend_childs.content);
1774 // console.dir(this.element_legend_childs.perfect_scroller);
1776 // the optimal height of the chart
1777 // showing the entire legend
1778 var optimal = this.event_resize.chart_last_h
1779 + this.element_legend_childs.perfect_scroller.scrollHeight
1780 - this.element_legend_childs.perfect_scroller.clientHeight;
1782 // if we are not optimal, be optimal
1783 if(this.event_resize.chart_last_h !== optimal) {
1784 // 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());
1785 resizeChartToHeight(optimal.toString() + 'px');
1788 // else if the current height is not the original/saved height
1789 // reset to the original/saved height
1790 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1791 // 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());
1792 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1795 // else if the current height is not the internal default height
1796 // reset to the internal default height
1797 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1798 // 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());
1799 resizeChartToHeight(this.height_original.toString());
1802 // else if the current height is not the firstchild's clientheight
1804 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1805 var parent_rect = this.element.getBoundingClientRect();
1806 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1807 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1809 // console.log(parent_rect);
1810 // console.log(content_rect);
1811 // console.log(wanted);
1813 // 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' );
1814 if(this.event_resize.chart_last_h !== wanted)
1815 resizeChartToHeight(wanted.toString() + 'px');
1819 this.event_resize.last = now;
1821 // process movement event
1822 document.onmousemove =
1823 document.ontouchmove =
1824 this.element_legend_childs.resize_handler.onmousemove =
1825 this.element_legend_childs.resize_handler.ontouchmove =
1830 case 'mousemove': y = e.clientY; break;
1831 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1835 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1837 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1838 resizeChartToHeight(newH.toString() + 'px');
1839 that.event_resize.chart_last_h = newH;
1844 // process end event
1845 document.onmouseup =
1846 document.ontouchend =
1847 this.element_legend_childs.resize_handler.onmouseup =
1848 this.element_legend_childs.resize_handler.ontouchend =
1850 // remove all the hooks
1851 document.onmouseup =
1852 document.onmousemove =
1853 document.ontouchmove =
1854 document.ontouchend =
1855 that.element_legend_childs.resize_handler.onmousemove =
1856 that.element_legend_childs.resize_handler.ontouchmove =
1857 that.element_legend_childs.resize_handler.onmouseout =
1858 that.element_legend_childs.resize_handler.onmouseup =
1859 that.element_legend_childs.resize_handler.ontouchend =
1862 // allow auto-refreshes
1863 NETDATA.options.auto_refresher_stop_until = 0;
1869 var noDataToShow = function() {
1870 showMessageIcon('<i class="fa fa-warning"></i> empty');
1871 that.legendUpdateDOM();
1872 that.tm.last_autorefreshed = Date.now();
1873 // that.data_update_every = 30 * 1000;
1874 //that.element_chart.style.display = 'none';
1875 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1876 //that.___chartIsHidden___ = true;
1879 // ============================================================================================================
1882 this.error = function(msg) {
1886 this.setMode = function(m) {
1887 if(this.current !== null && this.current.name === m) return;
1890 this.current = this.auto;
1891 else if(m === 'pan')
1892 this.current = this.pan;
1893 else if(m === 'zoom')
1894 this.current = this.zoom;
1896 this.current = this.auto;
1898 this.current.force_update_at = 0;
1899 this.current.force_before_ms = null;
1900 this.current.force_after_ms = null;
1902 this.tm.last_mode_switch = Date.now();
1905 // ----------------------------------------------------------------------------------------------------------------
1906 // global selection sync
1908 // prevent to global selection sync for some time
1909 this.globalSelectionSyncDelay = function(ms) {
1910 if(NETDATA.options.current.sync_selection === false)
1913 if(typeof ms === 'number')
1914 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1916 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1919 // can we globally apply selection sync?
1920 this.globalSelectionSyncAbility = function() {
1921 if(NETDATA.options.current.sync_selection === false)
1924 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1930 this.globalSelectionSyncIsMaster = function() {
1931 return (NETDATA.globalSelectionSync.state === this);
1934 // this chart is the master of the global selection sync
1935 this.globalSelectionSyncBeMaster = function() {
1937 if(this.globalSelectionSyncIsMaster()) {
1938 if(this.debug === true)
1939 this.log('sync: I am the master already.');
1944 if(NETDATA.globalSelectionSync.state) {
1945 if(this.debug === true)
1946 this.log('sync: I am not the sync master. Resetting global sync.');
1948 this.globalSelectionSyncStop();
1951 // become the master
1952 if(this.debug === true)
1953 this.log('sync: becoming sync master.');
1955 this.selected = true;
1956 NETDATA.globalSelectionSync.state = this;
1958 // find the all slaves
1959 var targets = NETDATA.options.targets;
1960 var len = targets.length;
1965 if(this.debug === true)
1966 st.log('sync: not adding me to sync');
1968 else if(st.globalSelectionSyncIsEligible()) {
1969 if(this.debug === true)
1970 st.log('sync: adding to sync as slave');
1972 st.globalSelectionSyncBeSlave();
1976 // this.globalSelectionSyncDelay(100);
1979 // can the chart participate to the global selection sync as a slave?
1980 this.globalSelectionSyncIsEligible = function() {
1981 if(this.enabled === true
1982 && this.library !== null
1983 && typeof this.library.setSelection === 'function'
1984 && this.isVisible() === true
1985 && this.chart_created === true)
1991 // this chart becomes a slave of the global selection sync
1992 this.globalSelectionSyncBeSlave = function() {
1993 if(NETDATA.globalSelectionSync.state !== this)
1994 NETDATA.globalSelectionSync.slaves.push(this);
1997 // sync all the visible charts to the given time
1998 // this is to be called from the chart libraries
1999 this.globalSelectionSync = function(t) {
2000 if(this.globalSelectionSyncAbility() === false)
2003 if(this.globalSelectionSyncIsMaster() === false) {
2004 if(this.debug === true)
2005 this.log('sync: trying to be sync master.');
2007 this.globalSelectionSyncBeMaster();
2009 if(this.globalSelectionSyncAbility() === false)
2013 NETDATA.globalSelectionSync.last_t = t;
2014 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2019 // stop syncing all charts to the given time
2020 this.globalSelectionSyncStop = function() {
2021 if(NETDATA.globalSelectionSync.slaves.length) {
2022 if(this.debug === true)
2023 this.log('sync: cleaning up...');
2025 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2027 if(that.debug === true)
2028 st.log('sync: not adding me to sync stop');
2031 if(that.debug === true)
2032 st.log('sync: removed slave from sync');
2034 st.clearSelection();
2038 NETDATA.globalSelectionSync.last_t = 0;
2039 NETDATA.globalSelectionSync.slaves = [];
2040 NETDATA.globalSelectionSync.state = null;
2043 this.clearSelection();
2046 this.setSelection = function(t) {
2047 if(typeof this.library.setSelection === 'function') {
2048 if(this.library.setSelection(this, t) === true)
2049 this.selected = true;
2051 this.selected = false;
2053 else this.selected = true;
2055 if(this.selected === true && this.debug === true)
2056 this.log('selection set to ' + t.toString());
2058 return this.selected;
2061 this.clearSelection = function() {
2062 if(this.selected === true) {
2063 if(typeof this.library.clearSelection === 'function') {
2064 if(this.library.clearSelection(this) === true)
2065 this.selected = false;
2067 this.selected = true;
2069 else this.selected = false;
2071 if(this.selected === false && this.debug === true)
2072 this.log('selection cleared');
2077 return this.selected;
2080 // find if a timestamp (ms) is shown in the current chart
2081 this.timeIsVisible = function(t) {
2082 if(t >= this.data_after && t <= this.data_before)
2087 this.calculateRowForTime = function(t) {
2088 if(this.timeIsVisible(t) === false) return -1;
2089 return Math.floor((t - this.data_after) / this.data_update_every);
2092 // ----------------------------------------------------------------------------------------------------------------
2095 this.log = function(msg) {
2096 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2099 this.pauseChart = function() {
2100 if(this.paused === false) {
2101 if(this.debug === true)
2102 this.log('pauseChart()');
2108 this.unpauseChart = function() {
2109 if(this.paused === true) {
2110 if(this.debug === true)
2111 this.log('unpauseChart()');
2113 this.paused = false;
2117 this.resetChart = function(dont_clear_master, dont_update) {
2118 if(this.debug === true)
2119 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2121 if(typeof dont_clear_master === 'undefined')
2122 dont_clear_master = false;
2124 if(typeof dont_update === 'undefined')
2125 dont_update = false;
2127 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2128 if(this.debug === true)
2129 this.log('resetChart() diverting to clearMaster().');
2130 // this will call us back with master === true
2131 NETDATA.globalPanAndZoom.clearMaster();
2135 this.clearSelection();
2137 this.tm.pan_and_zoom_seq = 0;
2139 this.setMode('auto');
2140 this.current.force_update_at = 0;
2141 this.current.force_before_ms = null;
2142 this.current.force_after_ms = null;
2143 this.tm.last_autorefreshed = 0;
2144 this.paused = false;
2145 this.selected = false;
2146 this.enabled = true;
2147 // this.debug = false;
2149 // do not update the chart here
2150 // or the chart will flip-flop when it is the master
2151 // of a selection sync and another chart becomes
2154 if(dont_update !== true && this.isVisible() === true) {
2159 this.updateChartPanOrZoom = function(after, before) {
2160 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2163 if(this.debug === true)
2166 if(before < after) {
2167 if(this.debug === true)
2168 this.log(logme + 'flipped parameters, rejecting it.');
2173 if(typeof this.fixed_min_duration === 'undefined')
2174 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2176 var min_duration = this.fixed_min_duration;
2177 var current_duration = Math.round(this.view_before - this.view_after);
2179 // round the numbers
2180 after = Math.round(after);
2181 before = Math.round(before);
2183 // align them to update_every
2184 // stretching them further away
2185 after -= after % this.data_update_every;
2186 before += this.data_update_every - (before % this.data_update_every);
2188 // the final wanted duration
2189 var wanted_duration = before - after;
2191 // to allow panning, accept just a point below our minimum
2192 if((current_duration - this.data_update_every) < min_duration)
2193 min_duration = current_duration - this.data_update_every;
2195 // we do it, but we adjust to minimum size and return false
2196 // when the wanted size is below the current and the minimum
2198 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2199 if(this.debug === true)
2200 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2202 min_duration = this.fixed_min_duration;
2204 var dt = (min_duration - wanted_duration) / 2;
2207 wanted_duration = before - after;
2211 var tolerance = this.data_update_every * 2;
2212 var movement = Math.abs(before - this.view_before);
2214 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2215 if(this.debug === true)
2216 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);
2220 if(this.current.name === 'auto') {
2221 this.log(logme + 'caller called me with mode: ' + this.current.name);
2222 this.setMode('pan');
2225 if(this.debug === true)
2226 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);
2228 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2229 this.current.force_after_ms = after;
2230 this.current.force_before_ms = before;
2231 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2235 this.legendFormatValue = function(value) {
2236 if(value === null || value === 'undefined') return '-';
2237 if(typeof value !== 'number') return value;
2239 if(this.value_decimal_detail !== -1)
2240 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2242 var abs = Math.abs(value);
2243 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2244 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2245 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2246 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2247 return (Math.round(value * 10000) / 10000).toLocaleString();
2250 this.legendSetLabelValue = function(label, value) {
2251 var series = this.element_legend_childs.series[label];
2252 if(typeof series === 'undefined') return;
2253 if(series.value === null && series.user === null) return;
2256 // this slows down firefox and edge significantly
2257 // since it requires to use innerHTML(), instead of innerText()
2259 // if the value has not changed, skip DOM update
2260 //if(series.last === value) return;
2263 if(typeof value === 'number') {
2264 var v = Math.abs(value);
2265 s = r = this.legendFormatValue(value);
2267 if(typeof series.last === 'number') {
2268 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2269 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2270 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2272 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2282 series.last = value;
2286 var s = this.legendFormatValue(value);
2288 // caching: do not update the update to show the same value again
2289 if(s === series.last_shown_value) return;
2290 series.last_shown_value = s;
2292 if(series.value !== null) series.value.innerText = s;
2293 if(series.user !== null) series.user.innerText = s;
2296 this.__legendSetDateString = function(date) {
2297 if(date !== this.__last_shown_legend_date) {
2298 this.element_legend_childs.title_date.innerText = date;
2299 this.__last_shown_legend_date = date;
2303 this.__legendSetTimeString = function(time) {
2304 if(time !== this.__last_shown_legend_time) {
2305 this.element_legend_childs.title_time.innerText = time;
2306 this.__last_shown_legend_time = time;
2310 this.__legendSetUnitsString = function(units) {
2311 if(units !== this.__last_shown_legend_units) {
2312 this.element_legend_childs.title_units.innerText = units;
2313 this.__last_shown_legend_units = units;
2317 this.legendSetDate = function(ms) {
2318 if(typeof ms !== 'number') {
2319 this.legendShowUndefined();
2323 var d = new Date(ms);
2325 if(this.element_legend_childs.title_date)
2326 this.__legendSetDateString(d.toLocaleDateString());
2328 if(this.element_legend_childs.title_time)
2329 this.__legendSetTimeString(d.toLocaleTimeString());
2331 if(this.element_legend_childs.title_units)
2332 this.__legendSetUnitsString(this.units)
2335 this.legendShowUndefined = function() {
2336 if(this.element_legend_childs.title_date)
2337 this.__legendSetDateString(' ');
2339 if(this.element_legend_childs.title_time)
2340 this.__legendSetTimeString(this.chart.name);
2342 if(this.element_legend_childs.title_units)
2343 this.__legendSetUnitsString(' ')
2345 if(this.data && this.element_legend_childs.series !== null) {
2346 var labels = this.data.dimension_names;
2347 var i = labels.length;
2349 var label = labels[i];
2351 if(typeof label === 'undefined') continue;
2352 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2353 this.legendSetLabelValue(label, null);
2358 this.legendShowLatestValues = function() {
2359 if(this.chart === null) return;
2360 if(this.selected) return;
2362 if(this.data === null || this.element_legend_childs.series === null) {
2363 this.legendShowUndefined();
2367 var show_undefined = true;
2368 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2369 show_undefined = false;
2371 if(show_undefined) {
2372 this.legendShowUndefined();
2376 this.legendSetDate(this.view_before);
2378 var labels = this.data.dimension_names;
2379 var i = labels.length;
2381 var label = labels[i];
2383 if(typeof label === 'undefined') continue;
2384 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2387 this.legendSetLabelValue(label, null);
2389 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2393 this.legendReset = function() {
2394 this.legendShowLatestValues();
2397 // this should be called just ONCE per dimension per chart
2398 this._chartDimensionColor = function(label) {
2399 if(this.colors === null) this.chartColors();
2401 if(typeof this.colors_assigned[label] === 'undefined') {
2402 if(this.colors_available.length === 0) {
2403 var len = NETDATA.themes.current.colors.length;
2405 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2408 this.colors_assigned[label] = this.colors_available.shift();
2410 if(this.debug === true)
2411 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2414 if(this.debug === true)
2415 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2418 this.colors.push(this.colors_assigned[label]);
2419 return this.colors_assigned[label];
2422 this.chartColors = function() {
2423 if(this.colors !== null) return this.colors;
2425 this.colors = new Array();
2426 this.colors_available = new Array();
2428 // add the standard colors
2429 var len = NETDATA.themes.current.colors.length;
2431 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2433 // add the user supplied colors
2434 var c = $(this.element).data('colors');
2435 // this.log('read colors: ' + c);
2436 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2437 if(typeof c !== 'string') {
2438 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2448 this.colors_available.unshift(c[len]);
2449 // this.log('adding color: ' + c[len]);
2458 this.legendUpdateDOM = function() {
2459 var needed = false, dim, keys, len, i;
2461 // check that the legend DOM is up to date for the downloaded dimensions
2462 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2463 // this.log('the legend does not have any series - requesting legend update');
2466 else if(this.data === null) {
2467 // this.log('the chart does not have any data - requesting legend update');
2470 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2474 var labels = this.data.dimension_names.toString();
2475 if(labels !== this.element_legend_childs.series.labels_key) {
2478 if(this.debug === true)
2479 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2483 if(needed === false) {
2484 // make sure colors available
2487 // do we have to update the current values?
2488 // we do this, only when the visible chart is current
2489 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2490 if(this.debug === true)
2491 this.log('chart is in latest position... updating values on legend...');
2493 //var labels = this.data.dimension_names;
2494 //var i = labels.length;
2496 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2500 if(this.colors === null) {
2501 // this is the first time we update the chart
2502 // let's assign colors to all dimensions
2503 if(this.library.track_colors() === true) {
2504 keys = Object.keys(this.chart.dimensions);
2506 for(i = 0; i < len ;i++)
2507 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2510 // we will re-generate the colors for the chart
2511 // based on the selected dimensions
2514 if(this.debug === true)
2515 this.log('updating Legend DOM');
2517 // mark all dimensions as invalid
2518 this.dimensions_visibility.invalidateAll();
2520 var genLabel = function(state, parent, dim, name, count) {
2521 var color = state._chartDimensionColor(name);
2523 var user_element = null;
2524 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2525 if(user_id === null)
2526 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2527 if(user_id !== null) {
2528 user_element = document.getElementById(user_id) || null;
2529 if (user_element === null)
2530 state.log('Cannot find element with id: ' + user_id);
2533 state.element_legend_childs.series[name] = {
2534 name: document.createElement('span'),
2535 value: document.createElement('span'),
2538 last_shown_value: null
2541 var label = state.element_legend_childs.series[name];
2543 // create the dimension visibility tracking for this label
2544 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2546 var rgb = NETDATA.colorHex2Rgb(color);
2547 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2548 + state.chart.chart_type
2549 + '" style="background-color: '
2550 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2551 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2553 var text = document.createTextNode(' ' + name);
2554 label.name.appendChild(text);
2557 parent.appendChild(document.createElement('br'));
2559 parent.appendChild(label.name);
2560 parent.appendChild(label.value);
2563 var content = document.createElement('div');
2565 if(this.hasLegend()) {
2566 this.element_legend_childs = {
2568 resize_handler: document.createElement('div'),
2569 toolbox: document.createElement('div'),
2570 toolbox_left: document.createElement('div'),
2571 toolbox_right: document.createElement('div'),
2572 toolbox_reset: document.createElement('div'),
2573 toolbox_zoomin: document.createElement('div'),
2574 toolbox_zoomout: document.createElement('div'),
2575 toolbox_volume: document.createElement('div'),
2576 title_date: document.createElement('span'),
2577 title_time: document.createElement('span'),
2578 title_units: document.createElement('span'),
2579 perfect_scroller: document.createElement('div'),
2583 this.element_legend.innerHTML = '';
2585 if(this.library.toolboxPanAndZoom !== null) {
2587 var get_pan_and_zoom_step = function(event) {
2589 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2591 else if (event.shiftKey)
2592 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2594 else if (event.altKey)
2595 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2598 return NETDATA.options.current.pan_and_zoom_factor;
2601 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2602 this.element.appendChild(this.element_legend_childs.toolbox);
2604 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2605 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2606 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2607 this.element_legend_childs.toolbox_left.onclick = function(e) {
2610 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2611 var before = that.view_before - step;
2612 var after = that.view_after - step;
2613 if(after >= that.netdata_first)
2614 that.library.toolboxPanAndZoom(that, after, before);
2616 if(NETDATA.options.current.show_help === true)
2617 $(this.element_legend_childs.toolbox_left).popover({
2622 placement: 'bottom',
2623 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2625 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>'
2629 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2630 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2631 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2632 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2634 NETDATA.resetAllCharts(that);
2636 if(NETDATA.options.current.show_help === true)
2637 $(this.element_legend_childs.toolbox_reset).popover({
2642 placement: 'bottom',
2643 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2644 title: 'Chart Reset',
2645 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>'
2648 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2649 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2650 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2651 this.element_legend_childs.toolbox_right.onclick = function(e) {
2653 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2654 var before = that.view_before + step;
2655 var after = that.view_after + step;
2656 if(before <= that.netdata_last)
2657 that.library.toolboxPanAndZoom(that, after, before);
2659 if(NETDATA.options.current.show_help === true)
2660 $(this.element_legend_childs.toolbox_right).popover({
2665 placement: 'bottom',
2666 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2668 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>'
2672 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2673 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2674 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2675 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2677 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2678 var before = that.view_before - dt;
2679 var after = that.view_after + dt;
2680 that.library.toolboxPanAndZoom(that, after, before);
2682 if(NETDATA.options.current.show_help === true)
2683 $(this.element_legend_childs.toolbox_zoomin).popover({
2688 placement: 'bottom',
2689 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2690 title: 'Chart Zoom In',
2691 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>'
2694 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2695 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2696 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2697 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2699 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);
2700 var before = that.view_before + dt;
2701 var after = that.view_after - dt;
2703 that.library.toolboxPanAndZoom(that, after, before);
2705 if(NETDATA.options.current.show_help === true)
2706 $(this.element_legend_childs.toolbox_zoomout).popover({
2711 placement: 'bottom',
2712 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2713 title: 'Chart Zoom Out',
2714 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>'
2717 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2718 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2719 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2720 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2721 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2722 //e.preventDefault();
2723 //alert('clicked toolbox_volume on ' + that.id);
2727 this.element_legend_childs.toolbox = null;
2728 this.element_legend_childs.toolbox_left = null;
2729 this.element_legend_childs.toolbox_reset = null;
2730 this.element_legend_childs.toolbox_right = null;
2731 this.element_legend_childs.toolbox_zoomin = null;
2732 this.element_legend_childs.toolbox_zoomout = null;
2733 this.element_legend_childs.toolbox_volume = null;
2736 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2737 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2738 this.element.appendChild(this.element_legend_childs.resize_handler);
2739 if(NETDATA.options.current.show_help === true)
2740 $(this.element_legend_childs.resize_handler).popover({
2745 placement: 'bottom',
2746 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2747 title: 'Chart Resize',
2748 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>'
2752 this.element_legend_childs.resize_handler.onmousedown =
2754 that.resizeHandler(e);
2758 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2759 that.resizeHandler(e);
2762 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2763 this.element_legend.appendChild(this.element_legend_childs.title_date);
2765 this.element_legend.appendChild(document.createElement('br'));
2767 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2768 this.element_legend.appendChild(this.element_legend_childs.title_time);
2770 this.element_legend.appendChild(document.createElement('br'));
2772 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2773 this.element_legend.appendChild(this.element_legend_childs.title_units);
2775 this.element_legend.appendChild(document.createElement('br'));
2777 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2778 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2780 content.className = 'netdata-legend-series-content';
2781 this.element_legend_childs.perfect_scroller.appendChild(content);
2783 if(NETDATA.options.current.show_help === true)
2784 $(content).popover({
2789 placement: 'bottom',
2790 title: 'Chart Legend',
2791 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2792 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>'
2796 this.element_legend_childs = {
2798 resize_handler: null,
2801 toolbox_right: null,
2802 toolbox_reset: null,
2803 toolbox_zoomin: null,
2804 toolbox_zoomout: null,
2805 toolbox_volume: null,
2809 perfect_scroller: null,
2815 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2816 if(this.debug === true)
2817 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2819 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2820 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2824 var tmp = new Array();
2825 keys = Object.keys(this.chart.dimensions);
2826 for(i = 0, len = keys.length; i < len ;i++) {
2828 tmp.push(this.chart.dimensions[dim].name);
2829 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2831 this.element_legend_childs.series.labels_key = tmp.toString();
2832 if(this.debug === true)
2833 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2836 // create a hidden div to be used for hidding
2837 // the original legend of the chart library
2838 var el = document.createElement('div');
2839 if(this.element_legend !== null)
2840 this.element_legend.appendChild(el);
2841 el.style.display = 'none';
2843 this.element_legend_childs.hidden = document.createElement('div');
2844 el.appendChild(this.element_legend_childs.hidden);
2846 if(this.element_legend_childs.perfect_scroller !== null) {
2847 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2849 wheelPropagation: true,
2850 swipePropagation: true,
2851 minScrollbarLength: null,
2852 maxScrollbarLength: null,
2853 useBothWheelAxes: false,
2854 suppressScrollX: true,
2855 suppressScrollY: false,
2856 scrollXMarginOffset: 0,
2857 scrollYMarginOffset: 0,
2860 Ps.update(this.element_legend_childs.perfect_scroller);
2863 this.legendShowLatestValues();
2866 this.hasLegend = function() {
2867 if(typeof this.___hasLegendCache___ !== 'undefined')
2868 return this.___hasLegendCache___;
2871 if(this.library && this.library.legend(this) === 'right-side') {
2872 var legend = $(this.element).data('legend') || 'yes';
2873 if(legend === 'yes') leg = true;
2876 this.___hasLegendCache___ = leg;
2880 this.legendWidth = function() {
2881 return (this.hasLegend())?140:0;
2884 this.legendHeight = function() {
2885 return $(this.element).height();
2888 this.chartWidth = function() {
2889 return $(this.element).width() - this.legendWidth();
2892 this.chartHeight = function() {
2893 return $(this.element).height();
2896 this.chartPixelsPerPoint = function() {
2897 // force an options provided detail
2898 var px = this.pixels_per_point;
2900 if(this.library && px < this.library.pixels_per_point(this))
2901 px = this.library.pixels_per_point(this);
2903 if(px < NETDATA.options.current.pixels_per_point)
2904 px = NETDATA.options.current.pixels_per_point;
2909 this.needsRecreation = function() {
2911 this.chart_created === true
2913 && this.library.autoresize() === false
2914 && this.tm.last_resized < NETDATA.options.last_resized
2918 this.chartURL = function() {
2919 var after, before, points_multiplier = 1;
2920 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2921 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2923 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2924 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2925 this.view_after = after * 1000;
2926 this.view_before = before * 1000;
2928 this.requested_padding = null;
2929 points_multiplier = 1;
2931 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2932 this.tm.pan_and_zoom_seq = 0;
2934 before = Math.round(this.current.force_before_ms / 1000);
2935 after = Math.round(this.current.force_after_ms / 1000);
2936 this.view_after = after * 1000;
2937 this.view_before = before * 1000;
2939 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2940 this.requested_padding = Math.round((before - after) / 2);
2941 after -= this.requested_padding;
2942 before += this.requested_padding;
2943 this.requested_padding *= 1000;
2944 points_multiplier = 2;
2947 this.current.force_before_ms = null;
2948 this.current.force_after_ms = null;
2951 this.tm.pan_and_zoom_seq = 0;
2953 before = this.before;
2955 this.view_after = after * 1000;
2956 this.view_before = before * 1000;
2958 this.requested_padding = null;
2959 points_multiplier = 1;
2962 this.requested_after = after * 1000;
2963 this.requested_before = before * 1000;
2965 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2967 // build the data URL
2968 this.data_url = this.host + this.chart.data_url;
2969 this.data_url += "&format=" + this.library.format();
2970 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2971 this.data_url += "&group=" + this.method;
2973 if(this.override_options !== null)
2974 this.data_url += "&options=" + this.override_options.toString();
2976 this.data_url += "&options=" + this.library.options(this);
2978 this.data_url += '|jsonwrap';
2980 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2981 this.data_url += '|nonzero';
2983 if(this.append_options !== null)
2984 this.data_url += '|' + this.append_options.toString();
2987 this.data_url += "&after=" + after.toString();
2990 this.data_url += "&before=" + before.toString();
2993 this.data_url += "&dimensions=" + this.dimensions;
2995 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2996 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2999 this.redrawChart = function() {
3000 if(this.data !== null)
3001 this.updateChartWithData(this.data);
3004 this.updateChartWithData = function(data) {
3005 if(this.debug === true)
3006 this.log('updateChartWithData() called.');
3008 // this may force the chart to be re-created
3012 this.updates_counter++;
3013 this.updates_since_last_unhide++;
3014 this.updates_since_last_creation++;
3016 var started = Date.now();
3018 // if the result is JSON, find the latest update-every
3019 this.data_update_every = data.view_update_every * 1000;
3020 this.data_after = data.after * 1000;
3021 this.data_before = data.before * 1000;
3022 this.netdata_first = data.first_entry * 1000;
3023 this.netdata_last = data.last_entry * 1000;
3024 this.data_points = data.points;
3027 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3028 if(this.view_after < this.data_after) {
3029 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3030 this.view_after = this.data_after;
3033 if(this.view_before > this.data_before) {
3034 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3035 this.view_before = this.data_before;
3039 this.view_after = this.data_after;
3040 this.view_before = this.data_before;
3043 if(this.debug === true) {
3044 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3046 if(this.current.force_after_ms)
3047 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3049 this.log('STATUS: forced : unset');
3051 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3052 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3053 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3054 this.log('STATUS: points : ' + (this.data_points).toString());
3057 if(this.data_points === 0) {
3062 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3063 if(this.debug === true)
3064 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3066 this.chart_created = false;
3069 // check and update the legend
3070 this.legendUpdateDOM();
3072 if(this.chart_created === true
3073 && typeof this.library.update === 'function') {
3075 if(this.debug === true)
3076 this.log('updating chart...');
3078 if(callChartLibraryUpdateSafely(data) === false)
3082 if(this.debug === true)
3083 this.log('creating chart...');
3085 if(callChartLibraryCreateSafely(data) === false)
3089 this.legendShowLatestValues();
3090 if(this.selected === true)
3091 NETDATA.globalSelectionSync.stop();
3093 // update the performance counters
3094 var now = Date.now();
3095 this.tm.last_updated = now;
3097 // don't update last_autorefreshed if this chart is
3098 // forced to be updated with global PanAndZoom
3099 if(NETDATA.globalPanAndZoom.isActive())
3100 this.tm.last_autorefreshed = 0;
3102 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3103 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3105 this.tm.last_autorefreshed = now;
3108 this.refresh_dt_ms = now - started;
3109 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3111 if(this.refresh_dt_element !== null)
3112 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3115 this.updateChart = function(callback) {
3116 if(this.debug === true)
3117 this.log('updateChart() called.');
3119 if(this._updating === true) {
3120 if(this.debug === true)
3121 this.log('I am already updating...');
3123 if(typeof callback === 'function')
3129 // due to late initialization of charts and libraries
3130 // we need to check this too
3131 if(this.enabled === false) {
3132 if(this.debug === true)
3133 this.log('I am not enabled');
3135 if(typeof callback === 'function')
3141 if(canBeRendered() === false) {
3142 if(typeof callback === 'function')
3148 if(this.chart === null)
3149 return this.getChart(function() {
3150 return that.updateChart(callback);
3153 if(this.library.initialized === false) {
3154 if(this.library.enabled === true) {
3155 return this.library.initialize(function () {
3156 return that.updateChart(callback);
3160 error('chart library "' + this.library_name + '" is not available.');
3162 if(typeof callback === 'function')
3169 this.clearSelection();
3172 if(this.debug === true)
3173 this.log('updating from ' + this.data_url);
3175 NETDATA.statistics.refreshes_total++;
3176 NETDATA.statistics.refreshes_active++;
3178 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3179 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3181 this._updating = true;
3183 this.xhr = $.ajax( {
3188 'Cache-Control': 'no-cache, no-store',
3189 'Pragma': 'no-cache'
3191 xhrFields: { withCredentials: true } // required for the cookie
3193 .done(function(data) {
3194 that.xhr = undefined;
3195 that.retries_on_data_failures = 0;
3197 if(that.debug === true)
3198 that.log('data received. updating chart.');
3200 that.updateChartWithData(data);
3202 .fail(function(msg) {
3203 that.xhr = undefined;
3205 if(msg.statusText !== 'abort') {
3206 that.retries_on_data_failures++;
3207 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3208 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3209 that.retries_on_data_failures = 0;
3210 error('data download failed for url: ' + that.data_url);
3213 that.tm.last_autorefreshed = Date.now();
3214 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3218 .always(function() {
3219 that.xhr = undefined;
3221 NETDATA.statistics.refreshes_active--;
3222 that._updating = false;
3224 if(typeof callback === 'function')
3229 this.isVisible = function(nocache) {
3230 if(typeof nocache === 'undefined')
3233 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3235 // caching - we do not evaluate the charts visibility
3236 // if the page has not been scrolled since the last check
3237 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3238 return this.___isVisible___;
3240 this.tm.last_visible_check = Date.now();
3242 var wh = window.innerHeight;
3243 var x = this.element.getBoundingClientRect();
3247 if(x.width === 0 || x.height === 0) {
3249 this.___isVisible___ = false;
3250 return this.___isVisible___;
3253 if(x.top < 0 && -x.top > x.height) {
3254 // the chart is entirely above
3255 ret = -x.top - x.height;
3257 else if(x.top > wh) {
3258 // the chart is entirely below
3262 if(ret > tolerance) {
3263 // the chart is too far
3266 this.___isVisible___ = false;
3267 return this.___isVisible___;
3270 // the chart is inside or very close
3273 this.___isVisible___ = true;
3274 return this.___isVisible___;
3278 this.isAutoRefreshable = function() {
3279 return (this.current.autorefresh);
3282 this.canBeAutoRefreshed = function() {
3283 var now = Date.now();
3285 if(this.running === true) {
3286 if(this.debug === true)
3287 this.log('I am already running');
3292 if(this.enabled === false) {
3293 if(this.debug === true)
3294 this.log('I am not enabled');
3299 if(this.library === null || this.library.enabled === false) {
3300 error('charting library "' + this.library_name + '" is not available');
3301 if(this.debug === true)
3302 this.log('My chart library ' + this.library_name + ' is not available');
3307 if(this.isVisible() === false) {
3308 if(NETDATA.options.debug.visibility === true || this.debug === true)
3309 this.log('I am not visible');
3314 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3315 if(this.debug === true)
3316 this.log('timed force update detected - allowing this update');
3318 this.current.force_update_at = 0;
3322 if(this.isAutoRefreshable() === true) {
3323 // allow the first update, even if the page is not visible
3324 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3325 if(NETDATA.options.debug.focus === true || this.debug === true)
3326 this.log('canBeAutoRefreshed(): page does not have focus');
3331 if(this.needsRecreation() === true) {
3332 if(this.debug === true)
3333 this.log('canBeAutoRefreshed(): needs re-creation.');
3338 // options valid only for autoRefresh()
3339 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3340 if(NETDATA.globalPanAndZoom.isActive()) {
3341 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3342 if(this.debug === true)
3343 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3348 if(this.debug === true)
3349 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3355 if(this.selected === true) {
3356 if(this.debug === true)
3357 this.log('canBeAutoRefreshed(): I have a selection in place.');
3362 if(this.paused === true) {
3363 if(this.debug === true)
3364 this.log('canBeAutoRefreshed(): I am paused.');
3369 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3370 if(this.debug === true)
3371 this.log('canBeAutoRefreshed(): It is time to update me.');
3381 this.autoRefresh = function(callback) {
3382 if(this.canBeAutoRefreshed() === true && this.running === false) {
3385 state.running = true;
3386 state.updateChart(function() {
3387 state.running = false;
3389 if(typeof callback !== 'undefined')
3394 if(typeof callback !== 'undefined')
3399 this._defaultsFromDownloadedChart = function(chart) {
3401 this.chart_url = chart.url;
3402 this.data_update_every = chart.update_every * 1000;
3403 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3404 this.tm.last_info_downloaded = Date.now();
3406 if(this.title === null)
3407 this.title = chart.title;
3409 if(this.units === null)
3410 this.units = chart.units;
3413 // fetch the chart description from the netdata server
3414 this.getChart = function(callback) {
3415 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3417 this._defaultsFromDownloadedChart(this.chart);
3419 if(typeof callback === 'function')
3423 this.chart_url = "/api/v1/chart?chart=" + this.id;
3425 if(this.debug === true)
3426 this.log('downloading ' + this.chart_url);
3429 url: this.host + this.chart_url,
3432 xhrFields: { withCredentials: true } // required for the cookie
3434 .done(function(chart) {
3435 chart.url = that.chart_url;
3436 that._defaultsFromDownloadedChart(chart);
3437 NETDATA.chartRegistry.add(that.host, that.id, chart);
3440 NETDATA.error(404, that.chart_url);
3441 error('chart not found on url "' + that.chart_url + '"');
3443 .always(function() {
3444 if(typeof callback === 'function')
3450 // ============================================================================================================
3456 NETDATA.resetAllCharts = function(state) {
3457 // first clear the global selection sync
3458 // to make sure no chart is in selected state
3459 state.globalSelectionSyncStop();
3461 // there are 2 possibilities here
3462 // a. state is the global Pan and Zoom master
3463 // b. state is not the global Pan and Zoom master
3465 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3468 // clear the global Pan and Zoom
3469 // this will also refresh the master
3470 // and unblock any charts currently mirroring the master
3471 NETDATA.globalPanAndZoom.clearMaster();
3473 // if we were not the master, reset our status too
3474 // this is required because most probably the mouse
3475 // is over this chart, blocking it from auto-refreshing
3476 if(master === false && (state.paused === true || state.selected === true))
3480 // get or create a chart state, given a DOM element
3481 NETDATA.chartState = function(element) {
3482 var state = $(element).data('netdata-state-object') || null;
3483 if(state === null) {
3484 state = new chartState(element);
3485 $(element).data('netdata-state-object', state);
3490 // ----------------------------------------------------------------------------------------------------------------
3491 // Library functions
3493 // Load a script without jquery
3494 // This is used to load jquery - after it is loaded, we use jquery
3495 NETDATA._loadjQuery = function(callback) {
3496 if(typeof jQuery === 'undefined') {
3497 if(NETDATA.options.debug.main_loop === true)
3498 console.log('loading ' + NETDATA.jQuery);
3500 var script = document.createElement('script');
3501 script.type = 'text/javascript';
3502 script.async = true;
3503 script.src = NETDATA.jQuery;
3505 // script.onabort = onError;
3506 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3507 if(typeof callback === "function")
3508 script.onload = callback;
3510 var s = document.getElementsByTagName('script')[0];
3511 s.parentNode.insertBefore(script, s);
3513 else if(typeof callback === "function")
3517 NETDATA._loadCSS = function(filename) {
3518 // don't use jQuery here
3519 // styles are loaded before jQuery
3520 // to eliminate showing an unstyled page to the user
3522 var fileref = document.createElement("link");
3523 fileref.setAttribute("rel", "stylesheet");
3524 fileref.setAttribute("type", "text/css");
3525 fileref.setAttribute("href", filename);
3527 if (typeof fileref !== 'undefined')
3528 document.getElementsByTagName("head")[0].appendChild(fileref);
3531 NETDATA.colorHex2Rgb = function(hex) {
3532 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3533 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3534 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3535 return r + r + g + g + b + b;
3538 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3540 r: parseInt(result[1], 16),
3541 g: parseInt(result[2], 16),
3542 b: parseInt(result[3], 16)
3546 NETDATA.colorLuminance = function(hex, lum) {
3547 // validate hex string
3548 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3550 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3554 // convert to decimal and change luminosity
3555 var rgb = "#", c, i;
3556 for (i = 0; i < 3; i++) {
3557 c = parseInt(hex.substr(i*2,2), 16);
3558 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3559 rgb += ("00"+c).substr(c.length);
3565 NETDATA.guid = function() {
3567 return Math.floor((1 + Math.random()) * 0x10000)
3572 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3575 NETDATA.zeropad = function(x) {
3576 if(x > -10 && x < 10) return '0' + x.toString();
3577 else return x.toString();
3580 // user function to signal us the DOM has been
3582 NETDATA.updatedDom = function() {
3583 NETDATA.options.updated_dom = true;
3586 NETDATA.ready = function(callback) {
3587 NETDATA.options.pauseCallback = callback;
3590 NETDATA.pause = function(callback) {
3591 if(typeof callback === 'function') {
3592 if (NETDATA.options.pause === true)
3595 NETDATA.options.pauseCallback = callback;
3599 NETDATA.unpause = function() {
3600 NETDATA.options.pauseCallback = null;
3601 NETDATA.options.updated_dom = true;
3602 NETDATA.options.pause = false;
3605 // ----------------------------------------------------------------------------------------------------------------
3607 // this is purely sequencial charts refresher
3608 // it is meant to be autonomous
3609 NETDATA.chartRefresherNoParallel = function(index) {
3610 if(NETDATA.options.debug.mail_loop === true)
3611 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3613 if(NETDATA.options.updated_dom === true) {
3614 // the dom has been updated
3615 // get the dom parts again
3616 NETDATA.parseDom(NETDATA.chartRefresher);
3619 if(index >= NETDATA.options.targets.length) {
3620 if(NETDATA.options.debug.main_loop === true)
3621 console.log('waiting to restart main loop...');
3623 NETDATA.options.auto_refresher_fast_weight = 0;
3625 setTimeout(function() {
3626 NETDATA.chartRefresher();
3627 }, NETDATA.options.current.idle_between_loops);
3630 var state = NETDATA.options.targets[index];
3632 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3633 if(NETDATA.options.debug.main_loop === true)
3634 console.log('fast rendering...');
3636 state.autoRefresh(function() {
3637 NETDATA.chartRefresherNoParallel(++index);
3641 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3642 NETDATA.options.auto_refresher_fast_weight = 0;
3644 setTimeout(function() {
3645 state.autoRefresh(function() {
3646 NETDATA.chartRefresherNoParallel(++index);
3648 }, NETDATA.options.current.idle_between_charts);
3653 // this is part of the parallel refresher
3654 // its cause is to refresh sequencially all the charts
3655 // that depend on chart library initialization
3656 // it will call the parallel refresher back
3657 // as soon as it sees a chart that its chart library
3659 NETDATA.chartRefresher_uninitialized = function() {
3660 if(NETDATA.options.updated_dom === true) {
3661 // the dom has been updated
3662 // get the dom parts again
3663 NETDATA.parseDom(NETDATA.chartRefresher);
3667 if(NETDATA.options.sequencial.length === 0)
3668 NETDATA.chartRefresher();
3670 var state = NETDATA.options.sequencial.pop();
3671 if(state.library.initialized === true)
3672 NETDATA.chartRefresher();
3674 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3678 NETDATA.chartRefresherWaitTime = function() {
3679 return NETDATA.options.current.idle_parallel_loops;
3682 // the default refresher
3683 // it will create 2 sets of charts:
3684 // - the ones that can be refreshed in parallel
3685 // - the ones that depend on something else
3686 // the first set will be executed in parallel
3687 // the second will be given to NETDATA.chartRefresher_uninitialized()
3688 NETDATA.chartRefresher = function() {
3689 // console.log('auto-refresher...');
3691 if(NETDATA.options.pause === true) {
3692 // console.log('auto-refresher is paused');
3693 setTimeout(NETDATA.chartRefresher,
3694 NETDATA.chartRefresherWaitTime());
3698 if(typeof NETDATA.options.pauseCallback === 'function') {
3699 // console.log('auto-refresher is calling pauseCallback');
3700 NETDATA.options.pause = true;
3701 NETDATA.options.pauseCallback();
3702 NETDATA.chartRefresher();
3706 if(NETDATA.options.current.parallel_refresher === false) {
3707 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3708 NETDATA.chartRefresherNoParallel(0);
3712 if(NETDATA.options.updated_dom === true) {
3713 // the dom has been updated
3714 // get the dom parts again
3715 // console.log('auto-refresher is calling parseDom()');
3716 NETDATA.parseDom(NETDATA.chartRefresher);
3720 var parallel = new Array();
3721 var targets = NETDATA.options.targets;
3722 var len = targets.length;
3725 state = targets[len];
3726 if(state.isVisible() === false || state.running === true)
3729 if(state.library.initialized === false) {
3730 if(state.library.enabled === true) {
3731 state.library.initialize(NETDATA.chartRefresher);
3735 state.error('chart library "' + state.library_name + '" is not enabled.');
3739 parallel.unshift(state);
3742 if(parallel.length > 0) {
3743 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3744 // this will execute the jobs in parallel
3745 $(parallel).each(function() {
3750 // console.log('auto-refresher nothing to do');
3753 // run the next refresh iteration
3754 setTimeout(NETDATA.chartRefresher,
3755 NETDATA.chartRefresherWaitTime());
3758 NETDATA.parseDom = function(callback) {
3759 NETDATA.options.last_page_scroll = Date.now();
3760 NETDATA.options.updated_dom = false;
3762 var targets = $('div[data-netdata]'); //.filter(':visible');
3764 if(NETDATA.options.debug.main_loop === true)
3765 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3767 NETDATA.options.targets = new Array();
3768 var len = targets.length;
3770 // the initialization will take care of sizing
3771 // and the "loading..." message
3772 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3775 if(typeof callback === 'function')
3779 // this is the main function - where everything starts
3780 NETDATA.start = function() {
3781 // this should be called only once
3783 NETDATA.options.page_is_visible = true;
3785 $(window).blur(function() {
3786 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3787 NETDATA.options.page_is_visible = false;
3788 if(NETDATA.options.debug.focus === true)
3789 console.log('Lost Focus!');
3793 $(window).focus(function() {
3794 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3795 NETDATA.options.page_is_visible = true;
3796 if(NETDATA.options.debug.focus === true)
3797 console.log('Focus restored!');
3801 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3802 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3803 NETDATA.options.page_is_visible = false;
3804 if(NETDATA.options.debug.focus === true)
3805 console.log('Document has no focus!');
3809 // bootstrap tab switching
3810 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3812 // bootstrap modal switching
3813 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3814 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3816 // bootstrap collapse switching
3817 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3818 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3820 NETDATA.parseDom(NETDATA.chartRefresher);
3822 // Alarms initialization
3823 setTimeout(NETDATA.alarms.init, 1000);
3825 // Registry initialization
3826 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3828 if(typeof netdataCallback === 'function')
3832 // ----------------------------------------------------------------------------------------------------------------
3835 NETDATA.peityInitialize = function(callback) {
3836 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3838 url: NETDATA.peity_js,
3841 xhrFields: { withCredentials: true } // required for the cookie
3844 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3847 NETDATA.chartLibraries.peity.enabled = false;
3848 NETDATA.error(100, NETDATA.peity_js);
3850 .always(function() {
3851 if(typeof callback === "function")
3856 NETDATA.chartLibraries.peity.enabled = false;
3857 if(typeof callback === "function")
3862 NETDATA.peityChartUpdate = function(state, data) {
3863 state.peity_instance.innerHTML = data.result;
3865 if(state.peity_options.stroke !== state.chartColors()[0]) {
3866 state.peity_options.stroke = state.chartColors()[0];
3867 if(state.chart.chart_type === 'line')
3868 state.peity_options.fill = NETDATA.themes.current.background;
3870 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3873 $(state.peity_instance).peity('line', state.peity_options);
3877 NETDATA.peityChartCreate = function(state, data) {
3878 state.peity_instance = document.createElement('div');
3879 state.element_chart.appendChild(state.peity_instance);
3881 var self = $(state.element);
3882 state.peity_options = {
3883 stroke: NETDATA.themes.current.foreground,
3884 strokeWidth: self.data('peity-strokewidth') || 1,
3885 width: state.chartWidth(),
3886 height: state.chartHeight(),
3887 fill: NETDATA.themes.current.foreground
3890 NETDATA.peityChartUpdate(state, data);
3894 // ----------------------------------------------------------------------------------------------------------------
3897 NETDATA.sparklineInitialize = function(callback) {
3898 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3900 url: NETDATA.sparkline_js,
3903 xhrFields: { withCredentials: true } // required for the cookie
3906 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3909 NETDATA.chartLibraries.sparkline.enabled = false;
3910 NETDATA.error(100, NETDATA.sparkline_js);
3912 .always(function() {
3913 if(typeof callback === "function")
3918 NETDATA.chartLibraries.sparkline.enabled = false;
3919 if(typeof callback === "function")
3924 NETDATA.sparklineChartUpdate = function(state, data) {
3925 state.sparkline_options.width = state.chartWidth();
3926 state.sparkline_options.height = state.chartHeight();
3928 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3932 NETDATA.sparklineChartCreate = function(state, data) {
3933 var self = $(state.element);
3934 var type = self.data('sparkline-type') || 'line';
3935 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3936 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3937 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3938 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3939 var composite = self.data('sparkline-composite') || undefined;
3940 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3941 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3942 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3943 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3944 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3945 var spotColor = self.data('sparkline-spotcolor') || undefined;
3946 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3947 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3948 var spotRadius = self.data('sparkline-spotradius') || undefined;
3949 var valueSpots = self.data('sparkline-valuespots') || undefined;
3950 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3951 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3952 var lineWidth = self.data('sparkline-linewidth') || undefined;
3953 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3954 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3955 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3956 var xvalues = self.data('sparkline-xvalues') || undefined;
3957 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3958 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3959 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3960 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3961 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3962 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3963 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3964 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3965 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3966 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3967 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3968 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3969 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3970 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3971 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3972 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3973 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3974 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3975 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3976 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3977 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3978 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3980 if(spotColor === 'disable') spotColor='';
3981 if(minSpotColor === 'disable') minSpotColor='';
3982 if(maxSpotColor === 'disable') maxSpotColor='';
3984 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3986 state.sparkline_options = {
3988 lineColor: lineColor,
3989 fillColor: fillColor,
3990 chartRangeMin: chartRangeMin,
3991 chartRangeMax: chartRangeMax,
3992 composite: composite,
3993 enableTagOptions: enableTagOptions,
3994 tagOptionPrefix: tagOptionPrefix,
3995 tagValuesAttribute: tagValuesAttribute,
3996 disableHiddenCheck: disableHiddenCheck,
3997 defaultPixelsPerValue: defaultPixelsPerValue,
3998 spotColor: spotColor,
3999 minSpotColor: minSpotColor,
4000 maxSpotColor: maxSpotColor,
4001 spotRadius: spotRadius,
4002 valueSpots: valueSpots,
4003 highlightSpotColor: highlightSpotColor,
4004 highlightLineColor: highlightLineColor,
4005 lineWidth: lineWidth,
4006 normalRangeMin: normalRangeMin,
4007 normalRangeMax: normalRangeMax,
4008 drawNormalOnTop: drawNormalOnTop,
4010 chartRangeClip: chartRangeClip,
4011 chartRangeMinX: chartRangeMinX,
4012 chartRangeMaxX: chartRangeMaxX,
4013 disableInteraction: disableInteraction,
4014 disableTooltips: disableTooltips,
4015 disableHighlight: disableHighlight,
4016 highlightLighten: highlightLighten,
4017 highlightColor: highlightColor,
4018 tooltipContainer: tooltipContainer,
4019 tooltipClassname: tooltipClassname,
4020 tooltipChartTitle: state.title,
4021 tooltipFormat: tooltipFormat,
4022 tooltipPrefix: tooltipPrefix,
4023 tooltipSuffix: tooltipSuffix,
4024 tooltipSkipNull: tooltipSkipNull,
4025 tooltipValueLookups: tooltipValueLookups,
4026 tooltipFormatFieldlist: tooltipFormatFieldlist,
4027 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
4028 numberFormatter: numberFormatter,
4029 numberDigitGroupSep: numberDigitGroupSep,
4030 numberDecimalMark: numberDecimalMark,
4031 numberDigitGroupCount: numberDigitGroupCount,
4032 animatedZooms: animatedZooms,
4033 width: state.chartWidth(),
4034 height: state.chartHeight()
4037 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4041 // ----------------------------------------------------------------------------------------------------------------
4048 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4049 if(after < state.netdata_first)
4050 after = state.netdata_first;
4052 if(before > state.netdata_last)
4053 before = state.netdata_last;
4055 state.setMode('zoom');
4056 state.globalSelectionSyncStop();
4057 state.globalSelectionSyncDelay();
4058 state.dygraph_user_action = true;
4059 state.dygraph_force_zoom = true;
4060 state.updateChartPanOrZoom(after, before);
4061 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4064 NETDATA.dygraphSetSelection = function(state, t) {
4065 if(typeof state.dygraph_instance !== 'undefined') {
4066 var r = state.calculateRowForTime(t);
4068 state.dygraph_instance.setSelection(r);
4070 state.dygraph_instance.clearSelection();
4071 state.legendShowUndefined();
4078 NETDATA.dygraphClearSelection = function(state, t) {
4079 if(typeof state.dygraph_instance !== 'undefined') {
4080 state.dygraph_instance.clearSelection();
4085 NETDATA.dygraphSmoothInitialize = function(callback) {
4087 url: NETDATA.dygraph_smooth_js,
4090 xhrFields: { withCredentials: true } // required for the cookie
4093 NETDATA.dygraph.smooth = true;
4094 smoothPlotter.smoothing = 0.3;
4097 NETDATA.dygraph.smooth = false;
4099 .always(function() {
4100 if(typeof callback === "function")
4105 NETDATA.dygraphInitialize = function(callback) {
4106 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4108 url: NETDATA.dygraph_js,
4111 xhrFields: { withCredentials: true } // required for the cookie
4114 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4117 NETDATA.chartLibraries.dygraph.enabled = false;
4118 NETDATA.error(100, NETDATA.dygraph_js);
4120 .always(function() {
4121 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4122 NETDATA.dygraphSmoothInitialize(callback);
4123 else if(typeof callback === "function")
4128 NETDATA.chartLibraries.dygraph.enabled = false;
4129 if(typeof callback === "function")
4134 NETDATA.dygraphChartUpdate = function(state, data) {
4135 var dygraph = state.dygraph_instance;
4137 if(typeof dygraph === 'undefined')
4138 return NETDATA.dygraphChartCreate(state, data);
4140 // when the chart is not visible, and hidden
4141 // if there is a window resize, dygraph detects
4142 // its element size as 0x0.
4143 // this will make it re-appear properly
4145 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4149 file: data.result.data,
4150 colors: state.chartColors(),
4151 labels: data.result.labels,
4152 labelsDivWidth: state.chartWidth() - 70,
4153 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4156 if(state.dygraph_force_zoom === true) {
4157 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4158 state.log('dygraphChartUpdate() forced zoom update');
4160 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4161 options.isZoomedIgnoreProgrammaticZoom = true;
4162 state.dygraph_force_zoom = false;
4164 else if(state.current.name !== 'auto') {
4165 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4166 state.log('dygraphChartUpdate() loose update');
4169 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4170 state.log('dygraphChartUpdate() strict update');
4172 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4173 options.isZoomedIgnoreProgrammaticZoom = true;
4176 options.valueRange = state.dygraph_options.valueRange;
4178 var oldMax = null, oldMin = null;
4179 if(state.__commonMin !== null) {
4180 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4181 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4183 if(state.__commonMax !== null) {
4184 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4185 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4188 if(state.dygraph_smooth_eligible === true) {
4189 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4190 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4191 NETDATA.dygraphChartCreate(state, data);
4196 dygraph.updateOptions(options);
4199 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4200 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4201 options.valueRange[0] = NETDATA.commonMin.get(state);
4204 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4205 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4206 options.valueRange[1] = NETDATA.commonMax.get(state);
4210 if(redraw === true) {
4211 // state.log('forcing redraw to adapt to common- min/max');
4212 dygraph.updateOptions(options);
4215 state.dygraph_last_rendered = Date.now();
4219 NETDATA.dygraphChartCreate = function(state, data) {
4220 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4221 state.log('dygraphChartCreate()');
4223 var self = $(state.element);
4225 var chart_type = state.chart.chart_type;
4226 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4227 chart_type = self.data('dygraph-type') || chart_type;
4229 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4230 smooth = self.data('dygraph-smooth') || smooth;
4232 if(NETDATA.dygraph.smooth === false)
4235 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4236 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4238 state.dygraph_options = {
4239 colors: self.data('dygraph-colors') || state.chartColors(),
4241 // leave a few pixels empty on the right of the chart
4242 rightGap: self.data('dygraph-rightgap') || 5,
4243 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4244 showRoller: self.data('dygraph-showroller') || false,
4246 title: self.data('dygraph-title') || state.title,
4247 titleHeight: self.data('dygraph-titleheight') || 19,
4249 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4250 labels: data.result.labels,
4251 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4252 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4253 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4254 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4255 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4258 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4259 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4261 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4262 xRangePad: self.data('dygraph-xrangepad') || 0,
4263 yRangePad: self.data('dygraph-yrangepad') || 1,
4265 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4267 ylabel: state.units,
4268 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4270 // the function to plot the chart
4273 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4274 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4275 strokePattern: self.data('dygraph-strokepattern') || undefined,
4277 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4278 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4279 drawPoints: self.data('dygraph-drawpoints') || false,
4281 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4282 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4284 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4285 pointSize: self.data('dygraph-pointsize') || 1,
4287 // enabling this makes the chart with little square lines
4288 stepPlot: self.data('dygraph-stepplot') || false,
4290 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4291 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4292 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4294 fillGraph: self.data('dygraph-fillgraph') || ((chart_type === 'area' || chart_type === 'stacked')?true:false),
4295 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4296 stackedGraph: self.data('dygraph-stackedgraph') || ((chart_type === 'stacked')?true:false),
4297 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4299 drawAxis: self.data('dygraph-drawaxis') || true,
4300 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4301 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4302 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4304 drawGrid: self.data('dygraph-drawgrid') || true,
4305 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4306 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4307 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4309 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4310 sigFigs: self.data('dygraph-sigfigs') || null,
4311 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4312 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4314 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4315 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4316 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4318 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4319 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4323 ticker: Dygraph.dateTicker,
4324 axisLabelFormatter: function (d, gran) {
4325 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4327 valueFormatter: function (ms) {
4328 //var d = new Date(ms);
4329 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4331 // no need to return anything here
4338 valueFormatter: function (x) {
4339 // we format legends with the state object
4340 // no need to do anything here
4341 // return (Math.round(x*100) / 100).toLocaleString();
4342 // return state.legendFormatValue(x);
4347 legendFormatter: function(data) {
4348 var elements = state.element_legend_childs;
4350 // if the hidden div is not there
4351 // we are not managing the legend
4352 if(elements.hidden === null) return;
4354 if (typeof data.x !== 'undefined') {
4355 state.legendSetDate(data.x);
4356 var i = data.series.length;
4358 var series = data.series[i];
4359 if(series.isVisible === true)
4360 state.legendSetLabelValue(series.label, series.y);
4362 state.legendSetLabelValue(series.label, null);
4368 drawCallback: function(dygraph, is_initial) {
4369 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4370 state.dygraph_user_action = false;
4372 var x_range = dygraph.xAxisRange();
4373 var after = Math.round(x_range[0]);
4374 var before = Math.round(x_range[1]);
4376 if(NETDATA.options.debug.dygraph === true)
4377 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4379 if(before <= state.netdata_last && after >= state.netdata_first)
4380 state.updateChartPanOrZoom(after, before);
4383 zoomCallback: function(minDate, maxDate, yRanges) {
4384 if(NETDATA.options.debug.dygraph === true)
4385 state.log('dygraphZoomCallback()');
4387 state.globalSelectionSyncStop();
4388 state.globalSelectionSyncDelay();
4389 state.setMode('zoom');
4391 // refresh it to the greatest possible zoom level
4392 state.dygraph_user_action = true;
4393 state.dygraph_force_zoom = true;
4394 state.updateChartPanOrZoom(minDate, maxDate);
4396 highlightCallback: function(event, x, points, row, seriesName) {
4397 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4398 state.log('dygraphHighlightCallback()');
4402 // there is a bug in dygraph when the chart is zoomed enough
4403 // the time it thinks is selected is wrong
4404 // here we calculate the time t based on the row number selected
4406 var t = state.data_after + row * state.data_update_every;
4407 // 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);
4409 state.globalSelectionSync(x);
4411 // fix legend zIndex using the internal structures of dygraph legend module
4412 // this works, but it is a hack!
4413 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4415 unhighlightCallback: function(event) {
4416 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4417 state.log('dygraphUnhighlightCallback()');
4419 state.unpauseChart();
4420 state.globalSelectionSyncStop();
4422 interactionModel : {
4423 mousedown: function(event, dygraph, context) {
4424 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4425 state.log('interactionModel.mousedown()');
4427 state.dygraph_user_action = true;
4428 state.globalSelectionSyncStop();
4430 if(NETDATA.options.debug.dygraph === true)
4431 state.log('dygraphMouseDown()');
4433 // Right-click should not initiate a zoom.
4434 if(event.button && event.button === 2) return;
4436 context.initializeMouseDown(event, dygraph, context);
4438 if(event.button && event.button === 1) {
4439 if (event.altKey || event.shiftKey) {
4440 state.setMode('pan');
4441 state.globalSelectionSyncDelay();
4442 Dygraph.startPan(event, dygraph, context);
4445 state.setMode('zoom');
4446 state.globalSelectionSyncDelay();
4447 Dygraph.startZoom(event, dygraph, context);
4451 if (event.altKey || event.shiftKey) {
4452 state.setMode('zoom');
4453 state.globalSelectionSyncDelay();
4454 Dygraph.startZoom(event, dygraph, context);
4457 state.setMode('pan');
4458 state.globalSelectionSyncDelay();
4459 Dygraph.startPan(event, dygraph, context);
4463 mousemove: function(event, dygraph, context) {
4464 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4465 state.log('interactionModel.mousemove()');
4467 if(context.isPanning) {
4468 state.dygraph_user_action = true;
4469 state.globalSelectionSyncStop();
4470 state.globalSelectionSyncDelay();
4471 state.setMode('pan');
4472 context.is2DPan = false;
4473 Dygraph.movePan(event, dygraph, context);
4475 else if(context.isZooming) {
4476 state.dygraph_user_action = true;
4477 state.globalSelectionSyncStop();
4478 state.globalSelectionSyncDelay();
4479 state.setMode('zoom');
4480 Dygraph.moveZoom(event, dygraph, context);
4483 mouseup: function(event, dygraph, context) {
4484 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4485 state.log('interactionModel.mouseup()');
4487 if (context.isPanning) {
4488 state.dygraph_user_action = true;
4489 state.globalSelectionSyncDelay();
4490 Dygraph.endPan(event, dygraph, context);
4492 else if (context.isZooming) {
4493 state.dygraph_user_action = true;
4494 state.globalSelectionSyncDelay();
4495 Dygraph.endZoom(event, dygraph, context);
4498 click: function(event, dygraph, context) {
4499 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500 state.log('interactionModel.click()');
4502 event.preventDefault();
4504 dblclick: function(event, dygraph, context) {
4505 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4506 state.log('interactionModel.dblclick()');
4507 NETDATA.resetAllCharts(state);
4509 wheel: function(event, dygraph, context) {
4510 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4511 state.log('interactionModel.wheel()');
4513 // Take the offset of a mouse event on the dygraph canvas and
4514 // convert it to a pair of percentages from the bottom left.
4515 // (Not top left, bottom is where the lower value is.)
4516 function offsetToPercentage(g, offsetX, offsetY) {
4517 // This is calculating the pixel offset of the leftmost date.
4518 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4519 var yar0 = g.yAxisRange(0);
4521 // This is calculating the pixel of the higest value. (Top pixel)
4522 var yOffset = g.toDomCoords(null, yar0[1])[1];
4524 // x y w and h are relative to the corner of the drawing area,
4525 // so that the upper corner of the drawing area is (0, 0).
4526 var x = offsetX - xOffset;
4527 var y = offsetY - yOffset;
4529 // This is computing the rightmost pixel, effectively defining the
4531 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4533 // This is computing the lowest pixel, effectively defining the height.
4534 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4536 // Percentage from the left.
4537 var xPct = w === 0 ? 0 : (x / w);
4538 // Percentage from the top.
4539 var yPct = h === 0 ? 0 : (y / h);
4541 // The (1-) part below changes it from "% distance down from the top"
4542 // to "% distance up from the bottom".
4543 return [xPct, (1-yPct)];
4546 // Adjusts [x, y] toward each other by zoomInPercentage%
4547 // Split it so the left/bottom axis gets xBias/yBias of that change and
4548 // tight/top gets (1-xBias)/(1-yBias) of that change.
4550 // If a bias is missing it splits it down the middle.
4551 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4552 xBias = xBias || 0.5;
4553 yBias = yBias || 0.5;
4555 function adjustAxis(axis, zoomInPercentage, bias) {
4556 var delta = axis[1] - axis[0];
4557 var increment = delta * zoomInPercentage;
4558 var foo = [increment * bias, increment * (1-bias)];
4560 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4563 var yAxes = g.yAxisRanges();
4565 for (var i = 0; i < yAxes.length; i++) {
4566 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4569 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4572 if(event.altKey || event.shiftKey) {
4573 state.dygraph_user_action = true;
4575 state.globalSelectionSyncStop();
4576 state.globalSelectionSyncDelay();
4578 // http://dygraphs.com/gallery/interaction-api.js
4580 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4582 normal_def = event.wheelDelta / 40;
4585 normal_def = event.deltaY * -1.2;
4587 var normal = (event.detail) ? event.detail * -1 : normal_def;
4588 var percentage = normal / 50;
4590 if (!(event.offsetX && event.offsetY)){
4591 event.offsetX = event.layerX - event.target.offsetLeft;
4592 event.offsetY = event.layerY - event.target.offsetTop;
4595 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4596 var xPct = percentages[0];
4597 var yPct = percentages[1];
4599 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4600 var after = new_x_range[0];
4601 var before = new_x_range[1];
4603 var first = state.netdata_first + state.data_update_every;
4604 var last = state.netdata_last + state.data_update_every;
4607 after -= (before - last);
4614 state.setMode('zoom');
4615 if(state.updateChartPanOrZoom(after, before) === true)
4616 dygraph.updateOptions({ dateWindow: [ after, before ] });
4618 event.preventDefault();
4621 touchstart: function(event, dygraph, context) {
4622 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4623 state.log('interactionModel.touchstart()');
4625 state.dygraph_user_action = true;
4626 state.setMode('zoom');
4629 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4631 // we overwrite the touch directions at the end, to overwrite
4632 // the internal default of dygraphs
4633 context.touchDirections = { x: true, y: false };
4635 state.dygraph_last_touch_start = Date.now();
4636 state.dygraph_last_touch_move = 0;
4638 if(typeof event.touches[0].pageX === 'number')
4639 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4641 state.dygraph_last_touch_page_x = 0;
4643 touchmove: function(event, dygraph, context) {
4644 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4645 state.log('interactionModel.touchmove()');
4647 state.dygraph_user_action = true;
4648 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4650 state.dygraph_last_touch_move = Date.now();
4652 touchend: function(event, dygraph, context) {
4653 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4654 state.log('interactionModel.touchend()');
4656 state.dygraph_user_action = true;
4657 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4659 // if it didn't move, it is a selection
4660 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4661 // internal api of dygraphs
4662 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4663 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4664 if(NETDATA.dygraphSetSelection(state, t) === true)
4665 state.globalSelectionSync(t);
4668 // if it was double tap within double click time, reset the charts
4669 var now = Date.now();
4670 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4671 if(state.dygraph_last_touch_move === 0) {
4672 var dt = now - state.dygraph_last_touch_end;
4673 if(dt <= NETDATA.options.current.double_click_speed)
4674 NETDATA.resetAllCharts(state);
4678 // remember the timestamp of the last touch end
4679 state.dygraph_last_touch_end = now;
4684 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4685 state.dygraph_options.drawGrid = false;
4686 state.dygraph_options.drawAxis = false;
4687 state.dygraph_options.title = undefined;
4688 state.dygraph_options.ylabel = undefined;
4689 state.dygraph_options.yLabelWidth = 0;
4690 state.dygraph_options.labelsDivWidth = 120;
4691 state.dygraph_options.labelsDivStyles.width = '120px';
4692 state.dygraph_options.labelsSeparateLines = true;
4693 state.dygraph_options.rightGap = 0;
4694 state.dygraph_options.yRangePad = 1;
4697 if(smooth === true) {
4698 state.dygraph_smooth_eligible = true;
4700 if(NETDATA.options.current.smooth_plot === true)
4701 state.dygraph_options.plotter = smoothPlotter;
4703 else state.dygraph_smooth_eligible = false;
4705 state.dygraph_instance = new Dygraph(state.element_chart,
4706 data.result.data, state.dygraph_options);
4708 state.dygraph_force_zoom = false;
4709 state.dygraph_user_action = false;
4710 state.dygraph_last_rendered = Date.now();
4712 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4713 state.__commonMin = self.data('common-min') || null;
4714 state.__commonMax = self.data('common-max') || null;
4717 state.log('incompatible version of dygraphs detected');
4718 state.__commonMin = null;
4719 state.__commonMax = null;
4725 // ----------------------------------------------------------------------------------------------------------------
4728 NETDATA.morrisInitialize = function(callback) {
4729 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4731 // morris requires raphael
4732 if(!NETDATA.chartLibraries.raphael.initialized) {
4733 if(NETDATA.chartLibraries.raphael.enabled) {
4734 NETDATA.raphaelInitialize(function() {
4735 NETDATA.morrisInitialize(callback);
4739 NETDATA.chartLibraries.morris.enabled = false;
4740 if(typeof callback === "function")
4745 NETDATA._loadCSS(NETDATA.morris_css);
4748 url: NETDATA.morris_js,
4751 xhrFields: { withCredentials: true } // required for the cookie
4754 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4757 NETDATA.chartLibraries.morris.enabled = false;
4758 NETDATA.error(100, NETDATA.morris_js);
4760 .always(function() {
4761 if(typeof callback === "function")
4767 NETDATA.chartLibraries.morris.enabled = false;
4768 if(typeof callback === "function")
4773 NETDATA.morrisChartUpdate = function(state, data) {
4774 state.morris_instance.setData(data.result.data);
4778 NETDATA.morrisChartCreate = function(state, data) {
4780 state.morris_options = {
4781 element: state.element_chart.id,
4782 data: data.result.data,
4784 ykeys: data.dimension_names,
4785 labels: data.dimension_names,
4791 continuousLine: false,
4792 behaveLikeLine: false
4795 if(state.chart.chart_type === 'line')
4796 state.morris_instance = new Morris.Line(state.morris_options);
4798 else if(state.chart.chart_type === 'area') {
4799 state.morris_options.behaveLikeLine = true;
4800 state.morris_instance = new Morris.Area(state.morris_options);
4803 state.morris_instance = new Morris.Area(state.morris_options);
4808 // ----------------------------------------------------------------------------------------------------------------
4811 NETDATA.raphaelInitialize = function(callback) {
4812 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4814 url: NETDATA.raphael_js,
4817 xhrFields: { withCredentials: true } // required for the cookie
4820 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4823 NETDATA.chartLibraries.raphael.enabled = false;
4824 NETDATA.error(100, NETDATA.raphael_js);
4826 .always(function() {
4827 if(typeof callback === "function")
4832 NETDATA.chartLibraries.raphael.enabled = false;
4833 if(typeof callback === "function")
4838 NETDATA.raphaelChartUpdate = function(state, data) {
4839 $(state.element_chart).raphael(data.result, {
4840 width: state.chartWidth(),
4841 height: state.chartHeight()
4847 NETDATA.raphaelChartCreate = function(state, data) {
4848 $(state.element_chart).raphael(data.result, {
4849 width: state.chartWidth(),
4850 height: state.chartHeight()
4856 // ----------------------------------------------------------------------------------------------------------------
4859 NETDATA.c3Initialize = function(callback) {
4860 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4863 if(!NETDATA.chartLibraries.d3.initialized) {
4864 if(NETDATA.chartLibraries.d3.enabled) {
4865 NETDATA.d3Initialize(function() {
4866 NETDATA.c3Initialize(callback);
4870 NETDATA.chartLibraries.c3.enabled = false;
4871 if(typeof callback === "function")
4876 NETDATA._loadCSS(NETDATA.c3_css);
4882 xhrFields: { withCredentials: true } // required for the cookie
4885 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4888 NETDATA.chartLibraries.c3.enabled = false;
4889 NETDATA.error(100, NETDATA.c3_js);
4891 .always(function() {
4892 if(typeof callback === "function")
4898 NETDATA.chartLibraries.c3.enabled = false;
4899 if(typeof callback === "function")
4904 NETDATA.c3ChartUpdate = function(state, data) {
4905 state.c3_instance.destroy();
4906 return NETDATA.c3ChartCreate(state, data);
4908 //state.c3_instance.load({
4909 // rows: data.result,
4916 NETDATA.c3ChartCreate = function(state, data) {
4918 state.element_chart.id = 'c3-' + state.uuid;
4919 // console.log('id = ' + state.element_chart.id);
4921 state.c3_instance = c3.generate({
4922 bindto: '#' + state.element_chart.id,
4924 width: state.chartWidth(),
4925 height: state.chartHeight()
4928 pattern: state.chartColors()
4933 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4939 format: function(x) {
4940 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4967 // console.log(state.c3_instance);
4972 // ----------------------------------------------------------------------------------------------------------------
4975 NETDATA.d3Initialize = function(callback) {
4976 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4981 xhrFields: { withCredentials: true } // required for the cookie
4984 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4987 NETDATA.chartLibraries.d3.enabled = false;
4988 NETDATA.error(100, NETDATA.d3_js);
4990 .always(function() {
4991 if(typeof callback === "function")
4996 NETDATA.chartLibraries.d3.enabled = false;
4997 if(typeof callback === "function")
5002 NETDATA.d3ChartUpdate = function(state, data) {
5006 NETDATA.d3ChartCreate = function(state, data) {
5010 // ----------------------------------------------------------------------------------------------------------------
5013 NETDATA.googleInitialize = function(callback) {
5014 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5016 url: NETDATA.google_js,
5019 xhrFields: { withCredentials: true } // required for the cookie
5022 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5023 google.load('visualization', '1.1', {
5024 'packages': ['corechart', 'controls'],
5025 'callback': callback
5029 NETDATA.chartLibraries.google.enabled = false;
5030 NETDATA.error(100, NETDATA.google_js);
5031 if(typeof callback === "function")
5036 NETDATA.chartLibraries.google.enabled = false;
5037 if(typeof callback === "function")
5042 NETDATA.googleChartUpdate = function(state, data) {
5043 var datatable = new google.visualization.DataTable(data.result);
5044 state.google_instance.draw(datatable, state.google_options);
5048 NETDATA.googleChartCreate = function(state, data) {
5049 var datatable = new google.visualization.DataTable(data.result);
5051 state.google_options = {
5052 colors: state.chartColors(),
5054 // do not set width, height - the chart resizes itself
5055 //width: state.chartWidth(),
5056 //height: state.chartHeight(),
5061 // title: "Time of Day",
5062 // format:'HH:mm:ss',
5063 viewWindowMode: 'maximized',
5075 viewWindowMode: 'pretty',
5090 focusTarget: 'category',
5097 titlePosition: 'out',
5108 curveType: 'function',
5113 switch(state.chart.chart_type) {
5115 state.google_options.vAxis.viewWindowMode = 'maximized';
5116 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5117 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5121 state.google_options.isStacked = true;
5122 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5123 state.google_options.vAxis.viewWindowMode = 'maximized';
5124 state.google_options.vAxis.minValue = null;
5125 state.google_options.vAxis.maxValue = null;
5126 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5131 state.google_options.lineWidth = 2;
5132 state.google_instance = new google.visualization.LineChart(state.element_chart);
5136 state.google_instance.draw(datatable, state.google_options);
5140 // ----------------------------------------------------------------------------------------------------------------
5142 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5143 if(typeof value !== 'number') value = 0;
5144 if(typeof min !== 'number') min = 0;
5145 if(typeof max !== 'number') max = 0;
5147 if(min > value) min = value;
5148 if(max < value) max = value;
5150 // make sure it is zero based
5151 if(min > 0) min = 0;
5152 if(max < 0) max = 0;
5157 pcent = Math.round(value * 100 / max);
5158 if(pcent === 0) pcent = 0.1;
5162 pcent = Math.round(-value * 100 / min);
5163 if(pcent === 0) pcent = -0.1;
5169 // ----------------------------------------------------------------------------------------------------------------
5172 NETDATA.easypiechartInitialize = function(callback) {
5173 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5175 url: NETDATA.easypiechart_js,
5178 xhrFields: { withCredentials: true } // required for the cookie
5181 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5184 NETDATA.chartLibraries.easypiechart.enabled = false;
5185 NETDATA.error(100, NETDATA.easypiechart_js);
5187 .always(function() {
5188 if(typeof callback === "function")
5193 NETDATA.chartLibraries.easypiechart.enabled = false;
5194 if(typeof callback === "function")
5199 NETDATA.easypiechartClearSelection = function(state) {
5200 if(typeof state.easyPieChartEvent !== 'undefined') {
5201 if(state.easyPieChartEvent.timer !== null)
5202 clearTimeout(state.easyPieChartEvent.timer);
5204 state.easyPieChartEvent.timer = null;
5207 if(state.isAutoRefreshable() === true && state.data !== null) {
5208 NETDATA.easypiechartChartUpdate(state, state.data);
5211 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5212 state.easyPieChart_instance.update(0);
5214 state.easyPieChart_instance.enableAnimation();
5219 NETDATA.easypiechartSetSelection = function(state, t) {
5220 if(state.timeIsVisible(t) !== true)
5221 return NETDATA.easypiechartClearSelection(state);
5223 var slot = state.calculateRowForTime(t);
5224 if(slot < 0 || slot >= state.data.result.length)
5225 return NETDATA.easypiechartClearSelection(state);
5227 if(typeof state.easyPieChartEvent === 'undefined') {
5228 state.easyPieChartEvent = {
5235 var value = state.data.result[state.data.result.length - 1 - slot];
5236 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5237 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5238 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5240 state.easyPieChartEvent.value = value;
5241 state.easyPieChartEvent.pcent = pcent;
5242 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5244 if(state.easyPieChartEvent.timer === null) {
5245 state.easyPieChart_instance.disableAnimation();
5247 state.easyPieChartEvent.timer = setTimeout(function() {
5248 state.easyPieChartEvent.timer = null;
5249 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5250 }, NETDATA.options.current.charts_selection_animation_delay);
5256 NETDATA.easypiechartChartUpdate = function(state, data) {
5257 var value, min, max, pcent;
5259 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5264 value = data.result[0];
5265 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5266 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5267 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5270 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5271 state.easyPieChart_instance.update(pcent);
5275 NETDATA.easypiechartChartCreate = function(state, data) {
5276 var self = $(state.element);
5277 var chart = $(state.element_chart);
5279 var value = data.result[0];
5280 var min = self.data('easypiechart-min-value') || null;
5281 var max = self.data('easypiechart-max-value') || null;
5282 var adjust = self.data('easypiechart-adjust') || null;
5285 min = NETDATA.commonMin.get(state);
5286 state.easyPieChartMin = null;
5289 state.easyPieChartMin = min;
5292 max = NETDATA.commonMax.get(state);
5293 state.easyPieChartMax = null;
5296 state.easyPieChartMax = max;
5298 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5300 chart.data('data-percent', pcent);
5304 case 'width': size = state.chartHeight(); break;
5305 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5306 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5308 default: size = state.chartWidth(); break;
5310 state.element.style.width = size + 'px';
5311 state.element.style.height = size + 'px';
5313 var stroke = Math.floor(size / 22);
5314 if(stroke < 3) stroke = 2;
5316 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5317 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5318 state.easyPieChartLabel = document.createElement('span');
5319 state.easyPieChartLabel.className = 'easyPieChartLabel';
5320 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5321 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5322 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5323 state.element_chart.appendChild(state.easyPieChartLabel);
5325 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5326 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5327 state.easyPieChartTitle = document.createElement('span');
5328 state.easyPieChartTitle.className = 'easyPieChartTitle';
5329 state.easyPieChartTitle.innerText = state.title;
5330 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5331 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5332 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5333 state.element_chart.appendChild(state.easyPieChartTitle);
5335 var unitfontsize = Math.round(titlefontsize * 0.9);
5336 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5337 state.easyPieChartUnits = document.createElement('span');
5338 state.easyPieChartUnits.className = 'easyPieChartUnits';
5339 state.easyPieChartUnits.innerText = state.units;
5340 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5341 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5342 state.element_chart.appendChild(state.easyPieChartUnits);
5344 var barColor = self.data('easypiechart-barcolor');
5345 if(typeof barColor === 'undefined' || barColor === null)
5346 barColor = state.chartColors()[0];
5348 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5349 var tmp = eval(barColor);
5350 if(typeof tmp === 'function')
5354 chart.easyPieChart({
5356 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5357 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5358 scaleLength: self.data('easypiechart-scalelength') || 5,
5359 lineCap: self.data('easypiechart-linecap') || 'round',
5360 lineWidth: self.data('easypiechart-linewidth') || stroke,
5361 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5362 size: self.data('easypiechart-size') || size,
5363 rotate: self.data('easypiechart-rotate') || 0,
5364 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5365 easing: self.data('easypiechart-easing') || undefined
5368 // when we just re-create the chart
5369 // do not animate the first update
5371 if(typeof state.easyPieChart_instance !== 'undefined')
5374 state.easyPieChart_instance = chart.data('easyPieChart');
5375 if(animate === false) state.easyPieChart_instance.disableAnimation();
5376 state.easyPieChart_instance.update(pcent);
5377 if(animate === false) state.easyPieChart_instance.enableAnimation();
5381 // ----------------------------------------------------------------------------------------------------------------
5384 NETDATA.gaugeInitialize = function(callback) {
5385 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5387 url: NETDATA.gauge_js,
5390 xhrFields: { withCredentials: true } // required for the cookie
5393 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5396 NETDATA.chartLibraries.gauge.enabled = false;
5397 NETDATA.error(100, NETDATA.gauge_js);
5399 .always(function() {
5400 if(typeof callback === "function")
5405 NETDATA.chartLibraries.gauge.enabled = false;
5406 if(typeof callback === "function")
5411 NETDATA.gaugeAnimation = function(state, status) {
5414 if(typeof status === 'boolean' && status === false)
5416 else if(typeof status === 'number')
5419 // console.log('gauge speed ' + speed);
5420 state.gauge_instance.animationSpeed = speed;
5421 state.___gaugeOld__.speed = speed;
5424 NETDATA.gaugeSet = function(state, value, min, max) {
5425 if(typeof value !== 'number') value = 0;
5426 if(typeof min !== 'number') min = 0;
5427 if(typeof max !== 'number') max = 0;
5428 if(value > max) max = value;
5429 if(value < min) min = value;
5435 else if(min === max)
5438 // gauge.js has an issue if the needle
5439 // is smaller than min or larger than max
5440 // when we set the new values
5441 // the needle will go crazy
5443 // to prevent it, we always feed it
5444 // with a percentage, so that the needle
5445 // is always between min and max
5446 var pcent = (value - min) * 100 / (max - min);
5448 // these should never happen
5449 if(pcent < 0) pcent = 0;
5450 if(pcent > 100) pcent = 100;
5452 state.gauge_instance.set(pcent);
5453 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5455 state.___gaugeOld__.value = value;
5456 state.___gaugeOld__.min = min;
5457 state.___gaugeOld__.max = max;
5460 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5461 if(state.___gaugeOld__.valueLabel !== value) {
5462 state.___gaugeOld__.valueLabel = value;
5463 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5465 if(state.___gaugeOld__.minLabel !== min) {
5466 state.___gaugeOld__.minLabel = min;
5467 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5469 if(state.___gaugeOld__.maxLabel !== max) {
5470 state.___gaugeOld__.maxLabel = max;
5471 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5475 NETDATA.gaugeClearSelection = function(state) {
5476 if(typeof state.gaugeEvent !== 'undefined') {
5477 if(state.gaugeEvent.timer !== null)
5478 clearTimeout(state.gaugeEvent.timer);
5480 state.gaugeEvent.timer = null;
5483 if(state.isAutoRefreshable() === true && state.data !== null) {
5484 NETDATA.gaugeChartUpdate(state, state.data);
5487 NETDATA.gaugeAnimation(state, false);
5488 NETDATA.gaugeSet(state, null, null, null);
5489 NETDATA.gaugeSetLabels(state, null, null, null);
5492 NETDATA.gaugeAnimation(state, true);
5496 NETDATA.gaugeSetSelection = function(state, t) {
5497 if(state.timeIsVisible(t) !== true)
5498 return NETDATA.gaugeClearSelection(state);
5500 var slot = state.calculateRowForTime(t);
5501 if(slot < 0 || slot >= state.data.result.length)
5502 return NETDATA.gaugeClearSelection(state);
5504 if(typeof state.gaugeEvent === 'undefined') {
5505 state.gaugeEvent = {
5513 var value = state.data.result[state.data.result.length - 1 - slot];
5514 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5515 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5517 // make sure it is zero based
5518 if(min > 0) min = 0;
5519 if(max < 0) max = 0;
5521 state.gaugeEvent.value = value;
5522 state.gaugeEvent.min = min;
5523 state.gaugeEvent.max = max;
5524 NETDATA.gaugeSetLabels(state, value, min, max);
5526 if(state.gaugeEvent.timer === null) {
5527 NETDATA.gaugeAnimation(state, false);
5529 state.gaugeEvent.timer = setTimeout(function() {
5530 state.gaugeEvent.timer = null;
5531 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5532 }, NETDATA.options.current.charts_selection_animation_delay);
5538 NETDATA.gaugeChartUpdate = function(state, data) {
5539 var value, min, max;
5541 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5545 NETDATA.gaugeSetLabels(state, null, null, null);
5548 value = data.result[0];
5549 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5550 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5551 if(value < min) min = value;
5552 if(value > max) max = value;
5554 // make sure it is zero based
5555 if(min > 0) min = 0;
5556 if(max < 0) max = 0;
5558 NETDATA.gaugeSetLabels(state, value, min, max);
5561 NETDATA.gaugeSet(state, value, min, max);
5565 NETDATA.gaugeChartCreate = function(state, data) {
5566 var self = $(state.element);
5567 // var chart = $(state.element_chart);
5569 var value = data.result[0];
5570 var min = self.data('gauge-min-value') || null;
5571 var max = self.data('gauge-max-value') || null;
5572 var adjust = self.data('gauge-adjust') || null;
5573 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5574 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5575 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5576 var stopColor = self.data('gauge-stop-color') || void 0;
5577 var generateGradient = self.data('gauge-generate-gradient') || false;
5580 min = NETDATA.commonMin.get(state);
5581 state.gaugeMin = null;
5584 state.gaugeMin = min;
5587 max = NETDATA.commonMax.get(state);
5588 state.gaugeMax = null;
5591 state.gaugeMax = max;
5593 // make sure it is zero based
5594 if(min > 0) min = 0;
5595 if(max < 0) max = 0;
5597 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5599 // case 'width': width = height * ratio; break;
5601 // default: height = width / ratio; break;
5603 //state.element.style.width = width.toString() + 'px';
5604 //state.element.style.height = height.toString() + 'px';
5609 lines: 12, // The number of lines to draw
5610 angle: 0.15, // The length of each line
5611 lineWidth: 0.44, // 0.44 The line thickness
5613 length: 0.8, // 0.9 The radius of the inner circle
5614 strokeWidth: 0.035, // The rotation offset
5615 color: pointerColor // Fill color
5617 colorStart: startColor, // Colors
5618 colorStop: stopColor, // just experiment with them
5619 strokeColor: strokeColor, // to see which ones work best for you
5621 generateGradient: (generateGradient === true)?true:false,
5625 if (generateGradient.constructor === Array) {
5627 // data-gauge-generate-gradient="[0, 50, 100]"
5628 // data-gauge-gradient-percent-color-0="#FFFFFF"
5629 // data-gauge-gradient-percent-color-50="#999900"
5630 // data-gauge-gradient-percent-color-100="#000000"
5632 options.percentColors = new Array();
5633 var len = generateGradient.length;
5635 var pcent = generateGradient[len];
5636 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5637 if(color !== false) {
5638 var a = new Array();
5641 options.percentColors.unshift(a);
5644 if(options.percentColors.length === 0)
5645 delete options.percentColors;
5647 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5648 options.percentColors = [
5649 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5650 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5651 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5652 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5653 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5654 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5655 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5656 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5657 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5658 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5659 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5662 state.gauge_canvas = document.createElement('canvas');
5663 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5664 state.gauge_canvas.className = 'gaugeChart';
5665 state.gauge_canvas.width = width;
5666 state.gauge_canvas.height = height;
5667 state.element_chart.appendChild(state.gauge_canvas);
5669 var valuefontsize = Math.floor(height / 6);
5670 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5671 state.gaugeChartLabel = document.createElement('span');
5672 state.gaugeChartLabel.className = 'gaugeChartLabel';
5673 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5674 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5675 state.element_chart.appendChild(state.gaugeChartLabel);
5677 var titlefontsize = Math.round(valuefontsize / 2);
5679 state.gaugeChartTitle = document.createElement('span');
5680 state.gaugeChartTitle.className = 'gaugeChartTitle';
5681 state.gaugeChartTitle.innerText = state.title;
5682 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5683 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5684 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5685 state.element_chart.appendChild(state.gaugeChartTitle);
5687 var unitfontsize = Math.round(titlefontsize * 0.9);
5688 state.gaugeChartUnits = document.createElement('span');
5689 state.gaugeChartUnits.className = 'gaugeChartUnits';
5690 state.gaugeChartUnits.innerText = state.units;
5691 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5692 state.element_chart.appendChild(state.gaugeChartUnits);
5694 state.gaugeChartMin = document.createElement('span');
5695 state.gaugeChartMin.className = 'gaugeChartMin';
5696 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5697 state.element_chart.appendChild(state.gaugeChartMin);
5699 state.gaugeChartMax = document.createElement('span');
5700 state.gaugeChartMax.className = 'gaugeChartMax';
5701 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5702 state.element_chart.appendChild(state.gaugeChartMax);
5704 // when we just re-create the chart
5705 // do not animate the first update
5707 if(typeof state.gauge_instance !== 'undefined')
5710 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5712 state.___gaugeOld__ = {
5721 // we will always feed a percentage
5722 state.gauge_instance.minValue = 0;
5723 state.gauge_instance.maxValue = 100;
5725 NETDATA.gaugeAnimation(state, animate);
5726 NETDATA.gaugeSet(state, value, min, max);
5727 NETDATA.gaugeSetLabels(state, value, min, max);
5728 NETDATA.gaugeAnimation(state, true);
5732 // ----------------------------------------------------------------------------------------------------------------
5733 // Charts Libraries Registration
5735 NETDATA.chartLibraries = {
5737 initialize: NETDATA.dygraphInitialize,
5738 create: NETDATA.dygraphChartCreate,
5739 update: NETDATA.dygraphChartUpdate,
5740 resize: function(state) {
5741 if(typeof state.dygraph_instance.resize === 'function')
5742 state.dygraph_instance.resize();
5744 setSelection: NETDATA.dygraphSetSelection,
5745 clearSelection: NETDATA.dygraphClearSelection,
5746 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5749 format: function(state) { return 'json'; },
5750 options: function(state) { return 'ms|flip'; },
5751 legend: function(state) {
5752 if(this.isSparkline(state) === false)
5753 return 'right-side';
5757 autoresize: function(state) { return true; },
5758 max_updates_to_recreate: function(state) { return 5000; },
5759 track_colors: function(state) { return true; },
5760 pixels_per_point: function(state) {
5761 if(this.isSparkline(state) === false)
5767 isSparkline: function(state) {
5768 if(typeof state.dygraph_sparkline === 'undefined') {
5769 var t = $(state.element).data('dygraph-theme');
5770 if(t === 'sparkline')
5771 state.dygraph_sparkline = true;
5773 state.dygraph_sparkline = false;
5775 return state.dygraph_sparkline;
5779 initialize: NETDATA.sparklineInitialize,
5780 create: NETDATA.sparklineChartCreate,
5781 update: NETDATA.sparklineChartUpdate,
5783 setSelection: undefined, // function(state, t) { return true; },
5784 clearSelection: undefined, // function(state) { return true; },
5785 toolboxPanAndZoom: null,
5788 format: function(state) { return 'array'; },
5789 options: function(state) { return 'flip|abs'; },
5790 legend: function(state) { return null; },
5791 autoresize: function(state) { return false; },
5792 max_updates_to_recreate: function(state) { return 5000; },
5793 track_colors: function(state) { return false; },
5794 pixels_per_point: function(state) { return 3; }
5797 initialize: NETDATA.peityInitialize,
5798 create: NETDATA.peityChartCreate,
5799 update: NETDATA.peityChartUpdate,
5801 setSelection: undefined, // function(state, t) { return true; },
5802 clearSelection: undefined, // function(state) { return true; },
5803 toolboxPanAndZoom: null,
5806 format: function(state) { return 'ssvcomma'; },
5807 options: function(state) { return 'null2zero|flip|abs'; },
5808 legend: function(state) { return null; },
5809 autoresize: function(state) { return false; },
5810 max_updates_to_recreate: function(state) { return 5000; },
5811 track_colors: function(state) { return false; },
5812 pixels_per_point: function(state) { return 3; }
5815 initialize: NETDATA.morrisInitialize,
5816 create: NETDATA.morrisChartCreate,
5817 update: NETDATA.morrisChartUpdate,
5819 setSelection: undefined, // function(state, t) { return true; },
5820 clearSelection: undefined, // function(state) { return true; },
5821 toolboxPanAndZoom: null,
5824 format: function(state) { return 'json'; },
5825 options: function(state) { return 'objectrows|ms'; },
5826 legend: function(state) { return null; },
5827 autoresize: function(state) { return false; },
5828 max_updates_to_recreate: function(state) { return 50; },
5829 track_colors: function(state) { return false; },
5830 pixels_per_point: function(state) { return 15; }
5833 initialize: NETDATA.googleInitialize,
5834 create: NETDATA.googleChartCreate,
5835 update: NETDATA.googleChartUpdate,
5837 setSelection: undefined, //function(state, t) { return true; },
5838 clearSelection: undefined, //function(state) { return true; },
5839 toolboxPanAndZoom: null,
5842 format: function(state) { return 'datatable'; },
5843 options: function(state) { return ''; },
5844 legend: function(state) { return null; },
5845 autoresize: function(state) { return false; },
5846 max_updates_to_recreate: function(state) { return 300; },
5847 track_colors: function(state) { return false; },
5848 pixels_per_point: function(state) { return 4; }
5851 initialize: NETDATA.raphaelInitialize,
5852 create: NETDATA.raphaelChartCreate,
5853 update: NETDATA.raphaelChartUpdate,
5855 setSelection: undefined, // function(state, t) { return true; },
5856 clearSelection: undefined, // function(state) { return true; },
5857 toolboxPanAndZoom: null,
5860 format: function(state) { return 'json'; },
5861 options: function(state) { return ''; },
5862 legend: function(state) { return null; },
5863 autoresize: function(state) { return false; },
5864 max_updates_to_recreate: function(state) { return 5000; },
5865 track_colors: function(state) { return false; },
5866 pixels_per_point: function(state) { return 3; }
5869 initialize: NETDATA.c3Initialize,
5870 create: NETDATA.c3ChartCreate,
5871 update: NETDATA.c3ChartUpdate,
5873 setSelection: undefined, // function(state, t) { return true; },
5874 clearSelection: undefined, // function(state) { return true; },
5875 toolboxPanAndZoom: null,
5878 format: function(state) { return 'csvjsonarray'; },
5879 options: function(state) { return 'milliseconds'; },
5880 legend: function(state) { return null; },
5881 autoresize: function(state) { return false; },
5882 max_updates_to_recreate: function(state) { return 5000; },
5883 track_colors: function(state) { return false; },
5884 pixels_per_point: function(state) { return 15; }
5887 initialize: NETDATA.d3Initialize,
5888 create: NETDATA.d3ChartCreate,
5889 update: NETDATA.d3ChartUpdate,
5891 setSelection: undefined, // function(state, t) { return true; },
5892 clearSelection: undefined, // function(state) { return true; },
5893 toolboxPanAndZoom: null,
5896 format: function(state) { return 'json'; },
5897 options: function(state) { return ''; },
5898 legend: function(state) { return null; },
5899 autoresize: function(state) { return false; },
5900 max_updates_to_recreate: function(state) { return 5000; },
5901 track_colors: function(state) { return false; },
5902 pixels_per_point: function(state) { return 3; }
5905 initialize: NETDATA.easypiechartInitialize,
5906 create: NETDATA.easypiechartChartCreate,
5907 update: NETDATA.easypiechartChartUpdate,
5909 setSelection: NETDATA.easypiechartSetSelection,
5910 clearSelection: NETDATA.easypiechartClearSelection,
5911 toolboxPanAndZoom: null,
5914 format: function(state) { return 'array'; },
5915 options: function(state) { return 'absolute'; },
5916 legend: function(state) { return null; },
5917 autoresize: function(state) { return false; },
5918 max_updates_to_recreate: function(state) { return 5000; },
5919 track_colors: function(state) { return true; },
5920 pixels_per_point: function(state) { return 3; },
5924 initialize: NETDATA.gaugeInitialize,
5925 create: NETDATA.gaugeChartCreate,
5926 update: NETDATA.gaugeChartUpdate,
5928 setSelection: NETDATA.gaugeSetSelection,
5929 clearSelection: NETDATA.gaugeClearSelection,
5930 toolboxPanAndZoom: null,
5933 format: function(state) { return 'array'; },
5934 options: function(state) { return 'absolute'; },
5935 legend: function(state) { return null; },
5936 autoresize: function(state) { return false; },
5937 max_updates_to_recreate: function(state) { return 5000; },
5938 track_colors: function(state) { return true; },
5939 pixels_per_point: function(state) { return 3; },
5944 NETDATA.registerChartLibrary = function(library, url) {
5945 if(NETDATA.options.debug.libraries === true)
5946 console.log("registering chart library: " + library);
5948 NETDATA.chartLibraries[library].url = url;
5949 NETDATA.chartLibraries[library].initialized = true;
5950 NETDATA.chartLibraries[library].enabled = true;
5953 // ----------------------------------------------------------------------------------------------------------------
5954 // Load required JS libraries and CSS
5956 NETDATA.requiredJs = [
5958 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5960 isAlreadyLoaded: function() {
5961 // check if bootstrap is loaded
5962 if(typeof $().emulateTransitionEnd === 'function')
5965 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5973 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5974 isAlreadyLoaded: function() { return false; }
5978 NETDATA.requiredCSS = [
5980 url: NETDATA.themes.current.bootstrap_css,
5981 isAlreadyLoaded: function() {
5982 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5989 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5990 isAlreadyLoaded: function() { return false; }
5993 url: NETDATA.themes.current.dashboard_css,
5994 isAlreadyLoaded: function() { return false; }
5998 NETDATA.loadedRequiredJs = 0;
5999 NETDATA.loadRequiredJs = function(index, callback) {
6000 if(index >= NETDATA.requiredJs.length) {
6001 if(typeof callback === 'function')
6006 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
6007 NETDATA.loadedRequiredJs++;
6008 NETDATA.loadRequiredJs(++index, callback);
6012 if(NETDATA.options.debug.main_loop === true)
6013 console.log('loading ' + NETDATA.requiredJs[index].url);
6016 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
6020 url: NETDATA.requiredJs[index].url,
6023 xhrFields: { withCredentials: true } // required for the cookie
6026 if(NETDATA.options.debug.main_loop === true)
6027 console.log('loaded ' + NETDATA.requiredJs[index].url);
6030 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6032 .always(function() {
6033 NETDATA.loadedRequiredJs++;
6036 NETDATA.loadRequiredJs(++index, callback);
6040 NETDATA.loadRequiredJs(++index, callback);
6043 NETDATA.loadRequiredCSS = function(index) {
6044 if(index >= NETDATA.requiredCSS.length)
6047 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6048 NETDATA.loadRequiredCSS(++index);
6052 if(NETDATA.options.debug.main_loop === true)
6053 console.log('loading ' + NETDATA.requiredCSS[index].url);
6055 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6056 NETDATA.loadRequiredCSS(++index);
6060 // ----------------------------------------------------------------------------------------------------------------
6061 // Registry of netdata hosts
6064 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6065 chart_div_offset: 100, // give that space above the chart when scrolling to it
6066 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6067 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6069 ms_penalty: 0, // the time penalty of the next alarm
6070 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6071 // if alarms are shown faster than: one per 500ms
6073 notifications: false, // when true, the browser supports notifications (may not be granted though)
6074 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6075 first_notification_id: 0, // the id of the first alarm_log entry for this session
6076 // this is used to prevent CLEAR notifications for past events
6077 // notifications_shown: new Array(),
6079 server: null, // the server to connect to for fetching alarms
6080 current: null, // the list of raised alarms - updated in the background
6081 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6083 notify: function(entry) {
6084 // console.log('alarm ' + entry.unique_id);
6086 if(entry.updated === true) {
6087 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6091 var value = entry.value;
6092 if(NETDATA.alarms.current !== null) {
6093 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6094 if(typeof t !== 'undefined' && entry.status === t.status)
6098 var name = entry.name.replace(/_/g, ' ');
6099 var status = entry.status.toLowerCase();
6100 var title = name + ' = ' + entry.value_string.toString();
6101 var tag = entry.alarm_id;
6102 var icon = 'images/seo-performance-128.png';
6103 var interaction = false;
6107 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6109 switch(entry.status) {
6117 case 'UNINITIALIZED':
6121 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6122 // console.log('alarm ' + entry.unique_id + ' is not current');
6125 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6126 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6129 if(entry.no_clear_notification === true) {
6130 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6133 title = name + ' back to normal (' + entry.value_string.toString() + ')';
6134 icon = 'images/check-mark-2-128-green.png'
6135 interaction = false;
6139 if(entry.old_status === 'CRITICAL')
6140 status = 'demoted to ' + entry.status.toLowerCase();
6142 icon = 'images/alert-128-orange.png';
6143 interaction = false;
6147 if(entry.old_status === 'WARNING')
6148 status = 'escalated to ' + entry.status.toLowerCase();
6150 icon = 'images/alert-128-red.png'
6155 console.log('invalid alarm status ' + entry.status);
6160 // cleanup old notifications with the same alarm_id as this one
6161 // FIXME: it does not seem to work on any web browser!
6162 var len = NETDATA.alarms.notifications_shown.length;
6164 var n = NETDATA.alarms.notifications_shown[len];
6165 if(n.data.alarm_id === entry.alarm_id) {
6166 console.log('removing old alarm ' + n.data.unique_id);
6168 // close the notification
6171 // remove it from the array
6172 NETDATA.alarms.notifications_shown.splice(len, 1);
6173 len = NETDATA.alarms.notifications_shown.length;
6180 setTimeout(function() {
6181 // show this notification
6182 // console.log('new notification: ' + title);
6183 var n = new Notification(title, {
6184 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6186 requireInteraction: interaction,
6187 icon: NETDATA.serverDefault + icon,
6191 n.onclick = function(event) {
6192 event.preventDefault();
6193 NETDATA.alarms.onclick(event.target.data);
6197 // NETDATA.alarms.notifications_shown.push(n);
6198 // console.log(entry);
6199 }, NETDATA.alarms.ms_penalty);
6201 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6205 scrollToChart: function(chart_id) {
6206 if(typeof chart_id === 'string') {
6207 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6208 if(typeof offset !== 'undefined') {
6209 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6216 scrollToAlarm: function(alarm) {
6217 if(typeof alarm === 'object') {
6218 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6220 if(ret === true && NETDATA.options.page_is_visible === false)
6222 // 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.');
6227 notifyAll: function() {
6228 // console.log('FETCHING ALARM LOG');
6229 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6230 // console.log('ALARM LOG FETCHED');
6232 if(data === null || typeof data !== 'object') {
6233 console.log('invalid alarms log response');
6237 if(data.length === 0) {
6238 console.log('received empty alarm log');
6242 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6244 data.sort(function(a, b) {
6245 if(a.unique_id > b.unique_id) return -1;
6246 if(a.unique_id < b.unique_id) return 1;
6250 NETDATA.alarms.ms_penalty = 0;
6252 var len = data.length;
6254 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6255 NETDATA.alarms.notify(data[len]);
6258 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6261 NETDATA.alarms.last_notification_id = data[0].unique_id;
6262 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6263 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6267 check_notifications: function() {
6268 // returns true if we should fire 1+ notifications
6270 if(NETDATA.alarms.notifications !== true) {
6271 // console.log('notifications not available');
6275 if(Notification.permission !== 'granted') {
6276 // console.log('notifications not granted');
6280 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6281 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6283 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6284 // console.log('new alarms detected');
6287 //else console.log('no new alarms');
6289 // else console.log('cannot process alarms');
6294 get: function(what, callback) {
6296 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6300 'Cache-Control': 'no-cache, no-store',
6301 'Pragma': 'no-cache'
6303 xhrFields: { withCredentials: true } // required for the cookie
6305 .done(function(data) {
6306 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6307 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6309 if(typeof callback === 'function')
6310 return callback(data);
6313 NETDATA.error(415, NETDATA.alarms.server);
6315 if(typeof callback === 'function')
6316 return callback(null);
6320 update_forever: function() {
6321 NETDATA.alarms.get('active', function(data) {
6323 NETDATA.alarms.current = data;
6325 if(NETDATA.alarms.check_notifications() === true) {
6326 NETDATA.alarms.notifyAll();
6329 if (typeof NETDATA.alarms.callback === 'function') {
6330 NETDATA.alarms.callback(data);
6333 // Health monitoring is disabled on this netdata
6334 if(data.status === false) return;
6337 setTimeout(NETDATA.alarms.update_forever, 10000);
6341 get_log: function(last_id, callback) {
6342 // console.log('fetching all log after ' + last_id.toString());
6344 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6348 'Cache-Control': 'no-cache, no-store',
6349 'Pragma': 'no-cache'
6351 xhrFields: { withCredentials: true } // required for the cookie
6353 .done(function(data) {
6354 if(typeof callback === 'function')
6355 return callback(data);
6358 NETDATA.error(416, NETDATA.alarms.server);
6360 if(typeof callback === 'function')
6361 return callback(null);
6366 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6368 NETDATA.alarms.last_notification_id =
6369 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6371 if(NETDATA.alarms.onclick === null)
6372 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6374 if(netdataShowAlarms === true) {
6375 NETDATA.alarms.update_forever();
6377 if('Notification' in window) {
6378 // console.log('notifications available');
6379 NETDATA.alarms.notifications = true;
6381 if(Notification.permission === 'default')
6382 Notification.requestPermission();
6388 // ----------------------------------------------------------------------------------------------------------------
6389 // Registry of netdata hosts
6391 NETDATA.registry = {
6392 server: null, // the netdata registry server
6393 person_guid: null, // the unique ID of this browser / user
6394 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6395 hostname: null, // the hostname of the netdata server that served dashboard.js
6396 machines: null, // the user's other URLs
6397 machines_array: null, // the user's other URLs in an array
6400 parsePersonUrls: function(person_urls) {
6401 // console.log(person_urls);
6402 NETDATA.registry.person_urls = person_urls;
6405 NETDATA.registry.machines = {};
6406 NETDATA.registry.machines_array = new Array();
6408 var now = Date.now();
6409 var apu = person_urls;
6412 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6413 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6419 accesses: apu[i][3],
6421 alternate_urls: new Array()
6423 obj.alternate_urls.push(apu[i][1]);
6425 NETDATA.registry.machines[apu[i][0]] = obj;
6426 NETDATA.registry.machines_array.push(obj);
6429 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6431 var pu = NETDATA.registry.machines[apu[i][0]];
6432 if(pu.last_t < apu[i][2]) {
6434 pu.last_t = apu[i][2];
6435 pu.name = apu[i][4];
6437 pu.accesses += apu[i][3];
6438 pu.alternate_urls.push(apu[i][1]);
6443 if(typeof netdataRegistryCallback === 'function')
6444 netdataRegistryCallback(NETDATA.registry.machines_array);
6448 if(netdataRegistry !== true) return;
6450 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6452 NETDATA.registry.server = data.registry;
6453 NETDATA.registry.machine_guid = data.machine_guid;
6454 NETDATA.registry.hostname = data.hostname;
6456 NETDATA.registry.access(2, function (person_urls) {
6457 NETDATA.registry.parsePersonUrls(person_urls);
6464 hello: function(host, callback) {
6465 host = NETDATA.fixHost(host);
6467 // send HELLO to a netdata server:
6468 // 1. verifies the server is reachable
6469 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6471 url: host + '/api/v1/registry?action=hello',
6475 'Cache-Control': 'no-cache, no-store',
6476 'Pragma': 'no-cache'
6478 xhrFields: { withCredentials: true } // required for the cookie
6480 .done(function(data) {
6481 if(typeof data.status !== 'string' || data.status !== 'ok') {
6482 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6486 if(typeof callback === 'function')
6487 return callback(data);
6490 NETDATA.error(407, host);
6492 if(typeof callback === 'function')
6493 return callback(null);
6497 access: function(max_redirects, callback) {
6498 // send ACCESS to a netdata registry:
6499 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6500 // 2. it responds with a list of netdata servers we know
6501 // the registry identifies us using a cookie it sets the first time we access it
6502 // the registry may respond with a redirect URL to send us to another registry
6504 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),
6508 'Cache-Control': 'no-cache, no-store',
6509 'Pragma': 'no-cache'
6511 xhrFields: { withCredentials: true } // required for the cookie
6513 .done(function(data) {
6514 var redirect = null;
6515 if(typeof data.registry === 'string')
6516 redirect = data.registry;
6518 if(typeof data.status !== 'string' || data.status !== 'ok') {
6519 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6524 if(redirect !== null && max_redirects > 0) {
6525 NETDATA.registry.server = redirect;
6526 NETDATA.registry.access(max_redirects - 1, callback);
6529 if(typeof callback === 'function')
6530 return callback(null);
6534 if(typeof data.person_guid === 'string')
6535 NETDATA.registry.person_guid = data.person_guid;
6537 if(typeof callback === 'function')
6538 return callback(data.urls);
6542 NETDATA.error(410, NETDATA.registry.server);
6544 if(typeof callback === 'function')
6545 return callback(null);
6549 delete: function(delete_url, callback) {
6550 // send DELETE to a netdata registry:
6552 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),
6556 'Cache-Control': 'no-cache, no-store',
6557 'Pragma': 'no-cache'
6559 xhrFields: { withCredentials: true } // required for the cookie
6561 .done(function(data) {
6562 if(typeof data.status !== 'string' || data.status !== 'ok') {
6563 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6567 if(typeof callback === 'function')
6568 return callback(data);
6571 NETDATA.error(412, NETDATA.registry.server);
6573 if(typeof callback === 'function')
6574 return callback(null);
6578 search: function(machine_guid, callback) {
6579 // SEARCH for the URLs of a machine:
6581 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,
6585 'Cache-Control': 'no-cache, no-store',
6586 'Pragma': 'no-cache'
6588 xhrFields: { withCredentials: true } // required for the cookie
6590 .done(function(data) {
6591 if(typeof data.status !== 'string' || data.status !== 'ok') {
6592 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6596 if(typeof callback === 'function')
6597 return callback(data);
6600 NETDATA.error(418, NETDATA.registry.server);
6602 if(typeof callback === 'function')
6603 return callback(null);
6607 switch: function(new_person_guid, callback) {
6610 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,
6614 'Cache-Control': 'no-cache, no-store',
6615 'Pragma': 'no-cache'
6617 xhrFields: { withCredentials: true } // required for the cookie
6619 .done(function(data) {
6620 if(typeof data.status !== 'string' || data.status !== 'ok') {
6621 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6625 if(typeof callback === 'function')
6626 return callback(data);
6629 NETDATA.error(414, NETDATA.registry.server);
6631 if(typeof callback === 'function')
6632 return callback(null);
6637 // ----------------------------------------------------------------------------------------------------------------
6640 if(typeof netdataPrepCallback === 'function')
6641 netdataPrepCallback();
6643 NETDATA.errorReset();
6644 NETDATA.loadRequiredCSS(0);
6646 NETDATA._loadjQuery(function() {
6647 NETDATA.loadRequiredJs(0, function() {
6648 if(typeof $().emulateTransitionEnd !== 'function') {
6649 // bootstrap is not available
6650 NETDATA.options.current.show_help = false;
6653 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6654 if(NETDATA.options.debug.main_loop === true)
6655 console.log('starting chart refresh thread');
6661 })(window, document);