1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
32 //(function(window, document, undefined) {
34 // ------------------------------------------------------------------------
35 // compatibility fixes
37 // fix IE issue with console
38 if(!window.console) { window.console = { log: function(){} }; }
40 // if string.endsWith is not defined, define it
41 if(typeof String.prototype.endsWith !== 'function') {
42 String.prototype.endsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(-s.length) === s;
48 // if string.startsWith is not defined, define it
49 if(typeof String.prototype.startsWith !== 'function') {
50 String.prototype.startsWith = function(s) {
51 if(s.length > this.length) return false;
52 return this.slice(s.length) === s;
57 var NETDATA = window.NETDATA || {};
59 NETDATA.name2id = function(s) {
68 // ----------------------------------------------------------------------------------------------------------------
69 // Detect the netdata server
71 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73 NETDATA._scriptSource = function() {
76 if(typeof document.currentScript !== 'undefined') {
77 script = document.currentScript;
80 var all_scripts = document.getElementsByTagName('script');
81 script = all_scripts[all_scripts.length - 1];
84 if (typeof script.getAttribute.length !== 'undefined')
87 script = script.getAttribute('src', -1);
92 if(typeof netdataServer !== 'undefined')
93 NETDATA.serverDefault = netdataServer;
95 var s = NETDATA._scriptSource();
96 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
98 console.log('WARNING: Cannot detect the URL of the netdata server.');
99 NETDATA.serverDefault = null;
103 if(NETDATA.serverDefault === null)
104 NETDATA.serverDefault = '';
105 else if(NETDATA.serverDefault.slice(-1) !== '/')
106 NETDATA.serverDefault += '/';
108 // default URLs for all the external files we need
109 // make them RELATIVE so that the whole thing can also be
110 // installed under a web server
111 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-3.1.1.min.js';
112 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
113 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
114 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
115 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
116 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-f6ec7be.js';
117 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
118 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
119 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
120 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
121 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
122 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
123 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
124 NETDATA.google_js = 'https://www.google.com/jsapi';
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.min.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v57',
130 background: '#FFFFFF',
131 foreground: '#000000',
134 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
135 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
136 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
137 '#329262', '#3B3EAC' ],
138 easypiechart_track: '#f0f0f0',
139 easypiechart_scale: '#dfe0e0',
140 gauge_pointer: '#C0C0C0',
141 gauge_stroke: '#F0F0F0',
142 gauge_gradient: false
145 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v57',
147 background: '#272b30',
148 foreground: '#C8C8C8',
151 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
152 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
153 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
154 '#a6a479', '#a66da8' ],
156 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
157 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
158 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
159 '#329262', '#3B3EFF' ],
160 easypiechart_track: '#373b40',
161 easypiechart_scale: '#373b40',
162 gauge_pointer: '#474b50',
163 gauge_stroke: '#373b40',
164 gauge_gradient: false
168 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169 NETDATA.themes.current = NETDATA.themes[netdataTheme];
171 NETDATA.themes.current = NETDATA.themes.white;
173 NETDATA.colors = NETDATA.themes.current.colors;
175 // these are the colors Google Charts are using
176 // we have them here to attempt emulate their look and feel on the other chart libraries
177 // http://there4.io/2012/05/02/google-chart-color-list/
178 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
182 // an alternative set
183 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
185 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
187 if(typeof netdataShowHelp === 'undefined')
188 netdataShowHelp = true;
190 if(typeof netdataShowAlarms === 'undefined')
191 netdataShowAlarms = false;
193 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194 netdataRegistryAfterMs = 1500;
196 if(typeof netdataRegistry === 'undefined') {
197 // backward compatibility
198 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199 netdataRegistry = true;
201 netdataRegistry = false;
203 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204 netdataRegistry = true;
206 // ----------------------------------------------------------------------------------------------------------------
207 // the defaults for all charts
209 // if the user does not specify any of these, the following will be used
211 NETDATA.chartDefaults = {
212 host: NETDATA.serverDefault, // the server to get data from
213 width: '100%', // the chart width - can be null
214 height: '100%', // the chart height - can be null
215 min_width: null, // the chart minimum width - can be null
216 library: 'dygraph', // the graphing library to use
217 method: 'average', // the grouping method
218 before: 0, // panning
219 after: -600, // panning
220 pixels_per_point: 1, // the detail of the chart
221 fill_luminance: 0.8 // luminance of colors in solit areas
224 // ----------------------------------------------------------------------------------------------------------------
228 pauseCallback: null, // a callback when we are really paused
230 pause: false, // when enabled we don't auto-refresh the charts
232 targets: null, // an array of all the state objects that are
233 // currently active (independently of their
234 // viewport visibility)
236 updated_dom: true, // when true, the DOM has been updated with
237 // new elements we have to check.
239 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
240 // rendering charts continiously.
241 // used with .current.fast_render_timeframe
243 page_is_visible: true, // when true, this page is visible
245 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
246 // auto-refresher for some time (when a chart is
247 // performing pan or zoom, we need to stop refreshing
248 // all other charts, to have the maximum speed for
249 // rendering the chart that is panned or zoomed).
250 // Used with .current.global_pan_sync_time
252 last_resized: new Date().getTime(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false,
333 setOptionCallback: function() { ; }
341 chart_data_url: false,
342 chart_errors: false, // FIXME
350 NETDATA.statistics = {
353 refreshes_active_max: 0
357 // ----------------------------------------------------------------------------------------------------------------
358 // local storage options
360 NETDATA.localStorage = {
363 callback: {} // only used for resetting back to defaults
366 NETDATA.localStorageGet = function(key, def, callback) {
369 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
370 NETDATA.localStorage.default[key.toString()] = def;
371 NETDATA.localStorage.callback[key.toString()] = callback;
374 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
376 // console.log('localStorage: loading "' + key.toString() + '"');
377 ret = localStorage.getItem(key.toString());
378 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
379 if(ret === null || ret === 'undefined') {
380 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
381 localStorage.setItem(key.toString(), JSON.stringify(def));
385 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
386 ret = JSON.parse(ret);
387 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
391 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
396 if(typeof ret === 'undefined' || ret === 'undefined') {
397 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
401 NETDATA.localStorage.current[key.toString()] = ret;
405 NETDATA.localStorageSet = function(key, value, callback) {
406 if(typeof value === 'undefined' || value === 'undefined') {
407 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
410 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
411 NETDATA.localStorage.default[key.toString()] = value;
412 NETDATA.localStorage.current[key.toString()] = value;
413 NETDATA.localStorage.callback[key.toString()] = callback;
416 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
417 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
419 localStorage.setItem(key.toString(), JSON.stringify(value));
422 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
426 NETDATA.localStorage.current[key.toString()] = value;
430 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
432 if(typeof obj[i] === 'object') {
433 //console.log('object ' + prefix + '.' + i.toString());
434 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
438 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
442 NETDATA.setOption = function(key, value) {
443 if(key.toString() === 'setOptionCallback') {
444 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
445 NETDATA.options.current[key.toString()] = value;
446 NETDATA.options.current.setOptionCallback();
449 else if(NETDATA.options.current[key.toString()] !== value) {
450 var name = 'options.' + key.toString();
452 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
453 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
455 //console.log(NETDATA.localStorage);
456 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
457 //console.log(NETDATA.options);
458 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
460 if(typeof NETDATA.options.current.setOptionCallback === 'function')
461 NETDATA.options.current.setOptionCallback();
467 NETDATA.getOption = function(key) {
468 return NETDATA.options.current[key.toString()];
471 // read settings from local storage
472 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
474 // always start with this option enabled.
475 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
477 NETDATA.resetOptions = function() {
478 for(var i in NETDATA.localStorage.default) {
479 var a = i.split('.');
481 if(a[0] === 'options') {
482 if(a[1] === 'setOptionCallback') continue;
483 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
484 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
486 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
488 else if(a[0] === 'chart_heights') {
489 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
490 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
496 // ----------------------------------------------------------------------------------------------------------------
498 if(NETDATA.options.debug.main_loop === true)
499 console.log('welcome to NETDATA');
501 NETDATA.onresize = function() {
502 NETDATA.options.last_resized = new Date().getTime();
506 NETDATA.onscroll = function() {
507 // console.log('onscroll');
509 NETDATA.options.last_page_scroll = new Date().getTime();
510 NETDATA.options.auto_refresher_stop_until = 0;
512 if(NETDATA.options.targets === null) return;
514 // when the user scrolls he sees that we have
515 // hidden all the not-visible charts
516 // using this little function we try to switch
517 // the charts back to visible quickly
518 var targets = NETDATA.options.targets;
519 var len = targets.length;
520 if(NETDATA.options.abort_ajax_on_scroll === true) {
522 if (targets[len]._updating === true) {
523 if (typeof targets[len].xhr !== 'undefined') {
524 targets[len].xhr.abort();
525 targets[len].running = false;
526 targets[len]._updating = false;
528 targets[len].isVisible();
534 targets[len].isVisible();
538 window.onresize = NETDATA.onresize;
539 window.onscroll = NETDATA.onscroll;
541 // ----------------------------------------------------------------------------------------------------------------
544 NETDATA.errorCodes = {
545 100: { message: "Cannot load chart library", alert: true },
546 101: { message: "Cannot load jQuery", alert: true },
547 402: { message: "Chart library not found", alert: false },
548 403: { message: "Chart library not enabled/is failed", alert: false },
549 404: { message: "Chart not found", alert: false },
550 405: { message: "Cannot download charts index from server", alert: true },
551 406: { message: "Invalid charts index downloaded from server", alert: true },
552 407: { message: "Cannot HELLO netdata server", alert: false },
553 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
554 409: { message: "Cannot ACCESS netdata registry", alert: false },
555 410: { message: "Netdata registry ACCESS failed", alert: false },
556 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
557 412: { message: "Netdata registry DELETE failed", alert: false },
558 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
559 414: { message: "Netdata registry SWITCH failed", alert: false },
560 415: { message: "Netdata alarms download failed", alert: false },
561 416: { message: "Netdata alarms log download failed", alert: false },
562 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
563 418: { message: "Netdata registry SEARCH failed", alert: false }
565 NETDATA.errorLast = {
571 NETDATA.error = function(code, msg) {
572 NETDATA.errorLast.code = code;
573 NETDATA.errorLast.message = msg;
574 NETDATA.errorLast.datetime = new Date().getTime();
576 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
579 if(typeof netdataErrorCallback === 'function') {
580 ret = netdataErrorCallback('system', code, msg);
583 if(ret && NETDATA.errorCodes[code].alert)
584 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
587 NETDATA.errorReset = function() {
588 NETDATA.errorLast.code = 0;
589 NETDATA.errorLast.message = "You are doing fine!";
590 NETDATA.errorLast.datetime = 0;
593 // ----------------------------------------------------------------------------------------------------------------
596 // When multiple charts need the same chart, we avoid downloading it
597 // multiple times (and having it in browser memory multiple time)
598 // by using this registry.
600 // Every time we download a chart definition, we save it here with .add()
601 // Then we try to get it back with .get(). If that fails, we download it.
603 NETDATA.chartRegistry = {
606 fixid: function(id) {
607 return id.replace(/:/g, "_").replace(/\//g, "_");
610 add: function(host, id, data) {
611 host = this.fixid(host);
614 if(typeof this.charts[host] === 'undefined')
615 this.charts[host] = {};
617 //console.log('added ' + host + '/' + id);
618 this.charts[host][id] = data;
621 get: function(host, id) {
622 host = this.fixid(host);
625 if(typeof this.charts[host] === 'undefined')
628 if(typeof this.charts[host][id] === 'undefined')
631 //console.log('cached ' + host + '/' + id);
632 return this.charts[host][id];
635 downloadAll: function(host, callback) {
636 while(host.slice(-1) === '/')
637 host = host.substring(0, host.length - 1);
642 url: host + '/api/v1/charts',
645 xhrFields: { withCredentials: true } // required for the cookie
647 .done(function(data) {
649 var h = NETDATA.chartRegistry.fixid(host);
650 self.charts[h] = data.charts;
652 else NETDATA.error(406, host + '/api/v1/charts');
654 if(typeof callback === 'function')
658 NETDATA.error(405, host + '/api/v1/charts');
660 if(typeof callback === 'function')
666 // ----------------------------------------------------------------------------------------------------------------
667 // Global Pan and Zoom on charts
669 // Using this structure are synchronize all the charts, so that
670 // when you pan or zoom one, all others are automatically refreshed
671 // to the same timespan.
673 NETDATA.globalPanAndZoom = {
674 seq: 0, // timestamp ms
675 // every time a chart is panned or zoomed
676 // we set the timestamp here
677 // then we use it as a sequence number
678 // to find if other charts are syncronized
681 master: null, // the master chart (state), to which all others
684 force_before_ms: null, // the timespan to sync all other charts
685 force_after_ms: null,
690 setMaster: function(state, after, before) {
691 if(NETDATA.options.current.sync_pan_and_zoom === false)
694 if(this.master !== null && this.master !== state)
695 this.master.resetChart(true, true);
697 var now = new Date().getTime();
700 this.force_after_ms = after;
701 this.force_before_ms = before;
702 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
704 if(typeof this.callback === 'function')
705 this.callback(true, after, before);
709 clearMaster: function() {
710 if(this.master !== null) {
711 var st = this.master;
718 this.force_after_ms = null;
719 this.force_before_ms = null;
720 NETDATA.options.auto_refresher_stop_until = 0;
722 if(typeof this.callback === 'function')
723 this.callback(false, 0, 0);
726 // is the given state the master of the global
727 // pan and zoom sync?
728 isMaster: function(state) {
729 if(this.master === state) return true;
733 // are we currently have a global pan and zoom sync?
734 isActive: function() {
735 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
739 // check if a chart, other than the master
740 // needs to be refreshed, due to the global pan and zoom
741 shouldBeAutoRefreshed: function(state) {
742 if(this.master === null || this.seq === 0)
745 //if(state.needsRecreation())
748 if(state.tm.pan_and_zoom_seq === this.seq)
755 // ----------------------------------------------------------------------------------------------------------------
756 // dimensions selection
759 // move color assignment to dimensions, here
761 dimensionStatus = function(parent, label, name_div, value_div, color) {
762 this.enabled = false;
763 this.parent = parent;
765 this.name_div = null;
766 this.value_div = null;
767 this.color = NETDATA.themes.current.foreground;
769 if(parent.selected_count > parent.unselected_count)
770 this.selected = true;
772 this.selected = false;
774 this.setOptions(name_div, value_div, color);
777 dimensionStatus.prototype.invalidate = function() {
778 this.name_div = null;
779 this.value_div = null;
780 this.enabled = false;
783 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
786 if(this.name_div != name_div) {
787 this.name_div = name_div;
788 this.name_div.title = this.label;
789 this.name_div.style.color = this.color;
790 if(this.selected === false)
791 this.name_div.className = 'netdata-legend-name not-selected';
793 this.name_div.className = 'netdata-legend-name selected';
796 if(this.value_div != value_div) {
797 this.value_div = value_div;
798 this.value_div.title = this.label;
799 this.value_div.style.color = this.color;
800 if(this.selected === false)
801 this.value_div.className = 'netdata-legend-value not-selected';
803 this.value_div.className = 'netdata-legend-value selected';
810 dimensionStatus.prototype.setHandler = function() {
811 if(this.enabled === false) return;
815 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
816 this.name_div.onclick = this.value_div.onclick = function(e) {
818 if(ds.isSelected()) {
820 if(e.shiftKey === true || e.ctrlKey === true) {
821 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
824 if(ds.parent.countSelected() === 0)
825 ds.parent.selectAll();
828 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
829 if(ds.parent.countSelected() === 1) {
830 ds.parent.selectAll();
833 ds.parent.selectNone();
839 // this is not selected
840 if(e.shiftKey === true || e.ctrlKey === true) {
841 // control or shift key is pressed -> select this too
845 // no key is pressed -> select only this
846 ds.parent.selectNone();
851 ds.parent.state.redrawChart();
855 dimensionStatus.prototype.select = function() {
856 if(this.enabled === false) return;
858 this.name_div.className = 'netdata-legend-name selected';
859 this.value_div.className = 'netdata-legend-value selected';
860 this.selected = true;
863 dimensionStatus.prototype.unselect = function() {
864 if(this.enabled === false) return;
866 this.name_div.className = 'netdata-legend-name not-selected';
867 this.value_div.className = 'netdata-legend-value hidden';
868 this.selected = false;
871 dimensionStatus.prototype.isSelected = function() {
872 return(this.enabled === true && this.selected === true);
875 // ----------------------------------------------------------------------------------------------------------------
877 dimensionsVisibility = function(state) {
880 this.dimensions = {};
881 this.selected_count = 0;
882 this.unselected_count = 0;
885 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
886 if(typeof this.dimensions[label] === 'undefined') {
888 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
891 this.dimensions[label].setOptions(name_div, value_div, color);
893 return this.dimensions[label];
896 dimensionsVisibility.prototype.dimensionGet = function(label) {
897 return this.dimensions[label];
900 dimensionsVisibility.prototype.invalidateAll = function() {
901 for(var d in this.dimensions)
902 this.dimensions[d].invalidate();
905 dimensionsVisibility.prototype.selectAll = function() {
906 for(var d in this.dimensions)
907 this.dimensions[d].select();
910 dimensionsVisibility.prototype.countSelected = function() {
912 for(var d in this.dimensions)
913 if(this.dimensions[d].isSelected()) i++;
918 dimensionsVisibility.prototype.selectNone = function() {
919 for(var d in this.dimensions)
920 this.dimensions[d].unselect();
923 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
924 var ret = new Array();
925 this.selected_count = 0;
926 this.unselected_count = 0;
928 for(var i = 0, len = array.length; i < len ; i++) {
929 var ds = this.dimensions[array[i]];
930 if(typeof ds === 'undefined') {
931 // console.log(array[i] + ' is not found');
936 if(ds.isSelected()) {
938 this.selected_count++;
942 this.unselected_count++;
946 if(this.selected_count === 0 && this.unselected_count !== 0) {
948 return this.selected2BooleanArray(array);
955 // ----------------------------------------------------------------------------------------------------------------
956 // global selection sync
958 NETDATA.globalSelectionSync = {
965 if(this.state !== null)
966 this.state.globalSelectionSyncStop();
970 if(this.state !== null) {
971 this.state.globalSelectionSyncDelay();
976 // ----------------------------------------------------------------------------------------------------------------
977 // Our state object, where all per-chart values are stored
979 chartState = function(element) {
980 var self = $(element);
981 this.element = element;
984 // all private functions should use 'that', instead of 'this'
988 * show an error instead of the chart
990 var error = function(msg) {
993 if(typeof netdataErrorCallback === 'function') {
994 ret = netdataErrorCallback('chart', that.id, msg);
998 that.element.innerHTML = that.id + ': ' + msg;
999 that.enabled = false;
1000 that.current = that.pan;
1004 // GUID - a unique identifier for the chart
1005 this.uuid = NETDATA.guid();
1007 // string - the name of chart
1008 this.id = self.data('netdata');
1010 // string - the key for localStorage settings
1011 this.settings_id = self.data('id') || null;
1013 // the user given dimensions of the element
1014 this.width = self.data('width') || NETDATA.chartDefaults.width;
1015 this.height = self.data('height') || NETDATA.chartDefaults.height;
1017 if(this.settings_id !== null) {
1018 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1019 // this is the callback that will be called
1020 // if and when the user resets all localStorage variables
1021 // to their defaults
1023 resizeChartToHeight(height);
1027 // string - the netdata server URL, without any path
1028 this.host = self.data('host') || NETDATA.chartDefaults.host;
1030 // make sure the host does not end with /
1031 // all netdata API requests use absolute paths
1032 while(this.host.slice(-1) === '/')
1033 this.host = this.host.substring(0, this.host.length - 1);
1035 // string - the grouping method requested by the user
1036 this.method = self.data('method') || NETDATA.chartDefaults.method;
1038 // the time-range requested by the user
1039 this.after = self.data('after') || NETDATA.chartDefaults.after;
1040 this.before = self.data('before') || NETDATA.chartDefaults.before;
1042 // the pixels per point requested by the user
1043 this.pixels_per_point = self.data('pixels-per-point') || 1;
1044 this.points = self.data('points') || null;
1046 // the dimensions requested by the user
1047 this.dimensions = self.data('dimensions') || null;
1049 // the chart library requested by the user
1050 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1052 // object - the chart library used
1053 this.library = null;
1057 this.colors_assigned = {};
1058 this.colors_available = null;
1060 // the element already created by the user
1061 this.element_message = null;
1063 // the element with the chart
1064 this.element_chart = null;
1066 // the element with the legend of the chart (if created by us)
1067 this.element_legend = null;
1068 this.element_legend_childs = {
1078 this.chart_url = null; // string - the url to download chart info
1079 this.chart = null; // object - the chart as downloaded from the server
1081 this.title = self.data('title') || null; // the title of the chart
1082 this.units = self.data('units') || null; // the units of the chart dimensions
1083 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1085 this.running = false; // boolean - true when the chart is being refreshed now
1086 this.validated = false; // boolean - has the chart been validated?
1087 this.enabled = true; // boolean - is the chart enabled for refresh?
1088 this.paused = false; // boolean - is the chart paused for any reason?
1089 this.selected = false; // boolean - is the chart shown a selection?
1090 this.debug = false; // boolean - console.log() debug info about this chart
1092 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1093 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1094 this.requested_after = null; // milliseconds - the timestamp of the request after param
1095 this.requested_before = null; // milliseconds - the timestamp of the request before param
1096 this.requested_padding = null;
1097 this.view_after = 0;
1098 this.view_before = 0;
1103 force_update_at: 0, // the timestamp to force the update at
1104 force_before_ms: null,
1105 force_after_ms: null
1110 force_update_at: 0, // the timestamp to force the update at
1111 force_before_ms: null,
1112 force_after_ms: null
1117 force_update_at: 0, // the timestamp to force the update at
1118 force_before_ms: null,
1119 force_after_ms: null
1122 // this is a pointer to one of the sub-classes below
1124 this.current = this.auto;
1126 // check the requested library is available
1127 // we don't initialize it here - it will be initialized when
1128 // this chart will be first used
1129 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1130 NETDATA.error(402, that.library_name);
1131 error('chart library "' + that.library_name + '" is not found');
1134 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1135 NETDATA.error(403, that.library_name);
1136 error('chart library "' + that.library_name + '" is not enabled');
1140 that.library = NETDATA.chartLibraries[that.library_name];
1142 // milliseconds - the time the last refresh took
1143 this.refresh_dt_ms = 0;
1145 // if we need to report the rendering speed
1146 // find the element that needs to be updated
1147 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1149 if(refresh_dt_element_name !== null)
1150 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1152 this.refresh_dt_element = null;
1154 this.dimensions_visibility = new dimensionsVisibility(this);
1156 this._updating = false;
1158 // ============================================================================================================
1159 // PRIVATE FUNCTIONS
1161 var createDOM = function() {
1162 if(that.enabled === false) return;
1164 if(that.element_message !== null) that.element_message.innerHTML = '';
1165 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1166 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1168 that.element.innerHTML = '';
1170 that.element_message = document.createElement('div');
1171 that.element_message.className = ' netdata-message hidden';
1172 that.element.appendChild(that.element_message);
1174 that.element_chart = document.createElement('div');
1175 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1176 that.element.appendChild(that.element_chart);
1178 if(that.hasLegend() === true) {
1179 that.element.className = "netdata-container-with-legend";
1180 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1182 that.element_legend = document.createElement('div');
1183 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1184 that.element.appendChild(that.element_legend);
1187 that.element.className = "netdata-container";
1188 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1190 that.element_legend = null;
1192 that.element_legend_childs.series = null;
1194 if(typeof(that.width) === 'string')
1195 $(that.element).css('width', that.width);
1196 else if(typeof(that.width) === 'number')
1197 $(that.element).css('width', that.width + 'px');
1199 if(typeof(that.library.aspect_ratio) === 'undefined') {
1200 if(typeof(that.height) === 'string')
1201 $(that.element).css('height', that.height);
1202 else if(typeof(that.height) === 'number')
1203 $(that.element).css('height', that.height + 'px');
1206 var w = that.element.offsetWidth;
1207 if(w === null || w === 0) {
1208 // the div is hidden
1209 // this will resize the chart when next viewed
1210 that.tm.last_resized = 0;
1213 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1216 if(NETDATA.chartDefaults.min_width !== null)
1217 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1219 that.tm.last_dom_created = new Date().getTime();
1225 * initialize state variables
1226 * destroy all (possibly) created state elements
1227 * create the basic DOM for a chart
1229 var init = function() {
1230 if(that.enabled === false) return;
1232 that.paused = false;
1233 that.selected = false;
1235 that.chart_created = false; // boolean - is the library.create() been called?
1236 that.updates_counter = 0; // numeric - the number of refreshes made so far
1237 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1238 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1241 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1242 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1243 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1245 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1246 last_updated: 0, // the timestamp the chart last updated with data
1247 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1249 // Used with NETDATA.globalPanAndZoom.seq
1250 last_visible_check: 0, // the time we last checked if it is visible
1251 last_resized: 0, // the time the chart was resized
1252 last_hidden: 0, // the time the chart was hidden
1253 last_unhidden: 0, // the time the chart was unhidden
1254 last_autorefreshed: 0 // the time the chart was last refreshed
1257 that.data = null; // the last data as downloaded from the netdata server
1258 that.data_url = 'invalid://'; // string - the last url used to update the chart
1259 that.data_points = 0; // number - the number of points returned from netdata
1260 that.data_after = 0; // milliseconds - the first timestamp of the data
1261 that.data_before = 0; // milliseconds - the last timestamp of the data
1262 that.data_update_every = 0; // milliseconds - the frequency to update the data
1264 that.tm.last_initialized = new Date().getTime();
1267 that.setMode('auto');
1270 var maxMessageFontSize = function() {
1271 // normally we want a font size, as tall as the element
1272 var h = that.element_message.clientHeight;
1274 // but give it some air, 20% let's say, or 5 pixels min
1275 var lost = Math.max(h * 0.2, 5);
1278 // center the text, vertically
1279 var paddingTop = (lost - 5) / 2;
1281 // but check the width too
1282 // it should fit 10 characters in it
1283 var w = that.element_message.clientWidth / 10;
1285 paddingTop += (h - w) / 2;
1289 // and don't make it too huge
1290 // 5% of the screen size is good
1291 if(h > screen.height / 20) {
1292 paddingTop += (h - (screen.height / 20)) / 2;
1293 h = screen.height / 20;
1297 that.element_message.style.fontSize = h.toString() + 'px';
1298 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1301 var showMessage = function(msg) {
1302 that.element_message.className = 'netdata-message';
1303 that.element_message.innerHTML = msg;
1304 that.element_message.style.fontSize = 'x-small';
1305 that.element_message.style.paddingTop = '0px';
1306 that.___messageHidden___ = undefined;
1309 var showMessageIcon = function(icon) {
1310 that.element_message.innerHTML = icon;
1311 that.element_message.className = 'netdata-message icon';
1312 maxMessageFontSize();
1313 that.___messageHidden___ = undefined;
1316 var hideMessage = function() {
1317 if(typeof that.___messageHidden___ === 'undefined') {
1318 that.___messageHidden___ = true;
1319 that.element_message.className = 'netdata-message hidden';
1323 var showRendering = function() {
1325 if(that.chart !== null) {
1326 if(that.chart.chart_type === 'line')
1327 icon = '<i class="fa fa-line-chart"></i>';
1329 icon = '<i class="fa fa-area-chart"></i>';
1332 icon = '<i class="fa fa-area-chart"></i>';
1334 showMessageIcon(icon + ' netdata');
1337 var showLoading = function() {
1338 if(that.chart_created === false) {
1339 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1345 var isHidden = function() {
1346 if(typeof that.___chartIsHidden___ !== 'undefined')
1352 // hide the chart, when it is not visible - called from isVisible()
1353 var hideChart = function() {
1354 // hide it, if it is not already hidden
1355 if(isHidden() === true) return;
1357 if(that.chart_created === true) {
1358 if(NETDATA.options.current.destroy_on_hide === true) {
1359 // we should destroy it
1364 that.element_chart.style.display = 'none';
1365 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1366 that.tm.last_hidden = new Date().getTime();
1369 // This works, but I not sure there are no corner cases somewhere
1370 // so it is commented - if the user has memory issues he can
1371 // set Destroy on Hide for all charts
1372 // that.data = null;
1376 that.___chartIsHidden___ = true;
1379 // unhide the chart, when it is visible - called from isVisible()
1380 var unhideChart = function() {
1381 if(isHidden() === false) return;
1383 that.___chartIsHidden___ = undefined;
1384 that.updates_since_last_unhide = 0;
1386 if(that.chart_created === false) {
1387 // we need to re-initialize it, to show our background
1388 // logo in bootstrap tabs, until the chart loads
1392 that.tm.last_unhidden = new Date().getTime();
1393 that.element_chart.style.display = '';
1394 if(that.element_legend !== null) that.element_legend.style.display = '';
1400 var canBeRendered = function() {
1401 if(isHidden() === true || that.isVisible(true) === false)
1407 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1408 var callChartLibraryUpdateSafely = function(data) {
1411 if(canBeRendered() === false)
1414 if(NETDATA.options.debug.chart_errors === true)
1415 status = that.library.update(that, data);
1418 status = that.library.update(that, data);
1425 if(status === false) {
1426 error('chart failed to be updated as ' + that.library_name);
1433 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1434 var callChartLibraryCreateSafely = function(data) {
1437 if(canBeRendered() === false)
1440 if(NETDATA.options.debug.chart_errors === true)
1441 status = that.library.create(that, data);
1444 status = that.library.create(that, data);
1451 if(status === false) {
1452 error('chart failed to be created as ' + that.library_name);
1456 that.chart_created = true;
1457 that.updates_since_last_creation = 0;
1461 // ----------------------------------------------------------------------------------------------------------------
1464 // resizeChart() - private
1465 // to be called just before the chart library to make sure that
1466 // a properly sized dom is available
1467 var resizeChart = function() {
1468 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1469 if(that.chart_created === false) return;
1471 if(that.needsRecreation()) {
1474 else if(typeof that.library.resize === 'function') {
1475 that.library.resize(that);
1477 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1478 $(that.element_legend_childs.nano).nanoScroller();
1480 maxMessageFontSize();
1483 that.tm.last_resized = new Date().getTime();
1487 // this is the actual chart resize algorithm
1489 // - resize the entire container
1490 // - update the internal states
1491 // - resize the chart as the div changes height
1492 // - update the scrollbar of the legend
1493 var resizeChartToHeight = function(h) {
1495 that.element.style.height = h;
1497 if(that.settings_id !== null)
1498 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1500 var now = new Date().getTime();
1501 NETDATA.options.last_page_scroll = now;
1502 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1505 that.tm.last_resized = 0;
1509 this.resizeHandler = function(e) {
1512 if(typeof this.event_resize === 'undefined'
1513 || this.event_resize.chart_original_w === 'undefined'
1514 || this.event_resize.chart_original_h === 'undefined')
1515 this.event_resize = {
1516 chart_original_w: this.element.clientWidth,
1517 chart_original_h: this.element.clientHeight,
1521 if(e.type === 'touchstart') {
1522 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1523 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1526 this.event_resize.mouse_start_x = e.clientX;
1527 this.event_resize.mouse_start_y = e.clientY;
1530 this.event_resize.chart_start_w = this.element.clientWidth;
1531 this.event_resize.chart_start_h = this.element.clientHeight;
1532 this.event_resize.chart_last_w = this.element.clientWidth;
1533 this.event_resize.chart_last_h = this.element.clientHeight;
1535 var now = new Date().getTime();
1536 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1537 // double click / double tap event
1539 // the optimal height of the chart
1540 // showing the entire legend
1541 var optimal = this.event_resize.chart_last_h
1542 + this.element_legend_childs.content.scrollHeight
1543 - this.element_legend_childs.content.clientHeight;
1545 // if we are not optimal, be optimal
1546 if(this.event_resize.chart_last_h != optimal)
1547 resizeChartToHeight(optimal.toString() + 'px');
1549 // else if we do not have the original height
1550 // reset to the original height
1551 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1552 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1555 this.event_resize.last = now;
1557 // process movement event
1558 document.onmousemove =
1559 document.ontouchmove =
1560 this.element_legend_childs.resize_handler.onmousemove =
1561 this.element_legend_childs.resize_handler.ontouchmove =
1566 case 'mousemove': y = e.clientY; break;
1567 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1571 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1573 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1574 resizeChartToHeight(newH.toString() + 'px');
1575 that.event_resize.chart_last_h = newH;
1580 // process end event
1581 document.onmouseup =
1582 document.ontouchend =
1583 this.element_legend_childs.resize_handler.onmouseup =
1584 this.element_legend_childs.resize_handler.ontouchend =
1586 // remove all the hooks
1587 document.onmouseup =
1588 document.onmousemove =
1589 document.ontouchmove =
1590 document.ontouchend =
1591 that.element_legend_childs.resize_handler.onmousemove =
1592 that.element_legend_childs.resize_handler.ontouchmove =
1593 that.element_legend_childs.resize_handler.onmouseout =
1594 that.element_legend_childs.resize_handler.onmouseup =
1595 that.element_legend_childs.resize_handler.ontouchend =
1598 // allow auto-refreshes
1599 NETDATA.options.auto_refresher_stop_until = 0;
1605 var noDataToShow = function() {
1606 showMessageIcon('<i class="fa fa-warning"></i> empty');
1607 that.legendUpdateDOM();
1608 that.tm.last_autorefreshed = new Date().getTime();
1609 // that.data_update_every = 30 * 1000;
1610 //that.element_chart.style.display = 'none';
1611 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1612 //that.___chartIsHidden___ = true;
1615 // ============================================================================================================
1618 this.error = function(msg) {
1622 this.setMode = function(m) {
1623 if(this.current !== null && this.current.name === m) return;
1626 this.current = this.auto;
1627 else if(m === 'pan')
1628 this.current = this.pan;
1629 else if(m === 'zoom')
1630 this.current = this.zoom;
1632 this.current = this.auto;
1634 this.current.force_update_at = 0;
1635 this.current.force_before_ms = null;
1636 this.current.force_after_ms = null;
1638 this.tm.last_mode_switch = new Date().getTime();
1641 // ----------------------------------------------------------------------------------------------------------------
1642 // global selection sync
1644 // prevent to global selection sync for some time
1645 this.globalSelectionSyncDelay = function(ms) {
1646 if(NETDATA.options.current.sync_selection === false)
1649 if(typeof ms === 'number')
1650 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1652 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1655 // can we globally apply selection sync?
1656 this.globalSelectionSyncAbility = function() {
1657 if(NETDATA.options.current.sync_selection === false)
1660 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1666 this.globalSelectionSyncIsMaster = function() {
1667 if(NETDATA.globalSelectionSync.state === this)
1673 // this chart is the master of the global selection sync
1674 this.globalSelectionSyncBeMaster = function() {
1676 if(this.globalSelectionSyncIsMaster()) {
1677 if(this.debug === true)
1678 this.log('sync: I am the master already.');
1683 if(NETDATA.globalSelectionSync.state) {
1684 if(this.debug === true)
1685 this.log('sync: I am not the sync master. Resetting global sync.');
1687 this.globalSelectionSyncStop();
1690 // become the master
1691 if(this.debug === true)
1692 this.log('sync: becoming sync master.');
1694 this.selected = true;
1695 NETDATA.globalSelectionSync.state = this;
1697 // find the all slaves
1698 var targets = NETDATA.options.targets;
1699 var len = targets.length;
1704 if(this.debug === true)
1705 st.log('sync: not adding me to sync');
1707 else if(st.globalSelectionSyncIsEligible()) {
1708 if(this.debug === true)
1709 st.log('sync: adding to sync as slave');
1711 st.globalSelectionSyncBeSlave();
1715 // this.globalSelectionSyncDelay(100);
1718 // can the chart participate to the global selection sync as a slave?
1719 this.globalSelectionSyncIsEligible = function() {
1720 if(this.enabled === true
1721 && this.library !== null
1722 && typeof this.library.setSelection === 'function'
1723 && this.isVisible() === true
1724 && this.chart_created === true)
1730 // this chart becomes a slave of the global selection sync
1731 this.globalSelectionSyncBeSlave = function() {
1732 if(NETDATA.globalSelectionSync.state !== this)
1733 NETDATA.globalSelectionSync.slaves.push(this);
1736 // sync all the visible charts to the given time
1737 // this is to be called from the chart libraries
1738 this.globalSelectionSync = function(t) {
1739 if(this.globalSelectionSyncAbility() === false) {
1740 if(this.debug === true)
1741 this.log('sync: cannot sync (yet?).');
1746 if(this.globalSelectionSyncIsMaster() === false) {
1747 if(this.debug === true)
1748 this.log('sync: trying to be sync master.');
1750 this.globalSelectionSyncBeMaster();
1752 if(this.globalSelectionSyncAbility() === false) {
1753 if(this.debug === true)
1754 this.log('sync: cannot sync (yet?).');
1760 NETDATA.globalSelectionSync.last_t = t;
1761 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1766 // stop syncing all charts to the given time
1767 this.globalSelectionSyncStop = function() {
1768 if(NETDATA.globalSelectionSync.slaves.length) {
1769 if(this.debug === true)
1770 this.log('sync: cleaning up...');
1772 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1774 if(that.debug === true)
1775 st.log('sync: not adding me to sync stop');
1778 if(that.debug === true)
1779 st.log('sync: removed slave from sync');
1781 st.clearSelection();
1785 NETDATA.globalSelectionSync.last_t = 0;
1786 NETDATA.globalSelectionSync.slaves = [];
1787 NETDATA.globalSelectionSync.state = null;
1790 this.clearSelection();
1793 this.setSelection = function(t) {
1794 if(typeof this.library.setSelection === 'function') {
1795 if(this.library.setSelection(this, t) === true)
1796 this.selected = true;
1798 this.selected = false;
1800 else this.selected = true;
1802 if(this.selected === true && this.debug === true)
1803 this.log('selection set to ' + t.toString());
1805 return this.selected;
1808 this.clearSelection = function() {
1809 if(this.selected === true) {
1810 if(typeof this.library.clearSelection === 'function') {
1811 if(this.library.clearSelection(this) === true)
1812 this.selected = false;
1814 this.selected = true;
1816 else this.selected = false;
1818 if(this.selected === false && this.debug === true)
1819 this.log('selection cleared');
1824 return this.selected;
1827 // find if a timestamp (ms) is shown in the current chart
1828 this.timeIsVisible = function(t) {
1829 if(t >= this.data_after && t <= this.data_before)
1834 this.calculateRowForTime = function(t) {
1835 if(this.timeIsVisible(t) === false) return -1;
1836 return Math.floor((t - this.data_after) / this.data_update_every);
1839 // ----------------------------------------------------------------------------------------------------------------
1842 this.log = function(msg) {
1843 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1846 this.pauseChart = function() {
1847 if(this.paused === false) {
1848 if(this.debug === true)
1849 this.log('pauseChart()');
1855 this.unpauseChart = function() {
1856 if(this.paused === true) {
1857 if(this.debug === true)
1858 this.log('unpauseChart()');
1860 this.paused = false;
1864 this.resetChart = function(dont_clear_master, dont_update) {
1865 if(this.debug === true)
1866 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1868 if(typeof dont_clear_master === 'undefined')
1869 dont_clear_master = false;
1871 if(typeof dont_update === 'undefined')
1872 dont_update = false;
1874 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1875 if(this.debug === true)
1876 this.log('resetChart() diverting to clearMaster().');
1877 // this will call us back with master === true
1878 NETDATA.globalPanAndZoom.clearMaster();
1882 this.clearSelection();
1884 this.tm.pan_and_zoom_seq = 0;
1886 this.setMode('auto');
1887 this.current.force_update_at = 0;
1888 this.current.force_before_ms = null;
1889 this.current.force_after_ms = null;
1890 this.tm.last_autorefreshed = 0;
1891 this.paused = false;
1892 this.selected = false;
1893 this.enabled = true;
1894 // this.debug = false;
1896 // do not update the chart here
1897 // or the chart will flip-flop when it is the master
1898 // of a selection sync and another chart becomes
1901 if(dont_update !== true && this.isVisible() === true) {
1906 this.updateChartPanOrZoom = function(after, before) {
1907 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1910 if(this.debug === true)
1913 if(before < after) {
1914 if(this.debug === true)
1915 this.log(logme + 'flipped parameters, rejecting it.');
1920 if(typeof this.fixed_min_duration === 'undefined')
1921 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1923 var min_duration = this.fixed_min_duration;
1924 var current_duration = Math.round(this.view_before - this.view_after);
1926 // round the numbers
1927 after = Math.round(after);
1928 before = Math.round(before);
1930 // align them to update_every
1931 // stretching them further away
1932 after -= after % this.data_update_every;
1933 before += this.data_update_every - (before % this.data_update_every);
1935 // the final wanted duration
1936 var wanted_duration = before - after;
1938 // to allow panning, accept just a point below our minimum
1939 if((current_duration - this.data_update_every) < min_duration)
1940 min_duration = current_duration - this.data_update_every;
1942 // we do it, but we adjust to minimum size and return false
1943 // when the wanted size is below the current and the minimum
1945 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1946 if(this.debug === true)
1947 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1949 min_duration = this.fixed_min_duration;
1951 var dt = (min_duration - wanted_duration) / 2;
1954 wanted_duration = before - after;
1958 var tolerance = this.data_update_every * 2;
1959 var movement = Math.abs(before - this.view_before);
1961 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1962 if(this.debug === true)
1963 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);
1967 if(this.current.name === 'auto') {
1968 this.log(logme + 'caller called me with mode: ' + this.current.name);
1969 this.setMode('pan');
1972 if(this.debug === true)
1973 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);
1975 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1976 this.current.force_after_ms = after;
1977 this.current.force_before_ms = before;
1978 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1982 this.legendFormatValue = function(value) {
1983 if(value === null || value === 'undefined') return '-';
1984 if(typeof value !== 'number') return value;
1986 var abs = Math.abs(value);
1987 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1988 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1989 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1990 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1991 return (Math.round(value * 10000) / 10000).toLocaleString();
1994 this.legendSetLabelValue = function(label, value) {
1995 var series = this.element_legend_childs.series[label];
1996 if(typeof series === 'undefined') return;
1997 if(series.value === null && series.user === null) return;
1999 // if the value has not changed, skip DOM update
2000 //if(series.last === value) return;
2003 if(typeof value === 'number') {
2004 var v = Math.abs(value);
2005 s = r = this.legendFormatValue(value);
2007 if(typeof series.last === 'number') {
2008 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2009 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2010 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2012 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2017 series.last = value;
2020 if(series.value !== null) series.value.innerHTML = s;
2021 if(series.user !== null) series.user.innerHTML = r;
2024 this.legendSetDate = function(ms) {
2025 if(typeof ms !== 'number') {
2026 this.legendShowUndefined();
2030 var d = new Date(ms);
2032 if(this.element_legend_childs.title_date)
2033 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2035 if(this.element_legend_childs.title_time)
2036 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2038 if(this.element_legend_childs.title_units)
2039 this.element_legend_childs.title_units.innerHTML = this.units;
2042 this.legendShowUndefined = function() {
2043 if(this.element_legend_childs.title_date)
2044 this.element_legend_childs.title_date.innerHTML = ' ';
2046 if(this.element_legend_childs.title_time)
2047 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2049 if(this.element_legend_childs.title_units)
2050 this.element_legend_childs.title_units.innerHTML = ' ';
2052 if(this.data && this.element_legend_childs.series !== null) {
2053 var labels = this.data.dimension_names;
2054 var i = labels.length;
2056 var label = labels[i];
2058 if(typeof label === 'undefined') continue;
2059 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2060 this.legendSetLabelValue(label, null);
2065 this.legendShowLatestValues = function() {
2066 if(this.chart === null) return;
2067 if(this.selected) return;
2069 if(this.data === null || this.element_legend_childs.series === null) {
2070 this.legendShowUndefined();
2074 var show_undefined = true;
2075 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2076 show_undefined = false;
2078 if(show_undefined) {
2079 this.legendShowUndefined();
2083 this.legendSetDate(this.view_before);
2085 var labels = this.data.dimension_names;
2086 var i = labels.length;
2088 var label = labels[i];
2090 if(typeof label === 'undefined') continue;
2091 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2094 this.legendSetLabelValue(label, null);
2096 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2100 this.legendReset = function() {
2101 this.legendShowLatestValues();
2104 // this should be called just ONCE per dimension per chart
2105 this._chartDimensionColor = function(label) {
2106 if(this.colors === null) this.chartColors();
2108 if(typeof this.colors_assigned[label] === 'undefined') {
2109 if(this.colors_available.length === 0) {
2110 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2111 this.colors_available.push(NETDATA.themes.current.colors[i]);
2114 this.colors_assigned[label] = this.colors_available.shift();
2116 if(this.debug === true)
2117 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2120 if(this.debug === true)
2121 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2124 this.colors.push(this.colors_assigned[label]);
2125 return this.colors_assigned[label];
2128 this.chartColors = function() {
2129 if(this.colors !== null) return this.colors;
2131 this.colors = new Array();
2132 this.colors_available = new Array();
2135 var c = $(this.element).data('colors');
2136 // this.log('read colors: ' + c);
2137 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2138 if(typeof c !== 'string') {
2139 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2146 for(i = 0, len = c.length; i < len ; i++) {
2148 this.colors_available.push(c[i]);
2149 // this.log('adding color: ' + c[i]);
2155 // push all the standard colors too
2156 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2157 this.colors_available.push(NETDATA.themes.current.colors[i]);
2162 this.legendUpdateDOM = function() {
2165 // check that the legend DOM is up to date for the downloaded dimensions
2166 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2167 // this.log('the legend does not have any series - requesting legend update');
2170 else if(this.data === null) {
2171 // this.log('the chart does not have any data - requesting legend update');
2174 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2178 var labels = this.data.dimension_names.toString();
2179 if(labels !== this.element_legend_childs.series.labels_key) {
2182 if(this.debug === true)
2183 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2187 if(needed === false) {
2188 // make sure colors available
2191 // do we have to update the current values?
2192 // we do this, only when the visible chart is current
2193 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2194 if(this.debug === true)
2195 this.log('chart is in latest position... updating values on legend...');
2197 //var labels = this.data.dimension_names;
2198 //var i = labels.length;
2200 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2204 if(this.colors === null) {
2205 // this is the first time we update the chart
2206 // let's assign colors to all dimensions
2207 if(this.library.track_colors() === true)
2208 for(var dim in this.chart.dimensions)
2209 this._chartDimensionColor(this.chart.dimensions[dim].name);
2211 // we will re-generate the colors for the chart
2212 // based on the selected dimensions
2215 if(this.debug === true)
2216 this.log('updating Legend DOM');
2218 // mark all dimensions as invalid
2219 this.dimensions_visibility.invalidateAll();
2221 var genLabel = function(state, parent, dim, name, count) {
2222 var color = state._chartDimensionColor(name);
2224 var user_element = null;
2225 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2226 if(user_id !== null) {
2227 user_element = document.getElementById(user_id) || null;
2228 if(user_element === null)
2229 state.log('Cannot find element with id: ' + user_id);
2232 state.element_legend_childs.series[name] = {
2233 name: document.createElement('span'),
2234 value: document.createElement('span'),
2239 var label = state.element_legend_childs.series[name];
2241 // create the dimension visibility tracking for this label
2242 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2244 var rgb = NETDATA.colorHex2Rgb(color);
2245 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2246 + state.chart.chart_type
2247 + '" style="background-color: '
2248 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2249 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2251 var text = document.createTextNode(' ' + name);
2252 label.name.appendChild(text);
2255 parent.appendChild(document.createElement('br'));
2257 parent.appendChild(label.name);
2258 parent.appendChild(label.value);
2261 var content = document.createElement('div');
2263 if(this.hasLegend()) {
2264 this.element_legend_childs = {
2266 resize_handler: document.createElement('div'),
2267 toolbox: document.createElement('div'),
2268 toolbox_left: document.createElement('div'),
2269 toolbox_right: document.createElement('div'),
2270 toolbox_reset: document.createElement('div'),
2271 toolbox_zoomin: document.createElement('div'),
2272 toolbox_zoomout: document.createElement('div'),
2273 toolbox_volume: document.createElement('div'),
2274 title_date: document.createElement('span'),
2275 title_time: document.createElement('span'),
2276 title_units: document.createElement('span'),
2277 nano: document.createElement('div'),
2279 paneClass: 'netdata-legend-series-pane',
2280 sliderClass: 'netdata-legend-series-slider',
2281 contentClass: 'netdata-legend-series-content',
2282 enabledClass: '__enabled',
2283 flashedClass: '__flashed',
2284 activeClass: '__active',
2286 alwaysVisible: true,
2292 this.element_legend.innerHTML = '';
2294 if(this.library.toolboxPanAndZoom !== null) {
2296 function get_pan_and_zoom_step(event) {
2298 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2300 else if (event.shiftKey)
2301 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2303 else if (event.altKey)
2304 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2307 return NETDATA.options.current.pan_and_zoom_factor;
2310 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2311 this.element.appendChild(this.element_legend_childs.toolbox);
2313 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2314 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2315 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2316 this.element_legend_childs.toolbox_left.onclick = function(e) {
2319 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2320 var before = that.view_before - step;
2321 var after = that.view_after - step;
2322 if(after >= that.netdata_first)
2323 that.library.toolboxPanAndZoom(that, after, before);
2325 if(NETDATA.options.current.show_help === true)
2326 $(this.element_legend_childs.toolbox_left).popover({
2331 placement: 'bottom',
2332 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2334 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>'
2338 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2339 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2340 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2341 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2343 NETDATA.resetAllCharts(that);
2345 if(NETDATA.options.current.show_help === true)
2346 $(this.element_legend_childs.toolbox_reset).popover({
2351 placement: 'bottom',
2352 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2353 title: 'Chart Reset',
2354 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>'
2357 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2358 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2359 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2360 this.element_legend_childs.toolbox_right.onclick = function(e) {
2362 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2363 var before = that.view_before + step;
2364 var after = that.view_after + step;
2365 if(before <= that.netdata_last)
2366 that.library.toolboxPanAndZoom(that, after, before);
2368 if(NETDATA.options.current.show_help === true)
2369 $(this.element_legend_childs.toolbox_right).popover({
2374 placement: 'bottom',
2375 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2377 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>'
2381 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2382 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2383 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2384 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2386 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2387 var before = that.view_before - dt;
2388 var after = that.view_after + dt;
2389 that.library.toolboxPanAndZoom(that, after, before);
2391 if(NETDATA.options.current.show_help === true)
2392 $(this.element_legend_childs.toolbox_zoomin).popover({
2397 placement: 'bottom',
2398 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2399 title: 'Chart Zoom In',
2400 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>'
2403 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2404 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2405 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2406 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2408 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);
2409 var before = that.view_before + dt;
2410 var after = that.view_after - dt;
2412 that.library.toolboxPanAndZoom(that, after, before);
2414 if(NETDATA.options.current.show_help === true)
2415 $(this.element_legend_childs.toolbox_zoomout).popover({
2420 placement: 'bottom',
2421 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2422 title: 'Chart Zoom Out',
2423 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>'
2426 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2427 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2428 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2429 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2430 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2431 //e.preventDefault();
2432 //alert('clicked toolbox_volume on ' + that.id);
2436 this.element_legend_childs.toolbox = null;
2437 this.element_legend_childs.toolbox_left = null;
2438 this.element_legend_childs.toolbox_reset = null;
2439 this.element_legend_childs.toolbox_right = null;
2440 this.element_legend_childs.toolbox_zoomin = null;
2441 this.element_legend_childs.toolbox_zoomout = null;
2442 this.element_legend_childs.toolbox_volume = null;
2445 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2446 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2447 this.element.appendChild(this.element_legend_childs.resize_handler);
2448 if(NETDATA.options.current.show_help === true)
2449 $(this.element_legend_childs.resize_handler).popover({
2454 placement: 'bottom',
2455 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2456 title: 'Chart Resize',
2457 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>'
2461 this.element_legend_childs.resize_handler.onmousedown =
2463 that.resizeHandler(e);
2467 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2468 that.resizeHandler(e);
2471 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2472 this.element_legend.appendChild(this.element_legend_childs.title_date);
2474 this.element_legend.appendChild(document.createElement('br'));
2476 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2477 this.element_legend.appendChild(this.element_legend_childs.title_time);
2479 this.element_legend.appendChild(document.createElement('br'));
2481 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2482 this.element_legend.appendChild(this.element_legend_childs.title_units);
2484 this.element_legend.appendChild(document.createElement('br'));
2486 this.element_legend_childs.nano.className = 'netdata-legend-series';
2487 this.element_legend.appendChild(this.element_legend_childs.nano);
2489 content.className = 'netdata-legend-series-content';
2490 this.element_legend_childs.nano.appendChild(content);
2492 if(NETDATA.options.current.show_help === true)
2493 $(content).popover({
2498 placement: 'bottom',
2499 title: 'Chart Legend',
2500 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2501 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>'
2505 this.element_legend_childs = {
2507 resize_handler: null,
2510 toolbox_right: null,
2511 toolbox_reset: null,
2512 toolbox_zoomin: null,
2513 toolbox_zoomout: null,
2514 toolbox_volume: null,
2525 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2526 if(this.debug === true)
2527 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2529 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2530 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2534 var tmp = new Array();
2535 for(var dim in this.chart.dimensions) {
2536 tmp.push(this.chart.dimensions[dim].name);
2537 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2539 this.element_legend_childs.series.labels_key = tmp.toString();
2540 if(this.debug === true)
2541 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2544 // create a hidden div to be used for hidding
2545 // the original legend of the chart library
2546 var el = document.createElement('div');
2547 if(this.element_legend !== null)
2548 this.element_legend.appendChild(el);
2549 el.style.display = 'none';
2551 this.element_legend_childs.hidden = document.createElement('div');
2552 el.appendChild(this.element_legend_childs.hidden);
2554 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2555 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2557 this.legendShowLatestValues();
2560 this.hasLegend = function() {
2561 if(typeof this.___hasLegendCache___ !== 'undefined')
2562 return this.___hasLegendCache___;
2565 if(this.library && this.library.legend(this) === 'right-side') {
2566 var legend = $(this.element).data('legend') || 'yes';
2567 if(legend === 'yes') leg = true;
2570 this.___hasLegendCache___ = leg;
2574 this.legendWidth = function() {
2575 return (this.hasLegend())?140:0;
2578 this.legendHeight = function() {
2579 return $(this.element).height();
2582 this.chartWidth = function() {
2583 return $(this.element).width() - this.legendWidth();
2586 this.chartHeight = function() {
2587 return $(this.element).height();
2590 this.chartPixelsPerPoint = function() {
2591 // force an options provided detail
2592 var px = this.pixels_per_point;
2594 if(this.library && px < this.library.pixels_per_point(this))
2595 px = this.library.pixels_per_point(this);
2597 if(px < NETDATA.options.current.pixels_per_point)
2598 px = NETDATA.options.current.pixels_per_point;
2603 this.needsRecreation = function() {
2605 this.chart_created === true
2607 && this.library.autoresize() === false
2608 && this.tm.last_resized < NETDATA.options.last_resized
2612 this.chartURL = function() {
2613 var after, before, points_multiplier = 1;
2614 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2615 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2617 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2618 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2619 this.view_after = after * 1000;
2620 this.view_before = before * 1000;
2622 this.requested_padding = null;
2623 points_multiplier = 1;
2625 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2626 this.tm.pan_and_zoom_seq = 0;
2628 before = Math.round(this.current.force_before_ms / 1000);
2629 after = Math.round(this.current.force_after_ms / 1000);
2630 this.view_after = after * 1000;
2631 this.view_before = before * 1000;
2633 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2634 this.requested_padding = Math.round((before - after) / 2);
2635 after -= this.requested_padding;
2636 before += this.requested_padding;
2637 this.requested_padding *= 1000;
2638 points_multiplier = 2;
2641 this.current.force_before_ms = null;
2642 this.current.force_after_ms = null;
2645 this.tm.pan_and_zoom_seq = 0;
2647 before = this.before;
2649 this.view_after = after * 1000;
2650 this.view_before = before * 1000;
2652 this.requested_padding = null;
2653 points_multiplier = 1;
2656 this.requested_after = after * 1000;
2657 this.requested_before = before * 1000;
2659 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2661 // build the data URL
2662 this.data_url = this.host + this.chart.data_url;
2663 this.data_url += "&format=" + this.library.format();
2664 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2665 this.data_url += "&group=" + this.method;
2666 this.data_url += "&options=" + this.library.options(this);
2667 this.data_url += '|jsonwrap';
2669 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2670 this.data_url += '|nonzero';
2672 if(this.append_options !== null)
2673 this.data_url += '|' + this.append_options.toString();
2676 this.data_url += "&after=" + after.toString();
2679 this.data_url += "&before=" + before.toString();
2682 this.data_url += "&dimensions=" + this.dimensions;
2684 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2685 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2688 this.redrawChart = function() {
2689 if(this.data !== null)
2690 this.updateChartWithData(this.data);
2693 this.updateChartWithData = function(data) {
2694 if(this.debug === true)
2695 this.log('updateChartWithData() called.');
2697 // this may force the chart to be re-created
2701 this.updates_counter++;
2702 this.updates_since_last_unhide++;
2703 this.updates_since_last_creation++;
2705 var started = new Date().getTime();
2707 // if the result is JSON, find the latest update-every
2708 this.data_update_every = data.view_update_every * 1000;
2709 this.data_after = data.after * 1000;
2710 this.data_before = data.before * 1000;
2711 this.netdata_first = data.first_entry * 1000;
2712 this.netdata_last = data.last_entry * 1000;
2713 this.data_points = data.points;
2716 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2717 if(this.view_after < this.data_after) {
2718 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2719 this.view_after = this.data_after;
2722 if(this.view_before > this.data_before) {
2723 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2724 this.view_before = this.data_before;
2728 this.view_after = this.data_after;
2729 this.view_before = this.data_before;
2732 if(this.debug === true) {
2733 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2735 if(this.current.force_after_ms)
2736 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2738 this.log('STATUS: forced : unset');
2740 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2741 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2742 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2743 this.log('STATUS: points : ' + (this.data_points).toString());
2746 if(this.data_points === 0) {
2751 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2752 if(this.debug === true)
2753 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2755 this.chart_created = false;
2758 // check and update the legend
2759 this.legendUpdateDOM();
2761 if(this.chart_created === true
2762 && typeof this.library.update === 'function') {
2764 if(this.debug === true)
2765 this.log('updating chart...');
2767 if(callChartLibraryUpdateSafely(data) === false)
2771 if(this.debug === true)
2772 this.log('creating chart...');
2774 if(callChartLibraryCreateSafely(data) === false)
2778 this.legendShowLatestValues();
2779 if(this.selected === true)
2780 NETDATA.globalSelectionSync.stop();
2782 // update the performance counters
2783 var now = new Date().getTime();
2784 this.tm.last_updated = now;
2786 // don't update last_autorefreshed if this chart is
2787 // forced to be updated with global PanAndZoom
2788 if(NETDATA.globalPanAndZoom.isActive())
2789 this.tm.last_autorefreshed = 0;
2791 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2792 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2794 this.tm.last_autorefreshed = now;
2797 this.refresh_dt_ms = now - started;
2798 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2800 if(this.refresh_dt_element !== null)
2801 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2804 this.updateChart = function(callback) {
2805 if(this.debug === true)
2806 this.log('updateChart() called.');
2808 if(this._updating === true) {
2809 if(this.debug === true)
2810 this.log('I am already updating...');
2812 if(typeof callback === 'function') callback();
2816 // due to late initialization of charts and libraries
2817 // we need to check this too
2818 if(this.enabled === false) {
2819 if(this.debug === true)
2820 this.log('I am not enabled');
2822 if(typeof callback === 'function') callback();
2826 if(canBeRendered() === false) {
2827 if(typeof callback === 'function') callback();
2831 if(this.chart === null) {
2832 this.getChart(function() { that.updateChart(callback); });
2836 if(this.library.initialized === false) {
2837 if(this.library.enabled === true) {
2838 this.library.initialize(function() { that.updateChart(callback); });
2842 error('chart library "' + this.library_name + '" is not available.');
2843 if(typeof callback === 'function') callback();
2848 this.clearSelection();
2851 if(this.debug === true)
2852 this.log('updating from ' + this.data_url);
2854 NETDATA.statistics.refreshes_total++;
2855 NETDATA.statistics.refreshes_active++;
2857 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2858 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2860 this._updating = true;
2862 this.xhr = $.ajax( {
2866 xhrFields: { withCredentials: true } // required for the cookie
2868 .done(function(data) {
2869 that.xhr = undefined;
2871 if(that.debug === true)
2872 that.log('data received. updating chart.');
2874 that.updateChartWithData(data);
2876 .fail(function(msg) {
2877 that.xhr = undefined;
2879 if(msg.statusText !== 'abort')
2880 error('data download failed for url: ' + that.data_url);
2882 .always(function() {
2883 that.xhr = undefined;
2885 NETDATA.statistics.refreshes_active--;
2886 that._updating = false;
2887 if(typeof callback === 'function') callback();
2893 this.isVisible = function(nocache) {
2894 if(typeof nocache === 'undefined')
2897 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2899 // caching - we do not evaluate the charts visibility
2900 // if the page has not been scrolled since the last check
2901 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2902 return this.___isVisible___;
2904 this.tm.last_visible_check = new Date().getTime();
2906 var wh = window.innerHeight;
2907 var x = this.element.getBoundingClientRect();
2911 if(x.width === 0 || x.height === 0) {
2913 this.___isVisible___ = false;
2914 return this.___isVisible___;
2917 if(x.top < 0 && -x.top > x.height) {
2918 // the chart is entirely above
2919 ret = -x.top - x.height;
2921 else if(x.top > wh) {
2922 // the chart is entirely below
2926 if(ret > tolerance) {
2927 // the chart is too far
2930 this.___isVisible___ = false;
2931 return this.___isVisible___;
2934 // the chart is inside or very close
2937 this.___isVisible___ = true;
2938 return this.___isVisible___;
2942 this.isAutoRefreshable = function() {
2943 return (this.current.autorefresh);
2946 this.canBeAutoRefreshed = function() {
2947 var now = new Date().getTime();
2949 if(this.running === true) {
2950 if(this.debug === true)
2951 this.log('I am already running');
2956 if(this.enabled === false) {
2957 if(this.debug === true)
2958 this.log('I am not enabled');
2963 if(this.library === null || this.library.enabled === false) {
2964 error('charting library "' + this.library_name + '" is not available');
2965 if(this.debug === true)
2966 this.log('My chart library ' + this.library_name + ' is not available');
2971 if(this.isVisible() === false) {
2972 if(NETDATA.options.debug.visibility === true || this.debug === true)
2973 this.log('I am not visible');
2978 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2979 if(this.debug === true)
2980 this.log('timed force update detected - allowing this update');
2982 this.current.force_update_at = 0;
2986 if(this.isAutoRefreshable() === true) {
2987 // allow the first update, even if the page is not visible
2988 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2989 if(NETDATA.options.debug.focus === true || this.debug === true)
2990 this.log('canBeAutoRefreshed(): page does not have focus');
2995 if(this.needsRecreation() === true) {
2996 if(this.debug === true)
2997 this.log('canBeAutoRefreshed(): needs re-creation.');
3002 // options valid only for autoRefresh()
3003 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3004 if(NETDATA.globalPanAndZoom.isActive()) {
3005 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3006 if(this.debug === true)
3007 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3012 if(this.debug === true)
3013 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3019 if(this.selected === true) {
3020 if(this.debug === true)
3021 this.log('canBeAutoRefreshed(): I have a selection in place.');
3026 if(this.paused === true) {
3027 if(this.debug === true)
3028 this.log('canBeAutoRefreshed(): I am paused.');
3033 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3034 if(this.debug === true)
3035 this.log('canBeAutoRefreshed(): It is time to update me.');
3045 this.autoRefresh = function(callback) {
3046 if(this.canBeAutoRefreshed() === true && this.running === false) {
3049 state.running = true;
3050 state.updateChart(function() {
3051 state.running = false;
3053 if(typeof callback !== 'undefined')
3058 if(typeof callback !== 'undefined')
3063 this._defaultsFromDownloadedChart = function(chart) {
3065 this.chart_url = chart.url;
3066 this.data_update_every = chart.update_every * 1000;
3067 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3068 this.tm.last_info_downloaded = new Date().getTime();
3070 if(this.title === null)
3071 this.title = chart.title;
3073 if(this.units === null)
3074 this.units = chart.units;
3077 // fetch the chart description from the netdata server
3078 this.getChart = function(callback) {
3079 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3081 this._defaultsFromDownloadedChart(this.chart);
3082 if(typeof callback === 'function') callback();
3085 this.chart_url = "/api/v1/chart?chart=" + this.id;
3087 if(this.debug === true)
3088 this.log('downloading ' + this.chart_url);
3091 url: this.host + this.chart_url,
3094 xhrFields: { withCredentials: true } // required for the cookie
3096 .done(function(chart) {
3097 chart.url = that.chart_url;
3098 that._defaultsFromDownloadedChart(chart);
3099 NETDATA.chartRegistry.add(that.host, that.id, chart);
3102 NETDATA.error(404, that.chart_url);
3103 error('chart not found on url "' + that.chart_url + '"');
3105 .always(function() {
3106 if(typeof callback === 'function') callback();
3111 // ============================================================================================================
3117 NETDATA.resetAllCharts = function(state) {
3118 // first clear the global selection sync
3119 // to make sure no chart is in selected state
3120 state.globalSelectionSyncStop();
3122 // there are 2 possibilities here
3123 // a. state is the global Pan and Zoom master
3124 // b. state is not the global Pan and Zoom master
3126 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3129 // clear the global Pan and Zoom
3130 // this will also refresh the master
3131 // and unblock any charts currently mirroring the master
3132 NETDATA.globalPanAndZoom.clearMaster();
3134 // if we were not the master, reset our status too
3135 // this is required because most probably the mouse
3136 // is over this chart, blocking it from auto-refreshing
3137 if(master === false && (state.paused === true || state.selected === true))
3141 // get or create a chart state, given a DOM element
3142 NETDATA.chartState = function(element) {
3143 var state = $(element).data('netdata-state-object') || null;
3144 if(state === null) {
3145 state = new chartState(element);
3146 $(element).data('netdata-state-object', state);
3151 // ----------------------------------------------------------------------------------------------------------------
3152 // Library functions
3154 // Load a script without jquery
3155 // This is used to load jquery - after it is loaded, we use jquery
3156 NETDATA._loadjQuery = function(callback) {
3157 if(typeof jQuery === 'undefined') {
3158 if(NETDATA.options.debug.main_loop === true)
3159 console.log('loading ' + NETDATA.jQuery);
3161 var script = document.createElement('script');
3162 script.type = 'text/javascript';
3163 script.async = true;
3164 script.src = NETDATA.jQuery;
3166 // script.onabort = onError;
3167 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3168 if(typeof callback === "function")
3169 script.onload = callback;
3171 var s = document.getElementsByTagName('script')[0];
3172 s.parentNode.insertBefore(script, s);
3174 else if(typeof callback === "function")
3178 NETDATA._loadCSS = function(filename) {
3179 // don't use jQuery here
3180 // styles are loaded before jQuery
3181 // to eliminate showing an unstyled page to the user
3183 var fileref = document.createElement("link");
3184 fileref.setAttribute("rel", "stylesheet");
3185 fileref.setAttribute("type", "text/css");
3186 fileref.setAttribute("href", filename);
3188 if (typeof fileref !== 'undefined')
3189 document.getElementsByTagName("head")[0].appendChild(fileref);
3192 NETDATA.colorHex2Rgb = function(hex) {
3193 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3194 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3195 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3196 return r + r + g + g + b + b;
3199 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3201 r: parseInt(result[1], 16),
3202 g: parseInt(result[2], 16),
3203 b: parseInt(result[3], 16)
3207 NETDATA.colorLuminance = function(hex, lum) {
3208 // validate hex string
3209 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3211 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3215 // convert to decimal and change luminosity
3216 var rgb = "#", c, i;
3217 for (i = 0; i < 3; i++) {
3218 c = parseInt(hex.substr(i*2,2), 16);
3219 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3220 rgb += ("00"+c).substr(c.length);
3226 NETDATA.guid = function() {
3228 return Math.floor((1 + Math.random()) * 0x10000)
3233 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3236 NETDATA.zeropad = function(x) {
3237 if(x > -10 && x < 10) return '0' + x.toString();
3238 else return x.toString();
3241 // user function to signal us the DOM has been
3243 NETDATA.updatedDom = function() {
3244 NETDATA.options.updated_dom = true;
3247 NETDATA.ready = function(callback) {
3248 NETDATA.options.pauseCallback = callback;
3251 NETDATA.pause = function(callback) {
3252 if(NETDATA.options.pause === true)
3255 NETDATA.options.pauseCallback = callback;
3258 NETDATA.unpause = function() {
3259 NETDATA.options.pauseCallback = null;
3260 NETDATA.options.updated_dom = true;
3261 NETDATA.options.pause = false;
3264 // ----------------------------------------------------------------------------------------------------------------
3266 // this is purely sequencial charts refresher
3267 // it is meant to be autonomous
3268 NETDATA.chartRefresherNoParallel = function(index) {
3269 if(NETDATA.options.debug.mail_loop === true)
3270 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3272 if(NETDATA.options.updated_dom === true) {
3273 // the dom has been updated
3274 // get the dom parts again
3275 NETDATA.parseDom(NETDATA.chartRefresher);
3278 if(index >= NETDATA.options.targets.length) {
3279 if(NETDATA.options.debug.main_loop === true)
3280 console.log('waiting to restart main loop...');
3282 NETDATA.options.auto_refresher_fast_weight = 0;
3284 setTimeout(function() {
3285 NETDATA.chartRefresher();
3286 }, NETDATA.options.current.idle_between_loops);
3289 var state = NETDATA.options.targets[index];
3291 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3292 if(NETDATA.options.debug.main_loop === true)
3293 console.log('fast rendering...');
3295 state.autoRefresh(function() {
3296 NETDATA.chartRefresherNoParallel(++index);
3300 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3301 NETDATA.options.auto_refresher_fast_weight = 0;
3303 setTimeout(function() {
3304 state.autoRefresh(function() {
3305 NETDATA.chartRefresherNoParallel(++index);
3307 }, NETDATA.options.current.idle_between_charts);
3312 // this is part of the parallel refresher
3313 // its cause is to refresh sequencially all the charts
3314 // that depend on chart library initialization
3315 // it will call the parallel refresher back
3316 // as soon as it sees a chart that its chart library
3318 NETDATA.chartRefresher_uninitialized = function() {
3319 if(NETDATA.options.updated_dom === true) {
3320 // the dom has been updated
3321 // get the dom parts again
3322 NETDATA.parseDom(NETDATA.chartRefresher);
3326 if(NETDATA.options.sequencial.length === 0)
3327 NETDATA.chartRefresher();
3329 var state = NETDATA.options.sequencial.pop();
3330 if(state.library.initialized === true)
3331 NETDATA.chartRefresher();
3333 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3337 NETDATA.chartRefresherWaitTime = function() {
3338 return NETDATA.options.current.idle_parallel_loops;
3341 // the default refresher
3342 // it will create 2 sets of charts:
3343 // - the ones that can be refreshed in parallel
3344 // - the ones that depend on something else
3345 // the first set will be executed in parallel
3346 // the second will be given to NETDATA.chartRefresher_uninitialized()
3347 NETDATA.chartRefresher = function() {
3348 // console.log('auto-refresher...');
3350 if(NETDATA.options.pause === true) {
3351 // console.log('auto-refresher is paused');
3352 setTimeout(NETDATA.chartRefresher,
3353 NETDATA.chartRefresherWaitTime());
3357 if(typeof NETDATA.options.pauseCallback === 'function') {
3358 // console.log('auto-refresher is calling pauseCallback');
3359 NETDATA.options.pause = true;
3360 NETDATA.options.pauseCallback();
3361 NETDATA.chartRefresher();
3365 if(NETDATA.options.current.parallel_refresher === false) {
3366 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3367 NETDATA.chartRefresherNoParallel(0);
3371 if(NETDATA.options.updated_dom === true) {
3372 // the dom has been updated
3373 // get the dom parts again
3374 // console.log('auto-refresher is calling parseDom()');
3375 NETDATA.parseDom(NETDATA.chartRefresher);
3379 var parallel = new Array();
3380 var targets = NETDATA.options.targets;
3381 var len = targets.length;
3384 state = targets[len];
3385 if(state.isVisible() === false || state.running === true)
3388 if(state.library.initialized === false) {
3389 if(state.library.enabled === true) {
3390 state.library.initialize(NETDATA.chartRefresher);
3394 state.error('chart library "' + state.library_name + '" is not enabled.');
3398 parallel.unshift(state);
3401 if(parallel.length > 0) {
3402 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3403 // this will execute the jobs in parallel
3404 $(parallel).each(function() {
3409 // console.log('auto-refresher nothing to do');
3412 // run the next refresh iteration
3413 setTimeout(NETDATA.chartRefresher,
3414 NETDATA.chartRefresherWaitTime());
3417 NETDATA.parseDom = function(callback) {
3418 NETDATA.options.last_page_scroll = new Date().getTime();
3419 NETDATA.options.updated_dom = false;
3421 var targets = $('div[data-netdata]'); //.filter(':visible');
3423 if(NETDATA.options.debug.main_loop === true)
3424 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3426 NETDATA.options.targets = new Array();
3427 var len = targets.length;
3429 // the initialization will take care of sizing
3430 // and the "loading..." message
3431 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3434 if(typeof callback === 'function') callback();
3437 // this is the main function - where everything starts
3438 NETDATA.start = function() {
3439 // this should be called only once
3441 NETDATA.options.page_is_visible = true;
3443 $(window).blur(function() {
3444 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3445 NETDATA.options.page_is_visible = false;
3446 if(NETDATA.options.debug.focus === true)
3447 console.log('Lost Focus!');
3451 $(window).focus(function() {
3452 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3453 NETDATA.options.page_is_visible = true;
3454 if(NETDATA.options.debug.focus === true)
3455 console.log('Focus restored!');
3459 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3460 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3461 NETDATA.options.page_is_visible = false;
3462 if(NETDATA.options.debug.focus === true)
3463 console.log('Document has no focus!');
3467 // bootstrap tab switching
3468 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3470 // bootstrap modal switching
3471 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3472 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3474 // bootstrap collapse switching
3475 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3476 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3478 NETDATA.parseDom(NETDATA.chartRefresher);
3480 // Alarms initialization
3481 setTimeout(NETDATA.alarms.init, 1000);
3483 // Registry initialization
3484 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3486 if(typeof netdataCallback === 'function')
3490 // ----------------------------------------------------------------------------------------------------------------
3493 NETDATA.peityInitialize = function(callback) {
3494 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3496 url: NETDATA.peity_js,
3499 xhrFields: { withCredentials: true } // required for the cookie
3502 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3505 NETDATA.chartLibraries.peity.enabled = false;
3506 NETDATA.error(100, NETDATA.peity_js);
3508 .always(function() {
3509 if(typeof callback === "function")
3514 NETDATA.chartLibraries.peity.enabled = false;
3515 if(typeof callback === "function")
3520 NETDATA.peityChartUpdate = function(state, data) {
3521 state.peity_instance.innerHTML = data.result;
3523 if(state.peity_options.stroke !== state.chartColors()[0]) {
3524 state.peity_options.stroke = state.chartColors()[0];
3525 if(state.chart.chart_type === 'line')
3526 state.peity_options.fill = NETDATA.themes.current.background;
3528 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3531 $(state.peity_instance).peity('line', state.peity_options);
3535 NETDATA.peityChartCreate = function(state, data) {
3536 state.peity_instance = document.createElement('div');
3537 state.element_chart.appendChild(state.peity_instance);
3539 var self = $(state.element);
3540 state.peity_options = {
3541 stroke: NETDATA.themes.current.foreground,
3542 strokeWidth: self.data('peity-strokewidth') || 1,
3543 width: state.chartWidth(),
3544 height: state.chartHeight(),
3545 fill: NETDATA.themes.current.foreground
3548 NETDATA.peityChartUpdate(state, data);
3552 // ----------------------------------------------------------------------------------------------------------------
3555 NETDATA.sparklineInitialize = function(callback) {
3556 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3558 url: NETDATA.sparkline_js,
3561 xhrFields: { withCredentials: true } // required for the cookie
3564 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3567 NETDATA.chartLibraries.sparkline.enabled = false;
3568 NETDATA.error(100, NETDATA.sparkline_js);
3570 .always(function() {
3571 if(typeof callback === "function")
3576 NETDATA.chartLibraries.sparkline.enabled = false;
3577 if(typeof callback === "function")
3582 NETDATA.sparklineChartUpdate = function(state, data) {
3583 state.sparkline_options.width = state.chartWidth();
3584 state.sparkline_options.height = state.chartHeight();
3586 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3590 NETDATA.sparklineChartCreate = function(state, data) {
3591 var self = $(state.element);
3592 var type = self.data('sparkline-type') || 'line';
3593 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3594 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3595 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3596 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3597 var composite = self.data('sparkline-composite') || undefined;
3598 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3599 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3600 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3601 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3602 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3603 var spotColor = self.data('sparkline-spotcolor') || undefined;
3604 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3605 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3606 var spotRadius = self.data('sparkline-spotradius') || undefined;
3607 var valueSpots = self.data('sparkline-valuespots') || undefined;
3608 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3609 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3610 var lineWidth = self.data('sparkline-linewidth') || undefined;
3611 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3612 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3613 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3614 var xvalues = self.data('sparkline-xvalues') || undefined;
3615 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3616 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3617 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3618 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3619 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3620 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3621 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3622 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3623 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3624 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3625 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3626 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3627 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3628 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3629 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3630 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3631 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3632 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3633 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3634 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3635 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3636 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3638 if(spotColor === 'disable') spotColor='';
3639 if(minSpotColor === 'disable') minSpotColor='';
3640 if(maxSpotColor === 'disable') maxSpotColor='';
3642 state.sparkline_options = {
3644 lineColor: lineColor,
3645 fillColor: fillColor,
3646 chartRangeMin: chartRangeMin,
3647 chartRangeMax: chartRangeMax,
3648 composite: composite,
3649 enableTagOptions: enableTagOptions,
3650 tagOptionPrefix: tagOptionPrefix,
3651 tagValuesAttribute: tagValuesAttribute,
3652 disableHiddenCheck: disableHiddenCheck,
3653 defaultPixelsPerValue: defaultPixelsPerValue,
3654 spotColor: spotColor,
3655 minSpotColor: minSpotColor,
3656 maxSpotColor: maxSpotColor,
3657 spotRadius: spotRadius,
3658 valueSpots: valueSpots,
3659 highlightSpotColor: highlightSpotColor,
3660 highlightLineColor: highlightLineColor,
3661 lineWidth: lineWidth,
3662 normalRangeMin: normalRangeMin,
3663 normalRangeMax: normalRangeMax,
3664 drawNormalOnTop: drawNormalOnTop,
3666 chartRangeClip: chartRangeClip,
3667 chartRangeMinX: chartRangeMinX,
3668 chartRangeMaxX: chartRangeMaxX,
3669 disableInteraction: disableInteraction,
3670 disableTooltips: disableTooltips,
3671 disableHighlight: disableHighlight,
3672 highlightLighten: highlightLighten,
3673 highlightColor: highlightColor,
3674 tooltipContainer: tooltipContainer,
3675 tooltipClassname: tooltipClassname,
3676 tooltipChartTitle: state.title,
3677 tooltipFormat: tooltipFormat,
3678 tooltipPrefix: tooltipPrefix,
3679 tooltipSuffix: tooltipSuffix,
3680 tooltipSkipNull: tooltipSkipNull,
3681 tooltipValueLookups: tooltipValueLookups,
3682 tooltipFormatFieldlist: tooltipFormatFieldlist,
3683 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3684 numberFormatter: numberFormatter,
3685 numberDigitGroupSep: numberDigitGroupSep,
3686 numberDecimalMark: numberDecimalMark,
3687 numberDigitGroupCount: numberDigitGroupCount,
3688 animatedZooms: animatedZooms,
3689 width: state.chartWidth(),
3690 height: state.chartHeight()
3693 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3697 // ----------------------------------------------------------------------------------------------------------------
3704 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3705 if(after < state.netdata_first)
3706 after = state.netdata_first;
3708 if(before > state.netdata_last)
3709 before = state.netdata_last;
3711 state.setMode('zoom');
3712 state.globalSelectionSyncStop();
3713 state.globalSelectionSyncDelay();
3714 state.dygraph_user_action = true;
3715 state.dygraph_force_zoom = true;
3716 state.updateChartPanOrZoom(after, before);
3717 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3720 NETDATA.dygraphSetSelection = function(state, t) {
3721 if(typeof state.dygraph_instance !== 'undefined') {
3722 var r = state.calculateRowForTime(t);
3724 state.dygraph_instance.setSelection(r);
3726 state.dygraph_instance.clearSelection();
3727 state.legendShowUndefined();
3734 NETDATA.dygraphClearSelection = function(state, t) {
3735 if(typeof state.dygraph_instance !== 'undefined') {
3736 state.dygraph_instance.clearSelection();
3741 NETDATA.dygraphSmoothInitialize = function(callback) {
3743 url: NETDATA.dygraph_smooth_js,
3746 xhrFields: { withCredentials: true } // required for the cookie
3749 NETDATA.dygraph.smooth = true;
3750 smoothPlotter.smoothing = 0.3;
3753 NETDATA.dygraph.smooth = false;
3755 .always(function() {
3756 if(typeof callback === "function")
3761 NETDATA.dygraphInitialize = function(callback) {
3762 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3764 url: NETDATA.dygraph_js,
3767 xhrFields: { withCredentials: true } // required for the cookie
3770 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3773 NETDATA.chartLibraries.dygraph.enabled = false;
3774 NETDATA.error(100, NETDATA.dygraph_js);
3776 .always(function() {
3777 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3778 NETDATA.dygraphSmoothInitialize(callback);
3779 else if(typeof callback === "function")
3784 NETDATA.chartLibraries.dygraph.enabled = false;
3785 if(typeof callback === "function")
3790 NETDATA.dygraphChartUpdate = function(state, data) {
3791 var dygraph = state.dygraph_instance;
3793 if(typeof dygraph === 'undefined')
3794 return NETDATA.dygraphChartCreate(state, data);
3796 // when the chart is not visible, and hidden
3797 // if there is a window resize, dygraph detects
3798 // its element size as 0x0.
3799 // this will make it re-appear properly
3801 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3805 file: data.result.data,
3806 colors: state.chartColors(),
3807 labels: data.result.labels,
3808 labelsDivWidth: state.chartWidth() - 70,
3809 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3812 if(state.dygraph_force_zoom === true) {
3813 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3814 state.log('dygraphChartUpdate() forced zoom update');
3816 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3817 options.valueRange = state.dygraph_options.valueRange;
3818 options.isZoomedIgnoreProgrammaticZoom = true;
3819 state.dygraph_force_zoom = false;
3821 else if(state.current.name !== 'auto') {
3822 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3823 state.log('dygraphChartUpdate() loose update');
3825 options.valueRange = state.dygraph_options.valueRange;
3828 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3829 state.log('dygraphChartUpdate() strict update');
3831 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3832 options.valueRange = state.dygraph_options.valueRange;
3833 options.isZoomedIgnoreProgrammaticZoom = true;
3836 if(state.dygraph_smooth_eligible === true) {
3837 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3838 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3839 NETDATA.dygraphChartCreate(state, data);
3844 dygraph.updateOptions(options);
3846 state.dygraph_last_rendered = new Date().getTime();
3850 NETDATA.dygraphChartCreate = function(state, data) {
3851 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3852 state.log('dygraphChartCreate()');
3854 var self = $(state.element);
3856 var chart_type = state.chart.chart_type;
3857 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3858 chart_type = self.data('dygraph-type') || chart_type;
3860 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3861 smooth = self.data('dygraph-smooth') || smooth;
3863 if(NETDATA.dygraph.smooth === false)
3866 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3867 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3869 state.dygraph_options = {
3870 colors: self.data('dygraph-colors') || state.chartColors(),
3872 // leave a few pixels empty on the right of the chart
3873 rightGap: self.data('dygraph-rightgap') || 5,
3874 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3875 showRoller: self.data('dygraph-showroller') || false,
3877 title: self.data('dygraph-title') || state.title,
3878 titleHeight: self.data('dygraph-titleheight') || 19,
3880 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3881 labels: data.result.labels,
3882 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3883 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3884 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3885 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3886 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3889 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3890 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3892 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
3893 xRangePad: self.data('dygraph-xrangepad') || 0,
3894 // yRangePad: self.data('dygraph-yrangepad') || 1,
3896 valueRange: self.data('dygraph-valuerange') || null,
3898 ylabel: state.units,
3899 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3901 // the function to plot the chart
3904 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3905 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3906 strokePattern: self.data('dygraph-strokepattern') || undefined,
3908 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3909 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3910 drawPoints: self.data('dygraph-drawpoints') || false,
3912 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3913 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3915 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3916 pointSize: self.data('dygraph-pointsize') || 1,
3918 // enabling this makes the chart with little square lines
3919 stepPlot: self.data('dygraph-stepplot') || false,
3921 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3922 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3923 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3925 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3926 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3927 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3928 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3930 drawAxis: self.data('dygraph-drawaxis') || true,
3931 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3932 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3933 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3935 drawGrid: self.data('dygraph-drawgrid') || true,
3936 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3937 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3938 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3939 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3940 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3942 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3943 sigFigs: self.data('dygraph-sigfigs') || null,
3944 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3945 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3947 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3948 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3949 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3951 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3952 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3956 ticker: Dygraph.dateTicker,
3957 axisLabelFormatter: function (d, gran) {
3958 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3960 valueFormatter: function (ms) {
3961 var d = new Date(ms);
3962 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3963 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3968 valueFormatter: function (x) {
3969 // we format legends with the state object
3970 // no need to do anything here
3971 // return (Math.round(x*100) / 100).toLocaleString();
3972 // return state.legendFormatValue(x);
3977 legendFormatter: function(data) {
3978 var elements = state.element_legend_childs;
3980 // if the hidden div is not there
3981 // we are not managing the legend
3982 if(elements.hidden === null) return;
3984 if (typeof data.x !== 'undefined') {
3985 state.legendSetDate(data.x);
3986 var i = data.series.length;
3988 var series = data.series[i];
3989 if(!series.isVisible) continue;
3990 state.legendSetLabelValue(series.label, series.y);
3996 drawCallback: function(dygraph, is_initial) {
3997 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3998 state.dygraph_user_action = false;
4000 var x_range = dygraph.xAxisRange();
4001 var after = Math.round(x_range[0]);
4002 var before = Math.round(x_range[1]);
4004 if(NETDATA.options.debug.dygraph === true)
4005 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4007 if(before <= state.netdata_last && after >= state.netdata_first)
4008 state.updateChartPanOrZoom(after, before);
4011 zoomCallback: function(minDate, maxDate, yRanges) {
4012 if(NETDATA.options.debug.dygraph === true)
4013 state.log('dygraphZoomCallback()');
4015 state.globalSelectionSyncStop();
4016 state.globalSelectionSyncDelay();
4017 state.setMode('zoom');
4019 // refresh it to the greatest possible zoom level
4020 state.dygraph_user_action = true;
4021 state.dygraph_force_zoom = true;
4022 state.updateChartPanOrZoom(minDate, maxDate);
4024 highlightCallback: function(event, x, points, row, seriesName) {
4025 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4026 state.log('dygraphHighlightCallback()');
4030 // there is a bug in dygraph when the chart is zoomed enough
4031 // the time it thinks is selected is wrong
4032 // here we calculate the time t based on the row number selected
4034 var t = state.data_after + row * state.data_update_every;
4035 // 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);
4037 state.globalSelectionSync(x);
4039 // fix legend zIndex using the internal structures of dygraph legend module
4040 // this works, but it is a hack!
4041 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4043 unhighlightCallback: function(event) {
4044 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4045 state.log('dygraphUnhighlightCallback()');
4047 state.unpauseChart();
4048 state.globalSelectionSyncStop();
4050 interactionModel : {
4051 mousedown: function(event, dygraph, context) {
4052 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4053 state.log('interactionModel.mousedown()');
4055 state.dygraph_user_action = true;
4056 state.globalSelectionSyncStop();
4058 if(NETDATA.options.debug.dygraph === true)
4059 state.log('dygraphMouseDown()');
4061 // Right-click should not initiate a zoom.
4062 if(event.button && event.button === 2) return;
4064 context.initializeMouseDown(event, dygraph, context);
4066 if(event.button && event.button === 1) {
4067 if (event.altKey || event.shiftKey) {
4068 state.setMode('pan');
4069 state.globalSelectionSyncDelay();
4070 Dygraph.startPan(event, dygraph, context);
4073 state.setMode('zoom');
4074 state.globalSelectionSyncDelay();
4075 Dygraph.startZoom(event, dygraph, context);
4079 if (event.altKey || event.shiftKey) {
4080 state.setMode('zoom');
4081 state.globalSelectionSyncDelay();
4082 Dygraph.startZoom(event, dygraph, context);
4085 state.setMode('pan');
4086 state.globalSelectionSyncDelay();
4087 Dygraph.startPan(event, dygraph, context);
4091 mousemove: function(event, dygraph, context) {
4092 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4093 state.log('interactionModel.mousemove()');
4095 if(context.isPanning) {
4096 state.dygraph_user_action = true;
4097 state.globalSelectionSyncStop();
4098 state.globalSelectionSyncDelay();
4099 state.setMode('pan');
4100 Dygraph.movePan(event, dygraph, context);
4102 else if(context.isZooming) {
4103 state.dygraph_user_action = true;
4104 state.globalSelectionSyncStop();
4105 state.globalSelectionSyncDelay();
4106 state.setMode('zoom');
4107 Dygraph.moveZoom(event, dygraph, context);
4110 mouseup: function(event, dygraph, context) {
4111 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4112 state.log('interactionModel.mouseup()');
4114 if (context.isPanning) {
4115 state.dygraph_user_action = true;
4116 state.globalSelectionSyncDelay();
4117 Dygraph.endPan(event, dygraph, context);
4119 else if (context.isZooming) {
4120 state.dygraph_user_action = true;
4121 state.globalSelectionSyncDelay();
4122 Dygraph.endZoom(event, dygraph, context);
4125 click: function(event, dygraph, context) {
4126 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4127 state.log('interactionModel.click()');
4129 event.preventDefault();
4131 dblclick: function(event, dygraph, context) {
4132 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4133 state.log('interactionModel.dblclick()');
4134 NETDATA.resetAllCharts(state);
4136 mousewheel: function(event, dygraph, context) {
4137 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4138 state.log('interactionModel.mousewheel()');
4140 // Take the offset of a mouse event on the dygraph canvas and
4141 // convert it to a pair of percentages from the bottom left.
4142 // (Not top left, bottom is where the lower value is.)
4143 function offsetToPercentage(g, offsetX, offsetY) {
4144 // This is calculating the pixel offset of the leftmost date.
4145 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4146 var yar0 = g.yAxisRange(0);
4148 // This is calculating the pixel of the higest value. (Top pixel)
4149 var yOffset = g.toDomCoords(null, yar0[1])[1];
4151 // x y w and h are relative to the corner of the drawing area,
4152 // so that the upper corner of the drawing area is (0, 0).
4153 var x = offsetX - xOffset;
4154 var y = offsetY - yOffset;
4156 // This is computing the rightmost pixel, effectively defining the
4158 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4160 // This is computing the lowest pixel, effectively defining the height.
4161 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4163 // Percentage from the left.
4164 var xPct = w === 0 ? 0 : (x / w);
4165 // Percentage from the top.
4166 var yPct = h === 0 ? 0 : (y / h);
4168 // The (1-) part below changes it from "% distance down from the top"
4169 // to "% distance up from the bottom".
4170 return [xPct, (1-yPct)];
4173 // Adjusts [x, y] toward each other by zoomInPercentage%
4174 // Split it so the left/bottom axis gets xBias/yBias of that change and
4175 // tight/top gets (1-xBias)/(1-yBias) of that change.
4177 // If a bias is missing it splits it down the middle.
4178 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4179 xBias = xBias || 0.5;
4180 yBias = yBias || 0.5;
4182 function adjustAxis(axis, zoomInPercentage, bias) {
4183 var delta = axis[1] - axis[0];
4184 var increment = delta * zoomInPercentage;
4185 var foo = [increment * bias, increment * (1-bias)];
4187 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4190 var yAxes = g.yAxisRanges();
4192 for (var i = 0; i < yAxes.length; i++) {
4193 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4196 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4199 if(event.altKey || event.shiftKey) {
4200 state.dygraph_user_action = true;
4202 state.globalSelectionSyncStop();
4203 state.globalSelectionSyncDelay();
4205 // http://dygraphs.com/gallery/interaction-api.js
4206 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4207 var percentage = normal / 50;
4209 if (!(event.offsetX && event.offsetY)){
4210 event.offsetX = event.layerX - event.target.offsetLeft;
4211 event.offsetY = event.layerY - event.target.offsetTop;
4214 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4215 var xPct = percentages[0];
4216 var yPct = percentages[1];
4218 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4220 var after = new_x_range[0];
4221 var before = new_x_range[1];
4223 var first = state.netdata_first + state.data_update_every;
4224 var last = state.netdata_last + state.data_update_every;
4227 after -= (before - last);
4234 state.setMode('zoom');
4235 if(state.updateChartPanOrZoom(after, before) === true)
4236 dygraph.updateOptions({ dateWindow: [ after, before ] });
4238 event.preventDefault();
4241 touchstart: function(event, dygraph, context) {
4242 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4243 state.log('interactionModel.touchstart()');
4245 state.dygraph_user_action = true;
4246 state.setMode('zoom');
4249 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4251 // we overwrite the touch directions at the end, to overwrite
4252 // the internal default of dygraphs
4253 context.touchDirections = { x: true, y: false };
4255 state.dygraph_last_touch_start = new Date().getTime();
4256 state.dygraph_last_touch_move = 0;
4258 if(typeof event.touches[0].pageX === 'number')
4259 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4261 state.dygraph_last_touch_page_x = 0;
4263 touchmove: function(event, dygraph, context) {
4264 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4265 state.log('interactionModel.touchmove()');
4267 state.dygraph_user_action = true;
4268 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4270 state.dygraph_last_touch_move = new Date().getTime();
4272 touchend: function(event, dygraph, context) {
4273 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4274 state.log('interactionModel.touchend()');
4276 state.dygraph_user_action = true;
4277 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4279 // if it didn't move, it is a selection
4280 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4281 // internal api of dygraphs
4282 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4283 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4284 if(NETDATA.dygraphSetSelection(state, t) === true)
4285 state.globalSelectionSync(t);
4288 // if it was double tap within double click time, reset the charts
4289 var now = new Date().getTime();
4290 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4291 if(state.dygraph_last_touch_move === 0) {
4292 var dt = now - state.dygraph_last_touch_end;
4293 if(dt <= NETDATA.options.current.double_click_speed)
4294 NETDATA.resetAllCharts(state);
4298 // remember the timestamp of the last touch end
4299 state.dygraph_last_touch_end = now;
4304 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4305 state.dygraph_options.drawGrid = false;
4306 state.dygraph_options.drawAxis = false;
4307 state.dygraph_options.title = undefined;
4308 state.dygraph_options.units = undefined;
4309 state.dygraph_options.ylabel = undefined;
4310 state.dygraph_options.yLabelWidth = 0;
4311 state.dygraph_options.labelsDivWidth = 120;
4312 state.dygraph_options.labelsDivStyles.width = '120px';
4313 state.dygraph_options.labelsSeparateLines = true;
4314 state.dygraph_options.rightGap = 0;
4315 state.dygraph_options.yRangePad = 1;
4318 if(smooth === true) {
4319 state.dygraph_smooth_eligible = true;
4321 if(NETDATA.options.current.smooth_plot === true)
4322 state.dygraph_options.plotter = smoothPlotter;
4324 else state.dygraph_smooth_eligible = false;
4326 state.dygraph_instance = new Dygraph(state.element_chart,
4327 data.result.data, state.dygraph_options);
4329 state.dygraph_force_zoom = false;
4330 state.dygraph_user_action = false;
4331 state.dygraph_last_rendered = new Date().getTime();
4335 // ----------------------------------------------------------------------------------------------------------------
4338 NETDATA.morrisInitialize = function(callback) {
4339 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4341 // morris requires raphael
4342 if(!NETDATA.chartLibraries.raphael.initialized) {
4343 if(NETDATA.chartLibraries.raphael.enabled) {
4344 NETDATA.raphaelInitialize(function() {
4345 NETDATA.morrisInitialize(callback);
4349 NETDATA.chartLibraries.morris.enabled = false;
4350 if(typeof callback === "function")
4355 NETDATA._loadCSS(NETDATA.morris_css);
4358 url: NETDATA.morris_js,
4361 xhrFields: { withCredentials: true } // required for the cookie
4364 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4367 NETDATA.chartLibraries.morris.enabled = false;
4368 NETDATA.error(100, NETDATA.morris_js);
4370 .always(function() {
4371 if(typeof callback === "function")
4377 NETDATA.chartLibraries.morris.enabled = false;
4378 if(typeof callback === "function")
4383 NETDATA.morrisChartUpdate = function(state, data) {
4384 state.morris_instance.setData(data.result.data);
4388 NETDATA.morrisChartCreate = function(state, data) {
4390 state.morris_options = {
4391 element: state.element_chart.id,
4392 data: data.result.data,
4394 ykeys: data.dimension_names,
4395 labels: data.dimension_names,
4401 continuousLine: false,
4402 behaveLikeLine: false
4405 if(state.chart.chart_type === 'line')
4406 state.morris_instance = new Morris.Line(state.morris_options);
4408 else if(state.chart.chart_type === 'area') {
4409 state.morris_options.behaveLikeLine = true;
4410 state.morris_instance = new Morris.Area(state.morris_options);
4413 state.morris_instance = new Morris.Area(state.morris_options);
4418 // ----------------------------------------------------------------------------------------------------------------
4421 NETDATA.raphaelInitialize = function(callback) {
4422 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4424 url: NETDATA.raphael_js,
4427 xhrFields: { withCredentials: true } // required for the cookie
4430 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4433 NETDATA.chartLibraries.raphael.enabled = false;
4434 NETDATA.error(100, NETDATA.raphael_js);
4436 .always(function() {
4437 if(typeof callback === "function")
4442 NETDATA.chartLibraries.raphael.enabled = false;
4443 if(typeof callback === "function")
4448 NETDATA.raphaelChartUpdate = function(state, data) {
4449 $(state.element_chart).raphael(data.result, {
4450 width: state.chartWidth(),
4451 height: state.chartHeight()
4457 NETDATA.raphaelChartCreate = function(state, data) {
4458 $(state.element_chart).raphael(data.result, {
4459 width: state.chartWidth(),
4460 height: state.chartHeight()
4466 // ----------------------------------------------------------------------------------------------------------------
4469 NETDATA.c3Initialize = function(callback) {
4470 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4473 if(!NETDATA.chartLibraries.d3.initialized) {
4474 if(NETDATA.chartLibraries.d3.enabled) {
4475 NETDATA.d3Initialize(function() {
4476 NETDATA.c3Initialize(callback);
4480 NETDATA.chartLibraries.c3.enabled = false;
4481 if(typeof callback === "function")
4486 NETDATA._loadCSS(NETDATA.c3_css);
4492 xhrFields: { withCredentials: true } // required for the cookie
4495 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4498 NETDATA.chartLibraries.c3.enabled = false;
4499 NETDATA.error(100, NETDATA.c3_js);
4501 .always(function() {
4502 if(typeof callback === "function")
4508 NETDATA.chartLibraries.c3.enabled = false;
4509 if(typeof callback === "function")
4514 NETDATA.c3ChartUpdate = function(state, data) {
4515 state.c3_instance.destroy();
4516 return NETDATA.c3ChartCreate(state, data);
4518 //state.c3_instance.load({
4519 // rows: data.result,
4526 NETDATA.c3ChartCreate = function(state, data) {
4528 state.element_chart.id = 'c3-' + state.uuid;
4529 // console.log('id = ' + state.element_chart.id);
4531 state.c3_instance = c3.generate({
4532 bindto: '#' + state.element_chart.id,
4534 width: state.chartWidth(),
4535 height: state.chartHeight()
4538 pattern: state.chartColors()
4543 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4549 format: function(x) {
4550 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4577 // console.log(state.c3_instance);
4582 // ----------------------------------------------------------------------------------------------------------------
4585 NETDATA.d3Initialize = function(callback) {
4586 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4591 xhrFields: { withCredentials: true } // required for the cookie
4594 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4597 NETDATA.chartLibraries.d3.enabled = false;
4598 NETDATA.error(100, NETDATA.d3_js);
4600 .always(function() {
4601 if(typeof callback === "function")
4606 NETDATA.chartLibraries.d3.enabled = false;
4607 if(typeof callback === "function")
4612 NETDATA.d3ChartUpdate = function(state, data) {
4616 NETDATA.d3ChartCreate = function(state, data) {
4620 // ----------------------------------------------------------------------------------------------------------------
4623 NETDATA.googleInitialize = function(callback) {
4624 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4626 url: NETDATA.google_js,
4629 xhrFields: { withCredentials: true } // required for the cookie
4632 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4633 google.load('visualization', '1.1', {
4634 'packages': ['corechart', 'controls'],
4635 'callback': callback
4639 NETDATA.chartLibraries.google.enabled = false;
4640 NETDATA.error(100, NETDATA.google_js);
4641 if(typeof callback === "function")
4646 NETDATA.chartLibraries.google.enabled = false;
4647 if(typeof callback === "function")
4652 NETDATA.googleChartUpdate = function(state, data) {
4653 var datatable = new google.visualization.DataTable(data.result);
4654 state.google_instance.draw(datatable, state.google_options);
4658 NETDATA.googleChartCreate = function(state, data) {
4659 var datatable = new google.visualization.DataTable(data.result);
4661 state.google_options = {
4662 colors: state.chartColors(),
4664 // do not set width, height - the chart resizes itself
4665 //width: state.chartWidth(),
4666 //height: state.chartHeight(),
4671 // title: "Time of Day",
4672 // format:'HH:mm:ss',
4673 viewWindowMode: 'maximized',
4685 viewWindowMode: 'pretty',
4700 focusTarget: 'category',
4707 titlePosition: 'out',
4718 curveType: 'function',
4723 switch(state.chart.chart_type) {
4725 state.google_options.vAxis.viewWindowMode = 'maximized';
4726 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4727 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4731 state.google_options.isStacked = true;
4732 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4733 state.google_options.vAxis.viewWindowMode = 'maximized';
4734 state.google_options.vAxis.minValue = null;
4735 state.google_options.vAxis.maxValue = null;
4736 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4741 state.google_options.lineWidth = 2;
4742 state.google_instance = new google.visualization.LineChart(state.element_chart);
4746 state.google_instance.draw(datatable, state.google_options);
4750 // ----------------------------------------------------------------------------------------------------------------
4752 NETDATA.percentFromValueMax = function(value, max) {
4753 if(value === null) value = 0;
4754 if(max < value) max = value;
4758 pcent = Math.round(value * 100 / max);
4759 if(pcent === 0 && value > 0) pcent = 1;
4765 // ----------------------------------------------------------------------------------------------------------------
4768 NETDATA.easypiechartInitialize = function(callback) {
4769 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4771 url: NETDATA.easypiechart_js,
4774 xhrFields: { withCredentials: true } // required for the cookie
4777 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4780 NETDATA.chartLibraries.easypiechart.enabled = false;
4781 NETDATA.error(100, NETDATA.easypiechart_js);
4783 .always(function() {
4784 if(typeof callback === "function")
4789 NETDATA.chartLibraries.easypiechart.enabled = false;
4790 if(typeof callback === "function")
4795 NETDATA.easypiechartClearSelection = function(state) {
4796 if(typeof state.easyPieChartEvent !== 'undefined') {
4797 if(state.easyPieChartEvent.timer !== null)
4798 clearTimeout(state.easyPieChartEvent.timer);
4800 state.easyPieChartEvent.timer = null;
4803 if(state.isAutoRefreshable() === true && state.data !== null) {
4804 NETDATA.easypiechartChartUpdate(state, state.data);
4807 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4808 state.easyPieChart_instance.update(0);
4810 state.easyPieChart_instance.enableAnimation();
4815 NETDATA.easypiechartSetSelection = function(state, t) {
4816 if(state.timeIsVisible(t) !== true)
4817 return NETDATA.easypiechartClearSelection(state);
4819 var slot = state.calculateRowForTime(t);
4820 if(slot < 0 || slot >= state.data.result.length)
4821 return NETDATA.easypiechartClearSelection(state);
4823 if(typeof state.easyPieChartEvent === 'undefined') {
4824 state.easyPieChartEvent = {
4831 var value = state.data.result[state.data.result.length - 1 - slot];
4832 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4833 var pcent = NETDATA.percentFromValueMax(value, max);
4835 state.easyPieChartEvent.value = value;
4836 state.easyPieChartEvent.pcent = pcent;
4837 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4839 if(state.easyPieChartEvent.timer === null) {
4840 state.easyPieChart_instance.disableAnimation();
4842 state.easyPieChartEvent.timer = setTimeout(function() {
4843 state.easyPieChartEvent.timer = null;
4844 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4845 }, NETDATA.options.current.charts_selection_animation_delay);
4851 NETDATA.easypiechartChartUpdate = function(state, data) {
4852 var value, max, pcent;
4854 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4860 value = data.result[0];
4861 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4862 pcent = NETDATA.percentFromValueMax(value, max);
4865 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4866 state.easyPieChart_instance.update(pcent);
4870 NETDATA.easypiechartChartCreate = function(state, data) {
4871 var self = $(state.element);
4872 var chart = $(state.element_chart);
4874 var value = data.result[0];
4875 var max = self.data('easypiechart-max-value') || null;
4876 var adjust = self.data('easypiechart-adjust') || null;
4880 state.easyPieChartMax = null;
4883 state.easyPieChartMax = max;
4885 var pcent = NETDATA.percentFromValueMax(value, max);
4887 chart.data('data-percent', pcent);
4891 case 'width': size = state.chartHeight(); break;
4892 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4893 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4895 default: size = state.chartWidth(); break;
4897 state.element.style.width = size + 'px';
4898 state.element.style.height = size + 'px';
4900 var stroke = Math.floor(size / 22);
4901 if(stroke < 3) stroke = 2;
4903 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4904 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4905 state.easyPieChartLabel = document.createElement('span');
4906 state.easyPieChartLabel.className = 'easyPieChartLabel';
4907 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4908 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4909 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4910 state.element_chart.appendChild(state.easyPieChartLabel);
4912 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4913 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4914 state.easyPieChartTitle = document.createElement('span');
4915 state.easyPieChartTitle.className = 'easyPieChartTitle';
4916 state.easyPieChartTitle.innerHTML = state.title;
4917 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4918 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4919 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4920 state.element_chart.appendChild(state.easyPieChartTitle);
4922 var unitfontsize = Math.round(titlefontsize * 0.9);
4923 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4924 state.easyPieChartUnits = document.createElement('span');
4925 state.easyPieChartUnits.className = 'easyPieChartUnits';
4926 state.easyPieChartUnits.innerHTML = state.units;
4927 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4928 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4929 state.element_chart.appendChild(state.easyPieChartUnits);
4931 chart.easyPieChart({
4932 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4933 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4934 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4935 scaleLength: self.data('easypiechart-scalelength') || 5,
4936 lineCap: self.data('easypiechart-linecap') || 'round',
4937 lineWidth: self.data('easypiechart-linewidth') || stroke,
4938 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4939 size: self.data('easypiechart-size') || size,
4940 rotate: self.data('easypiechart-rotate') || 0,
4941 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4942 easing: self.data('easypiechart-easing') || undefined
4945 // when we just re-create the chart
4946 // do not animate the first update
4948 if(typeof state.easyPieChart_instance !== 'undefined')
4951 state.easyPieChart_instance = chart.data('easyPieChart');
4952 if(animate === false) state.easyPieChart_instance.disableAnimation();
4953 state.easyPieChart_instance.update(pcent);
4954 if(animate === false) state.easyPieChart_instance.enableAnimation();
4958 // ----------------------------------------------------------------------------------------------------------------
4961 NETDATA.gaugeInitialize = function(callback) {
4962 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4964 url: NETDATA.gauge_js,
4967 xhrFields: { withCredentials: true } // required for the cookie
4970 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4973 NETDATA.chartLibraries.gauge.enabled = false;
4974 NETDATA.error(100, NETDATA.gauge_js);
4976 .always(function() {
4977 if(typeof callback === "function")
4982 NETDATA.chartLibraries.gauge.enabled = false;
4983 if(typeof callback === "function")
4988 NETDATA.gaugeAnimation = function(state, status) {
4991 if(typeof status === 'boolean' && status === false)
4993 else if(typeof status === 'number')
4996 state.gauge_instance.animationSpeed = speed;
4997 state.___gaugeOld__.speed = speed;
5000 NETDATA.gaugeSet = function(state, value, min, max) {
5001 if(typeof value !== 'number') value = 0;
5002 if(typeof min !== 'number') min = 0;
5003 if(typeof max !== 'number') max = 0;
5004 if(value > max) max = value;
5005 if(value < min) min = value;
5014 // gauge.js has an issue if the needle
5015 // is smaller than min or larger than max
5016 // when we set the new values
5017 // the needle will go crazy
5019 // to prevent it, we always feed it
5020 // with a percentage, so that the needle
5021 // is always between min and max
5022 var pcent = (value - min) * 100 / (max - min);
5024 // these should never happen
5025 if(pcent < 0) pcent = 0;
5026 if(pcent > 100) pcent = 100;
5028 state.gauge_instance.set(pcent);
5030 state.___gaugeOld__.value = value;
5031 state.___gaugeOld__.min = min;
5032 state.___gaugeOld__.max = max;
5035 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5036 if(state.___gaugeOld__.valueLabel !== value) {
5037 state.___gaugeOld__.valueLabel = value;
5038 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5040 if(state.___gaugeOld__.minLabel !== min) {
5041 state.___gaugeOld__.minLabel = min;
5042 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5044 if(state.___gaugeOld__.maxLabel !== max) {
5045 state.___gaugeOld__.maxLabel = max;
5046 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5050 NETDATA.gaugeClearSelection = function(state) {
5051 if(typeof state.gaugeEvent !== 'undefined') {
5052 if(state.gaugeEvent.timer !== null)
5053 clearTimeout(state.gaugeEvent.timer);
5055 state.gaugeEvent.timer = null;
5058 if(state.isAutoRefreshable() === true && state.data !== null) {
5059 NETDATA.gaugeChartUpdate(state, state.data);
5062 NETDATA.gaugeAnimation(state, false);
5063 NETDATA.gaugeSet(state, null, null, null);
5064 NETDATA.gaugeSetLabels(state, null, null, null);
5067 NETDATA.gaugeAnimation(state, true);
5071 NETDATA.gaugeSetSelection = function(state, t) {
5072 if(state.timeIsVisible(t) !== true)
5073 return NETDATA.gaugeClearSelection(state);
5075 var slot = state.calculateRowForTime(t);
5076 if(slot < 0 || slot >= state.data.result.length)
5077 return NETDATA.gaugeClearSelection(state);
5079 if(typeof state.gaugeEvent === 'undefined') {
5080 state.gaugeEvent = {
5088 var value = state.data.result[state.data.result.length - 1 - slot];
5089 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5092 state.gaugeEvent.value = value;
5093 state.gaugeEvent.max = max;
5094 state.gaugeEvent.min = min;
5095 NETDATA.gaugeSetLabels(state, value, min, max);
5097 if(state.gaugeEvent.timer === null) {
5098 NETDATA.gaugeAnimation(state, false);
5100 state.gaugeEvent.timer = setTimeout(function() {
5101 state.gaugeEvent.timer = null;
5102 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5103 }, NETDATA.options.current.charts_selection_animation_delay);
5109 NETDATA.gaugeChartUpdate = function(state, data) {
5110 var value, min, max;
5112 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5116 NETDATA.gaugeSetLabels(state, null, null, null);
5119 value = data.result[0];
5121 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5122 if(value > max) max = value;
5123 NETDATA.gaugeSetLabels(state, value, min, max);
5126 NETDATA.gaugeSet(state, value, min, max);
5130 NETDATA.gaugeChartCreate = function(state, data) {
5131 var self = $(state.element);
5132 // var chart = $(state.element_chart);
5134 var value = data.result[0];
5135 var max = self.data('gauge-max-value') || null;
5136 var adjust = self.data('gauge-adjust') || null;
5137 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5138 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5139 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5140 var stopColor = self.data('gauge-stop-color') || void 0;
5141 var generateGradient = self.data('gauge-generate-gradient') || false;
5145 state.gaugeMax = null;
5148 state.gaugeMax = max;
5150 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5152 // case 'width': width = height * ratio; break;
5154 // default: height = width / ratio; break;
5156 //state.element.style.width = width.toString() + 'px';
5157 //state.element.style.height = height.toString() + 'px';
5162 lines: 12, // The number of lines to draw
5163 angle: 0.15, // The length of each line
5164 lineWidth: 0.44, // 0.44 The line thickness
5166 length: 0.8, // 0.9 The radius of the inner circle
5167 strokeWidth: 0.035, // The rotation offset
5168 color: pointerColor // Fill color
5170 colorStart: startColor, // Colors
5171 colorStop: stopColor, // just experiment with them
5172 strokeColor: strokeColor, // to see which ones work best for you
5174 generateGradient: (generateGradient === true)?true:false,
5178 if (generateGradient.constructor === Array) {
5180 // data-gauge-generate-gradient="[0, 50, 100]"
5181 // data-gauge-gradient-percent-color-0="#FFFFFF"
5182 // data-gauge-gradient-percent-color-50="#999900"
5183 // data-gauge-gradient-percent-color-100="#000000"
5185 options.percentColors = new Array();
5186 var len = generateGradient.length;
5188 var pcent = generateGradient[len];
5189 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5190 if(color !== false) {
5191 var a = new Array();
5194 options.percentColors.unshift(a);
5197 if(options.percentColors.length === 0)
5198 delete options.percentColors;
5200 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5201 options.percentColors = [
5202 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5203 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5204 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5205 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5206 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5207 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5208 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5209 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5210 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5211 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5212 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5215 state.gauge_canvas = document.createElement('canvas');
5216 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5217 state.gauge_canvas.className = 'gaugeChart';
5218 state.gauge_canvas.width = width;
5219 state.gauge_canvas.height = height;
5220 state.element_chart.appendChild(state.gauge_canvas);
5222 var valuefontsize = Math.floor(height / 6);
5223 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5224 state.gaugeChartLabel = document.createElement('span');
5225 state.gaugeChartLabel.className = 'gaugeChartLabel';
5226 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5227 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5228 state.element_chart.appendChild(state.gaugeChartLabel);
5230 var titlefontsize = Math.round(valuefontsize / 2);
5232 state.gaugeChartTitle = document.createElement('span');
5233 state.gaugeChartTitle.className = 'gaugeChartTitle';
5234 state.gaugeChartTitle.innerHTML = state.title;
5235 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5236 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5237 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5238 state.element_chart.appendChild(state.gaugeChartTitle);
5240 var unitfontsize = Math.round(titlefontsize * 0.9);
5241 state.gaugeChartUnits = document.createElement('span');
5242 state.gaugeChartUnits.className = 'gaugeChartUnits';
5243 state.gaugeChartUnits.innerHTML = state.units;
5244 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5245 state.element_chart.appendChild(state.gaugeChartUnits);
5247 state.gaugeChartMin = document.createElement('span');
5248 state.gaugeChartMin.className = 'gaugeChartMin';
5249 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5250 state.element_chart.appendChild(state.gaugeChartMin);
5252 state.gaugeChartMax = document.createElement('span');
5253 state.gaugeChartMax.className = 'gaugeChartMax';
5254 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5255 state.element_chart.appendChild(state.gaugeChartMax);
5257 // when we just re-create the chart
5258 // do not animate the first update
5260 if(typeof state.gauge_instance !== 'undefined')
5263 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5265 state.___gaugeOld__ = {
5274 // we will always feed a percentage
5275 state.gauge_instance.minValue = 0;
5276 state.gauge_instance.maxValue = 100;
5278 NETDATA.gaugeAnimation(state, animate);
5279 NETDATA.gaugeSet(state, value, 0, max);
5280 NETDATA.gaugeSetLabels(state, value, 0, max);
5281 NETDATA.gaugeAnimation(state, true);
5285 // ----------------------------------------------------------------------------------------------------------------
5286 // Charts Libraries Registration
5288 NETDATA.chartLibraries = {
5290 initialize: NETDATA.dygraphInitialize,
5291 create: NETDATA.dygraphChartCreate,
5292 update: NETDATA.dygraphChartUpdate,
5293 resize: function(state) {
5294 if(typeof state.dygraph_instance.resize === 'function')
5295 state.dygraph_instance.resize();
5297 setSelection: NETDATA.dygraphSetSelection,
5298 clearSelection: NETDATA.dygraphClearSelection,
5299 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5302 format: function(state) { return 'json'; },
5303 options: function(state) { return 'ms|flip'; },
5304 legend: function(state) {
5305 if(this.isSparkline(state) === false)
5306 return 'right-side';
5310 autoresize: function(state) { return true; },
5311 max_updates_to_recreate: function(state) { return 5000; },
5312 track_colors: function(state) { return true; },
5313 pixels_per_point: function(state) {
5314 if(this.isSparkline(state) === false)
5320 isSparkline: function(state) {
5321 if(typeof state.dygraph_sparkline === 'undefined') {
5322 var t = $(state.element).data('dygraph-theme');
5323 if(t === 'sparkline')
5324 state.dygraph_sparkline = true;
5326 state.dygraph_sparkline = false;
5328 return state.dygraph_sparkline;
5332 initialize: NETDATA.sparklineInitialize,
5333 create: NETDATA.sparklineChartCreate,
5334 update: NETDATA.sparklineChartUpdate,
5336 setSelection: undefined, // function(state, t) { return true; },
5337 clearSelection: undefined, // function(state) { return true; },
5338 toolboxPanAndZoom: null,
5341 format: function(state) { return 'array'; },
5342 options: function(state) { return '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.peityInitialize,
5351 create: NETDATA.peityChartCreate,
5352 update: NETDATA.peityChartUpdate,
5354 setSelection: undefined, // function(state, t) { return true; },
5355 clearSelection: undefined, // function(state) { return true; },
5356 toolboxPanAndZoom: null,
5359 format: function(state) { return 'ssvcomma'; },
5360 options: function(state) { return 'null2zero|flip|abs'; },
5361 legend: function(state) { return null; },
5362 autoresize: function(state) { return false; },
5363 max_updates_to_recreate: function(state) { return 5000; },
5364 track_colors: function(state) { return false; },
5365 pixels_per_point: function(state) { return 3; }
5368 initialize: NETDATA.morrisInitialize,
5369 create: NETDATA.morrisChartCreate,
5370 update: NETDATA.morrisChartUpdate,
5372 setSelection: undefined, // function(state, t) { return true; },
5373 clearSelection: undefined, // function(state) { return true; },
5374 toolboxPanAndZoom: null,
5377 format: function(state) { return 'json'; },
5378 options: function(state) { return 'objectrows|ms'; },
5379 legend: function(state) { return null; },
5380 autoresize: function(state) { return false; },
5381 max_updates_to_recreate: function(state) { return 50; },
5382 track_colors: function(state) { return false; },
5383 pixels_per_point: function(state) { return 15; }
5386 initialize: NETDATA.googleInitialize,
5387 create: NETDATA.googleChartCreate,
5388 update: NETDATA.googleChartUpdate,
5390 setSelection: undefined, //function(state, t) { return true; },
5391 clearSelection: undefined, //function(state) { return true; },
5392 toolboxPanAndZoom: null,
5395 format: function(state) { return 'datatable'; },
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 300; },
5400 track_colors: function(state) { return false; },
5401 pixels_per_point: function(state) { return 4; }
5404 initialize: NETDATA.raphaelInitialize,
5405 create: NETDATA.raphaelChartCreate,
5406 update: NETDATA.raphaelChartUpdate,
5408 setSelection: undefined, // function(state, t) { return true; },
5409 clearSelection: undefined, // function(state) { return true; },
5410 toolboxPanAndZoom: null,
5413 format: function(state) { return 'json'; },
5414 options: function(state) { return ''; },
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 3; }
5422 initialize: NETDATA.c3Initialize,
5423 create: NETDATA.c3ChartCreate,
5424 update: NETDATA.c3ChartUpdate,
5426 setSelection: undefined, // function(state, t) { return true; },
5427 clearSelection: undefined, // function(state) { return true; },
5428 toolboxPanAndZoom: null,
5431 format: function(state) { return 'csvjsonarray'; },
5432 options: function(state) { return 'milliseconds'; },
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 15; }
5440 initialize: NETDATA.d3Initialize,
5441 create: NETDATA.d3ChartCreate,
5442 update: NETDATA.d3ChartUpdate,
5444 setSelection: undefined, // function(state, t) { return true; },
5445 clearSelection: undefined, // function(state) { return true; },
5446 toolboxPanAndZoom: null,
5449 format: function(state) { return 'json'; },
5450 options: function(state) { return ''; },
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 false; },
5455 pixels_per_point: function(state) { return 3; }
5458 initialize: NETDATA.easypiechartInitialize,
5459 create: NETDATA.easypiechartChartCreate,
5460 update: NETDATA.easypiechartChartUpdate,
5462 setSelection: NETDATA.easypiechartSetSelection,
5463 clearSelection: NETDATA.easypiechartClearSelection,
5464 toolboxPanAndZoom: null,
5467 format: function(state) { return 'array'; },
5468 options: function(state) { return 'absolute'; },
5469 legend: function(state) { return null; },
5470 autoresize: function(state) { return false; },
5471 max_updates_to_recreate: function(state) { return 5000; },
5472 track_colors: function(state) { return true; },
5473 pixels_per_point: function(state) { return 3; },
5477 initialize: NETDATA.gaugeInitialize,
5478 create: NETDATA.gaugeChartCreate,
5479 update: NETDATA.gaugeChartUpdate,
5481 setSelection: NETDATA.gaugeSetSelection,
5482 clearSelection: NETDATA.gaugeClearSelection,
5483 toolboxPanAndZoom: null,
5486 format: function(state) { return 'array'; },
5487 options: function(state) { return 'absolute'; },
5488 legend: function(state) { return null; },
5489 autoresize: function(state) { return false; },
5490 max_updates_to_recreate: function(state) { return 5000; },
5491 track_colors: function(state) { return true; },
5492 pixels_per_point: function(state) { return 3; },
5497 NETDATA.registerChartLibrary = function(library, url) {
5498 if(NETDATA.options.debug.libraries === true)
5499 console.log("registering chart library: " + library);
5501 NETDATA.chartLibraries[library].url = url;
5502 NETDATA.chartLibraries[library].initialized = true;
5503 NETDATA.chartLibraries[library].enabled = true;
5506 // ----------------------------------------------------------------------------------------------------------------
5507 // Load required JS libraries and CSS
5509 NETDATA.requiredJs = [
5511 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5512 isAlreadyLoaded: function() {
5513 // check if bootstrap is loaded
5514 if(typeof $().emulateTransitionEnd == 'function')
5517 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5525 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5526 isAlreadyLoaded: function() { return false; }
5529 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5530 isAlreadyLoaded: function() { return false; }
5534 NETDATA.requiredCSS = [
5536 url: NETDATA.themes.current.bootstrap_css,
5537 isAlreadyLoaded: function() {
5538 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5545 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5546 isAlreadyLoaded: function() { return false; }
5549 url: NETDATA.themes.current.dashboard_css,
5550 isAlreadyLoaded: function() { return false; }
5553 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5554 isAlreadyLoaded: function() { return false; }
5558 NETDATA.loadedRequiredJs = 0;
5559 NETDATA.loadRequiredJs = function(index, callback) {
5560 if(index >= NETDATA.requiredJs.length) return;
5562 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5563 NETDATA.loadedRequiredJs++;
5564 NETDATA.loadRequiredJs(++index, callback);
5568 if(NETDATA.options.debug.main_loop === true)
5569 console.log('loading ' + NETDATA.requiredJs[index].url);
5572 url: NETDATA.requiredJs[index].url,
5576 xhrFields: { withCredentials: true } // required for the cookie
5579 if(NETDATA.options.debug.main_loop === true)
5580 console.log('loaded ' + NETDATA.requiredJs[index].url);
5583 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5585 .always(function() {
5586 NETDATA.loadedRequiredJs++;
5587 if(typeof callback === 'function' && NETDATA.loadedRequiredJs >= NETDATA.requiredJs.length)
5591 NETDATA.loadRequiredJs(++index, callback);
5594 NETDATA.loadRequiredCSS = function(index) {
5595 if(index >= NETDATA.requiredCSS.length)
5598 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5599 NETDATA.loadRequiredCSS(++index);
5603 if(NETDATA.options.debug.main_loop === true)
5604 console.log('loading ' + NETDATA.requiredCSS[index].url);
5606 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5607 NETDATA.loadRequiredCSS(++index);
5611 // ----------------------------------------------------------------------------------------------------------------
5612 // Registry of netdata hosts
5615 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5616 chart_div_offset: 100, // give that space above the chart when scrolling to it
5617 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5618 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5620 ms_penalty: 0, // the time penalty of the next alarm
5621 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5622 // if alarms are shown faster than: one per 500ms
5624 notifications: false, // when true, the browser supports notifications (may not be granted though)
5625 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5626 first_notification_id: 0, // the id of the first alarm_log entry for this session
5627 // this is used to prevent CLEAR notifications for past events
5628 // notifications_shown: new Array(),
5630 server: null, // the server to connect to for fetching alarms
5631 current: null, // the list of raised alarms - updated in the background
5632 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5634 notify: function(entry) {
5635 // console.log('alarm ' + entry.unique_id);
5637 if(entry.updated === true) {
5638 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5642 var value = entry.value;
5643 if(NETDATA.alarms.current !== null) {
5644 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5645 if(typeof t !== 'undefined' && entry.status == t.status)
5649 var name = entry.name.replace(/_/g, ' ');
5650 var status = entry.status.toLowerCase();
5651 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5652 var tag = entry.alarm_id;
5653 var icon = 'images/seo-performance-128.png';
5654 var interaction = false;
5658 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5660 switch(entry.status) {
5668 case 'UNINITIALIZED':
5672 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5673 // console.log('alarm ' + entry.unique_id + ' is not current');
5676 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5677 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5680 title = name + ' back to normal';
5681 icon = 'images/check-mark-2-128-green.png'
5682 interaction = false;
5686 if(entry.old_status === 'CRITICAL')
5687 status = 'demoted to ' + entry.status.toLowerCase();
5689 icon = 'images/alert-128-orange.png';
5690 interaction = false;
5694 if(entry.old_status === 'WARNING')
5695 status = 'escalated to ' + entry.status.toLowerCase();
5697 icon = 'images/alert-128-red.png'
5702 console.log('invalid alarm status ' + entry.status);
5707 // cleanup old notifications with the same alarm_id as this one
5708 // FIXME: it does not seem to work on any web browser!
5709 var len = NETDATA.alarms.notifications_shown.length;
5711 var n = NETDATA.alarms.notifications_shown[len];
5712 if(n.data.alarm_id === entry.alarm_id) {
5713 console.log('removing old alarm ' + n.data.unique_id);
5715 // close the notification
5718 // remove it from the array
5719 NETDATA.alarms.notifications_shown.splice(len, 1);
5720 len = NETDATA.alarms.notifications_shown.length;
5727 setTimeout(function() {
5728 // show this notification
5729 // console.log('new notification: ' + title);
5730 var n = new Notification(title, {
5731 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5733 requireInteraction: interaction,
5734 icon: NETDATA.serverDefault + icon,
5738 n.onclick = function(event) {
5739 event.preventDefault();
5740 NETDATA.alarms.onclick(event.target.data);
5744 // NETDATA.alarms.notifications_shown.push(n);
5745 // console.log(entry);
5746 }, NETDATA.alarms.ms_penalty);
5748 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5752 scrollToChart: function(chart_id) {
5753 if(typeof chart_id === 'string') {
5754 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5755 if(typeof offset !== 'undefined') {
5756 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5763 scrollToAlarm: function(alarm) {
5764 if(typeof alarm === 'object') {
5765 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5767 if(ret === true && NETDATA.options.page_is_visible === false)
5769 // alert('netdata dashboard will now scroll to chart: ' + alarm.chart + '\n\nThis alarm opened to bring the browser window in front of the screen. Click on the dashboard to prevent it from appearing again.');
5774 notifyAll: function() {
5775 // console.log('FETCHING ALARM LOG');
5776 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5777 // console.log('ALARM LOG FETCHED');
5779 if(data === null || typeof data !== 'object') {
5780 console.log('invalid alarms log response');
5784 if(data.length === 0) {
5785 console.log('received empty alarm log');
5789 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5791 data.sort(function(a, b) {
5792 if(a.unique_id > b.unique_id) return -1;
5793 if(a.unique_id < b.unique_id) return 1;
5797 NETDATA.alarms.ms_penalty = 0;
5799 var len = data.length;
5801 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5802 NETDATA.alarms.notify(data[len]);
5805 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5808 NETDATA.alarms.last_notification_id = data[0].unique_id;
5809 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5810 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5814 check_notifications: function() {
5815 // returns true if we should fire 1+ notifications
5817 if(NETDATA.alarms.notifications !== true) {
5818 // console.log('notifications not available');
5822 if(Notification.permission !== 'granted') {
5823 // console.log('notifications not granted');
5827 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5828 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5830 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5831 // console.log('new alarms detected');
5834 //else console.log('no new alarms');
5836 // else console.log('cannot process alarms');
5841 get: function(what, callback) {
5843 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5846 xhrFields: { withCredentials: true } // required for the cookie
5848 .done(function(data) {
5849 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5850 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5852 if(typeof callback === 'function')
5856 NETDATA.error(415, NETDATA.alarms.server);
5858 if(typeof callback === 'function')
5863 update_forever: function() {
5864 NETDATA.alarms.get('active', function(data) {
5866 NETDATA.alarms.current = data;
5868 if(NETDATA.alarms.check_notifications() === true) {
5869 NETDATA.alarms.notifyAll();
5872 if (typeof NETDATA.alarms.callback === 'function') {
5873 NETDATA.alarms.callback(data);
5876 // Health monitoring is disabled on this netdata
5877 if(data.status === false) return;
5880 setTimeout(NETDATA.alarms.update_forever, 10000);
5884 get_log: function(last_id, callback) {
5885 // console.log('fetching all log after ' + last_id.toString());
5887 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5890 xhrFields: { withCredentials: true } // required for the cookie
5892 .done(function(data) {
5893 if(typeof callback === 'function')
5897 NETDATA.error(416, NETDATA.alarms.server);
5899 if(typeof callback === 'function')
5905 var host = NETDATA.serverDefault;
5906 while(host.slice(-1) === '/')
5907 host = host.substring(0, host.length - 1);
5908 NETDATA.alarms.server = host;
5910 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5912 if(NETDATA.alarms.onclick === null)
5913 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5915 if(netdataShowAlarms === true) {
5916 NETDATA.alarms.update_forever();
5918 if('Notification' in window) {
5919 // console.log('notifications available');
5920 NETDATA.alarms.notifications = true;
5922 if(Notification.permission === 'default')
5923 Notification.requestPermission();
5929 // ----------------------------------------------------------------------------------------------------------------
5930 // Registry of netdata hosts
5932 NETDATA.registry = {
5933 server: null, // the netdata registry server
5934 person_guid: null, // the unique ID of this browser / user
5935 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5936 hostname: null, // the hostname of the netdata server that served dashboard.js
5937 machines: null, // the user's other URLs
5938 machines_array: null, // the user's other URLs in an array
5941 parsePersonUrls: function(person_urls) {
5942 // console.log(person_urls);
5943 NETDATA.registry.person_urls = person_urls;
5946 NETDATA.registry.machines = {};
5947 NETDATA.registry.machines_array = new Array();
5949 var now = new Date().getTime();
5950 var apu = person_urls;
5953 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5954 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5960 accesses: apu[i][3],
5962 alternate_urls: new Array()
5964 obj.alternate_urls.push(apu[i][1]);
5966 NETDATA.registry.machines[apu[i][0]] = obj;
5967 NETDATA.registry.machines_array.push(obj);
5970 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5972 var pu = NETDATA.registry.machines[apu[i][0]];
5973 if(pu.last_t < apu[i][2]) {
5975 pu.last_t = apu[i][2];
5976 pu.name = apu[i][4];
5978 pu.accesses += apu[i][3];
5979 pu.alternate_urls.push(apu[i][1]);
5984 if(typeof netdataRegistryCallback === 'function')
5985 netdataRegistryCallback(NETDATA.registry.machines_array);
5989 if(netdataRegistry !== true) return;
5991 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5993 NETDATA.registry.server = data.registry;
5994 NETDATA.registry.machine_guid = data.machine_guid;
5995 NETDATA.registry.hostname = data.hostname;
5997 NETDATA.registry.access(2, function (person_urls) {
5998 NETDATA.registry.parsePersonUrls(person_urls);
6005 hello: function(host, callback) {
6006 while(host.slice(-1) === '/')
6007 host = host.substring(0, host.length - 1);
6009 // send HELLO to a netdata server:
6010 // 1. verifies the server is reachable
6011 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6013 url: host + '/api/v1/registry?action=hello',
6016 xhrFields: { withCredentials: true } // required for the cookie
6018 .done(function(data) {
6019 if(typeof data.status !== 'string' || data.status !== 'ok') {
6020 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6024 if(typeof callback === 'function')
6028 NETDATA.error(407, host);
6030 if(typeof callback === 'function')
6035 access: function(max_redirects, callback) {
6036 // send ACCESS to a netdata registry:
6037 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6038 // 2. it responds with a list of netdata servers we know
6039 // the registry identifies us using a cookie it sets the first time we access it
6040 // the registry may respond with a redirect URL to send us to another registry
6042 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),
6045 xhrFields: { withCredentials: true } // required for the cookie
6047 .done(function(data) {
6048 var redirect = null;
6049 if(typeof data.registry === 'string')
6050 redirect = data.registry;
6052 if(typeof data.status !== 'string' || data.status !== 'ok') {
6053 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6058 if(redirect !== null && max_redirects > 0) {
6059 NETDATA.registry.server = redirect;
6060 NETDATA.registry.access(max_redirects - 1, callback);
6063 if(typeof callback === 'function')
6068 if(typeof data.person_guid === 'string')
6069 NETDATA.registry.person_guid = data.person_guid;
6071 if(typeof callback === 'function')
6072 callback(data.urls);
6076 NETDATA.error(410, NETDATA.registry.server);
6078 if(typeof callback === 'function')
6083 delete: function(delete_url, callback) {
6084 // send DELETE to a netdata registry:
6086 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),
6089 xhrFields: { withCredentials: true } // required for the cookie
6091 .done(function(data) {
6092 if(typeof data.status !== 'string' || data.status !== 'ok') {
6093 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6097 if(typeof callback === 'function')
6101 NETDATA.error(412, NETDATA.registry.server);
6103 if(typeof callback === 'function')
6108 search: function(machine_guid, callback) {
6109 // SEARCH for the URLs of a machine:
6111 url: NETDATA.registry.server + '/api/v1/registry?action=search&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&for=' + machine_guid,
6114 xhrFields: { withCredentials: true } // required for the cookie
6116 .done(function(data) {
6117 if(typeof data.status !== 'string' || data.status !== 'ok') {
6118 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6122 if(typeof callback === 'function')
6126 NETDATA.error(418, NETDATA.registry.server);
6128 if(typeof callback === 'function')
6133 switch: function(new_person_guid, callback) {
6136 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,
6139 xhrFields: { withCredentials: true } // required for the cookie
6141 .done(function(data) {
6142 if(typeof data.status !== 'string' || data.status !== 'ok') {
6143 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6147 if(typeof callback === 'function')
6151 NETDATA.error(414, NETDATA.registry.server);
6153 if(typeof callback === 'function')
6159 // ----------------------------------------------------------------------------------------------------------------
6162 if(typeof netdataPrepCallback === 'function')
6163 netdataPrepCallback();
6165 NETDATA.errorReset();
6166 NETDATA.loadRequiredCSS(0);
6168 NETDATA._loadjQuery(function() {
6169 NETDATA.loadRequiredJs(0, function() {
6170 if(typeof $().emulateTransitionEnd !== 'function') {
6171 // bootstrap is not available
6172 NETDATA.options.current.show_help = false;
6175 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6176 if(NETDATA.options.debug.main_loop === true)
6177 console.log('starting chart refresh thread');
6184 // window.NETDATA = NETDATA;
6185 // })(window, document);