1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // You can also set the default netdata server, using the following.
24 // When this variable is not set, we assume the page is hosted on your
25 // netdata server already.
26 // var netdataServer = "http://yourhost:19999"; // set your NetData server
28 //(function(window, document, undefined) {
30 // ------------------------------------------------------------------------
31 // compatibility fixes
33 // fix IE issue with console
34 if(!window.console) { window.console = { log: function(){} }; }
36 // if string.endsWith is not defined, define it
37 if(typeof String.prototype.endsWith !== 'function') {
38 String.prototype.endsWith = function(s) {
39 if(s.length > this.length) return false;
40 return this.slice(-s.length) === s;
44 // if string.startsWith is not defined, define it
45 if(typeof String.prototype.startsWith !== 'function') {
46 String.prototype.startsWith = function(s) {
47 if(s.length > this.length) return false;
48 return this.slice(s.length) === s;
53 var NETDATA = window.NETDATA || {};
55 // ----------------------------------------------------------------------------------------------------------------
56 // Detect the netdata server
58 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
59 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
60 NETDATA._scriptSource = function() {
63 if(typeof document.currentScript !== 'undefined') {
64 script = document.currentScript;
67 var all_scripts = document.getElementsByTagName('script');
68 script = all_scripts[all_scripts.length - 1];
71 if (typeof script.getAttribute.length !== 'undefined')
74 script = script.getAttribute('src', -1);
79 if(typeof netdataServer !== 'undefined')
80 NETDATA.serverDefault = netdataServer;
82 var s = NETDATA._scriptSource();
83 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
85 console.log('WARNING: Cannot detect the URL of the netdata server.');
86 NETDATA.serverDefault = null;
90 if(NETDATA.serverDefault === null)
91 NETDATA.serverDefault = '';
92 else if(NETDATA.serverDefault.slice(-1) !== '/')
93 NETDATA.serverDefault += '/';
95 // default URLs for all the external files we need
96 // make them RELATIVE so that the whole thing can also be
97 // installed under a web server
98 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
99 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
100 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
101 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
102 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
103 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
104 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
105 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
106 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
107 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
108 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
109 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
110 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
111 NETDATA.google_js = 'https://www.google.com/jsapi';
115 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
116 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
117 background: '#FFFFFF',
118 foreground: '#000000',
121 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
122 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
123 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
124 '#329262', '#3B3EAC' ],
125 easypiechart_track: '#f0f0f0',
126 easypiechart_scale: '#dfe0e0',
127 gauge_pointer: '#C0C0C0',
128 gauge_stroke: '#F0F0F0',
129 gauge_gradient: false
132 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
133 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
134 background: '#272b30',
135 foreground: '#C8C8C8',
138 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
139 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
140 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
141 '#a6a479', '#a66da8' ],
143 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
144 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
145 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
146 '#329262', '#3B3EFF' ],
147 easypiechart_track: '#373b40',
148 easypiechart_scale: '#373b40',
149 gauge_pointer: '#474b50',
150 gauge_stroke: '#373b40',
151 gauge_gradient: false
155 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
156 NETDATA.themes.current = NETDATA.themes[netdataTheme];
158 NETDATA.themes.current = NETDATA.themes.white;
160 NETDATA.colors = NETDATA.themes.current.colors;
162 // these are the colors Google Charts are using
163 // we have them here to attempt emulate their look and feel on the other chart libraries
164 // http://there4.io/2012/05/02/google-chart-color-list/
165 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
166 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
167 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
169 // an alternative set
170 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
171 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
172 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
174 if(typeof netdataShowHelp === 'undefined')
175 netdataShowHelp = true;
177 if(typeof netdataShowAlarms === 'undefined')
178 netdataShowAlarms = false;
180 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
181 netdataRegistryAfterMs = 1500;
183 if(typeof netdataRegistry === 'undefined') {
184 // backward compatibility
185 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
186 netdataRegistry = true;
188 netdataRegistry = false;
190 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
191 netdataRegistry = true;
193 // ----------------------------------------------------------------------------------------------------------------
194 // the defaults for all charts
196 // if the user does not specify any of these, the following will be used
198 NETDATA.chartDefaults = {
199 host: NETDATA.serverDefault, // the server to get data from
200 width: '100%', // the chart width - can be null
201 height: '100%', // the chart height - can be null
202 min_width: null, // the chart minimum width - can be null
203 library: 'dygraph', // the graphing library to use
204 method: 'average', // the grouping method
205 before: 0, // panning
206 after: -600, // panning
207 pixels_per_point: 1, // the detail of the chart
208 fill_luminance: 0.8 // luminance of colors in solit areas
211 // ----------------------------------------------------------------------------------------------------------------
215 pauseCallback: null, // a callback when we are really paused
217 pause: false, // when enabled we don't auto-refresh the charts
219 targets: null, // an array of all the state objects that are
220 // currently active (independently of their
221 // viewport visibility)
223 updated_dom: true, // when true, the DOM has been updated with
224 // new elements we have to check.
226 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
227 // rendering charts continiously.
228 // used with .current.fast_render_timeframe
230 page_is_visible: true, // when true, this page is visible
232 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
233 // auto-refresher for some time (when a chart is
234 // performing pan or zoom, we need to stop refreshing
235 // all other charts, to have the maximum speed for
236 // rendering the chart that is panned or zoomed).
237 // Used with .current.global_pan_sync_time
239 last_resized: new Date().getTime(), // the timestamp of the last resize request
241 last_page_scroll: 0, // the timestamp the last time the page was scrolled
243 // the current profile
244 // we may have many...
246 pixels_per_point: 1, // the minimum pixels per point for all charts
247 // increase this to speed javascript up
248 // each chart library has its own limit too
249 // the max of this and the chart library is used
250 // the final is calculated every time, so a change
251 // here will have immediate effect on the next chart
254 idle_between_charts: 100, // ms - how much time to wait between chart updates
256 fast_render_timeframe: 200, // ms - render continously until this time of continious
257 // rendering has been reached
258 // this setting is used to make it render e.g. 10
259 // charts at once, sleep idle_between_charts time
260 // and continue for another 10 charts.
262 idle_between_loops: 500, // ms - if all charts have been updated, wait this
263 // time before starting again.
265 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
267 idle_lost_focus: 500, // ms - when the window does not have focus, check
268 // if focus has been regained, every this time
270 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
271 // autorefreshing of charts is paused for this amount
274 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
275 // of time before setting up synchronized selections
278 sync_selection: true, // enable or disable selection sync
280 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
282 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
284 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
286 update_only_visible: true, // enable or disable visibility management
288 parallel_refresher: true, // enable parallel refresh of charts
290 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
292 destroy_on_hide: false, // destroy charts when they are not visible
294 show_help: netdataShowHelp, // when enabled the charts will show some help
295 show_help_delay_show_ms: 500,
296 show_help_delay_hide_ms: 0,
298 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
300 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
301 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
303 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
305 smooth_plot: true, // enable smooth plot, where possible
307 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
309 color_fill_opacity_line: 1.0,
310 color_fill_opacity_area: 0.2,
311 color_fill_opacity_stacked: 0.8,
313 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
314 pan_and_zoom_factor_multiplier_control: 2.0,
315 pan_and_zoom_factor_multiplier_shift: 3.0,
316 pan_and_zoom_factor_multiplier_alt: 4.0,
318 abort_ajax_on_scroll: false,
320 setOptionCallback: function() { ; }
328 chart_data_url: false,
329 chart_errors: false, // FIXME
337 NETDATA.statistics = {
340 refreshes_active_max: 0
344 // ----------------------------------------------------------------------------------------------------------------
345 // local storage options
347 NETDATA.localStorage = {
350 callback: {} // only used for resetting back to defaults
353 NETDATA.localStorageGet = function(key, def, callback) {
356 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
357 NETDATA.localStorage.default[key.toString()] = def;
358 NETDATA.localStorage.callback[key.toString()] = callback;
361 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
363 // console.log('localStorage: loading "' + key.toString() + '"');
364 ret = localStorage.getItem(key.toString());
365 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
366 if(ret === null || ret === 'undefined') {
367 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
368 localStorage.setItem(key.toString(), JSON.stringify(def));
372 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
373 ret = JSON.parse(ret);
374 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
378 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
383 if(typeof ret === 'undefined' || ret === 'undefined') {
384 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
388 NETDATA.localStorage.current[key.toString()] = ret;
392 NETDATA.localStorageSet = function(key, value, callback) {
393 if(typeof value === 'undefined' || value === 'undefined') {
394 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
397 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
398 NETDATA.localStorage.default[key.toString()] = value;
399 NETDATA.localStorage.current[key.toString()] = value;
400 NETDATA.localStorage.callback[key.toString()] = callback;
403 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
404 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
406 localStorage.setItem(key.toString(), JSON.stringify(value));
409 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
413 NETDATA.localStorage.current[key.toString()] = value;
417 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
419 if(typeof obj[i] === 'object') {
420 //console.log('object ' + prefix + '.' + i.toString());
421 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
425 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
429 NETDATA.setOption = function(key, value) {
430 if(key.toString() === 'setOptionCallback') {
431 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
432 NETDATA.options.current[key.toString()] = value;
433 NETDATA.options.current.setOptionCallback();
436 else if(NETDATA.options.current[key.toString()] !== value) {
437 var name = 'options.' + key.toString();
439 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
440 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
442 //console.log(NETDATA.localStorage);
443 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
444 //console.log(NETDATA.options);
445 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
447 if(typeof NETDATA.options.current.setOptionCallback === 'function')
448 NETDATA.options.current.setOptionCallback();
454 NETDATA.getOption = function(key) {
455 return NETDATA.options.current[key.toString()];
458 // read settings from local storage
459 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
461 // always start with this option enabled.
462 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
464 NETDATA.resetOptions = function() {
465 for(var i in NETDATA.localStorage.default) {
466 var a = i.split('.');
468 if(a[0] === 'options') {
469 if(a[1] === 'setOptionCallback') continue;
470 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
471 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
473 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
475 else if(a[0] === 'chart_heights') {
476 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
477 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
483 // ----------------------------------------------------------------------------------------------------------------
485 if(NETDATA.options.debug.main_loop === true)
486 console.log('welcome to NETDATA');
488 NETDATA.onresize = function() {
489 NETDATA.options.last_resized = new Date().getTime();
493 NETDATA.onscroll = function() {
494 // console.log('onscroll');
496 NETDATA.options.last_page_scroll = new Date().getTime();
497 NETDATA.options.auto_refresher_stop_until = 0;
499 if(NETDATA.options.targets === null) return;
501 // when the user scrolls he sees that we have
502 // hidden all the not-visible charts
503 // using this little function we try to switch
504 // the charts back to visible quickly
505 var targets = NETDATA.options.targets;
506 var len = targets.length;
507 if(NETDATA.options.abort_ajax_on_scroll === true) {
509 if (targets[len]._updating === true) {
510 if (typeof targets[len].xhr !== 'undefined') {
511 targets[len].xhr.abort();
512 targets[len].running = false;
513 targets[len]._updating = false;
515 targets[len].isVisible();
521 targets[len].isVisible();
525 window.onresize = NETDATA.onresize;
526 window.onscroll = NETDATA.onscroll;
528 // ----------------------------------------------------------------------------------------------------------------
531 NETDATA.errorCodes = {
532 100: { message: "Cannot load chart library", alert: true },
533 101: { message: "Cannot load jQuery", alert: true },
534 402: { message: "Chart library not found", alert: false },
535 403: { message: "Chart library not enabled/is failed", alert: false },
536 404: { message: "Chart not found", alert: false },
537 405: { message: "Cannot download charts index from server", alert: true },
538 406: { message: "Invalid charts index downloaded from server", alert: true },
539 407: { message: "Cannot HELLO netdata server", alert: false },
540 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
541 409: { message: "Cannot ACCESS netdata registry", alert: false },
542 410: { message: "Netdata registry ACCESS failed", alert: false },
543 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
544 412: { message: "Netdata registry DELETE failed", alert: false },
545 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
546 414: { message: "Netdata registry SWITCH failed", alert: false },
547 415: { message: "Netdata alarms download failed", alert: false },
548 416: { message: "Netdata alarms log download failed", alert: false }
550 NETDATA.errorLast = {
556 NETDATA.error = function(code, msg) {
557 NETDATA.errorLast.code = code;
558 NETDATA.errorLast.message = msg;
559 NETDATA.errorLast.datetime = new Date().getTime();
561 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
564 if(typeof netdataErrorCallback === 'function') {
565 ret = netdataErrorCallback('system', code, msg);
568 if(ret && NETDATA.errorCodes[code].alert)
569 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
572 NETDATA.errorReset = function() {
573 NETDATA.errorLast.code = 0;
574 NETDATA.errorLast.message = "You are doing fine!";
575 NETDATA.errorLast.datetime = 0;
578 // ----------------------------------------------------------------------------------------------------------------
581 // When multiple charts need the same chart, we avoid downloading it
582 // multiple times (and having it in browser memory multiple time)
583 // by using this registry.
585 // Every time we download a chart definition, we save it here with .add()
586 // Then we try to get it back with .get(). If that fails, we download it.
588 NETDATA.chartRegistry = {
591 fixid: function(id) {
592 return id.replace(/:/g, "_").replace(/\//g, "_");
595 add: function(host, id, data) {
596 host = this.fixid(host);
599 if(typeof this.charts[host] === 'undefined')
600 this.charts[host] = {};
602 //console.log('added ' + host + '/' + id);
603 this.charts[host][id] = data;
606 get: function(host, id) {
607 host = this.fixid(host);
610 if(typeof this.charts[host] === 'undefined')
613 if(typeof this.charts[host][id] === 'undefined')
616 //console.log('cached ' + host + '/' + id);
617 return this.charts[host][id];
620 downloadAll: function(host, callback) {
621 while(host.slice(-1) === '/')
622 host = host.substring(0, host.length - 1);
627 url: host + '/api/v1/charts',
630 xhrFields: { withCredentials: true } // required for the cookie
632 .done(function(data) {
634 var h = NETDATA.chartRegistry.fixid(host);
635 self.charts[h] = data.charts;
637 else NETDATA.error(406, host + '/api/v1/charts');
639 if(typeof callback === 'function')
643 NETDATA.error(405, host + '/api/v1/charts');
645 if(typeof callback === 'function')
651 // ----------------------------------------------------------------------------------------------------------------
652 // Global Pan and Zoom on charts
654 // Using this structure are synchronize all the charts, so that
655 // when you pan or zoom one, all others are automatically refreshed
656 // to the same timespan.
658 NETDATA.globalPanAndZoom = {
659 seq: 0, // timestamp ms
660 // every time a chart is panned or zoomed
661 // we set the timestamp here
662 // then we use it as a sequence number
663 // to find if other charts are syncronized
666 master: null, // the master chart (state), to which all others
669 force_before_ms: null, // the timespan to sync all other charts
670 force_after_ms: null,
675 setMaster: function(state, after, before) {
676 if(NETDATA.options.current.sync_pan_and_zoom === false)
679 if(this.master !== null && this.master !== state)
680 this.master.resetChart(true, true);
682 var now = new Date().getTime();
685 this.force_after_ms = after;
686 this.force_before_ms = before;
687 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
689 if(typeof this.callback === 'function')
690 this.callback(true, after, before);
694 clearMaster: function() {
695 if(this.master !== null) {
696 var st = this.master;
703 this.force_after_ms = null;
704 this.force_before_ms = null;
705 NETDATA.options.auto_refresher_stop_until = 0;
707 if(typeof this.callback === 'function')
708 this.callback(false, 0, 0);
711 // is the given state the master of the global
712 // pan and zoom sync?
713 isMaster: function(state) {
714 if(this.master === state) return true;
718 // are we currently have a global pan and zoom sync?
719 isActive: function() {
720 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
724 // check if a chart, other than the master
725 // needs to be refreshed, due to the global pan and zoom
726 shouldBeAutoRefreshed: function(state) {
727 if(this.master === null || this.seq === 0)
730 //if(state.needsRecreation())
733 if(state.tm.pan_and_zoom_seq === this.seq)
740 // ----------------------------------------------------------------------------------------------------------------
741 // dimensions selection
744 // move color assignment to dimensions, here
746 dimensionStatus = function(parent, label, name_div, value_div, color) {
747 this.enabled = false;
748 this.parent = parent;
750 this.name_div = null;
751 this.value_div = null;
752 this.color = NETDATA.themes.current.foreground;
754 if(parent.selected_count > parent.unselected_count)
755 this.selected = true;
757 this.selected = false;
759 this.setOptions(name_div, value_div, color);
762 dimensionStatus.prototype.invalidate = function() {
763 this.name_div = null;
764 this.value_div = null;
765 this.enabled = false;
768 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
771 if(this.name_div != name_div) {
772 this.name_div = name_div;
773 this.name_div.title = this.label;
774 this.name_div.style.color = this.color;
775 if(this.selected === false)
776 this.name_div.className = 'netdata-legend-name not-selected';
778 this.name_div.className = 'netdata-legend-name selected';
781 if(this.value_div != value_div) {
782 this.value_div = value_div;
783 this.value_div.title = this.label;
784 this.value_div.style.color = this.color;
785 if(this.selected === false)
786 this.value_div.className = 'netdata-legend-value not-selected';
788 this.value_div.className = 'netdata-legend-value selected';
795 dimensionStatus.prototype.setHandler = function() {
796 if(this.enabled === false) return;
800 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
801 this.name_div.onclick = this.value_div.onclick = function(e) {
803 if(ds.isSelected()) {
805 if(e.shiftKey === true || e.ctrlKey === true) {
806 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
809 if(ds.parent.countSelected() === 0)
810 ds.parent.selectAll();
813 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
814 if(ds.parent.countSelected() === 1) {
815 ds.parent.selectAll();
818 ds.parent.selectNone();
824 // this is not selected
825 if(e.shiftKey === true || e.ctrlKey === true) {
826 // control or shift key is pressed -> select this too
830 // no key is pressed -> select only this
831 ds.parent.selectNone();
836 ds.parent.state.redrawChart();
840 dimensionStatus.prototype.select = function() {
841 if(this.enabled === false) return;
843 this.name_div.className = 'netdata-legend-name selected';
844 this.value_div.className = 'netdata-legend-value selected';
845 this.selected = true;
848 dimensionStatus.prototype.unselect = function() {
849 if(this.enabled === false) return;
851 this.name_div.className = 'netdata-legend-name not-selected';
852 this.value_div.className = 'netdata-legend-value hidden';
853 this.selected = false;
856 dimensionStatus.prototype.isSelected = function() {
857 return(this.enabled === true && this.selected === true);
860 // ----------------------------------------------------------------------------------------------------------------
862 dimensionsVisibility = function(state) {
865 this.dimensions = {};
866 this.selected_count = 0;
867 this.unselected_count = 0;
870 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
871 if(typeof this.dimensions[label] === 'undefined') {
873 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
876 this.dimensions[label].setOptions(name_div, value_div, color);
878 return this.dimensions[label];
881 dimensionsVisibility.prototype.dimensionGet = function(label) {
882 return this.dimensions[label];
885 dimensionsVisibility.prototype.invalidateAll = function() {
886 for(var d in this.dimensions)
887 this.dimensions[d].invalidate();
890 dimensionsVisibility.prototype.selectAll = function() {
891 for(var d in this.dimensions)
892 this.dimensions[d].select();
895 dimensionsVisibility.prototype.countSelected = function() {
897 for(var d in this.dimensions)
898 if(this.dimensions[d].isSelected()) i++;
903 dimensionsVisibility.prototype.selectNone = function() {
904 for(var d in this.dimensions)
905 this.dimensions[d].unselect();
908 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
909 var ret = new Array();
910 this.selected_count = 0;
911 this.unselected_count = 0;
913 for(var i = 0, len = array.length; i < len ; i++) {
914 var ds = this.dimensions[array[i]];
915 if(typeof ds === 'undefined') {
916 // console.log(array[i] + ' is not found');
921 if(ds.isSelected()) {
923 this.selected_count++;
927 this.unselected_count++;
931 if(this.selected_count === 0 && this.unselected_count !== 0) {
933 return this.selected2BooleanArray(array);
940 // ----------------------------------------------------------------------------------------------------------------
941 // global selection sync
943 NETDATA.globalSelectionSync = {
950 if(this.state !== null)
951 this.state.globalSelectionSyncStop();
955 if(this.state !== null) {
956 this.state.globalSelectionSyncDelay();
961 // ----------------------------------------------------------------------------------------------------------------
962 // Our state object, where all per-chart values are stored
964 chartState = function(element) {
965 var self = $(element);
966 this.element = element;
969 // all private functions should use 'that', instead of 'this'
973 * show an error instead of the chart
975 var error = function(msg) {
978 if(typeof netdataErrorCallback === 'function') {
979 ret = netdataErrorCallback('chart', that.id, msg);
983 that.element.innerHTML = that.id + ': ' + msg;
984 that.enabled = false;
985 that.current = that.pan;
989 // GUID - a unique identifier for the chart
990 this.uuid = NETDATA.guid();
992 // string - the name of chart
993 this.id = self.data('netdata');
995 // string - the key for localStorage settings
996 this.settings_id = self.data('id') || null;
998 // the user given dimensions of the element
999 this.width = self.data('width') || NETDATA.chartDefaults.width;
1000 this.height = self.data('height') || NETDATA.chartDefaults.height;
1002 if(this.settings_id !== null) {
1003 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1004 // this is the callback that will be called
1005 // if and when the user resets all localStorage variables
1006 // to their defaults
1008 resizeChartToHeight(height);
1012 // string - the netdata server URL, without any path
1013 this.host = self.data('host') || NETDATA.chartDefaults.host;
1015 // make sure the host does not end with /
1016 // all netdata API requests use absolute paths
1017 while(this.host.slice(-1) === '/')
1018 this.host = this.host.substring(0, this.host.length - 1);
1020 // string - the grouping method requested by the user
1021 this.method = self.data('method') || NETDATA.chartDefaults.method;
1023 // the time-range requested by the user
1024 this.after = self.data('after') || NETDATA.chartDefaults.after;
1025 this.before = self.data('before') || NETDATA.chartDefaults.before;
1027 // the pixels per point requested by the user
1028 this.pixels_per_point = self.data('pixels-per-point') || 1;
1029 this.points = self.data('points') || null;
1031 // the dimensions requested by the user
1032 this.dimensions = self.data('dimensions') || null;
1034 // the chart library requested by the user
1035 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1037 // object - the chart library used
1038 this.library = null;
1042 this.colors_assigned = {};
1043 this.colors_available = null;
1045 // the element already created by the user
1046 this.element_message = null;
1048 // the element with the chart
1049 this.element_chart = null;
1051 // the element with the legend of the chart (if created by us)
1052 this.element_legend = null;
1053 this.element_legend_childs = {
1063 this.chart_url = null; // string - the url to download chart info
1064 this.chart = null; // object - the chart as downloaded from the server
1066 this.title = self.data('title') || null; // the title of the chart
1067 this.units = self.data('units') || null; // the units of the chart dimensions
1068 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1070 this.running = false; // boolean - true when the chart is being refreshed now
1071 this.validated = false; // boolean - has the chart been validated?
1072 this.enabled = true; // boolean - is the chart enabled for refresh?
1073 this.paused = false; // boolean - is the chart paused for any reason?
1074 this.selected = false; // boolean - is the chart shown a selection?
1075 this.debug = false; // boolean - console.log() debug info about this chart
1077 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1078 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1079 this.requested_after = null; // milliseconds - the timestamp of the request after param
1080 this.requested_before = null; // milliseconds - the timestamp of the request before param
1081 this.requested_padding = null;
1082 this.view_after = 0;
1083 this.view_before = 0;
1088 force_update_at: 0, // the timestamp to force the update at
1089 force_before_ms: null,
1090 force_after_ms: null
1095 force_update_at: 0, // the timestamp to force the update at
1096 force_before_ms: null,
1097 force_after_ms: null
1102 force_update_at: 0, // the timestamp to force the update at
1103 force_before_ms: null,
1104 force_after_ms: null
1107 // this is a pointer to one of the sub-classes below
1109 this.current = this.auto;
1111 // check the requested library is available
1112 // we don't initialize it here - it will be initialized when
1113 // this chart will be first used
1114 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1115 NETDATA.error(402, that.library_name);
1116 error('chart library "' + that.library_name + '" is not found');
1119 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1120 NETDATA.error(403, that.library_name);
1121 error('chart library "' + that.library_name + '" is not enabled');
1125 that.library = NETDATA.chartLibraries[that.library_name];
1127 // milliseconds - the time the last refresh took
1128 this.refresh_dt_ms = 0;
1130 // if we need to report the rendering speed
1131 // find the element that needs to be updated
1132 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1134 if(refresh_dt_element_name !== null)
1135 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1137 this.refresh_dt_element = null;
1139 this.dimensions_visibility = new dimensionsVisibility(this);
1141 this._updating = false;
1143 // ============================================================================================================
1144 // PRIVATE FUNCTIONS
1146 var createDOM = function() {
1147 if(that.enabled === false) return;
1149 if(that.element_message !== null) that.element_message.innerHTML = '';
1150 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1151 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1153 that.element.innerHTML = '';
1155 that.element_message = document.createElement('div');
1156 that.element_message.className = ' netdata-message hidden';
1157 that.element.appendChild(that.element_message);
1159 that.element_chart = document.createElement('div');
1160 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1161 that.element.appendChild(that.element_chart);
1163 if(that.hasLegend() === true) {
1164 that.element.className = "netdata-container-with-legend";
1165 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1167 that.element_legend = document.createElement('div');
1168 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1169 that.element.appendChild(that.element_legend);
1172 that.element.className = "netdata-container";
1173 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1175 that.element_legend = null;
1177 that.element_legend_childs.series = null;
1179 if(typeof(that.width) === 'string')
1180 $(that.element).css('width', that.width);
1181 else if(typeof(that.width) === 'number')
1182 $(that.element).css('width', that.width + 'px');
1184 if(typeof(that.library.aspect_ratio) === 'undefined') {
1185 if(typeof(that.height) === 'string')
1186 $(that.element).css('height', that.height);
1187 else if(typeof(that.height) === 'number')
1188 $(that.element).css('height', that.height + 'px');
1191 var w = that.element.offsetWidth;
1192 if(w === null || w === 0) {
1193 // the div is hidden
1194 // this will resize the chart when next viewed
1195 that.tm.last_resized = 0;
1198 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1201 if(NETDATA.chartDefaults.min_width !== null)
1202 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1204 that.tm.last_dom_created = new Date().getTime();
1210 * initialize state variables
1211 * destroy all (possibly) created state elements
1212 * create the basic DOM for a chart
1214 var init = function() {
1215 if(that.enabled === false) return;
1217 that.paused = false;
1218 that.selected = false;
1220 that.chart_created = false; // boolean - is the library.create() been called?
1221 that.updates_counter = 0; // numeric - the number of refreshes made so far
1222 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1223 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1226 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1227 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1228 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1230 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1231 last_updated: 0, // the timestamp the chart last updated with data
1232 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1234 // Used with NETDATA.globalPanAndZoom.seq
1235 last_visible_check: 0, // the time we last checked if it is visible
1236 last_resized: 0, // the time the chart was resized
1237 last_hidden: 0, // the time the chart was hidden
1238 last_unhidden: 0, // the time the chart was unhidden
1239 last_autorefreshed: 0 // the time the chart was last refreshed
1242 that.data = null; // the last data as downloaded from the netdata server
1243 that.data_url = 'invalid://'; // string - the last url used to update the chart
1244 that.data_points = 0; // number - the number of points returned from netdata
1245 that.data_after = 0; // milliseconds - the first timestamp of the data
1246 that.data_before = 0; // milliseconds - the last timestamp of the data
1247 that.data_update_every = 0; // milliseconds - the frequency to update the data
1249 that.tm.last_initialized = new Date().getTime();
1252 that.setMode('auto');
1255 var maxMessageFontSize = function() {
1256 // normally we want a font size, as tall as the element
1257 var h = that.element_message.clientHeight;
1259 // but give it some air, 20% let's say, or 5 pixels min
1260 var lost = Math.max(h * 0.2, 5);
1263 // center the text, vertically
1264 var paddingTop = (lost - 5) / 2;
1266 // but check the width too
1267 // it should fit 10 characters in it
1268 var w = that.element_message.clientWidth / 10;
1270 paddingTop += (h - w) / 2;
1274 // and don't make it too huge
1275 // 5% of the screen size is good
1276 if(h > screen.height / 20) {
1277 paddingTop += (h - (screen.height / 20)) / 2;
1278 h = screen.height / 20;
1282 that.element_message.style.fontSize = h.toString() + 'px';
1283 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1286 var showMessage = function(msg) {
1287 that.element_message.className = 'netdata-message';
1288 that.element_message.innerHTML = msg;
1289 that.element_message.style.fontSize = 'x-small';
1290 that.element_message.style.paddingTop = '0px';
1291 that.___messageHidden___ = undefined;
1294 var showMessageIcon = function(icon) {
1295 that.element_message.innerHTML = icon;
1296 that.element_message.className = 'netdata-message icon';
1297 maxMessageFontSize();
1298 that.___messageHidden___ = undefined;
1301 var hideMessage = function() {
1302 if(typeof that.___messageHidden___ === 'undefined') {
1303 that.___messageHidden___ = true;
1304 that.element_message.className = 'netdata-message hidden';
1308 var showRendering = function() {
1310 if(that.chart !== null) {
1311 if(that.chart.chart_type === 'line')
1312 icon = '<i class="fa fa-line-chart"></i>';
1314 icon = '<i class="fa fa-area-chart"></i>';
1317 icon = '<i class="fa fa-area-chart"></i>';
1319 showMessageIcon(icon + ' netdata');
1322 var showLoading = function() {
1323 if(that.chart_created === false) {
1324 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1330 var isHidden = function() {
1331 if(typeof that.___chartIsHidden___ !== 'undefined')
1337 // hide the chart, when it is not visible - called from isVisible()
1338 var hideChart = function() {
1339 // hide it, if it is not already hidden
1340 if(isHidden() === true) return;
1342 if(that.chart_created === true) {
1343 if(NETDATA.options.current.destroy_on_hide === true) {
1344 // we should destroy it
1349 that.element_chart.style.display = 'none';
1350 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1351 that.tm.last_hidden = new Date().getTime();
1354 // This works, but I not sure there are no corner cases somewhere
1355 // so it is commented - if the user has memory issues he can
1356 // set Destroy on Hide for all charts
1357 // that.data = null;
1361 that.___chartIsHidden___ = true;
1364 // unhide the chart, when it is visible - called from isVisible()
1365 var unhideChart = function() {
1366 if(isHidden() === false) return;
1368 that.___chartIsHidden___ = undefined;
1369 that.updates_since_last_unhide = 0;
1371 if(that.chart_created === false) {
1372 // we need to re-initialize it, to show our background
1373 // logo in bootstrap tabs, until the chart loads
1377 that.tm.last_unhidden = new Date().getTime();
1378 that.element_chart.style.display = '';
1379 if(that.element_legend !== null) that.element_legend.style.display = '';
1385 var canBeRendered = function() {
1386 if(isHidden() === true || that.isVisible(true) === false)
1392 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1393 var callChartLibraryUpdateSafely = function(data) {
1396 if(canBeRendered() === false)
1399 if(NETDATA.options.debug.chart_errors === true)
1400 status = that.library.update(that, data);
1403 status = that.library.update(that, data);
1410 if(status === false) {
1411 error('chart failed to be updated as ' + that.library_name);
1418 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1419 var callChartLibraryCreateSafely = function(data) {
1422 if(canBeRendered() === false)
1425 if(NETDATA.options.debug.chart_errors === true)
1426 status = that.library.create(that, data);
1429 status = that.library.create(that, data);
1436 if(status === false) {
1437 error('chart failed to be created as ' + that.library_name);
1441 that.chart_created = true;
1442 that.updates_since_last_creation = 0;
1446 // ----------------------------------------------------------------------------------------------------------------
1449 // resizeChart() - private
1450 // to be called just before the chart library to make sure that
1451 // a properly sized dom is available
1452 var resizeChart = function() {
1453 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1454 if(that.chart_created === false) return;
1456 if(that.needsRecreation()) {
1459 else if(typeof that.library.resize === 'function') {
1460 that.library.resize(that);
1462 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1463 $(that.element_legend_childs.nano).nanoScroller();
1465 maxMessageFontSize();
1468 that.tm.last_resized = new Date().getTime();
1472 // this is the actual chart resize algorithm
1474 // - resize the entire container
1475 // - update the internal states
1476 // - resize the chart as the div changes height
1477 // - update the scrollbar of the legend
1478 var resizeChartToHeight = function(h) {
1480 that.element.style.height = h;
1482 if(that.settings_id !== null)
1483 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1485 var now = new Date().getTime();
1486 NETDATA.options.last_page_scroll = now;
1487 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1490 that.tm.last_resized = 0;
1494 this.resizeHandler = function(e) {
1497 if(typeof this.event_resize === 'undefined'
1498 || this.event_resize.chart_original_w === 'undefined'
1499 || this.event_resize.chart_original_h === 'undefined')
1500 this.event_resize = {
1501 chart_original_w: this.element.clientWidth,
1502 chart_original_h: this.element.clientHeight,
1506 if(e.type === 'touchstart') {
1507 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1508 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1511 this.event_resize.mouse_start_x = e.clientX;
1512 this.event_resize.mouse_start_y = e.clientY;
1515 this.event_resize.chart_start_w = this.element.clientWidth;
1516 this.event_resize.chart_start_h = this.element.clientHeight;
1517 this.event_resize.chart_last_w = this.element.clientWidth;
1518 this.event_resize.chart_last_h = this.element.clientHeight;
1520 var now = new Date().getTime();
1521 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1522 // double click / double tap event
1524 // the optimal height of the chart
1525 // showing the entire legend
1526 var optimal = this.event_resize.chart_last_h
1527 + this.element_legend_childs.content.scrollHeight
1528 - this.element_legend_childs.content.clientHeight;
1530 // if we are not optimal, be optimal
1531 if(this.event_resize.chart_last_h != optimal)
1532 resizeChartToHeight(optimal.toString() + 'px');
1534 // else if we do not have the original height
1535 // reset to the original height
1536 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1537 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1540 this.event_resize.last = now;
1542 // process movement event
1543 document.onmousemove =
1544 document.ontouchmove =
1545 this.element_legend_childs.resize_handler.onmousemove =
1546 this.element_legend_childs.resize_handler.ontouchmove =
1551 case 'mousemove': y = e.clientY; break;
1552 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1556 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1558 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1559 resizeChartToHeight(newH.toString() + 'px');
1560 that.event_resize.chart_last_h = newH;
1565 // process end event
1566 document.onmouseup =
1567 document.ontouchend =
1568 this.element_legend_childs.resize_handler.onmouseup =
1569 this.element_legend_childs.resize_handler.ontouchend =
1571 // remove all the hooks
1572 document.onmouseup =
1573 document.onmousemove =
1574 document.ontouchmove =
1575 document.ontouchend =
1576 that.element_legend_childs.resize_handler.onmousemove =
1577 that.element_legend_childs.resize_handler.ontouchmove =
1578 that.element_legend_childs.resize_handler.onmouseout =
1579 that.element_legend_childs.resize_handler.onmouseup =
1580 that.element_legend_childs.resize_handler.ontouchend =
1583 // allow auto-refreshes
1584 NETDATA.options.auto_refresher_stop_until = 0;
1590 var noDataToShow = function() {
1591 showMessageIcon('<i class="fa fa-warning"></i> empty');
1592 that.legendUpdateDOM();
1593 that.tm.last_autorefreshed = new Date().getTime();
1594 // that.data_update_every = 30 * 1000;
1595 //that.element_chart.style.display = 'none';
1596 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1597 //that.___chartIsHidden___ = true;
1600 // ============================================================================================================
1603 this.error = function(msg) {
1607 this.setMode = function(m) {
1608 if(this.current !== null && this.current.name === m) return;
1611 this.current = this.auto;
1612 else if(m === 'pan')
1613 this.current = this.pan;
1614 else if(m === 'zoom')
1615 this.current = this.zoom;
1617 this.current = this.auto;
1619 this.current.force_update_at = 0;
1620 this.current.force_before_ms = null;
1621 this.current.force_after_ms = null;
1623 this.tm.last_mode_switch = new Date().getTime();
1626 // ----------------------------------------------------------------------------------------------------------------
1627 // global selection sync
1629 // prevent to global selection sync for some time
1630 this.globalSelectionSyncDelay = function(ms) {
1631 if(NETDATA.options.current.sync_selection === false)
1634 if(typeof ms === 'number')
1635 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1637 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1640 // can we globally apply selection sync?
1641 this.globalSelectionSyncAbility = function() {
1642 if(NETDATA.options.current.sync_selection === false)
1645 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1651 this.globalSelectionSyncIsMaster = function() {
1652 if(NETDATA.globalSelectionSync.state === this)
1658 // this chart is the master of the global selection sync
1659 this.globalSelectionSyncBeMaster = function() {
1661 if(this.globalSelectionSyncIsMaster()) {
1662 if(this.debug === true)
1663 this.log('sync: I am the master already.');
1668 if(NETDATA.globalSelectionSync.state) {
1669 if(this.debug === true)
1670 this.log('sync: I am not the sync master. Resetting global sync.');
1672 this.globalSelectionSyncStop();
1675 // become the master
1676 if(this.debug === true)
1677 this.log('sync: becoming sync master.');
1679 this.selected = true;
1680 NETDATA.globalSelectionSync.state = this;
1682 // find the all slaves
1683 var targets = NETDATA.options.targets;
1684 var len = targets.length;
1689 if(this.debug === true)
1690 st.log('sync: not adding me to sync');
1692 else if(st.globalSelectionSyncIsEligible()) {
1693 if(this.debug === true)
1694 st.log('sync: adding to sync as slave');
1696 st.globalSelectionSyncBeSlave();
1700 // this.globalSelectionSyncDelay(100);
1703 // can the chart participate to the global selection sync as a slave?
1704 this.globalSelectionSyncIsEligible = function() {
1705 if(this.enabled === true
1706 && this.library !== null
1707 && typeof this.library.setSelection === 'function'
1708 && this.isVisible() === true
1709 && this.chart_created === true)
1715 // this chart becomes a slave of the global selection sync
1716 this.globalSelectionSyncBeSlave = function() {
1717 if(NETDATA.globalSelectionSync.state !== this)
1718 NETDATA.globalSelectionSync.slaves.push(this);
1721 // sync all the visible charts to the given time
1722 // this is to be called from the chart libraries
1723 this.globalSelectionSync = function(t) {
1724 if(this.globalSelectionSyncAbility() === false) {
1725 if(this.debug === true)
1726 this.log('sync: cannot sync (yet?).');
1731 if(this.globalSelectionSyncIsMaster() === false) {
1732 if(this.debug === true)
1733 this.log('sync: trying to be sync master.');
1735 this.globalSelectionSyncBeMaster();
1737 if(this.globalSelectionSyncAbility() === false) {
1738 if(this.debug === true)
1739 this.log('sync: cannot sync (yet?).');
1745 NETDATA.globalSelectionSync.last_t = t;
1746 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1751 // stop syncing all charts to the given time
1752 this.globalSelectionSyncStop = function() {
1753 if(NETDATA.globalSelectionSync.slaves.length) {
1754 if(this.debug === true)
1755 this.log('sync: cleaning up...');
1757 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1759 if(that.debug === true)
1760 st.log('sync: not adding me to sync stop');
1763 if(that.debug === true)
1764 st.log('sync: removed slave from sync');
1766 st.clearSelection();
1770 NETDATA.globalSelectionSync.last_t = 0;
1771 NETDATA.globalSelectionSync.slaves = [];
1772 NETDATA.globalSelectionSync.state = null;
1775 this.clearSelection();
1778 this.setSelection = function(t) {
1779 if(typeof this.library.setSelection === 'function') {
1780 if(this.library.setSelection(this, t) === true)
1781 this.selected = true;
1783 this.selected = false;
1785 else this.selected = true;
1787 if(this.selected === true && this.debug === true)
1788 this.log('selection set to ' + t.toString());
1790 return this.selected;
1793 this.clearSelection = function() {
1794 if(this.selected === true) {
1795 if(typeof this.library.clearSelection === 'function') {
1796 if(this.library.clearSelection(this) === true)
1797 this.selected = false;
1799 this.selected = true;
1801 else this.selected = false;
1803 if(this.selected === false && this.debug === true)
1804 this.log('selection cleared');
1809 return this.selected;
1812 // find if a timestamp (ms) is shown in the current chart
1813 this.timeIsVisible = function(t) {
1814 if(t >= this.data_after && t <= this.data_before)
1819 this.calculateRowForTime = function(t) {
1820 if(this.timeIsVisible(t) === false) return -1;
1821 return Math.floor((t - this.data_after) / this.data_update_every);
1824 // ----------------------------------------------------------------------------------------------------------------
1827 this.log = function(msg) {
1828 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1831 this.pauseChart = function() {
1832 if(this.paused === false) {
1833 if(this.debug === true)
1834 this.log('pauseChart()');
1840 this.unpauseChart = function() {
1841 if(this.paused === true) {
1842 if(this.debug === true)
1843 this.log('unpauseChart()');
1845 this.paused = false;
1849 this.resetChart = function(dont_clear_master, dont_update) {
1850 if(this.debug === true)
1851 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1853 if(typeof dont_clear_master === 'undefined')
1854 dont_clear_master = false;
1856 if(typeof dont_update === 'undefined')
1857 dont_update = false;
1859 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1860 if(this.debug === true)
1861 this.log('resetChart() diverting to clearMaster().');
1862 // this will call us back with master === true
1863 NETDATA.globalPanAndZoom.clearMaster();
1867 this.clearSelection();
1869 this.tm.pan_and_zoom_seq = 0;
1871 this.setMode('auto');
1872 this.current.force_update_at = 0;
1873 this.current.force_before_ms = null;
1874 this.current.force_after_ms = null;
1875 this.tm.last_autorefreshed = 0;
1876 this.paused = false;
1877 this.selected = false;
1878 this.enabled = true;
1879 // this.debug = false;
1881 // do not update the chart here
1882 // or the chart will flip-flop when it is the master
1883 // of a selection sync and another chart becomes
1886 if(dont_update !== true && this.isVisible() === true) {
1891 this.updateChartPanOrZoom = function(after, before) {
1892 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1895 if(this.debug === true)
1898 if(before < after) {
1899 if(this.debug === true)
1900 this.log(logme + 'flipped parameters, rejecting it.');
1905 if(typeof this.fixed_min_duration === 'undefined')
1906 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1908 var min_duration = this.fixed_min_duration;
1909 var current_duration = Math.round(this.view_before - this.view_after);
1911 // round the numbers
1912 after = Math.round(after);
1913 before = Math.round(before);
1915 // align them to update_every
1916 // stretching them further away
1917 after -= after % this.data_update_every;
1918 before += this.data_update_every - (before % this.data_update_every);
1920 // the final wanted duration
1921 var wanted_duration = before - after;
1923 // to allow panning, accept just a point below our minimum
1924 if((current_duration - this.data_update_every) < min_duration)
1925 min_duration = current_duration - this.data_update_every;
1927 // we do it, but we adjust to minimum size and return false
1928 // when the wanted size is below the current and the minimum
1930 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1931 if(this.debug === true)
1932 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1934 min_duration = this.fixed_min_duration;
1936 var dt = (min_duration - wanted_duration) / 2;
1939 wanted_duration = before - after;
1943 var tolerance = this.data_update_every * 2;
1944 var movement = Math.abs(before - this.view_before);
1946 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1947 if(this.debug === true)
1948 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);
1952 if(this.current.name === 'auto') {
1953 this.log(logme + 'caller called me with mode: ' + this.current.name);
1954 this.setMode('pan');
1957 if(this.debug === true)
1958 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);
1960 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1961 this.current.force_after_ms = after;
1962 this.current.force_before_ms = before;
1963 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1967 this.legendFormatValue = function(value) {
1968 if(value === null || value === 'undefined') return '-';
1969 if(typeof value !== 'number') return value;
1971 var abs = Math.abs(value);
1972 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1973 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1974 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1975 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1976 return (Math.round(value * 10000) / 10000).toLocaleString();
1979 this.legendSetLabelValue = function(label, value) {
1980 var series = this.element_legend_childs.series[label];
1981 if(typeof series === 'undefined') return;
1982 if(series.value === null && series.user === null) return;
1984 // if the value has not changed, skip DOM update
1985 //if(series.last === value) return;
1988 if(typeof value === 'number') {
1989 var v = Math.abs(value);
1990 s = r = this.legendFormatValue(value);
1992 if(typeof series.last === 'number') {
1993 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1994 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1995 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1997 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2002 series.last = value;
2005 if(series.value !== null) series.value.innerHTML = s;
2006 if(series.user !== null) series.user.innerHTML = r;
2009 this.legendSetDate = function(ms) {
2010 if(typeof ms !== 'number') {
2011 this.legendShowUndefined();
2015 var d = new Date(ms);
2017 if(this.element_legend_childs.title_date)
2018 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2020 if(this.element_legend_childs.title_time)
2021 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2023 if(this.element_legend_childs.title_units)
2024 this.element_legend_childs.title_units.innerHTML = this.units;
2027 this.legendShowUndefined = function() {
2028 if(this.element_legend_childs.title_date)
2029 this.element_legend_childs.title_date.innerHTML = ' ';
2031 if(this.element_legend_childs.title_time)
2032 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2034 if(this.element_legend_childs.title_units)
2035 this.element_legend_childs.title_units.innerHTML = ' ';
2037 if(this.data && this.element_legend_childs.series !== null) {
2038 var labels = this.data.dimension_names;
2039 var i = labels.length;
2041 var label = labels[i];
2043 if(typeof label === 'undefined') continue;
2044 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2045 this.legendSetLabelValue(label, null);
2050 this.legendShowLatestValues = function() {
2051 if(this.chart === null) return;
2052 if(this.selected) return;
2054 if(this.data === null || this.element_legend_childs.series === null) {
2055 this.legendShowUndefined();
2059 var show_undefined = true;
2060 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2061 show_undefined = false;
2063 if(show_undefined) {
2064 this.legendShowUndefined();
2068 this.legendSetDate(this.view_before);
2070 var labels = this.data.dimension_names;
2071 var i = labels.length;
2073 var label = labels[i];
2075 if(typeof label === 'undefined') continue;
2076 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2079 this.legendSetLabelValue(label, null);
2081 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2085 this.legendReset = function() {
2086 this.legendShowLatestValues();
2089 // this should be called just ONCE per dimension per chart
2090 this._chartDimensionColor = function(label) {
2091 if(this.colors === null) this.chartColors();
2093 if(typeof this.colors_assigned[label] === 'undefined') {
2094 if(this.colors_available.length === 0) {
2095 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2096 this.colors_available.push(NETDATA.themes.current.colors[i]);
2099 this.colors_assigned[label] = this.colors_available.shift();
2101 if(this.debug === true)
2102 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2105 if(this.debug === true)
2106 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2109 this.colors.push(this.colors_assigned[label]);
2110 return this.colors_assigned[label];
2113 this.chartColors = function() {
2114 if(this.colors !== null) return this.colors;
2116 this.colors = new Array();
2117 this.colors_available = new Array();
2120 var c = $(this.element).data('colors');
2121 // this.log('read colors: ' + c);
2122 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2123 if(typeof c !== 'string') {
2124 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2131 for(i = 0, len = c.length; i < len ; i++) {
2133 this.colors_available.push(c[i]);
2134 // this.log('adding color: ' + c[i]);
2140 // push all the standard colors too
2141 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2142 this.colors_available.push(NETDATA.themes.current.colors[i]);
2147 this.legendUpdateDOM = function() {
2150 // check that the legend DOM is up to date for the downloaded dimensions
2151 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2152 // this.log('the legend does not have any series - requesting legend update');
2155 else if(this.data === null) {
2156 // this.log('the chart does not have any data - requesting legend update');
2159 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2163 var labels = this.data.dimension_names.toString();
2164 if(labels !== this.element_legend_childs.series.labels_key) {
2167 if(this.debug === true)
2168 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2172 if(needed === false) {
2173 // make sure colors available
2176 // do we have to update the current values?
2177 // we do this, only when the visible chart is current
2178 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2179 if(this.debug === true)
2180 this.log('chart is in latest position... updating values on legend...');
2182 //var labels = this.data.dimension_names;
2183 //var i = labels.length;
2185 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2189 if(this.colors === null) {
2190 // this is the first time we update the chart
2191 // let's assign colors to all dimensions
2192 if(this.library.track_colors() === true)
2193 for(var dim in this.chart.dimensions)
2194 this._chartDimensionColor(this.chart.dimensions[dim].name);
2196 // we will re-generate the colors for the chart
2197 // based on the selected dimensions
2200 if(this.debug === true)
2201 this.log('updating Legend DOM');
2203 // mark all dimensions as invalid
2204 this.dimensions_visibility.invalidateAll();
2206 var genLabel = function(state, parent, dim, name, count) {
2207 var color = state._chartDimensionColor(name);
2209 var user_element = null;
2210 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2211 if(user_id !== null) {
2212 user_element = document.getElementById(user_id) || null;
2213 if(user_element === null)
2214 state.log('Cannot find element with id: ' + user_id);
2217 state.element_legend_childs.series[name] = {
2218 name: document.createElement('span'),
2219 value: document.createElement('span'),
2224 var label = state.element_legend_childs.series[name];
2226 // create the dimension visibility tracking for this label
2227 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2229 var rgb = NETDATA.colorHex2Rgb(color);
2230 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2231 + state.chart.chart_type
2232 + '" style="background-color: '
2233 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2234 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2236 var text = document.createTextNode(' ' + name);
2237 label.name.appendChild(text);
2240 parent.appendChild(document.createElement('br'));
2242 parent.appendChild(label.name);
2243 parent.appendChild(label.value);
2246 var content = document.createElement('div');
2248 if(this.hasLegend()) {
2249 this.element_legend_childs = {
2251 resize_handler: document.createElement('div'),
2252 toolbox: document.createElement('div'),
2253 toolbox_left: document.createElement('div'),
2254 toolbox_right: document.createElement('div'),
2255 toolbox_reset: document.createElement('div'),
2256 toolbox_zoomin: document.createElement('div'),
2257 toolbox_zoomout: document.createElement('div'),
2258 toolbox_volume: document.createElement('div'),
2259 title_date: document.createElement('span'),
2260 title_time: document.createElement('span'),
2261 title_units: document.createElement('span'),
2262 nano: document.createElement('div'),
2264 paneClass: 'netdata-legend-series-pane',
2265 sliderClass: 'netdata-legend-series-slider',
2266 contentClass: 'netdata-legend-series-content',
2267 enabledClass: '__enabled',
2268 flashedClass: '__flashed',
2269 activeClass: '__active',
2271 alwaysVisible: true,
2277 this.element_legend.innerHTML = '';
2279 if(this.library.toolboxPanAndZoom !== null) {
2281 function get_pan_and_zoom_step(event) {
2283 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2285 else if (event.shiftKey)
2286 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2288 else if (event.altKey)
2289 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2292 return NETDATA.options.current.pan_and_zoom_factor;
2295 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2296 this.element.appendChild(this.element_legend_childs.toolbox);
2298 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2299 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2300 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2301 this.element_legend_childs.toolbox_left.onclick = function(e) {
2304 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2305 var before = that.view_before - step;
2306 var after = that.view_after - step;
2307 if(after >= that.netdata_first)
2308 that.library.toolboxPanAndZoom(that, after, before);
2310 if(NETDATA.options.current.show_help === true)
2311 $(this.element_legend_childs.toolbox_left).popover({
2316 placement: 'bottom',
2317 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2319 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>'
2323 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2324 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2325 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2326 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2328 NETDATA.resetAllCharts(that);
2330 if(NETDATA.options.current.show_help === true)
2331 $(this.element_legend_childs.toolbox_reset).popover({
2336 placement: 'bottom',
2337 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2338 title: 'Chart Reset',
2339 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>'
2342 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2343 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2344 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2345 this.element_legend_childs.toolbox_right.onclick = function(e) {
2347 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2348 var before = that.view_before + step;
2349 var after = that.view_after + step;
2350 if(before <= that.netdata_last)
2351 that.library.toolboxPanAndZoom(that, after, before);
2353 if(NETDATA.options.current.show_help === true)
2354 $(this.element_legend_childs.toolbox_right).popover({
2359 placement: 'bottom',
2360 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2362 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>'
2366 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2367 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2368 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2369 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2371 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2372 var before = that.view_before - dt;
2373 var after = that.view_after + dt;
2374 that.library.toolboxPanAndZoom(that, after, before);
2376 if(NETDATA.options.current.show_help === true)
2377 $(this.element_legend_childs.toolbox_zoomin).popover({
2382 placement: 'bottom',
2383 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2384 title: 'Chart Zoom In',
2385 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>'
2388 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2389 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2390 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2391 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2393 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);
2394 var before = that.view_before + dt;
2395 var after = that.view_after - dt;
2397 that.library.toolboxPanAndZoom(that, after, before);
2399 if(NETDATA.options.current.show_help === true)
2400 $(this.element_legend_childs.toolbox_zoomout).popover({
2405 placement: 'bottom',
2406 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2407 title: 'Chart Zoom Out',
2408 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>'
2411 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2412 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2413 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2414 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2415 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2416 //e.preventDefault();
2417 //alert('clicked toolbox_volume on ' + that.id);
2421 this.element_legend_childs.toolbox = null;
2422 this.element_legend_childs.toolbox_left = null;
2423 this.element_legend_childs.toolbox_reset = null;
2424 this.element_legend_childs.toolbox_right = null;
2425 this.element_legend_childs.toolbox_zoomin = null;
2426 this.element_legend_childs.toolbox_zoomout = null;
2427 this.element_legend_childs.toolbox_volume = null;
2430 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2431 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2432 this.element.appendChild(this.element_legend_childs.resize_handler);
2433 if(NETDATA.options.current.show_help === true)
2434 $(this.element_legend_childs.resize_handler).popover({
2439 placement: 'bottom',
2440 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2441 title: 'Chart Resize',
2442 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>'
2446 this.element_legend_childs.resize_handler.onmousedown =
2448 that.resizeHandler(e);
2452 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2453 that.resizeHandler(e);
2456 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2457 this.element_legend.appendChild(this.element_legend_childs.title_date);
2459 this.element_legend.appendChild(document.createElement('br'));
2461 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2462 this.element_legend.appendChild(this.element_legend_childs.title_time);
2464 this.element_legend.appendChild(document.createElement('br'));
2466 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2467 this.element_legend.appendChild(this.element_legend_childs.title_units);
2469 this.element_legend.appendChild(document.createElement('br'));
2471 this.element_legend_childs.nano.className = 'netdata-legend-series';
2472 this.element_legend.appendChild(this.element_legend_childs.nano);
2474 content.className = 'netdata-legend-series-content';
2475 this.element_legend_childs.nano.appendChild(content);
2477 if(NETDATA.options.current.show_help === true)
2478 $(content).popover({
2483 placement: 'bottom',
2484 title: 'Chart Legend',
2485 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2486 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2490 this.element_legend_childs = {
2492 resize_handler: null,
2495 toolbox_right: null,
2496 toolbox_reset: null,
2497 toolbox_zoomin: null,
2498 toolbox_zoomout: null,
2499 toolbox_volume: null,
2510 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2511 if(this.debug === true)
2512 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2514 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2515 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2519 var tmp = new Array();
2520 for(var dim in this.chart.dimensions) {
2521 tmp.push(this.chart.dimensions[dim].name);
2522 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2524 this.element_legend_childs.series.labels_key = tmp.toString();
2525 if(this.debug === true)
2526 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2529 // create a hidden div to be used for hidding
2530 // the original legend of the chart library
2531 var el = document.createElement('div');
2532 if(this.element_legend !== null)
2533 this.element_legend.appendChild(el);
2534 el.style.display = 'none';
2536 this.element_legend_childs.hidden = document.createElement('div');
2537 el.appendChild(this.element_legend_childs.hidden);
2539 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2540 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2542 this.legendShowLatestValues();
2545 this.hasLegend = function() {
2546 if(typeof this.___hasLegendCache___ !== 'undefined')
2547 return this.___hasLegendCache___;
2550 if(this.library && this.library.legend(this) === 'right-side') {
2551 var legend = $(this.element).data('legend') || 'yes';
2552 if(legend === 'yes') leg = true;
2555 this.___hasLegendCache___ = leg;
2559 this.legendWidth = function() {
2560 return (this.hasLegend())?140:0;
2563 this.legendHeight = function() {
2564 return $(this.element).height();
2567 this.chartWidth = function() {
2568 return $(this.element).width() - this.legendWidth();
2571 this.chartHeight = function() {
2572 return $(this.element).height();
2575 this.chartPixelsPerPoint = function() {
2576 // force an options provided detail
2577 var px = this.pixels_per_point;
2579 if(this.library && px < this.library.pixels_per_point(this))
2580 px = this.library.pixels_per_point(this);
2582 if(px < NETDATA.options.current.pixels_per_point)
2583 px = NETDATA.options.current.pixels_per_point;
2588 this.needsRecreation = function() {
2590 this.chart_created === true
2592 && this.library.autoresize() === false
2593 && this.tm.last_resized < NETDATA.options.last_resized
2597 this.chartURL = function() {
2598 var after, before, points_multiplier = 1;
2599 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2600 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2602 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2603 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2604 this.view_after = after * 1000;
2605 this.view_before = before * 1000;
2607 this.requested_padding = null;
2608 points_multiplier = 1;
2610 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2611 this.tm.pan_and_zoom_seq = 0;
2613 before = Math.round(this.current.force_before_ms / 1000);
2614 after = Math.round(this.current.force_after_ms / 1000);
2615 this.view_after = after * 1000;
2616 this.view_before = before * 1000;
2618 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2619 this.requested_padding = Math.round((before - after) / 2);
2620 after -= this.requested_padding;
2621 before += this.requested_padding;
2622 this.requested_padding *= 1000;
2623 points_multiplier = 2;
2626 this.current.force_before_ms = null;
2627 this.current.force_after_ms = null;
2630 this.tm.pan_and_zoom_seq = 0;
2632 before = this.before;
2634 this.view_after = after * 1000;
2635 this.view_before = before * 1000;
2637 this.requested_padding = null;
2638 points_multiplier = 1;
2641 this.requested_after = after * 1000;
2642 this.requested_before = before * 1000;
2644 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2646 // build the data URL
2647 this.data_url = this.host + this.chart.data_url;
2648 this.data_url += "&format=" + this.library.format();
2649 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2650 this.data_url += "&group=" + this.method;
2651 this.data_url += "&options=" + this.library.options(this);
2652 this.data_url += '|jsonwrap';
2654 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2655 this.data_url += '|nonzero';
2657 if(this.append_options !== null)
2658 this.data_url += '|' + this.append_options.toString();
2661 this.data_url += "&after=" + after.toString();
2664 this.data_url += "&before=" + before.toString();
2667 this.data_url += "&dimensions=" + this.dimensions;
2669 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2670 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2673 this.redrawChart = function() {
2674 if(this.data !== null)
2675 this.updateChartWithData(this.data);
2678 this.updateChartWithData = function(data) {
2679 if(this.debug === true)
2680 this.log('updateChartWithData() called.');
2682 // this may force the chart to be re-created
2686 this.updates_counter++;
2687 this.updates_since_last_unhide++;
2688 this.updates_since_last_creation++;
2690 var started = new Date().getTime();
2692 // if the result is JSON, find the latest update-every
2693 this.data_update_every = data.view_update_every * 1000;
2694 this.data_after = data.after * 1000;
2695 this.data_before = data.before * 1000;
2696 this.netdata_first = data.first_entry * 1000;
2697 this.netdata_last = data.last_entry * 1000;
2698 this.data_points = data.points;
2701 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2702 if(this.view_after < this.data_after) {
2703 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2704 this.view_after = this.data_after;
2707 if(this.view_before > this.data_before) {
2708 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2709 this.view_before = this.data_before;
2713 this.view_after = this.data_after;
2714 this.view_before = this.data_before;
2717 if(this.debug === true) {
2718 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2720 if(this.current.force_after_ms)
2721 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2723 this.log('STATUS: forced : unset');
2725 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2726 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2727 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2728 this.log('STATUS: points : ' + (this.data_points).toString());
2731 if(this.data_points === 0) {
2736 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2737 if(this.debug === true)
2738 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2740 this.chart_created = false;
2743 // check and update the legend
2744 this.legendUpdateDOM();
2746 if(this.chart_created === true
2747 && typeof this.library.update === 'function') {
2749 if(this.debug === true)
2750 this.log('updating chart...');
2752 if(callChartLibraryUpdateSafely(data) === false)
2756 if(this.debug === true)
2757 this.log('creating chart...');
2759 if(callChartLibraryCreateSafely(data) === false)
2763 this.legendShowLatestValues();
2764 if(this.selected === true)
2765 NETDATA.globalSelectionSync.stop();
2767 // update the performance counters
2768 var now = new Date().getTime();
2769 this.tm.last_updated = now;
2771 // don't update last_autorefreshed if this chart is
2772 // forced to be updated with global PanAndZoom
2773 if(NETDATA.globalPanAndZoom.isActive())
2774 this.tm.last_autorefreshed = 0;
2776 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2777 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2779 this.tm.last_autorefreshed = now;
2782 this.refresh_dt_ms = now - started;
2783 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2785 if(this.refresh_dt_element !== null)
2786 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2789 this.updateChart = function(callback) {
2790 if(this.debug === true)
2791 this.log('updateChart() called.');
2793 if(this._updating === true) {
2794 if(this.debug === true)
2795 this.log('I am already updating...');
2797 if(typeof callback === 'function') callback();
2801 // due to late initialization of charts and libraries
2802 // we need to check this too
2803 if(this.enabled === false) {
2804 if(this.debug === true)
2805 this.log('I am not enabled');
2807 if(typeof callback === 'function') callback();
2811 if(canBeRendered() === false) {
2812 if(typeof callback === 'function') callback();
2816 if(this.chart === null) {
2817 this.getChart(function() { that.updateChart(callback); });
2821 if(this.library.initialized === false) {
2822 if(this.library.enabled === true) {
2823 this.library.initialize(function() { that.updateChart(callback); });
2827 error('chart library "' + this.library_name + '" is not available.');
2828 if(typeof callback === 'function') callback();
2833 this.clearSelection();
2836 if(this.debug === true)
2837 this.log('updating from ' + this.data_url);
2839 NETDATA.statistics.refreshes_total++;
2840 NETDATA.statistics.refreshes_active++;
2842 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2843 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2845 this._updating = true;
2847 this.xhr = $.ajax( {
2851 xhrFields: { withCredentials: true } // required for the cookie
2853 .done(function(data) {
2854 that.xhr = undefined;
2856 if(that.debug === true)
2857 that.log('data received. updating chart.');
2859 that.updateChartWithData(data);
2861 .fail(function(msg) {
2862 that.xhr = undefined;
2864 if(msg.statusText !== 'abort')
2865 error('data download failed for url: ' + that.data_url);
2867 .always(function() {
2868 that.xhr = undefined;
2870 NETDATA.statistics.refreshes_active--;
2871 that._updating = false;
2872 if(typeof callback === 'function') callback();
2878 this.isVisible = function(nocache) {
2879 if(typeof nocache === 'undefined')
2882 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2884 // caching - we do not evaluate the charts visibility
2885 // if the page has not been scrolled since the last check
2886 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2887 return this.___isVisible___;
2889 this.tm.last_visible_check = new Date().getTime();
2891 var wh = window.innerHeight;
2892 var x = this.element.getBoundingClientRect();
2896 if(x.width === 0 || x.height === 0) {
2898 this.___isVisible___ = false;
2899 return this.___isVisible___;
2902 if(x.top < 0 && -x.top > x.height) {
2903 // the chart is entirely above
2904 ret = -x.top - x.height;
2906 else if(x.top > wh) {
2907 // the chart is entirely below
2911 if(ret > tolerance) {
2912 // the chart is too far
2915 this.___isVisible___ = false;
2916 return this.___isVisible___;
2919 // the chart is inside or very close
2922 this.___isVisible___ = true;
2923 return this.___isVisible___;
2927 this.isAutoRefreshable = function() {
2928 return (this.current.autorefresh);
2931 this.canBeAutoRefreshed = function() {
2932 var now = new Date().getTime();
2934 if(this.running === true) {
2935 if(this.debug === true)
2936 this.log('I am already running');
2941 if(this.enabled === false) {
2942 if(this.debug === true)
2943 this.log('I am not enabled');
2948 if(this.library === null || this.library.enabled === false) {
2949 error('charting library "' + this.library_name + '" is not available');
2950 if(this.debug === true)
2951 this.log('My chart library ' + this.library_name + ' is not available');
2956 if(this.isVisible() === false) {
2957 if(NETDATA.options.debug.visibility === true || this.debug === true)
2958 this.log('I am not visible');
2963 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2964 if(this.debug === true)
2965 this.log('timed force update detected - allowing this update');
2967 this.current.force_update_at = 0;
2971 if(this.isAutoRefreshable() === true) {
2972 // allow the first update, even if the page is not visible
2973 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2974 if(NETDATA.options.debug.focus === true || this.debug === true)
2975 this.log('canBeAutoRefreshed(): page does not have focus');
2980 if(this.needsRecreation() === true) {
2981 if(this.debug === true)
2982 this.log('canBeAutoRefreshed(): needs re-creation.');
2987 // options valid only for autoRefresh()
2988 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2989 if(NETDATA.globalPanAndZoom.isActive()) {
2990 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2991 if(this.debug === true)
2992 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2997 if(this.debug === true)
2998 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3004 if(this.selected === true) {
3005 if(this.debug === true)
3006 this.log('canBeAutoRefreshed(): I have a selection in place.');
3011 if(this.paused === true) {
3012 if(this.debug === true)
3013 this.log('canBeAutoRefreshed(): I am paused.');
3018 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3019 if(this.debug === true)
3020 this.log('canBeAutoRefreshed(): It is time to update me.');
3030 this.autoRefresh = function(callback) {
3031 if(this.canBeAutoRefreshed() === true && this.running === false) {
3034 state.running = true;
3035 state.updateChart(function() {
3036 state.running = false;
3038 if(typeof callback !== 'undefined')
3043 if(typeof callback !== 'undefined')
3048 this._defaultsFromDownloadedChart = function(chart) {
3050 this.chart_url = chart.url;
3051 this.data_update_every = chart.update_every * 1000;
3052 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3053 this.tm.last_info_downloaded = new Date().getTime();
3055 if(this.title === null)
3056 this.title = chart.title;
3058 if(this.units === null)
3059 this.units = chart.units;
3062 // fetch the chart description from the netdata server
3063 this.getChart = function(callback) {
3064 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3066 this._defaultsFromDownloadedChart(this.chart);
3067 if(typeof callback === 'function') callback();
3070 this.chart_url = "/api/v1/chart?chart=" + this.id;
3072 if(this.debug === true)
3073 this.log('downloading ' + this.chart_url);
3076 url: this.host + this.chart_url,
3079 xhrFields: { withCredentials: true } // required for the cookie
3081 .done(function(chart) {
3082 chart.url = that.chart_url;
3083 that._defaultsFromDownloadedChart(chart);
3084 NETDATA.chartRegistry.add(that.host, that.id, chart);
3087 NETDATA.error(404, that.chart_url);
3088 error('chart not found on url "' + that.chart_url + '"');
3090 .always(function() {
3091 if(typeof callback === 'function') callback();
3096 // ============================================================================================================
3102 NETDATA.resetAllCharts = function(state) {
3103 // first clear the global selection sync
3104 // to make sure no chart is in selected state
3105 state.globalSelectionSyncStop();
3107 // there are 2 possibilities here
3108 // a. state is the global Pan and Zoom master
3109 // b. state is not the global Pan and Zoom master
3111 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3114 // clear the global Pan and Zoom
3115 // this will also refresh the master
3116 // and unblock any charts currently mirroring the master
3117 NETDATA.globalPanAndZoom.clearMaster();
3119 // if we were not the master, reset our status too
3120 // this is required because most probably the mouse
3121 // is over this chart, blocking it from auto-refreshing
3122 if(master === false && (state.paused === true || state.selected === true))
3126 // get or create a chart state, given a DOM element
3127 NETDATA.chartState = function(element) {
3128 var state = $(element).data('netdata-state-object') || null;
3129 if(state === null) {
3130 state = new chartState(element);
3131 $(element).data('netdata-state-object', state);
3136 // ----------------------------------------------------------------------------------------------------------------
3137 // Library functions
3139 // Load a script without jquery
3140 // This is used to load jquery - after it is loaded, we use jquery
3141 NETDATA._loadjQuery = function(callback) {
3142 if(typeof jQuery === 'undefined') {
3143 if(NETDATA.options.debug.main_loop === true)
3144 console.log('loading ' + NETDATA.jQuery);
3146 var script = document.createElement('script');
3147 script.type = 'text/javascript';
3148 script.async = true;
3149 script.src = NETDATA.jQuery;
3151 // script.onabort = onError;
3152 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3153 if(typeof callback === "function")
3154 script.onload = callback;
3156 var s = document.getElementsByTagName('script')[0];
3157 s.parentNode.insertBefore(script, s);
3159 else if(typeof callback === "function")
3163 NETDATA._loadCSS = function(filename) {
3164 // don't use jQuery here
3165 // styles are loaded before jQuery
3166 // to eliminate showing an unstyled page to the user
3168 var fileref = document.createElement("link");
3169 fileref.setAttribute("rel", "stylesheet");
3170 fileref.setAttribute("type", "text/css");
3171 fileref.setAttribute("href", filename);
3173 if (typeof fileref !== 'undefined')
3174 document.getElementsByTagName("head")[0].appendChild(fileref);
3177 NETDATA.colorHex2Rgb = function(hex) {
3178 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3179 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3180 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3181 return r + r + g + g + b + b;
3184 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3186 r: parseInt(result[1], 16),
3187 g: parseInt(result[2], 16),
3188 b: parseInt(result[3], 16)
3192 NETDATA.colorLuminance = function(hex, lum) {
3193 // validate hex string
3194 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3196 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3200 // convert to decimal and change luminosity
3201 var rgb = "#", c, i;
3202 for (i = 0; i < 3; i++) {
3203 c = parseInt(hex.substr(i*2,2), 16);
3204 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3205 rgb += ("00"+c).substr(c.length);
3211 NETDATA.guid = function() {
3213 return Math.floor((1 + Math.random()) * 0x10000)
3218 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3221 NETDATA.zeropad = function(x) {
3222 if(x > -10 && x < 10) return '0' + x.toString();
3223 else return x.toString();
3226 // user function to signal us the DOM has been
3228 NETDATA.updatedDom = function() {
3229 NETDATA.options.updated_dom = true;
3232 NETDATA.ready = function(callback) {
3233 NETDATA.options.pauseCallback = callback;
3236 NETDATA.pause = function(callback) {
3237 if(NETDATA.options.pause === true)
3240 NETDATA.options.pauseCallback = callback;
3243 NETDATA.unpause = function() {
3244 NETDATA.options.pauseCallback = null;
3245 NETDATA.options.updated_dom = true;
3246 NETDATA.options.pause = false;
3249 // ----------------------------------------------------------------------------------------------------------------
3251 // this is purely sequencial charts refresher
3252 // it is meant to be autonomous
3253 NETDATA.chartRefresherNoParallel = function(index) {
3254 if(NETDATA.options.debug.mail_loop === true)
3255 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3257 if(NETDATA.options.updated_dom === true) {
3258 // the dom has been updated
3259 // get the dom parts again
3260 NETDATA.parseDom(NETDATA.chartRefresher);
3263 if(index >= NETDATA.options.targets.length) {
3264 if(NETDATA.options.debug.main_loop === true)
3265 console.log('waiting to restart main loop...');
3267 NETDATA.options.auto_refresher_fast_weight = 0;
3269 setTimeout(function() {
3270 NETDATA.chartRefresher();
3271 }, NETDATA.options.current.idle_between_loops);
3274 var state = NETDATA.options.targets[index];
3276 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3277 if(NETDATA.options.debug.main_loop === true)
3278 console.log('fast rendering...');
3280 state.autoRefresh(function() {
3281 NETDATA.chartRefresherNoParallel(++index);
3285 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3286 NETDATA.options.auto_refresher_fast_weight = 0;
3288 setTimeout(function() {
3289 state.autoRefresh(function() {
3290 NETDATA.chartRefresherNoParallel(++index);
3292 }, NETDATA.options.current.idle_between_charts);
3297 // this is part of the parallel refresher
3298 // its cause is to refresh sequencially all the charts
3299 // that depend on chart library initialization
3300 // it will call the parallel refresher back
3301 // as soon as it sees a chart that its chart library
3303 NETDATA.chartRefresher_uninitialized = function() {
3304 if(NETDATA.options.updated_dom === true) {
3305 // the dom has been updated
3306 // get the dom parts again
3307 NETDATA.parseDom(NETDATA.chartRefresher);
3311 if(NETDATA.options.sequencial.length === 0)
3312 NETDATA.chartRefresher();
3314 var state = NETDATA.options.sequencial.pop();
3315 if(state.library.initialized === true)
3316 NETDATA.chartRefresher();
3318 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3322 NETDATA.chartRefresherWaitTime = function() {
3323 return NETDATA.options.current.idle_parallel_loops;
3326 // the default refresher
3327 // it will create 2 sets of charts:
3328 // - the ones that can be refreshed in parallel
3329 // - the ones that depend on something else
3330 // the first set will be executed in parallel
3331 // the second will be given to NETDATA.chartRefresher_uninitialized()
3332 NETDATA.chartRefresher = function() {
3333 // console.log('auto-refresher...');
3335 if(NETDATA.options.pause === true) {
3336 // console.log('auto-refresher is paused');
3337 setTimeout(NETDATA.chartRefresher,
3338 NETDATA.chartRefresherWaitTime());
3342 if(typeof NETDATA.options.pauseCallback === 'function') {
3343 // console.log('auto-refresher is calling pauseCallback');
3344 NETDATA.options.pause = true;
3345 NETDATA.options.pauseCallback();
3346 NETDATA.chartRefresher();
3350 if(NETDATA.options.current.parallel_refresher === false) {
3351 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3352 NETDATA.chartRefresherNoParallel(0);
3356 if(NETDATA.options.updated_dom === true) {
3357 // the dom has been updated
3358 // get the dom parts again
3359 // console.log('auto-refresher is calling parseDom()');
3360 NETDATA.parseDom(NETDATA.chartRefresher);
3364 var parallel = new Array();
3365 var targets = NETDATA.options.targets;
3366 var len = targets.length;
3369 state = targets[len];
3370 if(state.isVisible() === false || state.running === true)
3373 if(state.library.initialized === false) {
3374 if(state.library.enabled === true) {
3375 state.library.initialize(NETDATA.chartRefresher);
3379 state.error('chart library "' + state.library_name + '" is not enabled.');
3383 parallel.unshift(state);
3386 if(parallel.length > 0) {
3387 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3388 // this will execute the jobs in parallel
3389 $(parallel).each(function() {
3394 // console.log('auto-refresher nothing to do');
3397 // run the next refresh iteration
3398 setTimeout(NETDATA.chartRefresher,
3399 NETDATA.chartRefresherWaitTime());
3402 NETDATA.parseDom = function(callback) {
3403 NETDATA.options.last_page_scroll = new Date().getTime();
3404 NETDATA.options.updated_dom = false;
3406 var targets = $('div[data-netdata]'); //.filter(':visible');
3408 if(NETDATA.options.debug.main_loop === true)
3409 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3411 NETDATA.options.targets = new Array();
3412 var len = targets.length;
3414 // the initialization will take care of sizing
3415 // and the "loading..." message
3416 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3419 if(typeof callback === 'function') callback();
3422 // this is the main function - where everything starts
3423 NETDATA.start = function() {
3424 // this should be called only once
3426 NETDATA.options.page_is_visible = true;
3428 $(window).blur(function() {
3429 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3430 NETDATA.options.page_is_visible = false;
3431 if(NETDATA.options.debug.focus === true)
3432 console.log('Lost Focus!');
3436 $(window).focus(function() {
3437 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3438 NETDATA.options.page_is_visible = true;
3439 if(NETDATA.options.debug.focus === true)
3440 console.log('Focus restored!');
3444 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3445 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3446 NETDATA.options.page_is_visible = false;
3447 if(NETDATA.options.debug.focus === true)
3448 console.log('Document has no focus!');
3452 // bootstrap tab switching
3453 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3455 // bootstrap modal switching
3456 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3457 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3459 // bootstrap collapse switching
3460 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3461 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3463 NETDATA.parseDom(NETDATA.chartRefresher);
3465 // Alarms initialization
3466 setTimeout(NETDATA.alarms.init, 1000);
3468 // Registry initialization
3469 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3472 // ----------------------------------------------------------------------------------------------------------------
3475 NETDATA.peityInitialize = function(callback) {
3476 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3478 url: NETDATA.peity_js,
3481 xhrFields: { withCredentials: true } // required for the cookie
3484 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3487 NETDATA.chartLibraries.peity.enabled = false;
3488 NETDATA.error(100, NETDATA.peity_js);
3490 .always(function() {
3491 if(typeof callback === "function")
3496 NETDATA.chartLibraries.peity.enabled = false;
3497 if(typeof callback === "function")
3502 NETDATA.peityChartUpdate = function(state, data) {
3503 state.peity_instance.innerHTML = data.result;
3505 if(state.peity_options.stroke !== state.chartColors()[0]) {
3506 state.peity_options.stroke = state.chartColors()[0];
3507 if(state.chart.chart_type === 'line')
3508 state.peity_options.fill = NETDATA.themes.current.background;
3510 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3513 $(state.peity_instance).peity('line', state.peity_options);
3517 NETDATA.peityChartCreate = function(state, data) {
3518 state.peity_instance = document.createElement('div');
3519 state.element_chart.appendChild(state.peity_instance);
3521 var self = $(state.element);
3522 state.peity_options = {
3523 stroke: NETDATA.themes.current.foreground,
3524 strokeWidth: self.data('peity-strokewidth') || 1,
3525 width: state.chartWidth(),
3526 height: state.chartHeight(),
3527 fill: NETDATA.themes.current.foreground
3530 NETDATA.peityChartUpdate(state, data);
3534 // ----------------------------------------------------------------------------------------------------------------
3537 NETDATA.sparklineInitialize = function(callback) {
3538 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3540 url: NETDATA.sparkline_js,
3543 xhrFields: { withCredentials: true } // required for the cookie
3546 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3549 NETDATA.chartLibraries.sparkline.enabled = false;
3550 NETDATA.error(100, NETDATA.sparkline_js);
3552 .always(function() {
3553 if(typeof callback === "function")
3558 NETDATA.chartLibraries.sparkline.enabled = false;
3559 if(typeof callback === "function")
3564 NETDATA.sparklineChartUpdate = function(state, data) {
3565 state.sparkline_options.width = state.chartWidth();
3566 state.sparkline_options.height = state.chartHeight();
3568 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3572 NETDATA.sparklineChartCreate = function(state, data) {
3573 var self = $(state.element);
3574 var type = self.data('sparkline-type') || 'line';
3575 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3576 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3577 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3578 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3579 var composite = self.data('sparkline-composite') || undefined;
3580 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3581 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3582 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3583 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3584 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3585 var spotColor = self.data('sparkline-spotcolor') || undefined;
3586 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3587 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3588 var spotRadius = self.data('sparkline-spotradius') || undefined;
3589 var valueSpots = self.data('sparkline-valuespots') || undefined;
3590 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3591 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3592 var lineWidth = self.data('sparkline-linewidth') || undefined;
3593 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3594 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3595 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3596 var xvalues = self.data('sparkline-xvalues') || undefined;
3597 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3598 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3599 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3600 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3601 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3602 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3603 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3604 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3605 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3606 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3607 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3608 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3609 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3610 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3611 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3612 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3613 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3614 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3615 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3616 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3617 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3618 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3620 if(spotColor === 'disable') spotColor='';
3621 if(minSpotColor === 'disable') minSpotColor='';
3622 if(maxSpotColor === 'disable') maxSpotColor='';
3624 state.sparkline_options = {
3626 lineColor: lineColor,
3627 fillColor: fillColor,
3628 chartRangeMin: chartRangeMin,
3629 chartRangeMax: chartRangeMax,
3630 composite: composite,
3631 enableTagOptions: enableTagOptions,
3632 tagOptionPrefix: tagOptionPrefix,
3633 tagValuesAttribute: tagValuesAttribute,
3634 disableHiddenCheck: disableHiddenCheck,
3635 defaultPixelsPerValue: defaultPixelsPerValue,
3636 spotColor: spotColor,
3637 minSpotColor: minSpotColor,
3638 maxSpotColor: maxSpotColor,
3639 spotRadius: spotRadius,
3640 valueSpots: valueSpots,
3641 highlightSpotColor: highlightSpotColor,
3642 highlightLineColor: highlightLineColor,
3643 lineWidth: lineWidth,
3644 normalRangeMin: normalRangeMin,
3645 normalRangeMax: normalRangeMax,
3646 drawNormalOnTop: drawNormalOnTop,
3648 chartRangeClip: chartRangeClip,
3649 chartRangeMinX: chartRangeMinX,
3650 chartRangeMaxX: chartRangeMaxX,
3651 disableInteraction: disableInteraction,
3652 disableTooltips: disableTooltips,
3653 disableHighlight: disableHighlight,
3654 highlightLighten: highlightLighten,
3655 highlightColor: highlightColor,
3656 tooltipContainer: tooltipContainer,
3657 tooltipClassname: tooltipClassname,
3658 tooltipChartTitle: state.title,
3659 tooltipFormat: tooltipFormat,
3660 tooltipPrefix: tooltipPrefix,
3661 tooltipSuffix: tooltipSuffix,
3662 tooltipSkipNull: tooltipSkipNull,
3663 tooltipValueLookups: tooltipValueLookups,
3664 tooltipFormatFieldlist: tooltipFormatFieldlist,
3665 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3666 numberFormatter: numberFormatter,
3667 numberDigitGroupSep: numberDigitGroupSep,
3668 numberDecimalMark: numberDecimalMark,
3669 numberDigitGroupCount: numberDigitGroupCount,
3670 animatedZooms: animatedZooms,
3671 width: state.chartWidth(),
3672 height: state.chartHeight()
3675 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3679 // ----------------------------------------------------------------------------------------------------------------
3686 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3687 if(after < state.netdata_first)
3688 after = state.netdata_first;
3690 if(before > state.netdata_last)
3691 before = state.netdata_last;
3693 state.setMode('zoom');
3694 state.globalSelectionSyncStop();
3695 state.globalSelectionSyncDelay();
3696 state.dygraph_user_action = true;
3697 state.dygraph_force_zoom = true;
3698 state.updateChartPanOrZoom(after, before);
3699 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3702 NETDATA.dygraphSetSelection = function(state, t) {
3703 if(typeof state.dygraph_instance !== 'undefined') {
3704 var r = state.calculateRowForTime(t);
3706 state.dygraph_instance.setSelection(r);
3708 state.dygraph_instance.clearSelection();
3709 state.legendShowUndefined();
3716 NETDATA.dygraphClearSelection = function(state, t) {
3717 if(typeof state.dygraph_instance !== 'undefined') {
3718 state.dygraph_instance.clearSelection();
3723 NETDATA.dygraphSmoothInitialize = function(callback) {
3725 url: NETDATA.dygraph_smooth_js,
3728 xhrFields: { withCredentials: true } // required for the cookie
3731 NETDATA.dygraph.smooth = true;
3732 smoothPlotter.smoothing = 0.3;
3735 NETDATA.dygraph.smooth = false;
3737 .always(function() {
3738 if(typeof callback === "function")
3743 NETDATA.dygraphInitialize = function(callback) {
3744 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3746 url: NETDATA.dygraph_js,
3749 xhrFields: { withCredentials: true } // required for the cookie
3752 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3755 NETDATA.chartLibraries.dygraph.enabled = false;
3756 NETDATA.error(100, NETDATA.dygraph_js);
3758 .always(function() {
3759 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3760 NETDATA.dygraphSmoothInitialize(callback);
3761 else if(typeof callback === "function")
3766 NETDATA.chartLibraries.dygraph.enabled = false;
3767 if(typeof callback === "function")
3772 NETDATA.dygraphChartUpdate = function(state, data) {
3773 var dygraph = state.dygraph_instance;
3775 if(typeof dygraph === 'undefined')
3776 return NETDATA.dygraphChartCreate(state, data);
3778 // when the chart is not visible, and hidden
3779 // if there is a window resize, dygraph detects
3780 // its element size as 0x0.
3781 // this will make it re-appear properly
3783 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3787 file: data.result.data,
3788 colors: state.chartColors(),
3789 labels: data.result.labels,
3790 labelsDivWidth: state.chartWidth() - 70,
3791 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3794 if(state.dygraph_force_zoom === true) {
3795 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3796 state.log('dygraphChartUpdate() forced zoom update');
3798 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3799 options.valueRange = state.dygraph_options.valueRange;
3800 options.isZoomedIgnoreProgrammaticZoom = true;
3801 state.dygraph_force_zoom = false;
3803 else if(state.current.name !== 'auto') {
3804 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3805 state.log('dygraphChartUpdate() loose update');
3807 options.valueRange = state.dygraph_options.valueRange;
3810 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3811 state.log('dygraphChartUpdate() strict update');
3813 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3814 options.valueRange = state.dygraph_options.valueRange;
3815 options.isZoomedIgnoreProgrammaticZoom = true;
3818 if(state.dygraph_smooth_eligible === true) {
3819 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3820 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3821 NETDATA.dygraphChartCreate(state, data);
3826 dygraph.updateOptions(options);
3828 state.dygraph_last_rendered = new Date().getTime();
3832 NETDATA.dygraphChartCreate = function(state, data) {
3833 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3834 state.log('dygraphChartCreate()');
3836 var self = $(state.element);
3838 var chart_type = state.chart.chart_type;
3839 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3840 chart_type = self.data('dygraph-type') || chart_type;
3842 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3843 smooth = self.data('dygraph-smooth') || smooth;
3845 if(NETDATA.dygraph.smooth === false)
3848 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3849 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3851 state.dygraph_options = {
3852 colors: self.data('dygraph-colors') || state.chartColors(),
3854 // leave a few pixels empty on the right of the chart
3855 rightGap: self.data('dygraph-rightgap') || 5,
3856 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3857 showRoller: self.data('dygraph-showroller') || false,
3859 title: self.data('dygraph-title') || state.title,
3860 titleHeight: self.data('dygraph-titleheight') || 19,
3862 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3863 labels: data.result.labels,
3864 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3865 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3866 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3867 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3868 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3871 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3872 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3874 includeZero: self.data('dygraph-includezero') || false,
3875 xRangePad: self.data('dygraph-xrangepad') || 0,
3876 yRangePad: self.data('dygraph-yrangepad') || 1,
3878 valueRange: self.data('dygraph-valuerange') || null,
3880 ylabel: state.units,
3881 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3883 // the function to plot the chart
3886 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3887 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3888 strokePattern: self.data('dygraph-strokepattern') || undefined,
3890 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3891 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3892 drawPoints: self.data('dygraph-drawpoints') || false,
3894 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3895 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3897 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3898 pointSize: self.data('dygraph-pointsize') || 1,
3900 // enabling this makes the chart with little square lines
3901 stepPlot: self.data('dygraph-stepplot') || false,
3903 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3904 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3905 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3907 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3908 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3909 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3910 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3912 drawAxis: self.data('dygraph-drawaxis') || true,
3913 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3914 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3915 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3917 drawGrid: self.data('dygraph-drawgrid') || true,
3918 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3919 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3920 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3921 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3922 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3924 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3925 sigFigs: self.data('dygraph-sigfigs') || null,
3926 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3927 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3929 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3930 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3931 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3933 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3934 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3938 ticker: Dygraph.dateTicker,
3939 axisLabelFormatter: function (d, gran) {
3940 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3942 valueFormatter: function (ms) {
3943 var d = new Date(ms);
3944 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3945 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3950 valueFormatter: function (x) {
3951 // we format legends with the state object
3952 // no need to do anything here
3953 // return (Math.round(x*100) / 100).toLocaleString();
3954 // return state.legendFormatValue(x);
3959 legendFormatter: function(data) {
3960 var elements = state.element_legend_childs;
3962 // if the hidden div is not there
3963 // we are not managing the legend
3964 if(elements.hidden === null) return;
3966 if (typeof data.x !== 'undefined') {
3967 state.legendSetDate(data.x);
3968 var i = data.series.length;
3970 var series = data.series[i];
3971 if(!series.isVisible) continue;
3972 state.legendSetLabelValue(series.label, series.y);
3978 drawCallback: function(dygraph, is_initial) {
3979 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3980 state.dygraph_user_action = false;
3982 var x_range = dygraph.xAxisRange();
3983 var after = Math.round(x_range[0]);
3984 var before = Math.round(x_range[1]);
3986 if(NETDATA.options.debug.dygraph === true)
3987 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3989 if(before <= state.netdata_last && after >= state.netdata_first)
3990 state.updateChartPanOrZoom(after, before);
3993 zoomCallback: function(minDate, maxDate, yRanges) {
3994 if(NETDATA.options.debug.dygraph === true)
3995 state.log('dygraphZoomCallback()');
3997 state.globalSelectionSyncStop();
3998 state.globalSelectionSyncDelay();
3999 state.setMode('zoom');
4001 // refresh it to the greatest possible zoom level
4002 state.dygraph_user_action = true;
4003 state.dygraph_force_zoom = true;
4004 state.updateChartPanOrZoom(minDate, maxDate);
4006 highlightCallback: function(event, x, points, row, seriesName) {
4007 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4008 state.log('dygraphHighlightCallback()');
4012 // there is a bug in dygraph when the chart is zoomed enough
4013 // the time it thinks is selected is wrong
4014 // here we calculate the time t based on the row number selected
4016 var t = state.data_after + row * state.data_update_every;
4017 // 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);
4019 state.globalSelectionSync(x);
4021 // fix legend zIndex using the internal structures of dygraph legend module
4022 // this works, but it is a hack!
4023 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4025 unhighlightCallback: function(event) {
4026 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4027 state.log('dygraphUnhighlightCallback()');
4029 state.unpauseChart();
4030 state.globalSelectionSyncStop();
4032 interactionModel : {
4033 mousedown: function(event, dygraph, context) {
4034 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4035 state.log('interactionModel.mousedown()');
4037 state.dygraph_user_action = true;
4038 state.globalSelectionSyncStop();
4040 if(NETDATA.options.debug.dygraph === true)
4041 state.log('dygraphMouseDown()');
4043 // Right-click should not initiate a zoom.
4044 if(event.button && event.button === 2) return;
4046 context.initializeMouseDown(event, dygraph, context);
4048 if(event.button && event.button === 1) {
4049 if (event.altKey || event.shiftKey) {
4050 state.setMode('pan');
4051 state.globalSelectionSyncDelay();
4052 Dygraph.startPan(event, dygraph, context);
4055 state.setMode('zoom');
4056 state.globalSelectionSyncDelay();
4057 Dygraph.startZoom(event, dygraph, context);
4061 if (event.altKey || event.shiftKey) {
4062 state.setMode('zoom');
4063 state.globalSelectionSyncDelay();
4064 Dygraph.startZoom(event, dygraph, context);
4067 state.setMode('pan');
4068 state.globalSelectionSyncDelay();
4069 Dygraph.startPan(event, dygraph, context);
4073 mousemove: function(event, dygraph, context) {
4074 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4075 state.log('interactionModel.mousemove()');
4077 if(context.isPanning) {
4078 state.dygraph_user_action = true;
4079 state.globalSelectionSyncStop();
4080 state.globalSelectionSyncDelay();
4081 state.setMode('pan');
4082 Dygraph.movePan(event, dygraph, context);
4084 else if(context.isZooming) {
4085 state.dygraph_user_action = true;
4086 state.globalSelectionSyncStop();
4087 state.globalSelectionSyncDelay();
4088 state.setMode('zoom');
4089 Dygraph.moveZoom(event, dygraph, context);
4092 mouseup: function(event, dygraph, context) {
4093 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4094 state.log('interactionModel.mouseup()');
4096 if (context.isPanning) {
4097 state.dygraph_user_action = true;
4098 state.globalSelectionSyncDelay();
4099 Dygraph.endPan(event, dygraph, context);
4101 else if (context.isZooming) {
4102 state.dygraph_user_action = true;
4103 state.globalSelectionSyncDelay();
4104 Dygraph.endZoom(event, dygraph, context);
4107 click: function(event, dygraph, context) {
4108 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4109 state.log('interactionModel.click()');
4111 event.preventDefault();
4113 dblclick: function(event, dygraph, context) {
4114 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4115 state.log('interactionModel.dblclick()');
4116 NETDATA.resetAllCharts(state);
4118 mousewheel: function(event, dygraph, context) {
4119 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4120 state.log('interactionModel.mousewheel()');
4122 // Take the offset of a mouse event on the dygraph canvas and
4123 // convert it to a pair of percentages from the bottom left.
4124 // (Not top left, bottom is where the lower value is.)
4125 function offsetToPercentage(g, offsetX, offsetY) {
4126 // This is calculating the pixel offset of the leftmost date.
4127 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4128 var yar0 = g.yAxisRange(0);
4130 // This is calculating the pixel of the higest value. (Top pixel)
4131 var yOffset = g.toDomCoords(null, yar0[1])[1];
4133 // x y w and h are relative to the corner of the drawing area,
4134 // so that the upper corner of the drawing area is (0, 0).
4135 var x = offsetX - xOffset;
4136 var y = offsetY - yOffset;
4138 // This is computing the rightmost pixel, effectively defining the
4140 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4142 // This is computing the lowest pixel, effectively defining the height.
4143 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4145 // Percentage from the left.
4146 var xPct = w === 0 ? 0 : (x / w);
4147 // Percentage from the top.
4148 var yPct = h === 0 ? 0 : (y / h);
4150 // The (1-) part below changes it from "% distance down from the top"
4151 // to "% distance up from the bottom".
4152 return [xPct, (1-yPct)];
4155 // Adjusts [x, y] toward each other by zoomInPercentage%
4156 // Split it so the left/bottom axis gets xBias/yBias of that change and
4157 // tight/top gets (1-xBias)/(1-yBias) of that change.
4159 // If a bias is missing it splits it down the middle.
4160 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4161 xBias = xBias || 0.5;
4162 yBias = yBias || 0.5;
4164 function adjustAxis(axis, zoomInPercentage, bias) {
4165 var delta = axis[1] - axis[0];
4166 var increment = delta * zoomInPercentage;
4167 var foo = [increment * bias, increment * (1-bias)];
4169 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4172 var yAxes = g.yAxisRanges();
4174 for (var i = 0; i < yAxes.length; i++) {
4175 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4178 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4181 if(event.altKey || event.shiftKey) {
4182 state.dygraph_user_action = true;
4184 state.globalSelectionSyncStop();
4185 state.globalSelectionSyncDelay();
4187 // http://dygraphs.com/gallery/interaction-api.js
4188 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4189 var percentage = normal / 50;
4191 if (!(event.offsetX && event.offsetY)){
4192 event.offsetX = event.layerX - event.target.offsetLeft;
4193 event.offsetY = event.layerY - event.target.offsetTop;
4196 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4197 var xPct = percentages[0];
4198 var yPct = percentages[1];
4200 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4202 var after = new_x_range[0];
4203 var before = new_x_range[1];
4205 var first = state.netdata_first + state.data_update_every;
4206 var last = state.netdata_last + state.data_update_every;
4209 after -= (before - last);
4216 state.setMode('zoom');
4217 if(state.updateChartPanOrZoom(after, before) === true)
4218 dygraph.updateOptions({ dateWindow: [ after, before ] });
4220 event.preventDefault();
4223 touchstart: function(event, dygraph, context) {
4224 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4225 state.log('interactionModel.touchstart()');
4227 state.dygraph_user_action = true;
4228 state.setMode('zoom');
4231 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4233 // we overwrite the touch directions at the end, to overwrite
4234 // the internal default of dygraphs
4235 context.touchDirections = { x: true, y: false };
4237 state.dygraph_last_touch_start = new Date().getTime();
4238 state.dygraph_last_touch_move = 0;
4240 if(typeof event.touches[0].pageX === 'number')
4241 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4243 state.dygraph_last_touch_page_x = 0;
4245 touchmove: function(event, dygraph, context) {
4246 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4247 state.log('interactionModel.touchmove()');
4249 state.dygraph_user_action = true;
4250 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4252 state.dygraph_last_touch_move = new Date().getTime();
4254 touchend: function(event, dygraph, context) {
4255 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4256 state.log('interactionModel.touchend()');
4258 state.dygraph_user_action = true;
4259 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4261 // if it didn't move, it is a selection
4262 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4263 // internal api of dygraphs
4264 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4265 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4266 if(NETDATA.dygraphSetSelection(state, t) === true)
4267 state.globalSelectionSync(t);
4270 // if it was double tap within double click time, reset the charts
4271 var now = new Date().getTime();
4272 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4273 if(state.dygraph_last_touch_move === 0) {
4274 var dt = now - state.dygraph_last_touch_end;
4275 if(dt <= NETDATA.options.current.double_click_speed)
4276 NETDATA.resetAllCharts(state);
4280 // remember the timestamp of the last touch end
4281 state.dygraph_last_touch_end = now;
4286 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4287 state.dygraph_options.drawGrid = false;
4288 state.dygraph_options.drawAxis = false;
4289 state.dygraph_options.title = undefined;
4290 state.dygraph_options.units = undefined;
4291 state.dygraph_options.ylabel = undefined;
4292 state.dygraph_options.yLabelWidth = 0;
4293 state.dygraph_options.labelsDivWidth = 120;
4294 state.dygraph_options.labelsDivStyles.width = '120px';
4295 state.dygraph_options.labelsSeparateLines = true;
4296 state.dygraph_options.rightGap = 0;
4297 state.dygraph_options.yRangePad = 1;
4300 if(smooth === true) {
4301 state.dygraph_smooth_eligible = true;
4303 if(NETDATA.options.current.smooth_plot === true)
4304 state.dygraph_options.plotter = smoothPlotter;
4306 else state.dygraph_smooth_eligible = false;
4308 state.dygraph_instance = new Dygraph(state.element_chart,
4309 data.result.data, state.dygraph_options);
4311 state.dygraph_force_zoom = false;
4312 state.dygraph_user_action = false;
4313 state.dygraph_last_rendered = new Date().getTime();
4317 // ----------------------------------------------------------------------------------------------------------------
4320 NETDATA.morrisInitialize = function(callback) {
4321 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4323 // morris requires raphael
4324 if(!NETDATA.chartLibraries.raphael.initialized) {
4325 if(NETDATA.chartLibraries.raphael.enabled) {
4326 NETDATA.raphaelInitialize(function() {
4327 NETDATA.morrisInitialize(callback);
4331 NETDATA.chartLibraries.morris.enabled = false;
4332 if(typeof callback === "function")
4337 NETDATA._loadCSS(NETDATA.morris_css);
4340 url: NETDATA.morris_js,
4343 xhrFields: { withCredentials: true } // required for the cookie
4346 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4349 NETDATA.chartLibraries.morris.enabled = false;
4350 NETDATA.error(100, NETDATA.morris_js);
4352 .always(function() {
4353 if(typeof callback === "function")
4359 NETDATA.chartLibraries.morris.enabled = false;
4360 if(typeof callback === "function")
4365 NETDATA.morrisChartUpdate = function(state, data) {
4366 state.morris_instance.setData(data.result.data);
4370 NETDATA.morrisChartCreate = function(state, data) {
4372 state.morris_options = {
4373 element: state.element_chart.id,
4374 data: data.result.data,
4376 ykeys: data.dimension_names,
4377 labels: data.dimension_names,
4383 continuousLine: false,
4384 behaveLikeLine: false
4387 if(state.chart.chart_type === 'line')
4388 state.morris_instance = new Morris.Line(state.morris_options);
4390 else if(state.chart.chart_type === 'area') {
4391 state.morris_options.behaveLikeLine = true;
4392 state.morris_instance = new Morris.Area(state.morris_options);
4395 state.morris_instance = new Morris.Area(state.morris_options);
4400 // ----------------------------------------------------------------------------------------------------------------
4403 NETDATA.raphaelInitialize = function(callback) {
4404 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4406 url: NETDATA.raphael_js,
4409 xhrFields: { withCredentials: true } // required for the cookie
4412 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4415 NETDATA.chartLibraries.raphael.enabled = false;
4416 NETDATA.error(100, NETDATA.raphael_js);
4418 .always(function() {
4419 if(typeof callback === "function")
4424 NETDATA.chartLibraries.raphael.enabled = false;
4425 if(typeof callback === "function")
4430 NETDATA.raphaelChartUpdate = function(state, data) {
4431 $(state.element_chart).raphael(data.result, {
4432 width: state.chartWidth(),
4433 height: state.chartHeight()
4439 NETDATA.raphaelChartCreate = function(state, data) {
4440 $(state.element_chart).raphael(data.result, {
4441 width: state.chartWidth(),
4442 height: state.chartHeight()
4448 // ----------------------------------------------------------------------------------------------------------------
4451 NETDATA.c3Initialize = function(callback) {
4452 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4455 if(!NETDATA.chartLibraries.d3.initialized) {
4456 if(NETDATA.chartLibraries.d3.enabled) {
4457 NETDATA.d3Initialize(function() {
4458 NETDATA.c3Initialize(callback);
4462 NETDATA.chartLibraries.c3.enabled = false;
4463 if(typeof callback === "function")
4468 NETDATA._loadCSS(NETDATA.c3_css);
4474 xhrFields: { withCredentials: true } // required for the cookie
4477 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4480 NETDATA.chartLibraries.c3.enabled = false;
4481 NETDATA.error(100, NETDATA.c3_js);
4483 .always(function() {
4484 if(typeof callback === "function")
4490 NETDATA.chartLibraries.c3.enabled = false;
4491 if(typeof callback === "function")
4496 NETDATA.c3ChartUpdate = function(state, data) {
4497 state.c3_instance.destroy();
4498 return NETDATA.c3ChartCreate(state, data);
4500 //state.c3_instance.load({
4501 // rows: data.result,
4508 NETDATA.c3ChartCreate = function(state, data) {
4510 state.element_chart.id = 'c3-' + state.uuid;
4511 // console.log('id = ' + state.element_chart.id);
4513 state.c3_instance = c3.generate({
4514 bindto: '#' + state.element_chart.id,
4516 width: state.chartWidth(),
4517 height: state.chartHeight()
4520 pattern: state.chartColors()
4525 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4531 format: function(x) {
4532 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4559 // console.log(state.c3_instance);
4564 // ----------------------------------------------------------------------------------------------------------------
4567 NETDATA.d3Initialize = function(callback) {
4568 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4573 xhrFields: { withCredentials: true } // required for the cookie
4576 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4579 NETDATA.chartLibraries.d3.enabled = false;
4580 NETDATA.error(100, NETDATA.d3_js);
4582 .always(function() {
4583 if(typeof callback === "function")
4588 NETDATA.chartLibraries.d3.enabled = false;
4589 if(typeof callback === "function")
4594 NETDATA.d3ChartUpdate = function(state, data) {
4598 NETDATA.d3ChartCreate = function(state, data) {
4602 // ----------------------------------------------------------------------------------------------------------------
4605 NETDATA.googleInitialize = function(callback) {
4606 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4608 url: NETDATA.google_js,
4611 xhrFields: { withCredentials: true } // required for the cookie
4614 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4615 google.load('visualization', '1.1', {
4616 'packages': ['corechart', 'controls'],
4617 'callback': callback
4621 NETDATA.chartLibraries.google.enabled = false;
4622 NETDATA.error(100, NETDATA.google_js);
4623 if(typeof callback === "function")
4628 NETDATA.chartLibraries.google.enabled = false;
4629 if(typeof callback === "function")
4634 NETDATA.googleChartUpdate = function(state, data) {
4635 var datatable = new google.visualization.DataTable(data.result);
4636 state.google_instance.draw(datatable, state.google_options);
4640 NETDATA.googleChartCreate = function(state, data) {
4641 var datatable = new google.visualization.DataTable(data.result);
4643 state.google_options = {
4644 colors: state.chartColors(),
4646 // do not set width, height - the chart resizes itself
4647 //width: state.chartWidth(),
4648 //height: state.chartHeight(),
4653 // title: "Time of Day",
4654 // format:'HH:mm:ss',
4655 viewWindowMode: 'maximized',
4667 viewWindowMode: 'pretty',
4682 focusTarget: 'category',
4689 titlePosition: 'out',
4700 curveType: 'function',
4705 switch(state.chart.chart_type) {
4707 state.google_options.vAxis.viewWindowMode = 'maximized';
4708 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4709 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4713 state.google_options.isStacked = true;
4714 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4715 state.google_options.vAxis.viewWindowMode = 'maximized';
4716 state.google_options.vAxis.minValue = null;
4717 state.google_options.vAxis.maxValue = null;
4718 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4723 state.google_options.lineWidth = 2;
4724 state.google_instance = new google.visualization.LineChart(state.element_chart);
4728 state.google_instance.draw(datatable, state.google_options);
4732 // ----------------------------------------------------------------------------------------------------------------
4734 NETDATA.percentFromValueMax = function(value, max) {
4735 if(value === null) value = 0;
4736 if(max < value) max = value;
4740 pcent = Math.round(value * 100 / max);
4741 if(pcent === 0 && value > 0) pcent = 1;
4747 // ----------------------------------------------------------------------------------------------------------------
4750 NETDATA.easypiechartInitialize = function(callback) {
4751 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4753 url: NETDATA.easypiechart_js,
4756 xhrFields: { withCredentials: true } // required for the cookie
4759 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4762 NETDATA.chartLibraries.easypiechart.enabled = false;
4763 NETDATA.error(100, NETDATA.easypiechart_js);
4765 .always(function() {
4766 if(typeof callback === "function")
4771 NETDATA.chartLibraries.easypiechart.enabled = false;
4772 if(typeof callback === "function")
4777 NETDATA.easypiechartClearSelection = function(state) {
4778 if(typeof state.easyPieChartEvent !== 'undefined') {
4779 if(state.easyPieChartEvent.timer !== null)
4780 clearTimeout(state.easyPieChartEvent.timer);
4782 state.easyPieChartEvent.timer = null;
4785 if(state.isAutoRefreshable() === true && state.data !== null) {
4786 NETDATA.easypiechartChartUpdate(state, state.data);
4789 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4790 state.easyPieChart_instance.update(0);
4792 state.easyPieChart_instance.enableAnimation();
4797 NETDATA.easypiechartSetSelection = function(state, t) {
4798 if(state.timeIsVisible(t) !== true)
4799 return NETDATA.easypiechartClearSelection(state);
4801 var slot = state.calculateRowForTime(t);
4802 if(slot < 0 || slot >= state.data.result.length)
4803 return NETDATA.easypiechartClearSelection(state);
4805 if(typeof state.easyPieChartEvent === 'undefined') {
4806 state.easyPieChartEvent = {
4813 var value = state.data.result[state.data.result.length - 1 - slot];
4814 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4815 var pcent = NETDATA.percentFromValueMax(value, max);
4817 state.easyPieChartEvent.value = value;
4818 state.easyPieChartEvent.pcent = pcent;
4819 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4821 if(state.easyPieChartEvent.timer === null) {
4822 state.easyPieChart_instance.disableAnimation();
4824 state.easyPieChartEvent.timer = setTimeout(function() {
4825 state.easyPieChartEvent.timer = null;
4826 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4827 }, NETDATA.options.current.charts_selection_animation_delay);
4833 NETDATA.easypiechartChartUpdate = function(state, data) {
4834 var value, max, pcent;
4836 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4842 value = data.result[0];
4843 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4844 pcent = NETDATA.percentFromValueMax(value, max);
4847 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4848 state.easyPieChart_instance.update(pcent);
4852 NETDATA.easypiechartChartCreate = function(state, data) {
4853 var self = $(state.element);
4854 var chart = $(state.element_chart);
4856 var value = data.result[0];
4857 var max = self.data('easypiechart-max-value') || null;
4858 var adjust = self.data('easypiechart-adjust') || null;
4862 state.easyPieChartMax = null;
4865 state.easyPieChartMax = max;
4867 var pcent = NETDATA.percentFromValueMax(value, max);
4869 chart.data('data-percent', pcent);
4873 case 'width': size = state.chartHeight(); break;
4874 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4875 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4877 default: size = state.chartWidth(); break;
4879 state.element.style.width = size + 'px';
4880 state.element.style.height = size + 'px';
4882 var stroke = Math.floor(size / 22);
4883 if(stroke < 3) stroke = 2;
4885 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4886 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4887 state.easyPieChartLabel = document.createElement('span');
4888 state.easyPieChartLabel.className = 'easyPieChartLabel';
4889 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4890 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4891 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4892 state.element_chart.appendChild(state.easyPieChartLabel);
4894 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4895 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4896 state.easyPieChartTitle = document.createElement('span');
4897 state.easyPieChartTitle.className = 'easyPieChartTitle';
4898 state.easyPieChartTitle.innerHTML = state.title;
4899 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4900 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4901 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4902 state.element_chart.appendChild(state.easyPieChartTitle);
4904 var unitfontsize = Math.round(titlefontsize * 0.9);
4905 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4906 state.easyPieChartUnits = document.createElement('span');
4907 state.easyPieChartUnits.className = 'easyPieChartUnits';
4908 state.easyPieChartUnits.innerHTML = state.units;
4909 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4910 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4911 state.element_chart.appendChild(state.easyPieChartUnits);
4913 chart.easyPieChart({
4914 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4915 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4916 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4917 scaleLength: self.data('easypiechart-scalelength') || 5,
4918 lineCap: self.data('easypiechart-linecap') || 'round',
4919 lineWidth: self.data('easypiechart-linewidth') || stroke,
4920 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4921 size: self.data('easypiechart-size') || size,
4922 rotate: self.data('easypiechart-rotate') || 0,
4923 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4924 easing: self.data('easypiechart-easing') || undefined
4927 // when we just re-create the chart
4928 // do not animate the first update
4930 if(typeof state.easyPieChart_instance !== 'undefined')
4933 state.easyPieChart_instance = chart.data('easyPieChart');
4934 if(animate === false) state.easyPieChart_instance.disableAnimation();
4935 state.easyPieChart_instance.update(pcent);
4936 if(animate === false) state.easyPieChart_instance.enableAnimation();
4940 // ----------------------------------------------------------------------------------------------------------------
4943 NETDATA.gaugeInitialize = function(callback) {
4944 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4946 url: NETDATA.gauge_js,
4949 xhrFields: { withCredentials: true } // required for the cookie
4952 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4955 NETDATA.chartLibraries.gauge.enabled = false;
4956 NETDATA.error(100, NETDATA.gauge_js);
4958 .always(function() {
4959 if(typeof callback === "function")
4964 NETDATA.chartLibraries.gauge.enabled = false;
4965 if(typeof callback === "function")
4970 NETDATA.gaugeAnimation = function(state, status) {
4973 if(typeof status === 'boolean' && status === false)
4975 else if(typeof status === 'number')
4978 state.gauge_instance.animationSpeed = speed;
4979 state.___gaugeOld__.speed = speed;
4982 NETDATA.gaugeSet = function(state, value, min, max) {
4983 if(typeof value !== 'number') value = 0;
4984 if(typeof min !== 'number') min = 0;
4985 if(typeof max !== 'number') max = 0;
4986 if(value > max) max = value;
4987 if(value < min) min = value;
4996 // gauge.js has an issue if the needle
4997 // is smaller than min or larger than max
4998 // when we set the new values
4999 // the needle will go crazy
5001 // to prevent it, we always feed it
5002 // with a percentage, so that the needle
5003 // is always between min and max
5004 var pcent = (value - min) * 100 / (max - min);
5006 // these should never happen
5007 if(pcent < 0) pcent = 0;
5008 if(pcent > 100) pcent = 100;
5010 state.gauge_instance.set(pcent);
5012 state.___gaugeOld__.value = value;
5013 state.___gaugeOld__.min = min;
5014 state.___gaugeOld__.max = max;
5017 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5018 if(state.___gaugeOld__.valueLabel !== value) {
5019 state.___gaugeOld__.valueLabel = value;
5020 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5022 if(state.___gaugeOld__.minLabel !== min) {
5023 state.___gaugeOld__.minLabel = min;
5024 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5026 if(state.___gaugeOld__.maxLabel !== max) {
5027 state.___gaugeOld__.maxLabel = max;
5028 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5032 NETDATA.gaugeClearSelection = function(state) {
5033 if(typeof state.gaugeEvent !== 'undefined') {
5034 if(state.gaugeEvent.timer !== null)
5035 clearTimeout(state.gaugeEvent.timer);
5037 state.gaugeEvent.timer = null;
5040 if(state.isAutoRefreshable() === true && state.data !== null) {
5041 NETDATA.gaugeChartUpdate(state, state.data);
5044 NETDATA.gaugeAnimation(state, false);
5045 NETDATA.gaugeSet(state, null, null, null);
5046 NETDATA.gaugeSetLabels(state, null, null, null);
5049 NETDATA.gaugeAnimation(state, true);
5053 NETDATA.gaugeSetSelection = function(state, t) {
5054 if(state.timeIsVisible(t) !== true)
5055 return NETDATA.gaugeClearSelection(state);
5057 var slot = state.calculateRowForTime(t);
5058 if(slot < 0 || slot >= state.data.result.length)
5059 return NETDATA.gaugeClearSelection(state);
5061 if(typeof state.gaugeEvent === 'undefined') {
5062 state.gaugeEvent = {
5070 var value = state.data.result[state.data.result.length - 1 - slot];
5071 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5074 state.gaugeEvent.value = value;
5075 state.gaugeEvent.max = max;
5076 state.gaugeEvent.min = min;
5077 NETDATA.gaugeSetLabels(state, value, min, max);
5079 if(state.gaugeEvent.timer === null) {
5080 NETDATA.gaugeAnimation(state, false);
5082 state.gaugeEvent.timer = setTimeout(function() {
5083 state.gaugeEvent.timer = null;
5084 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5085 }, NETDATA.options.current.charts_selection_animation_delay);
5091 NETDATA.gaugeChartUpdate = function(state, data) {
5092 var value, min, max;
5094 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5098 NETDATA.gaugeSetLabels(state, null, null, null);
5101 value = data.result[0];
5103 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5104 if(value > max) max = value;
5105 NETDATA.gaugeSetLabels(state, value, min, max);
5108 NETDATA.gaugeSet(state, value, min, max);
5112 NETDATA.gaugeChartCreate = function(state, data) {
5113 var self = $(state.element);
5114 // var chart = $(state.element_chart);
5116 var value = data.result[0];
5117 var max = self.data('gauge-max-value') || null;
5118 var adjust = self.data('gauge-adjust') || null;
5119 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5120 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5121 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5122 var stopColor = self.data('gauge-stop-color') || void 0;
5123 var generateGradient = self.data('gauge-generate-gradient') || false;
5127 state.gaugeMax = null;
5130 state.gaugeMax = max;
5132 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5134 // case 'width': width = height * ratio; break;
5136 // default: height = width / ratio; break;
5138 //state.element.style.width = width.toString() + 'px';
5139 //state.element.style.height = height.toString() + 'px';
5144 lines: 12, // The number of lines to draw
5145 angle: 0.15, // The length of each line
5146 lineWidth: 0.44, // 0.44 The line thickness
5148 length: 0.8, // 0.9 The radius of the inner circle
5149 strokeWidth: 0.035, // The rotation offset
5150 color: pointerColor // Fill color
5152 colorStart: startColor, // Colors
5153 colorStop: stopColor, // just experiment with them
5154 strokeColor: strokeColor, // to see which ones work best for you
5156 generateGradient: (generateGradient === true)?true:false,
5160 if (generateGradient.constructor === Array) {
5162 // data-gauge-generate-gradient="[0, 50, 100]"
5163 // data-gauge-gradient-percent-color-0="#FFFFFF"
5164 // data-gauge-gradient-percent-color-50="#999900"
5165 // data-gauge-gradient-percent-color-100="#000000"
5167 options.percentColors = new Array();
5168 var len = generateGradient.length;
5170 var pcent = generateGradient[len];
5171 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5172 if(color !== false) {
5173 var a = new Array();
5176 options.percentColors.unshift(a);
5179 if(options.percentColors.length === 0)
5180 delete options.percentColors;
5182 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5183 options.percentColors = [
5184 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5185 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5186 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5187 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5188 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5189 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5190 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5191 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5192 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5193 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5194 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5197 state.gauge_canvas = document.createElement('canvas');
5198 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5199 state.gauge_canvas.className = 'gaugeChart';
5200 state.gauge_canvas.width = width;
5201 state.gauge_canvas.height = height;
5202 state.element_chart.appendChild(state.gauge_canvas);
5204 var valuefontsize = Math.floor(height / 6);
5205 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5206 state.gaugeChartLabel = document.createElement('span');
5207 state.gaugeChartLabel.className = 'gaugeChartLabel';
5208 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5209 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5210 state.element_chart.appendChild(state.gaugeChartLabel);
5212 var titlefontsize = Math.round(valuefontsize / 2);
5214 state.gaugeChartTitle = document.createElement('span');
5215 state.gaugeChartTitle.className = 'gaugeChartTitle';
5216 state.gaugeChartTitle.innerHTML = state.title;
5217 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5218 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5219 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5220 state.element_chart.appendChild(state.gaugeChartTitle);
5222 var unitfontsize = Math.round(titlefontsize * 0.9);
5223 state.gaugeChartUnits = document.createElement('span');
5224 state.gaugeChartUnits.className = 'gaugeChartUnits';
5225 state.gaugeChartUnits.innerHTML = state.units;
5226 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5227 state.element_chart.appendChild(state.gaugeChartUnits);
5229 state.gaugeChartMin = document.createElement('span');
5230 state.gaugeChartMin.className = 'gaugeChartMin';
5231 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5232 state.element_chart.appendChild(state.gaugeChartMin);
5234 state.gaugeChartMax = document.createElement('span');
5235 state.gaugeChartMax.className = 'gaugeChartMax';
5236 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5237 state.element_chart.appendChild(state.gaugeChartMax);
5239 // when we just re-create the chart
5240 // do not animate the first update
5242 if(typeof state.gauge_instance !== 'undefined')
5245 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5247 state.___gaugeOld__ = {
5256 // we will always feed a percentage
5257 state.gauge_instance.minValue = 0;
5258 state.gauge_instance.maxValue = 100;
5260 NETDATA.gaugeAnimation(state, animate);
5261 NETDATA.gaugeSet(state, value, 0, max);
5262 NETDATA.gaugeSetLabels(state, value, 0, max);
5263 NETDATA.gaugeAnimation(state, true);
5267 // ----------------------------------------------------------------------------------------------------------------
5268 // Charts Libraries Registration
5270 NETDATA.chartLibraries = {
5272 initialize: NETDATA.dygraphInitialize,
5273 create: NETDATA.dygraphChartCreate,
5274 update: NETDATA.dygraphChartUpdate,
5275 resize: function(state) {
5276 if(typeof state.dygraph_instance.resize === 'function')
5277 state.dygraph_instance.resize();
5279 setSelection: NETDATA.dygraphSetSelection,
5280 clearSelection: NETDATA.dygraphClearSelection,
5281 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5284 format: function(state) { return 'json'; },
5285 options: function(state) { return 'ms|flip'; },
5286 legend: function(state) {
5287 if(this.isSparkline(state) === false)
5288 return 'right-side';
5292 autoresize: function(state) { return true; },
5293 max_updates_to_recreate: function(state) { return 5000; },
5294 track_colors: function(state) { return true; },
5295 pixels_per_point: function(state) {
5296 if(this.isSparkline(state) === false)
5302 isSparkline: function(state) {
5303 if(typeof state.dygraph_sparkline === 'undefined') {
5304 var t = $(state.element).data('dygraph-theme');
5305 if(t === 'sparkline')
5306 state.dygraph_sparkline = true;
5308 state.dygraph_sparkline = false;
5310 return state.dygraph_sparkline;
5314 initialize: NETDATA.sparklineInitialize,
5315 create: NETDATA.sparklineChartCreate,
5316 update: NETDATA.sparklineChartUpdate,
5318 setSelection: undefined, // function(state, t) { return true; },
5319 clearSelection: undefined, // function(state) { return true; },
5320 toolboxPanAndZoom: null,
5323 format: function(state) { return 'array'; },
5324 options: function(state) { return 'flip|abs'; },
5325 legend: function(state) { return null; },
5326 autoresize: function(state) { return false; },
5327 max_updates_to_recreate: function(state) { return 5000; },
5328 track_colors: function(state) { return false; },
5329 pixels_per_point: function(state) { return 3; }
5332 initialize: NETDATA.peityInitialize,
5333 create: NETDATA.peityChartCreate,
5334 update: NETDATA.peityChartUpdate,
5336 setSelection: undefined, // function(state, t) { return true; },
5337 clearSelection: undefined, // function(state) { return true; },
5338 toolboxPanAndZoom: null,
5341 format: function(state) { return 'ssvcomma'; },
5342 options: function(state) { return 'null2zero|flip|abs'; },
5343 legend: function(state) { return null; },
5344 autoresize: function(state) { return false; },
5345 max_updates_to_recreate: function(state) { return 5000; },
5346 track_colors: function(state) { return false; },
5347 pixels_per_point: function(state) { return 3; }
5350 initialize: NETDATA.morrisInitialize,
5351 create: NETDATA.morrisChartCreate,
5352 update: NETDATA.morrisChartUpdate,
5354 setSelection: undefined, // function(state, t) { return true; },
5355 clearSelection: undefined, // function(state) { return true; },
5356 toolboxPanAndZoom: null,
5359 format: function(state) { return 'json'; },
5360 options: function(state) { return 'objectrows|ms'; },
5361 legend: function(state) { return null; },
5362 autoresize: function(state) { return false; },
5363 max_updates_to_recreate: function(state) { return 50; },
5364 track_colors: function(state) { return false; },
5365 pixels_per_point: function(state) { return 15; }
5368 initialize: NETDATA.googleInitialize,
5369 create: NETDATA.googleChartCreate,
5370 update: NETDATA.googleChartUpdate,
5372 setSelection: undefined, //function(state, t) { return true; },
5373 clearSelection: undefined, //function(state) { return true; },
5374 toolboxPanAndZoom: null,
5377 format: function(state) { return 'datatable'; },
5378 options: function(state) { return ''; },
5379 legend: function(state) { return null; },
5380 autoresize: function(state) { return false; },
5381 max_updates_to_recreate: function(state) { return 300; },
5382 track_colors: function(state) { return false; },
5383 pixels_per_point: function(state) { return 4; }
5386 initialize: NETDATA.raphaelInitialize,
5387 create: NETDATA.raphaelChartCreate,
5388 update: NETDATA.raphaelChartUpdate,
5390 setSelection: undefined, // function(state, t) { return true; },
5391 clearSelection: undefined, // function(state) { return true; },
5392 toolboxPanAndZoom: null,
5395 format: function(state) { return 'json'; },
5396 options: function(state) { return ''; },
5397 legend: function(state) { return null; },
5398 autoresize: function(state) { return false; },
5399 max_updates_to_recreate: function(state) { return 5000; },
5400 track_colors: function(state) { return false; },
5401 pixels_per_point: function(state) { return 3; }
5404 initialize: NETDATA.c3Initialize,
5405 create: NETDATA.c3ChartCreate,
5406 update: NETDATA.c3ChartUpdate,
5408 setSelection: undefined, // function(state, t) { return true; },
5409 clearSelection: undefined, // function(state) { return true; },
5410 toolboxPanAndZoom: null,
5413 format: function(state) { return 'csvjsonarray'; },
5414 options: function(state) { return 'milliseconds'; },
5415 legend: function(state) { return null; },
5416 autoresize: function(state) { return false; },
5417 max_updates_to_recreate: function(state) { return 5000; },
5418 track_colors: function(state) { return false; },
5419 pixels_per_point: function(state) { return 15; }
5422 initialize: NETDATA.d3Initialize,
5423 create: NETDATA.d3ChartCreate,
5424 update: NETDATA.d3ChartUpdate,
5426 setSelection: undefined, // function(state, t) { return true; },
5427 clearSelection: undefined, // function(state) { return true; },
5428 toolboxPanAndZoom: null,
5431 format: function(state) { return 'json'; },
5432 options: function(state) { return ''; },
5433 legend: function(state) { return null; },
5434 autoresize: function(state) { return false; },
5435 max_updates_to_recreate: function(state) { return 5000; },
5436 track_colors: function(state) { return false; },
5437 pixels_per_point: function(state) { return 3; }
5440 initialize: NETDATA.easypiechartInitialize,
5441 create: NETDATA.easypiechartChartCreate,
5442 update: NETDATA.easypiechartChartUpdate,
5444 setSelection: NETDATA.easypiechartSetSelection,
5445 clearSelection: NETDATA.easypiechartClearSelection,
5446 toolboxPanAndZoom: null,
5449 format: function(state) { return 'array'; },
5450 options: function(state) { return 'absolute'; },
5451 legend: function(state) { return null; },
5452 autoresize: function(state) { return false; },
5453 max_updates_to_recreate: function(state) { return 5000; },
5454 track_colors: function(state) { return true; },
5455 pixels_per_point: function(state) { return 3; },
5459 initialize: NETDATA.gaugeInitialize,
5460 create: NETDATA.gaugeChartCreate,
5461 update: NETDATA.gaugeChartUpdate,
5463 setSelection: NETDATA.gaugeSetSelection,
5464 clearSelection: NETDATA.gaugeClearSelection,
5465 toolboxPanAndZoom: null,
5468 format: function(state) { return 'array'; },
5469 options: function(state) { return 'absolute'; },
5470 legend: function(state) { return null; },
5471 autoresize: function(state) { return false; },
5472 max_updates_to_recreate: function(state) { return 5000; },
5473 track_colors: function(state) { return true; },
5474 pixels_per_point: function(state) { return 3; },
5479 NETDATA.registerChartLibrary = function(library, url) {
5480 if(NETDATA.options.debug.libraries === true)
5481 console.log("registering chart library: " + library);
5483 NETDATA.chartLibraries[library].url = url;
5484 NETDATA.chartLibraries[library].initialized = true;
5485 NETDATA.chartLibraries[library].enabled = true;
5488 // ----------------------------------------------------------------------------------------------------------------
5489 // Load required JS libraries and CSS
5491 NETDATA.requiredJs = [
5493 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5494 isAlreadyLoaded: function() {
5495 // check if bootstrap is loaded
5496 if(typeof $().emulateTransitionEnd == 'function')
5499 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5507 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5508 isAlreadyLoaded: function() { return false; }
5511 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5512 isAlreadyLoaded: function() { return false; }
5516 NETDATA.requiredCSS = [
5518 url: NETDATA.themes.current.bootstrap_css,
5519 isAlreadyLoaded: function() {
5520 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5527 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5528 isAlreadyLoaded: function() { return false; }
5531 url: NETDATA.themes.current.dashboard_css,
5532 isAlreadyLoaded: function() { return false; }
5535 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5536 isAlreadyLoaded: function() { return false; }
5540 NETDATA.loadRequiredJs = function(index, callback) {
5541 if(index >= NETDATA.requiredJs.length) {
5542 if(typeof callback === 'function')
5547 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5548 NETDATA.loadRequiredJs(++index, callback);
5552 if(NETDATA.options.debug.main_loop === true)
5553 console.log('loading ' + NETDATA.requiredJs[index].url);
5556 url: NETDATA.requiredJs[index].url,
5559 xhrFields: { withCredentials: true } // required for the cookie
5561 .success(function() {
5562 if(NETDATA.options.debug.main_loop === true)
5563 console.log('loaded ' + NETDATA.requiredJs[index].url);
5565 NETDATA.loadRequiredJs(++index, callback);
5568 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5572 NETDATA.loadRequiredCSS = function(index) {
5573 if(index >= NETDATA.requiredCSS.length)
5576 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5577 NETDATA.loadRequiredCSS(++index);
5581 if(NETDATA.options.debug.main_loop === true)
5582 console.log('loading ' + NETDATA.requiredCSS[index].url);
5584 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5585 NETDATA.loadRequiredCSS(++index);
5589 // ----------------------------------------------------------------------------------------------------------------
5590 // Registry of netdata hosts
5593 notifications: false, // when true, the browser supports notifications (may not be granted though)
5594 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5595 // notifications_shown: new Array(),
5597 server: null, // the server to connect to for fetching alarms
5598 current: null, // the list of raised alarms - updated in the background
5599 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5601 notify: function(entry) {
5602 // console.log('alarm ' + entry.unique_id);
5604 if(entry.updated === true) {
5605 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5609 var name = entry.name.replace(/_/g, ' ');
5610 var status = entry.status.toLowerCase();
5611 var title = name + ' = ' + ((entry.value === null)?'NaN':Math.floor(entry.value)).toString() + ' ' + entry.units;
5612 var tag = entry.alarm_id;
5613 var icon = 'images/seo-performance-128.png';
5614 var interaction = false;
5618 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5620 switch(entry.status) {
5628 case 'UNINITIALIZED':
5632 if(NETDATA.alarms.last_notification_id === 0) {
5633 // console.log('alarm ' + entry.unique_id + ' is not current');
5636 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5637 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5640 title = name + ' back to normal';
5641 icon = 'images/check-mark-2-128-green.png'
5642 interaction = false;
5646 if(entry.old_status === 'CRITICAL')
5647 status = 'demoted to ' + entry.status.toLowerCase();
5649 icon = 'images/alert-128-orange.png';
5650 interaction = false;
5654 if(entry.old_status === 'WARNING')
5655 status = 'escalated to ' + entry.status.toLowerCase();
5657 icon = 'images/alert-128-red.png'
5662 console.log('invalid alarm status ' + entry.status);
5667 // cleanup old notifications with the same alarm_id as this one
5668 // FIXME: it does not seem to work on any web browser!
5669 var len = NETDATA.alarms.notifications_shown.length;
5671 var n = NETDATA.alarms.notifications_shown[len];
5672 if(n.data.alarm_id === entry.alarm_id) {
5673 console.log('removing old alarm ' + n.data.unique_id);
5675 // close the notification
5678 // remove it from the array
5679 NETDATA.alarms.notifications_shown.splice(len, 1);
5680 len = NETDATA.alarms.notifications_shown.length;
5686 // show this notification
5687 // console.log('new notification: ' + title);
5688 var n = new Notification(title, {
5689 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5691 requireInteraction: interaction,
5692 icon: NETDATA.serverDefault + icon,
5694 // FIXME: does not seem to work on any web browser
5695 // onclick: function(event) {
5696 // event.preventDefault();
5697 // console.log('clicked: ');
5698 // console.log(event);
5703 // NETDATA.alarms.notifications_shown.push(n);
5704 // console.log(entry);
5708 notifyAll: function() {
5709 // console.log('FETCHING ALARM LOG');
5710 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5711 // console.log('ALARM LOG FETCHED');
5713 if(data === null || typeof data !== 'object') {
5714 console.log('invalid alarms log response');
5718 if(data.length === 0) {
5719 console.log('received empty alarm log');
5723 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5725 data.sort(function(a, b) {
5726 if(a.unique_id > b.unique_id) return -1;
5727 if(a.unique_id < b.unique_id) return 1;
5731 var len = data.length;
5733 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5734 NETDATA.alarms.notify(data[len]);
5737 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5740 NETDATA.alarms.last_notification_id = data[0].unique_id;
5741 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5745 check_notifications: function() {
5746 // returns true if we should fire 1+ notifications
5748 if(NETDATA.alarms.notifications !== true) {
5749 // console.log('notifications not available');
5753 if(Notification.permission !== 'granted') {
5754 // console.log('notifications not granted');
5758 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5759 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5761 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5762 // console.log('new alarms detected');
5765 //else console.log('no new alarms');
5767 // else console.log('cannot process alarms');
5772 get: function(what, callback) {
5774 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5777 xhrFields: { withCredentials: true } // required for the cookie
5779 .done(function(data) {
5780 if(typeof callback === 'function')
5784 NETDATA.error(415, NETDATA.alarms.server);
5786 if(typeof callback === 'function')
5791 update_forever: function() {
5792 NETDATA.alarms.get('active', function(data) {
5794 NETDATA.alarms.current = data;
5796 if(NETDATA.alarms.check_notifications() === true) {
5797 NETDATA.alarms.notifyAll();
5800 if (typeof NETDATA.alarms.callback === 'function') {
5801 NETDATA.alarms.callback(data);
5804 // Health monitoring is disabled on this netdata
5805 if(data.status === false) return;
5808 setTimeout(NETDATA.alarms.update_forever, 10000);
5812 get_log: function(last_id, callback) {
5813 // console.log('fetching all log after ' + last_id.toString());
5815 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5818 xhrFields: { withCredentials: true } // required for the cookie
5820 .done(function(data) {
5821 if(typeof callback === 'function')
5825 NETDATA.error(416, NETDATA.alarms.server);
5827 if(typeof callback === 'function')
5833 var host = NETDATA.serverDefault;
5834 while(host.slice(-1) === '/')
5835 host = host.substring(0, host.length - 1);
5836 NETDATA.alarms.server = host;
5838 if(netdataShowAlarms === true) {
5839 NETDATA.alarms.update_forever();
5841 if('Notification' in window) {
5842 // console.log('notifications available');
5843 NETDATA.alarms.notifications = true;
5845 if(Notification.permission === 'default')
5846 Notification.requestPermission();
5852 // ----------------------------------------------------------------------------------------------------------------
5853 // Registry of netdata hosts
5855 NETDATA.registry = {
5856 server: null, // the netdata registry server
5857 person_guid: null, // the unique ID of this browser / user
5858 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5859 hostname: null, // the hostname of the netdata server that served dashboard.js
5860 machines: null, // the user's other URLs
5861 machines_array: null, // the user's other URLs in an array
5864 parsePersonUrls: function(person_urls) {
5865 // console.log(person_urls);
5866 NETDATA.registry.person_urls = person_urls;
5869 NETDATA.registry.machines = {};
5870 NETDATA.registry.machines_array = new Array();
5872 var now = new Date().getTime();
5873 var apu = person_urls;
5876 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5877 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5883 accesses: apu[i][3],
5885 alternate_urls: new Array()
5887 obj.alternate_urls.push(apu[i][1]);
5889 NETDATA.registry.machines[apu[i][0]] = obj;
5890 NETDATA.registry.machines_array.push(obj);
5893 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5895 var pu = NETDATA.registry.machines[apu[i][0]];
5896 if(pu.last_t < apu[i][2]) {
5898 pu.last_t = apu[i][2];
5899 pu.name = apu[i][4];
5901 pu.accesses += apu[i][3];
5902 pu.alternate_urls.push(apu[i][1]);
5907 if(typeof netdataRegistryCallback === 'function')
5908 netdataRegistryCallback(NETDATA.registry.machines_array);
5912 if(netdataRegistry !== true) return;
5914 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5916 NETDATA.registry.server = data.registry;
5917 NETDATA.registry.machine_guid = data.machine_guid;
5918 NETDATA.registry.hostname = data.hostname;
5920 NETDATA.registry.access(2, function (person_urls) {
5921 NETDATA.registry.parsePersonUrls(person_urls);
5928 hello: function(host, callback) {
5929 while(host.slice(-1) === '/')
5930 host = host.substring(0, host.length - 1);
5932 // send HELLO to a netdata server:
5933 // 1. verifies the server is reachable
5934 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5936 url: host + '/api/v1/registry?action=hello',
5939 xhrFields: { withCredentials: true } // required for the cookie
5941 .done(function(data) {
5942 if(typeof data.status !== 'string' || data.status !== 'ok') {
5943 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5947 if(typeof callback === 'function')
5951 NETDATA.error(407, host);
5953 if(typeof callback === 'function')
5958 access: function(max_redirects, callback) {
5959 // send ACCESS to a netdata registry:
5960 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5961 // 2. it responds with a list of netdata servers we know
5962 // the registry identifies us using a cookie it sets the first time we access it
5963 // the registry may respond with a redirect URL to send us to another registry
5965 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),
5968 xhrFields: { withCredentials: true } // required for the cookie
5970 .done(function(data) {
5971 var redirect = null;
5972 if(typeof data.registry === 'string')
5973 redirect = data.registry;
5975 if(typeof data.status !== 'string' || data.status !== 'ok') {
5976 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5981 if(redirect !== null && max_redirects > 0) {
5982 NETDATA.registry.server = redirect;
5983 NETDATA.registry.access(max_redirects - 1, callback);
5986 if(typeof callback === 'function')
5991 if(typeof data.person_guid === 'string')
5992 NETDATA.registry.person_guid = data.person_guid;
5994 if(typeof callback === 'function')
5995 callback(data.urls);
5999 NETDATA.error(410, NETDATA.registry.server);
6001 if(typeof callback === 'function')
6006 delete: function(delete_url, callback) {
6007 // send DELETE to a netdata registry:
6009 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),
6012 xhrFields: { withCredentials: true } // required for the cookie
6014 .done(function(data) {
6015 if(typeof data.status !== 'string' || data.status !== 'ok') {
6016 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6020 if(typeof callback === 'function')
6024 NETDATA.error(412, NETDATA.registry.server);
6026 if(typeof callback === 'function')
6031 switch: function(new_person_guid, callback) {
6034 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,
6037 xhrFields: { withCredentials: true } // required for the cookie
6039 .done(function(data) {
6040 if(typeof data.status !== 'string' || data.status !== 'ok') {
6041 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6045 if(typeof callback === 'function')
6049 NETDATA.error(414, NETDATA.registry.server);
6051 if(typeof callback === 'function')
6057 // ----------------------------------------------------------------------------------------------------------------
6060 NETDATA.errorReset();
6061 NETDATA.loadRequiredCSS(0);
6063 NETDATA._loadjQuery(function() {
6064 NETDATA.loadRequiredJs(0, function() {
6065 if(typeof $().emulateTransitionEnd !== 'function') {
6066 // bootstrap is not available
6067 NETDATA.options.current.show_help = false;
6070 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6071 if(NETDATA.options.debug.main_loop === true)
6072 console.log('starting chart refresh thread');
6079 // window.NETDATA = NETDATA;
6080 // })(window, document);