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-1.12.0.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.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.min.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
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',
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 }
563 NETDATA.errorLast = {
569 NETDATA.error = function(code, msg) {
570 NETDATA.errorLast.code = code;
571 NETDATA.errorLast.message = msg;
572 NETDATA.errorLast.datetime = new Date().getTime();
574 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
577 if(typeof netdataErrorCallback === 'function') {
578 ret = netdataErrorCallback('system', code, msg);
581 if(ret && NETDATA.errorCodes[code].alert)
582 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
585 NETDATA.errorReset = function() {
586 NETDATA.errorLast.code = 0;
587 NETDATA.errorLast.message = "You are doing fine!";
588 NETDATA.errorLast.datetime = 0;
591 // ----------------------------------------------------------------------------------------------------------------
594 // When multiple charts need the same chart, we avoid downloading it
595 // multiple times (and having it in browser memory multiple time)
596 // by using this registry.
598 // Every time we download a chart definition, we save it here with .add()
599 // Then we try to get it back with .get(). If that fails, we download it.
601 NETDATA.chartRegistry = {
604 fixid: function(id) {
605 return id.replace(/:/g, "_").replace(/\//g, "_");
608 add: function(host, id, data) {
609 host = this.fixid(host);
612 if(typeof this.charts[host] === 'undefined')
613 this.charts[host] = {};
615 //console.log('added ' + host + '/' + id);
616 this.charts[host][id] = data;
619 get: function(host, id) {
620 host = this.fixid(host);
623 if(typeof this.charts[host] === 'undefined')
626 if(typeof this.charts[host][id] === 'undefined')
629 //console.log('cached ' + host + '/' + id);
630 return this.charts[host][id];
633 downloadAll: function(host, callback) {
634 while(host.slice(-1) === '/')
635 host = host.substring(0, host.length - 1);
640 url: host + '/api/v1/charts',
643 xhrFields: { withCredentials: true } // required for the cookie
645 .done(function(data) {
647 var h = NETDATA.chartRegistry.fixid(host);
648 self.charts[h] = data.charts;
650 else NETDATA.error(406, host + '/api/v1/charts');
652 if(typeof callback === 'function')
656 NETDATA.error(405, host + '/api/v1/charts');
658 if(typeof callback === 'function')
664 // ----------------------------------------------------------------------------------------------------------------
665 // Global Pan and Zoom on charts
667 // Using this structure are synchronize all the charts, so that
668 // when you pan or zoom one, all others are automatically refreshed
669 // to the same timespan.
671 NETDATA.globalPanAndZoom = {
672 seq: 0, // timestamp ms
673 // every time a chart is panned or zoomed
674 // we set the timestamp here
675 // then we use it as a sequence number
676 // to find if other charts are syncronized
679 master: null, // the master chart (state), to which all others
682 force_before_ms: null, // the timespan to sync all other charts
683 force_after_ms: null,
688 setMaster: function(state, after, before) {
689 if(NETDATA.options.current.sync_pan_and_zoom === false)
692 if(this.master !== null && this.master !== state)
693 this.master.resetChart(true, true);
695 var now = new Date().getTime();
698 this.force_after_ms = after;
699 this.force_before_ms = before;
700 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
702 if(typeof this.callback === 'function')
703 this.callback(true, after, before);
707 clearMaster: function() {
708 if(this.master !== null) {
709 var st = this.master;
716 this.force_after_ms = null;
717 this.force_before_ms = null;
718 NETDATA.options.auto_refresher_stop_until = 0;
720 if(typeof this.callback === 'function')
721 this.callback(false, 0, 0);
724 // is the given state the master of the global
725 // pan and zoom sync?
726 isMaster: function(state) {
727 if(this.master === state) return true;
731 // are we currently have a global pan and zoom sync?
732 isActive: function() {
733 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
737 // check if a chart, other than the master
738 // needs to be refreshed, due to the global pan and zoom
739 shouldBeAutoRefreshed: function(state) {
740 if(this.master === null || this.seq === 0)
743 //if(state.needsRecreation())
746 if(state.tm.pan_and_zoom_seq === this.seq)
753 // ----------------------------------------------------------------------------------------------------------------
754 // dimensions selection
757 // move color assignment to dimensions, here
759 dimensionStatus = function(parent, label, name_div, value_div, color) {
760 this.enabled = false;
761 this.parent = parent;
763 this.name_div = null;
764 this.value_div = null;
765 this.color = NETDATA.themes.current.foreground;
767 if(parent.selected_count > parent.unselected_count)
768 this.selected = true;
770 this.selected = false;
772 this.setOptions(name_div, value_div, color);
775 dimensionStatus.prototype.invalidate = function() {
776 this.name_div = null;
777 this.value_div = null;
778 this.enabled = false;
781 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
784 if(this.name_div != name_div) {
785 this.name_div = name_div;
786 this.name_div.title = this.label;
787 this.name_div.style.color = this.color;
788 if(this.selected === false)
789 this.name_div.className = 'netdata-legend-name not-selected';
791 this.name_div.className = 'netdata-legend-name selected';
794 if(this.value_div != value_div) {
795 this.value_div = value_div;
796 this.value_div.title = this.label;
797 this.value_div.style.color = this.color;
798 if(this.selected === false)
799 this.value_div.className = 'netdata-legend-value not-selected';
801 this.value_div.className = 'netdata-legend-value selected';
808 dimensionStatus.prototype.setHandler = function() {
809 if(this.enabled === false) return;
813 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
814 this.name_div.onclick = this.value_div.onclick = function(e) {
816 if(ds.isSelected()) {
818 if(e.shiftKey === true || e.ctrlKey === true) {
819 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
822 if(ds.parent.countSelected() === 0)
823 ds.parent.selectAll();
826 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
827 if(ds.parent.countSelected() === 1) {
828 ds.parent.selectAll();
831 ds.parent.selectNone();
837 // this is not selected
838 if(e.shiftKey === true || e.ctrlKey === true) {
839 // control or shift key is pressed -> select this too
843 // no key is pressed -> select only this
844 ds.parent.selectNone();
849 ds.parent.state.redrawChart();
853 dimensionStatus.prototype.select = function() {
854 if(this.enabled === false) return;
856 this.name_div.className = 'netdata-legend-name selected';
857 this.value_div.className = 'netdata-legend-value selected';
858 this.selected = true;
861 dimensionStatus.prototype.unselect = function() {
862 if(this.enabled === false) return;
864 this.name_div.className = 'netdata-legend-name not-selected';
865 this.value_div.className = 'netdata-legend-value hidden';
866 this.selected = false;
869 dimensionStatus.prototype.isSelected = function() {
870 return(this.enabled === true && this.selected === true);
873 // ----------------------------------------------------------------------------------------------------------------
875 dimensionsVisibility = function(state) {
878 this.dimensions = {};
879 this.selected_count = 0;
880 this.unselected_count = 0;
883 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
884 if(typeof this.dimensions[label] === 'undefined') {
886 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
889 this.dimensions[label].setOptions(name_div, value_div, color);
891 return this.dimensions[label];
894 dimensionsVisibility.prototype.dimensionGet = function(label) {
895 return this.dimensions[label];
898 dimensionsVisibility.prototype.invalidateAll = function() {
899 for(var d in this.dimensions)
900 this.dimensions[d].invalidate();
903 dimensionsVisibility.prototype.selectAll = function() {
904 for(var d in this.dimensions)
905 this.dimensions[d].select();
908 dimensionsVisibility.prototype.countSelected = function() {
910 for(var d in this.dimensions)
911 if(this.dimensions[d].isSelected()) i++;
916 dimensionsVisibility.prototype.selectNone = function() {
917 for(var d in this.dimensions)
918 this.dimensions[d].unselect();
921 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
922 var ret = new Array();
923 this.selected_count = 0;
924 this.unselected_count = 0;
926 for(var i = 0, len = array.length; i < len ; i++) {
927 var ds = this.dimensions[array[i]];
928 if(typeof ds === 'undefined') {
929 // console.log(array[i] + ' is not found');
934 if(ds.isSelected()) {
936 this.selected_count++;
940 this.unselected_count++;
944 if(this.selected_count === 0 && this.unselected_count !== 0) {
946 return this.selected2BooleanArray(array);
953 // ----------------------------------------------------------------------------------------------------------------
954 // global selection sync
956 NETDATA.globalSelectionSync = {
963 if(this.state !== null)
964 this.state.globalSelectionSyncStop();
968 if(this.state !== null) {
969 this.state.globalSelectionSyncDelay();
974 // ----------------------------------------------------------------------------------------------------------------
975 // Our state object, where all per-chart values are stored
977 chartState = function(element) {
978 var self = $(element);
979 this.element = element;
982 // all private functions should use 'that', instead of 'this'
986 * show an error instead of the chart
988 var error = function(msg) {
991 if(typeof netdataErrorCallback === 'function') {
992 ret = netdataErrorCallback('chart', that.id, msg);
996 that.element.innerHTML = that.id + ': ' + msg;
997 that.enabled = false;
998 that.current = that.pan;
1002 // GUID - a unique identifier for the chart
1003 this.uuid = NETDATA.guid();
1005 // string - the name of chart
1006 this.id = self.data('netdata');
1008 // string - the key for localStorage settings
1009 this.settings_id = self.data('id') || null;
1011 // the user given dimensions of the element
1012 this.width = self.data('width') || NETDATA.chartDefaults.width;
1013 this.height = self.data('height') || NETDATA.chartDefaults.height;
1015 if(this.settings_id !== null) {
1016 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1017 // this is the callback that will be called
1018 // if and when the user resets all localStorage variables
1019 // to their defaults
1021 resizeChartToHeight(height);
1025 // string - the netdata server URL, without any path
1026 this.host = self.data('host') || NETDATA.chartDefaults.host;
1028 // make sure the host does not end with /
1029 // all netdata API requests use absolute paths
1030 while(this.host.slice(-1) === '/')
1031 this.host = this.host.substring(0, this.host.length - 1);
1033 // string - the grouping method requested by the user
1034 this.method = self.data('method') || NETDATA.chartDefaults.method;
1036 // the time-range requested by the user
1037 this.after = self.data('after') || NETDATA.chartDefaults.after;
1038 this.before = self.data('before') || NETDATA.chartDefaults.before;
1040 // the pixels per point requested by the user
1041 this.pixels_per_point = self.data('pixels-per-point') || 1;
1042 this.points = self.data('points') || null;
1044 // the dimensions requested by the user
1045 this.dimensions = self.data('dimensions') || null;
1047 // the chart library requested by the user
1048 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1050 // object - the chart library used
1051 this.library = null;
1055 this.colors_assigned = {};
1056 this.colors_available = null;
1058 // the element already created by the user
1059 this.element_message = null;
1061 // the element with the chart
1062 this.element_chart = null;
1064 // the element with the legend of the chart (if created by us)
1065 this.element_legend = null;
1066 this.element_legend_childs = {
1076 this.chart_url = null; // string - the url to download chart info
1077 this.chart = null; // object - the chart as downloaded from the server
1079 this.title = self.data('title') || null; // the title of the chart
1080 this.units = self.data('units') || null; // the units of the chart dimensions
1081 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1083 this.running = false; // boolean - true when the chart is being refreshed now
1084 this.validated = false; // boolean - has the chart been validated?
1085 this.enabled = true; // boolean - is the chart enabled for refresh?
1086 this.paused = false; // boolean - is the chart paused for any reason?
1087 this.selected = false; // boolean - is the chart shown a selection?
1088 this.debug = false; // boolean - console.log() debug info about this chart
1090 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1091 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1092 this.requested_after = null; // milliseconds - the timestamp of the request after param
1093 this.requested_before = null; // milliseconds - the timestamp of the request before param
1094 this.requested_padding = null;
1095 this.view_after = 0;
1096 this.view_before = 0;
1101 force_update_at: 0, // the timestamp to force the update at
1102 force_before_ms: null,
1103 force_after_ms: null
1108 force_update_at: 0, // the timestamp to force the update at
1109 force_before_ms: null,
1110 force_after_ms: null
1115 force_update_at: 0, // the timestamp to force the update at
1116 force_before_ms: null,
1117 force_after_ms: null
1120 // this is a pointer to one of the sub-classes below
1122 this.current = this.auto;
1124 // check the requested library is available
1125 // we don't initialize it here - it will be initialized when
1126 // this chart will be first used
1127 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1128 NETDATA.error(402, that.library_name);
1129 error('chart library "' + that.library_name + '" is not found');
1132 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1133 NETDATA.error(403, that.library_name);
1134 error('chart library "' + that.library_name + '" is not enabled');
1138 that.library = NETDATA.chartLibraries[that.library_name];
1140 // milliseconds - the time the last refresh took
1141 this.refresh_dt_ms = 0;
1143 // if we need to report the rendering speed
1144 // find the element that needs to be updated
1145 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1147 if(refresh_dt_element_name !== null)
1148 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1150 this.refresh_dt_element = null;
1152 this.dimensions_visibility = new dimensionsVisibility(this);
1154 this._updating = false;
1156 // ============================================================================================================
1157 // PRIVATE FUNCTIONS
1159 var createDOM = function() {
1160 if(that.enabled === false) return;
1162 if(that.element_message !== null) that.element_message.innerHTML = '';
1163 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1164 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1166 that.element.innerHTML = '';
1168 that.element_message = document.createElement('div');
1169 that.element_message.className = ' netdata-message hidden';
1170 that.element.appendChild(that.element_message);
1172 that.element_chart = document.createElement('div');
1173 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1174 that.element.appendChild(that.element_chart);
1176 if(that.hasLegend() === true) {
1177 that.element.className = "netdata-container-with-legend";
1178 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1180 that.element_legend = document.createElement('div');
1181 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1182 that.element.appendChild(that.element_legend);
1185 that.element.className = "netdata-container";
1186 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1188 that.element_legend = null;
1190 that.element_legend_childs.series = null;
1192 if(typeof(that.width) === 'string')
1193 $(that.element).css('width', that.width);
1194 else if(typeof(that.width) === 'number')
1195 $(that.element).css('width', that.width + 'px');
1197 if(typeof(that.library.aspect_ratio) === 'undefined') {
1198 if(typeof(that.height) === 'string')
1199 $(that.element).css('height', that.height);
1200 else if(typeof(that.height) === 'number')
1201 $(that.element).css('height', that.height + 'px');
1204 var w = that.element.offsetWidth;
1205 if(w === null || w === 0) {
1206 // the div is hidden
1207 // this will resize the chart when next viewed
1208 that.tm.last_resized = 0;
1211 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1214 if(NETDATA.chartDefaults.min_width !== null)
1215 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1217 that.tm.last_dom_created = new Date().getTime();
1223 * initialize state variables
1224 * destroy all (possibly) created state elements
1225 * create the basic DOM for a chart
1227 var init = function() {
1228 if(that.enabled === false) return;
1230 that.paused = false;
1231 that.selected = false;
1233 that.chart_created = false; // boolean - is the library.create() been called?
1234 that.updates_counter = 0; // numeric - the number of refreshes made so far
1235 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1236 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1239 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1240 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1241 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1243 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1244 last_updated: 0, // the timestamp the chart last updated with data
1245 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1247 // Used with NETDATA.globalPanAndZoom.seq
1248 last_visible_check: 0, // the time we last checked if it is visible
1249 last_resized: 0, // the time the chart was resized
1250 last_hidden: 0, // the time the chart was hidden
1251 last_unhidden: 0, // the time the chart was unhidden
1252 last_autorefreshed: 0 // the time the chart was last refreshed
1255 that.data = null; // the last data as downloaded from the netdata server
1256 that.data_url = 'invalid://'; // string - the last url used to update the chart
1257 that.data_points = 0; // number - the number of points returned from netdata
1258 that.data_after = 0; // milliseconds - the first timestamp of the data
1259 that.data_before = 0; // milliseconds - the last timestamp of the data
1260 that.data_update_every = 0; // milliseconds - the frequency to update the data
1262 that.tm.last_initialized = new Date().getTime();
1265 that.setMode('auto');
1268 var maxMessageFontSize = function() {
1269 // normally we want a font size, as tall as the element
1270 var h = that.element_message.clientHeight;
1272 // but give it some air, 20% let's say, or 5 pixels min
1273 var lost = Math.max(h * 0.2, 5);
1276 // center the text, vertically
1277 var paddingTop = (lost - 5) / 2;
1279 // but check the width too
1280 // it should fit 10 characters in it
1281 var w = that.element_message.clientWidth / 10;
1283 paddingTop += (h - w) / 2;
1287 // and don't make it too huge
1288 // 5% of the screen size is good
1289 if(h > screen.height / 20) {
1290 paddingTop += (h - (screen.height / 20)) / 2;
1291 h = screen.height / 20;
1295 that.element_message.style.fontSize = h.toString() + 'px';
1296 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1299 var showMessage = function(msg) {
1300 that.element_message.className = 'netdata-message';
1301 that.element_message.innerHTML = msg;
1302 that.element_message.style.fontSize = 'x-small';
1303 that.element_message.style.paddingTop = '0px';
1304 that.___messageHidden___ = undefined;
1307 var showMessageIcon = function(icon) {
1308 that.element_message.innerHTML = icon;
1309 that.element_message.className = 'netdata-message icon';
1310 maxMessageFontSize();
1311 that.___messageHidden___ = undefined;
1314 var hideMessage = function() {
1315 if(typeof that.___messageHidden___ === 'undefined') {
1316 that.___messageHidden___ = true;
1317 that.element_message.className = 'netdata-message hidden';
1321 var showRendering = function() {
1323 if(that.chart !== null) {
1324 if(that.chart.chart_type === 'line')
1325 icon = '<i class="fa fa-line-chart"></i>';
1327 icon = '<i class="fa fa-area-chart"></i>';
1330 icon = '<i class="fa fa-area-chart"></i>';
1332 showMessageIcon(icon + ' netdata');
1335 var showLoading = function() {
1336 if(that.chart_created === false) {
1337 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1343 var isHidden = function() {
1344 if(typeof that.___chartIsHidden___ !== 'undefined')
1350 // hide the chart, when it is not visible - called from isVisible()
1351 var hideChart = function() {
1352 // hide it, if it is not already hidden
1353 if(isHidden() === true) return;
1355 if(that.chart_created === true) {
1356 if(NETDATA.options.current.destroy_on_hide === true) {
1357 // we should destroy it
1362 that.element_chart.style.display = 'none';
1363 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1364 that.tm.last_hidden = new Date().getTime();
1367 // This works, but I not sure there are no corner cases somewhere
1368 // so it is commented - if the user has memory issues he can
1369 // set Destroy on Hide for all charts
1370 // that.data = null;
1374 that.___chartIsHidden___ = true;
1377 // unhide the chart, when it is visible - called from isVisible()
1378 var unhideChart = function() {
1379 if(isHidden() === false) return;
1381 that.___chartIsHidden___ = undefined;
1382 that.updates_since_last_unhide = 0;
1384 if(that.chart_created === false) {
1385 // we need to re-initialize it, to show our background
1386 // logo in bootstrap tabs, until the chart loads
1390 that.tm.last_unhidden = new Date().getTime();
1391 that.element_chart.style.display = '';
1392 if(that.element_legend !== null) that.element_legend.style.display = '';
1398 var canBeRendered = function() {
1399 if(isHidden() === true || that.isVisible(true) === false)
1405 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1406 var callChartLibraryUpdateSafely = function(data) {
1409 if(canBeRendered() === false)
1412 if(NETDATA.options.debug.chart_errors === true)
1413 status = that.library.update(that, data);
1416 status = that.library.update(that, data);
1423 if(status === false) {
1424 error('chart failed to be updated as ' + that.library_name);
1431 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1432 var callChartLibraryCreateSafely = function(data) {
1435 if(canBeRendered() === false)
1438 if(NETDATA.options.debug.chart_errors === true)
1439 status = that.library.create(that, data);
1442 status = that.library.create(that, data);
1449 if(status === false) {
1450 error('chart failed to be created as ' + that.library_name);
1454 that.chart_created = true;
1455 that.updates_since_last_creation = 0;
1459 // ----------------------------------------------------------------------------------------------------------------
1462 // resizeChart() - private
1463 // to be called just before the chart library to make sure that
1464 // a properly sized dom is available
1465 var resizeChart = function() {
1466 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1467 if(that.chart_created === false) return;
1469 if(that.needsRecreation()) {
1472 else if(typeof that.library.resize === 'function') {
1473 that.library.resize(that);
1475 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1476 $(that.element_legend_childs.nano).nanoScroller();
1478 maxMessageFontSize();
1481 that.tm.last_resized = new Date().getTime();
1485 // this is the actual chart resize algorithm
1487 // - resize the entire container
1488 // - update the internal states
1489 // - resize the chart as the div changes height
1490 // - update the scrollbar of the legend
1491 var resizeChartToHeight = function(h) {
1493 that.element.style.height = h;
1495 if(that.settings_id !== null)
1496 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1498 var now = new Date().getTime();
1499 NETDATA.options.last_page_scroll = now;
1500 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1503 that.tm.last_resized = 0;
1507 this.resizeHandler = function(e) {
1510 if(typeof this.event_resize === 'undefined'
1511 || this.event_resize.chart_original_w === 'undefined'
1512 || this.event_resize.chart_original_h === 'undefined')
1513 this.event_resize = {
1514 chart_original_w: this.element.clientWidth,
1515 chart_original_h: this.element.clientHeight,
1519 if(e.type === 'touchstart') {
1520 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1521 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1524 this.event_resize.mouse_start_x = e.clientX;
1525 this.event_resize.mouse_start_y = e.clientY;
1528 this.event_resize.chart_start_w = this.element.clientWidth;
1529 this.event_resize.chart_start_h = this.element.clientHeight;
1530 this.event_resize.chart_last_w = this.element.clientWidth;
1531 this.event_resize.chart_last_h = this.element.clientHeight;
1533 var now = new Date().getTime();
1534 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1535 // double click / double tap event
1537 // the optimal height of the chart
1538 // showing the entire legend
1539 var optimal = this.event_resize.chart_last_h
1540 + this.element_legend_childs.content.scrollHeight
1541 - this.element_legend_childs.content.clientHeight;
1543 // if we are not optimal, be optimal
1544 if(this.event_resize.chart_last_h != optimal)
1545 resizeChartToHeight(optimal.toString() + 'px');
1547 // else if we do not have the original height
1548 // reset to the original height
1549 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1550 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1553 this.event_resize.last = now;
1555 // process movement event
1556 document.onmousemove =
1557 document.ontouchmove =
1558 this.element_legend_childs.resize_handler.onmousemove =
1559 this.element_legend_childs.resize_handler.ontouchmove =
1564 case 'mousemove': y = e.clientY; break;
1565 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1569 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1571 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1572 resizeChartToHeight(newH.toString() + 'px');
1573 that.event_resize.chart_last_h = newH;
1578 // process end event
1579 document.onmouseup =
1580 document.ontouchend =
1581 this.element_legend_childs.resize_handler.onmouseup =
1582 this.element_legend_childs.resize_handler.ontouchend =
1584 // remove all the hooks
1585 document.onmouseup =
1586 document.onmousemove =
1587 document.ontouchmove =
1588 document.ontouchend =
1589 that.element_legend_childs.resize_handler.onmousemove =
1590 that.element_legend_childs.resize_handler.ontouchmove =
1591 that.element_legend_childs.resize_handler.onmouseout =
1592 that.element_legend_childs.resize_handler.onmouseup =
1593 that.element_legend_childs.resize_handler.ontouchend =
1596 // allow auto-refreshes
1597 NETDATA.options.auto_refresher_stop_until = 0;
1603 var noDataToShow = function() {
1604 showMessageIcon('<i class="fa fa-warning"></i> empty');
1605 that.legendUpdateDOM();
1606 that.tm.last_autorefreshed = new Date().getTime();
1607 // that.data_update_every = 30 * 1000;
1608 //that.element_chart.style.display = 'none';
1609 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1610 //that.___chartIsHidden___ = true;
1613 // ============================================================================================================
1616 this.error = function(msg) {
1620 this.setMode = function(m) {
1621 if(this.current !== null && this.current.name === m) return;
1624 this.current = this.auto;
1625 else if(m === 'pan')
1626 this.current = this.pan;
1627 else if(m === 'zoom')
1628 this.current = this.zoom;
1630 this.current = this.auto;
1632 this.current.force_update_at = 0;
1633 this.current.force_before_ms = null;
1634 this.current.force_after_ms = null;
1636 this.tm.last_mode_switch = new Date().getTime();
1639 // ----------------------------------------------------------------------------------------------------------------
1640 // global selection sync
1642 // prevent to global selection sync for some time
1643 this.globalSelectionSyncDelay = function(ms) {
1644 if(NETDATA.options.current.sync_selection === false)
1647 if(typeof ms === 'number')
1648 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1650 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1653 // can we globally apply selection sync?
1654 this.globalSelectionSyncAbility = function() {
1655 if(NETDATA.options.current.sync_selection === false)
1658 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1664 this.globalSelectionSyncIsMaster = function() {
1665 if(NETDATA.globalSelectionSync.state === this)
1671 // this chart is the master of the global selection sync
1672 this.globalSelectionSyncBeMaster = function() {
1674 if(this.globalSelectionSyncIsMaster()) {
1675 if(this.debug === true)
1676 this.log('sync: I am the master already.');
1681 if(NETDATA.globalSelectionSync.state) {
1682 if(this.debug === true)
1683 this.log('sync: I am not the sync master. Resetting global sync.');
1685 this.globalSelectionSyncStop();
1688 // become the master
1689 if(this.debug === true)
1690 this.log('sync: becoming sync master.');
1692 this.selected = true;
1693 NETDATA.globalSelectionSync.state = this;
1695 // find the all slaves
1696 var targets = NETDATA.options.targets;
1697 var len = targets.length;
1702 if(this.debug === true)
1703 st.log('sync: not adding me to sync');
1705 else if(st.globalSelectionSyncIsEligible()) {
1706 if(this.debug === true)
1707 st.log('sync: adding to sync as slave');
1709 st.globalSelectionSyncBeSlave();
1713 // this.globalSelectionSyncDelay(100);
1716 // can the chart participate to the global selection sync as a slave?
1717 this.globalSelectionSyncIsEligible = function() {
1718 if(this.enabled === true
1719 && this.library !== null
1720 && typeof this.library.setSelection === 'function'
1721 && this.isVisible() === true
1722 && this.chart_created === true)
1728 // this chart becomes a slave of the global selection sync
1729 this.globalSelectionSyncBeSlave = function() {
1730 if(NETDATA.globalSelectionSync.state !== this)
1731 NETDATA.globalSelectionSync.slaves.push(this);
1734 // sync all the visible charts to the given time
1735 // this is to be called from the chart libraries
1736 this.globalSelectionSync = function(t) {
1737 if(this.globalSelectionSyncAbility() === false) {
1738 if(this.debug === true)
1739 this.log('sync: cannot sync (yet?).');
1744 if(this.globalSelectionSyncIsMaster() === false) {
1745 if(this.debug === true)
1746 this.log('sync: trying to be sync master.');
1748 this.globalSelectionSyncBeMaster();
1750 if(this.globalSelectionSyncAbility() === false) {
1751 if(this.debug === true)
1752 this.log('sync: cannot sync (yet?).');
1758 NETDATA.globalSelectionSync.last_t = t;
1759 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1764 // stop syncing all charts to the given time
1765 this.globalSelectionSyncStop = function() {
1766 if(NETDATA.globalSelectionSync.slaves.length) {
1767 if(this.debug === true)
1768 this.log('sync: cleaning up...');
1770 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1772 if(that.debug === true)
1773 st.log('sync: not adding me to sync stop');
1776 if(that.debug === true)
1777 st.log('sync: removed slave from sync');
1779 st.clearSelection();
1783 NETDATA.globalSelectionSync.last_t = 0;
1784 NETDATA.globalSelectionSync.slaves = [];
1785 NETDATA.globalSelectionSync.state = null;
1788 this.clearSelection();
1791 this.setSelection = function(t) {
1792 if(typeof this.library.setSelection === 'function') {
1793 if(this.library.setSelection(this, t) === true)
1794 this.selected = true;
1796 this.selected = false;
1798 else this.selected = true;
1800 if(this.selected === true && this.debug === true)
1801 this.log('selection set to ' + t.toString());
1803 return this.selected;
1806 this.clearSelection = function() {
1807 if(this.selected === true) {
1808 if(typeof this.library.clearSelection === 'function') {
1809 if(this.library.clearSelection(this) === true)
1810 this.selected = false;
1812 this.selected = true;
1814 else this.selected = false;
1816 if(this.selected === false && this.debug === true)
1817 this.log('selection cleared');
1822 return this.selected;
1825 // find if a timestamp (ms) is shown in the current chart
1826 this.timeIsVisible = function(t) {
1827 if(t >= this.data_after && t <= this.data_before)
1832 this.calculateRowForTime = function(t) {
1833 if(this.timeIsVisible(t) === false) return -1;
1834 return Math.floor((t - this.data_after) / this.data_update_every);
1837 // ----------------------------------------------------------------------------------------------------------------
1840 this.log = function(msg) {
1841 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1844 this.pauseChart = function() {
1845 if(this.paused === false) {
1846 if(this.debug === true)
1847 this.log('pauseChart()');
1853 this.unpauseChart = function() {
1854 if(this.paused === true) {
1855 if(this.debug === true)
1856 this.log('unpauseChart()');
1858 this.paused = false;
1862 this.resetChart = function(dont_clear_master, dont_update) {
1863 if(this.debug === true)
1864 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1866 if(typeof dont_clear_master === 'undefined')
1867 dont_clear_master = false;
1869 if(typeof dont_update === 'undefined')
1870 dont_update = false;
1872 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1873 if(this.debug === true)
1874 this.log('resetChart() diverting to clearMaster().');
1875 // this will call us back with master === true
1876 NETDATA.globalPanAndZoom.clearMaster();
1880 this.clearSelection();
1882 this.tm.pan_and_zoom_seq = 0;
1884 this.setMode('auto');
1885 this.current.force_update_at = 0;
1886 this.current.force_before_ms = null;
1887 this.current.force_after_ms = null;
1888 this.tm.last_autorefreshed = 0;
1889 this.paused = false;
1890 this.selected = false;
1891 this.enabled = true;
1892 // this.debug = false;
1894 // do not update the chart here
1895 // or the chart will flip-flop when it is the master
1896 // of a selection sync and another chart becomes
1899 if(dont_update !== true && this.isVisible() === true) {
1904 this.updateChartPanOrZoom = function(after, before) {
1905 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1908 if(this.debug === true)
1911 if(before < after) {
1912 if(this.debug === true)
1913 this.log(logme + 'flipped parameters, rejecting it.');
1918 if(typeof this.fixed_min_duration === 'undefined')
1919 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1921 var min_duration = this.fixed_min_duration;
1922 var current_duration = Math.round(this.view_before - this.view_after);
1924 // round the numbers
1925 after = Math.round(after);
1926 before = Math.round(before);
1928 // align them to update_every
1929 // stretching them further away
1930 after -= after % this.data_update_every;
1931 before += this.data_update_every - (before % this.data_update_every);
1933 // the final wanted duration
1934 var wanted_duration = before - after;
1936 // to allow panning, accept just a point below our minimum
1937 if((current_duration - this.data_update_every) < min_duration)
1938 min_duration = current_duration - this.data_update_every;
1940 // we do it, but we adjust to minimum size and return false
1941 // when the wanted size is below the current and the minimum
1943 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1944 if(this.debug === true)
1945 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1947 min_duration = this.fixed_min_duration;
1949 var dt = (min_duration - wanted_duration) / 2;
1952 wanted_duration = before - after;
1956 var tolerance = this.data_update_every * 2;
1957 var movement = Math.abs(before - this.view_before);
1959 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1960 if(this.debug === true)
1961 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);
1965 if(this.current.name === 'auto') {
1966 this.log(logme + 'caller called me with mode: ' + this.current.name);
1967 this.setMode('pan');
1970 if(this.debug === true)
1971 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);
1973 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1974 this.current.force_after_ms = after;
1975 this.current.force_before_ms = before;
1976 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1980 this.legendFormatValue = function(value) {
1981 if(value === null || value === 'undefined') return '-';
1982 if(typeof value !== 'number') return value;
1984 var abs = Math.abs(value);
1985 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1986 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1987 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1988 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1989 return (Math.round(value * 10000) / 10000).toLocaleString();
1992 this.legendSetLabelValue = function(label, value) {
1993 var series = this.element_legend_childs.series[label];
1994 if(typeof series === 'undefined') return;
1995 if(series.value === null && series.user === null) return;
1997 // if the value has not changed, skip DOM update
1998 //if(series.last === value) return;
2001 if(typeof value === 'number') {
2002 var v = Math.abs(value);
2003 s = r = this.legendFormatValue(value);
2005 if(typeof series.last === 'number') {
2006 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2007 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2008 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2010 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2015 series.last = value;
2018 if(series.value !== null) series.value.innerHTML = s;
2019 if(series.user !== null) series.user.innerHTML = r;
2022 this.legendSetDate = function(ms) {
2023 if(typeof ms !== 'number') {
2024 this.legendShowUndefined();
2028 var d = new Date(ms);
2030 if(this.element_legend_childs.title_date)
2031 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2033 if(this.element_legend_childs.title_time)
2034 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2036 if(this.element_legend_childs.title_units)
2037 this.element_legend_childs.title_units.innerHTML = this.units;
2040 this.legendShowUndefined = function() {
2041 if(this.element_legend_childs.title_date)
2042 this.element_legend_childs.title_date.innerHTML = ' ';
2044 if(this.element_legend_childs.title_time)
2045 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2047 if(this.element_legend_childs.title_units)
2048 this.element_legend_childs.title_units.innerHTML = ' ';
2050 if(this.data && this.element_legend_childs.series !== null) {
2051 var labels = this.data.dimension_names;
2052 var i = labels.length;
2054 var label = labels[i];
2056 if(typeof label === 'undefined') continue;
2057 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2058 this.legendSetLabelValue(label, null);
2063 this.legendShowLatestValues = function() {
2064 if(this.chart === null) return;
2065 if(this.selected) return;
2067 if(this.data === null || this.element_legend_childs.series === null) {
2068 this.legendShowUndefined();
2072 var show_undefined = true;
2073 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2074 show_undefined = false;
2076 if(show_undefined) {
2077 this.legendShowUndefined();
2081 this.legendSetDate(this.view_before);
2083 var labels = this.data.dimension_names;
2084 var i = labels.length;
2086 var label = labels[i];
2088 if(typeof label === 'undefined') continue;
2089 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2092 this.legendSetLabelValue(label, null);
2094 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2098 this.legendReset = function() {
2099 this.legendShowLatestValues();
2102 // this should be called just ONCE per dimension per chart
2103 this._chartDimensionColor = function(label) {
2104 if(this.colors === null) this.chartColors();
2106 if(typeof this.colors_assigned[label] === 'undefined') {
2107 if(this.colors_available.length === 0) {
2108 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2109 this.colors_available.push(NETDATA.themes.current.colors[i]);
2112 this.colors_assigned[label] = this.colors_available.shift();
2114 if(this.debug === true)
2115 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2118 if(this.debug === true)
2119 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2122 this.colors.push(this.colors_assigned[label]);
2123 return this.colors_assigned[label];
2126 this.chartColors = function() {
2127 if(this.colors !== null) return this.colors;
2129 this.colors = new Array();
2130 this.colors_available = new Array();
2133 var c = $(this.element).data('colors');
2134 // this.log('read colors: ' + c);
2135 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2136 if(typeof c !== 'string') {
2137 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2144 for(i = 0, len = c.length; i < len ; i++) {
2146 this.colors_available.push(c[i]);
2147 // this.log('adding color: ' + c[i]);
2153 // push all the standard colors too
2154 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2155 this.colors_available.push(NETDATA.themes.current.colors[i]);
2160 this.legendUpdateDOM = function() {
2163 // check that the legend DOM is up to date for the downloaded dimensions
2164 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2165 // this.log('the legend does not have any series - requesting legend update');
2168 else if(this.data === null) {
2169 // this.log('the chart does not have any data - requesting legend update');
2172 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2176 var labels = this.data.dimension_names.toString();
2177 if(labels !== this.element_legend_childs.series.labels_key) {
2180 if(this.debug === true)
2181 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2185 if(needed === false) {
2186 // make sure colors available
2189 // do we have to update the current values?
2190 // we do this, only when the visible chart is current
2191 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2192 if(this.debug === true)
2193 this.log('chart is in latest position... updating values on legend...');
2195 //var labels = this.data.dimension_names;
2196 //var i = labels.length;
2198 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2202 if(this.colors === null) {
2203 // this is the first time we update the chart
2204 // let's assign colors to all dimensions
2205 if(this.library.track_colors() === true)
2206 for(var dim in this.chart.dimensions)
2207 this._chartDimensionColor(this.chart.dimensions[dim].name);
2209 // we will re-generate the colors for the chart
2210 // based on the selected dimensions
2213 if(this.debug === true)
2214 this.log('updating Legend DOM');
2216 // mark all dimensions as invalid
2217 this.dimensions_visibility.invalidateAll();
2219 var genLabel = function(state, parent, dim, name, count) {
2220 var color = state._chartDimensionColor(name);
2222 var user_element = null;
2223 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2224 if(user_id !== null) {
2225 user_element = document.getElementById(user_id) || null;
2226 if(user_element === null)
2227 state.log('Cannot find element with id: ' + user_id);
2230 state.element_legend_childs.series[name] = {
2231 name: document.createElement('span'),
2232 value: document.createElement('span'),
2237 var label = state.element_legend_childs.series[name];
2239 // create the dimension visibility tracking for this label
2240 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2242 var rgb = NETDATA.colorHex2Rgb(color);
2243 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2244 + state.chart.chart_type
2245 + '" style="background-color: '
2246 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2247 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2249 var text = document.createTextNode(' ' + name);
2250 label.name.appendChild(text);
2253 parent.appendChild(document.createElement('br'));
2255 parent.appendChild(label.name);
2256 parent.appendChild(label.value);
2259 var content = document.createElement('div');
2261 if(this.hasLegend()) {
2262 this.element_legend_childs = {
2264 resize_handler: document.createElement('div'),
2265 toolbox: document.createElement('div'),
2266 toolbox_left: document.createElement('div'),
2267 toolbox_right: document.createElement('div'),
2268 toolbox_reset: document.createElement('div'),
2269 toolbox_zoomin: document.createElement('div'),
2270 toolbox_zoomout: document.createElement('div'),
2271 toolbox_volume: document.createElement('div'),
2272 title_date: document.createElement('span'),
2273 title_time: document.createElement('span'),
2274 title_units: document.createElement('span'),
2275 nano: document.createElement('div'),
2277 paneClass: 'netdata-legend-series-pane',
2278 sliderClass: 'netdata-legend-series-slider',
2279 contentClass: 'netdata-legend-series-content',
2280 enabledClass: '__enabled',
2281 flashedClass: '__flashed',
2282 activeClass: '__active',
2284 alwaysVisible: true,
2290 this.element_legend.innerHTML = '';
2292 if(this.library.toolboxPanAndZoom !== null) {
2294 function get_pan_and_zoom_step(event) {
2296 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2298 else if (event.shiftKey)
2299 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2301 else if (event.altKey)
2302 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2305 return NETDATA.options.current.pan_and_zoom_factor;
2308 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2309 this.element.appendChild(this.element_legend_childs.toolbox);
2311 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2312 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2313 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2314 this.element_legend_childs.toolbox_left.onclick = function(e) {
2317 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2318 var before = that.view_before - step;
2319 var after = that.view_after - step;
2320 if(after >= that.netdata_first)
2321 that.library.toolboxPanAndZoom(that, after, before);
2323 if(NETDATA.options.current.show_help === true)
2324 $(this.element_legend_childs.toolbox_left).popover({
2329 placement: 'bottom',
2330 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2332 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>'
2336 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2337 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2338 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2339 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2341 NETDATA.resetAllCharts(that);
2343 if(NETDATA.options.current.show_help === true)
2344 $(this.element_legend_childs.toolbox_reset).popover({
2349 placement: 'bottom',
2350 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2351 title: 'Chart Reset',
2352 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>'
2355 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2356 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2357 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2358 this.element_legend_childs.toolbox_right.onclick = function(e) {
2360 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2361 var before = that.view_before + step;
2362 var after = that.view_after + step;
2363 if(before <= that.netdata_last)
2364 that.library.toolboxPanAndZoom(that, after, before);
2366 if(NETDATA.options.current.show_help === true)
2367 $(this.element_legend_childs.toolbox_right).popover({
2372 placement: 'bottom',
2373 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2375 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>'
2379 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2380 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2381 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2382 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2384 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2385 var before = that.view_before - dt;
2386 var after = that.view_after + dt;
2387 that.library.toolboxPanAndZoom(that, after, before);
2389 if(NETDATA.options.current.show_help === true)
2390 $(this.element_legend_childs.toolbox_zoomin).popover({
2395 placement: 'bottom',
2396 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2397 title: 'Chart Zoom In',
2398 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>'
2401 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2402 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2403 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2404 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2406 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);
2407 var before = that.view_before + dt;
2408 var after = that.view_after - dt;
2410 that.library.toolboxPanAndZoom(that, after, before);
2412 if(NETDATA.options.current.show_help === true)
2413 $(this.element_legend_childs.toolbox_zoomout).popover({
2418 placement: 'bottom',
2419 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2420 title: 'Chart Zoom Out',
2421 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>'
2424 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2425 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2426 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2427 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2428 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2429 //e.preventDefault();
2430 //alert('clicked toolbox_volume on ' + that.id);
2434 this.element_legend_childs.toolbox = null;
2435 this.element_legend_childs.toolbox_left = null;
2436 this.element_legend_childs.toolbox_reset = null;
2437 this.element_legend_childs.toolbox_right = null;
2438 this.element_legend_childs.toolbox_zoomin = null;
2439 this.element_legend_childs.toolbox_zoomout = null;
2440 this.element_legend_childs.toolbox_volume = null;
2443 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2444 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2445 this.element.appendChild(this.element_legend_childs.resize_handler);
2446 if(NETDATA.options.current.show_help === true)
2447 $(this.element_legend_childs.resize_handler).popover({
2452 placement: 'bottom',
2453 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2454 title: 'Chart Resize',
2455 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>'
2459 this.element_legend_childs.resize_handler.onmousedown =
2461 that.resizeHandler(e);
2465 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2466 that.resizeHandler(e);
2469 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2470 this.element_legend.appendChild(this.element_legend_childs.title_date);
2472 this.element_legend.appendChild(document.createElement('br'));
2474 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2475 this.element_legend.appendChild(this.element_legend_childs.title_time);
2477 this.element_legend.appendChild(document.createElement('br'));
2479 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2480 this.element_legend.appendChild(this.element_legend_childs.title_units);
2482 this.element_legend.appendChild(document.createElement('br'));
2484 this.element_legend_childs.nano.className = 'netdata-legend-series';
2485 this.element_legend.appendChild(this.element_legend_childs.nano);
2487 content.className = 'netdata-legend-series-content';
2488 this.element_legend_childs.nano.appendChild(content);
2490 if(NETDATA.options.current.show_help === true)
2491 $(content).popover({
2496 placement: 'bottom',
2497 title: 'Chart Legend',
2498 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2499 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>'
2503 this.element_legend_childs = {
2505 resize_handler: null,
2508 toolbox_right: null,
2509 toolbox_reset: null,
2510 toolbox_zoomin: null,
2511 toolbox_zoomout: null,
2512 toolbox_volume: null,
2523 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2524 if(this.debug === true)
2525 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2527 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2528 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2532 var tmp = new Array();
2533 for(var dim in this.chart.dimensions) {
2534 tmp.push(this.chart.dimensions[dim].name);
2535 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2537 this.element_legend_childs.series.labels_key = tmp.toString();
2538 if(this.debug === true)
2539 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2542 // create a hidden div to be used for hidding
2543 // the original legend of the chart library
2544 var el = document.createElement('div');
2545 if(this.element_legend !== null)
2546 this.element_legend.appendChild(el);
2547 el.style.display = 'none';
2549 this.element_legend_childs.hidden = document.createElement('div');
2550 el.appendChild(this.element_legend_childs.hidden);
2552 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2553 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2555 this.legendShowLatestValues();
2558 this.hasLegend = function() {
2559 if(typeof this.___hasLegendCache___ !== 'undefined')
2560 return this.___hasLegendCache___;
2563 if(this.library && this.library.legend(this) === 'right-side') {
2564 var legend = $(this.element).data('legend') || 'yes';
2565 if(legend === 'yes') leg = true;
2568 this.___hasLegendCache___ = leg;
2572 this.legendWidth = function() {
2573 return (this.hasLegend())?140:0;
2576 this.legendHeight = function() {
2577 return $(this.element).height();
2580 this.chartWidth = function() {
2581 return $(this.element).width() - this.legendWidth();
2584 this.chartHeight = function() {
2585 return $(this.element).height();
2588 this.chartPixelsPerPoint = function() {
2589 // force an options provided detail
2590 var px = this.pixels_per_point;
2592 if(this.library && px < this.library.pixels_per_point(this))
2593 px = this.library.pixels_per_point(this);
2595 if(px < NETDATA.options.current.pixels_per_point)
2596 px = NETDATA.options.current.pixels_per_point;
2601 this.needsRecreation = function() {
2603 this.chart_created === true
2605 && this.library.autoresize() === false
2606 && this.tm.last_resized < NETDATA.options.last_resized
2610 this.chartURL = function() {
2611 var after, before, points_multiplier = 1;
2612 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2613 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2615 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2616 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2617 this.view_after = after * 1000;
2618 this.view_before = before * 1000;
2620 this.requested_padding = null;
2621 points_multiplier = 1;
2623 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2624 this.tm.pan_and_zoom_seq = 0;
2626 before = Math.round(this.current.force_before_ms / 1000);
2627 after = Math.round(this.current.force_after_ms / 1000);
2628 this.view_after = after * 1000;
2629 this.view_before = before * 1000;
2631 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2632 this.requested_padding = Math.round((before - after) / 2);
2633 after -= this.requested_padding;
2634 before += this.requested_padding;
2635 this.requested_padding *= 1000;
2636 points_multiplier = 2;
2639 this.current.force_before_ms = null;
2640 this.current.force_after_ms = null;
2643 this.tm.pan_and_zoom_seq = 0;
2645 before = this.before;
2647 this.view_after = after * 1000;
2648 this.view_before = before * 1000;
2650 this.requested_padding = null;
2651 points_multiplier = 1;
2654 this.requested_after = after * 1000;
2655 this.requested_before = before * 1000;
2657 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2659 // build the data URL
2660 this.data_url = this.host + this.chart.data_url;
2661 this.data_url += "&format=" + this.library.format();
2662 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2663 this.data_url += "&group=" + this.method;
2664 this.data_url += "&options=" + this.library.options(this);
2665 this.data_url += '|jsonwrap';
2667 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2668 this.data_url += '|nonzero';
2670 if(this.append_options !== null)
2671 this.data_url += '|' + this.append_options.toString();
2674 this.data_url += "&after=" + after.toString();
2677 this.data_url += "&before=" + before.toString();
2680 this.data_url += "&dimensions=" + this.dimensions;
2682 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2683 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2686 this.redrawChart = function() {
2687 if(this.data !== null)
2688 this.updateChartWithData(this.data);
2691 this.updateChartWithData = function(data) {
2692 if(this.debug === true)
2693 this.log('updateChartWithData() called.');
2695 // this may force the chart to be re-created
2699 this.updates_counter++;
2700 this.updates_since_last_unhide++;
2701 this.updates_since_last_creation++;
2703 var started = new Date().getTime();
2705 // if the result is JSON, find the latest update-every
2706 this.data_update_every = data.view_update_every * 1000;
2707 this.data_after = data.after * 1000;
2708 this.data_before = data.before * 1000;
2709 this.netdata_first = data.first_entry * 1000;
2710 this.netdata_last = data.last_entry * 1000;
2711 this.data_points = data.points;
2714 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2715 if(this.view_after < this.data_after) {
2716 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2717 this.view_after = this.data_after;
2720 if(this.view_before > this.data_before) {
2721 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2722 this.view_before = this.data_before;
2726 this.view_after = this.data_after;
2727 this.view_before = this.data_before;
2730 if(this.debug === true) {
2731 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2733 if(this.current.force_after_ms)
2734 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2736 this.log('STATUS: forced : unset');
2738 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2739 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2740 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2741 this.log('STATUS: points : ' + (this.data_points).toString());
2744 if(this.data_points === 0) {
2749 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2750 if(this.debug === true)
2751 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2753 this.chart_created = false;
2756 // check and update the legend
2757 this.legendUpdateDOM();
2759 if(this.chart_created === true
2760 && typeof this.library.update === 'function') {
2762 if(this.debug === true)
2763 this.log('updating chart...');
2765 if(callChartLibraryUpdateSafely(data) === false)
2769 if(this.debug === true)
2770 this.log('creating chart...');
2772 if(callChartLibraryCreateSafely(data) === false)
2776 this.legendShowLatestValues();
2777 if(this.selected === true)
2778 NETDATA.globalSelectionSync.stop();
2780 // update the performance counters
2781 var now = new Date().getTime();
2782 this.tm.last_updated = now;
2784 // don't update last_autorefreshed if this chart is
2785 // forced to be updated with global PanAndZoom
2786 if(NETDATA.globalPanAndZoom.isActive())
2787 this.tm.last_autorefreshed = 0;
2789 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2790 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2792 this.tm.last_autorefreshed = now;
2795 this.refresh_dt_ms = now - started;
2796 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2798 if(this.refresh_dt_element !== null)
2799 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2802 this.updateChart = function(callback) {
2803 if(this.debug === true)
2804 this.log('updateChart() called.');
2806 if(this._updating === true) {
2807 if(this.debug === true)
2808 this.log('I am already updating...');
2810 if(typeof callback === 'function') callback();
2814 // due to late initialization of charts and libraries
2815 // we need to check this too
2816 if(this.enabled === false) {
2817 if(this.debug === true)
2818 this.log('I am not enabled');
2820 if(typeof callback === 'function') callback();
2824 if(canBeRendered() === false) {
2825 if(typeof callback === 'function') callback();
2829 if(this.chart === null) {
2830 this.getChart(function() { that.updateChart(callback); });
2834 if(this.library.initialized === false) {
2835 if(this.library.enabled === true) {
2836 this.library.initialize(function() { that.updateChart(callback); });
2840 error('chart library "' + this.library_name + '" is not available.');
2841 if(typeof callback === 'function') callback();
2846 this.clearSelection();
2849 if(this.debug === true)
2850 this.log('updating from ' + this.data_url);
2852 NETDATA.statistics.refreshes_total++;
2853 NETDATA.statistics.refreshes_active++;
2855 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2856 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2858 this._updating = true;
2860 this.xhr = $.ajax( {
2864 xhrFields: { withCredentials: true } // required for the cookie
2866 .done(function(data) {
2867 that.xhr = undefined;
2869 if(that.debug === true)
2870 that.log('data received. updating chart.');
2872 that.updateChartWithData(data);
2874 .fail(function(msg) {
2875 that.xhr = undefined;
2877 if(msg.statusText !== 'abort')
2878 error('data download failed for url: ' + that.data_url);
2880 .always(function() {
2881 that.xhr = undefined;
2883 NETDATA.statistics.refreshes_active--;
2884 that._updating = false;
2885 if(typeof callback === 'function') callback();
2891 this.isVisible = function(nocache) {
2892 if(typeof nocache === 'undefined')
2895 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2897 // caching - we do not evaluate the charts visibility
2898 // if the page has not been scrolled since the last check
2899 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2900 return this.___isVisible___;
2902 this.tm.last_visible_check = new Date().getTime();
2904 var wh = window.innerHeight;
2905 var x = this.element.getBoundingClientRect();
2909 if(x.width === 0 || x.height === 0) {
2911 this.___isVisible___ = false;
2912 return this.___isVisible___;
2915 if(x.top < 0 && -x.top > x.height) {
2916 // the chart is entirely above
2917 ret = -x.top - x.height;
2919 else if(x.top > wh) {
2920 // the chart is entirely below
2924 if(ret > tolerance) {
2925 // the chart is too far
2928 this.___isVisible___ = false;
2929 return this.___isVisible___;
2932 // the chart is inside or very close
2935 this.___isVisible___ = true;
2936 return this.___isVisible___;
2940 this.isAutoRefreshable = function() {
2941 return (this.current.autorefresh);
2944 this.canBeAutoRefreshed = function() {
2945 var now = new Date().getTime();
2947 if(this.running === true) {
2948 if(this.debug === true)
2949 this.log('I am already running');
2954 if(this.enabled === false) {
2955 if(this.debug === true)
2956 this.log('I am not enabled');
2961 if(this.library === null || this.library.enabled === false) {
2962 error('charting library "' + this.library_name + '" is not available');
2963 if(this.debug === true)
2964 this.log('My chart library ' + this.library_name + ' is not available');
2969 if(this.isVisible() === false) {
2970 if(NETDATA.options.debug.visibility === true || this.debug === true)
2971 this.log('I am not visible');
2976 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2977 if(this.debug === true)
2978 this.log('timed force update detected - allowing this update');
2980 this.current.force_update_at = 0;
2984 if(this.isAutoRefreshable() === true) {
2985 // allow the first update, even if the page is not visible
2986 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2987 if(NETDATA.options.debug.focus === true || this.debug === true)
2988 this.log('canBeAutoRefreshed(): page does not have focus');
2993 if(this.needsRecreation() === true) {
2994 if(this.debug === true)
2995 this.log('canBeAutoRefreshed(): needs re-creation.');
3000 // options valid only for autoRefresh()
3001 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3002 if(NETDATA.globalPanAndZoom.isActive()) {
3003 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3004 if(this.debug === true)
3005 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3010 if(this.debug === true)
3011 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3017 if(this.selected === true) {
3018 if(this.debug === true)
3019 this.log('canBeAutoRefreshed(): I have a selection in place.');
3024 if(this.paused === true) {
3025 if(this.debug === true)
3026 this.log('canBeAutoRefreshed(): I am paused.');
3031 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3032 if(this.debug === true)
3033 this.log('canBeAutoRefreshed(): It is time to update me.');
3043 this.autoRefresh = function(callback) {
3044 if(this.canBeAutoRefreshed() === true && this.running === false) {
3047 state.running = true;
3048 state.updateChart(function() {
3049 state.running = false;
3051 if(typeof callback !== 'undefined')
3056 if(typeof callback !== 'undefined')
3061 this._defaultsFromDownloadedChart = function(chart) {
3063 this.chart_url = chart.url;
3064 this.data_update_every = chart.update_every * 1000;
3065 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3066 this.tm.last_info_downloaded = new Date().getTime();
3068 if(this.title === null)
3069 this.title = chart.title;
3071 if(this.units === null)
3072 this.units = chart.units;
3075 // fetch the chart description from the netdata server
3076 this.getChart = function(callback) {
3077 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3079 this._defaultsFromDownloadedChart(this.chart);
3080 if(typeof callback === 'function') callback();
3083 this.chart_url = "/api/v1/chart?chart=" + this.id;
3085 if(this.debug === true)
3086 this.log('downloading ' + this.chart_url);
3089 url: this.host + this.chart_url,
3092 xhrFields: { withCredentials: true } // required for the cookie
3094 .done(function(chart) {
3095 chart.url = that.chart_url;
3096 that._defaultsFromDownloadedChart(chart);
3097 NETDATA.chartRegistry.add(that.host, that.id, chart);
3100 NETDATA.error(404, that.chart_url);
3101 error('chart not found on url "' + that.chart_url + '"');
3103 .always(function() {
3104 if(typeof callback === 'function') callback();
3109 // ============================================================================================================
3115 NETDATA.resetAllCharts = function(state) {
3116 // first clear the global selection sync
3117 // to make sure no chart is in selected state
3118 state.globalSelectionSyncStop();
3120 // there are 2 possibilities here
3121 // a. state is the global Pan and Zoom master
3122 // b. state is not the global Pan and Zoom master
3124 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3127 // clear the global Pan and Zoom
3128 // this will also refresh the master
3129 // and unblock any charts currently mirroring the master
3130 NETDATA.globalPanAndZoom.clearMaster();
3132 // if we were not the master, reset our status too
3133 // this is required because most probably the mouse
3134 // is over this chart, blocking it from auto-refreshing
3135 if(master === false && (state.paused === true || state.selected === true))
3139 // get or create a chart state, given a DOM element
3140 NETDATA.chartState = function(element) {
3141 var state = $(element).data('netdata-state-object') || null;
3142 if(state === null) {
3143 state = new chartState(element);
3144 $(element).data('netdata-state-object', state);
3149 // ----------------------------------------------------------------------------------------------------------------
3150 // Library functions
3152 // Load a script without jquery
3153 // This is used to load jquery - after it is loaded, we use jquery
3154 NETDATA._loadjQuery = function(callback) {
3155 if(typeof jQuery === 'undefined') {
3156 if(NETDATA.options.debug.main_loop === true)
3157 console.log('loading ' + NETDATA.jQuery);
3159 var script = document.createElement('script');
3160 script.type = 'text/javascript';
3161 script.async = true;
3162 script.src = NETDATA.jQuery;
3164 // script.onabort = onError;
3165 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3166 if(typeof callback === "function")
3167 script.onload = callback;
3169 var s = document.getElementsByTagName('script')[0];
3170 s.parentNode.insertBefore(script, s);
3172 else if(typeof callback === "function")
3176 NETDATA._loadCSS = function(filename) {
3177 // don't use jQuery here
3178 // styles are loaded before jQuery
3179 // to eliminate showing an unstyled page to the user
3181 var fileref = document.createElement("link");
3182 fileref.setAttribute("rel", "stylesheet");
3183 fileref.setAttribute("type", "text/css");
3184 fileref.setAttribute("href", filename);
3186 if (typeof fileref !== 'undefined')
3187 document.getElementsByTagName("head")[0].appendChild(fileref);
3190 NETDATA.colorHex2Rgb = function(hex) {
3191 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3192 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3193 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3194 return r + r + g + g + b + b;
3197 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3199 r: parseInt(result[1], 16),
3200 g: parseInt(result[2], 16),
3201 b: parseInt(result[3], 16)
3205 NETDATA.colorLuminance = function(hex, lum) {
3206 // validate hex string
3207 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3209 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3213 // convert to decimal and change luminosity
3214 var rgb = "#", c, i;
3215 for (i = 0; i < 3; i++) {
3216 c = parseInt(hex.substr(i*2,2), 16);
3217 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3218 rgb += ("00"+c).substr(c.length);
3224 NETDATA.guid = function() {
3226 return Math.floor((1 + Math.random()) * 0x10000)
3231 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3234 NETDATA.zeropad = function(x) {
3235 if(x > -10 && x < 10) return '0' + x.toString();
3236 else return x.toString();
3239 // user function to signal us the DOM has been
3241 NETDATA.updatedDom = function() {
3242 NETDATA.options.updated_dom = true;
3245 NETDATA.ready = function(callback) {
3246 NETDATA.options.pauseCallback = callback;
3249 NETDATA.pause = function(callback) {
3250 if(NETDATA.options.pause === true)
3253 NETDATA.options.pauseCallback = callback;
3256 NETDATA.unpause = function() {
3257 NETDATA.options.pauseCallback = null;
3258 NETDATA.options.updated_dom = true;
3259 NETDATA.options.pause = false;
3262 // ----------------------------------------------------------------------------------------------------------------
3264 // this is purely sequencial charts refresher
3265 // it is meant to be autonomous
3266 NETDATA.chartRefresherNoParallel = function(index) {
3267 if(NETDATA.options.debug.mail_loop === true)
3268 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3270 if(NETDATA.options.updated_dom === true) {
3271 // the dom has been updated
3272 // get the dom parts again
3273 NETDATA.parseDom(NETDATA.chartRefresher);
3276 if(index >= NETDATA.options.targets.length) {
3277 if(NETDATA.options.debug.main_loop === true)
3278 console.log('waiting to restart main loop...');
3280 NETDATA.options.auto_refresher_fast_weight = 0;
3282 setTimeout(function() {
3283 NETDATA.chartRefresher();
3284 }, NETDATA.options.current.idle_between_loops);
3287 var state = NETDATA.options.targets[index];
3289 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3290 if(NETDATA.options.debug.main_loop === true)
3291 console.log('fast rendering...');
3293 state.autoRefresh(function() {
3294 NETDATA.chartRefresherNoParallel(++index);
3298 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3299 NETDATA.options.auto_refresher_fast_weight = 0;
3301 setTimeout(function() {
3302 state.autoRefresh(function() {
3303 NETDATA.chartRefresherNoParallel(++index);
3305 }, NETDATA.options.current.idle_between_charts);
3310 // this is part of the parallel refresher
3311 // its cause is to refresh sequencially all the charts
3312 // that depend on chart library initialization
3313 // it will call the parallel refresher back
3314 // as soon as it sees a chart that its chart library
3316 NETDATA.chartRefresher_uninitialized = function() {
3317 if(NETDATA.options.updated_dom === true) {
3318 // the dom has been updated
3319 // get the dom parts again
3320 NETDATA.parseDom(NETDATA.chartRefresher);
3324 if(NETDATA.options.sequencial.length === 0)
3325 NETDATA.chartRefresher();
3327 var state = NETDATA.options.sequencial.pop();
3328 if(state.library.initialized === true)
3329 NETDATA.chartRefresher();
3331 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3335 NETDATA.chartRefresherWaitTime = function() {
3336 return NETDATA.options.current.idle_parallel_loops;
3339 // the default refresher
3340 // it will create 2 sets of charts:
3341 // - the ones that can be refreshed in parallel
3342 // - the ones that depend on something else
3343 // the first set will be executed in parallel
3344 // the second will be given to NETDATA.chartRefresher_uninitialized()
3345 NETDATA.chartRefresher = function() {
3346 // console.log('auto-refresher...');
3348 if(NETDATA.options.pause === true) {
3349 // console.log('auto-refresher is paused');
3350 setTimeout(NETDATA.chartRefresher,
3351 NETDATA.chartRefresherWaitTime());
3355 if(typeof NETDATA.options.pauseCallback === 'function') {
3356 // console.log('auto-refresher is calling pauseCallback');
3357 NETDATA.options.pause = true;
3358 NETDATA.options.pauseCallback();
3359 NETDATA.chartRefresher();
3363 if(NETDATA.options.current.parallel_refresher === false) {
3364 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3365 NETDATA.chartRefresherNoParallel(0);
3369 if(NETDATA.options.updated_dom === true) {
3370 // the dom has been updated
3371 // get the dom parts again
3372 // console.log('auto-refresher is calling parseDom()');
3373 NETDATA.parseDom(NETDATA.chartRefresher);
3377 var parallel = new Array();
3378 var targets = NETDATA.options.targets;
3379 var len = targets.length;
3382 state = targets[len];
3383 if(state.isVisible() === false || state.running === true)
3386 if(state.library.initialized === false) {
3387 if(state.library.enabled === true) {
3388 state.library.initialize(NETDATA.chartRefresher);
3392 state.error('chart library "' + state.library_name + '" is not enabled.');
3396 parallel.unshift(state);
3399 if(parallel.length > 0) {
3400 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3401 // this will execute the jobs in parallel
3402 $(parallel).each(function() {
3407 // console.log('auto-refresher nothing to do');
3410 // run the next refresh iteration
3411 setTimeout(NETDATA.chartRefresher,
3412 NETDATA.chartRefresherWaitTime());
3415 NETDATA.parseDom = function(callback) {
3416 NETDATA.options.last_page_scroll = new Date().getTime();
3417 NETDATA.options.updated_dom = false;
3419 var targets = $('div[data-netdata]'); //.filter(':visible');
3421 if(NETDATA.options.debug.main_loop === true)
3422 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3424 NETDATA.options.targets = new Array();
3425 var len = targets.length;
3427 // the initialization will take care of sizing
3428 // and the "loading..." message
3429 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3432 if(typeof callback === 'function') callback();
3435 // this is the main function - where everything starts
3436 NETDATA.start = function() {
3437 // this should be called only once
3439 NETDATA.options.page_is_visible = true;
3441 $(window).blur(function() {
3442 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3443 NETDATA.options.page_is_visible = false;
3444 if(NETDATA.options.debug.focus === true)
3445 console.log('Lost Focus!');
3449 $(window).focus(function() {
3450 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3451 NETDATA.options.page_is_visible = true;
3452 if(NETDATA.options.debug.focus === true)
3453 console.log('Focus restored!');
3457 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3458 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3459 NETDATA.options.page_is_visible = false;
3460 if(NETDATA.options.debug.focus === true)
3461 console.log('Document has no focus!');
3465 // bootstrap tab switching
3466 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3468 // bootstrap modal switching
3469 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3470 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3472 // bootstrap collapse switching
3473 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3474 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3476 NETDATA.parseDom(NETDATA.chartRefresher);
3478 // Alarms initialization
3479 setTimeout(NETDATA.alarms.init, 1000);
3481 // Registry initialization
3482 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3484 if(typeof netdataCallback === 'function')
3488 // ----------------------------------------------------------------------------------------------------------------
3491 NETDATA.peityInitialize = function(callback) {
3492 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3494 url: NETDATA.peity_js,
3497 xhrFields: { withCredentials: true } // required for the cookie
3500 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3503 NETDATA.chartLibraries.peity.enabled = false;
3504 NETDATA.error(100, NETDATA.peity_js);
3506 .always(function() {
3507 if(typeof callback === "function")
3512 NETDATA.chartLibraries.peity.enabled = false;
3513 if(typeof callback === "function")
3518 NETDATA.peityChartUpdate = function(state, data) {
3519 state.peity_instance.innerHTML = data.result;
3521 if(state.peity_options.stroke !== state.chartColors()[0]) {
3522 state.peity_options.stroke = state.chartColors()[0];
3523 if(state.chart.chart_type === 'line')
3524 state.peity_options.fill = NETDATA.themes.current.background;
3526 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3529 $(state.peity_instance).peity('line', state.peity_options);
3533 NETDATA.peityChartCreate = function(state, data) {
3534 state.peity_instance = document.createElement('div');
3535 state.element_chart.appendChild(state.peity_instance);
3537 var self = $(state.element);
3538 state.peity_options = {
3539 stroke: NETDATA.themes.current.foreground,
3540 strokeWidth: self.data('peity-strokewidth') || 1,
3541 width: state.chartWidth(),
3542 height: state.chartHeight(),
3543 fill: NETDATA.themes.current.foreground
3546 NETDATA.peityChartUpdate(state, data);
3550 // ----------------------------------------------------------------------------------------------------------------
3553 NETDATA.sparklineInitialize = function(callback) {
3554 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3556 url: NETDATA.sparkline_js,
3559 xhrFields: { withCredentials: true } // required for the cookie
3562 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3565 NETDATA.chartLibraries.sparkline.enabled = false;
3566 NETDATA.error(100, NETDATA.sparkline_js);
3568 .always(function() {
3569 if(typeof callback === "function")
3574 NETDATA.chartLibraries.sparkline.enabled = false;
3575 if(typeof callback === "function")
3580 NETDATA.sparklineChartUpdate = function(state, data) {
3581 state.sparkline_options.width = state.chartWidth();
3582 state.sparkline_options.height = state.chartHeight();
3584 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3588 NETDATA.sparklineChartCreate = function(state, data) {
3589 var self = $(state.element);
3590 var type = self.data('sparkline-type') || 'line';
3591 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3592 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3593 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3594 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3595 var composite = self.data('sparkline-composite') || undefined;
3596 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3597 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3598 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3599 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3600 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3601 var spotColor = self.data('sparkline-spotcolor') || undefined;
3602 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3603 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3604 var spotRadius = self.data('sparkline-spotradius') || undefined;
3605 var valueSpots = self.data('sparkline-valuespots') || undefined;
3606 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3607 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3608 var lineWidth = self.data('sparkline-linewidth') || undefined;
3609 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3610 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3611 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3612 var xvalues = self.data('sparkline-xvalues') || undefined;
3613 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3614 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3615 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3616 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3617 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3618 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3619 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3620 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3621 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3622 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3623 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3624 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3625 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3626 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3627 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3628 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3629 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3630 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3631 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3632 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3633 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3634 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3636 if(spotColor === 'disable') spotColor='';
3637 if(minSpotColor === 'disable') minSpotColor='';
3638 if(maxSpotColor === 'disable') maxSpotColor='';
3640 state.sparkline_options = {
3642 lineColor: lineColor,
3643 fillColor: fillColor,
3644 chartRangeMin: chartRangeMin,
3645 chartRangeMax: chartRangeMax,
3646 composite: composite,
3647 enableTagOptions: enableTagOptions,
3648 tagOptionPrefix: tagOptionPrefix,
3649 tagValuesAttribute: tagValuesAttribute,
3650 disableHiddenCheck: disableHiddenCheck,
3651 defaultPixelsPerValue: defaultPixelsPerValue,
3652 spotColor: spotColor,
3653 minSpotColor: minSpotColor,
3654 maxSpotColor: maxSpotColor,
3655 spotRadius: spotRadius,
3656 valueSpots: valueSpots,
3657 highlightSpotColor: highlightSpotColor,
3658 highlightLineColor: highlightLineColor,
3659 lineWidth: lineWidth,
3660 normalRangeMin: normalRangeMin,
3661 normalRangeMax: normalRangeMax,
3662 drawNormalOnTop: drawNormalOnTop,
3664 chartRangeClip: chartRangeClip,
3665 chartRangeMinX: chartRangeMinX,
3666 chartRangeMaxX: chartRangeMaxX,
3667 disableInteraction: disableInteraction,
3668 disableTooltips: disableTooltips,
3669 disableHighlight: disableHighlight,
3670 highlightLighten: highlightLighten,
3671 highlightColor: highlightColor,
3672 tooltipContainer: tooltipContainer,
3673 tooltipClassname: tooltipClassname,
3674 tooltipChartTitle: state.title,
3675 tooltipFormat: tooltipFormat,
3676 tooltipPrefix: tooltipPrefix,
3677 tooltipSuffix: tooltipSuffix,
3678 tooltipSkipNull: tooltipSkipNull,
3679 tooltipValueLookups: tooltipValueLookups,
3680 tooltipFormatFieldlist: tooltipFormatFieldlist,
3681 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3682 numberFormatter: numberFormatter,
3683 numberDigitGroupSep: numberDigitGroupSep,
3684 numberDecimalMark: numberDecimalMark,
3685 numberDigitGroupCount: numberDigitGroupCount,
3686 animatedZooms: animatedZooms,
3687 width: state.chartWidth(),
3688 height: state.chartHeight()
3691 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3695 // ----------------------------------------------------------------------------------------------------------------
3702 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3703 if(after < state.netdata_first)
3704 after = state.netdata_first;
3706 if(before > state.netdata_last)
3707 before = state.netdata_last;
3709 state.setMode('zoom');
3710 state.globalSelectionSyncStop();
3711 state.globalSelectionSyncDelay();
3712 state.dygraph_user_action = true;
3713 state.dygraph_force_zoom = true;
3714 state.updateChartPanOrZoom(after, before);
3715 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3718 NETDATA.dygraphSetSelection = function(state, t) {
3719 if(typeof state.dygraph_instance !== 'undefined') {
3720 var r = state.calculateRowForTime(t);
3722 state.dygraph_instance.setSelection(r);
3724 state.dygraph_instance.clearSelection();
3725 state.legendShowUndefined();
3732 NETDATA.dygraphClearSelection = function(state, t) {
3733 if(typeof state.dygraph_instance !== 'undefined') {
3734 state.dygraph_instance.clearSelection();
3739 NETDATA.dygraphSmoothInitialize = function(callback) {
3741 url: NETDATA.dygraph_smooth_js,
3744 xhrFields: { withCredentials: true } // required for the cookie
3747 NETDATA.dygraph.smooth = true;
3748 smoothPlotter.smoothing = 0.3;
3751 NETDATA.dygraph.smooth = false;
3753 .always(function() {
3754 if(typeof callback === "function")
3759 NETDATA.dygraphInitialize = function(callback) {
3760 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3762 url: NETDATA.dygraph_js,
3765 xhrFields: { withCredentials: true } // required for the cookie
3768 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3771 NETDATA.chartLibraries.dygraph.enabled = false;
3772 NETDATA.error(100, NETDATA.dygraph_js);
3774 .always(function() {
3775 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3776 NETDATA.dygraphSmoothInitialize(callback);
3777 else if(typeof callback === "function")
3782 NETDATA.chartLibraries.dygraph.enabled = false;
3783 if(typeof callback === "function")
3788 NETDATA.dygraphChartUpdate = function(state, data) {
3789 var dygraph = state.dygraph_instance;
3791 if(typeof dygraph === 'undefined')
3792 return NETDATA.dygraphChartCreate(state, data);
3794 // when the chart is not visible, and hidden
3795 // if there is a window resize, dygraph detects
3796 // its element size as 0x0.
3797 // this will make it re-appear properly
3799 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3803 file: data.result.data,
3804 colors: state.chartColors(),
3805 labels: data.result.labels,
3806 labelsDivWidth: state.chartWidth() - 70,
3807 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3810 if(state.dygraph_force_zoom === true) {
3811 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3812 state.log('dygraphChartUpdate() forced zoom update');
3814 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3815 options.valueRange = state.dygraph_options.valueRange;
3816 options.isZoomedIgnoreProgrammaticZoom = true;
3817 state.dygraph_force_zoom = false;
3819 else if(state.current.name !== 'auto') {
3820 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3821 state.log('dygraphChartUpdate() loose update');
3823 options.valueRange = state.dygraph_options.valueRange;
3826 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3827 state.log('dygraphChartUpdate() strict update');
3829 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3830 options.valueRange = state.dygraph_options.valueRange;
3831 options.isZoomedIgnoreProgrammaticZoom = true;
3834 if(state.dygraph_smooth_eligible === true) {
3835 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3836 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3837 NETDATA.dygraphChartCreate(state, data);
3842 dygraph.updateOptions(options);
3844 state.dygraph_last_rendered = new Date().getTime();
3848 NETDATA.dygraphChartCreate = function(state, data) {
3849 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3850 state.log('dygraphChartCreate()');
3852 var self = $(state.element);
3854 var chart_type = state.chart.chart_type;
3855 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3856 chart_type = self.data('dygraph-type') || chart_type;
3858 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3859 smooth = self.data('dygraph-smooth') || smooth;
3861 if(NETDATA.dygraph.smooth === false)
3864 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3865 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3867 state.dygraph_options = {
3868 colors: self.data('dygraph-colors') || state.chartColors(),
3870 // leave a few pixels empty on the right of the chart
3871 rightGap: self.data('dygraph-rightgap') || 5,
3872 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3873 showRoller: self.data('dygraph-showroller') || false,
3875 title: self.data('dygraph-title') || state.title,
3876 titleHeight: self.data('dygraph-titleheight') || 19,
3878 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3879 labels: data.result.labels,
3880 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3881 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3882 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3883 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3884 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3887 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3888 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3890 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
3891 xRangePad: self.data('dygraph-xrangepad') || 0,
3892 yRangePad: self.data('dygraph-yrangepad') || 1,
3894 valueRange: self.data('dygraph-valuerange') || null,
3896 ylabel: state.units,
3897 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3899 // the function to plot the chart
3902 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3903 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3904 strokePattern: self.data('dygraph-strokepattern') || undefined,
3906 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3907 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3908 drawPoints: self.data('dygraph-drawpoints') || false,
3910 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3911 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3913 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3914 pointSize: self.data('dygraph-pointsize') || 1,
3916 // enabling this makes the chart with little square lines
3917 stepPlot: self.data('dygraph-stepplot') || false,
3919 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3920 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3921 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3923 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3924 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3925 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3926 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3928 drawAxis: self.data('dygraph-drawaxis') || true,
3929 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3930 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3931 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3933 drawGrid: self.data('dygraph-drawgrid') || true,
3934 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3935 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3936 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3937 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3938 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3940 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3941 sigFigs: self.data('dygraph-sigfigs') || null,
3942 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3943 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3945 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3946 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3947 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3949 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3950 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3954 ticker: Dygraph.dateTicker,
3955 axisLabelFormatter: function (d, gran) {
3956 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3958 valueFormatter: function (ms) {
3959 var d = new Date(ms);
3960 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3961 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3966 valueFormatter: function (x) {
3967 // we format legends with the state object
3968 // no need to do anything here
3969 // return (Math.round(x*100) / 100).toLocaleString();
3970 // return state.legendFormatValue(x);
3975 legendFormatter: function(data) {
3976 var elements = state.element_legend_childs;
3978 // if the hidden div is not there
3979 // we are not managing the legend
3980 if(elements.hidden === null) return;
3982 if (typeof data.x !== 'undefined') {
3983 state.legendSetDate(data.x);
3984 var i = data.series.length;
3986 var series = data.series[i];
3987 if(!series.isVisible) continue;
3988 state.legendSetLabelValue(series.label, series.y);
3994 drawCallback: function(dygraph, is_initial) {
3995 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3996 state.dygraph_user_action = false;
3998 var x_range = dygraph.xAxisRange();
3999 var after = Math.round(x_range[0]);
4000 var before = Math.round(x_range[1]);
4002 if(NETDATA.options.debug.dygraph === true)
4003 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4005 if(before <= state.netdata_last && after >= state.netdata_first)
4006 state.updateChartPanOrZoom(after, before);
4009 zoomCallback: function(minDate, maxDate, yRanges) {
4010 if(NETDATA.options.debug.dygraph === true)
4011 state.log('dygraphZoomCallback()');
4013 state.globalSelectionSyncStop();
4014 state.globalSelectionSyncDelay();
4015 state.setMode('zoom');
4017 // refresh it to the greatest possible zoom level
4018 state.dygraph_user_action = true;
4019 state.dygraph_force_zoom = true;
4020 state.updateChartPanOrZoom(minDate, maxDate);
4022 highlightCallback: function(event, x, points, row, seriesName) {
4023 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4024 state.log('dygraphHighlightCallback()');
4028 // there is a bug in dygraph when the chart is zoomed enough
4029 // the time it thinks is selected is wrong
4030 // here we calculate the time t based on the row number selected
4032 var t = state.data_after + row * state.data_update_every;
4033 // 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);
4035 state.globalSelectionSync(x);
4037 // fix legend zIndex using the internal structures of dygraph legend module
4038 // this works, but it is a hack!
4039 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4041 unhighlightCallback: function(event) {
4042 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4043 state.log('dygraphUnhighlightCallback()');
4045 state.unpauseChart();
4046 state.globalSelectionSyncStop();
4048 interactionModel : {
4049 mousedown: function(event, dygraph, context) {
4050 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4051 state.log('interactionModel.mousedown()');
4053 state.dygraph_user_action = true;
4054 state.globalSelectionSyncStop();
4056 if(NETDATA.options.debug.dygraph === true)
4057 state.log('dygraphMouseDown()');
4059 // Right-click should not initiate a zoom.
4060 if(event.button && event.button === 2) return;
4062 context.initializeMouseDown(event, dygraph, context);
4064 if(event.button && event.button === 1) {
4065 if (event.altKey || event.shiftKey) {
4066 state.setMode('pan');
4067 state.globalSelectionSyncDelay();
4068 Dygraph.startPan(event, dygraph, context);
4071 state.setMode('zoom');
4072 state.globalSelectionSyncDelay();
4073 Dygraph.startZoom(event, dygraph, context);
4077 if (event.altKey || event.shiftKey) {
4078 state.setMode('zoom');
4079 state.globalSelectionSyncDelay();
4080 Dygraph.startZoom(event, dygraph, context);
4083 state.setMode('pan');
4084 state.globalSelectionSyncDelay();
4085 Dygraph.startPan(event, dygraph, context);
4089 mousemove: function(event, dygraph, context) {
4090 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4091 state.log('interactionModel.mousemove()');
4093 if(context.isPanning) {
4094 state.dygraph_user_action = true;
4095 state.globalSelectionSyncStop();
4096 state.globalSelectionSyncDelay();
4097 state.setMode('pan');
4098 Dygraph.movePan(event, dygraph, context);
4100 else if(context.isZooming) {
4101 state.dygraph_user_action = true;
4102 state.globalSelectionSyncStop();
4103 state.globalSelectionSyncDelay();
4104 state.setMode('zoom');
4105 Dygraph.moveZoom(event, dygraph, context);
4108 mouseup: function(event, dygraph, context) {
4109 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4110 state.log('interactionModel.mouseup()');
4112 if (context.isPanning) {
4113 state.dygraph_user_action = true;
4114 state.globalSelectionSyncDelay();
4115 Dygraph.endPan(event, dygraph, context);
4117 else if (context.isZooming) {
4118 state.dygraph_user_action = true;
4119 state.globalSelectionSyncDelay();
4120 Dygraph.endZoom(event, dygraph, context);
4123 click: function(event, dygraph, context) {
4124 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4125 state.log('interactionModel.click()');
4127 event.preventDefault();
4129 dblclick: function(event, dygraph, context) {
4130 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4131 state.log('interactionModel.dblclick()');
4132 NETDATA.resetAllCharts(state);
4134 mousewheel: function(event, dygraph, context) {
4135 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4136 state.log('interactionModel.mousewheel()');
4138 // Take the offset of a mouse event on the dygraph canvas and
4139 // convert it to a pair of percentages from the bottom left.
4140 // (Not top left, bottom is where the lower value is.)
4141 function offsetToPercentage(g, offsetX, offsetY) {
4142 // This is calculating the pixel offset of the leftmost date.
4143 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4144 var yar0 = g.yAxisRange(0);
4146 // This is calculating the pixel of the higest value. (Top pixel)
4147 var yOffset = g.toDomCoords(null, yar0[1])[1];
4149 // x y w and h are relative to the corner of the drawing area,
4150 // so that the upper corner of the drawing area is (0, 0).
4151 var x = offsetX - xOffset;
4152 var y = offsetY - yOffset;
4154 // This is computing the rightmost pixel, effectively defining the
4156 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4158 // This is computing the lowest pixel, effectively defining the height.
4159 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4161 // Percentage from the left.
4162 var xPct = w === 0 ? 0 : (x / w);
4163 // Percentage from the top.
4164 var yPct = h === 0 ? 0 : (y / h);
4166 // The (1-) part below changes it from "% distance down from the top"
4167 // to "% distance up from the bottom".
4168 return [xPct, (1-yPct)];
4171 // Adjusts [x, y] toward each other by zoomInPercentage%
4172 // Split it so the left/bottom axis gets xBias/yBias of that change and
4173 // tight/top gets (1-xBias)/(1-yBias) of that change.
4175 // If a bias is missing it splits it down the middle.
4176 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4177 xBias = xBias || 0.5;
4178 yBias = yBias || 0.5;
4180 function adjustAxis(axis, zoomInPercentage, bias) {
4181 var delta = axis[1] - axis[0];
4182 var increment = delta * zoomInPercentage;
4183 var foo = [increment * bias, increment * (1-bias)];
4185 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4188 var yAxes = g.yAxisRanges();
4190 for (var i = 0; i < yAxes.length; i++) {
4191 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4194 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4197 if(event.altKey || event.shiftKey) {
4198 state.dygraph_user_action = true;
4200 state.globalSelectionSyncStop();
4201 state.globalSelectionSyncDelay();
4203 // http://dygraphs.com/gallery/interaction-api.js
4204 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4205 var percentage = normal / 50;
4207 if (!(event.offsetX && event.offsetY)){
4208 event.offsetX = event.layerX - event.target.offsetLeft;
4209 event.offsetY = event.layerY - event.target.offsetTop;
4212 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4213 var xPct = percentages[0];
4214 var yPct = percentages[1];
4216 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4218 var after = new_x_range[0];
4219 var before = new_x_range[1];
4221 var first = state.netdata_first + state.data_update_every;
4222 var last = state.netdata_last + state.data_update_every;
4225 after -= (before - last);
4232 state.setMode('zoom');
4233 if(state.updateChartPanOrZoom(after, before) === true)
4234 dygraph.updateOptions({ dateWindow: [ after, before ] });
4236 event.preventDefault();
4239 touchstart: function(event, dygraph, context) {
4240 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4241 state.log('interactionModel.touchstart()');
4243 state.dygraph_user_action = true;
4244 state.setMode('zoom');
4247 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4249 // we overwrite the touch directions at the end, to overwrite
4250 // the internal default of dygraphs
4251 context.touchDirections = { x: true, y: false };
4253 state.dygraph_last_touch_start = new Date().getTime();
4254 state.dygraph_last_touch_move = 0;
4256 if(typeof event.touches[0].pageX === 'number')
4257 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4259 state.dygraph_last_touch_page_x = 0;
4261 touchmove: function(event, dygraph, context) {
4262 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4263 state.log('interactionModel.touchmove()');
4265 state.dygraph_user_action = true;
4266 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4268 state.dygraph_last_touch_move = new Date().getTime();
4270 touchend: function(event, dygraph, context) {
4271 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4272 state.log('interactionModel.touchend()');
4274 state.dygraph_user_action = true;
4275 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4277 // if it didn't move, it is a selection
4278 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4279 // internal api of dygraphs
4280 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4281 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4282 if(NETDATA.dygraphSetSelection(state, t) === true)
4283 state.globalSelectionSync(t);
4286 // if it was double tap within double click time, reset the charts
4287 var now = new Date().getTime();
4288 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4289 if(state.dygraph_last_touch_move === 0) {
4290 var dt = now - state.dygraph_last_touch_end;
4291 if(dt <= NETDATA.options.current.double_click_speed)
4292 NETDATA.resetAllCharts(state);
4296 // remember the timestamp of the last touch end
4297 state.dygraph_last_touch_end = now;
4302 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4303 state.dygraph_options.drawGrid = false;
4304 state.dygraph_options.drawAxis = false;
4305 state.dygraph_options.title = undefined;
4306 state.dygraph_options.units = undefined;
4307 state.dygraph_options.ylabel = undefined;
4308 state.dygraph_options.yLabelWidth = 0;
4309 state.dygraph_options.labelsDivWidth = 120;
4310 state.dygraph_options.labelsDivStyles.width = '120px';
4311 state.dygraph_options.labelsSeparateLines = true;
4312 state.dygraph_options.rightGap = 0;
4313 state.dygraph_options.yRangePad = 1;
4316 if(smooth === true) {
4317 state.dygraph_smooth_eligible = true;
4319 if(NETDATA.options.current.smooth_plot === true)
4320 state.dygraph_options.plotter = smoothPlotter;
4322 else state.dygraph_smooth_eligible = false;
4324 state.dygraph_instance = new Dygraph(state.element_chart,
4325 data.result.data, state.dygraph_options);
4327 state.dygraph_force_zoom = false;
4328 state.dygraph_user_action = false;
4329 state.dygraph_last_rendered = new Date().getTime();
4333 // ----------------------------------------------------------------------------------------------------------------
4336 NETDATA.morrisInitialize = function(callback) {
4337 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4339 // morris requires raphael
4340 if(!NETDATA.chartLibraries.raphael.initialized) {
4341 if(NETDATA.chartLibraries.raphael.enabled) {
4342 NETDATA.raphaelInitialize(function() {
4343 NETDATA.morrisInitialize(callback);
4347 NETDATA.chartLibraries.morris.enabled = false;
4348 if(typeof callback === "function")
4353 NETDATA._loadCSS(NETDATA.morris_css);
4356 url: NETDATA.morris_js,
4359 xhrFields: { withCredentials: true } // required for the cookie
4362 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4365 NETDATA.chartLibraries.morris.enabled = false;
4366 NETDATA.error(100, NETDATA.morris_js);
4368 .always(function() {
4369 if(typeof callback === "function")
4375 NETDATA.chartLibraries.morris.enabled = false;
4376 if(typeof callback === "function")
4381 NETDATA.morrisChartUpdate = function(state, data) {
4382 state.morris_instance.setData(data.result.data);
4386 NETDATA.morrisChartCreate = function(state, data) {
4388 state.morris_options = {
4389 element: state.element_chart.id,
4390 data: data.result.data,
4392 ykeys: data.dimension_names,
4393 labels: data.dimension_names,
4399 continuousLine: false,
4400 behaveLikeLine: false
4403 if(state.chart.chart_type === 'line')
4404 state.morris_instance = new Morris.Line(state.morris_options);
4406 else if(state.chart.chart_type === 'area') {
4407 state.morris_options.behaveLikeLine = true;
4408 state.morris_instance = new Morris.Area(state.morris_options);
4411 state.morris_instance = new Morris.Area(state.morris_options);
4416 // ----------------------------------------------------------------------------------------------------------------
4419 NETDATA.raphaelInitialize = function(callback) {
4420 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4422 url: NETDATA.raphael_js,
4425 xhrFields: { withCredentials: true } // required for the cookie
4428 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4431 NETDATA.chartLibraries.raphael.enabled = false;
4432 NETDATA.error(100, NETDATA.raphael_js);
4434 .always(function() {
4435 if(typeof callback === "function")
4440 NETDATA.chartLibraries.raphael.enabled = false;
4441 if(typeof callback === "function")
4446 NETDATA.raphaelChartUpdate = function(state, data) {
4447 $(state.element_chart).raphael(data.result, {
4448 width: state.chartWidth(),
4449 height: state.chartHeight()
4455 NETDATA.raphaelChartCreate = function(state, data) {
4456 $(state.element_chart).raphael(data.result, {
4457 width: state.chartWidth(),
4458 height: state.chartHeight()
4464 // ----------------------------------------------------------------------------------------------------------------
4467 NETDATA.c3Initialize = function(callback) {
4468 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4471 if(!NETDATA.chartLibraries.d3.initialized) {
4472 if(NETDATA.chartLibraries.d3.enabled) {
4473 NETDATA.d3Initialize(function() {
4474 NETDATA.c3Initialize(callback);
4478 NETDATA.chartLibraries.c3.enabled = false;
4479 if(typeof callback === "function")
4484 NETDATA._loadCSS(NETDATA.c3_css);
4490 xhrFields: { withCredentials: true } // required for the cookie
4493 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4496 NETDATA.chartLibraries.c3.enabled = false;
4497 NETDATA.error(100, NETDATA.c3_js);
4499 .always(function() {
4500 if(typeof callback === "function")
4506 NETDATA.chartLibraries.c3.enabled = false;
4507 if(typeof callback === "function")
4512 NETDATA.c3ChartUpdate = function(state, data) {
4513 state.c3_instance.destroy();
4514 return NETDATA.c3ChartCreate(state, data);
4516 //state.c3_instance.load({
4517 // rows: data.result,
4524 NETDATA.c3ChartCreate = function(state, data) {
4526 state.element_chart.id = 'c3-' + state.uuid;
4527 // console.log('id = ' + state.element_chart.id);
4529 state.c3_instance = c3.generate({
4530 bindto: '#' + state.element_chart.id,
4532 width: state.chartWidth(),
4533 height: state.chartHeight()
4536 pattern: state.chartColors()
4541 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4547 format: function(x) {
4548 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4575 // console.log(state.c3_instance);
4580 // ----------------------------------------------------------------------------------------------------------------
4583 NETDATA.d3Initialize = function(callback) {
4584 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4589 xhrFields: { withCredentials: true } // required for the cookie
4592 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4595 NETDATA.chartLibraries.d3.enabled = false;
4596 NETDATA.error(100, NETDATA.d3_js);
4598 .always(function() {
4599 if(typeof callback === "function")
4604 NETDATA.chartLibraries.d3.enabled = false;
4605 if(typeof callback === "function")
4610 NETDATA.d3ChartUpdate = function(state, data) {
4614 NETDATA.d3ChartCreate = function(state, data) {
4618 // ----------------------------------------------------------------------------------------------------------------
4621 NETDATA.googleInitialize = function(callback) {
4622 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4624 url: NETDATA.google_js,
4627 xhrFields: { withCredentials: true } // required for the cookie
4630 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4631 google.load('visualization', '1.1', {
4632 'packages': ['corechart', 'controls'],
4633 'callback': callback
4637 NETDATA.chartLibraries.google.enabled = false;
4638 NETDATA.error(100, NETDATA.google_js);
4639 if(typeof callback === "function")
4644 NETDATA.chartLibraries.google.enabled = false;
4645 if(typeof callback === "function")
4650 NETDATA.googleChartUpdate = function(state, data) {
4651 var datatable = new google.visualization.DataTable(data.result);
4652 state.google_instance.draw(datatable, state.google_options);
4656 NETDATA.googleChartCreate = function(state, data) {
4657 var datatable = new google.visualization.DataTable(data.result);
4659 state.google_options = {
4660 colors: state.chartColors(),
4662 // do not set width, height - the chart resizes itself
4663 //width: state.chartWidth(),
4664 //height: state.chartHeight(),
4669 // title: "Time of Day",
4670 // format:'HH:mm:ss',
4671 viewWindowMode: 'maximized',
4683 viewWindowMode: 'pretty',
4698 focusTarget: 'category',
4705 titlePosition: 'out',
4716 curveType: 'function',
4721 switch(state.chart.chart_type) {
4723 state.google_options.vAxis.viewWindowMode = 'maximized';
4724 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4725 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4729 state.google_options.isStacked = true;
4730 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4731 state.google_options.vAxis.viewWindowMode = 'maximized';
4732 state.google_options.vAxis.minValue = null;
4733 state.google_options.vAxis.maxValue = null;
4734 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4739 state.google_options.lineWidth = 2;
4740 state.google_instance = new google.visualization.LineChart(state.element_chart);
4744 state.google_instance.draw(datatable, state.google_options);
4748 // ----------------------------------------------------------------------------------------------------------------
4750 NETDATA.percentFromValueMax = function(value, max) {
4751 if(value === null) value = 0;
4752 if(max < value) max = value;
4756 pcent = Math.round(value * 100 / max);
4757 if(pcent === 0 && value > 0) pcent = 1;
4763 // ----------------------------------------------------------------------------------------------------------------
4766 NETDATA.easypiechartInitialize = function(callback) {
4767 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4769 url: NETDATA.easypiechart_js,
4772 xhrFields: { withCredentials: true } // required for the cookie
4775 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4778 NETDATA.chartLibraries.easypiechart.enabled = false;
4779 NETDATA.error(100, NETDATA.easypiechart_js);
4781 .always(function() {
4782 if(typeof callback === "function")
4787 NETDATA.chartLibraries.easypiechart.enabled = false;
4788 if(typeof callback === "function")
4793 NETDATA.easypiechartClearSelection = function(state) {
4794 if(typeof state.easyPieChartEvent !== 'undefined') {
4795 if(state.easyPieChartEvent.timer !== null)
4796 clearTimeout(state.easyPieChartEvent.timer);
4798 state.easyPieChartEvent.timer = null;
4801 if(state.isAutoRefreshable() === true && state.data !== null) {
4802 NETDATA.easypiechartChartUpdate(state, state.data);
4805 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4806 state.easyPieChart_instance.update(0);
4808 state.easyPieChart_instance.enableAnimation();
4813 NETDATA.easypiechartSetSelection = function(state, t) {
4814 if(state.timeIsVisible(t) !== true)
4815 return NETDATA.easypiechartClearSelection(state);
4817 var slot = state.calculateRowForTime(t);
4818 if(slot < 0 || slot >= state.data.result.length)
4819 return NETDATA.easypiechartClearSelection(state);
4821 if(typeof state.easyPieChartEvent === 'undefined') {
4822 state.easyPieChartEvent = {
4829 var value = state.data.result[state.data.result.length - 1 - slot];
4830 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4831 var pcent = NETDATA.percentFromValueMax(value, max);
4833 state.easyPieChartEvent.value = value;
4834 state.easyPieChartEvent.pcent = pcent;
4835 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4837 if(state.easyPieChartEvent.timer === null) {
4838 state.easyPieChart_instance.disableAnimation();
4840 state.easyPieChartEvent.timer = setTimeout(function() {
4841 state.easyPieChartEvent.timer = null;
4842 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4843 }, NETDATA.options.current.charts_selection_animation_delay);
4849 NETDATA.easypiechartChartUpdate = function(state, data) {
4850 var value, max, pcent;
4852 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4858 value = data.result[0];
4859 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4860 pcent = NETDATA.percentFromValueMax(value, max);
4863 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4864 state.easyPieChart_instance.update(pcent);
4868 NETDATA.easypiechartChartCreate = function(state, data) {
4869 var self = $(state.element);
4870 var chart = $(state.element_chart);
4872 var value = data.result[0];
4873 var max = self.data('easypiechart-max-value') || null;
4874 var adjust = self.data('easypiechart-adjust') || null;
4878 state.easyPieChartMax = null;
4881 state.easyPieChartMax = max;
4883 var pcent = NETDATA.percentFromValueMax(value, max);
4885 chart.data('data-percent', pcent);
4889 case 'width': size = state.chartHeight(); break;
4890 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4891 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4893 default: size = state.chartWidth(); break;
4895 state.element.style.width = size + 'px';
4896 state.element.style.height = size + 'px';
4898 var stroke = Math.floor(size / 22);
4899 if(stroke < 3) stroke = 2;
4901 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4902 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4903 state.easyPieChartLabel = document.createElement('span');
4904 state.easyPieChartLabel.className = 'easyPieChartLabel';
4905 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4906 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4907 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4908 state.element_chart.appendChild(state.easyPieChartLabel);
4910 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4911 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4912 state.easyPieChartTitle = document.createElement('span');
4913 state.easyPieChartTitle.className = 'easyPieChartTitle';
4914 state.easyPieChartTitle.innerHTML = state.title;
4915 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4916 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4917 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4918 state.element_chart.appendChild(state.easyPieChartTitle);
4920 var unitfontsize = Math.round(titlefontsize * 0.9);
4921 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4922 state.easyPieChartUnits = document.createElement('span');
4923 state.easyPieChartUnits.className = 'easyPieChartUnits';
4924 state.easyPieChartUnits.innerHTML = state.units;
4925 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4926 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4927 state.element_chart.appendChild(state.easyPieChartUnits);
4929 chart.easyPieChart({
4930 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4931 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4932 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4933 scaleLength: self.data('easypiechart-scalelength') || 5,
4934 lineCap: self.data('easypiechart-linecap') || 'round',
4935 lineWidth: self.data('easypiechart-linewidth') || stroke,
4936 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4937 size: self.data('easypiechart-size') || size,
4938 rotate: self.data('easypiechart-rotate') || 0,
4939 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4940 easing: self.data('easypiechart-easing') || undefined
4943 // when we just re-create the chart
4944 // do not animate the first update
4946 if(typeof state.easyPieChart_instance !== 'undefined')
4949 state.easyPieChart_instance = chart.data('easyPieChart');
4950 if(animate === false) state.easyPieChart_instance.disableAnimation();
4951 state.easyPieChart_instance.update(pcent);
4952 if(animate === false) state.easyPieChart_instance.enableAnimation();
4956 // ----------------------------------------------------------------------------------------------------------------
4959 NETDATA.gaugeInitialize = function(callback) {
4960 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4962 url: NETDATA.gauge_js,
4965 xhrFields: { withCredentials: true } // required for the cookie
4968 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4971 NETDATA.chartLibraries.gauge.enabled = false;
4972 NETDATA.error(100, NETDATA.gauge_js);
4974 .always(function() {
4975 if(typeof callback === "function")
4980 NETDATA.chartLibraries.gauge.enabled = false;
4981 if(typeof callback === "function")
4986 NETDATA.gaugeAnimation = function(state, status) {
4989 if(typeof status === 'boolean' && status === false)
4991 else if(typeof status === 'number')
4994 state.gauge_instance.animationSpeed = speed;
4995 state.___gaugeOld__.speed = speed;
4998 NETDATA.gaugeSet = function(state, value, min, max) {
4999 if(typeof value !== 'number') value = 0;
5000 if(typeof min !== 'number') min = 0;
5001 if(typeof max !== 'number') max = 0;
5002 if(value > max) max = value;
5003 if(value < min) min = value;
5012 // gauge.js has an issue if the needle
5013 // is smaller than min or larger than max
5014 // when we set the new values
5015 // the needle will go crazy
5017 // to prevent it, we always feed it
5018 // with a percentage, so that the needle
5019 // is always between min and max
5020 var pcent = (value - min) * 100 / (max - min);
5022 // these should never happen
5023 if(pcent < 0) pcent = 0;
5024 if(pcent > 100) pcent = 100;
5026 state.gauge_instance.set(pcent);
5028 state.___gaugeOld__.value = value;
5029 state.___gaugeOld__.min = min;
5030 state.___gaugeOld__.max = max;
5033 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5034 if(state.___gaugeOld__.valueLabel !== value) {
5035 state.___gaugeOld__.valueLabel = value;
5036 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5038 if(state.___gaugeOld__.minLabel !== min) {
5039 state.___gaugeOld__.minLabel = min;
5040 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5042 if(state.___gaugeOld__.maxLabel !== max) {
5043 state.___gaugeOld__.maxLabel = max;
5044 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5048 NETDATA.gaugeClearSelection = function(state) {
5049 if(typeof state.gaugeEvent !== 'undefined') {
5050 if(state.gaugeEvent.timer !== null)
5051 clearTimeout(state.gaugeEvent.timer);
5053 state.gaugeEvent.timer = null;
5056 if(state.isAutoRefreshable() === true && state.data !== null) {
5057 NETDATA.gaugeChartUpdate(state, state.data);
5060 NETDATA.gaugeAnimation(state, false);
5061 NETDATA.gaugeSet(state, null, null, null);
5062 NETDATA.gaugeSetLabels(state, null, null, null);
5065 NETDATA.gaugeAnimation(state, true);
5069 NETDATA.gaugeSetSelection = function(state, t) {
5070 if(state.timeIsVisible(t) !== true)
5071 return NETDATA.gaugeClearSelection(state);
5073 var slot = state.calculateRowForTime(t);
5074 if(slot < 0 || slot >= state.data.result.length)
5075 return NETDATA.gaugeClearSelection(state);
5077 if(typeof state.gaugeEvent === 'undefined') {
5078 state.gaugeEvent = {
5086 var value = state.data.result[state.data.result.length - 1 - slot];
5087 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5090 state.gaugeEvent.value = value;
5091 state.gaugeEvent.max = max;
5092 state.gaugeEvent.min = min;
5093 NETDATA.gaugeSetLabels(state, value, min, max);
5095 if(state.gaugeEvent.timer === null) {
5096 NETDATA.gaugeAnimation(state, false);
5098 state.gaugeEvent.timer = setTimeout(function() {
5099 state.gaugeEvent.timer = null;
5100 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5101 }, NETDATA.options.current.charts_selection_animation_delay);
5107 NETDATA.gaugeChartUpdate = function(state, data) {
5108 var value, min, max;
5110 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5114 NETDATA.gaugeSetLabels(state, null, null, null);
5117 value = data.result[0];
5119 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5120 if(value > max) max = value;
5121 NETDATA.gaugeSetLabels(state, value, min, max);
5124 NETDATA.gaugeSet(state, value, min, max);
5128 NETDATA.gaugeChartCreate = function(state, data) {
5129 var self = $(state.element);
5130 // var chart = $(state.element_chart);
5132 var value = data.result[0];
5133 var max = self.data('gauge-max-value') || null;
5134 var adjust = self.data('gauge-adjust') || null;
5135 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5136 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5137 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5138 var stopColor = self.data('gauge-stop-color') || void 0;
5139 var generateGradient = self.data('gauge-generate-gradient') || false;
5143 state.gaugeMax = null;
5146 state.gaugeMax = max;
5148 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5150 // case 'width': width = height * ratio; break;
5152 // default: height = width / ratio; break;
5154 //state.element.style.width = width.toString() + 'px';
5155 //state.element.style.height = height.toString() + 'px';
5160 lines: 12, // The number of lines to draw
5161 angle: 0.15, // The length of each line
5162 lineWidth: 0.44, // 0.44 The line thickness
5164 length: 0.8, // 0.9 The radius of the inner circle
5165 strokeWidth: 0.035, // The rotation offset
5166 color: pointerColor // Fill color
5168 colorStart: startColor, // Colors
5169 colorStop: stopColor, // just experiment with them
5170 strokeColor: strokeColor, // to see which ones work best for you
5172 generateGradient: (generateGradient === true)?true:false,
5176 if (generateGradient.constructor === Array) {
5178 // data-gauge-generate-gradient="[0, 50, 100]"
5179 // data-gauge-gradient-percent-color-0="#FFFFFF"
5180 // data-gauge-gradient-percent-color-50="#999900"
5181 // data-gauge-gradient-percent-color-100="#000000"
5183 options.percentColors = new Array();
5184 var len = generateGradient.length;
5186 var pcent = generateGradient[len];
5187 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5188 if(color !== false) {
5189 var a = new Array();
5192 options.percentColors.unshift(a);
5195 if(options.percentColors.length === 0)
5196 delete options.percentColors;
5198 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5199 options.percentColors = [
5200 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5201 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5202 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5203 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5204 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5205 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5206 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5207 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5208 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5209 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5210 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5213 state.gauge_canvas = document.createElement('canvas');
5214 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5215 state.gauge_canvas.className = 'gaugeChart';
5216 state.gauge_canvas.width = width;
5217 state.gauge_canvas.height = height;
5218 state.element_chart.appendChild(state.gauge_canvas);
5220 var valuefontsize = Math.floor(height / 6);
5221 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5222 state.gaugeChartLabel = document.createElement('span');
5223 state.gaugeChartLabel.className = 'gaugeChartLabel';
5224 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5225 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5226 state.element_chart.appendChild(state.gaugeChartLabel);
5228 var titlefontsize = Math.round(valuefontsize / 2);
5230 state.gaugeChartTitle = document.createElement('span');
5231 state.gaugeChartTitle.className = 'gaugeChartTitle';
5232 state.gaugeChartTitle.innerHTML = state.title;
5233 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5234 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5235 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5236 state.element_chart.appendChild(state.gaugeChartTitle);
5238 var unitfontsize = Math.round(titlefontsize * 0.9);
5239 state.gaugeChartUnits = document.createElement('span');
5240 state.gaugeChartUnits.className = 'gaugeChartUnits';
5241 state.gaugeChartUnits.innerHTML = state.units;
5242 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5243 state.element_chart.appendChild(state.gaugeChartUnits);
5245 state.gaugeChartMin = document.createElement('span');
5246 state.gaugeChartMin.className = 'gaugeChartMin';
5247 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5248 state.element_chart.appendChild(state.gaugeChartMin);
5250 state.gaugeChartMax = document.createElement('span');
5251 state.gaugeChartMax.className = 'gaugeChartMax';
5252 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5253 state.element_chart.appendChild(state.gaugeChartMax);
5255 // when we just re-create the chart
5256 // do not animate the first update
5258 if(typeof state.gauge_instance !== 'undefined')
5261 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5263 state.___gaugeOld__ = {
5272 // we will always feed a percentage
5273 state.gauge_instance.minValue = 0;
5274 state.gauge_instance.maxValue = 100;
5276 NETDATA.gaugeAnimation(state, animate);
5277 NETDATA.gaugeSet(state, value, 0, max);
5278 NETDATA.gaugeSetLabels(state, value, 0, max);
5279 NETDATA.gaugeAnimation(state, true);
5283 // ----------------------------------------------------------------------------------------------------------------
5284 // Charts Libraries Registration
5286 NETDATA.chartLibraries = {
5288 initialize: NETDATA.dygraphInitialize,
5289 create: NETDATA.dygraphChartCreate,
5290 update: NETDATA.dygraphChartUpdate,
5291 resize: function(state) {
5292 if(typeof state.dygraph_instance.resize === 'function')
5293 state.dygraph_instance.resize();
5295 setSelection: NETDATA.dygraphSetSelection,
5296 clearSelection: NETDATA.dygraphClearSelection,
5297 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5300 format: function(state) { return 'json'; },
5301 options: function(state) { return 'ms|flip'; },
5302 legend: function(state) {
5303 if(this.isSparkline(state) === false)
5304 return 'right-side';
5308 autoresize: function(state) { return true; },
5309 max_updates_to_recreate: function(state) { return 5000; },
5310 track_colors: function(state) { return true; },
5311 pixels_per_point: function(state) {
5312 if(this.isSparkline(state) === false)
5318 isSparkline: function(state) {
5319 if(typeof state.dygraph_sparkline === 'undefined') {
5320 var t = $(state.element).data('dygraph-theme');
5321 if(t === 'sparkline')
5322 state.dygraph_sparkline = true;
5324 state.dygraph_sparkline = false;
5326 return state.dygraph_sparkline;
5330 initialize: NETDATA.sparklineInitialize,
5331 create: NETDATA.sparklineChartCreate,
5332 update: NETDATA.sparklineChartUpdate,
5334 setSelection: undefined, // function(state, t) { return true; },
5335 clearSelection: undefined, // function(state) { return true; },
5336 toolboxPanAndZoom: null,
5339 format: function(state) { return 'array'; },
5340 options: function(state) { return 'flip|abs'; },
5341 legend: function(state) { return null; },
5342 autoresize: function(state) { return false; },
5343 max_updates_to_recreate: function(state) { return 5000; },
5344 track_colors: function(state) { return false; },
5345 pixels_per_point: function(state) { return 3; }
5348 initialize: NETDATA.peityInitialize,
5349 create: NETDATA.peityChartCreate,
5350 update: NETDATA.peityChartUpdate,
5352 setSelection: undefined, // function(state, t) { return true; },
5353 clearSelection: undefined, // function(state) { return true; },
5354 toolboxPanAndZoom: null,
5357 format: function(state) { return 'ssvcomma'; },
5358 options: function(state) { return 'null2zero|flip|abs'; },
5359 legend: function(state) { return null; },
5360 autoresize: function(state) { return false; },
5361 max_updates_to_recreate: function(state) { return 5000; },
5362 track_colors: function(state) { return false; },
5363 pixels_per_point: function(state) { return 3; }
5366 initialize: NETDATA.morrisInitialize,
5367 create: NETDATA.morrisChartCreate,
5368 update: NETDATA.morrisChartUpdate,
5370 setSelection: undefined, // function(state, t) { return true; },
5371 clearSelection: undefined, // function(state) { return true; },
5372 toolboxPanAndZoom: null,
5375 format: function(state) { return 'json'; },
5376 options: function(state) { return 'objectrows|ms'; },
5377 legend: function(state) { return null; },
5378 autoresize: function(state) { return false; },
5379 max_updates_to_recreate: function(state) { return 50; },
5380 track_colors: function(state) { return false; },
5381 pixels_per_point: function(state) { return 15; }
5384 initialize: NETDATA.googleInitialize,
5385 create: NETDATA.googleChartCreate,
5386 update: NETDATA.googleChartUpdate,
5388 setSelection: undefined, //function(state, t) { return true; },
5389 clearSelection: undefined, //function(state) { return true; },
5390 toolboxPanAndZoom: null,
5393 format: function(state) { return 'datatable'; },
5394 options: function(state) { return ''; },
5395 legend: function(state) { return null; },
5396 autoresize: function(state) { return false; },
5397 max_updates_to_recreate: function(state) { return 300; },
5398 track_colors: function(state) { return false; },
5399 pixels_per_point: function(state) { return 4; }
5402 initialize: NETDATA.raphaelInitialize,
5403 create: NETDATA.raphaelChartCreate,
5404 update: NETDATA.raphaelChartUpdate,
5406 setSelection: undefined, // function(state, t) { return true; },
5407 clearSelection: undefined, // function(state) { return true; },
5408 toolboxPanAndZoom: null,
5411 format: function(state) { return 'json'; },
5412 options: function(state) { return ''; },
5413 legend: function(state) { return null; },
5414 autoresize: function(state) { return false; },
5415 max_updates_to_recreate: function(state) { return 5000; },
5416 track_colors: function(state) { return false; },
5417 pixels_per_point: function(state) { return 3; }
5420 initialize: NETDATA.c3Initialize,
5421 create: NETDATA.c3ChartCreate,
5422 update: NETDATA.c3ChartUpdate,
5424 setSelection: undefined, // function(state, t) { return true; },
5425 clearSelection: undefined, // function(state) { return true; },
5426 toolboxPanAndZoom: null,
5429 format: function(state) { return 'csvjsonarray'; },
5430 options: function(state) { return 'milliseconds'; },
5431 legend: function(state) { return null; },
5432 autoresize: function(state) { return false; },
5433 max_updates_to_recreate: function(state) { return 5000; },
5434 track_colors: function(state) { return false; },
5435 pixels_per_point: function(state) { return 15; }
5438 initialize: NETDATA.d3Initialize,
5439 create: NETDATA.d3ChartCreate,
5440 update: NETDATA.d3ChartUpdate,
5442 setSelection: undefined, // function(state, t) { return true; },
5443 clearSelection: undefined, // function(state) { return true; },
5444 toolboxPanAndZoom: null,
5447 format: function(state) { return 'json'; },
5448 options: function(state) { return ''; },
5449 legend: function(state) { return null; },
5450 autoresize: function(state) { return false; },
5451 max_updates_to_recreate: function(state) { return 5000; },
5452 track_colors: function(state) { return false; },
5453 pixels_per_point: function(state) { return 3; }
5456 initialize: NETDATA.easypiechartInitialize,
5457 create: NETDATA.easypiechartChartCreate,
5458 update: NETDATA.easypiechartChartUpdate,
5460 setSelection: NETDATA.easypiechartSetSelection,
5461 clearSelection: NETDATA.easypiechartClearSelection,
5462 toolboxPanAndZoom: null,
5465 format: function(state) { return 'array'; },
5466 options: function(state) { return 'absolute'; },
5467 legend: function(state) { return null; },
5468 autoresize: function(state) { return false; },
5469 max_updates_to_recreate: function(state) { return 5000; },
5470 track_colors: function(state) { return true; },
5471 pixels_per_point: function(state) { return 3; },
5475 initialize: NETDATA.gaugeInitialize,
5476 create: NETDATA.gaugeChartCreate,
5477 update: NETDATA.gaugeChartUpdate,
5479 setSelection: NETDATA.gaugeSetSelection,
5480 clearSelection: NETDATA.gaugeClearSelection,
5481 toolboxPanAndZoom: null,
5484 format: function(state) { return 'array'; },
5485 options: function(state) { return 'absolute'; },
5486 legend: function(state) { return null; },
5487 autoresize: function(state) { return false; },
5488 max_updates_to_recreate: function(state) { return 5000; },
5489 track_colors: function(state) { return true; },
5490 pixels_per_point: function(state) { return 3; },
5495 NETDATA.registerChartLibrary = function(library, url) {
5496 if(NETDATA.options.debug.libraries === true)
5497 console.log("registering chart library: " + library);
5499 NETDATA.chartLibraries[library].url = url;
5500 NETDATA.chartLibraries[library].initialized = true;
5501 NETDATA.chartLibraries[library].enabled = true;
5504 // ----------------------------------------------------------------------------------------------------------------
5505 // Load required JS libraries and CSS
5507 NETDATA.requiredJs = [
5509 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5510 isAlreadyLoaded: function() {
5511 // check if bootstrap is loaded
5512 if(typeof $().emulateTransitionEnd == 'function')
5515 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5523 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5524 isAlreadyLoaded: function() { return false; }
5527 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5528 isAlreadyLoaded: function() { return false; }
5532 NETDATA.requiredCSS = [
5534 url: NETDATA.themes.current.bootstrap_css,
5535 isAlreadyLoaded: function() {
5536 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5543 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5544 isAlreadyLoaded: function() { return false; }
5547 url: NETDATA.themes.current.dashboard_css,
5548 isAlreadyLoaded: function() { return false; }
5551 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5552 isAlreadyLoaded: function() { return false; }
5556 NETDATA.loadedRequiredJs = 0;
5557 NETDATA.loadRequiredJs = function(index, callback) {
5558 if(index >= NETDATA.requiredJs.length) return;
5560 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5561 NETDATA.loadedRequiredJs++;
5562 NETDATA.loadRequiredJs(++index, callback);
5566 if(NETDATA.options.debug.main_loop === true)
5567 console.log('loading ' + NETDATA.requiredJs[index].url);
5570 url: NETDATA.requiredJs[index].url,
5574 xhrFields: { withCredentials: true } // required for the cookie
5577 if(NETDATA.options.debug.main_loop === true)
5578 console.log('loaded ' + NETDATA.requiredJs[index].url);
5581 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5583 .always(function() {
5584 NETDATA.loadedRequiredJs++;
5585 if(typeof callback === 'function' && NETDATA.loadedRequiredJs >= NETDATA.requiredJs.length)
5589 NETDATA.loadRequiredJs(++index, callback);
5592 NETDATA.loadRequiredCSS = function(index) {
5593 if(index >= NETDATA.requiredCSS.length)
5596 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5597 NETDATA.loadRequiredCSS(++index);
5601 if(NETDATA.options.debug.main_loop === true)
5602 console.log('loading ' + NETDATA.requiredCSS[index].url);
5604 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5605 NETDATA.loadRequiredCSS(++index);
5609 // ----------------------------------------------------------------------------------------------------------------
5610 // Registry of netdata hosts
5613 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5614 chart_div_offset: 100, // give that space above the chart when scrolling to it
5615 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5616 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5618 ms_penalty: 0, // the time penalty of the next alarm
5619 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5620 // if alarms are shown faster than: one per 500ms
5622 notifications: false, // when true, the browser supports notifications (may not be granted though)
5623 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5624 first_notification_id: 0, // the id of the first alarm_log entry for this session
5625 // this is used to prevent CLEAR notifications for past events
5626 // notifications_shown: new Array(),
5628 server: null, // the server to connect to for fetching alarms
5629 current: null, // the list of raised alarms - updated in the background
5630 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5632 notify: function(entry) {
5633 // console.log('alarm ' + entry.unique_id);
5635 if(entry.updated === true) {
5636 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5640 var value = entry.value;
5641 if(NETDATA.alarms.current !== null) {
5642 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5643 if(typeof t !== 'undefined' && entry.status == t.status)
5647 var name = entry.name.replace(/_/g, ' ');
5648 var status = entry.status.toLowerCase();
5649 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5650 var tag = entry.alarm_id;
5651 var icon = 'images/seo-performance-128.png';
5652 var interaction = false;
5656 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5658 switch(entry.status) {
5666 case 'UNINITIALIZED':
5670 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5671 // console.log('alarm ' + entry.unique_id + ' is not current');
5674 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5675 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5678 title = name + ' back to normal';
5679 icon = 'images/check-mark-2-128-green.png'
5680 interaction = false;
5684 if(entry.old_status === 'CRITICAL')
5685 status = 'demoted to ' + entry.status.toLowerCase();
5687 icon = 'images/alert-128-orange.png';
5688 interaction = false;
5692 if(entry.old_status === 'WARNING')
5693 status = 'escalated to ' + entry.status.toLowerCase();
5695 icon = 'images/alert-128-red.png'
5700 console.log('invalid alarm status ' + entry.status);
5705 // cleanup old notifications with the same alarm_id as this one
5706 // FIXME: it does not seem to work on any web browser!
5707 var len = NETDATA.alarms.notifications_shown.length;
5709 var n = NETDATA.alarms.notifications_shown[len];
5710 if(n.data.alarm_id === entry.alarm_id) {
5711 console.log('removing old alarm ' + n.data.unique_id);
5713 // close the notification
5716 // remove it from the array
5717 NETDATA.alarms.notifications_shown.splice(len, 1);
5718 len = NETDATA.alarms.notifications_shown.length;
5725 setTimeout(function() {
5726 // show this notification
5727 // console.log('new notification: ' + title);
5728 var n = new Notification(title, {
5729 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5731 requireInteraction: interaction,
5732 icon: NETDATA.serverDefault + icon,
5736 n.onclick = function(event) {
5737 event.preventDefault();
5738 NETDATA.alarms.onclick(event.target.data);
5742 // NETDATA.alarms.notifications_shown.push(n);
5743 // console.log(entry);
5744 }, NETDATA.alarms.ms_penalty);
5746 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5750 scrollToChart: function(chart_id) {
5751 if(typeof chart_id === 'string') {
5752 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5753 if(typeof offset !== 'undefined') {
5754 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5761 scrollToAlarm: function(alarm) {
5762 if(typeof alarm === 'object') {
5763 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5765 if(ret === true && NETDATA.options.page_is_visible === false)
5767 // 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.');
5772 notifyAll: function() {
5773 // console.log('FETCHING ALARM LOG');
5774 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5775 // console.log('ALARM LOG FETCHED');
5777 if(data === null || typeof data !== 'object') {
5778 console.log('invalid alarms log response');
5782 if(data.length === 0) {
5783 console.log('received empty alarm log');
5787 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5789 data.sort(function(a, b) {
5790 if(a.unique_id > b.unique_id) return -1;
5791 if(a.unique_id < b.unique_id) return 1;
5795 NETDATA.alarms.ms_penalty = 0;
5797 var len = data.length;
5799 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5800 NETDATA.alarms.notify(data[len]);
5803 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5806 NETDATA.alarms.last_notification_id = data[0].unique_id;
5807 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5808 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5812 check_notifications: function() {
5813 // returns true if we should fire 1+ notifications
5815 if(NETDATA.alarms.notifications !== true) {
5816 // console.log('notifications not available');
5820 if(Notification.permission !== 'granted') {
5821 // console.log('notifications not granted');
5825 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5826 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5828 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5829 // console.log('new alarms detected');
5832 //else console.log('no new alarms');
5834 // else console.log('cannot process alarms');
5839 get: function(what, callback) {
5841 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5844 xhrFields: { withCredentials: true } // required for the cookie
5846 .done(function(data) {
5847 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5848 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5850 if(typeof callback === 'function')
5854 NETDATA.error(415, NETDATA.alarms.server);
5856 if(typeof callback === 'function')
5861 update_forever: function() {
5862 NETDATA.alarms.get('active', function(data) {
5864 NETDATA.alarms.current = data;
5866 if(NETDATA.alarms.check_notifications() === true) {
5867 NETDATA.alarms.notifyAll();
5870 if (typeof NETDATA.alarms.callback === 'function') {
5871 NETDATA.alarms.callback(data);
5874 // Health monitoring is disabled on this netdata
5875 if(data.status === false) return;
5878 setTimeout(NETDATA.alarms.update_forever, 10000);
5882 get_log: function(last_id, callback) {
5883 // console.log('fetching all log after ' + last_id.toString());
5885 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5888 xhrFields: { withCredentials: true } // required for the cookie
5890 .done(function(data) {
5891 if(typeof callback === 'function')
5895 NETDATA.error(416, NETDATA.alarms.server);
5897 if(typeof callback === 'function')
5903 var host = NETDATA.serverDefault;
5904 while(host.slice(-1) === '/')
5905 host = host.substring(0, host.length - 1);
5906 NETDATA.alarms.server = host;
5908 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5910 if(NETDATA.alarms.onclick === null)
5911 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5913 if(netdataShowAlarms === true) {
5914 NETDATA.alarms.update_forever();
5916 if('Notification' in window) {
5917 // console.log('notifications available');
5918 NETDATA.alarms.notifications = true;
5920 if(Notification.permission === 'default')
5921 Notification.requestPermission();
5927 // ----------------------------------------------------------------------------------------------------------------
5928 // Registry of netdata hosts
5930 NETDATA.registry = {
5931 server: null, // the netdata registry server
5932 person_guid: null, // the unique ID of this browser / user
5933 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5934 hostname: null, // the hostname of the netdata server that served dashboard.js
5935 machines: null, // the user's other URLs
5936 machines_array: null, // the user's other URLs in an array
5939 parsePersonUrls: function(person_urls) {
5940 // console.log(person_urls);
5941 NETDATA.registry.person_urls = person_urls;
5944 NETDATA.registry.machines = {};
5945 NETDATA.registry.machines_array = new Array();
5947 var now = new Date().getTime();
5948 var apu = person_urls;
5951 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5952 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5958 accesses: apu[i][3],
5960 alternate_urls: new Array()
5962 obj.alternate_urls.push(apu[i][1]);
5964 NETDATA.registry.machines[apu[i][0]] = obj;
5965 NETDATA.registry.machines_array.push(obj);
5968 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5970 var pu = NETDATA.registry.machines[apu[i][0]];
5971 if(pu.last_t < apu[i][2]) {
5973 pu.last_t = apu[i][2];
5974 pu.name = apu[i][4];
5976 pu.accesses += apu[i][3];
5977 pu.alternate_urls.push(apu[i][1]);
5982 if(typeof netdataRegistryCallback === 'function')
5983 netdataRegistryCallback(NETDATA.registry.machines_array);
5987 if(netdataRegistry !== true) return;
5989 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5991 NETDATA.registry.server = data.registry;
5992 NETDATA.registry.machine_guid = data.machine_guid;
5993 NETDATA.registry.hostname = data.hostname;
5995 NETDATA.registry.access(2, function (person_urls) {
5996 NETDATA.registry.parsePersonUrls(person_urls);
6003 hello: function(host, callback) {
6004 while(host.slice(-1) === '/')
6005 host = host.substring(0, host.length - 1);
6007 // send HELLO to a netdata server:
6008 // 1. verifies the server is reachable
6009 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6011 url: host + '/api/v1/registry?action=hello',
6014 xhrFields: { withCredentials: true } // required for the cookie
6016 .done(function(data) {
6017 if(typeof data.status !== 'string' || data.status !== 'ok') {
6018 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6022 if(typeof callback === 'function')
6026 NETDATA.error(407, host);
6028 if(typeof callback === 'function')
6033 access: function(max_redirects, callback) {
6034 // send ACCESS to a netdata registry:
6035 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6036 // 2. it responds with a list of netdata servers we know
6037 // the registry identifies us using a cookie it sets the first time we access it
6038 // the registry may respond with a redirect URL to send us to another registry
6040 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),
6043 xhrFields: { withCredentials: true } // required for the cookie
6045 .done(function(data) {
6046 var redirect = null;
6047 if(typeof data.registry === 'string')
6048 redirect = data.registry;
6050 if(typeof data.status !== 'string' || data.status !== 'ok') {
6051 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6056 if(redirect !== null && max_redirects > 0) {
6057 NETDATA.registry.server = redirect;
6058 NETDATA.registry.access(max_redirects - 1, callback);
6061 if(typeof callback === 'function')
6066 if(typeof data.person_guid === 'string')
6067 NETDATA.registry.person_guid = data.person_guid;
6069 if(typeof callback === 'function')
6070 callback(data.urls);
6074 NETDATA.error(410, NETDATA.registry.server);
6076 if(typeof callback === 'function')
6081 delete: function(delete_url, callback) {
6082 // send DELETE to a netdata registry:
6084 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),
6087 xhrFields: { withCredentials: true } // required for the cookie
6089 .done(function(data) {
6090 if(typeof data.status !== 'string' || data.status !== 'ok') {
6091 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6095 if(typeof callback === 'function')
6099 NETDATA.error(412, NETDATA.registry.server);
6101 if(typeof callback === 'function')
6106 switch: function(new_person_guid, callback) {
6109 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,
6112 xhrFields: { withCredentials: true } // required for the cookie
6114 .done(function(data) {
6115 if(typeof data.status !== 'string' || data.status !== 'ok') {
6116 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6120 if(typeof callback === 'function')
6124 NETDATA.error(414, NETDATA.registry.server);
6126 if(typeof callback === 'function')
6132 // ----------------------------------------------------------------------------------------------------------------
6135 if(typeof netdataPrepCallback === 'function')
6136 netdataPrepCallback();
6138 NETDATA.errorReset();
6139 NETDATA.loadRequiredCSS(0);
6141 NETDATA._loadjQuery(function() {
6142 NETDATA.loadRequiredJs(0, function() {
6143 if(typeof $().emulateTransitionEnd !== 'function') {
6144 // bootstrap is not available
6145 NETDATA.options.current.show_help = false;
6148 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6149 if(NETDATA.options.debug.main_loop === true)
6150 console.log('starting chart refresh thread');
6157 // window.NETDATA = NETDATA;
6158 // })(window, document);