1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
4 /*global netdataNoDygraphs *//* boolean, disable dygraph charts
6 /*global netdataNoSparklines *//* boolean, disable sparkline charts
8 /*global netdataNoPeitys *//* boolean, disable peity charts
10 /*global netdataNoGoogleCharts *//* boolean, disable google charts
12 /*global netdataNoMorris *//* boolean, disable morris charts
14 /*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
16 /*global netdataNoGauge *//* boolean, disable gauge.js charts
18 /*global netdataNoD3 *//* boolean, disable d3 charts
20 /*global netdataNoC3 *//* boolean, disable c3 charts
22 /*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
24 /*global netdataDontStart *//* boolean, do not start the thread to process the charts
26 /*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
28 /*global netdataRegistry:true *//* boolean, use the netdata registry
30 /*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
31 * (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
34 /*global netdataShowHelp:true *//* boolean, disable charts help
36 /*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
38 /*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
40 /*global netdataCallback *//* function, callback to be called when netdata is ready to start
42 * netdata will be running while this is called
43 * (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
46 /*global netdataServer *//* string, the URL of the netdata server to use
47 * (default: the URL the page is hosted at) */
49 // ----------------------------------------------------------------------------
52 var NETDATA = window.NETDATA || {};
54 (function(window, document) {
55 // ------------------------------------------------------------------------
56 // compatibility fixes
58 // fix IE issue with console
59 if(!window.console) { window.console = { log: function(){} }; }
61 // if string.endsWith is not defined, define it
62 if(typeof String.prototype.endsWith !== 'function') {
63 String.prototype.endsWith = function(s) {
64 if(s.length > this.length) return false;
65 return this.slice(-s.length) === s;
69 // if string.startsWith is not defined, define it
70 if(typeof String.prototype.startsWith !== 'function') {
71 String.prototype.startsWith = function(s) {
72 if(s.length > this.length) return false;
73 return this.slice(s.length) === s;
77 NETDATA.name2id = function(s) {
86 // ----------------------------------------------------------------------------------------------------------------
87 // Detect the netdata server
89 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91 NETDATA._scriptSource = function() {
94 if(typeof document.currentScript !== 'undefined') {
95 script = document.currentScript;
98 var all_scripts = document.getElementsByTagName('script');
99 script = all_scripts[all_scripts.length - 1];
102 if (typeof script.getAttribute.length !== 'undefined')
105 script = script.getAttribute('src', -1);
110 if(typeof netdataServer !== 'undefined')
111 NETDATA.serverDefault = netdataServer;
113 var s = NETDATA._scriptSource();
114 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
116 console.log('WARNING: Cannot detect the URL of the netdata server.');
117 NETDATA.serverDefault = null;
121 if(NETDATA.serverDefault === null)
122 NETDATA.serverDefault = '';
123 else if(NETDATA.serverDefault.slice(-1) !== '/')
124 NETDATA.serverDefault += '/';
126 // default URLs for all the external files we need
127 // make them RELATIVE so that the whole thing can also be
128 // installed under a web server
129 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
134 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
135 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
136 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
137 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
138 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
139 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
140 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
141 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
142 NETDATA.google_js = 'https://www.google.com/jsapi';
146 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
147 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
148 background: '#FFFFFF',
149 foreground: '#000000',
152 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
153 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
154 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
155 '#329262', '#3B3EAC' ],
156 easypiechart_track: '#f0f0f0',
157 easypiechart_scale: '#dfe0e0',
158 gauge_pointer: '#C0C0C0',
159 gauge_stroke: '#F0F0F0',
160 gauge_gradient: false
163 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
164 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
165 background: '#272b30',
166 foreground: '#C8C8C8',
169 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
170 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
171 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
172 '#a6a479', '#a66da8' ],
174 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
175 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
176 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
177 '#329262', '#3B3EFF' ],
178 easypiechart_track: '#373b40',
179 easypiechart_scale: '#373b40',
180 gauge_pointer: '#474b50',
181 gauge_stroke: '#373b40',
182 gauge_gradient: false
186 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
187 NETDATA.themes.current = NETDATA.themes[netdataTheme];
189 NETDATA.themes.current = NETDATA.themes.white;
191 NETDATA.colors = NETDATA.themes.current.colors;
193 // these are the colors Google Charts are using
194 // we have them here to attempt emulate their look and feel on the other chart libraries
195 // http://there4.io/2012/05/02/google-chart-color-list/
196 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
197 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
198 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
200 // an alternative set
201 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
202 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
203 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
205 if(typeof netdataShowHelp === 'undefined')
206 netdataShowHelp = true;
208 if(typeof netdataShowAlarms === 'undefined')
209 netdataShowAlarms = false;
211 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
212 netdataRegistryAfterMs = 1500;
214 if(typeof netdataRegistry === 'undefined') {
215 // backward compatibility
216 netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
218 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
219 netdataRegistry = true;
221 // ----------------------------------------------------------------------------------------------------------------
222 // the defaults for all charts
224 // if the user does not specify any of these, the following will be used
226 NETDATA.chartDefaults = {
227 host: NETDATA.serverDefault, // the server to get data from
228 width: '100%', // the chart width - can be null
229 height: '100%', // the chart height - can be null
230 min_width: null, // the chart minimum width - can be null
231 library: 'dygraph', // the graphing library to use
232 method: 'average', // the grouping method
233 before: 0, // panning
234 after: -600, // panning
235 pixels_per_point: 1, // the detail of the chart
236 fill_luminance: 0.8 // luminance of colors in solid areas
239 // ----------------------------------------------------------------------------------------------------------------
243 pauseCallback: null, // a callback when we are really paused
245 pause: false, // when enabled we don't auto-refresh the charts
247 targets: null, // an array of all the state objects that are
248 // currently active (independently of their
249 // viewport visibility)
251 updated_dom: true, // when true, the DOM has been updated with
252 // new elements we have to check.
254 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
255 // rendering charts continuously.
256 // used with .current.fast_render_timeframe
258 page_is_visible: true, // when true, this page is visible
260 auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
261 // auto-refresher for some time (when a chart is
262 // performing pan or zoom, we need to stop refreshing
263 // all other charts, to have the maximum speed for
264 // rendering the chart that is panned or zoomed).
265 // Used with .current.global_pan_sync_time
267 last_resized: Date.now(), // the timestamp of the last resize request
269 last_page_scroll: 0, // the timestamp the last time the page was scrolled
271 // the current profile
272 // we may have many...
274 pixels_per_point: 1, // the minimum pixels per point for all charts
275 // increase this to speed javascript up
276 // each chart library has its own limit too
277 // the max of this and the chart library is used
278 // the final is calculated every time, so a change
279 // here will have immediate effect on the next chart
282 idle_between_charts: 100, // ms - how much time to wait between chart updates
284 fast_render_timeframe: 200, // ms - render continuously until this time of continuous
285 // rendering has been reached
286 // this setting is used to make it render e.g. 10
287 // charts at once, sleep idle_between_charts time
288 // and continue for another 10 charts.
290 idle_between_loops: 500, // ms - if all charts have been updated, wait this
291 // time before starting again.
293 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
295 idle_lost_focus: 500, // ms - when the window does not have focus, check
296 // if focus has been regained, every this time
298 global_pan_sync_time: 1000, // ms - when you pan or zoom a chart, the background
299 // auto-refreshing of charts is paused for this amount
302 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
303 // of time before setting up synchronized selections
306 sync_selection: true, // enable or disable selection sync
308 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
310 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
312 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
314 update_only_visible: true, // enable or disable visibility management
316 parallel_refresher: true, // enable parallel refresh of charts
318 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
320 destroy_on_hide: false, // destroy charts when they are not visible
322 show_help: netdataShowHelp, // when enabled the charts will show some help
323 show_help_delay_show_ms: 500,
324 show_help_delay_hide_ms: 0,
326 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
328 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
329 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
331 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
333 smooth_plot: true, // enable smooth plot, where possible
335 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
337 color_fill_opacity_line: 1.0,
338 color_fill_opacity_area: 0.2,
339 color_fill_opacity_stacked: 0.8,
341 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
342 pan_and_zoom_factor_multiplier_control: 2.0,
343 pan_and_zoom_factor_multiplier_shift: 3.0,
344 pan_and_zoom_factor_multiplier_alt: 4.0,
346 abort_ajax_on_scroll: false, // kill pending ajax page scroll
347 async_on_scroll: false, // sync/async onscroll handler
348 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
350 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
352 setOptionCallback: function() { }
360 chart_data_url: false,
361 chart_errors: false, // FIXME: remember to set it to false before merging
369 NETDATA.statistics = {
372 refreshes_active_max: 0
376 // ----------------------------------------------------------------------------------------------------------------
377 // local storage options
379 NETDATA.localStorage = {
382 callback: {} // only used for resetting back to defaults
385 NETDATA.localStorageTested = -1;
386 NETDATA.localStorageTest = function() {
387 if(NETDATA.localStorageTested !== -1)
388 return NETDATA.localStorageTested;
390 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
393 localStorage.setItem(test, test);
394 localStorage.removeItem(test);
395 NETDATA.localStorageTested = true;
398 NETDATA.localStorageTested = false;
402 NETDATA.localStorageTested = false;
404 return NETDATA.localStorageTested;
407 NETDATA.localStorageGet = function(key, def, callback) {
410 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
411 NETDATA.localStorage.default[key.toString()] = def;
412 NETDATA.localStorage.callback[key.toString()] = callback;
415 if(NETDATA.localStorageTest() === true) {
417 // console.log('localStorage: loading "' + key.toString() + '"');
418 ret = localStorage.getItem(key.toString());
419 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
420 if(ret === null || ret === 'undefined') {
421 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
422 localStorage.setItem(key.toString(), JSON.stringify(def));
426 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
427 ret = JSON.parse(ret);
428 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
432 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
437 if(typeof ret === 'undefined' || ret === 'undefined') {
438 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
442 NETDATA.localStorage.current[key.toString()] = ret;
446 NETDATA.localStorageSet = function(key, value, callback) {
447 if(typeof value === 'undefined' || value === 'undefined') {
448 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
451 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
452 NETDATA.localStorage.default[key.toString()] = value;
453 NETDATA.localStorage.current[key.toString()] = value;
454 NETDATA.localStorage.callback[key.toString()] = callback;
457 if(NETDATA.localStorageTest() === true) {
458 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
460 localStorage.setItem(key.toString(), JSON.stringify(value));
463 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
467 NETDATA.localStorage.current[key.toString()] = value;
471 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
472 var keys = Object.keys(obj);
473 var len = keys.length;
477 if(typeof obj[i] === 'object') {
478 //console.log('object ' + prefix + '.' + i.toString());
479 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
483 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
487 NETDATA.setOption = function(key, value) {
488 if(key.toString() === 'setOptionCallback') {
489 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
490 NETDATA.options.current[key.toString()] = value;
491 NETDATA.options.current.setOptionCallback();
494 else if(NETDATA.options.current[key.toString()] !== value) {
495 var name = 'options.' + key.toString();
497 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
498 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
500 //console.log(NETDATA.localStorage);
501 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
502 //console.log(NETDATA.options);
503 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
505 if(typeof NETDATA.options.current.setOptionCallback === 'function')
506 NETDATA.options.current.setOptionCallback();
512 NETDATA.getOption = function(key) {
513 return NETDATA.options.current[key.toString()];
516 // read settings from local storage
517 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
519 // always start with this option enabled.
520 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
522 NETDATA.resetOptions = function() {
523 var keys = Object.keys(NETDATA.localStorage.default);
524 var len = keys.length;
527 var a = i.split('.');
529 if(a[0] === 'options') {
530 if(a[1] === 'setOptionCallback') continue;
531 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
532 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
534 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
536 else if(a[0] === 'chart_heights') {
537 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
538 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
544 // ----------------------------------------------------------------------------------------------------------------
546 if(NETDATA.options.debug.main_loop === true)
547 console.log('welcome to NETDATA');
549 NETDATA.onresizeCallback = null;
550 NETDATA.onresize = function() {
551 NETDATA.options.last_resized = Date.now();
554 if(typeof NETDATA.onresizeCallback === 'function')
555 NETDATA.onresizeCallback();
558 NETDATA.onscroll_updater_count = 0;
559 NETDATA.onscroll_updater_running = false;
560 NETDATA.onscroll_updater_last_run = 0;
561 NETDATA.onscroll_updater_watchdog = null;
562 NETDATA.onscroll_updater_max_duration = 0;
563 NETDATA.onscroll_updater_above_threshold_count = 0;
564 NETDATA.onscroll_updater = function() {
565 NETDATA.onscroll_updater_running = true;
566 NETDATA.onscroll_updater_count++;
567 var start = Date.now();
569 var targets = NETDATA.options.targets;
570 var len = targets.length;
572 // when the user scrolls he sees that we have
573 // hidden all the not-visible charts
574 // using this little function we try to switch
575 // the charts back to visible quickly
578 if(NETDATA.options.abort_ajax_on_scroll === true) {
579 // we have to cancel pending requests too
582 if (targets[len]._updating === true) {
583 if (typeof targets[len].xhr !== 'undefined') {
584 targets[len].xhr.abort();
585 targets[len].running = false;
586 targets[len]._updating = false;
588 targets[len].isVisible();
593 // just find which chart is visible
596 targets[len].isVisible();
599 var end = Date.now();
600 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
602 if(NETDATA.options.current.async_on_scroll === false) {
603 var dt = end - start;
604 if(dt > NETDATA.onscroll_updater_max_duration) {
605 // console.log('max onscroll event handler duration increased to ' + dt);
606 NETDATA.onscroll_updater_max_duration = dt;
609 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
610 // console.log('slow: ' + dt);
611 NETDATA.onscroll_updater_above_threshold_count++;
613 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
614 NETDATA.setOption('async_on_scroll', true);
615 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
620 NETDATA.onscroll_updater_last_run = start;
621 NETDATA.onscroll_updater_running = false;
624 NETDATA.onscroll = function() {
625 // console.log('onscroll');
627 NETDATA.options.last_page_scroll = Date.now();
628 NETDATA.options.auto_refresher_stop_until = 0;
630 if(NETDATA.options.targets === null) return;
632 if(NETDATA.options.current.async_on_scroll === true) {
634 if(NETDATA.onscroll_updater_running === false) {
635 NETDATA.onscroll_updater_running = true;
636 setTimeout(NETDATA.onscroll_updater, 0);
639 if(NETDATA.onscroll_updater_watchdog !== null)
640 clearTimeout(NETDATA.onscroll_updater_watchdog);
642 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
643 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
644 // console.log('watchdog');
645 NETDATA.onscroll_updater();
648 NETDATA.onscroll_updater_watchdog = null;
654 NETDATA.onscroll_updater();
658 window.onresize = NETDATA.onresize;
659 window.onscroll = NETDATA.onscroll;
661 // ----------------------------------------------------------------------------------------------------------------
664 NETDATA.errorCodes = {
665 100: { message: "Cannot load chart library", alert: true },
666 101: { message: "Cannot load jQuery", alert: true },
667 402: { message: "Chart library not found", alert: false },
668 403: { message: "Chart library not enabled/is failed", alert: false },
669 404: { message: "Chart not found", alert: false },
670 405: { message: "Cannot download charts index from server", alert: true },
671 406: { message: "Invalid charts index downloaded from server", alert: true },
672 407: { message: "Cannot HELLO netdata server", alert: false },
673 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
674 409: { message: "Cannot ACCESS netdata registry", alert: false },
675 410: { message: "Netdata registry ACCESS failed", alert: false },
676 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
677 412: { message: "Netdata registry DELETE failed", alert: false },
678 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
679 414: { message: "Netdata registry SWITCH failed", alert: false },
680 415: { message: "Netdata alarms download failed", alert: false },
681 416: { message: "Netdata alarms log download failed", alert: false },
682 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
683 418: { message: "Netdata registry SEARCH failed", alert: false }
685 NETDATA.errorLast = {
691 NETDATA.error = function(code, msg) {
692 NETDATA.errorLast.code = code;
693 NETDATA.errorLast.message = msg;
694 NETDATA.errorLast.datetime = Date.now();
696 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
699 if(typeof netdataErrorCallback === 'function') {
700 ret = netdataErrorCallback('system', code, msg);
703 if(ret && NETDATA.errorCodes[code].alert)
704 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
707 NETDATA.errorReset = function() {
708 NETDATA.errorLast.code = 0;
709 NETDATA.errorLast.message = "You are doing fine!";
710 NETDATA.errorLast.datetime = 0;
713 // ----------------------------------------------------------------------------------------------------------------
714 // commonMin & commonMax
716 NETDATA.commonMin = {
720 get: function(state) {
721 if(typeof state.__commonMin === 'undefined') {
722 // get the commonMin setting
723 var self = $(state.element);
724 state.__commonMin = self.data('common-min') || null;
727 var min = state.data.min;
728 var name = state.__commonMin;
731 // we don't need commonMin
732 //state.log('no need for commonMin');
736 var t = this.keys[name];
737 if(typeof t === 'undefined') {
739 this.keys[name] = {};
743 var uuid = state.uuid;
744 if(typeof t[uuid] !== 'undefined') {
745 if(t[uuid] === min) {
746 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
747 return this.latest[name];
749 else if(min < this.latest[name]) {
750 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
752 this.latest[name] = min;
760 // find the common min
763 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
765 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
766 this.latest[name] = m;
771 NETDATA.commonMax = {
775 get: function(state) {
776 if(typeof state.__commonMax === 'undefined') {
777 // get the commonMax setting
778 var self = $(state.element);
779 state.__commonMax = self.data('common-max') || null;
782 var max = state.data.max;
783 var name = state.__commonMax;
786 // we don't need commonMax
787 //state.log('no need for commonMax');
791 var t = this.keys[name];
792 if(typeof t === 'undefined') {
794 this.keys[name] = {};
798 var uuid = state.uuid;
799 if(typeof t[uuid] !== 'undefined') {
800 if(t[uuid] === max) {
801 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
802 return this.latest[name];
804 else if(max > this.latest[name]) {
805 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
807 this.latest[name] = max;
815 // find the common max
818 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
820 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
821 this.latest[name] = m;
826 // ----------------------------------------------------------------------------------------------------------------
829 // When multiple charts need the same chart, we avoid downloading it
830 // multiple times (and having it in browser memory multiple time)
831 // by using this registry.
833 // Every time we download a chart definition, we save it here with .add()
834 // Then we try to get it back with .get(). If that fails, we download it.
836 NETDATA.fixHost = function(host) {
837 while(host.slice(-1) === '/')
838 host = host.substring(0, host.length - 1);
843 NETDATA.chartRegistry = {
846 fixid: function(id) {
847 return id.replace(/:/g, "_").replace(/\//g, "_");
850 add: function(host, id, data) {
851 host = this.fixid(host);
854 if(typeof this.charts[host] === 'undefined')
855 this.charts[host] = {};
857 //console.log('added ' + host + '/' + id);
858 this.charts[host][id] = data;
861 get: function(host, id) {
862 host = this.fixid(host);
865 if(typeof this.charts[host] === 'undefined')
868 if(typeof this.charts[host][id] === 'undefined')
871 //console.log('cached ' + host + '/' + id);
872 return this.charts[host][id];
875 downloadAll: function(host, callback) {
876 host = NETDATA.fixHost(host);
881 url: host + '/api/v1/charts',
884 xhrFields: { withCredentials: true } // required for the cookie
886 .done(function(data) {
888 var h = NETDATA.chartRegistry.fixid(host);
889 self.charts[h] = data.charts;
891 else NETDATA.error(406, host + '/api/v1/charts');
893 if(typeof callback === 'function')
894 return callback(data);
897 NETDATA.error(405, host + '/api/v1/charts');
899 if(typeof callback === 'function')
900 return callback(null);
905 // ----------------------------------------------------------------------------------------------------------------
906 // Global Pan and Zoom on charts
908 // Using this structure are synchronize all the charts, so that
909 // when you pan or zoom one, all others are automatically refreshed
910 // to the same timespan.
912 NETDATA.globalPanAndZoom = {
913 seq: 0, // timestamp ms
914 // every time a chart is panned or zoomed
915 // we set the timestamp here
916 // then we use it as a sequence number
917 // to find if other charts are synchronized
918 // to this time-range
920 master: null, // the master chart (state), to which all others
923 force_before_ms: null, // the timespan to sync all other charts
924 force_after_ms: null,
929 setMaster: function(state, after, before) {
930 if(NETDATA.options.current.sync_pan_and_zoom === false)
933 if(this.master !== null && this.master !== state)
934 this.master.resetChart(true, true);
936 var now = Date.now();
939 this.force_after_ms = after;
940 this.force_before_ms = before;
941 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
943 if(typeof this.callback === 'function')
944 this.callback(true, after, before);
948 clearMaster: function() {
949 if(this.master !== null) {
950 var st = this.master;
957 this.force_after_ms = null;
958 this.force_before_ms = null;
959 NETDATA.options.auto_refresher_stop_until = 0;
961 if(typeof this.callback === 'function')
962 this.callback(false, 0, 0);
965 // is the given state the master of the global
966 // pan and zoom sync?
967 isMaster: function(state) {
968 return (this.master === state);
971 // are we currently have a global pan and zoom sync?
972 isActive: function() {
973 return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
976 // check if a chart, other than the master
977 // needs to be refreshed, due to the global pan and zoom
978 shouldBeAutoRefreshed: function(state) {
979 if(this.master === null || this.seq === 0)
982 //if(state.needsRecreation())
985 return (state.tm.pan_and_zoom_seq !== this.seq);
989 // ----------------------------------------------------------------------------------------------------------------
990 // dimensions selection
993 // move color assignment to dimensions, here
995 var dimensionStatus = function(parent, label, name_div, value_div, color) {
996 this.enabled = false;
997 this.parent = parent;
999 this.name_div = null;
1000 this.value_div = null;
1001 this.color = NETDATA.themes.current.foreground;
1002 this.selected = (parent.unselected_count === 0);
1004 this.setOptions(name_div, value_div, color);
1007 dimensionStatus.prototype.invalidate = function() {
1008 this.name_div = null;
1009 this.value_div = null;
1010 this.enabled = false;
1013 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1016 if(this.name_div !== name_div) {
1017 this.name_div = name_div;
1018 this.name_div.title = this.label;
1019 this.name_div.style.color = this.color;
1020 if(this.selected === false)
1021 this.name_div.className = 'netdata-legend-name not-selected';
1023 this.name_div.className = 'netdata-legend-name selected';
1026 if(this.value_div !== value_div) {
1027 this.value_div = value_div;
1028 this.value_div.title = this.label;
1029 this.value_div.style.color = this.color;
1030 if(this.selected === false)
1031 this.value_div.className = 'netdata-legend-value not-selected';
1033 this.value_div.className = 'netdata-legend-value selected';
1036 this.enabled = true;
1040 dimensionStatus.prototype.setHandler = function() {
1041 if(this.enabled === false) return;
1045 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1046 this.name_div.onclick = this.value_div.onclick = function(e) {
1048 if(ds.isSelected()) {
1050 if(e.shiftKey === true || e.ctrlKey === true) {
1051 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1054 if(ds.parent.countSelected() === 0)
1055 ds.parent.selectAll();
1058 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1059 if(ds.parent.countSelected() === 1) {
1060 ds.parent.selectAll();
1063 ds.parent.selectNone();
1069 // this is not selected
1070 if(e.shiftKey === true || e.ctrlKey === true) {
1071 // control or shift key is pressed -> select this too
1075 // no key is pressed -> select only this
1076 ds.parent.selectNone();
1081 ds.parent.state.redrawChart();
1085 dimensionStatus.prototype.select = function() {
1086 if(this.enabled === false) return;
1088 this.name_div.className = 'netdata-legend-name selected';
1089 this.value_div.className = 'netdata-legend-value selected';
1090 this.selected = true;
1093 dimensionStatus.prototype.unselect = function() {
1094 if(this.enabled === false) return;
1096 this.name_div.className = 'netdata-legend-name not-selected';
1097 this.value_div.className = 'netdata-legend-value hidden';
1098 this.selected = false;
1101 dimensionStatus.prototype.isSelected = function() {
1102 return(this.enabled === true && this.selected === true);
1105 // ----------------------------------------------------------------------------------------------------------------
1107 var dimensionsVisibility = function(state) {
1110 this.dimensions = {};
1111 this.selected_count = 0;
1112 this.unselected_count = 0;
1115 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1116 if(typeof this.dimensions[label] === 'undefined') {
1118 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1121 this.dimensions[label].setOptions(name_div, value_div, color);
1123 return this.dimensions[label];
1126 dimensionsVisibility.prototype.dimensionGet = function(label) {
1127 return this.dimensions[label];
1130 dimensionsVisibility.prototype.invalidateAll = function() {
1131 var keys = Object.keys(this.dimensions);
1132 var len = keys.length;
1134 this.dimensions[keys[len]].invalidate();
1137 dimensionsVisibility.prototype.selectAll = function() {
1138 var keys = Object.keys(this.dimensions);
1139 var len = keys.length;
1141 this.dimensions[keys[len]].select();
1144 dimensionsVisibility.prototype.countSelected = function() {
1146 var keys = Object.keys(this.dimensions);
1147 var len = keys.length;
1149 if(this.dimensions[keys[len]].isSelected()) selected++;
1154 dimensionsVisibility.prototype.selectNone = function() {
1155 var keys = Object.keys(this.dimensions);
1156 var len = keys.length;
1158 this.dimensions[keys[len]].unselect();
1161 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1163 this.selected_count = 0;
1164 this.unselected_count = 0;
1166 var len = array.length;
1168 var ds = this.dimensions[array[len]];
1169 if(typeof ds === 'undefined') {
1170 // console.log(array[i] + ' is not found');
1173 else if(ds.isSelected()) {
1175 this.selected_count++;
1179 this.unselected_count++;
1183 if(this.selected_count === 0 && this.unselected_count !== 0) {
1185 return this.selected2BooleanArray(array);
1192 // ----------------------------------------------------------------------------------------------------------------
1193 // global selection sync
1195 NETDATA.globalSelectionSync = {
1197 dont_sync_before: 0,
1202 if(this.state !== null)
1203 this.state.globalSelectionSyncStop();
1207 if(this.state !== null) {
1208 this.state.globalSelectionSyncDelay();
1213 // ----------------------------------------------------------------------------------------------------------------
1214 // Our state object, where all per-chart values are stored
1216 var chartState = function(element) {
1217 var self = $(element);
1218 this.element = element;
1221 // all private functions should use 'that', instead of 'this'
1224 /* error() - private
1225 * show an error instead of the chart
1227 var error = function(msg) {
1230 if(typeof netdataErrorCallback === 'function') {
1231 ret = netdataErrorCallback('chart', that.id, msg);
1235 that.element.innerHTML = that.id + ': ' + msg;
1236 that.enabled = false;
1237 that.current = that.pan;
1241 // GUID - a unique identifier for the chart
1242 this.uuid = NETDATA.guid();
1244 // string - the name of chart
1245 this.id = self.data('netdata');
1247 // string - the key for localStorage settings
1248 this.settings_id = self.data('id') || null;
1250 // the user given dimensions of the element
1251 this.width = self.data('width') || NETDATA.chartDefaults.width;
1252 this.height = self.data('height') || NETDATA.chartDefaults.height;
1253 this.height_original = this.height;
1255 if(this.settings_id !== null) {
1256 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1257 // this is the callback that will be called
1258 // if and when the user resets all localStorage variables
1259 // to their defaults
1261 resizeChartToHeight(height);
1265 // string - the netdata server URL, without any path
1266 this.host = self.data('host') || NETDATA.chartDefaults.host;
1268 // make sure the host does not end with /
1269 // all netdata API requests use absolute paths
1270 while(this.host.slice(-1) === '/')
1271 this.host = this.host.substring(0, this.host.length - 1);
1273 // string - the grouping method requested by the user
1274 this.method = self.data('method') || NETDATA.chartDefaults.method;
1276 // the time-range requested by the user
1277 this.after = self.data('after') || NETDATA.chartDefaults.after;
1278 this.before = self.data('before') || NETDATA.chartDefaults.before;
1280 // the pixels per point requested by the user
1281 this.pixels_per_point = self.data('pixels-per-point') || 1;
1282 this.points = self.data('points') || null;
1284 // the dimensions requested by the user
1285 this.dimensions = self.data('dimensions') || null;
1287 // the chart library requested by the user
1288 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1290 // how many retries we have made to load chart data from the server
1291 this.retries_on_data_failures = 0;
1293 // object - the chart library used
1294 this.library = null;
1298 this.colors_assigned = {};
1299 this.colors_available = null;
1301 // the element already created by the user
1302 this.element_message = null;
1304 // the element with the chart
1305 this.element_chart = null;
1307 // the element with the legend of the chart (if created by us)
1308 this.element_legend = null;
1309 this.element_legend_childs = {
1314 perfect_scroller: null, // the container to apply perfect scroller to
1318 this.chart_url = null; // string - the url to download chart info
1319 this.chart = null; // object - the chart as downloaded from the server
1321 this.title = self.data('title') || null; // the title of the chart
1322 this.units = self.data('units') || null; // the units of the chart dimensions
1323 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1324 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1326 this.running = false; // boolean - true when the chart is being refreshed now
1327 this.enabled = true; // boolean - is the chart enabled for refresh?
1328 this.paused = false; // boolean - is the chart paused for any reason?
1329 this.selected = false; // boolean - is the chart shown a selection?
1330 this.debug = false; // boolean - console.log() debug info about this chart
1332 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1333 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1334 this.requested_after = null; // milliseconds - the timestamp of the request after param
1335 this.requested_before = null; // milliseconds - the timestamp of the request before param
1336 this.requested_padding = null;
1337 this.view_after = 0;
1338 this.view_before = 0;
1340 this.value_decimal_detail = -1;
1341 var d = self.data('decimal-digits');
1342 if(typeof d === 'number') {
1343 this.value_decimal_detail = 1;
1345 this.value_decimal_detail *= 10;
1351 force_update_at: 0, // the timestamp to force the update at
1352 force_before_ms: null,
1353 force_after_ms: null
1358 force_update_at: 0, // the timestamp to force the update at
1359 force_before_ms: null,
1360 force_after_ms: null
1365 force_update_at: 0, // the timestamp to force the update at
1366 force_before_ms: null,
1367 force_after_ms: null
1370 // this is a pointer to one of the sub-classes below
1372 this.current = this.auto;
1374 // check the requested library is available
1375 // we don't initialize it here - it will be initialized when
1376 // this chart will be first used
1377 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1378 NETDATA.error(402, that.library_name);
1379 error('chart library "' + that.library_name + '" is not found');
1382 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1383 NETDATA.error(403, that.library_name);
1384 error('chart library "' + that.library_name + '" is not enabled');
1388 that.library = NETDATA.chartLibraries[that.library_name];
1390 // milliseconds - the time the last refresh took
1391 this.refresh_dt_ms = 0;
1393 // if we need to report the rendering speed
1394 // find the element that needs to be updated
1395 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1397 if(refresh_dt_element_name !== null) {
1398 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1401 this.refresh_dt_element = null;
1403 this.dimensions_visibility = new dimensionsVisibility(this);
1405 this._updating = false;
1407 // ============================================================================================================
1408 // PRIVATE FUNCTIONS
1410 var createDOM = function() {
1411 if(that.enabled === false) return;
1413 if(that.element_message !== null) that.element_message.innerHTML = '';
1414 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1415 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1417 that.element.innerHTML = '';
1419 that.element_message = document.createElement('div');
1420 that.element_message.className = 'netdata-message icon hidden';
1421 that.element.appendChild(that.element_message);
1423 that.element_chart = document.createElement('div');
1424 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1425 that.element.appendChild(that.element_chart);
1427 if(that.hasLegend() === true) {
1428 that.element.className = "netdata-container-with-legend";
1429 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1431 that.element_legend = document.createElement('div');
1432 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1433 that.element.appendChild(that.element_legend);
1436 that.element.className = "netdata-container";
1437 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1439 that.element_legend = null;
1441 that.element_legend_childs.series = null;
1443 if(typeof(that.width) === 'string')
1444 $(that.element).css('width', that.width);
1445 else if(typeof(that.width) === 'number')
1446 $(that.element).css('width', that.width + 'px');
1448 if(typeof(that.library.aspect_ratio) === 'undefined') {
1449 if(typeof(that.height) === 'string')
1450 that.element.style.height = that.height;
1451 else if(typeof(that.height) === 'number')
1452 that.element.style.height = that.height.toString() + 'px';
1455 var w = that.element.offsetWidth;
1456 if(w === null || w === 0) {
1457 // the div is hidden
1458 // this will resize the chart when next viewed
1459 that.tm.last_resized = 0;
1462 that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1465 if(NETDATA.chartDefaults.min_width !== null)
1466 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1468 that.tm.last_dom_created = Date.now();
1474 * initialize state variables
1475 * destroy all (possibly) created state elements
1476 * create the basic DOM for a chart
1478 var init = function() {
1479 if(that.enabled === false) return;
1481 that.paused = false;
1482 that.selected = false;
1484 that.chart_created = false; // boolean - is the library.create() been called?
1485 that.updates_counter = 0; // numeric - the number of refreshes made so far
1486 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1487 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1490 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1491 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1492 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1494 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1495 last_updated: 0, // the timestamp the chart last updated with data
1496 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1498 // Used with NETDATA.globalPanAndZoom.seq
1499 last_visible_check: 0, // the time we last checked if it is visible
1500 last_resized: 0, // the time the chart was resized
1501 last_hidden: 0, // the time the chart was hidden
1502 last_unhidden: 0, // the time the chart was unhidden
1503 last_autorefreshed: 0 // the time the chart was last refreshed
1506 that.data = null; // the last data as downloaded from the netdata server
1507 that.data_url = 'invalid://'; // string - the last url used to update the chart
1508 that.data_points = 0; // number - the number of points returned from netdata
1509 that.data_after = 0; // milliseconds - the first timestamp of the data
1510 that.data_before = 0; // milliseconds - the last timestamp of the data
1511 that.data_update_every = 0; // milliseconds - the frequency to update the data
1513 that.tm.last_initialized = Date.now();
1516 that.setMode('auto');
1519 var maxMessageFontSize = function() {
1520 var screenHeight = screen.height;
1521 var el = that.element;
1523 // normally we want a font size, as tall as the element
1524 var h = el.clientHeight;
1526 // but give it some air, 20% let's say, or 5 pixels min
1527 var lost = Math.max(h * 0.2, 5);
1530 // center the text, vertically
1531 var paddingTop = (lost - 5) / 2;
1533 // but check the width too
1534 // it should fit 10 characters in it
1535 var w = el.clientWidth / 10;
1537 paddingTop += (h - w) / 2;
1541 // and don't make it too huge
1542 // 5% of the screen size is good
1543 if(h > screenHeight / 20) {
1544 paddingTop += (h - (screenHeight / 20)) / 2;
1545 h = screenHeight / 20;
1549 that.element_message.style.fontSize = h.toString() + 'px';
1550 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1553 var showMessageIcon = function(icon) {
1554 that.element_message.innerHTML = icon;
1555 maxMessageFontSize();
1556 $(that.element_message).removeClass('hidden');
1557 that.___messageHidden___ = undefined;
1560 var hideMessage = function() {
1561 if(typeof that.___messageHidden___ === 'undefined') {
1562 that.___messageHidden___ = true;
1563 $(that.element_message).addClass('hidden');
1567 var showRendering = function() {
1569 if(that.chart !== null) {
1570 if(that.chart.chart_type === 'line')
1571 icon = '<i class="fa fa-line-chart"></i>';
1573 icon = '<i class="fa fa-area-chart"></i>';
1576 icon = '<i class="fa fa-area-chart"></i>';
1578 showMessageIcon(icon + ' netdata');
1581 var showLoading = function() {
1582 if(that.chart_created === false) {
1583 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1589 var isHidden = function() {
1590 return (typeof that.___chartIsHidden___ !== 'undefined');
1593 // hide the chart, when it is not visible - called from isVisible()
1594 var hideChart = function() {
1595 // hide it, if it is not already hidden
1596 if(isHidden() === true) return;
1598 if(that.chart_created === true) {
1599 if(NETDATA.options.current.destroy_on_hide === true) {
1600 // we should destroy it
1605 that.element_chart.style.display = 'none';
1606 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1607 that.tm.last_hidden = Date.now();
1610 // This works, but I not sure there are no corner cases somewhere
1611 // so it is commented - if the user has memory issues he can
1612 // set Destroy on Hide for all charts
1613 // that.data = null;
1617 that.___chartIsHidden___ = true;
1620 // unhide the chart, when it is visible - called from isVisible()
1621 var unhideChart = function() {
1622 if(isHidden() === false) return;
1624 that.___chartIsHidden___ = undefined;
1625 that.updates_since_last_unhide = 0;
1627 if(that.chart_created === false) {
1628 // we need to re-initialize it, to show our background
1629 // logo in bootstrap tabs, until the chart loads
1633 that.tm.last_unhidden = Date.now();
1634 that.element_chart.style.display = '';
1635 if(that.element_legend !== null) that.element_legend.style.display = '';
1641 var canBeRendered = function() {
1642 return (isHidden() === false && that.isVisible(true) === true);
1645 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1646 var callChartLibraryUpdateSafely = function(data) {
1649 if(canBeRendered() === false)
1652 if(NETDATA.options.debug.chart_errors === true)
1653 status = that.library.update(that, data);
1656 status = that.library.update(that, data);
1663 if(status === false) {
1664 error('chart failed to be updated as ' + that.library_name);
1671 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1672 var callChartLibraryCreateSafely = function(data) {
1675 if(canBeRendered() === false)
1678 if(NETDATA.options.debug.chart_errors === true)
1679 status = that.library.create(that, data);
1682 status = that.library.create(that, data);
1689 if(status === false) {
1690 error('chart failed to be created as ' + that.library_name);
1694 that.chart_created = true;
1695 that.updates_since_last_creation = 0;
1699 // ----------------------------------------------------------------------------------------------------------------
1702 // resizeChart() - private
1703 // to be called just before the chart library to make sure that
1704 // a properly sized dom is available
1705 var resizeChart = function() {
1706 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1707 if(that.chart_created === false) return;
1709 if(that.needsRecreation()) {
1712 else if(typeof that.library.resize === 'function') {
1713 that.library.resize(that);
1715 if(that.element_legend_childs.perfect_scroller !== null)
1716 Ps.update(that.element_legend_childs.perfect_scroller);
1718 maxMessageFontSize();
1721 that.tm.last_resized = Date.now();
1725 // this is the actual chart resize algorithm
1727 // - resize the entire container
1728 // - update the internal states
1729 // - resize the chart as the div changes height
1730 // - update the scrollbar of the legend
1731 var resizeChartToHeight = function(h) {
1733 that.element.style.height = h;
1735 if(that.settings_id !== null)
1736 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1738 var now = Date.now();
1739 NETDATA.options.last_page_scroll = now;
1740 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1743 that.tm.last_resized = 0;
1747 this.resizeHandler = function(e) {
1750 if(typeof this.event_resize === 'undefined'
1751 || this.event_resize.chart_original_w === 'undefined'
1752 || this.event_resize.chart_original_h === 'undefined')
1753 this.event_resize = {
1754 chart_original_w: this.element.clientWidth,
1755 chart_original_h: this.element.clientHeight,
1759 if(e.type === 'touchstart') {
1760 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1761 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1764 this.event_resize.mouse_start_x = e.clientX;
1765 this.event_resize.mouse_start_y = e.clientY;
1768 this.event_resize.chart_start_w = this.element.clientWidth;
1769 this.event_resize.chart_start_h = this.element.clientHeight;
1770 this.event_resize.chart_last_w = this.element.clientWidth;
1771 this.event_resize.chart_last_h = this.element.clientHeight;
1773 var now = Date.now();
1774 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1775 // double click / double tap event
1777 // console.dir(this.element_legend_childs.content);
1778 // console.dir(this.element_legend_childs.perfect_scroller);
1780 // the optimal height of the chart
1781 // showing the entire legend
1782 var optimal = this.event_resize.chart_last_h
1783 + this.element_legend_childs.perfect_scroller.scrollHeight
1784 - this.element_legend_childs.perfect_scroller.clientHeight;
1786 // if we are not optimal, be optimal
1787 if(this.event_resize.chart_last_h !== optimal) {
1788 // 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());
1789 resizeChartToHeight(optimal.toString() + 'px');
1792 // else if the current height is not the original/saved height
1793 // reset to the original/saved height
1794 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1795 // 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());
1796 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1799 // else if the current height is not the internal default height
1800 // reset to the internal default height
1801 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1802 // 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());
1803 resizeChartToHeight(this.height_original.toString());
1806 // else if the current height is not the firstchild's clientheight
1808 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1809 var parent_rect = this.element.getBoundingClientRect();
1810 var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1811 var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1813 // console.log(parent_rect);
1814 // console.log(content_rect);
1815 // console.log(wanted);
1817 // 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' );
1818 if(this.event_resize.chart_last_h !== wanted)
1819 resizeChartToHeight(wanted.toString() + 'px');
1823 this.event_resize.last = now;
1825 // process movement event
1826 document.onmousemove =
1827 document.ontouchmove =
1828 this.element_legend_childs.resize_handler.onmousemove =
1829 this.element_legend_childs.resize_handler.ontouchmove =
1834 case 'mousemove': y = e.clientY; break;
1835 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1839 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1841 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1842 resizeChartToHeight(newH.toString() + 'px');
1843 that.event_resize.chart_last_h = newH;
1848 // process end event
1849 document.onmouseup =
1850 document.ontouchend =
1851 this.element_legend_childs.resize_handler.onmouseup =
1852 this.element_legend_childs.resize_handler.ontouchend =
1856 // remove all the hooks
1857 document.onmouseup =
1858 document.onmousemove =
1859 document.ontouchmove =
1860 document.ontouchend =
1861 that.element_legend_childs.resize_handler.onmousemove =
1862 that.element_legend_childs.resize_handler.ontouchmove =
1863 that.element_legend_childs.resize_handler.onmouseout =
1864 that.element_legend_childs.resize_handler.onmouseup =
1865 that.element_legend_childs.resize_handler.ontouchend =
1868 // allow auto-refreshes
1869 NETDATA.options.auto_refresher_stop_until = 0;
1875 var noDataToShow = function() {
1876 showMessageIcon('<i class="fa fa-warning"></i> empty');
1877 that.legendUpdateDOM();
1878 that.tm.last_autorefreshed = Date.now();
1879 // that.data_update_every = 30 * 1000;
1880 //that.element_chart.style.display = 'none';
1881 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1882 //that.___chartIsHidden___ = true;
1885 // ============================================================================================================
1888 this.error = function(msg) {
1892 this.setMode = function(m) {
1893 if(this.current !== null && this.current.name === m) return;
1896 this.current = this.auto;
1897 else if(m === 'pan')
1898 this.current = this.pan;
1899 else if(m === 'zoom')
1900 this.current = this.zoom;
1902 this.current = this.auto;
1904 this.current.force_update_at = 0;
1905 this.current.force_before_ms = null;
1906 this.current.force_after_ms = null;
1908 this.tm.last_mode_switch = Date.now();
1911 // ----------------------------------------------------------------------------------------------------------------
1912 // global selection sync
1914 // prevent to global selection sync for some time
1915 this.globalSelectionSyncDelay = function(ms) {
1916 if(NETDATA.options.current.sync_selection === false)
1919 if(typeof ms === 'number')
1920 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1922 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1925 // can we globally apply selection sync?
1926 this.globalSelectionSyncAbility = function() {
1927 if(NETDATA.options.current.sync_selection === false)
1930 return (NETDATA.globalSelectionSync.dont_sync_before <= Date.now());
1933 this.globalSelectionSyncIsMaster = function() {
1934 return (NETDATA.globalSelectionSync.state === this);
1937 // this chart is the master of the global selection sync
1938 this.globalSelectionSyncBeMaster = function() {
1940 if(this.globalSelectionSyncIsMaster()) {
1941 if(this.debug === true)
1942 this.log('sync: I am the master already.');
1947 if(NETDATA.globalSelectionSync.state) {
1948 if(this.debug === true)
1949 this.log('sync: I am not the sync master. Resetting global sync.');
1951 this.globalSelectionSyncStop();
1954 // become the master
1955 if(this.debug === true)
1956 this.log('sync: becoming sync master.');
1958 this.selected = true;
1959 NETDATA.globalSelectionSync.state = this;
1961 // find the all slaves
1962 var targets = NETDATA.options.targets;
1963 var len = targets.length;
1965 var st = targets[len];
1968 if(this.debug === true)
1969 st.log('sync: not adding me to sync');
1971 else if(st.globalSelectionSyncIsEligible()) {
1972 if(this.debug === true)
1973 st.log('sync: adding to sync as slave');
1975 st.globalSelectionSyncBeSlave();
1979 // this.globalSelectionSyncDelay(100);
1982 // can the chart participate to the global selection sync as a slave?
1983 this.globalSelectionSyncIsEligible = function() {
1984 return (this.enabled === true
1985 && this.library !== null
1986 && typeof this.library.setSelection === 'function'
1987 && this.isVisible() === true
1988 && 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 this.selected = (this.library.setSelection(this, t) === true);
2050 this.selected = true;
2052 if(this.selected === true && this.debug === true)
2053 this.log('selection set to ' + t.toString());
2055 return this.selected;
2058 this.clearSelection = function() {
2059 if(this.selected === true) {
2060 if(typeof this.library.clearSelection === 'function')
2061 this.selected = (this.library.clearSelection(this) !== true);
2063 this.selected = false;
2065 if(this.selected === false && this.debug === true)
2066 this.log('selection cleared');
2071 return this.selected;
2074 // find if a timestamp (ms) is shown in the current chart
2075 this.timeIsVisible = function(t) {
2076 return (t >= this.data_after && t <= this.data_before);
2079 this.calculateRowForTime = function(t) {
2080 if(this.timeIsVisible(t) === false) return -1;
2081 return Math.floor((t - this.data_after) / this.data_update_every);
2084 // ----------------------------------------------------------------------------------------------------------------
2087 this.log = function(msg) {
2088 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2091 this.pauseChart = function() {
2092 if(this.paused === false) {
2093 if(this.debug === true)
2094 this.log('pauseChart()');
2100 this.unpauseChart = function() {
2101 if(this.paused === true) {
2102 if(this.debug === true)
2103 this.log('unpauseChart()');
2105 this.paused = false;
2109 this.resetChart = function(dont_clear_master, dont_update) {
2110 if(this.debug === true)
2111 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2113 if(typeof dont_clear_master === 'undefined')
2114 dont_clear_master = false;
2116 if(typeof dont_update === 'undefined')
2117 dont_update = false;
2119 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2120 if(this.debug === true)
2121 this.log('resetChart() diverting to clearMaster().');
2122 // this will call us back with master === true
2123 NETDATA.globalPanAndZoom.clearMaster();
2127 this.clearSelection();
2129 this.tm.pan_and_zoom_seq = 0;
2131 this.setMode('auto');
2132 this.current.force_update_at = 0;
2133 this.current.force_before_ms = null;
2134 this.current.force_after_ms = null;
2135 this.tm.last_autorefreshed = 0;
2136 this.paused = false;
2137 this.selected = false;
2138 this.enabled = true;
2139 // this.debug = false;
2141 // do not update the chart here
2142 // or the chart will flip-flop when it is the master
2143 // of a selection sync and another chart becomes
2146 if(dont_update !== true && this.isVisible() === true) {
2151 this.updateChartPanOrZoom = function(after, before) {
2152 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2155 if(this.debug === true)
2158 if(before < after) {
2159 if(this.debug === true)
2160 this.log(logme + 'flipped parameters, rejecting it.');
2165 if(typeof this.fixed_min_duration === 'undefined')
2166 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2168 var min_duration = this.fixed_min_duration;
2169 var current_duration = Math.round(this.view_before - this.view_after);
2171 // round the numbers
2172 after = Math.round(after);
2173 before = Math.round(before);
2175 // align them to update_every
2176 // stretching them further away
2177 after -= after % this.data_update_every;
2178 before += this.data_update_every - (before % this.data_update_every);
2180 // the final wanted duration
2181 var wanted_duration = before - after;
2183 // to allow panning, accept just a point below our minimum
2184 if((current_duration - this.data_update_every) < min_duration)
2185 min_duration = current_duration - this.data_update_every;
2187 // we do it, but we adjust to minimum size and return false
2188 // when the wanted size is below the current and the minimum
2190 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2191 if(this.debug === true)
2192 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2194 min_duration = this.fixed_min_duration;
2196 var dt = (min_duration - wanted_duration) / 2;
2199 wanted_duration = before - after;
2203 var tolerance = this.data_update_every * 2;
2204 var movement = Math.abs(before - this.view_before);
2206 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2207 if(this.debug === true)
2208 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);
2212 if(this.current.name === 'auto') {
2213 this.log(logme + 'caller called me with mode: ' + this.current.name);
2214 this.setMode('pan');
2217 if(this.debug === true)
2218 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);
2220 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2221 this.current.force_after_ms = after;
2222 this.current.force_before_ms = before;
2223 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2227 this.legendFormatValue = function(value) {
2228 if(value === null || value === 'undefined') return '-';
2229 if(typeof value !== 'number') return value;
2231 if(this.value_decimal_detail !== -1)
2232 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2234 var abs = Math.abs(value);
2235 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2236 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2237 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2238 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2239 return (Math.round(value * 10000) / 10000).toLocaleString();
2242 this.legendSetLabelValue = function(label, value) {
2243 var series = this.element_legend_childs.series[label];
2244 if(typeof series === 'undefined') return;
2245 if(series.value === null && series.user === null) return;
2248 // this slows down firefox and edge significantly
2249 // since it requires to use innerHTML(), instead of innerText()
2251 // if the value has not changed, skip DOM update
2252 //if(series.last === value) return;
2255 if(typeof value === 'number') {
2256 var v = Math.abs(value);
2257 s = r = this.legendFormatValue(value);
2259 if(typeof series.last === 'number') {
2260 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2261 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2262 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2264 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2274 series.last = value;
2278 var s = this.legendFormatValue(value);
2280 // caching: do not update the update to show the same value again
2281 if(s === series.last_shown_value) return;
2282 series.last_shown_value = s;
2284 if(series.value !== null) series.value.innerText = s;
2285 if(series.user !== null) series.user.innerText = s;
2288 this.__legendSetDateString = function(date) {
2289 if(date !== this.__last_shown_legend_date) {
2290 this.element_legend_childs.title_date.innerText = date;
2291 this.__last_shown_legend_date = date;
2295 this.__legendSetTimeString = function(time) {
2296 if(time !== this.__last_shown_legend_time) {
2297 this.element_legend_childs.title_time.innerText = time;
2298 this.__last_shown_legend_time = time;
2302 this.__legendSetUnitsString = function(units) {
2303 if(units !== this.__last_shown_legend_units) {
2304 this.element_legend_childs.title_units.innerText = units;
2305 this.__last_shown_legend_units = units;
2309 this.legendSetDate = function(ms) {
2310 if(typeof ms !== 'number') {
2311 this.legendShowUndefined();
2315 var d = new Date(ms);
2317 if(this.element_legend_childs.title_date)
2318 this.__legendSetDateString(d.toLocaleDateString());
2320 if(this.element_legend_childs.title_time)
2321 this.__legendSetTimeString(d.toLocaleTimeString());
2323 if(this.element_legend_childs.title_units)
2324 this.__legendSetUnitsString(this.units)
2327 this.legendShowUndefined = function() {
2328 if(this.element_legend_childs.title_date)
2329 this.__legendSetDateString(' ');
2331 if(this.element_legend_childs.title_time)
2332 this.__legendSetTimeString(this.chart.name);
2334 if(this.element_legend_childs.title_units)
2335 this.__legendSetUnitsString(' ');
2337 if(this.data && this.element_legend_childs.series !== null) {
2338 var labels = this.data.dimension_names;
2339 var i = labels.length;
2341 var label = labels[i];
2343 if(typeof label === 'undefined') continue;
2344 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2345 this.legendSetLabelValue(label, null);
2350 this.legendShowLatestValues = function() {
2351 if(this.chart === null) return;
2352 if(this.selected) return;
2354 if(this.data === null || this.element_legend_childs.series === null) {
2355 this.legendShowUndefined();
2359 var show_undefined = true;
2360 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2361 show_undefined = false;
2363 if(show_undefined) {
2364 this.legendShowUndefined();
2368 this.legendSetDate(this.view_before);
2370 var labels = this.data.dimension_names;
2371 var i = labels.length;
2373 var label = labels[i];
2375 if(typeof label === 'undefined') continue;
2376 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2379 this.legendSetLabelValue(label, null);
2381 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2385 this.legendReset = function() {
2386 this.legendShowLatestValues();
2389 // this should be called just ONCE per dimension per chart
2390 this._chartDimensionColor = function(label) {
2391 if(this.colors === null) this.chartColors();
2393 if(typeof this.colors_assigned[label] === 'undefined') {
2394 if(this.colors_available.length === 0) {
2395 var len = NETDATA.themes.current.colors.length;
2397 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2400 this.colors_assigned[label] = this.colors_available.shift();
2402 if(this.debug === true)
2403 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2406 if(this.debug === true)
2407 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2410 this.colors.push(this.colors_assigned[label]);
2411 return this.colors_assigned[label];
2414 this.chartColors = function() {
2415 if(this.colors !== null) return this.colors;
2418 this.colors_available = [];
2420 // add the standard colors
2421 var len = NETDATA.themes.current.colors.length;
2423 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2425 // add the user supplied colors
2426 var c = $(this.element).data('colors');
2427 // this.log('read colors: ' + c);
2428 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2429 if(typeof c !== 'string') {
2430 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2440 this.colors_available.unshift(c[len]);
2441 // this.log('adding color: ' + c[len]);
2450 this.legendUpdateDOM = function() {
2451 var needed = false, dim, keys, len, i;
2453 // check that the legend DOM is up to date for the downloaded dimensions
2454 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2455 // this.log('the legend does not have any series - requesting legend update');
2458 else if(this.data === null) {
2459 // this.log('the chart does not have any data - requesting legend update');
2462 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2466 var labels = this.data.dimension_names.toString();
2467 if(labels !== this.element_legend_childs.series.labels_key) {
2470 if(this.debug === true)
2471 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2475 if(needed === false) {
2476 // make sure colors available
2479 // do we have to update the current values?
2480 // we do this, only when the visible chart is current
2481 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2482 if(this.debug === true)
2483 this.log('chart is in latest position... updating values on legend...');
2485 //var labels = this.data.dimension_names;
2486 //var i = labels.length;
2488 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2492 if(this.colors === null) {
2493 // this is the first time we update the chart
2494 // let's assign colors to all dimensions
2495 if(this.library.track_colors() === true) {
2496 keys = Object.keys(this.chart.dimensions);
2498 for(i = 0; i < len ;i++)
2499 this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2502 // we will re-generate the colors for the chart
2503 // based on the selected dimensions
2506 if(this.debug === true)
2507 this.log('updating Legend DOM');
2509 // mark all dimensions as invalid
2510 this.dimensions_visibility.invalidateAll();
2512 var genLabel = function(state, parent, dim, name, count) {
2513 var color = state._chartDimensionColor(name);
2515 var user_element = null;
2516 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2517 if(user_id === null)
2518 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2519 if(user_id !== null) {
2520 user_element = document.getElementById(user_id) || null;
2521 if (user_element === null)
2522 state.log('Cannot find element with id: ' + user_id);
2525 state.element_legend_childs.series[name] = {
2526 name: document.createElement('span'),
2527 value: document.createElement('span'),
2530 last_shown_value: null
2533 var label = state.element_legend_childs.series[name];
2535 // create the dimension visibility tracking for this label
2536 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2538 var rgb = NETDATA.colorHex2Rgb(color);
2539 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2540 + state.chart.chart_type
2541 + '" style="background-color: '
2542 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2543 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2545 var text = document.createTextNode(' ' + name);
2546 label.name.appendChild(text);
2549 parent.appendChild(document.createElement('br'));
2551 parent.appendChild(label.name);
2552 parent.appendChild(label.value);
2555 var content = document.createElement('div');
2557 if(this.hasLegend()) {
2558 this.element_legend_childs = {
2560 resize_handler: document.createElement('div'),
2561 toolbox: document.createElement('div'),
2562 toolbox_left: document.createElement('div'),
2563 toolbox_right: document.createElement('div'),
2564 toolbox_reset: document.createElement('div'),
2565 toolbox_zoomin: document.createElement('div'),
2566 toolbox_zoomout: document.createElement('div'),
2567 toolbox_volume: document.createElement('div'),
2568 title_date: document.createElement('span'),
2569 title_time: document.createElement('span'),
2570 title_units: document.createElement('span'),
2571 perfect_scroller: document.createElement('div'),
2575 this.element_legend.innerHTML = '';
2577 if(this.library.toolboxPanAndZoom !== null) {
2579 var get_pan_and_zoom_step = function(event) {
2581 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2583 else if (event.shiftKey)
2584 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2586 else if (event.altKey)
2587 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2590 return NETDATA.options.current.pan_and_zoom_factor;
2593 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2594 this.element.appendChild(this.element_legend_childs.toolbox);
2596 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2597 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2598 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2599 this.element_legend_childs.toolbox_left.onclick = function(e) {
2602 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2603 var before = that.view_before - step;
2604 var after = that.view_after - step;
2605 if(after >= that.netdata_first)
2606 that.library.toolboxPanAndZoom(that, after, before);
2608 if(NETDATA.options.current.show_help === true)
2609 $(this.element_legend_childs.toolbox_left).popover({
2614 placement: 'bottom',
2615 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2617 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>'
2621 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2622 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2623 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2624 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2626 NETDATA.resetAllCharts(that);
2628 if(NETDATA.options.current.show_help === true)
2629 $(this.element_legend_childs.toolbox_reset).popover({
2634 placement: 'bottom',
2635 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2636 title: 'Chart Reset',
2637 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>'
2640 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2641 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2642 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2643 this.element_legend_childs.toolbox_right.onclick = function(e) {
2645 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2646 var before = that.view_before + step;
2647 var after = that.view_after + step;
2648 if(before <= that.netdata_last)
2649 that.library.toolboxPanAndZoom(that, after, before);
2651 if(NETDATA.options.current.show_help === true)
2652 $(this.element_legend_childs.toolbox_right).popover({
2657 placement: 'bottom',
2658 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2660 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>'
2664 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2665 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2666 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2667 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2669 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2670 var before = that.view_before - dt;
2671 var after = that.view_after + dt;
2672 that.library.toolboxPanAndZoom(that, after, before);
2674 if(NETDATA.options.current.show_help === true)
2675 $(this.element_legend_childs.toolbox_zoomin).popover({
2680 placement: 'bottom',
2681 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2682 title: 'Chart Zoom In',
2683 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>'
2686 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2687 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2688 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2689 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2691 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);
2692 var before = that.view_before + dt;
2693 var after = that.view_after - dt;
2695 that.library.toolboxPanAndZoom(that, after, before);
2697 if(NETDATA.options.current.show_help === true)
2698 $(this.element_legend_childs.toolbox_zoomout).popover({
2703 placement: 'bottom',
2704 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2705 title: 'Chart Zoom Out',
2706 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>'
2709 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2710 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2711 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2712 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2713 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2714 //e.preventDefault();
2715 //alert('clicked toolbox_volume on ' + that.id);
2719 this.element_legend_childs.toolbox = null;
2720 this.element_legend_childs.toolbox_left = null;
2721 this.element_legend_childs.toolbox_reset = null;
2722 this.element_legend_childs.toolbox_right = null;
2723 this.element_legend_childs.toolbox_zoomin = null;
2724 this.element_legend_childs.toolbox_zoomout = null;
2725 this.element_legend_childs.toolbox_volume = null;
2728 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2729 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2730 this.element.appendChild(this.element_legend_childs.resize_handler);
2731 if(NETDATA.options.current.show_help === true)
2732 $(this.element_legend_childs.resize_handler).popover({
2737 placement: 'bottom',
2738 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2739 title: 'Chart Resize',
2740 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>'
2744 this.element_legend_childs.resize_handler.onmousedown =
2746 that.resizeHandler(e);
2750 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2751 that.resizeHandler(e);
2754 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2755 this.element_legend.appendChild(this.element_legend_childs.title_date);
2757 this.element_legend.appendChild(document.createElement('br'));
2759 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2760 this.element_legend.appendChild(this.element_legend_childs.title_time);
2762 this.element_legend.appendChild(document.createElement('br'));
2764 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2765 this.element_legend.appendChild(this.element_legend_childs.title_units);
2767 this.element_legend.appendChild(document.createElement('br'));
2769 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2770 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2772 content.className = 'netdata-legend-series-content';
2773 this.element_legend_childs.perfect_scroller.appendChild(content);
2775 if(NETDATA.options.current.show_help === true)
2776 $(content).popover({
2781 placement: 'bottom',
2782 title: 'Chart Legend',
2783 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2784 content: 'You can click or tap on the values or the labels to select dimensions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2788 this.element_legend_childs = {
2790 resize_handler: null,
2793 toolbox_right: null,
2794 toolbox_reset: null,
2795 toolbox_zoomin: null,
2796 toolbox_zoomout: null,
2797 toolbox_volume: null,
2801 perfect_scroller: null,
2807 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2808 if(this.debug === true)
2809 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2811 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2812 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2817 keys = Object.keys(this.chart.dimensions);
2818 for(i = 0, len = keys.length; i < len ;i++) {
2820 tmp.push(this.chart.dimensions[dim].name);
2821 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2823 this.element_legend_childs.series.labels_key = tmp.toString();
2824 if(this.debug === true)
2825 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2828 // create a hidden div to be used for hidding
2829 // the original legend of the chart library
2830 var el = document.createElement('div');
2831 if(this.element_legend !== null)
2832 this.element_legend.appendChild(el);
2833 el.style.display = 'none';
2835 this.element_legend_childs.hidden = document.createElement('div');
2836 el.appendChild(this.element_legend_childs.hidden);
2838 if(this.element_legend_childs.perfect_scroller !== null) {
2839 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2841 wheelPropagation: true,
2842 swipePropagation: true,
2843 minScrollbarLength: null,
2844 maxScrollbarLength: null,
2845 useBothWheelAxes: false,
2846 suppressScrollX: true,
2847 suppressScrollY: false,
2848 scrollXMarginOffset: 0,
2849 scrollYMarginOffset: 0,
2852 Ps.update(this.element_legend_childs.perfect_scroller);
2855 this.legendShowLatestValues();
2858 this.hasLegend = function() {
2859 if(typeof this.___hasLegendCache___ !== 'undefined')
2860 return this.___hasLegendCache___;
2863 if(this.library && this.library.legend(this) === 'right-side') {
2864 var legend = $(this.element).data('legend') || 'yes';
2865 if(legend === 'yes') leg = true;
2868 this.___hasLegendCache___ = leg;
2872 this.legendWidth = function() {
2873 return (this.hasLegend())?140:0;
2876 this.legendHeight = function() {
2877 return $(this.element).height();
2880 this.chartWidth = function() {
2881 return $(this.element).width() - this.legendWidth();
2884 this.chartHeight = function() {
2885 return $(this.element).height();
2888 this.chartPixelsPerPoint = function() {
2889 // force an options provided detail
2890 var px = this.pixels_per_point;
2892 if(this.library && px < this.library.pixels_per_point(this))
2893 px = this.library.pixels_per_point(this);
2895 if(px < NETDATA.options.current.pixels_per_point)
2896 px = NETDATA.options.current.pixels_per_point;
2901 this.needsRecreation = function() {
2903 this.chart_created === true
2905 && this.library.autoresize() === false
2906 && this.tm.last_resized < NETDATA.options.last_resized
2910 this.chartURL = function() {
2911 var after, before, points_multiplier = 1;
2912 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2913 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2915 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2916 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2917 this.view_after = after * 1000;
2918 this.view_before = before * 1000;
2920 this.requested_padding = null;
2921 points_multiplier = 1;
2923 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2924 this.tm.pan_and_zoom_seq = 0;
2926 before = Math.round(this.current.force_before_ms / 1000);
2927 after = Math.round(this.current.force_after_ms / 1000);
2928 this.view_after = after * 1000;
2929 this.view_before = before * 1000;
2931 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2932 this.requested_padding = Math.round((before - after) / 2);
2933 after -= this.requested_padding;
2934 before += this.requested_padding;
2935 this.requested_padding *= 1000;
2936 points_multiplier = 2;
2939 this.current.force_before_ms = null;
2940 this.current.force_after_ms = null;
2943 this.tm.pan_and_zoom_seq = 0;
2945 before = this.before;
2947 this.view_after = after * 1000;
2948 this.view_before = before * 1000;
2950 this.requested_padding = null;
2951 points_multiplier = 1;
2954 this.requested_after = after * 1000;
2955 this.requested_before = before * 1000;
2957 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2959 // build the data URL
2960 this.data_url = this.host + this.chart.data_url;
2961 this.data_url += "&format=" + this.library.format();
2962 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2963 this.data_url += "&group=" + this.method;
2965 if(this.override_options !== null)
2966 this.data_url += "&options=" + this.override_options.toString();
2968 this.data_url += "&options=" + this.library.options(this);
2970 this.data_url += '|jsonwrap';
2972 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2973 this.data_url += '|nonzero';
2975 if(this.append_options !== null)
2976 this.data_url += '|' + this.append_options.toString();
2979 this.data_url += "&after=" + after.toString();
2982 this.data_url += "&before=" + before.toString();
2985 this.data_url += "&dimensions=" + this.dimensions;
2987 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2988 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2991 this.redrawChart = function() {
2992 if(this.data !== null)
2993 this.updateChartWithData(this.data);
2996 this.updateChartWithData = function(data) {
2997 if(this.debug === true)
2998 this.log('updateChartWithData() called.');
3000 // this may force the chart to be re-created
3004 this.updates_counter++;
3005 this.updates_since_last_unhide++;
3006 this.updates_since_last_creation++;
3008 var started = Date.now();
3010 // if the result is JSON, find the latest update-every
3011 this.data_update_every = data.view_update_every * 1000;
3012 this.data_after = data.after * 1000;
3013 this.data_before = data.before * 1000;
3014 this.netdata_first = data.first_entry * 1000;
3015 this.netdata_last = data.last_entry * 1000;
3016 this.data_points = data.points;
3019 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3020 if(this.view_after < this.data_after) {
3021 // console.log('adjusting view_after from ' + this.view_after + ' to ' + this.data_after);
3022 this.view_after = this.data_after;
3025 if(this.view_before > this.data_before) {
3026 // console.log('adjusting view_before from ' + this.view_before + ' to ' + this.data_before);
3027 this.view_before = this.data_before;
3031 this.view_after = this.data_after;
3032 this.view_before = this.data_before;
3035 if(this.debug === true) {
3036 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3038 if(this.current.force_after_ms)
3039 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3041 this.log('STATUS: forced : unset');
3043 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3044 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3045 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3046 this.log('STATUS: points : ' + (this.data_points).toString());
3049 if(this.data_points === 0) {
3054 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3055 if(this.debug === true)
3056 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3058 this.chart_created = false;
3061 // check and update the legend
3062 this.legendUpdateDOM();
3064 if(this.chart_created === true
3065 && typeof this.library.update === 'function') {
3067 if(this.debug === true)
3068 this.log('updating chart...');
3070 if(callChartLibraryUpdateSafely(data) === false)
3074 if(this.debug === true)
3075 this.log('creating chart...');
3077 if(callChartLibraryCreateSafely(data) === false)
3081 this.legendShowLatestValues();
3082 if(this.selected === true)
3083 NETDATA.globalSelectionSync.stop();
3085 // update the performance counters
3086 var now = Date.now();
3087 this.tm.last_updated = now;
3089 // don't update last_autorefreshed if this chart is
3090 // forced to be updated with global PanAndZoom
3091 if(NETDATA.globalPanAndZoom.isActive())
3092 this.tm.last_autorefreshed = 0;
3094 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3095 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3097 this.tm.last_autorefreshed = now;
3100 this.refresh_dt_ms = now - started;
3101 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3103 if(this.refresh_dt_element !== null)
3104 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3107 this.updateChart = function(callback) {
3108 if(this.debug === true)
3109 this.log('updateChart() called.');
3111 if(this._updating === true) {
3112 if(this.debug === true)
3113 this.log('I am already updating...');
3115 if(typeof callback === 'function')
3121 // due to late initialization of charts and libraries
3122 // we need to check this too
3123 if(this.enabled === false) {
3124 if(this.debug === true)
3125 this.log('I am not enabled');
3127 if(typeof callback === 'function')
3133 if(canBeRendered() === false) {
3134 if(typeof callback === 'function')
3140 if(this.chart === null)
3141 return this.getChart(function() {
3142 return that.updateChart(callback);
3145 if(this.library.initialized === false) {
3146 if(this.library.enabled === true) {
3147 return this.library.initialize(function () {
3148 return that.updateChart(callback);
3152 error('chart library "' + this.library_name + '" is not available.');
3154 if(typeof callback === 'function')
3161 this.clearSelection();
3164 if(this.debug === true)
3165 this.log('updating from ' + this.data_url);
3167 NETDATA.statistics.refreshes_total++;
3168 NETDATA.statistics.refreshes_active++;
3170 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3171 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3173 this._updating = true;
3175 this.xhr = $.ajax( {
3180 'Cache-Control': 'no-cache, no-store',
3181 'Pragma': 'no-cache'
3183 xhrFields: { withCredentials: true } // required for the cookie
3185 .done(function(data) {
3186 that.xhr = undefined;
3187 that.retries_on_data_failures = 0;
3189 if(that.debug === true)
3190 that.log('data received. updating chart.');
3192 that.updateChartWithData(data);
3194 .fail(function(msg) {
3195 that.xhr = undefined;
3197 if(msg.statusText !== 'abort') {
3198 that.retries_on_data_failures++;
3199 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3200 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3201 that.retries_on_data_failures = 0;
3202 error('data download failed for url: ' + that.data_url);
3205 that.tm.last_autorefreshed = Date.now();
3206 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3210 .always(function() {
3211 that.xhr = undefined;
3213 NETDATA.statistics.refreshes_active--;
3214 that._updating = false;
3216 if(typeof callback === 'function')
3221 this.isVisible = function(nocache) {
3222 if(typeof nocache === 'undefined')
3225 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3227 // caching - we do not evaluate the charts visibility
3228 // if the page has not been scrolled since the last check
3229 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3230 return this.___isVisible___;
3232 this.tm.last_visible_check = Date.now();
3234 var wh = window.innerHeight;
3235 var x = this.element.getBoundingClientRect();
3239 if(x.width === 0 || x.height === 0) {
3241 this.___isVisible___ = false;
3242 return this.___isVisible___;
3245 if(x.top < 0 && -x.top > x.height) {
3246 // the chart is entirely above
3247 ret = -x.top - x.height;
3249 else if(x.top > wh) {
3250 // the chart is entirely below
3254 if(ret > tolerance) {
3255 // the chart is too far
3258 this.___isVisible___ = false;
3259 return this.___isVisible___;
3262 // the chart is inside or very close
3265 this.___isVisible___ = true;
3266 return this.___isVisible___;
3270 this.isAutoRefreshable = function() {
3271 return (this.current.autorefresh);
3274 this.canBeAutoRefreshed = function() {
3275 var now = Date.now();
3277 if(this.running === true) {
3278 if(this.debug === true)
3279 this.log('I am already running');
3284 if(this.enabled === false) {
3285 if(this.debug === true)
3286 this.log('I am not enabled');
3291 if(this.library === null || this.library.enabled === false) {
3292 error('charting library "' + this.library_name + '" is not available');
3293 if(this.debug === true)
3294 this.log('My chart library ' + this.library_name + ' is not available');
3299 if(this.isVisible() === false) {
3300 if(NETDATA.options.debug.visibility === true || this.debug === true)
3301 this.log('I am not visible');
3306 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3307 if(this.debug === true)
3308 this.log('timed force update detected - allowing this update');
3310 this.current.force_update_at = 0;
3314 if(this.isAutoRefreshable() === true) {
3315 // allow the first update, even if the page is not visible
3316 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3317 if(NETDATA.options.debug.focus === true || this.debug === true)
3318 this.log('canBeAutoRefreshed(): page does not have focus');
3323 if(this.needsRecreation() === true) {
3324 if(this.debug === true)
3325 this.log('canBeAutoRefreshed(): needs re-creation.');
3330 // options valid only for autoRefresh()
3331 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3332 if(NETDATA.globalPanAndZoom.isActive()) {
3333 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3334 if(this.debug === true)
3335 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3340 if(this.debug === true)
3341 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3347 if(this.selected === true) {
3348 if(this.debug === true)
3349 this.log('canBeAutoRefreshed(): I have a selection in place.');
3354 if(this.paused === true) {
3355 if(this.debug === true)
3356 this.log('canBeAutoRefreshed(): I am paused.');
3361 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3362 if(this.debug === true)
3363 this.log('canBeAutoRefreshed(): It is time to update me.');
3373 this.autoRefresh = function(callback) {
3374 if(this.canBeAutoRefreshed() === true && this.running === false) {
3377 state.running = true;
3378 state.updateChart(function() {
3379 state.running = false;
3381 if(typeof callback !== 'undefined')
3386 if(typeof callback !== 'undefined')
3391 this._defaultsFromDownloadedChart = function(chart) {
3393 this.chart_url = chart.url;
3394 this.data_update_every = chart.update_every * 1000;
3395 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3396 this.tm.last_info_downloaded = Date.now();
3398 if(this.title === null)
3399 this.title = chart.title;
3401 if(this.units === null)
3402 this.units = chart.units;
3405 // fetch the chart description from the netdata server
3406 this.getChart = function(callback) {
3407 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3409 this._defaultsFromDownloadedChart(this.chart);
3411 if(typeof callback === 'function')
3415 this.chart_url = "/api/v1/chart?chart=" + this.id;
3417 if(this.debug === true)
3418 this.log('downloading ' + this.chart_url);
3421 url: this.host + this.chart_url,
3424 xhrFields: { withCredentials: true } // required for the cookie
3426 .done(function(chart) {
3427 chart.url = that.chart_url;
3428 that._defaultsFromDownloadedChart(chart);
3429 NETDATA.chartRegistry.add(that.host, that.id, chart);
3432 NETDATA.error(404, that.chart_url);
3433 error('chart not found on url "' + that.chart_url + '"');
3435 .always(function() {
3436 if(typeof callback === 'function')
3442 // ============================================================================================================
3448 NETDATA.resetAllCharts = function(state) {
3449 // first clear the global selection sync
3450 // to make sure no chart is in selected state
3451 state.globalSelectionSyncStop();
3453 // there are 2 possibilities here
3454 // a. state is the global Pan and Zoom master
3455 // b. state is not the global Pan and Zoom master
3457 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3460 // clear the global Pan and Zoom
3461 // this will also refresh the master
3462 // and unblock any charts currently mirroring the master
3463 NETDATA.globalPanAndZoom.clearMaster();
3465 // if we were not the master, reset our status too
3466 // this is required because most probably the mouse
3467 // is over this chart, blocking it from auto-refreshing
3468 if(master === false && (state.paused === true || state.selected === true))
3472 // get or create a chart state, given a DOM element
3473 NETDATA.chartState = function(element) {
3474 var state = $(element).data('netdata-state-object') || null;
3475 if(state === null) {
3476 state = new chartState(element);
3477 $(element).data('netdata-state-object', state);
3482 // ----------------------------------------------------------------------------------------------------------------
3483 // Library functions
3485 // Load a script without jquery
3486 // This is used to load jquery - after it is loaded, we use jquery
3487 NETDATA._loadjQuery = function(callback) {
3488 if(typeof jQuery === 'undefined') {
3489 if(NETDATA.options.debug.main_loop === true)
3490 console.log('loading ' + NETDATA.jQuery);
3492 var script = document.createElement('script');
3493 script.type = 'text/javascript';
3494 script.async = true;
3495 script.src = NETDATA.jQuery;
3497 // script.onabort = onError;
3498 script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3499 if(typeof callback === "function")
3500 script.onload = callback;
3502 var s = document.getElementsByTagName('script')[0];
3503 s.parentNode.insertBefore(script, s);
3505 else if(typeof callback === "function")
3509 NETDATA._loadCSS = function(filename) {
3510 // don't use jQuery here
3511 // styles are loaded before jQuery
3512 // to eliminate showing an unstyled page to the user
3514 var fileref = document.createElement("link");
3515 fileref.setAttribute("rel", "stylesheet");
3516 fileref.setAttribute("type", "text/css");
3517 fileref.setAttribute("href", filename);
3519 if (typeof fileref !== 'undefined')
3520 document.getElementsByTagName("head")[0].appendChild(fileref);
3523 NETDATA.colorHex2Rgb = function(hex) {
3524 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3525 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3526 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3527 return r + r + g + g + b + b;
3530 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3532 r: parseInt(result[1], 16),
3533 g: parseInt(result[2], 16),
3534 b: parseInt(result[3], 16)
3538 NETDATA.colorLuminance = function(hex, lum) {
3539 // validate hex string
3540 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3542 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3546 // convert to decimal and change luminosity
3547 var rgb = "#", c, i;
3548 for (i = 0; i < 3; i++) {
3549 c = parseInt(hex.substr(i*2,2), 16);
3550 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3551 rgb += ("00"+c).substr(c.length);
3557 NETDATA.guid = function() {
3559 return Math.floor((1 + Math.random()) * 0x10000)
3564 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3567 NETDATA.zeropad = function(x) {
3568 if(x > -10 && x < 10) return '0' + x.toString();
3569 else return x.toString();
3572 // user function to signal us the DOM has been
3574 NETDATA.updatedDom = function() {
3575 NETDATA.options.updated_dom = true;
3578 NETDATA.ready = function(callback) {
3579 NETDATA.options.pauseCallback = callback;
3582 NETDATA.pause = function(callback) {
3583 if(typeof callback === 'function') {
3584 if (NETDATA.options.pause === true)
3587 NETDATA.options.pauseCallback = callback;
3591 NETDATA.unpause = function() {
3592 NETDATA.options.pauseCallback = null;
3593 NETDATA.options.updated_dom = true;
3594 NETDATA.options.pause = false;
3597 // ----------------------------------------------------------------------------------------------------------------
3599 // this is purely sequential charts refresher
3600 // it is meant to be autonomous
3601 NETDATA.chartRefresherNoParallel = function(index) {
3602 if(NETDATA.options.debug.main_loop === true)
3603 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3605 if(NETDATA.options.updated_dom === true) {
3606 // the dom has been updated
3607 // get the dom parts again
3608 NETDATA.parseDom(NETDATA.chartRefresher);
3611 if(index >= NETDATA.options.targets.length) {
3612 if(NETDATA.options.debug.main_loop === true)
3613 console.log('waiting to restart main loop...');
3615 NETDATA.options.auto_refresher_fast_weight = 0;
3617 setTimeout(function() {
3618 NETDATA.chartRefresher();
3619 }, NETDATA.options.current.idle_between_loops);
3622 var state = NETDATA.options.targets[index];
3624 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3625 if(NETDATA.options.debug.main_loop === true)
3626 console.log('fast rendering...');
3628 state.autoRefresh(function() {
3629 NETDATA.chartRefresherNoParallel(++index);
3633 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3634 NETDATA.options.auto_refresher_fast_weight = 0;
3636 setTimeout(function() {
3637 state.autoRefresh(function() {
3638 NETDATA.chartRefresherNoParallel(++index);
3640 }, NETDATA.options.current.idle_between_charts);
3645 NETDATA.chartRefresherWaitTime = function() {
3646 return NETDATA.options.current.idle_parallel_loops;
3649 // the default refresher
3650 NETDATA.chartRefresher = function() {
3651 // console.log('auto-refresher...');
3653 if(NETDATA.options.pause === true) {
3654 // console.log('auto-refresher is paused');
3655 setTimeout(NETDATA.chartRefresher,
3656 NETDATA.chartRefresherWaitTime());
3660 if(typeof NETDATA.options.pauseCallback === 'function') {
3661 // console.log('auto-refresher is calling pauseCallback');
3662 NETDATA.options.pause = true;
3663 NETDATA.options.pauseCallback();
3664 NETDATA.chartRefresher();
3668 if(NETDATA.options.current.parallel_refresher === false) {
3669 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3670 NETDATA.chartRefresherNoParallel(0);
3674 if(NETDATA.options.updated_dom === true) {
3675 // the dom has been updated
3676 // get the dom parts again
3677 // console.log('auto-refresher is calling parseDom()');
3678 NETDATA.parseDom(NETDATA.chartRefresher);
3683 var targets = NETDATA.options.targets;
3684 var len = targets.length;
3687 state = targets[len];
3688 if(state.isVisible() === false || state.running === true)
3691 if(state.library.initialized === false) {
3692 if(state.library.enabled === true) {
3693 state.library.initialize(NETDATA.chartRefresher);
3697 state.error('chart library "' + state.library_name + '" is not enabled.');
3701 parallel.unshift(state);
3704 if(parallel.length > 0) {
3705 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3706 // this will execute the jobs in parallel
3707 $(parallel).each(function() {
3712 // console.log('auto-refresher nothing to do');
3715 // run the next refresh iteration
3716 setTimeout(NETDATA.chartRefresher,
3717 NETDATA.chartRefresherWaitTime());
3720 NETDATA.parseDom = function(callback) {
3721 NETDATA.options.last_page_scroll = Date.now();
3722 NETDATA.options.updated_dom = false;
3724 var targets = $('div[data-netdata]'); //.filter(':visible');
3726 if(NETDATA.options.debug.main_loop === true)
3727 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3729 NETDATA.options.targets = [];
3730 var len = targets.length;
3732 // the initialization will take care of sizing
3733 // and the "loading..." message
3734 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3737 if(typeof callback === 'function')
3741 // this is the main function - where everything starts
3742 NETDATA.start = function() {
3743 // this should be called only once
3745 NETDATA.options.page_is_visible = true;
3747 $(window).blur(function() {
3748 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3749 NETDATA.options.page_is_visible = false;
3750 if(NETDATA.options.debug.focus === true)
3751 console.log('Lost Focus!');
3755 $(window).focus(function() {
3756 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3757 NETDATA.options.page_is_visible = true;
3758 if(NETDATA.options.debug.focus === true)
3759 console.log('Focus restored!');
3763 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3764 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3765 NETDATA.options.page_is_visible = false;
3766 if(NETDATA.options.debug.focus === true)
3767 console.log('Document has no focus!');
3771 // bootstrap tab switching
3772 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3774 // bootstrap modal switching
3775 var $modal = $('.modal');
3776 $modal.on('hidden.bs.modal', NETDATA.onscroll);
3777 $modal.on('shown.bs.modal', NETDATA.onscroll);
3779 // bootstrap collapse switching
3780 var $collapse = $('.collapse');
3781 $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3782 $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3784 NETDATA.parseDom(NETDATA.chartRefresher);
3786 // Alarms initialization
3787 setTimeout(NETDATA.alarms.init, 1000);
3789 // Registry initialization
3790 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3792 if(typeof netdataCallback === 'function')
3796 // ----------------------------------------------------------------------------------------------------------------
3799 NETDATA.peityInitialize = function(callback) {
3800 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3802 url: NETDATA.peity_js,
3805 xhrFields: { withCredentials: true } // required for the cookie
3808 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3811 NETDATA.chartLibraries.peity.enabled = false;
3812 NETDATA.error(100, NETDATA.peity_js);
3814 .always(function() {
3815 if(typeof callback === "function")
3820 NETDATA.chartLibraries.peity.enabled = false;
3821 if(typeof callback === "function")
3826 NETDATA.peityChartUpdate = function(state, data) {
3827 state.peity_instance.innerHTML = data.result;
3829 if(state.peity_options.stroke !== state.chartColors()[0]) {
3830 state.peity_options.stroke = state.chartColors()[0];
3831 if(state.chart.chart_type === 'line')
3832 state.peity_options.fill = NETDATA.themes.current.background;
3834 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3837 $(state.peity_instance).peity('line', state.peity_options);
3841 NETDATA.peityChartCreate = function(state, data) {
3842 state.peity_instance = document.createElement('div');
3843 state.element_chart.appendChild(state.peity_instance);
3845 var self = $(state.element);
3846 state.peity_options = {
3847 stroke: NETDATA.themes.current.foreground,
3848 strokeWidth: self.data('peity-strokewidth') || 1,
3849 width: state.chartWidth(),
3850 height: state.chartHeight(),
3851 fill: NETDATA.themes.current.foreground
3854 NETDATA.peityChartUpdate(state, data);
3858 // ----------------------------------------------------------------------------------------------------------------
3861 NETDATA.sparklineInitialize = function(callback) {
3862 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3864 url: NETDATA.sparkline_js,
3867 xhrFields: { withCredentials: true } // required for the cookie
3870 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3873 NETDATA.chartLibraries.sparkline.enabled = false;
3874 NETDATA.error(100, NETDATA.sparkline_js);
3876 .always(function() {
3877 if(typeof callback === "function")
3882 NETDATA.chartLibraries.sparkline.enabled = false;
3883 if(typeof callback === "function")
3888 NETDATA.sparklineChartUpdate = function(state, data) {
3889 state.sparkline_options.width = state.chartWidth();
3890 state.sparkline_options.height = state.chartHeight();
3892 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3896 NETDATA.sparklineChartCreate = function(state, data) {
3897 var self = $(state.element);
3898 var type = self.data('sparkline-type') || 'line';
3899 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3900 var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3901 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3902 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3903 var composite = self.data('sparkline-composite') || undefined;
3904 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3905 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3906 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3907 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3908 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3909 var spotColor = self.data('sparkline-spotcolor') || undefined;
3910 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3911 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3912 var spotRadius = self.data('sparkline-spotradius') || undefined;
3913 var valueSpots = self.data('sparkline-valuespots') || undefined;
3914 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3915 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3916 var lineWidth = self.data('sparkline-linewidth') || undefined;
3917 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3918 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3919 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3920 var xvalues = self.data('sparkline-xvalues') || undefined;
3921 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3922 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3923 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3924 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3925 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3926 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3927 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3928 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3929 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3930 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3931 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3932 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3933 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3934 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3935 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3936 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3937 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3938 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3939 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3940 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3941 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3942 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3944 if(spotColor === 'disable') spotColor='';
3945 if(minSpotColor === 'disable') minSpotColor='';
3946 if(maxSpotColor === 'disable') maxSpotColor='';
3948 // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3950 state.sparkline_options = {
3952 lineColor: lineColor,
3953 fillColor: fillColor,
3954 chartRangeMin: chartRangeMin,
3955 chartRangeMax: chartRangeMax,
3956 composite: composite,
3957 enableTagOptions: enableTagOptions,
3958 tagOptionPrefix: tagOptionPrefix,
3959 tagValuesAttribute: tagValuesAttribute,
3960 disableHiddenCheck: disableHiddenCheck,
3961 defaultPixelsPerValue: defaultPixelsPerValue,
3962 spotColor: spotColor,
3963 minSpotColor: minSpotColor,
3964 maxSpotColor: maxSpotColor,
3965 spotRadius: spotRadius,
3966 valueSpots: valueSpots,
3967 highlightSpotColor: highlightSpotColor,
3968 highlightLineColor: highlightLineColor,
3969 lineWidth: lineWidth,
3970 normalRangeMin: normalRangeMin,
3971 normalRangeMax: normalRangeMax,
3972 drawNormalOnTop: drawNormalOnTop,
3974 chartRangeClip: chartRangeClip,
3975 chartRangeMinX: chartRangeMinX,
3976 chartRangeMaxX: chartRangeMaxX,
3977 disableInteraction: disableInteraction,
3978 disableTooltips: disableTooltips,
3979 disableHighlight: disableHighlight,
3980 highlightLighten: highlightLighten,
3981 highlightColor: highlightColor,
3982 tooltipContainer: tooltipContainer,
3983 tooltipClassname: tooltipClassname,
3984 tooltipChartTitle: state.title,
3985 tooltipFormat: tooltipFormat,
3986 tooltipPrefix: tooltipPrefix,
3987 tooltipSuffix: tooltipSuffix,
3988 tooltipSkipNull: tooltipSkipNull,
3989 tooltipValueLookups: tooltipValueLookups,
3990 tooltipFormatFieldlist: tooltipFormatFieldlist,
3991 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3992 numberFormatter: numberFormatter,
3993 numberDigitGroupSep: numberDigitGroupSep,
3994 numberDecimalMark: numberDecimalMark,
3995 numberDigitGroupCount: numberDigitGroupCount,
3996 animatedZooms: animatedZooms,
3997 width: state.chartWidth(),
3998 height: state.chartHeight()
4001 $(state.element_chart).sparkline(data.result, state.sparkline_options);
4005 // ----------------------------------------------------------------------------------------------------------------
4012 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4013 if(after < state.netdata_first)
4014 after = state.netdata_first;
4016 if(before > state.netdata_last)
4017 before = state.netdata_last;
4019 state.setMode('zoom');
4020 state.globalSelectionSyncStop();
4021 state.globalSelectionSyncDelay();
4022 state.dygraph_user_action = true;
4023 state.dygraph_force_zoom = true;
4024 state.updateChartPanOrZoom(after, before);
4025 NETDATA.globalPanAndZoom.setMaster(state, after, before);
4028 NETDATA.dygraphSetSelection = function(state, t) {
4029 if(typeof state.dygraph_instance !== 'undefined') {
4030 var r = state.calculateRowForTime(t);
4032 state.dygraph_instance.setSelection(r);
4034 state.dygraph_instance.clearSelection();
4035 state.legendShowUndefined();
4042 NETDATA.dygraphClearSelection = function(state) {
4043 if(typeof state.dygraph_instance !== 'undefined') {
4044 state.dygraph_instance.clearSelection();
4049 NETDATA.dygraphSmoothInitialize = function(callback) {
4051 url: NETDATA.dygraph_smooth_js,
4054 xhrFields: { withCredentials: true } // required for the cookie
4057 NETDATA.dygraph.smooth = true;
4058 smoothPlotter.smoothing = 0.3;
4061 NETDATA.dygraph.smooth = false;
4063 .always(function() {
4064 if(typeof callback === "function")
4069 NETDATA.dygraphInitialize = function(callback) {
4070 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4072 url: NETDATA.dygraph_js,
4075 xhrFields: { withCredentials: true } // required for the cookie
4078 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4081 NETDATA.chartLibraries.dygraph.enabled = false;
4082 NETDATA.error(100, NETDATA.dygraph_js);
4084 .always(function() {
4085 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4086 NETDATA.dygraphSmoothInitialize(callback);
4087 else if(typeof callback === "function")
4092 NETDATA.chartLibraries.dygraph.enabled = false;
4093 if(typeof callback === "function")
4098 NETDATA.dygraphChartUpdate = function(state, data) {
4099 var dygraph = state.dygraph_instance;
4101 if(typeof dygraph === 'undefined')
4102 return NETDATA.dygraphChartCreate(state, data);
4104 // when the chart is not visible, and hidden
4105 // if there is a window resize, dygraph detects
4106 // its element size as 0x0.
4107 // this will make it re-appear properly
4109 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4113 file: data.result.data,
4114 colors: state.chartColors(),
4115 labels: data.result.labels,
4116 labelsDivWidth: state.chartWidth() - 70,
4117 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4120 if(state.dygraph_force_zoom === true) {
4121 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4122 state.log('dygraphChartUpdate() forced zoom update');
4124 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4125 options.isZoomedIgnoreProgrammaticZoom = true;
4126 state.dygraph_force_zoom = false;
4128 else if(state.current.name !== 'auto') {
4129 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4130 state.log('dygraphChartUpdate() loose update');
4133 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4134 state.log('dygraphChartUpdate() strict update');
4136 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4137 options.isZoomedIgnoreProgrammaticZoom = true;
4140 options.valueRange = state.dygraph_options.valueRange;
4142 var oldMax = null, oldMin = null;
4143 if(state.__commonMin !== null) {
4144 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4145 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4147 if(state.__commonMax !== null) {
4148 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4149 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4152 if(state.dygraph_smooth_eligible === true) {
4153 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4154 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4155 NETDATA.dygraphChartCreate(state, data);
4160 dygraph.updateOptions(options);
4163 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4164 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4165 options.valueRange[0] = NETDATA.commonMin.get(state);
4168 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4169 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4170 options.valueRange[1] = NETDATA.commonMax.get(state);
4174 if(redraw === true) {
4175 // state.log('forcing redraw to adapt to common- min/max');
4176 dygraph.updateOptions(options);
4179 state.dygraph_last_rendered = Date.now();
4183 NETDATA.dygraphChartCreate = function(state, data) {
4184 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4185 state.log('dygraphChartCreate()');
4187 var self = $(state.element);
4189 var chart_type = state.chart.chart_type;
4190 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4191 chart_type = self.data('dygraph-type') || chart_type;
4193 var smooth = (chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false);
4194 smooth = self.data('dygraph-smooth') || smooth;
4196 if(NETDATA.dygraph.smooth === false)
4199 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7);
4200 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4202 state.dygraph_options = {
4203 colors: self.data('dygraph-colors') || state.chartColors(),
4205 // leave a few pixels empty on the right of the chart
4206 rightGap: self.data('dygraph-rightgap') || 5,
4207 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4208 showRoller: self.data('dygraph-showroller') || false,
4210 title: self.data('dygraph-title') || state.title,
4211 titleHeight: self.data('dygraph-titleheight') || 19,
4213 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4214 labels: data.result.labels,
4215 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4216 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4217 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4218 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4219 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4222 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4223 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4225 includeZero: self.data('dygraph-includezero') || (chart_type === 'stacked'),
4226 xRangePad: self.data('dygraph-xrangepad') || 0,
4227 yRangePad: self.data('dygraph-yrangepad') || 1,
4229 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4231 ylabel: state.units,
4232 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4234 // the function to plot the chart
4237 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4238 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4239 strokePattern: self.data('dygraph-strokepattern') || undefined,
4241 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4242 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4243 drawPoints: self.data('dygraph-drawpoints') || false,
4245 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4246 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4248 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4249 pointSize: self.data('dygraph-pointsize') || 1,
4251 // enabling this makes the chart with little square lines
4252 stepPlot: self.data('dygraph-stepplot') || false,
4254 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4255 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4256 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4258 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked'),
4259 fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4260 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked'),
4261 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4263 drawAxis: self.data('dygraph-drawaxis') || true,
4264 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4265 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4266 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4268 drawGrid: self.data('dygraph-drawgrid') || true,
4269 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4270 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4271 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4273 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4274 sigFigs: self.data('dygraph-sigfigs') || null,
4275 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4276 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4278 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4279 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4280 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4282 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4283 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4287 ticker: Dygraph.dateTicker,
4288 axisLabelFormatter: function (d, gran) {
4290 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4292 valueFormatter: function (ms) {
4294 //var d = new Date(ms);
4295 //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4297 // no need to return anything here
4304 valueFormatter: function (x) {
4305 // we format legends with the state object
4306 // no need to do anything here
4307 // return (Math.round(x*100) / 100).toLocaleString();
4308 // return state.legendFormatValue(x);
4313 legendFormatter: function(data) {
4314 var elements = state.element_legend_childs;
4316 // if the hidden div is not there
4317 // we are not managing the legend
4318 if(elements.hidden === null) return;
4320 if (typeof data.x !== 'undefined') {
4321 state.legendSetDate(data.x);
4322 var i = data.series.length;
4324 var series = data.series[i];
4325 if(series.isVisible === true)
4326 state.legendSetLabelValue(series.label, series.y);
4328 state.legendSetLabelValue(series.label, null);
4334 drawCallback: function(dygraph, is_initial) {
4335 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4336 state.dygraph_user_action = false;
4338 var x_range = dygraph.xAxisRange();
4339 var after = Math.round(x_range[0]);
4340 var before = Math.round(x_range[1]);
4342 if(NETDATA.options.debug.dygraph === true)
4343 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4345 if(before <= state.netdata_last && after >= state.netdata_first)
4346 state.updateChartPanOrZoom(after, before);
4349 zoomCallback: function(minDate, maxDate, yRanges) {
4352 if(NETDATA.options.debug.dygraph === true)
4353 state.log('dygraphZoomCallback()');
4355 state.globalSelectionSyncStop();
4356 state.globalSelectionSyncDelay();
4357 state.setMode('zoom');
4359 // refresh it to the greatest possible zoom level
4360 state.dygraph_user_action = true;
4361 state.dygraph_force_zoom = true;
4362 state.updateChartPanOrZoom(minDate, maxDate);
4364 highlightCallback: function(event, x, points, row, seriesName) {
4367 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4368 state.log('dygraphHighlightCallback()');
4372 // there is a bug in dygraph when the chart is zoomed enough
4373 // the time it thinks is selected is wrong
4374 // here we calculate the time t based on the row number selected
4376 // var t = state.data_after + row * state.data_update_every;
4377 // 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);
4379 state.globalSelectionSync(x);
4381 // fix legend zIndex using the internal structures of dygraph legend module
4382 // this works, but it is a hack!
4383 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4385 unhighlightCallback: function(event) {
4388 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4389 state.log('dygraphUnhighlightCallback()');
4391 state.unpauseChart();
4392 state.globalSelectionSyncStop();
4394 interactionModel : {
4395 mousedown: function(event, dygraph, context) {
4396 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4397 state.log('interactionModel.mousedown()');
4399 state.dygraph_user_action = true;
4400 state.globalSelectionSyncStop();
4402 if(NETDATA.options.debug.dygraph === true)
4403 state.log('dygraphMouseDown()');
4405 // Right-click should not initiate a zoom.
4406 if(event.button && event.button === 2) return;
4408 context.initializeMouseDown(event, dygraph, context);
4410 if(event.button && event.button === 1) {
4411 if (event.altKey || event.shiftKey) {
4412 state.setMode('pan');
4413 state.globalSelectionSyncDelay();
4414 Dygraph.startPan(event, dygraph, context);
4417 state.setMode('zoom');
4418 state.globalSelectionSyncDelay();
4419 Dygraph.startZoom(event, dygraph, context);
4423 if (event.altKey || event.shiftKey) {
4424 state.setMode('zoom');
4425 state.globalSelectionSyncDelay();
4426 Dygraph.startZoom(event, dygraph, context);
4429 state.setMode('pan');
4430 state.globalSelectionSyncDelay();
4431 Dygraph.startPan(event, dygraph, context);
4435 mousemove: function(event, dygraph, context) {
4436 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4437 state.log('interactionModel.mousemove()');
4439 if(context.isPanning) {
4440 state.dygraph_user_action = true;
4441 state.globalSelectionSyncStop();
4442 state.globalSelectionSyncDelay();
4443 state.setMode('pan');
4444 context.is2DPan = false;
4445 Dygraph.movePan(event, dygraph, context);
4447 else if(context.isZooming) {
4448 state.dygraph_user_action = true;
4449 state.globalSelectionSyncStop();
4450 state.globalSelectionSyncDelay();
4451 state.setMode('zoom');
4452 Dygraph.moveZoom(event, dygraph, context);
4455 mouseup: function(event, dygraph, context) {
4456 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4457 state.log('interactionModel.mouseup()');
4459 if (context.isPanning) {
4460 state.dygraph_user_action = true;
4461 state.globalSelectionSyncDelay();
4462 Dygraph.endPan(event, dygraph, context);
4464 else if (context.isZooming) {
4465 state.dygraph_user_action = true;
4466 state.globalSelectionSyncDelay();
4467 Dygraph.endZoom(event, dygraph, context);
4470 click: function(event, dygraph, context) {
4474 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4475 state.log('interactionModel.click()');
4477 event.preventDefault();
4479 dblclick: function(event, dygraph, context) {
4484 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4485 state.log('interactionModel.dblclick()');
4486 NETDATA.resetAllCharts(state);
4488 wheel: function(event, dygraph, context) {
4491 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4492 state.log('interactionModel.wheel()');
4494 // Take the offset of a mouse event on the dygraph canvas and
4495 // convert it to a pair of percentages from the bottom left.
4496 // (Not top left, bottom is where the lower value is.)
4497 function offsetToPercentage(g, offsetX, offsetY) {
4498 // This is calculating the pixel offset of the leftmost date.
4499 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4500 var yar0 = g.yAxisRange(0);
4502 // This is calculating the pixel of the highest value. (Top pixel)
4503 var yOffset = g.toDomCoords(null, yar0[1])[1];
4505 // x y w and h are relative to the corner of the drawing area,
4506 // so that the upper corner of the drawing area is (0, 0).
4507 var x = offsetX - xOffset;
4508 var y = offsetY - yOffset;
4510 // This is computing the rightmost pixel, effectively defining the
4512 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4514 // This is computing the lowest pixel, effectively defining the height.
4515 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4517 // Percentage from the left.
4518 var xPct = w === 0 ? 0 : (x / w);
4519 // Percentage from the top.
4520 var yPct = h === 0 ? 0 : (y / h);
4522 // The (1-) part below changes it from "% distance down from the top"
4523 // to "% distance up from the bottom".
4524 return [xPct, (1-yPct)];
4527 // Adjusts [x, y] toward each other by zoomInPercentage%
4528 // Split it so the left/bottom axis gets xBias/yBias of that change and
4529 // tight/top gets (1-xBias)/(1-yBias) of that change.
4531 // If a bias is missing it splits it down the middle.
4532 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4533 xBias = xBias || 0.5;
4534 yBias = yBias || 0.5;
4536 function adjustAxis(axis, zoomInPercentage, bias) {
4537 var delta = axis[1] - axis[0];
4538 var increment = delta * zoomInPercentage;
4539 var foo = [increment * bias, increment * (1-bias)];
4541 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4544 var yAxes = g.yAxisRanges();
4546 for (var i = 0; i < yAxes.length; i++) {
4547 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4550 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4553 if(event.altKey || event.shiftKey) {
4554 state.dygraph_user_action = true;
4556 state.globalSelectionSyncStop();
4557 state.globalSelectionSyncDelay();
4559 // http://dygraphs.com/gallery/interaction-api.js
4561 if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4563 normal_def = event.wheelDelta / 40;
4566 normal_def = event.deltaY * -1.2;
4568 var normal = (event.detail) ? event.detail * -1 : normal_def;
4569 var percentage = normal / 50;
4571 if (!(event.offsetX && event.offsetY)){
4572 event.offsetX = event.layerX - event.target.offsetLeft;
4573 event.offsetY = event.layerY - event.target.offsetTop;
4576 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4577 var xPct = percentages[0];
4578 var yPct = percentages[1];
4580 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4581 var after = new_x_range[0];
4582 var before = new_x_range[1];
4584 var first = state.netdata_first + state.data_update_every;
4585 var last = state.netdata_last + state.data_update_every;
4588 after -= (before - last);
4595 state.setMode('zoom');
4596 if(state.updateChartPanOrZoom(after, before) === true)
4597 dygraph.updateOptions({ dateWindow: [ after, before ] });
4599 event.preventDefault();
4602 touchstart: function(event, dygraph, context) {
4603 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4604 state.log('interactionModel.touchstart()');
4606 state.dygraph_user_action = true;
4607 state.setMode('zoom');
4610 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4612 // we overwrite the touch directions at the end, to overwrite
4613 // the internal default of dygraph
4614 context.touchDirections = { x: true, y: false };
4616 state.dygraph_last_touch_start = Date.now();
4617 state.dygraph_last_touch_move = 0;
4619 if(typeof event.touches[0].pageX === 'number')
4620 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4622 state.dygraph_last_touch_page_x = 0;
4624 touchmove: function(event, dygraph, context) {
4625 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4626 state.log('interactionModel.touchmove()');
4628 state.dygraph_user_action = true;
4629 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4631 state.dygraph_last_touch_move = Date.now();
4633 touchend: function(event, dygraph, context) {
4634 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4635 state.log('interactionModel.touchend()');
4637 state.dygraph_user_action = true;
4638 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4640 // if it didn't move, it is a selection
4641 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4642 // internal api of dygraph
4643 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4644 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4645 if(NETDATA.dygraphSetSelection(state, t) === true)
4646 state.globalSelectionSync(t);
4649 // if it was double tap within double click time, reset the charts
4650 var now = Date.now();
4651 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4652 if(state.dygraph_last_touch_move === 0) {
4653 var dt = now - state.dygraph_last_touch_end;
4654 if(dt <= NETDATA.options.current.double_click_speed)
4655 NETDATA.resetAllCharts(state);
4659 // remember the timestamp of the last touch end
4660 state.dygraph_last_touch_end = now;
4665 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4666 state.dygraph_options.drawGrid = false;
4667 state.dygraph_options.drawAxis = false;
4668 state.dygraph_options.title = undefined;
4669 state.dygraph_options.ylabel = undefined;
4670 state.dygraph_options.yLabelWidth = 0;
4671 state.dygraph_options.labelsDivWidth = 120;
4672 state.dygraph_options.labelsDivStyles.width = '120px';
4673 state.dygraph_options.labelsSeparateLines = true;
4674 state.dygraph_options.rightGap = 0;
4675 state.dygraph_options.yRangePad = 1;
4678 if(smooth === true) {
4679 state.dygraph_smooth_eligible = true;
4681 if(NETDATA.options.current.smooth_plot === true)
4682 state.dygraph_options.plotter = smoothPlotter;
4684 else state.dygraph_smooth_eligible = false;
4686 state.dygraph_instance = new Dygraph(state.element_chart,
4687 data.result.data, state.dygraph_options);
4689 state.dygraph_force_zoom = false;
4690 state.dygraph_user_action = false;
4691 state.dygraph_last_rendered = Date.now();
4693 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4694 state.__commonMin = self.data('common-min') || null;
4695 state.__commonMax = self.data('common-max') || null;
4698 state.log('incompatible version of Dygraph detected');
4699 state.__commonMin = null;
4700 state.__commonMax = null;
4706 // ----------------------------------------------------------------------------------------------------------------
4709 NETDATA.morrisInitialize = function(callback) {
4710 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4712 // morris requires raphael
4713 if(!NETDATA.chartLibraries.raphael.initialized) {
4714 if(NETDATA.chartLibraries.raphael.enabled) {
4715 NETDATA.raphaelInitialize(function() {
4716 NETDATA.morrisInitialize(callback);
4720 NETDATA.chartLibraries.morris.enabled = false;
4721 if(typeof callback === "function")
4726 NETDATA._loadCSS(NETDATA.morris_css);
4729 url: NETDATA.morris_js,
4732 xhrFields: { withCredentials: true } // required for the cookie
4735 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4738 NETDATA.chartLibraries.morris.enabled = false;
4739 NETDATA.error(100, NETDATA.morris_js);
4741 .always(function() {
4742 if(typeof callback === "function")
4748 NETDATA.chartLibraries.morris.enabled = false;
4749 if(typeof callback === "function")
4754 NETDATA.morrisChartUpdate = function(state, data) {
4755 state.morris_instance.setData(data.result.data);
4759 NETDATA.morrisChartCreate = function(state, data) {
4761 state.morris_options = {
4762 element: state.element_chart.id,
4763 data: data.result.data,
4765 ykeys: data.dimension_names,
4766 labels: data.dimension_names,
4772 continuousLine: false,
4773 behaveLikeLine: false
4776 if(state.chart.chart_type === 'line')
4777 state.morris_instance = new Morris.Line(state.morris_options);
4779 else if(state.chart.chart_type === 'area') {
4780 state.morris_options.behaveLikeLine = true;
4781 state.morris_instance = new Morris.Area(state.morris_options);
4784 state.morris_instance = new Morris.Area(state.morris_options);
4789 // ----------------------------------------------------------------------------------------------------------------
4792 NETDATA.raphaelInitialize = function(callback) {
4793 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4795 url: NETDATA.raphael_js,
4798 xhrFields: { withCredentials: true } // required for the cookie
4801 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4804 NETDATA.chartLibraries.raphael.enabled = false;
4805 NETDATA.error(100, NETDATA.raphael_js);
4807 .always(function() {
4808 if(typeof callback === "function")
4813 NETDATA.chartLibraries.raphael.enabled = false;
4814 if(typeof callback === "function")
4819 NETDATA.raphaelChartUpdate = function(state, data) {
4820 $(state.element_chart).raphael(data.result, {
4821 width: state.chartWidth(),
4822 height: state.chartHeight()
4828 NETDATA.raphaelChartCreate = function(state, data) {
4829 $(state.element_chart).raphael(data.result, {
4830 width: state.chartWidth(),
4831 height: state.chartHeight()
4837 // ----------------------------------------------------------------------------------------------------------------
4840 NETDATA.c3Initialize = function(callback) {
4841 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4844 if(!NETDATA.chartLibraries.d3.initialized) {
4845 if(NETDATA.chartLibraries.d3.enabled) {
4846 NETDATA.d3Initialize(function() {
4847 NETDATA.c3Initialize(callback);
4851 NETDATA.chartLibraries.c3.enabled = false;
4852 if(typeof callback === "function")
4857 NETDATA._loadCSS(NETDATA.c3_css);
4863 xhrFields: { withCredentials: true } // required for the cookie
4866 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4869 NETDATA.chartLibraries.c3.enabled = false;
4870 NETDATA.error(100, NETDATA.c3_js);
4872 .always(function() {
4873 if(typeof callback === "function")
4879 NETDATA.chartLibraries.c3.enabled = false;
4880 if(typeof callback === "function")
4885 NETDATA.c3ChartUpdate = function(state, data) {
4886 state.c3_instance.destroy();
4887 return NETDATA.c3ChartCreate(state, data);
4889 //state.c3_instance.load({
4890 // rows: data.result,
4897 NETDATA.c3ChartCreate = function(state, data) {
4899 state.element_chart.id = 'c3-' + state.uuid;
4900 // console.log('id = ' + state.element_chart.id);
4902 state.c3_instance = c3.generate({
4903 bindto: '#' + state.element_chart.id,
4905 width: state.chartWidth(),
4906 height: state.chartHeight()
4909 pattern: state.chartColors()
4914 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4920 format: function(x) {
4921 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4948 // console.log(state.c3_instance);
4953 // ----------------------------------------------------------------------------------------------------------------
4956 NETDATA.d3Initialize = function(callback) {
4957 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4962 xhrFields: { withCredentials: true } // required for the cookie
4965 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4968 NETDATA.chartLibraries.d3.enabled = false;
4969 NETDATA.error(100, NETDATA.d3_js);
4971 .always(function() {
4972 if(typeof callback === "function")
4977 NETDATA.chartLibraries.d3.enabled = false;
4978 if(typeof callback === "function")
4983 NETDATA.d3ChartUpdate = function(state, data) {
4990 NETDATA.d3ChartCreate = function(state, data) {
4997 // ----------------------------------------------------------------------------------------------------------------
5000 NETDATA.googleInitialize = function(callback) {
5001 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
5003 url: NETDATA.google_js,
5006 xhrFields: { withCredentials: true } // required for the cookie
5009 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5010 google.load('visualization', '1.1', {
5011 'packages': ['corechart', 'controls'],
5012 'callback': callback
5016 NETDATA.chartLibraries.google.enabled = false;
5017 NETDATA.error(100, NETDATA.google_js);
5018 if(typeof callback === "function")
5023 NETDATA.chartLibraries.google.enabled = false;
5024 if(typeof callback === "function")
5029 NETDATA.googleChartUpdate = function(state, data) {
5030 var datatable = new google.visualization.DataTable(data.result);
5031 state.google_instance.draw(datatable, state.google_options);
5035 NETDATA.googleChartCreate = function(state, data) {
5036 var datatable = new google.visualization.DataTable(data.result);
5038 state.google_options = {
5039 colors: state.chartColors(),
5041 // do not set width, height - the chart resizes itself
5042 //width: state.chartWidth(),
5043 //height: state.chartHeight(),
5048 // title: "Time of Day",
5049 // format:'HH:mm:ss',
5050 viewWindowMode: 'maximized',
5062 viewWindowMode: 'pretty',
5077 focusTarget: 'category',
5084 titlePosition: 'out',
5095 curveType: 'function',
5100 switch(state.chart.chart_type) {
5102 state.google_options.vAxis.viewWindowMode = 'maximized';
5103 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5104 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5108 state.google_options.isStacked = true;
5109 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5110 state.google_options.vAxis.viewWindowMode = 'maximized';
5111 state.google_options.vAxis.minValue = null;
5112 state.google_options.vAxis.maxValue = null;
5113 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5118 state.google_options.lineWidth = 2;
5119 state.google_instance = new google.visualization.LineChart(state.element_chart);
5123 state.google_instance.draw(datatable, state.google_options);
5127 // ----------------------------------------------------------------------------------------------------------------
5129 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5130 if(typeof value !== 'number') value = 0;
5131 if(typeof min !== 'number') min = 0;
5132 if(typeof max !== 'number') max = 0;
5134 if(min > value) min = value;
5135 if(max < value) max = value;
5137 // make sure it is zero based
5138 if(min > 0) min = 0;
5139 if(max < 0) max = 0;
5144 pcent = Math.round(value * 100 / max);
5145 if(pcent === 0) pcent = 0.1;
5149 pcent = Math.round(-value * 100 / min);
5150 if(pcent === 0) pcent = -0.1;
5156 // ----------------------------------------------------------------------------------------------------------------
5159 NETDATA.easypiechartInitialize = function(callback) {
5160 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5162 url: NETDATA.easypiechart_js,
5165 xhrFields: { withCredentials: true } // required for the cookie
5168 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5171 NETDATA.chartLibraries.easypiechart.enabled = false;
5172 NETDATA.error(100, NETDATA.easypiechart_js);
5174 .always(function() {
5175 if(typeof callback === "function")
5180 NETDATA.chartLibraries.easypiechart.enabled = false;
5181 if(typeof callback === "function")
5186 NETDATA.easypiechartClearSelection = function(state) {
5187 if(typeof state.easyPieChartEvent !== 'undefined') {
5188 if(state.easyPieChartEvent.timer !== undefined) {
5189 clearTimeout(state.easyPieChartEvent.timer);
5192 state.easyPieChartEvent.timer = undefined;
5195 if(state.isAutoRefreshable() === true && state.data !== null) {
5196 NETDATA.easypiechartChartUpdate(state, state.data);
5199 state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5200 state.easyPieChart_instance.update(0);
5202 state.easyPieChart_instance.enableAnimation();
5207 NETDATA.easypiechartSetSelection = function(state, t) {
5208 if(state.timeIsVisible(t) !== true)
5209 return NETDATA.easypiechartClearSelection(state);
5211 var slot = state.calculateRowForTime(t);
5212 if(slot < 0 || slot >= state.data.result.length)
5213 return NETDATA.easypiechartClearSelection(state);
5215 if(typeof state.easyPieChartEvent === 'undefined') {
5216 state.easyPieChartEvent = {
5223 var value = state.data.result[state.data.result.length - 1 - slot];
5224 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5225 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5226 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5228 state.easyPieChartEvent.value = value;
5229 state.easyPieChartEvent.pcent = pcent;
5230 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5232 if(state.easyPieChartEvent.timer === undefined) {
5233 state.easyPieChart_instance.disableAnimation();
5235 state.easyPieChartEvent.timer = setTimeout(function() {
5236 state.easyPieChartEvent.timer = undefined;
5237 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5238 }, NETDATA.options.current.charts_selection_animation_delay);
5244 NETDATA.easypiechartChartUpdate = function(state, data) {
5245 var value, min, max, pcent;
5247 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5252 value = data.result[0];
5253 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5254 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5255 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5258 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5259 state.easyPieChart_instance.update(pcent);
5263 NETDATA.easypiechartChartCreate = function(state, data) {
5264 var self = $(state.element);
5265 var chart = $(state.element_chart);
5267 var value = data.result[0];
5268 var min = self.data('easypiechart-min-value') || null;
5269 var max = self.data('easypiechart-max-value') || null;
5270 var adjust = self.data('easypiechart-adjust') || null;
5273 min = NETDATA.commonMin.get(state);
5274 state.easyPieChartMin = null;
5277 state.easyPieChartMin = min;
5280 max = NETDATA.commonMax.get(state);
5281 state.easyPieChartMax = null;
5284 state.easyPieChartMax = max;
5286 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5288 chart.data('data-percent', pcent);
5292 case 'width': size = state.chartHeight(); break;
5293 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5294 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5296 default: size = state.chartWidth(); break;
5298 state.element.style.width = size + 'px';
5299 state.element.style.height = size + 'px';
5301 var stroke = Math.floor(size / 22);
5302 if(stroke < 3) stroke = 2;
5304 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5305 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5306 state.easyPieChartLabel = document.createElement('span');
5307 state.easyPieChartLabel.className = 'easyPieChartLabel';
5308 state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5309 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5310 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5311 state.element_chart.appendChild(state.easyPieChartLabel);
5313 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5314 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5315 state.easyPieChartTitle = document.createElement('span');
5316 state.easyPieChartTitle.className = 'easyPieChartTitle';
5317 state.easyPieChartTitle.innerText = state.title;
5318 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5319 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5320 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5321 state.element_chart.appendChild(state.easyPieChartTitle);
5323 var unitfontsize = Math.round(titlefontsize * 0.9);
5324 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5325 state.easyPieChartUnits = document.createElement('span');
5326 state.easyPieChartUnits.className = 'easyPieChartUnits';
5327 state.easyPieChartUnits.innerText = state.units;
5328 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5329 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5330 state.element_chart.appendChild(state.easyPieChartUnits);
5332 var barColor = self.data('easypiechart-barcolor');
5333 if(typeof barColor === 'undefined' || barColor === null)
5334 barColor = state.chartColors()[0];
5336 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5337 var tmp = eval(barColor);
5338 if(typeof tmp === 'function')
5342 chart.easyPieChart({
5344 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5345 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5346 scaleLength: self.data('easypiechart-scalelength') || 5,
5347 lineCap: self.data('easypiechart-linecap') || 'round',
5348 lineWidth: self.data('easypiechart-linewidth') || stroke,
5349 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5350 size: self.data('easypiechart-size') || size,
5351 rotate: self.data('easypiechart-rotate') || 0,
5352 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5353 easing: self.data('easypiechart-easing') || undefined
5356 // when we just re-create the chart
5357 // do not animate the first update
5359 if(typeof state.easyPieChart_instance !== 'undefined')
5362 state.easyPieChart_instance = chart.data('easyPieChart');
5363 if(animate === false) state.easyPieChart_instance.disableAnimation();
5364 state.easyPieChart_instance.update(pcent);
5365 if(animate === false) state.easyPieChart_instance.enableAnimation();
5369 // ----------------------------------------------------------------------------------------------------------------
5372 NETDATA.gaugeInitialize = function(callback) {
5373 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5375 url: NETDATA.gauge_js,
5378 xhrFields: { withCredentials: true } // required for the cookie
5381 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5384 NETDATA.chartLibraries.gauge.enabled = false;
5385 NETDATA.error(100, NETDATA.gauge_js);
5387 .always(function() {
5388 if(typeof callback === "function")
5393 NETDATA.chartLibraries.gauge.enabled = false;
5394 if(typeof callback === "function")
5399 NETDATA.gaugeAnimation = function(state, status) {
5402 if(typeof status === 'boolean' && status === false)
5404 else if(typeof status === 'number')
5407 // console.log('gauge speed ' + speed);
5408 state.gauge_instance.animationSpeed = speed;
5409 state.___gaugeOld__.speed = speed;
5412 NETDATA.gaugeSet = function(state, value, min, max) {
5413 if(typeof value !== 'number') value = 0;
5414 if(typeof min !== 'number') min = 0;
5415 if(typeof max !== 'number') max = 0;
5416 if(value > max) max = value;
5417 if(value < min) min = value;
5423 else if(min === max)
5426 // gauge.js has an issue if the needle
5427 // is smaller than min or larger than max
5428 // when we set the new values
5429 // the needle will go crazy
5431 // to prevent it, we always feed it
5432 // with a percentage, so that the needle
5433 // is always between min and max
5434 var pcent = (value - min) * 100 / (max - min);
5436 // these should never happen
5437 if(pcent < 0) pcent = 0;
5438 if(pcent > 100) pcent = 100;
5440 state.gauge_instance.set(pcent);
5441 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5443 state.___gaugeOld__.value = value;
5444 state.___gaugeOld__.min = min;
5445 state.___gaugeOld__.max = max;
5448 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5449 if(state.___gaugeOld__.valueLabel !== value) {
5450 state.___gaugeOld__.valueLabel = value;
5451 state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5453 if(state.___gaugeOld__.minLabel !== min) {
5454 state.___gaugeOld__.minLabel = min;
5455 state.gaugeChartMin.innerText = state.legendFormatValue(min);
5457 if(state.___gaugeOld__.maxLabel !== max) {
5458 state.___gaugeOld__.maxLabel = max;
5459 state.gaugeChartMax.innerText = state.legendFormatValue(max);
5463 NETDATA.gaugeClearSelection = function(state) {
5464 if(typeof state.gaugeEvent !== 'undefined') {
5465 if(state.gaugeEvent.timer !== undefined) {
5466 clearTimeout(state.gaugeEvent.timer);
5469 state.gaugeEvent.timer = undefined;
5472 if(state.isAutoRefreshable() === true && state.data !== null) {
5473 NETDATA.gaugeChartUpdate(state, state.data);
5476 NETDATA.gaugeAnimation(state, false);
5477 NETDATA.gaugeSet(state, null, null, null);
5478 NETDATA.gaugeSetLabels(state, null, null, null);
5481 NETDATA.gaugeAnimation(state, true);
5485 NETDATA.gaugeSetSelection = function(state, t) {
5486 if(state.timeIsVisible(t) !== true)
5487 return NETDATA.gaugeClearSelection(state);
5489 var slot = state.calculateRowForTime(t);
5490 if(slot < 0 || slot >= state.data.result.length)
5491 return NETDATA.gaugeClearSelection(state);
5493 if(typeof state.gaugeEvent === 'undefined') {
5494 state.gaugeEvent = {
5502 var value = state.data.result[state.data.result.length - 1 - slot];
5503 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5504 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5506 // make sure it is zero based
5507 if(min > 0) min = 0;
5508 if(max < 0) max = 0;
5510 state.gaugeEvent.value = value;
5511 state.gaugeEvent.min = min;
5512 state.gaugeEvent.max = max;
5513 NETDATA.gaugeSetLabels(state, value, min, max);
5515 if(state.gaugeEvent.timer === undefined) {
5516 NETDATA.gaugeAnimation(state, false);
5518 state.gaugeEvent.timer = setTimeout(function() {
5519 state.gaugeEvent.timer = undefined;
5520 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5521 }, NETDATA.options.current.charts_selection_animation_delay);
5527 NETDATA.gaugeChartUpdate = function(state, data) {
5528 var value, min, max;
5530 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5534 NETDATA.gaugeSetLabels(state, null, null, null);
5537 value = data.result[0];
5538 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5539 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5540 if(value < min) min = value;
5541 if(value > max) max = value;
5543 // make sure it is zero based
5544 if(min > 0) min = 0;
5545 if(max < 0) max = 0;
5547 NETDATA.gaugeSetLabels(state, value, min, max);
5550 NETDATA.gaugeSet(state, value, min, max);
5554 NETDATA.gaugeChartCreate = function(state, data) {
5555 var self = $(state.element);
5556 // var chart = $(state.element_chart);
5558 var value = data.result[0];
5559 var min = self.data('gauge-min-value') || null;
5560 var max = self.data('gauge-max-value') || null;
5561 var adjust = self.data('gauge-adjust') || null;
5562 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5563 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5564 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5565 var stopColor = self.data('gauge-stop-color') || void 0;
5566 var generateGradient = self.data('gauge-generate-gradient') || false;
5569 min = NETDATA.commonMin.get(state);
5570 state.gaugeMin = null;
5573 state.gaugeMin = min;
5576 max = NETDATA.commonMax.get(state);
5577 state.gaugeMax = null;
5580 state.gaugeMax = max;
5582 // make sure it is zero based
5583 if(min > 0) min = 0;
5584 if(max < 0) max = 0;
5586 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5588 // case 'width': width = height * ratio; break;
5590 // default: height = width / ratio; break;
5592 //state.element.style.width = width.toString() + 'px';
5593 //state.element.style.height = height.toString() + 'px';
5598 lines: 12, // The number of lines to draw
5599 angle: 0.15, // The length of each line
5600 lineWidth: 0.44, // 0.44 The line thickness
5602 length: 0.8, // 0.9 The radius of the inner circle
5603 strokeWidth: 0.035, // The rotation offset
5604 color: pointerColor // Fill color
5606 colorStart: startColor, // Colors
5607 colorStop: stopColor, // just experiment with them
5608 strokeColor: strokeColor, // to see which ones work best for you
5610 generateGradient: (generateGradient === true),
5614 if (generateGradient.constructor === Array) {
5616 // data-gauge-generate-gradient="[0, 50, 100]"
5617 // data-gauge-gradient-percent-color-0="#FFFFFF"
5618 // data-gauge-gradient-percent-color-50="#999900"
5619 // data-gauge-gradient-percent-color-100="#000000"
5621 options.percentColors = [];
5622 var len = generateGradient.length;
5624 var pcent = generateGradient[len];
5625 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5626 if(color !== false) {
5630 options.percentColors.unshift(a);
5633 if(options.percentColors.length === 0)
5634 delete options.percentColors;
5636 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5637 //noinspection PointlessArithmeticExpressionJS
5638 options.percentColors = [
5639 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5640 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5641 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5642 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5643 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5644 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5645 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5646 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5647 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5648 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5649 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5652 state.gauge_canvas = document.createElement('canvas');
5653 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5654 state.gauge_canvas.className = 'gaugeChart';
5655 state.gauge_canvas.width = width;
5656 state.gauge_canvas.height = height;
5657 state.element_chart.appendChild(state.gauge_canvas);
5659 var valuefontsize = Math.floor(height / 6);
5660 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5661 state.gaugeChartLabel = document.createElement('span');
5662 state.gaugeChartLabel.className = 'gaugeChartLabel';
5663 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5664 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5665 state.element_chart.appendChild(state.gaugeChartLabel);
5667 var titlefontsize = Math.round(valuefontsize / 2);
5669 state.gaugeChartTitle = document.createElement('span');
5670 state.gaugeChartTitle.className = 'gaugeChartTitle';
5671 state.gaugeChartTitle.innerText = state.title;
5672 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5673 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5674 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5675 state.element_chart.appendChild(state.gaugeChartTitle);
5677 var unitfontsize = Math.round(titlefontsize * 0.9);
5678 state.gaugeChartUnits = document.createElement('span');
5679 state.gaugeChartUnits.className = 'gaugeChartUnits';
5680 state.gaugeChartUnits.innerText = state.units;
5681 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5682 state.element_chart.appendChild(state.gaugeChartUnits);
5684 state.gaugeChartMin = document.createElement('span');
5685 state.gaugeChartMin.className = 'gaugeChartMin';
5686 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5687 state.element_chart.appendChild(state.gaugeChartMin);
5689 state.gaugeChartMax = document.createElement('span');
5690 state.gaugeChartMax.className = 'gaugeChartMax';
5691 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5692 state.element_chart.appendChild(state.gaugeChartMax);
5694 // when we just re-create the chart
5695 // do not animate the first update
5697 if(typeof state.gauge_instance !== 'undefined')
5700 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5702 state.___gaugeOld__ = {
5711 // we will always feed a percentage
5712 state.gauge_instance.minValue = 0;
5713 state.gauge_instance.maxValue = 100;
5715 NETDATA.gaugeAnimation(state, animate);
5716 NETDATA.gaugeSet(state, value, min, max);
5717 NETDATA.gaugeSetLabels(state, value, min, max);
5718 NETDATA.gaugeAnimation(state, true);
5722 // ----------------------------------------------------------------------------------------------------------------
5723 // Charts Libraries Registration
5725 NETDATA.chartLibraries = {
5727 initialize: NETDATA.dygraphInitialize,
5728 create: NETDATA.dygraphChartCreate,
5729 update: NETDATA.dygraphChartUpdate,
5730 resize: function(state) {
5731 if(typeof state.dygraph_instance.resize === 'function')
5732 state.dygraph_instance.resize();
5734 setSelection: NETDATA.dygraphSetSelection,
5735 clearSelection: NETDATA.dygraphClearSelection,
5736 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5739 format: function(state) { void(state); return 'json'; },
5740 options: function(state) { void(state); return 'ms|flip'; },
5741 legend: function(state) {
5742 return (this.isSparkline(state) === false)?'right-side':null;
5744 autoresize: function(state) { void(state); return true; },
5745 max_updates_to_recreate: function(state) { void(state); return 5000; },
5746 track_colors: function(state) { void(state); return true; },
5747 pixels_per_point: function(state) {
5748 return (this.isSparkline(state) === false)?3:2;
5750 isSparkline: function(state) {
5751 if(typeof state.dygraph_sparkline === 'undefined') {
5752 var t = $(state.element).data('dygraph-theme');
5753 state.dygraph_sparkline = (t === 'sparkline');
5755 return state.dygraph_sparkline;
5759 initialize: NETDATA.sparklineInitialize,
5760 create: NETDATA.sparklineChartCreate,
5761 update: NETDATA.sparklineChartUpdate,
5763 setSelection: undefined, // function(state, t) { void(state); return true; },
5764 clearSelection: undefined, // function(state) { void(state); return true; },
5765 toolboxPanAndZoom: null,
5768 format: function(state) { void(state); return 'array'; },
5769 options: function(state) { void(state); return 'flip|abs'; },
5770 legend: function(state) { void(state); return null; },
5771 autoresize: function(state) { void(state); return false; },
5772 max_updates_to_recreate: function(state) { void(state); return 5000; },
5773 track_colors: function(state) { void(state); return false; },
5774 pixels_per_point: function(state) { void(state); return 3; }
5777 initialize: NETDATA.peityInitialize,
5778 create: NETDATA.peityChartCreate,
5779 update: NETDATA.peityChartUpdate,
5781 setSelection: undefined, // function(state, t) { void(state); return true; },
5782 clearSelection: undefined, // function(state) { void(state); return true; },
5783 toolboxPanAndZoom: null,
5786 format: function(state) { void(state); return 'ssvcomma'; },
5787 options: function(state) { void(state); return 'null2zero|flip|abs'; },
5788 legend: function(state) { void(state); return null; },
5789 autoresize: function(state) { void(state); return false; },
5790 max_updates_to_recreate: function(state) { void(state); return 5000; },
5791 track_colors: function(state) { void(state); return false; },
5792 pixels_per_point: function(state) { void(state); return 3; }
5795 initialize: NETDATA.morrisInitialize,
5796 create: NETDATA.morrisChartCreate,
5797 update: NETDATA.morrisChartUpdate,
5799 setSelection: undefined, // function(state, t) { void(state); return true; },
5800 clearSelection: undefined, // function(state) { void(state); return true; },
5801 toolboxPanAndZoom: null,
5804 format: function(state) { void(state); return 'json'; },
5805 options: function(state) { void(state); return 'objectrows|ms'; },
5806 legend: function(state) { void(state); return null; },
5807 autoresize: function(state) { void(state); return false; },
5808 max_updates_to_recreate: function(state) { void(state); return 50; },
5809 track_colors: function(state) { void(state); return false; },
5810 pixels_per_point: function(state) { void(state); return 15; }
5813 initialize: NETDATA.googleInitialize,
5814 create: NETDATA.googleChartCreate,
5815 update: NETDATA.googleChartUpdate,
5817 setSelection: undefined, //function(state, t) { void(state); return true; },
5818 clearSelection: undefined, //function(state) { void(state); return true; },
5819 toolboxPanAndZoom: null,
5822 format: function(state) { void(state); return 'datatable'; },
5823 options: function(state) { void(state); return ''; },
5824 legend: function(state) { void(state); return null; },
5825 autoresize: function(state) { void(state); return false; },
5826 max_updates_to_recreate: function(state) { void(state); return 300; },
5827 track_colors: function(state) { void(state); return false; },
5828 pixels_per_point: function(state) { void(state); return 4; }
5831 initialize: NETDATA.raphaelInitialize,
5832 create: NETDATA.raphaelChartCreate,
5833 update: NETDATA.raphaelChartUpdate,
5835 setSelection: undefined, // function(state, t) { void(state); return true; },
5836 clearSelection: undefined, // function(state) { void(state); return true; },
5837 toolboxPanAndZoom: null,
5840 format: function(state) { void(state); return 'json'; },
5841 options: function(state) { void(state); return ''; },
5842 legend: function(state) { void(state); return null; },
5843 autoresize: function(state) { void(state); return false; },
5844 max_updates_to_recreate: function(state) { void(state); return 5000; },
5845 track_colors: function(state) { void(state); return false; },
5846 pixels_per_point: function(state) { void(state); return 3; }
5849 initialize: NETDATA.c3Initialize,
5850 create: NETDATA.c3ChartCreate,
5851 update: NETDATA.c3ChartUpdate,
5853 setSelection: undefined, // function(state, t) { void(state); return true; },
5854 clearSelection: undefined, // function(state) { void(state); return true; },
5855 toolboxPanAndZoom: null,
5858 format: function(state) { void(state); return 'csvjsonarray'; },
5859 options: function(state) { void(state); return 'milliseconds'; },
5860 legend: function(state) { void(state); return null; },
5861 autoresize: function(state) { void(state); return false; },
5862 max_updates_to_recreate: function(state) { void(state); return 5000; },
5863 track_colors: function(state) { void(state); return false; },
5864 pixels_per_point: function(state) { void(state); return 15; }
5867 initialize: NETDATA.d3Initialize,
5868 create: NETDATA.d3ChartCreate,
5869 update: NETDATA.d3ChartUpdate,
5871 setSelection: undefined, // function(state, t) { void(state); return true; },
5872 clearSelection: undefined, // function(state) { void(state); return true; },
5873 toolboxPanAndZoom: null,
5876 format: function(state) { void(state); return 'json'; },
5877 options: function(state) { void(state); return ''; },
5878 legend: function(state) { void(state); return null; },
5879 autoresize: function(state) { void(state); return false; },
5880 max_updates_to_recreate: function(state) { void(state); return 5000; },
5881 track_colors: function(state) { void(state); return false; },
5882 pixels_per_point: function(state) { void(state); return 3; }
5885 initialize: NETDATA.easypiechartInitialize,
5886 create: NETDATA.easypiechartChartCreate,
5887 update: NETDATA.easypiechartChartUpdate,
5889 setSelection: NETDATA.easypiechartSetSelection,
5890 clearSelection: NETDATA.easypiechartClearSelection,
5891 toolboxPanAndZoom: null,
5894 format: function(state) { void(state); return 'array'; },
5895 options: function(state) { void(state); return 'absolute'; },
5896 legend: function(state) { void(state); return null; },
5897 autoresize: function(state) { void(state); return false; },
5898 max_updates_to_recreate: function(state) { void(state); return 5000; },
5899 track_colors: function(state) { void(state); return true; },
5900 pixels_per_point: function(state) { void(state); return 3; },
5904 initialize: NETDATA.gaugeInitialize,
5905 create: NETDATA.gaugeChartCreate,
5906 update: NETDATA.gaugeChartUpdate,
5908 setSelection: NETDATA.gaugeSetSelection,
5909 clearSelection: NETDATA.gaugeClearSelection,
5910 toolboxPanAndZoom: null,
5913 format: function(state) { void(state); return 'array'; },
5914 options: function(state) { void(state); return 'absolute'; },
5915 legend: function(state) { void(state); return null; },
5916 autoresize: function(state) { void(state); return false; },
5917 max_updates_to_recreate: function(state) { void(state); return 5000; },
5918 track_colors: function(state) { void(state); return true; },
5919 pixels_per_point: function(state) { void(state); return 3; },
5924 NETDATA.registerChartLibrary = function(library, url) {
5925 if(NETDATA.options.debug.libraries === true)
5926 console.log("registering chart library: " + library);
5928 NETDATA.chartLibraries[library].url = url;
5929 NETDATA.chartLibraries[library].initialized = true;
5930 NETDATA.chartLibraries[library].enabled = true;
5933 // ----------------------------------------------------------------------------------------------------------------
5934 // Load required JS libraries and CSS
5936 NETDATA.requiredJs = [
5938 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5940 isAlreadyLoaded: function() {
5941 // check if bootstrap is loaded
5942 if(typeof $().emulateTransitionEnd === 'function')
5945 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
5950 url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5951 isAlreadyLoaded: function() { return false; }
5955 NETDATA.requiredCSS = [
5957 url: NETDATA.themes.current.bootstrap_css,
5958 isAlreadyLoaded: function() {
5959 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap === true);
5963 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5964 isAlreadyLoaded: function() { return false; }
5967 url: NETDATA.themes.current.dashboard_css,
5968 isAlreadyLoaded: function() { return false; }
5972 NETDATA.loadedRequiredJs = 0;
5973 NETDATA.loadRequiredJs = function(index, callback) {
5974 if(index >= NETDATA.requiredJs.length) {
5975 if(typeof callback === 'function')
5980 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5981 NETDATA.loadedRequiredJs++;
5982 NETDATA.loadRequiredJs(++index, callback);
5986 if(NETDATA.options.debug.main_loop === true)
5987 console.log('loading ' + NETDATA.requiredJs[index].url);
5990 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5994 url: NETDATA.requiredJs[index].url,
5997 xhrFields: { withCredentials: true } // required for the cookie
6000 if(NETDATA.options.debug.main_loop === true)
6001 console.log('loaded ' + NETDATA.requiredJs[index].url);
6004 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
6006 .always(function() {
6007 NETDATA.loadedRequiredJs++;
6010 NETDATA.loadRequiredJs(++index, callback);
6014 NETDATA.loadRequiredJs(++index, callback);
6017 NETDATA.loadRequiredCSS = function(index) {
6018 if(index >= NETDATA.requiredCSS.length)
6021 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6022 NETDATA.loadRequiredCSS(++index);
6026 if(NETDATA.options.debug.main_loop === true)
6027 console.log('loading ' + NETDATA.requiredCSS[index].url);
6029 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6030 NETDATA.loadRequiredCSS(++index);
6034 // ----------------------------------------------------------------------------------------------------------------
6035 // Registry of netdata hosts
6038 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
6039 chart_div_offset: 100, // give that space above the chart when scrolling to it
6040 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6041 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6043 ms_penalty: 0, // the time penalty of the next alarm
6044 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
6045 // if alarms are shown faster than: one per 500ms
6047 notifications: false, // when true, the browser supports notifications (may not be granted though)
6048 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
6049 first_notification_id: 0, // the id of the first alarm_log entry for this session
6050 // this is used to prevent CLEAR notifications for past events
6051 // notifications_shown: [],
6053 server: null, // the server to connect to for fetching alarms
6054 current: null, // the list of raised alarms - updated in the background
6055 callback: null, // a callback function to call every time the list of raised alarms is refreshed
6057 notify: function(entry) {
6058 // console.log('alarm ' + entry.unique_id);
6060 if(entry.updated === true) {
6061 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6065 var value_string = entry.value_string;
6067 if(NETDATA.alarms.current !== null) {
6068 // get the current value_string
6069 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6070 if(typeof t !== 'undefined' && entry.status === t.status)
6071 value_string = t.value_string;
6074 var name = entry.name.replace(/_/g, ' ');
6075 var status = entry.status.toLowerCase();
6076 var title = name + ' = ' + value_string.toString();
6077 var tag = entry.alarm_id;
6078 var icon = 'images/seo-performance-128.png';
6079 var interaction = false;
6083 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6085 switch(entry.status) {
6093 case 'UNINITIALIZED':
6097 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6098 // console.log('alarm ' + entry.unique_id + ' is not current');
6101 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6102 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6105 if(entry.no_clear_notification === true) {
6106 // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6109 title = name + ' back to normal (' + value_string.toString() + ')';
6110 icon = 'images/check-mark-2-128-green.png';
6111 interaction = false;
6115 if(entry.old_status === 'CRITICAL')
6116 status = 'demoted to ' + entry.status.toLowerCase();
6118 icon = 'images/alert-128-orange.png';
6119 interaction = false;
6123 if(entry.old_status === 'WARNING')
6124 status = 'escalated to ' + entry.status.toLowerCase();
6126 icon = 'images/alert-128-red.png';
6131 console.log('invalid alarm status ' + entry.status);
6136 // cleanup old notifications with the same alarm_id as this one
6137 // FIXME: it does not seem to work on any web browser!
6138 var len = NETDATA.alarms.notifications_shown.length;
6140 var n = NETDATA.alarms.notifications_shown[len];
6141 if(n.data.alarm_id === entry.alarm_id) {
6142 console.log('removing old alarm ' + n.data.unique_id);
6144 // close the notification
6147 // remove it from the array
6148 NETDATA.alarms.notifications_shown.splice(len, 1);
6149 len = NETDATA.alarms.notifications_shown.length;
6156 setTimeout(function() {
6157 // show this notification
6158 // console.log('new notification: ' + title);
6159 var n = new Notification(title, {
6160 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6162 requireInteraction: interaction,
6163 icon: NETDATA.serverDefault + icon,
6167 n.onclick = function(event) {
6168 event.preventDefault();
6169 NETDATA.alarms.onclick(event.target.data);
6173 // NETDATA.alarms.notifications_shown.push(n);
6174 // console.log(entry);
6175 }, NETDATA.alarms.ms_penalty);
6177 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6181 scrollToChart: function(chart_id) {
6182 if(typeof chart_id === 'string') {
6183 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6184 if(typeof offset !== 'undefined') {
6185 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6192 scrollToAlarm: function(alarm) {
6193 if(typeof alarm === 'object') {
6194 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6196 if(ret === true && NETDATA.options.page_is_visible === false)
6198 // 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.');
6203 notifyAll: function() {
6204 // console.log('FETCHING ALARM LOG');
6205 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6206 // console.log('ALARM LOG FETCHED');
6208 if(data === null || typeof data !== 'object') {
6209 console.log('invalid alarms log response');
6213 if(data.length === 0) {
6214 console.log('received empty alarm log');
6218 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6220 data.sort(function(a, b) {
6221 if(a.unique_id > b.unique_id) return -1;
6222 if(a.unique_id < b.unique_id) return 1;
6226 NETDATA.alarms.ms_penalty = 0;
6228 var len = data.length;
6230 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6231 NETDATA.alarms.notify(data[len]);
6234 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6237 NETDATA.alarms.last_notification_id = data[0].unique_id;
6238 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6239 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6243 check_notifications: function() {
6244 // returns true if we should fire 1+ notifications
6246 if(NETDATA.alarms.notifications !== true) {
6247 // console.log('notifications not available');
6251 if(Notification.permission !== 'granted') {
6252 // console.log('notifications not granted');
6256 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6257 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6259 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6260 // console.log('new alarms detected');
6263 //else console.log('no new alarms');
6265 // else console.log('cannot process alarms');
6270 get: function(what, callback) {
6272 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6276 'Cache-Control': 'no-cache, no-store',
6277 'Pragma': 'no-cache'
6279 xhrFields: { withCredentials: true } // required for the cookie
6281 .done(function(data) {
6282 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6283 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6285 if(typeof callback === 'function')
6286 return callback(data);
6289 NETDATA.error(415, NETDATA.alarms.server);
6291 if(typeof callback === 'function')
6292 return callback(null);
6296 update_forever: function() {
6297 NETDATA.alarms.get('active', function(data) {
6299 NETDATA.alarms.current = data;
6301 if(NETDATA.alarms.check_notifications() === true) {
6302 NETDATA.alarms.notifyAll();
6305 if (typeof NETDATA.alarms.callback === 'function') {
6306 NETDATA.alarms.callback(data);
6309 // Health monitoring is disabled on this netdata
6310 if(data.status === false) return;
6313 setTimeout(NETDATA.alarms.update_forever, 10000);
6317 get_log: function(last_id, callback) {
6318 // console.log('fetching all log after ' + last_id.toString());
6320 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6324 'Cache-Control': 'no-cache, no-store',
6325 'Pragma': 'no-cache'
6327 xhrFields: { withCredentials: true } // required for the cookie
6329 .done(function(data) {
6330 if(typeof callback === 'function')
6331 return callback(data);
6334 NETDATA.error(416, NETDATA.alarms.server);
6336 if(typeof callback === 'function')
6337 return callback(null);
6342 NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6344 NETDATA.alarms.last_notification_id =
6345 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6347 if(NETDATA.alarms.onclick === null)
6348 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6350 if(netdataShowAlarms === true) {
6351 NETDATA.alarms.update_forever();
6353 if('Notification' in window) {
6354 // console.log('notifications available');
6355 NETDATA.alarms.notifications = true;
6357 if(Notification.permission === 'default')
6358 Notification.requestPermission();
6364 // ----------------------------------------------------------------------------------------------------------------
6365 // Registry of netdata hosts
6367 NETDATA.registry = {
6368 server: null, // the netdata registry server
6369 person_guid: null, // the unique ID of this browser / user
6370 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6371 hostname: 'unknown', // the hostname of the netdata server that served dashboard.js
6372 machines: null, // the user's other URLs
6373 machines_array: null, // the user's other URLs in an array
6376 parsePersonUrls: function(person_urls) {
6377 // console.log(person_urls);
6378 NETDATA.registry.person_urls = person_urls;
6381 NETDATA.registry.machines = {};
6382 NETDATA.registry.machines_array = [];
6384 var apu = person_urls;
6387 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6388 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6394 accesses: apu[i][3],
6398 obj.alternate_urls.push(apu[i][1]);
6400 NETDATA.registry.machines[apu[i][0]] = obj;
6401 NETDATA.registry.machines_array.push(obj);
6404 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6406 var pu = NETDATA.registry.machines[apu[i][0]];
6407 if(pu.last_t < apu[i][2]) {
6409 pu.last_t = apu[i][2];
6410 pu.name = apu[i][4];
6412 pu.accesses += apu[i][3];
6413 pu.alternate_urls.push(apu[i][1]);
6418 if(typeof netdataRegistryCallback === 'function')
6419 netdataRegistryCallback(NETDATA.registry.machines_array);
6423 if(netdataRegistry !== true) return;
6425 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6427 NETDATA.registry.server = data.registry;
6428 NETDATA.registry.machine_guid = data.machine_guid;
6429 NETDATA.registry.hostname = data.hostname;
6431 NETDATA.registry.access(2, function (person_urls) {
6432 NETDATA.registry.parsePersonUrls(person_urls);
6439 hello: function(host, callback) {
6440 host = NETDATA.fixHost(host);
6442 // send HELLO to a netdata server:
6443 // 1. verifies the server is reachable
6444 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6446 url: host + '/api/v1/registry?action=hello',
6450 'Cache-Control': 'no-cache, no-store',
6451 'Pragma': 'no-cache'
6453 xhrFields: { withCredentials: true } // required for the cookie
6455 .done(function(data) {
6456 if(typeof data.status !== 'string' || data.status !== 'ok') {
6457 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6461 if(typeof callback === 'function')
6462 return callback(data);
6465 NETDATA.error(407, host);
6467 if(typeof callback === 'function')
6468 return callback(null);
6472 access: function(max_redirects, callback) {
6473 // send ACCESS to a netdata registry:
6474 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6475 // 2. it responds with a list of netdata servers we know
6476 // the registry identifies us using a cookie it sets the first time we access it
6477 // the registry may respond with a redirect URL to send us to another registry
6479 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),
6483 'Cache-Control': 'no-cache, no-store',
6484 'Pragma': 'no-cache'
6486 xhrFields: { withCredentials: true } // required for the cookie
6488 .done(function(data) {
6489 var redirect = null;
6490 if(typeof data.registry === 'string')
6491 redirect = data.registry;
6493 if(typeof data.status !== 'string' || data.status !== 'ok') {
6494 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6499 if(redirect !== null && max_redirects > 0) {
6500 NETDATA.registry.server = redirect;
6501 NETDATA.registry.access(max_redirects - 1, callback);
6504 if(typeof callback === 'function')
6505 return callback(null);
6509 if(typeof data.person_guid === 'string')
6510 NETDATA.registry.person_guid = data.person_guid;
6512 if(typeof callback === 'function')
6513 return callback(data.urls);
6517 NETDATA.error(410, NETDATA.registry.server);
6519 if(typeof callback === 'function')
6520 return callback(null);
6524 delete: function(delete_url, callback) {
6525 // send DELETE to a netdata registry:
6527 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),
6531 'Cache-Control': 'no-cache, no-store',
6532 'Pragma': 'no-cache'
6534 xhrFields: { withCredentials: true } // required for the cookie
6536 .done(function(data) {
6537 if(typeof data.status !== 'string' || data.status !== 'ok') {
6538 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6542 if(typeof callback === 'function')
6543 return callback(data);
6546 NETDATA.error(412, NETDATA.registry.server);
6548 if(typeof callback === 'function')
6549 return callback(null);
6553 search: function(machine_guid, callback) {
6554 // SEARCH for the URLs of a machine:
6556 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,
6560 'Cache-Control': 'no-cache, no-store',
6561 'Pragma': 'no-cache'
6563 xhrFields: { withCredentials: true } // required for the cookie
6565 .done(function(data) {
6566 if(typeof data.status !== 'string' || data.status !== 'ok') {
6567 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6571 if(typeof callback === 'function')
6572 return callback(data);
6575 NETDATA.error(418, NETDATA.registry.server);
6577 if(typeof callback === 'function')
6578 return callback(null);
6582 switch: function(new_person_guid, callback) {
6585 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,
6589 'Cache-Control': 'no-cache, no-store',
6590 'Pragma': 'no-cache'
6592 xhrFields: { withCredentials: true } // required for the cookie
6594 .done(function(data) {
6595 if(typeof data.status !== 'string' || data.status !== 'ok') {
6596 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6600 if(typeof callback === 'function')
6601 return callback(data);
6604 NETDATA.error(414, NETDATA.registry.server);
6606 if(typeof callback === 'function')
6607 return callback(null);
6612 // ----------------------------------------------------------------------------------------------------------------
6615 if(typeof netdataPrepCallback === 'function')
6616 netdataPrepCallback();
6618 NETDATA.errorReset();
6619 NETDATA.loadRequiredCSS(0);
6621 NETDATA._loadjQuery(function() {
6622 NETDATA.loadRequiredJs(0, function() {
6623 if(typeof $().emulateTransitionEnd !== 'function') {
6624 // bootstrap is not available
6625 NETDATA.options.current.show_help = false;
6628 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6629 if(NETDATA.options.debug.main_loop === true)
6630 console.log('starting chart refresh thread');
6636 })(window, document);