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 netdataNoRegistry = true; // Don't update the registry for this access
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
19 // You can also set the default netdata server, using the following.
20 // When this variable is not set, we assume the page is hosted on your
21 // netdata server already.
22 // var netdataServer = "http://yourhost:19999"; // set your NetData server
24 //(function(window, document, undefined) {
26 // ------------------------------------------------------------------------
27 // compatibility fixes
29 // fix IE issue with console
30 if(!window.console) { window.console = { log: function(){} }; }
32 // if string.endsWith is not defined, define it
33 if(typeof String.prototype.endsWith !== 'function') {
34 String.prototype.endsWith = function(s) {
35 if(s.length > this.length) return false;
36 return this.slice(-s.length) === s;
40 // if string.startsWith is not defined, define it
41 if(typeof String.prototype.startsWith !== 'function') {
42 String.prototype.startsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(s.length) === s;
49 var NETDATA = window.NETDATA || {};
51 // ----------------------------------------------------------------------------------------------------------------
52 // Detect the netdata server
54 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
55 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
56 NETDATA._scriptSource = function() {
59 if(typeof document.currentScript !== 'undefined') {
60 script = document.currentScript;
63 var all_scripts = document.getElementsByTagName('script');
64 script = all_scripts[all_scripts.length - 1];
67 if (typeof script.getAttribute.length !== 'undefined')
70 script = script.getAttribute('src', -1);
75 if(typeof netdataServer !== 'undefined')
76 NETDATA.serverDefault = netdataServer;
78 var s = NETDATA._scriptSource();
79 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
81 console.log('WARNING: Cannot detect the URL of the netdata server.');
82 NETDATA.serverDefault = null;
86 if(NETDATA.serverDefault === null)
87 NETDATA.serverDefault = '';
88 else if(NETDATA.serverDefault.slice(-1) !== '/')
89 NETDATA.serverDefault += '/';
91 // default URLs for all the external files we need
92 // make them RELATIVE so that the whole thing can also be
93 // installed under a web server
94 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
95 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
96 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
97 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
98 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
99 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
100 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
101 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
102 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
103 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
104 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
105 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
106 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
107 NETDATA.google_js = 'https://www.google.com/jsapi';
111 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
112 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
113 background: '#FFFFFF',
114 foreground: '#000000',
117 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
118 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
119 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
120 '#329262', '#3B3EAC' ],
121 easypiechart_track: '#f0f0f0',
122 easypiechart_scale: '#dfe0e0',
123 gauge_pointer: '#C0C0C0',
124 gauge_stroke: '#F0F0F0',
125 gauge_gradient: false
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
130 background: '#272b30',
131 foreground: '#C8C8C8',
134 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
135 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
136 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
137 '#a6a479', '#a66da8' ],
139 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
140 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
141 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
142 '#329262', '#3B3EFF' ],
143 easypiechart_track: '#373b40',
144 easypiechart_scale: '#373b40',
145 gauge_pointer: '#474b50',
146 gauge_stroke: '#373b40',
147 gauge_gradient: false
151 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
152 NETDATA.themes.current = NETDATA.themes[netdataTheme];
154 NETDATA.themes.current = NETDATA.themes.white;
156 NETDATA.colors = NETDATA.themes.current.colors;
158 // these are the colors Google Charts are using
159 // we have them here to attempt emulate their look and feel on the other chart libraries
160 // http://there4.io/2012/05/02/google-chart-color-list/
161 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
162 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
163 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
165 // an alternative set
166 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
167 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
168 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
170 // ----------------------------------------------------------------------------------------------------------------
171 // the defaults for all charts
173 // if the user does not specify any of these, the following will be used
175 NETDATA.chartDefaults = {
176 host: NETDATA.serverDefault, // the server to get data from
177 width: '100%', // the chart width - can be null
178 height: '100%', // the chart height - can be null
179 min_width: null, // the chart minimum width - can be null
180 library: 'dygraph', // the graphing library to use
181 method: 'average', // the grouping method
182 before: 0, // panning
183 after: -600, // panning
184 pixels_per_point: 1, // the detail of the chart
185 fill_luminance: 0.8 // luminance of colors in solit areas
188 // ----------------------------------------------------------------------------------------------------------------
192 pauseCallback: null, // a callback when we are really paused
194 pause: false, // when enabled we don't auto-refresh the charts
196 targets: null, // an array of all the state objects that are
197 // currently active (independently of their
198 // viewport visibility)
200 updated_dom: true, // when true, the DOM has been updated with
201 // new elements we have to check.
203 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
204 // rendering charts continiously.
205 // used with .current.fast_render_timeframe
207 page_is_visible: true, // when true, this page is visible
209 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
210 // auto-refresher for some time (when a chart is
211 // performing pan or zoom, we need to stop refreshing
212 // all other charts, to have the maximum speed for
213 // rendering the chart that is panned or zoomed).
214 // Used with .current.global_pan_sync_time
216 last_resized: new Date().getTime(), // the timestamp of the last resize request
218 last_page_scroll: 0, // the timestamp the last time the page was scrolled
220 // the current profile
221 // we may have many...
223 pixels_per_point: 1, // the minimum pixels per point for all charts
224 // increase this to speed javascript up
225 // each chart library has its own limit too
226 // the max of this and the chart library is used
227 // the final is calculated every time, so a change
228 // here will have immediate effect on the next chart
231 idle_between_charts: 100, // ms - how much time to wait between chart updates
233 fast_render_timeframe: 200, // ms - render continously until this time of continious
234 // rendering has been reached
235 // this setting is used to make it render e.g. 10
236 // charts at once, sleep idle_between_charts time
237 // and continue for another 10 charts.
239 idle_between_loops: 500, // ms - if all charts have been updated, wait this
240 // time before starting again.
242 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
244 idle_lost_focus: 500, // ms - when the window does not have focus, check
245 // if focus has been regained, every this time
247 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
248 // autorefreshing of charts is paused for this amount
251 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
252 // of time before setting up synchronized selections
255 sync_selection: true, // enable or disable selection sync
257 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
259 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
261 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
263 update_only_visible: true, // enable or disable visibility management
265 parallel_refresher: true, // enable parallel refresh of charts
267 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
269 destroy_on_hide: false, // destroy charts when they are not visible
271 show_help: true, // when enabled the charts will show some help
272 show_help_delay_show_ms: 500,
273 show_help_delay_hide_ms: 0,
275 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
277 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
278 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
280 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
282 smooth_plot: true, // enable smooth plot, where possible
284 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
286 color_fill_opacity_line: 1.0,
287 color_fill_opacity_area: 0.2,
288 color_fill_opacity_stacked: 0.8,
290 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
291 pan_and_zoom_factor_multiplier_control: 2.0,
292 pan_and_zoom_factor_multiplier_shift: 3.0,
293 pan_and_zoom_factor_multiplier_alt: 4.0,
295 setOptionCallback: function() { ; }
303 chart_data_url: false,
304 chart_errors: false, // FIXME
312 NETDATA.statistics = {
315 refreshes_active_max: 0
319 // ----------------------------------------------------------------------------------------------------------------
320 // local storage options
322 NETDATA.localStorage = {
325 callback: {} // only used for resetting back to defaults
328 NETDATA.localStorageGet = function(key, def, callback) {
331 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
332 NETDATA.localStorage.default[key.toString()] = def;
333 NETDATA.localStorage.callback[key.toString()] = callback;
336 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
338 // console.log('localStorage: loading "' + key.toString() + '"');
339 ret = localStorage.getItem(key.toString());
340 if(ret === null || ret === 'undefined') {
341 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
342 localStorage.setItem(key.toString(), JSON.stringify(def));
346 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
347 ret = JSON.parse(ret);
348 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
352 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
357 if(typeof ret === 'undefined' || ret === 'undefined') {
358 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
362 NETDATA.localStorage.current[key.toString()] = ret;
366 NETDATA.localStorageSet = function(key, value, callback) {
367 if(typeof value === 'undefined' || value === 'undefined') {
368 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
371 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372 NETDATA.localStorage.default[key.toString()] = value;
373 NETDATA.localStorage.current[key.toString()] = value;
374 NETDATA.localStorage.callback[key.toString()] = callback;
377 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
378 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
380 localStorage.setItem(key.toString(), JSON.stringify(value));
383 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
387 NETDATA.localStorage.current[key.toString()] = value;
391 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
393 if(typeof obj[i] === 'object') {
394 //console.log('object ' + prefix + '.' + i.toString());
395 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
399 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
403 NETDATA.setOption = function(key, value) {
404 if(key.toString() === 'setOptionCallback') {
405 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
406 NETDATA.options.current[key.toString()] = value;
407 NETDATA.options.current.setOptionCallback();
410 else if(NETDATA.options.current[key.toString()] !== value) {
411 var name = 'options.' + key.toString();
413 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
414 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
416 //console.log(NETDATA.localStorage);
417 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
418 //console.log(NETDATA.options);
419 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
421 if(typeof NETDATA.options.current.setOptionCallback === 'function')
422 NETDATA.options.current.setOptionCallback();
428 NETDATA.getOption = function(key) {
429 return NETDATA.options.current[key.toString()];
432 // read settings from local storage
433 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
435 // always start with this option enabled.
436 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
438 NETDATA.resetOptions = function() {
439 for(var i in NETDATA.localStorage.default) {
440 var a = i.split('.');
442 if(a[0] === 'options') {
443 if(a[1] === 'setOptionCallback') continue;
444 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
445 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
447 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
449 else if(a[0] === 'chart_heights') {
450 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
451 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
457 // ----------------------------------------------------------------------------------------------------------------
459 if(NETDATA.options.debug.main_loop === true)
460 console.log('welcome to NETDATA');
462 NETDATA.onresize = function() {
463 NETDATA.options.last_resized = new Date().getTime();
467 NETDATA.onscroll = function() {
468 // console.log('onscroll');
470 NETDATA.options.last_page_scroll = new Date().getTime();
471 if(NETDATA.options.targets === null) return;
473 // when the user scrolls he sees that we have
474 // hidden all the not-visible charts
475 // using this little function we try to switch
476 // the charts back to visible quickly
477 var targets = NETDATA.options.targets;
478 var len = targets.length;
479 while(len--) targets[len].isVisible();
482 window.onresize = NETDATA.onresize;
483 window.onscroll = NETDATA.onscroll;
485 // ----------------------------------------------------------------------------------------------------------------
488 NETDATA.errorCodes = {
489 100: { message: "Cannot load chart library", alert: true },
490 101: { message: "Cannot load jQuery", alert: true },
491 402: { message: "Chart library not found", alert: false },
492 403: { message: "Chart library not enabled/is failed", alert: false },
493 404: { message: "Chart not found", alert: false },
494 405: { message: "Cannot download charts index from server", alert: true },
495 406: { message: "Invalid charts index downloaded from server", alert: true },
496 407: { message: "Cannot HELLO netdata server", alert: false },
497 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
498 409: { message: "Cannot ACCESS netdata registry", alert: false },
499 410: { message: "Netdata registry ACCESS failed", alert: false },
500 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
501 412: { message: "Netdata registry DELETE failed", alert: false },
502 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
503 414: { message: "Netdata registry SWITCH failed", alert: false }
505 NETDATA.errorLast = {
511 NETDATA.error = function(code, msg) {
512 NETDATA.errorLast.code = code;
513 NETDATA.errorLast.message = msg;
514 NETDATA.errorLast.datetime = new Date().getTime();
516 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
519 if(typeof netdataErrorCallback === 'function') {
520 ret = netdataErrorCallback('system', code, msg);
523 if(ret && NETDATA.errorCodes[code].alert)
524 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
527 NETDATA.errorReset = function() {
528 NETDATA.errorLast.code = 0;
529 NETDATA.errorLast.message = "You are doing fine!";
530 NETDATA.errorLast.datetime = 0;
533 // ----------------------------------------------------------------------------------------------------------------
536 // When multiple charts need the same chart, we avoid downloading it
537 // multiple times (and having it in browser memory multiple time)
538 // by using this registry.
540 // Every time we download a chart definition, we save it here with .add()
541 // Then we try to get it back with .get(). If that fails, we download it.
543 NETDATA.chartRegistry = {
546 fixid: function(id) {
547 return id.replace(/:/g, "_").replace(/\//g, "_");
550 add: function(host, id, data) {
551 host = this.fixid(host);
554 if(typeof this.charts[host] === 'undefined')
555 this.charts[host] = {};
557 //console.log('added ' + host + '/' + id);
558 this.charts[host][id] = data;
561 get: function(host, id) {
562 host = this.fixid(host);
565 if(typeof this.charts[host] === 'undefined')
568 if(typeof this.charts[host][id] === 'undefined')
571 //console.log('cached ' + host + '/' + id);
572 return this.charts[host][id];
575 downloadAll: function(host, callback) {
576 while(host.slice(-1) === '/')
577 host = host.substring(0, host.length - 1);
582 url: host + '/api/v1/charts',
585 xhrFields: { withCredentials: true } // required for the cookie
587 .done(function(data) {
589 var h = NETDATA.chartRegistry.fixid(host);
590 self.charts[h] = data.charts;
592 else NETDATA.error(406, host + '/api/v1/charts');
594 if(typeof callback === 'function')
598 NETDATA.error(405, host + '/api/v1/charts');
600 if(typeof callback === 'function')
606 // ----------------------------------------------------------------------------------------------------------------
607 // Global Pan and Zoom on charts
609 // Using this structure are synchronize all the charts, so that
610 // when you pan or zoom one, all others are automatically refreshed
611 // to the same timespan.
613 NETDATA.globalPanAndZoom = {
614 seq: 0, // timestamp ms
615 // every time a chart is panned or zoomed
616 // we set the timestamp here
617 // then we use it as a sequence number
618 // to find if other charts are syncronized
621 master: null, // the master chart (state), to which all others
624 force_before_ms: null, // the timespan to sync all other charts
625 force_after_ms: null,
628 setMaster: function(state, after, before) {
629 if(NETDATA.options.current.sync_pan_and_zoom === false)
632 if(this.master !== null && this.master !== state)
633 this.master.resetChart(true, true);
635 var now = new Date().getTime();
638 this.force_after_ms = after;
639 this.force_before_ms = before;
640 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
644 clearMaster: function() {
645 if(this.master !== null) {
646 var st = this.master;
653 this.force_after_ms = null;
654 this.force_before_ms = null;
655 NETDATA.options.auto_refresher_stop_until = 0;
658 // is the given state the master of the global
659 // pan and zoom sync?
660 isMaster: function(state) {
661 if(this.master === state) return true;
665 // are we currently have a global pan and zoom sync?
666 isActive: function() {
667 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
671 // check if a chart, other than the master
672 // needs to be refreshed, due to the global pan and zoom
673 shouldBeAutoRefreshed: function(state) {
674 if(this.master === null || this.seq === 0)
677 //if(state.needsRecreation())
680 if(state.tm.pan_and_zoom_seq === this.seq)
687 // ----------------------------------------------------------------------------------------------------------------
688 // dimensions selection
691 // move color assignment to dimensions, here
693 dimensionStatus = function(parent, label, name_div, value_div, color) {
694 this.enabled = false;
695 this.parent = parent;
697 this.name_div = null;
698 this.value_div = null;
699 this.color = NETDATA.themes.current.foreground;
701 if(parent.selected_count > parent.unselected_count)
702 this.selected = true;
704 this.selected = false;
706 this.setOptions(name_div, value_div, color);
709 dimensionStatus.prototype.invalidate = function() {
710 this.name_div = null;
711 this.value_div = null;
712 this.enabled = false;
715 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
718 if(this.name_div != name_div) {
719 this.name_div = name_div;
720 this.name_div.title = this.label;
721 this.name_div.style.color = this.color;
722 if(this.selected === false)
723 this.name_div.className = 'netdata-legend-name not-selected';
725 this.name_div.className = 'netdata-legend-name selected';
728 if(this.value_div != value_div) {
729 this.value_div = value_div;
730 this.value_div.title = this.label;
731 this.value_div.style.color = this.color;
732 if(this.selected === false)
733 this.value_div.className = 'netdata-legend-value not-selected';
735 this.value_div.className = 'netdata-legend-value selected';
742 dimensionStatus.prototype.setHandler = function() {
743 if(this.enabled === false) return;
747 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
748 this.name_div.onclick = this.value_div.onclick = function(e) {
750 if(ds.isSelected()) {
752 if(e.shiftKey === true || e.ctrlKey === true) {
753 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
756 if(ds.parent.countSelected() === 0)
757 ds.parent.selectAll();
760 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
761 if(ds.parent.countSelected() === 1) {
762 ds.parent.selectAll();
765 ds.parent.selectNone();
771 // this is not selected
772 if(e.shiftKey === true || e.ctrlKey === true) {
773 // control or shift key is pressed -> select this too
777 // no key is pressed -> select only this
778 ds.parent.selectNone();
783 ds.parent.state.redrawChart();
787 dimensionStatus.prototype.select = function() {
788 if(this.enabled === false) return;
790 this.name_div.className = 'netdata-legend-name selected';
791 this.value_div.className = 'netdata-legend-value selected';
792 this.selected = true;
795 dimensionStatus.prototype.unselect = function() {
796 if(this.enabled === false) return;
798 this.name_div.className = 'netdata-legend-name not-selected';
799 this.value_div.className = 'netdata-legend-value hidden';
800 this.selected = false;
803 dimensionStatus.prototype.isSelected = function() {
804 return(this.enabled === true && this.selected === true);
807 // ----------------------------------------------------------------------------------------------------------------
809 dimensionsVisibility = function(state) {
812 this.dimensions = {};
813 this.selected_count = 0;
814 this.unselected_count = 0;
817 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
818 if(typeof this.dimensions[label] === 'undefined') {
820 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
823 this.dimensions[label].setOptions(name_div, value_div, color);
825 return this.dimensions[label];
828 dimensionsVisibility.prototype.dimensionGet = function(label) {
829 return this.dimensions[label];
832 dimensionsVisibility.prototype.invalidateAll = function() {
833 for(var d in this.dimensions)
834 this.dimensions[d].invalidate();
837 dimensionsVisibility.prototype.selectAll = function() {
838 for(var d in this.dimensions)
839 this.dimensions[d].select();
842 dimensionsVisibility.prototype.countSelected = function() {
844 for(var d in this.dimensions)
845 if(this.dimensions[d].isSelected()) i++;
850 dimensionsVisibility.prototype.selectNone = function() {
851 for(var d in this.dimensions)
852 this.dimensions[d].unselect();
855 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
856 var ret = new Array();
857 this.selected_count = 0;
858 this.unselected_count = 0;
860 for(var i = 0, len = array.length; i < len ; i++) {
861 var ds = this.dimensions[array[i]];
862 if(typeof ds === 'undefined') {
863 // console.log(array[i] + ' is not found');
868 if(ds.isSelected()) {
870 this.selected_count++;
874 this.unselected_count++;
878 if(this.selected_count === 0 && this.unselected_count !== 0) {
880 return this.selected2BooleanArray(array);
887 // ----------------------------------------------------------------------------------------------------------------
888 // global selection sync
890 NETDATA.globalSelectionSync = {
897 if(this.state !== null)
898 this.state.globalSelectionSyncStop();
902 if(this.state !== null) {
903 this.state.globalSelectionSyncDelay();
908 // ----------------------------------------------------------------------------------------------------------------
909 // Our state object, where all per-chart values are stored
911 chartState = function(element) {
912 var self = $(element);
913 this.element = element;
916 // all private functions should use 'that', instead of 'this'
920 * show an error instead of the chart
922 var error = function(msg) {
925 if(typeof netdataErrorCallback === 'function') {
926 ret = netdataErrorCallback('chart', that.id, msg);
930 that.element.innerHTML = that.id + ': ' + msg;
931 that.enabled = false;
932 that.current = that.pan;
936 // GUID - a unique identifier for the chart
937 this.uuid = NETDATA.guid();
939 // string - the name of chart
940 this.id = self.data('netdata');
942 // string - the key for localStorage settings
943 this.settings_id = self.data('id') || null;
945 // the user given dimensions of the element
946 this.width = self.data('width') || NETDATA.chartDefaults.width;
947 this.height = self.data('height') || NETDATA.chartDefaults.height;
949 if(this.settings_id !== null) {
950 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
951 // this is the callback that will be called
952 // if and when the user resets all localStorage variables
955 resizeChartToHeight(height);
959 // string - the netdata server URL, without any path
960 this.host = self.data('host') || NETDATA.chartDefaults.host;
962 // make sure the host does not end with /
963 // all netdata API requests use absolute paths
964 while(this.host.slice(-1) === '/')
965 this.host = this.host.substring(0, this.host.length - 1);
967 // string - the grouping method requested by the user
968 this.method = self.data('method') || NETDATA.chartDefaults.method;
970 // the time-range requested by the user
971 this.after = self.data('after') || NETDATA.chartDefaults.after;
972 this.before = self.data('before') || NETDATA.chartDefaults.before;
974 // the pixels per point requested by the user
975 this.pixels_per_point = self.data('pixels-per-point') || 1;
976 this.points = self.data('points') || null;
978 // the dimensions requested by the user
979 this.dimensions = self.data('dimensions') || null;
981 // the chart library requested by the user
982 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
984 // object - the chart library used
989 this.colors_assigned = {};
990 this.colors_available = null;
992 // the element already created by the user
993 this.element_message = null;
995 // the element with the chart
996 this.element_chart = null;
998 // the element with the legend of the chart (if created by us)
999 this.element_legend = null;
1000 this.element_legend_childs = {
1010 this.chart_url = null; // string - the url to download chart info
1011 this.chart = null; // object - the chart as downloaded from the server
1013 this.title = self.data('title') || null; // the title of the chart
1014 this.units = self.data('units') || null; // the units of the chart dimensions
1015 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1017 this.running = false; // boolean - true when the chart is being refreshed now
1018 this.validated = false; // boolean - has the chart been validated?
1019 this.enabled = true; // boolean - is the chart enabled for refresh?
1020 this.paused = false; // boolean - is the chart paused for any reason?
1021 this.selected = false; // boolean - is the chart shown a selection?
1022 this.debug = false; // boolean - console.log() debug info about this chart
1024 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1025 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1026 this.requested_after = null; // milliseconds - the timestamp of the request after param
1027 this.requested_before = null; // milliseconds - the timestamp of the request before param
1028 this.requested_padding = null;
1029 this.view_after = 0;
1030 this.view_before = 0;
1035 force_update_at: 0, // the timestamp to force the update at
1036 force_before_ms: null,
1037 force_after_ms: null
1042 force_update_at: 0, // the timestamp to force the update at
1043 force_before_ms: null,
1044 force_after_ms: null
1049 force_update_at: 0, // the timestamp to force the update at
1050 force_before_ms: null,
1051 force_after_ms: null
1054 // this is a pointer to one of the sub-classes below
1056 this.current = this.auto;
1058 // check the requested library is available
1059 // we don't initialize it here - it will be initialized when
1060 // this chart will be first used
1061 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1062 NETDATA.error(402, that.library_name);
1063 error('chart library "' + that.library_name + '" is not found');
1066 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1067 NETDATA.error(403, that.library_name);
1068 error('chart library "' + that.library_name + '" is not enabled');
1072 that.library = NETDATA.chartLibraries[that.library_name];
1074 // milliseconds - the time the last refresh took
1075 this.refresh_dt_ms = 0;
1077 // if we need to report the rendering speed
1078 // find the element that needs to be updated
1079 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1081 if(refresh_dt_element_name !== null)
1082 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1084 this.refresh_dt_element = null;
1086 this.dimensions_visibility = new dimensionsVisibility(this);
1088 this._updating = false;
1090 // ============================================================================================================
1091 // PRIVATE FUNCTIONS
1093 var createDOM = function() {
1094 if(that.enabled === false) return;
1096 if(that.element_message !== null) that.element_message.innerHTML = '';
1097 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1098 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1100 that.element.innerHTML = '';
1102 that.element_message = document.createElement('div');
1103 that.element_message.className = ' netdata-message hidden';
1104 that.element.appendChild(that.element_message);
1106 that.element_chart = document.createElement('div');
1107 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1108 that.element.appendChild(that.element_chart);
1110 if(that.hasLegend() === true) {
1111 that.element.className = "netdata-container-with-legend";
1112 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1114 that.element_legend = document.createElement('div');
1115 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1116 that.element.appendChild(that.element_legend);
1119 that.element.className = "netdata-container";
1120 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1122 that.element_legend = null;
1124 that.element_legend_childs.series = null;
1126 if(typeof(that.width) === 'string')
1127 $(that.element).css('width', that.width);
1128 else if(typeof(that.width) === 'number')
1129 $(that.element).css('width', that.width + 'px');
1131 if(typeof(that.library.aspect_ratio) === 'undefined') {
1132 if(typeof(that.height) === 'string')
1133 $(that.element).css('height', that.height);
1134 else if(typeof(that.height) === 'number')
1135 $(that.element).css('height', that.height + 'px');
1138 var w = that.element.offsetWidth;
1139 if(w === null || w === 0) {
1140 // the div is hidden
1141 // this will resize the chart when next viewed
1142 that.tm.last_resized = 0;
1145 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1148 if(NETDATA.chartDefaults.min_width !== null)
1149 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1151 that.tm.last_dom_created = new Date().getTime();
1157 * initialize state variables
1158 * destroy all (possibly) created state elements
1159 * create the basic DOM for a chart
1161 var init = function() {
1162 if(that.enabled === false) return;
1164 that.paused = false;
1165 that.selected = false;
1167 that.chart_created = false; // boolean - is the library.create() been called?
1168 that.updates_counter = 0; // numeric - the number of refreshes made so far
1169 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1170 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1173 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1174 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1175 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1177 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1178 last_updated: 0, // the timestamp the chart last updated with data
1179 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1181 // Used with NETDATA.globalPanAndZoom.seq
1182 last_visible_check: 0, // the time we last checked if it is visible
1183 last_resized: 0, // the time the chart was resized
1184 last_hidden: 0, // the time the chart was hidden
1185 last_unhidden: 0, // the time the chart was unhidden
1186 last_autorefreshed: 0 // the time the chart was last refreshed
1189 that.data = null; // the last data as downloaded from the netdata server
1190 that.data_url = 'invalid://'; // string - the last url used to update the chart
1191 that.data_points = 0; // number - the number of points returned from netdata
1192 that.data_after = 0; // milliseconds - the first timestamp of the data
1193 that.data_before = 0; // milliseconds - the last timestamp of the data
1194 that.data_update_every = 0; // milliseconds - the frequency to update the data
1196 that.tm.last_initialized = new Date().getTime();
1199 that.setMode('auto');
1202 var maxMessageFontSize = function() {
1203 // normally we want a font size, as tall as the element
1204 var h = that.element_message.clientHeight;
1206 // but give it some air, 20% let's say, or 5 pixels min
1207 var lost = Math.max(h * 0.2, 5);
1210 // center the text, vertically
1211 var paddingTop = (lost - 5) / 2;
1213 // but check the width too
1214 // it should fit 10 characters in it
1215 var w = that.element_message.clientWidth / 10;
1217 paddingTop += (h - w) / 2;
1221 // and don't make it too huge
1222 // 5% of the screen size is good
1223 if(h > screen.height / 20) {
1224 paddingTop += (h - (screen.height / 20)) / 2;
1225 h = screen.height / 20;
1229 that.element_message.style.fontSize = h.toString() + 'px';
1230 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1233 var showMessage = function(msg) {
1234 that.element_message.className = 'netdata-message';
1235 that.element_message.innerHTML = msg;
1236 that.element_message.style.fontSize = 'x-small';
1237 that.element_message.style.paddingTop = '0px';
1238 that.___messageHidden___ = undefined;
1241 var showMessageIcon = function(icon) {
1242 that.element_message.innerHTML = icon;
1243 that.element_message.className = 'netdata-message icon';
1244 maxMessageFontSize();
1245 that.___messageHidden___ = undefined;
1248 var hideMessage = function() {
1249 if(typeof that.___messageHidden___ === 'undefined') {
1250 that.___messageHidden___ = true;
1251 that.element_message.className = 'netdata-message hidden';
1255 var showRendering = function() {
1257 if(that.chart !== null) {
1258 if(that.chart.chart_type === 'line')
1259 icon = '<i class="fa fa-line-chart"></i>';
1261 icon = '<i class="fa fa-area-chart"></i>';
1264 icon = '<i class="fa fa-area-chart"></i>';
1266 showMessageIcon(icon + ' netdata');
1269 var showLoading = function() {
1270 if(that.chart_created === false) {
1271 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1277 var isHidden = function() {
1278 if(typeof that.___chartIsHidden___ !== 'undefined')
1284 // hide the chart, when it is not visible - called from isVisible()
1285 var hideChart = function() {
1286 // hide it, if it is not already hidden
1287 if(isHidden() === true) return;
1289 if(that.chart_created === true) {
1290 if(NETDATA.options.current.destroy_on_hide === true) {
1291 // we should destroy it
1296 that.element_chart.style.display = 'none';
1297 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1298 that.tm.last_hidden = new Date().getTime();
1301 // This works, but I not sure there are no corner cases somewhere
1302 // so it is commented - if the user has memory issues he can
1303 // set Destroy on Hide for all charts
1304 // that.data = null;
1308 that.___chartIsHidden___ = true;
1311 // unhide the chart, when it is visible - called from isVisible()
1312 var unhideChart = function() {
1313 if(isHidden() === false) return;
1315 that.___chartIsHidden___ = undefined;
1316 that.updates_since_last_unhide = 0;
1318 if(that.chart_created === false) {
1319 // we need to re-initialize it, to show our background
1320 // logo in bootstrap tabs, until the chart loads
1324 that.tm.last_unhidden = new Date().getTime();
1325 that.element_chart.style.display = '';
1326 if(that.element_legend !== null) that.element_legend.style.display = '';
1332 var canBeRendered = function() {
1333 if(isHidden() === true || that.isVisible(true) === false)
1339 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1340 var callChartLibraryUpdateSafely = function(data) {
1343 if(canBeRendered() === false)
1346 if(NETDATA.options.debug.chart_errors === true)
1347 status = that.library.update(that, data);
1350 status = that.library.update(that, data);
1357 if(status === false) {
1358 error('chart failed to be updated as ' + that.library_name);
1365 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1366 var callChartLibraryCreateSafely = function(data) {
1369 if(canBeRendered() === false)
1372 if(NETDATA.options.debug.chart_errors === true)
1373 status = that.library.create(that, data);
1376 status = that.library.create(that, data);
1383 if(status === false) {
1384 error('chart failed to be created as ' + that.library_name);
1388 that.chart_created = true;
1389 that.updates_since_last_creation = 0;
1393 // ----------------------------------------------------------------------------------------------------------------
1396 // resizeChart() - private
1397 // to be called just before the chart library to make sure that
1398 // a properly sized dom is available
1399 var resizeChart = function() {
1400 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1401 if(that.chart_created === false) return;
1403 if(that.needsRecreation()) {
1406 else if(typeof that.library.resize === 'function') {
1407 that.library.resize(that);
1409 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1410 $(that.element_legend_childs.nano).nanoScroller();
1412 maxMessageFontSize();
1415 that.tm.last_resized = new Date().getTime();
1419 // this is the actual chart resize algorithm
1421 // - resize the entire container
1422 // - update the internal states
1423 // - resize the chart as the div changes height
1424 // - update the scrollbar of the legend
1425 var resizeChartToHeight = function(h) {
1427 that.element.style.height = h;
1429 if(that.settings_id !== null)
1430 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1432 var now = new Date().getTime();
1433 NETDATA.options.last_page_scroll = now;
1434 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1437 that.tm.last_resized = 0;
1441 this.resizeHandler = function(e) {
1444 if(typeof this.event_resize === 'undefined'
1445 || this.event_resize.chart_original_w === 'undefined'
1446 || this.event_resize.chart_original_h === 'undefined')
1447 this.event_resize = {
1448 chart_original_w: this.element.clientWidth,
1449 chart_original_h: this.element.clientHeight,
1453 if(e.type === 'touchstart') {
1454 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1455 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1458 this.event_resize.mouse_start_x = e.clientX;
1459 this.event_resize.mouse_start_y = e.clientY;
1462 this.event_resize.chart_start_w = this.element.clientWidth;
1463 this.event_resize.chart_start_h = this.element.clientHeight;
1464 this.event_resize.chart_last_w = this.element.clientWidth;
1465 this.event_resize.chart_last_h = this.element.clientHeight;
1467 var now = new Date().getTime();
1468 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1469 // double click / double tap event
1471 // the optimal height of the chart
1472 // showing the entire legend
1473 var optimal = this.event_resize.chart_last_h
1474 + this.element_legend_childs.content.scrollHeight
1475 - this.element_legend_childs.content.clientHeight;
1477 // if we are not optimal, be optimal
1478 if(this.event_resize.chart_last_h != optimal)
1479 resizeChartToHeight(optimal.toString() + 'px');
1481 // else if we do not have the original height
1482 // reset to the original height
1483 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1484 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1487 this.event_resize.last = now;
1489 // process movement event
1490 document.onmousemove =
1491 document.ontouchmove =
1492 this.element_legend_childs.resize_handler.onmousemove =
1493 this.element_legend_childs.resize_handler.ontouchmove =
1498 case 'mousemove': y = e.clientY; break;
1499 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1503 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1505 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1506 resizeChartToHeight(newH.toString() + 'px');
1507 that.event_resize.chart_last_h = newH;
1512 // process end event
1513 document.onmouseup =
1514 document.ontouchend =
1515 this.element_legend_childs.resize_handler.onmouseup =
1516 this.element_legend_childs.resize_handler.ontouchend =
1518 // remove all the hooks
1519 document.onmouseup =
1520 document.onmousemove =
1521 document.ontouchmove =
1522 document.ontouchend =
1523 that.element_legend_childs.resize_handler.onmousemove =
1524 that.element_legend_childs.resize_handler.ontouchmove =
1525 that.element_legend_childs.resize_handler.onmouseout =
1526 that.element_legend_childs.resize_handler.onmouseup =
1527 that.element_legend_childs.resize_handler.ontouchend =
1530 // allow auto-refreshes
1531 NETDATA.options.auto_refresher_stop_until = 0;
1537 var noDataToShow = function() {
1538 showMessageIcon('<i class="fa fa-warning"></i> empty');
1539 that.legendUpdateDOM();
1540 that.tm.last_autorefreshed = new Date().getTime();
1541 // that.data_update_every = 30 * 1000;
1542 //that.element_chart.style.display = 'none';
1543 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1544 //that.___chartIsHidden___ = true;
1547 // ============================================================================================================
1550 this.error = function(msg) {
1554 this.setMode = function(m) {
1555 if(this.current !== null && this.current.name === m) return;
1558 this.current = this.auto;
1559 else if(m === 'pan')
1560 this.current = this.pan;
1561 else if(m === 'zoom')
1562 this.current = this.zoom;
1564 this.current = this.auto;
1566 this.current.force_update_at = 0;
1567 this.current.force_before_ms = null;
1568 this.current.force_after_ms = null;
1570 this.tm.last_mode_switch = new Date().getTime();
1573 // ----------------------------------------------------------------------------------------------------------------
1574 // global selection sync
1576 // prevent to global selection sync for some time
1577 this.globalSelectionSyncDelay = function(ms) {
1578 if(NETDATA.options.current.sync_selection === false)
1581 if(typeof ms === 'number')
1582 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1584 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1587 // can we globally apply selection sync?
1588 this.globalSelectionSyncAbility = function() {
1589 if(NETDATA.options.current.sync_selection === false)
1592 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1598 this.globalSelectionSyncIsMaster = function() {
1599 if(NETDATA.globalSelectionSync.state === this)
1605 // this chart is the master of the global selection sync
1606 this.globalSelectionSyncBeMaster = function() {
1608 if(this.globalSelectionSyncIsMaster()) {
1609 if(this.debug === true)
1610 this.log('sync: I am the master already.');
1615 if(NETDATA.globalSelectionSync.state) {
1616 if(this.debug === true)
1617 this.log('sync: I am not the sync master. Resetting global sync.');
1619 this.globalSelectionSyncStop();
1622 // become the master
1623 if(this.debug === true)
1624 this.log('sync: becoming sync master.');
1626 this.selected = true;
1627 NETDATA.globalSelectionSync.state = this;
1629 // find the all slaves
1630 var targets = NETDATA.options.targets;
1631 var len = targets.length;
1636 if(this.debug === true)
1637 st.log('sync: not adding me to sync');
1639 else if(st.globalSelectionSyncIsEligible()) {
1640 if(this.debug === true)
1641 st.log('sync: adding to sync as slave');
1643 st.globalSelectionSyncBeSlave();
1647 // this.globalSelectionSyncDelay(100);
1650 // can the chart participate to the global selection sync as a slave?
1651 this.globalSelectionSyncIsEligible = function() {
1652 if(this.enabled === true
1653 && this.library !== null
1654 && typeof this.library.setSelection === 'function'
1655 && this.isVisible() === true
1656 && this.chart_created === true)
1662 // this chart becomes a slave of the global selection sync
1663 this.globalSelectionSyncBeSlave = function() {
1664 if(NETDATA.globalSelectionSync.state !== this)
1665 NETDATA.globalSelectionSync.slaves.push(this);
1668 // sync all the visible charts to the given time
1669 // this is to be called from the chart libraries
1670 this.globalSelectionSync = function(t) {
1671 if(this.globalSelectionSyncAbility() === false) {
1672 if(this.debug === true)
1673 this.log('sync: cannot sync (yet?).');
1678 if(this.globalSelectionSyncIsMaster() === false) {
1679 if(this.debug === true)
1680 this.log('sync: trying to be sync master.');
1682 this.globalSelectionSyncBeMaster();
1684 if(this.globalSelectionSyncAbility() === false) {
1685 if(this.debug === true)
1686 this.log('sync: cannot sync (yet?).');
1692 NETDATA.globalSelectionSync.last_t = t;
1693 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1698 // stop syncing all charts to the given time
1699 this.globalSelectionSyncStop = function() {
1700 if(NETDATA.globalSelectionSync.slaves.length) {
1701 if(this.debug === true)
1702 this.log('sync: cleaning up...');
1704 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1706 if(that.debug === true)
1707 st.log('sync: not adding me to sync stop');
1710 if(that.debug === true)
1711 st.log('sync: removed slave from sync');
1713 st.clearSelection();
1717 NETDATA.globalSelectionSync.last_t = 0;
1718 NETDATA.globalSelectionSync.slaves = [];
1719 NETDATA.globalSelectionSync.state = null;
1722 this.clearSelection();
1725 this.setSelection = function(t) {
1726 if(typeof this.library.setSelection === 'function') {
1727 if(this.library.setSelection(this, t) === true)
1728 this.selected = true;
1730 this.selected = false;
1732 else this.selected = true;
1734 if(this.selected === true && this.debug === true)
1735 this.log('selection set to ' + t.toString());
1737 return this.selected;
1740 this.clearSelection = function() {
1741 if(this.selected === true) {
1742 if(typeof this.library.clearSelection === 'function') {
1743 if(this.library.clearSelection(this) === true)
1744 this.selected = false;
1746 this.selected = true;
1748 else this.selected = false;
1750 if(this.selected === false && this.debug === true)
1751 this.log('selection cleared');
1756 return this.selected;
1759 // find if a timestamp (ms) is shown in the current chart
1760 this.timeIsVisible = function(t) {
1761 if(t >= this.data_after && t <= this.data_before)
1766 this.calculateRowForTime = function(t) {
1767 if(this.timeIsVisible(t) === false) return -1;
1768 return Math.floor((t - this.data_after) / this.data_update_every);
1771 // ----------------------------------------------------------------------------------------------------------------
1774 this.log = function(msg) {
1775 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1778 this.pauseChart = function() {
1779 if(this.paused === false) {
1780 if(this.debug === true)
1781 this.log('pauseChart()');
1787 this.unpauseChart = function() {
1788 if(this.paused === true) {
1789 if(this.debug === true)
1790 this.log('unpauseChart()');
1792 this.paused = false;
1796 this.resetChart = function(dont_clear_master, dont_update) {
1797 if(this.debug === true)
1798 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1800 if(typeof dont_clear_master === 'undefined')
1801 dont_clear_master = false;
1803 if(typeof dont_update === 'undefined')
1804 dont_update = false;
1806 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1807 if(this.debug === true)
1808 this.log('resetChart() diverting to clearMaster().');
1809 // this will call us back with master === true
1810 NETDATA.globalPanAndZoom.clearMaster();
1814 this.clearSelection();
1816 this.tm.pan_and_zoom_seq = 0;
1818 this.setMode('auto');
1819 this.current.force_update_at = 0;
1820 this.current.force_before_ms = null;
1821 this.current.force_after_ms = null;
1822 this.tm.last_autorefreshed = 0;
1823 this.paused = false;
1824 this.selected = false;
1825 this.enabled = true;
1826 // this.debug = false;
1828 // do not update the chart here
1829 // or the chart will flip-flop when it is the master
1830 // of a selection sync and another chart becomes
1833 if(dont_update !== true && this.isVisible() === true) {
1838 this.updateChartPanOrZoom = function(after, before) {
1839 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1842 if(this.debug === true)
1845 if(before < after) {
1846 if(this.debug === true)
1847 this.log(logme + 'flipped parameters, rejecting it.');
1852 if(typeof this.fixed_min_duration === 'undefined')
1853 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1855 var min_duration = this.fixed_min_duration;
1856 var current_duration = Math.round(this.view_before - this.view_after);
1858 // round the numbers
1859 after = Math.round(after);
1860 before = Math.round(before);
1862 // align them to update_every
1863 // stretching them further away
1864 after -= after % this.data_update_every;
1865 before += this.data_update_every - (before % this.data_update_every);
1867 // the final wanted duration
1868 var wanted_duration = before - after;
1870 // to allow panning, accept just a point below our minimum
1871 if((current_duration - this.data_update_every) < min_duration)
1872 min_duration = current_duration - this.data_update_every;
1874 // we do it, but we adjust to minimum size and return false
1875 // when the wanted size is below the current and the minimum
1877 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1878 if(this.debug === true)
1879 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1881 min_duration = this.fixed_min_duration;
1883 var dt = (min_duration - wanted_duration) / 2;
1886 wanted_duration = before - after;
1890 var tolerance = this.data_update_every * 2;
1891 var movement = Math.abs(before - this.view_before);
1893 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1894 if(this.debug === true)
1895 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);
1899 if(this.current.name === 'auto') {
1900 this.log(logme + 'caller called me with mode: ' + this.current.name);
1901 this.setMode('pan');
1904 if(this.debug === true)
1905 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);
1907 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1908 this.current.force_after_ms = after;
1909 this.current.force_before_ms = before;
1910 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1914 this.legendFormatValue = function(value) {
1915 if(value === null || value === 'undefined') return '-';
1916 if(typeof value !== 'number') return value;
1918 var abs = Math.abs(value);
1919 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1920 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1921 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1922 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1923 return (Math.round(value * 10000) / 10000).toLocaleString();
1926 this.legendSetLabelValue = function(label, value) {
1927 var series = this.element_legend_childs.series[label];
1928 if(typeof series === 'undefined') return;
1929 if(series.value === null && series.user === null) return;
1931 // if the value has not changed, skip DOM update
1932 //if(series.last === value) return;
1935 if(typeof value === 'number') {
1936 var v = Math.abs(value);
1937 s = r = this.legendFormatValue(value);
1939 if(typeof series.last === 'number') {
1940 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1941 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1942 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1944 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1949 series.last = value;
1952 if(series.value !== null) series.value.innerHTML = s;
1953 if(series.user !== null) series.user.innerHTML = r;
1956 this.legendSetDate = function(ms) {
1957 if(typeof ms !== 'number') {
1958 this.legendShowUndefined();
1962 var d = new Date(ms);
1964 if(this.element_legend_childs.title_date)
1965 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1967 if(this.element_legend_childs.title_time)
1968 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1970 if(this.element_legend_childs.title_units)
1971 this.element_legend_childs.title_units.innerHTML = this.units;
1974 this.legendShowUndefined = function() {
1975 if(this.element_legend_childs.title_date)
1976 this.element_legend_childs.title_date.innerHTML = ' ';
1978 if(this.element_legend_childs.title_time)
1979 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1981 if(this.element_legend_childs.title_units)
1982 this.element_legend_childs.title_units.innerHTML = ' ';
1984 if(this.data && this.element_legend_childs.series !== null) {
1985 var labels = this.data.dimension_names;
1986 var i = labels.length;
1988 var label = labels[i];
1990 if(typeof label === 'undefined') continue;
1991 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1992 this.legendSetLabelValue(label, null);
1997 this.legendShowLatestValues = function() {
1998 if(this.chart === null) return;
1999 if(this.selected) return;
2001 if(this.data === null || this.element_legend_childs.series === null) {
2002 this.legendShowUndefined();
2006 var show_undefined = true;
2007 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2008 show_undefined = false;
2010 if(show_undefined) {
2011 this.legendShowUndefined();
2015 this.legendSetDate(this.view_before);
2017 var labels = this.data.dimension_names;
2018 var i = labels.length;
2020 var label = labels[i];
2022 if(typeof label === 'undefined') continue;
2023 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2026 this.legendSetLabelValue(label, null);
2028 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2032 this.legendReset = function() {
2033 this.legendShowLatestValues();
2036 // this should be called just ONCE per dimension per chart
2037 this._chartDimensionColor = function(label) {
2038 if(this.colors === null) this.chartColors();
2040 if(typeof this.colors_assigned[label] === 'undefined') {
2041 if(this.colors_available.length === 0) {
2042 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2043 this.colors_available.push(NETDATA.themes.current.colors[i]);
2046 this.colors_assigned[label] = this.colors_available.shift();
2048 if(this.debug === true)
2049 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2052 if(this.debug === true)
2053 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2056 this.colors.push(this.colors_assigned[label]);
2057 return this.colors_assigned[label];
2060 this.chartColors = function() {
2061 if(this.colors !== null) return this.colors;
2063 this.colors = new Array();
2064 this.colors_available = new Array();
2067 var c = $(this.element).data('colors');
2068 // this.log('read colors: ' + c);
2069 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2070 if(typeof c !== 'string') {
2071 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2078 for(i = 0, len = c.length; i < len ; i++) {
2080 this.colors_available.push(c[i]);
2081 // this.log('adding color: ' + c[i]);
2087 // push all the standard colors too
2088 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2089 this.colors_available.push(NETDATA.themes.current.colors[i]);
2094 this.legendUpdateDOM = function() {
2097 // check that the legend DOM is up to date for the downloaded dimensions
2098 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2099 // this.log('the legend does not have any series - requesting legend update');
2102 else if(this.data === null) {
2103 // this.log('the chart does not have any data - requesting legend update');
2106 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2110 var labels = this.data.dimension_names.toString();
2111 if(labels !== this.element_legend_childs.series.labels_key) {
2114 if(this.debug === true)
2115 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2119 if(needed === false) {
2120 // make sure colors available
2123 // do we have to update the current values?
2124 // we do this, only when the visible chart is current
2125 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2126 if(this.debug === true)
2127 this.log('chart is in latest position... updating values on legend...');
2129 //var labels = this.data.dimension_names;
2130 //var i = labels.length;
2132 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2136 if(this.colors === null) {
2137 // this is the first time we update the chart
2138 // let's assign colors to all dimensions
2139 if(this.library.track_colors() === true)
2140 for(var dim in this.chart.dimensions)
2141 this._chartDimensionColor(this.chart.dimensions[dim].name);
2143 // we will re-generate the colors for the chart
2144 // based on the selected dimensions
2147 if(this.debug === true)
2148 this.log('updating Legend DOM');
2150 // mark all dimensions as invalid
2151 this.dimensions_visibility.invalidateAll();
2153 var genLabel = function(state, parent, dim, name, count) {
2154 var color = state._chartDimensionColor(name);
2156 var user_element = null;
2157 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2158 if(user_id !== null) {
2159 user_element = document.getElementById(user_id) || null;
2160 if(user_element === null)
2161 state.log('Cannot find element with id: ' + user_id);
2164 state.element_legend_childs.series[name] = {
2165 name: document.createElement('span'),
2166 value: document.createElement('span'),
2171 var label = state.element_legend_childs.series[name];
2173 // create the dimension visibility tracking for this label
2174 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2176 var rgb = NETDATA.colorHex2Rgb(color);
2177 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2178 + state.chart.chart_type
2179 + '" style="background-color: '
2180 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2181 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2183 var text = document.createTextNode(' ' + name);
2184 label.name.appendChild(text);
2187 parent.appendChild(document.createElement('br'));
2189 parent.appendChild(label.name);
2190 parent.appendChild(label.value);
2193 var content = document.createElement('div');
2195 if(this.hasLegend()) {
2196 this.element_legend_childs = {
2198 resize_handler: document.createElement('div'),
2199 toolbox: document.createElement('div'),
2200 toolbox_left: document.createElement('div'),
2201 toolbox_right: document.createElement('div'),
2202 toolbox_reset: document.createElement('div'),
2203 toolbox_zoomin: document.createElement('div'),
2204 toolbox_zoomout: document.createElement('div'),
2205 toolbox_volume: document.createElement('div'),
2206 title_date: document.createElement('span'),
2207 title_time: document.createElement('span'),
2208 title_units: document.createElement('span'),
2209 nano: document.createElement('div'),
2211 paneClass: 'netdata-legend-series-pane',
2212 sliderClass: 'netdata-legend-series-slider',
2213 contentClass: 'netdata-legend-series-content',
2214 enabledClass: '__enabled',
2215 flashedClass: '__flashed',
2216 activeClass: '__active',
2218 alwaysVisible: true,
2224 this.element_legend.innerHTML = '';
2226 if(this.library.toolboxPanAndZoom !== null) {
2228 function get_pan_and_zoom_step(event) {
2230 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2232 else if (event.shiftKey)
2233 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2235 else if (event.altKey)
2236 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2239 return NETDATA.options.current.pan_and_zoom_factor;
2242 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2243 this.element.appendChild(this.element_legend_childs.toolbox);
2245 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2246 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2247 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2248 this.element_legend_childs.toolbox_left.onclick = function(e) {
2251 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2252 var before = that.view_before - step;
2253 var after = that.view_after - step;
2254 if(after >= that.netdata_first)
2255 that.library.toolboxPanAndZoom(that, after, before);
2257 if(NETDATA.options.current.show_help === true)
2258 $(this.element_legend_childs.toolbox_left).popover({
2263 placement: 'bottom',
2264 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2266 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>'
2270 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2271 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2272 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2273 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2275 NETDATA.resetAllCharts(that);
2277 if(NETDATA.options.current.show_help === true)
2278 $(this.element_legend_childs.toolbox_reset).popover({
2283 placement: 'bottom',
2284 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2285 title: 'Chart Reset',
2286 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>'
2289 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2290 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2291 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2292 this.element_legend_childs.toolbox_right.onclick = function(e) {
2294 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2295 var before = that.view_before + step;
2296 var after = that.view_after + step;
2297 if(before <= that.netdata_last)
2298 that.library.toolboxPanAndZoom(that, after, before);
2300 if(NETDATA.options.current.show_help === true)
2301 $(this.element_legend_childs.toolbox_right).popover({
2306 placement: 'bottom',
2307 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2309 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>'
2313 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2314 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2315 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2316 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2318 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2319 var before = that.view_before - dt;
2320 var after = that.view_after + dt;
2321 that.library.toolboxPanAndZoom(that, after, before);
2323 if(NETDATA.options.current.show_help === true)
2324 $(this.element_legend_childs.toolbox_zoomin).popover({
2329 placement: 'bottom',
2330 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2331 title: 'Chart Zoom In',
2332 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>'
2335 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2336 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2337 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2338 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2340 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);
2341 var before = that.view_before + dt;
2342 var after = that.view_after - dt;
2344 that.library.toolboxPanAndZoom(that, after, before);
2346 if(NETDATA.options.current.show_help === true)
2347 $(this.element_legend_childs.toolbox_zoomout).popover({
2352 placement: 'bottom',
2353 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2354 title: 'Chart Zoom Out',
2355 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>'
2358 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2359 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2360 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2361 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2362 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2363 //e.preventDefault();
2364 //alert('clicked toolbox_volume on ' + that.id);
2368 this.element_legend_childs.toolbox = null;
2369 this.element_legend_childs.toolbox_left = null;
2370 this.element_legend_childs.toolbox_reset = null;
2371 this.element_legend_childs.toolbox_right = null;
2372 this.element_legend_childs.toolbox_zoomin = null;
2373 this.element_legend_childs.toolbox_zoomout = null;
2374 this.element_legend_childs.toolbox_volume = null;
2377 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2378 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2379 this.element.appendChild(this.element_legend_childs.resize_handler);
2380 if(NETDATA.options.current.show_help === true)
2381 $(this.element_legend_childs.resize_handler).popover({
2386 placement: 'bottom',
2387 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2388 title: 'Chart Resize',
2389 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>'
2393 this.element_legend_childs.resize_handler.onmousedown =
2395 that.resizeHandler(e);
2399 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2400 that.resizeHandler(e);
2403 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2404 this.element_legend.appendChild(this.element_legend_childs.title_date);
2406 this.element_legend.appendChild(document.createElement('br'));
2408 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2409 this.element_legend.appendChild(this.element_legend_childs.title_time);
2411 this.element_legend.appendChild(document.createElement('br'));
2413 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2414 this.element_legend.appendChild(this.element_legend_childs.title_units);
2416 this.element_legend.appendChild(document.createElement('br'));
2418 this.element_legend_childs.nano.className = 'netdata-legend-series';
2419 this.element_legend.appendChild(this.element_legend_childs.nano);
2421 content.className = 'netdata-legend-series-content';
2422 this.element_legend_childs.nano.appendChild(content);
2424 if(NETDATA.options.current.show_help === true)
2425 $(content).popover({
2430 placement: 'bottom',
2431 title: 'Chart Legend',
2432 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2433 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>'
2437 this.element_legend_childs = {
2439 resize_handler: null,
2442 toolbox_right: null,
2443 toolbox_reset: null,
2444 toolbox_zoomin: null,
2445 toolbox_zoomout: null,
2446 toolbox_volume: null,
2457 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2458 if(this.debug === true)
2459 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2461 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2462 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2466 var tmp = new Array();
2467 for(var dim in this.chart.dimensions) {
2468 tmp.push(this.chart.dimensions[dim].name);
2469 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2471 this.element_legend_childs.series.labels_key = tmp.toString();
2472 if(this.debug === true)
2473 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2476 // create a hidden div to be used for hidding
2477 // the original legend of the chart library
2478 var el = document.createElement('div');
2479 if(this.element_legend !== null)
2480 this.element_legend.appendChild(el);
2481 el.style.display = 'none';
2483 this.element_legend_childs.hidden = document.createElement('div');
2484 el.appendChild(this.element_legend_childs.hidden);
2486 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2487 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2489 this.legendShowLatestValues();
2492 this.hasLegend = function() {
2493 if(typeof this.___hasLegendCache___ !== 'undefined')
2494 return this.___hasLegendCache___;
2497 if(this.library && this.library.legend(this) === 'right-side') {
2498 var legend = $(this.element).data('legend') || 'yes';
2499 if(legend === 'yes') leg = true;
2502 this.___hasLegendCache___ = leg;
2506 this.legendWidth = function() {
2507 return (this.hasLegend())?140:0;
2510 this.legendHeight = function() {
2511 return $(this.element).height();
2514 this.chartWidth = function() {
2515 return $(this.element).width() - this.legendWidth();
2518 this.chartHeight = function() {
2519 return $(this.element).height();
2522 this.chartPixelsPerPoint = function() {
2523 // force an options provided detail
2524 var px = this.pixels_per_point;
2526 if(this.library && px < this.library.pixels_per_point(this))
2527 px = this.library.pixels_per_point(this);
2529 if(px < NETDATA.options.current.pixels_per_point)
2530 px = NETDATA.options.current.pixels_per_point;
2535 this.needsRecreation = function() {
2537 this.chart_created === true
2539 && this.library.autoresize() === false
2540 && this.tm.last_resized < NETDATA.options.last_resized
2544 this.chartURL = function() {
2545 var after, before, points_multiplier = 1;
2546 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2547 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2549 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2550 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2551 this.view_after = after * 1000;
2552 this.view_before = before * 1000;
2554 this.requested_padding = null;
2555 points_multiplier = 1;
2557 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2558 this.tm.pan_and_zoom_seq = 0;
2560 before = Math.round(this.current.force_before_ms / 1000);
2561 after = Math.round(this.current.force_after_ms / 1000);
2562 this.view_after = after * 1000;
2563 this.view_before = before * 1000;
2565 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2566 this.requested_padding = Math.round((before - after) / 2);
2567 after -= this.requested_padding;
2568 before += this.requested_padding;
2569 this.requested_padding *= 1000;
2570 points_multiplier = 2;
2573 this.current.force_before_ms = null;
2574 this.current.force_after_ms = null;
2577 this.tm.pan_and_zoom_seq = 0;
2579 before = this.before;
2581 this.view_after = after * 1000;
2582 this.view_before = before * 1000;
2584 this.requested_padding = null;
2585 points_multiplier = 1;
2588 this.requested_after = after * 1000;
2589 this.requested_before = before * 1000;
2591 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2593 // build the data URL
2594 this.data_url = this.host + this.chart.data_url;
2595 this.data_url += "&format=" + this.library.format();
2596 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2597 this.data_url += "&group=" + this.method;
2598 this.data_url += "&options=" + this.library.options(this);
2599 this.data_url += '|jsonwrap';
2601 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2602 this.data_url += '|nonzero';
2604 if(this.append_options !== null)
2605 this.data_url += '|' + this.append_options.toString();
2608 this.data_url += "&after=" + after.toString();
2611 this.data_url += "&before=" + before.toString();
2614 this.data_url += "&dimensions=" + this.dimensions;
2616 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2617 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2620 this.redrawChart = function() {
2621 if(this.data !== null)
2622 this.updateChartWithData(this.data);
2625 this.updateChartWithData = function(data) {
2626 if(this.debug === true)
2627 this.log('updateChartWithData() called.');
2629 // this may force the chart to be re-created
2633 this.updates_counter++;
2634 this.updates_since_last_unhide++;
2635 this.updates_since_last_creation++;
2637 var started = new Date().getTime();
2639 // if the result is JSON, find the latest update-every
2640 this.data_update_every = data.view_update_every * 1000;
2641 this.data_after = data.after * 1000;
2642 this.data_before = data.before * 1000;
2643 this.netdata_first = data.first_entry * 1000;
2644 this.netdata_last = data.last_entry * 1000;
2645 this.data_points = data.points;
2648 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2649 if(this.view_after < this.data_after) {
2650 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2651 this.view_after = this.data_after;
2654 if(this.view_before > this.data_before) {
2655 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2656 this.view_before = this.data_before;
2660 this.view_after = this.data_after;
2661 this.view_before = this.data_before;
2664 if(this.debug === true) {
2665 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2667 if(this.current.force_after_ms)
2668 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2670 this.log('STATUS: forced : unset');
2672 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2673 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2674 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2675 this.log('STATUS: points : ' + (this.data_points).toString());
2678 if(this.data_points === 0) {
2683 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2684 if(this.debug === true)
2685 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2687 this.chart_created = false;
2690 // check and update the legend
2691 this.legendUpdateDOM();
2693 if(this.chart_created === true
2694 && typeof this.library.update === 'function') {
2696 if(this.debug === true)
2697 this.log('updating chart...');
2699 if(callChartLibraryUpdateSafely(data) === false)
2703 if(this.debug === true)
2704 this.log('creating chart...');
2706 if(callChartLibraryCreateSafely(data) === false)
2710 this.legendShowLatestValues();
2711 if(this.selected === true)
2712 NETDATA.globalSelectionSync.stop();
2714 // update the performance counters
2715 var now = new Date().getTime();
2716 this.tm.last_updated = now;
2718 // don't update last_autorefreshed if this chart is
2719 // forced to be updated with global PanAndZoom
2720 if(NETDATA.globalPanAndZoom.isActive())
2721 this.tm.last_autorefreshed = 0;
2723 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2724 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2726 this.tm.last_autorefreshed = now;
2729 this.refresh_dt_ms = now - started;
2730 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2732 if(this.refresh_dt_element !== null)
2733 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2736 this.updateChart = function(callback) {
2737 if(this.debug === true)
2738 this.log('updateChart() called.');
2740 if(this._updating === true) {
2741 if(this.debug === true)
2742 this.log('I am already updating...');
2744 if(typeof callback === 'function') callback();
2748 // due to late initialization of charts and libraries
2749 // we need to check this too
2750 if(this.enabled === false) {
2751 if(this.debug === true)
2752 this.log('I am not enabled');
2754 if(typeof callback === 'function') callback();
2758 if(canBeRendered() === false) {
2759 if(typeof callback === 'function') callback();
2763 if(this.chart === null) {
2764 this.getChart(function() { that.updateChart(callback); });
2768 if(this.library.initialized === false) {
2769 if(this.library.enabled === true) {
2770 this.library.initialize(function() { that.updateChart(callback); });
2774 error('chart library "' + this.library_name + '" is not available.');
2775 if(typeof callback === 'function') callback();
2780 this.clearSelection();
2783 if(this.debug === true)
2784 this.log('updating from ' + this.data_url);
2786 NETDATA.statistics.refreshes_total++;
2787 NETDATA.statistics.refreshes_active++;
2789 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2790 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2792 this._updating = true;
2794 this.xhr = $.ajax( {
2798 xhrFields: { withCredentials: true } // required for the cookie
2800 .success(function(data) {
2801 if(that.debug === true)
2802 that.log('data received. updating chart.');
2804 that.updateChartWithData(data);
2807 error('data download failed for url: ' + that.data_url);
2809 .always(function() {
2810 NETDATA.statistics.refreshes_active--;
2811 that._updating = false;
2812 if(typeof callback === 'function') callback();
2818 this.isVisible = function(nocache) {
2819 if(typeof nocache === 'undefined')
2822 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2824 // caching - we do not evaluate the charts visibility
2825 // if the page has not been scrolled since the last check
2826 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2827 return this.___isVisible___;
2829 this.tm.last_visible_check = new Date().getTime();
2831 var wh = window.innerHeight;
2832 var x = this.element.getBoundingClientRect();
2836 if(x.width === 0 || x.height === 0) {
2838 this.___isVisible___ = false;
2839 return this.___isVisible___;
2842 if(x.top < 0 && -x.top > x.height) {
2843 // the chart is entirely above
2844 ret = -x.top - x.height;
2846 else if(x.top > wh) {
2847 // the chart is entirely below
2851 if(ret > tolerance) {
2852 // the chart is too far
2855 this.___isVisible___ = false;
2856 return this.___isVisible___;
2859 // the chart is inside or very close
2862 this.___isVisible___ = true;
2863 return this.___isVisible___;
2867 this.isAutoRefreshable = function() {
2868 return (this.current.autorefresh);
2871 this.canBeAutoRefreshed = function() {
2872 var now = new Date().getTime();
2874 if(this.running === true) {
2875 if(this.debug === true)
2876 this.log('I am already running');
2881 if(this.enabled === false) {
2882 if(this.debug === true)
2883 this.log('I am not enabled');
2888 if(this.library === null || this.library.enabled === false) {
2889 error('charting library "' + this.library_name + '" is not available');
2890 if(this.debug === true)
2891 this.log('My chart library ' + this.library_name + ' is not available');
2896 if(this.isVisible() === false) {
2897 if(NETDATA.options.debug.visibility === true || this.debug === true)
2898 this.log('I am not visible');
2903 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2904 if(this.debug === true)
2905 this.log('timed force update detected - allowing this update');
2907 this.current.force_update_at = 0;
2911 if(this.isAutoRefreshable() === true) {
2912 // allow the first update, even if the page is not visible
2913 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2914 if(NETDATA.options.debug.focus === true || this.debug === true)
2915 this.log('canBeAutoRefreshed(): page does not have focus');
2920 if(this.needsRecreation() === true) {
2921 if(this.debug === true)
2922 this.log('canBeAutoRefreshed(): needs re-creation.');
2927 // options valid only for autoRefresh()
2928 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2929 if(NETDATA.globalPanAndZoom.isActive()) {
2930 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2931 if(this.debug === true)
2932 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2937 if(this.debug === true)
2938 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2944 if(this.selected === true) {
2945 if(this.debug === true)
2946 this.log('canBeAutoRefreshed(): I have a selection in place.');
2951 if(this.paused === true) {
2952 if(this.debug === true)
2953 this.log('canBeAutoRefreshed(): I am paused.');
2958 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2959 if(this.debug === true)
2960 this.log('canBeAutoRefreshed(): It is time to update me.');
2970 this.autoRefresh = function(callback) {
2971 if(this.canBeAutoRefreshed() === true && this.running === false) {
2974 state.running = true;
2975 state.updateChart(function() {
2976 state.running = false;
2978 if(typeof callback !== 'undefined')
2983 if(typeof callback !== 'undefined')
2988 this._defaultsFromDownloadedChart = function(chart) {
2990 this.chart_url = chart.url;
2991 this.data_update_every = chart.update_every * 1000;
2992 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2993 this.tm.last_info_downloaded = new Date().getTime();
2995 if(this.title === null)
2996 this.title = chart.title;
2998 if(this.units === null)
2999 this.units = chart.units;
3002 // fetch the chart description from the netdata server
3003 this.getChart = function(callback) {
3004 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3006 this._defaultsFromDownloadedChart(this.chart);
3007 if(typeof callback === 'function') callback();
3010 this.chart_url = "/api/v1/chart?chart=" + this.id;
3012 if(this.debug === true)
3013 this.log('downloading ' + this.chart_url);
3016 url: this.host + this.chart_url,
3019 xhrFields: { withCredentials: true } // required for the cookie
3021 .done(function(chart) {
3022 chart.url = that.chart_url;
3023 that._defaultsFromDownloadedChart(chart);
3024 NETDATA.chartRegistry.add(that.host, that.id, chart);
3027 NETDATA.error(404, that.chart_url);
3028 error('chart not found on url "' + that.chart_url + '"');
3030 .always(function() {
3031 if(typeof callback === 'function') callback();
3036 // ============================================================================================================
3042 NETDATA.resetAllCharts = function(state) {
3043 // first clear the global selection sync
3044 // to make sure no chart is in selected state
3045 state.globalSelectionSyncStop();
3047 // there are 2 possibilities here
3048 // a. state is the global Pan and Zoom master
3049 // b. state is not the global Pan and Zoom master
3051 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3054 // clear the global Pan and Zoom
3055 // this will also refresh the master
3056 // and unblock any charts currently mirroring the master
3057 NETDATA.globalPanAndZoom.clearMaster();
3059 // if we were not the master, reset our status too
3060 // this is required because most probably the mouse
3061 // is over this chart, blocking it from auto-refreshing
3062 if(master === false && (state.paused === true || state.selected === true))
3066 // get or create a chart state, given a DOM element
3067 NETDATA.chartState = function(element) {
3068 var state = $(element).data('netdata-state-object') || null;
3069 if(state === null) {
3070 state = new chartState(element);
3071 $(element).data('netdata-state-object', state);
3076 // ----------------------------------------------------------------------------------------------------------------
3077 // Library functions
3079 // Load a script without jquery
3080 // This is used to load jquery - after it is loaded, we use jquery
3081 NETDATA._loadjQuery = function(callback) {
3082 if(typeof jQuery === 'undefined') {
3083 if(NETDATA.options.debug.main_loop === true)
3084 console.log('loading ' + NETDATA.jQuery);
3086 var script = document.createElement('script');
3087 script.type = 'text/javascript';
3088 script.async = true;
3089 script.src = NETDATA.jQuery;
3091 // script.onabort = onError;
3092 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3093 if(typeof callback === "function")
3094 script.onload = callback;
3096 var s = document.getElementsByTagName('script')[0];
3097 s.parentNode.insertBefore(script, s);
3099 else if(typeof callback === "function")
3103 NETDATA._loadCSS = function(filename) {
3104 // don't use jQuery here
3105 // styles are loaded before jQuery
3106 // to eliminate showing an unstyled page to the user
3108 var fileref = document.createElement("link");
3109 fileref.setAttribute("rel", "stylesheet");
3110 fileref.setAttribute("type", "text/css");
3111 fileref.setAttribute("href", filename);
3113 if (typeof fileref !== 'undefined')
3114 document.getElementsByTagName("head")[0].appendChild(fileref);
3117 NETDATA.colorHex2Rgb = function(hex) {
3118 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3119 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3120 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3121 return r + r + g + g + b + b;
3124 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3126 r: parseInt(result[1], 16),
3127 g: parseInt(result[2], 16),
3128 b: parseInt(result[3], 16)
3132 NETDATA.colorLuminance = function(hex, lum) {
3133 // validate hex string
3134 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3136 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3140 // convert to decimal and change luminosity
3141 var rgb = "#", c, i;
3142 for (i = 0; i < 3; i++) {
3143 c = parseInt(hex.substr(i*2,2), 16);
3144 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3145 rgb += ("00"+c).substr(c.length);
3151 NETDATA.guid = function() {
3153 return Math.floor((1 + Math.random()) * 0x10000)
3158 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3161 NETDATA.zeropad = function(x) {
3162 if(x > -10 && x < 10) return '0' + x.toString();
3163 else return x.toString();
3166 // user function to signal us the DOM has been
3168 NETDATA.updatedDom = function() {
3169 NETDATA.options.updated_dom = true;
3172 NETDATA.ready = function(callback) {
3173 NETDATA.options.pauseCallback = callback;
3176 NETDATA.pause = function(callback) {
3177 if(NETDATA.options.pause === true)
3180 NETDATA.options.pauseCallback = callback;
3183 NETDATA.unpause = function() {
3184 NETDATA.options.pauseCallback = null;
3185 NETDATA.options.updated_dom = true;
3186 NETDATA.options.pause = false;
3189 // ----------------------------------------------------------------------------------------------------------------
3191 // this is purely sequencial charts refresher
3192 // it is meant to be autonomous
3193 NETDATA.chartRefresherNoParallel = function(index) {
3194 if(NETDATA.options.debug.mail_loop === true)
3195 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3197 if(NETDATA.options.updated_dom === true) {
3198 // the dom has been updated
3199 // get the dom parts again
3200 NETDATA.parseDom(NETDATA.chartRefresher);
3203 if(index >= NETDATA.options.targets.length) {
3204 if(NETDATA.options.debug.main_loop === true)
3205 console.log('waiting to restart main loop...');
3207 NETDATA.options.auto_refresher_fast_weight = 0;
3209 setTimeout(function() {
3210 NETDATA.chartRefresher();
3211 }, NETDATA.options.current.idle_between_loops);
3214 var state = NETDATA.options.targets[index];
3216 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3217 if(NETDATA.options.debug.main_loop === true)
3218 console.log('fast rendering...');
3220 state.autoRefresh(function() {
3221 NETDATA.chartRefresherNoParallel(++index);
3225 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3226 NETDATA.options.auto_refresher_fast_weight = 0;
3228 setTimeout(function() {
3229 state.autoRefresh(function() {
3230 NETDATA.chartRefresherNoParallel(++index);
3232 }, NETDATA.options.current.idle_between_charts);
3237 // this is part of the parallel refresher
3238 // its cause is to refresh sequencially all the charts
3239 // that depend on chart library initialization
3240 // it will call the parallel refresher back
3241 // as soon as it sees a chart that its chart library
3243 NETDATA.chartRefresher_uninitialized = function() {
3244 if(NETDATA.options.updated_dom === true) {
3245 // the dom has been updated
3246 // get the dom parts again
3247 NETDATA.parseDom(NETDATA.chartRefresher);
3251 if(NETDATA.options.sequencial.length === 0)
3252 NETDATA.chartRefresher();
3254 var state = NETDATA.options.sequencial.pop();
3255 if(state.library.initialized === true)
3256 NETDATA.chartRefresher();
3258 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3262 NETDATA.chartRefresherWaitTime = function() {
3263 return NETDATA.options.current.idle_parallel_loops;
3266 // the default refresher
3267 // it will create 2 sets of charts:
3268 // - the ones that can be refreshed in parallel
3269 // - the ones that depend on something else
3270 // the first set will be executed in parallel
3271 // the second will be given to NETDATA.chartRefresher_uninitialized()
3272 NETDATA.chartRefresher = function() {
3273 if(NETDATA.options.pause === true) {
3274 // console.log('auto-refresher is paused');
3275 setTimeout(NETDATA.chartRefresher,
3276 NETDATA.chartRefresherWaitTime());
3280 if(typeof NETDATA.options.pauseCallback === 'function') {
3281 // console.log('auto-refresher is calling pauseCallback');
3282 NETDATA.options.pause = true;
3283 NETDATA.options.pauseCallback();
3284 NETDATA.chartRefresher();
3288 if(NETDATA.options.current.parallel_refresher === false) {
3289 NETDATA.chartRefresherNoParallel(0);
3293 if(NETDATA.options.updated_dom === true) {
3294 // the dom has been updated
3295 // get the dom parts again
3296 NETDATA.parseDom(NETDATA.chartRefresher);
3300 var parallel = new Array();
3301 var targets = NETDATA.options.targets;
3302 var len = targets.length;
3305 state = targets[len];
3306 if(state.isVisible() === false || state.running === true)
3309 if(state.library.initialized === false) {
3310 if(state.library.enabled === true) {
3311 state.library.initialize(NETDATA.chartRefresher);
3315 state.error('chart library "' + state.library_name + '" is not enabled.');
3319 parallel.unshift(state);
3322 if(parallel.length > 0) {
3323 // this will execute the jobs in parallel
3324 $(parallel).each(function() {
3329 // run the next refresh iteration
3330 setTimeout(NETDATA.chartRefresher,
3331 NETDATA.chartRefresherWaitTime());
3334 NETDATA.parseDom = function(callback) {
3335 NETDATA.options.last_page_scroll = new Date().getTime();
3336 NETDATA.options.updated_dom = false;
3338 var targets = $('div[data-netdata]'); //.filter(':visible');
3340 if(NETDATA.options.debug.main_loop === true)
3341 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3343 NETDATA.options.targets = new Array();
3344 var len = targets.length;
3346 // the initialization will take care of sizing
3347 // and the "loading..." message
3348 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3351 if(typeof callback === 'function') callback();
3354 // this is the main function - where everything starts
3355 NETDATA.start = function() {
3356 // this should be called only once
3358 NETDATA.options.page_is_visible = true;
3360 $(window).blur(function() {
3361 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3362 NETDATA.options.page_is_visible = false;
3363 if(NETDATA.options.debug.focus === true)
3364 console.log('Lost Focus!');
3368 $(window).focus(function() {
3369 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3370 NETDATA.options.page_is_visible = true;
3371 if(NETDATA.options.debug.focus === true)
3372 console.log('Focus restored!');
3376 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3377 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3378 NETDATA.options.page_is_visible = false;
3379 if(NETDATA.options.debug.focus === true)
3380 console.log('Document has no focus!');
3384 // bootstrap tab switching
3385 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3387 // bootstrap modal switching
3388 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3389 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3391 // bootstrap collapse switching
3392 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3393 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3395 NETDATA.parseDom(NETDATA.chartRefresher);
3397 // Registry initialization
3398 setTimeout(NETDATA.registry.init, 3000);
3401 // ----------------------------------------------------------------------------------------------------------------
3404 NETDATA.peityInitialize = function(callback) {
3405 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3407 url: NETDATA.peity_js,
3410 xhrFields: { withCredentials: true } // required for the cookie
3413 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3416 NETDATA.chartLibraries.peity.enabled = false;
3417 NETDATA.error(100, NETDATA.peity_js);
3419 .always(function() {
3420 if(typeof callback === "function")
3425 NETDATA.chartLibraries.peity.enabled = false;
3426 if(typeof callback === "function")
3431 NETDATA.peityChartUpdate = function(state, data) {
3432 state.peity_instance.innerHTML = data.result;
3434 if(state.peity_options.stroke !== state.chartColors()[0]) {
3435 state.peity_options.stroke = state.chartColors()[0];
3436 if(state.chart.chart_type === 'line')
3437 state.peity_options.fill = NETDATA.themes.current.background;
3439 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3442 $(state.peity_instance).peity('line', state.peity_options);
3446 NETDATA.peityChartCreate = function(state, data) {
3447 state.peity_instance = document.createElement('div');
3448 state.element_chart.appendChild(state.peity_instance);
3450 var self = $(state.element);
3451 state.peity_options = {
3452 stroke: NETDATA.themes.current.foreground,
3453 strokeWidth: self.data('peity-strokewidth') || 1,
3454 width: state.chartWidth(),
3455 height: state.chartHeight(),
3456 fill: NETDATA.themes.current.foreground
3459 NETDATA.peityChartUpdate(state, data);
3463 // ----------------------------------------------------------------------------------------------------------------
3466 NETDATA.sparklineInitialize = function(callback) {
3467 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3469 url: NETDATA.sparkline_js,
3472 xhrFields: { withCredentials: true } // required for the cookie
3475 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3478 NETDATA.chartLibraries.sparkline.enabled = false;
3479 NETDATA.error(100, NETDATA.sparkline_js);
3481 .always(function() {
3482 if(typeof callback === "function")
3487 NETDATA.chartLibraries.sparkline.enabled = false;
3488 if(typeof callback === "function")
3493 NETDATA.sparklineChartUpdate = function(state, data) {
3494 state.sparkline_options.width = state.chartWidth();
3495 state.sparkline_options.height = state.chartHeight();
3497 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3501 NETDATA.sparklineChartCreate = function(state, data) {
3502 var self = $(state.element);
3503 var type = self.data('sparkline-type') || 'line';
3504 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3505 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3506 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3507 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3508 var composite = self.data('sparkline-composite') || undefined;
3509 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3510 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3511 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3512 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3513 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3514 var spotColor = self.data('sparkline-spotcolor') || undefined;
3515 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3516 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3517 var spotRadius = self.data('sparkline-spotradius') || undefined;
3518 var valueSpots = self.data('sparkline-valuespots') || undefined;
3519 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3520 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3521 var lineWidth = self.data('sparkline-linewidth') || undefined;
3522 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3523 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3524 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3525 var xvalues = self.data('sparkline-xvalues') || undefined;
3526 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3527 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3528 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3529 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3530 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3531 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3532 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3533 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3534 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3535 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3536 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3537 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3538 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3539 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3540 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3541 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3542 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3543 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3544 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3545 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3546 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3547 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3549 if(spotColor === 'disable') spotColor='';
3550 if(minSpotColor === 'disable') minSpotColor='';
3551 if(maxSpotColor === 'disable') maxSpotColor='';
3553 state.sparkline_options = {
3555 lineColor: lineColor,
3556 fillColor: fillColor,
3557 chartRangeMin: chartRangeMin,
3558 chartRangeMax: chartRangeMax,
3559 composite: composite,
3560 enableTagOptions: enableTagOptions,
3561 tagOptionPrefix: tagOptionPrefix,
3562 tagValuesAttribute: tagValuesAttribute,
3563 disableHiddenCheck: disableHiddenCheck,
3564 defaultPixelsPerValue: defaultPixelsPerValue,
3565 spotColor: spotColor,
3566 minSpotColor: minSpotColor,
3567 maxSpotColor: maxSpotColor,
3568 spotRadius: spotRadius,
3569 valueSpots: valueSpots,
3570 highlightSpotColor: highlightSpotColor,
3571 highlightLineColor: highlightLineColor,
3572 lineWidth: lineWidth,
3573 normalRangeMin: normalRangeMin,
3574 normalRangeMax: normalRangeMax,
3575 drawNormalOnTop: drawNormalOnTop,
3577 chartRangeClip: chartRangeClip,
3578 chartRangeMinX: chartRangeMinX,
3579 chartRangeMaxX: chartRangeMaxX,
3580 disableInteraction: disableInteraction,
3581 disableTooltips: disableTooltips,
3582 disableHighlight: disableHighlight,
3583 highlightLighten: highlightLighten,
3584 highlightColor: highlightColor,
3585 tooltipContainer: tooltipContainer,
3586 tooltipClassname: tooltipClassname,
3587 tooltipChartTitle: state.title,
3588 tooltipFormat: tooltipFormat,
3589 tooltipPrefix: tooltipPrefix,
3590 tooltipSuffix: tooltipSuffix,
3591 tooltipSkipNull: tooltipSkipNull,
3592 tooltipValueLookups: tooltipValueLookups,
3593 tooltipFormatFieldlist: tooltipFormatFieldlist,
3594 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3595 numberFormatter: numberFormatter,
3596 numberDigitGroupSep: numberDigitGroupSep,
3597 numberDecimalMark: numberDecimalMark,
3598 numberDigitGroupCount: numberDigitGroupCount,
3599 animatedZooms: animatedZooms,
3600 width: state.chartWidth(),
3601 height: state.chartHeight()
3604 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3608 // ----------------------------------------------------------------------------------------------------------------
3615 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3616 if(after < state.netdata_first)
3617 after = state.netdata_first;
3619 if(before > state.netdata_last)
3620 before = state.netdata_last;
3622 state.setMode('zoom');
3623 state.globalSelectionSyncStop();
3624 state.globalSelectionSyncDelay();
3625 state.dygraph_user_action = true;
3626 state.dygraph_force_zoom = true;
3627 state.updateChartPanOrZoom(after, before);
3628 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3631 NETDATA.dygraphSetSelection = function(state, t) {
3632 if(typeof state.dygraph_instance !== 'undefined') {
3633 var r = state.calculateRowForTime(t);
3635 state.dygraph_instance.setSelection(r);
3637 state.dygraph_instance.clearSelection();
3638 state.legendShowUndefined();
3645 NETDATA.dygraphClearSelection = function(state, t) {
3646 if(typeof state.dygraph_instance !== 'undefined') {
3647 state.dygraph_instance.clearSelection();
3652 NETDATA.dygraphSmoothInitialize = function(callback) {
3654 url: NETDATA.dygraph_smooth_js,
3657 xhrFields: { withCredentials: true } // required for the cookie
3660 NETDATA.dygraph.smooth = true;
3661 smoothPlotter.smoothing = 0.3;
3664 NETDATA.dygraph.smooth = false;
3666 .always(function() {
3667 if(typeof callback === "function")
3672 NETDATA.dygraphInitialize = function(callback) {
3673 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3675 url: NETDATA.dygraph_js,
3678 xhrFields: { withCredentials: true } // required for the cookie
3681 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3684 NETDATA.chartLibraries.dygraph.enabled = false;
3685 NETDATA.error(100, NETDATA.dygraph_js);
3687 .always(function() {
3688 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3689 NETDATA.dygraphSmoothInitialize(callback);
3690 else if(typeof callback === "function")
3695 NETDATA.chartLibraries.dygraph.enabled = false;
3696 if(typeof callback === "function")
3701 NETDATA.dygraphChartUpdate = function(state, data) {
3702 var dygraph = state.dygraph_instance;
3704 if(typeof dygraph === 'undefined')
3705 return NETDATA.dygraphChartCreate(state, data);
3707 // when the chart is not visible, and hidden
3708 // if there is a window resize, dygraph detects
3709 // its element size as 0x0.
3710 // this will make it re-appear properly
3712 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3716 file: data.result.data,
3717 colors: state.chartColors(),
3718 labels: data.result.labels,
3719 labelsDivWidth: state.chartWidth() - 70,
3720 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3723 if(state.dygraph_force_zoom === true) {
3724 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3725 state.log('dygraphChartUpdate() forced zoom update');
3727 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3728 options.valueRange = state.dygraph_options.valueRange;
3729 options.isZoomedIgnoreProgrammaticZoom = true;
3730 state.dygraph_force_zoom = false;
3732 else if(state.current.name !== 'auto') {
3733 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3734 state.log('dygraphChartUpdate() loose update');
3736 options.valueRange = state.dygraph_options.valueRange;
3739 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3740 state.log('dygraphChartUpdate() strict update');
3742 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3743 options.valueRange = state.dygraph_options.valueRange;
3744 options.isZoomedIgnoreProgrammaticZoom = true;
3747 if(state.dygraph_smooth_eligible === true) {
3748 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3749 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3750 NETDATA.dygraphChartCreate(state, data);
3755 dygraph.updateOptions(options);
3757 state.dygraph_last_rendered = new Date().getTime();
3761 NETDATA.dygraphChartCreate = function(state, data) {
3762 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3763 state.log('dygraphChartCreate()');
3765 var self = $(state.element);
3767 var chart_type = state.chart.chart_type;
3768 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3769 chart_type = self.data('dygraph-type') || chart_type;
3771 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3772 smooth = self.data('dygraph-smooth') || smooth;
3774 if(NETDATA.dygraph.smooth === false)
3777 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3778 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3780 state.dygraph_options = {
3781 colors: self.data('dygraph-colors') || state.chartColors(),
3783 // leave a few pixels empty on the right of the chart
3784 rightGap: self.data('dygraph-rightgap') || 5,
3785 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3786 showRoller: self.data('dygraph-showroller') || false,
3788 title: self.data('dygraph-title') || state.title,
3789 titleHeight: self.data('dygraph-titleheight') || 19,
3791 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3792 labels: data.result.labels,
3793 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3794 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3795 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3796 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3797 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3800 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3801 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3803 includeZero: self.data('dygraph-includezero') || false,
3804 xRangePad: self.data('dygraph-xrangepad') || 0,
3805 yRangePad: self.data('dygraph-yrangepad') || 1,
3807 valueRange: self.data('dygraph-valuerange') || null,
3809 ylabel: state.units,
3810 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3812 // the function to plot the chart
3815 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3816 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3817 strokePattern: self.data('dygraph-strokepattern') || undefined,
3819 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3820 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3821 drawPoints: self.data('dygraph-drawpoints') || false,
3823 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3824 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3826 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3827 pointSize: self.data('dygraph-pointsize') || 1,
3829 // enabling this makes the chart with little square lines
3830 stepPlot: self.data('dygraph-stepplot') || false,
3832 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3833 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3834 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3836 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3837 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3838 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3839 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3841 drawAxis: self.data('dygraph-drawaxis') || true,
3842 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3843 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3844 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3846 drawGrid: self.data('dygraph-drawgrid') || true,
3847 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3848 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3849 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3850 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3851 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3853 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3854 sigFigs: self.data('dygraph-sigfigs') || null,
3855 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3856 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3858 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3859 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3860 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3862 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3863 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3867 ticker: Dygraph.dateTicker,
3868 axisLabelFormatter: function (d, gran) {
3869 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3871 valueFormatter: function (ms) {
3872 var d = new Date(ms);
3873 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3874 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3879 valueFormatter: function (x) {
3880 // we format legends with the state object
3881 // no need to do anything here
3882 // return (Math.round(x*100) / 100).toLocaleString();
3883 // return state.legendFormatValue(x);
3888 legendFormatter: function(data) {
3889 var elements = state.element_legend_childs;
3891 // if the hidden div is not there
3892 // we are not managing the legend
3893 if(elements.hidden === null) return;
3895 if (typeof data.x !== 'undefined') {
3896 state.legendSetDate(data.x);
3897 var i = data.series.length;
3899 var series = data.series[i];
3900 if(!series.isVisible) continue;
3901 state.legendSetLabelValue(series.label, series.y);
3907 drawCallback: function(dygraph, is_initial) {
3908 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3909 state.dygraph_user_action = false;
3911 var x_range = dygraph.xAxisRange();
3912 var after = Math.round(x_range[0]);
3913 var before = Math.round(x_range[1]);
3915 if(NETDATA.options.debug.dygraph === true)
3916 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3918 if(before <= state.netdata_last && after >= state.netdata_first)
3919 state.updateChartPanOrZoom(after, before);
3922 zoomCallback: function(minDate, maxDate, yRanges) {
3923 if(NETDATA.options.debug.dygraph === true)
3924 state.log('dygraphZoomCallback()');
3926 state.globalSelectionSyncStop();
3927 state.globalSelectionSyncDelay();
3928 state.setMode('zoom');
3930 // refresh it to the greatest possible zoom level
3931 state.dygraph_user_action = true;
3932 state.dygraph_force_zoom = true;
3933 state.updateChartPanOrZoom(minDate, maxDate);
3935 highlightCallback: function(event, x, points, row, seriesName) {
3936 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3937 state.log('dygraphHighlightCallback()');
3941 // there is a bug in dygraph when the chart is zoomed enough
3942 // the time it thinks is selected is wrong
3943 // here we calculate the time t based on the row number selected
3945 var t = state.data_after + row * state.data_update_every;
3946 // 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);
3948 state.globalSelectionSync(x);
3950 // fix legend zIndex using the internal structures of dygraph legend module
3951 // this works, but it is a hack!
3952 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3954 unhighlightCallback: function(event) {
3955 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3956 state.log('dygraphUnhighlightCallback()');
3958 state.unpauseChart();
3959 state.globalSelectionSyncStop();
3961 interactionModel : {
3962 mousedown: function(event, dygraph, context) {
3963 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3964 state.log('interactionModel.mousedown()');
3966 state.dygraph_user_action = true;
3967 state.globalSelectionSyncStop();
3969 if(NETDATA.options.debug.dygraph === true)
3970 state.log('dygraphMouseDown()');
3972 // Right-click should not initiate a zoom.
3973 if(event.button && event.button === 2) return;
3975 context.initializeMouseDown(event, dygraph, context);
3977 if(event.button && event.button === 1) {
3978 if (event.altKey || event.shiftKey) {
3979 state.setMode('pan');
3980 state.globalSelectionSyncDelay();
3981 Dygraph.startPan(event, dygraph, context);
3984 state.setMode('zoom');
3985 state.globalSelectionSyncDelay();
3986 Dygraph.startZoom(event, dygraph, context);
3990 if (event.altKey || event.shiftKey) {
3991 state.setMode('zoom');
3992 state.globalSelectionSyncDelay();
3993 Dygraph.startZoom(event, dygraph, context);
3996 state.setMode('pan');
3997 state.globalSelectionSyncDelay();
3998 Dygraph.startPan(event, dygraph, context);
4002 mousemove: function(event, dygraph, context) {
4003 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4004 state.log('interactionModel.mousemove()');
4006 if(context.isPanning) {
4007 state.dygraph_user_action = true;
4008 state.globalSelectionSyncStop();
4009 state.globalSelectionSyncDelay();
4010 state.setMode('pan');
4011 Dygraph.movePan(event, dygraph, context);
4013 else if(context.isZooming) {
4014 state.dygraph_user_action = true;
4015 state.globalSelectionSyncStop();
4016 state.globalSelectionSyncDelay();
4017 state.setMode('zoom');
4018 Dygraph.moveZoom(event, dygraph, context);
4021 mouseup: function(event, dygraph, context) {
4022 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4023 state.log('interactionModel.mouseup()');
4025 if (context.isPanning) {
4026 state.dygraph_user_action = true;
4027 state.globalSelectionSyncDelay();
4028 Dygraph.endPan(event, dygraph, context);
4030 else if (context.isZooming) {
4031 state.dygraph_user_action = true;
4032 state.globalSelectionSyncDelay();
4033 Dygraph.endZoom(event, dygraph, context);
4036 click: function(event, dygraph, context) {
4037 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4038 state.log('interactionModel.click()');
4040 event.preventDefault();
4042 dblclick: function(event, dygraph, context) {
4043 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4044 state.log('interactionModel.dblclick()');
4045 NETDATA.resetAllCharts(state);
4047 mousewheel: function(event, dygraph, context) {
4048 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4049 state.log('interactionModel.mousewheel()');
4051 // Take the offset of a mouse event on the dygraph canvas and
4052 // convert it to a pair of percentages from the bottom left.
4053 // (Not top left, bottom is where the lower value is.)
4054 function offsetToPercentage(g, offsetX, offsetY) {
4055 // This is calculating the pixel offset of the leftmost date.
4056 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4057 var yar0 = g.yAxisRange(0);
4059 // This is calculating the pixel of the higest value. (Top pixel)
4060 var yOffset = g.toDomCoords(null, yar0[1])[1];
4062 // x y w and h are relative to the corner of the drawing area,
4063 // so that the upper corner of the drawing area is (0, 0).
4064 var x = offsetX - xOffset;
4065 var y = offsetY - yOffset;
4067 // This is computing the rightmost pixel, effectively defining the
4069 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4071 // This is computing the lowest pixel, effectively defining the height.
4072 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4074 // Percentage from the left.
4075 var xPct = w === 0 ? 0 : (x / w);
4076 // Percentage from the top.
4077 var yPct = h === 0 ? 0 : (y / h);
4079 // The (1-) part below changes it from "% distance down from the top"
4080 // to "% distance up from the bottom".
4081 return [xPct, (1-yPct)];
4084 // Adjusts [x, y] toward each other by zoomInPercentage%
4085 // Split it so the left/bottom axis gets xBias/yBias of that change and
4086 // tight/top gets (1-xBias)/(1-yBias) of that change.
4088 // If a bias is missing it splits it down the middle.
4089 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4090 xBias = xBias || 0.5;
4091 yBias = yBias || 0.5;
4093 function adjustAxis(axis, zoomInPercentage, bias) {
4094 var delta = axis[1] - axis[0];
4095 var increment = delta * zoomInPercentage;
4096 var foo = [increment * bias, increment * (1-bias)];
4098 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4101 var yAxes = g.yAxisRanges();
4103 for (var i = 0; i < yAxes.length; i++) {
4104 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4107 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4110 if(event.altKey || event.shiftKey) {
4111 state.dygraph_user_action = true;
4113 state.globalSelectionSyncStop();
4114 state.globalSelectionSyncDelay();
4116 // http://dygraphs.com/gallery/interaction-api.js
4117 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4118 var percentage = normal / 50;
4120 if (!(event.offsetX && event.offsetY)){
4121 event.offsetX = event.layerX - event.target.offsetLeft;
4122 event.offsetY = event.layerY - event.target.offsetTop;
4125 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4126 var xPct = percentages[0];
4127 var yPct = percentages[1];
4129 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4131 var after = new_x_range[0];
4132 var before = new_x_range[1];
4134 var first = state.netdata_first + state.data_update_every;
4135 var last = state.netdata_last + state.data_update_every;
4138 after -= (before - last);
4145 state.setMode('zoom');
4146 if(state.updateChartPanOrZoom(after, before) === true)
4147 dygraph.updateOptions({ dateWindow: [ after, before ] });
4149 event.preventDefault();
4152 touchstart: function(event, dygraph, context) {
4153 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4154 state.log('interactionModel.touchstart()');
4156 state.dygraph_user_action = true;
4157 state.setMode('zoom');
4160 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4162 // we overwrite the touch directions at the end, to overwrite
4163 // the internal default of dygraphs
4164 context.touchDirections = { x: true, y: false };
4166 state.dygraph_last_touch_start = new Date().getTime();
4167 state.dygraph_last_touch_move = 0;
4169 if(typeof event.touches[0].pageX === 'number')
4170 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4172 state.dygraph_last_touch_page_x = 0;
4174 touchmove: function(event, dygraph, context) {
4175 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4176 state.log('interactionModel.touchmove()');
4178 state.dygraph_user_action = true;
4179 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4181 state.dygraph_last_touch_move = new Date().getTime();
4183 touchend: function(event, dygraph, context) {
4184 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4185 state.log('interactionModel.touchend()');
4187 state.dygraph_user_action = true;
4188 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4190 // if it didn't move, it is a selection
4191 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4192 // internal api of dygraphs
4193 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4194 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4195 if(NETDATA.dygraphSetSelection(state, t) === true)
4196 state.globalSelectionSync(t);
4199 // if it was double tap within double click time, reset the charts
4200 var now = new Date().getTime();
4201 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4202 if(state.dygraph_last_touch_move === 0) {
4203 var dt = now - state.dygraph_last_touch_end;
4204 if(dt <= NETDATA.options.current.double_click_speed)
4205 NETDATA.resetAllCharts(state);
4209 // remember the timestamp of the last touch end
4210 state.dygraph_last_touch_end = now;
4215 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4216 state.dygraph_options.drawGrid = false;
4217 state.dygraph_options.drawAxis = false;
4218 state.dygraph_options.title = undefined;
4219 state.dygraph_options.units = undefined;
4220 state.dygraph_options.ylabel = undefined;
4221 state.dygraph_options.yLabelWidth = 0;
4222 state.dygraph_options.labelsDivWidth = 120;
4223 state.dygraph_options.labelsDivStyles.width = '120px';
4224 state.dygraph_options.labelsSeparateLines = true;
4225 state.dygraph_options.rightGap = 0;
4226 state.dygraph_options.yRangePad = 1;
4229 if(smooth === true) {
4230 state.dygraph_smooth_eligible = true;
4232 if(NETDATA.options.current.smooth_plot === true)
4233 state.dygraph_options.plotter = smoothPlotter;
4235 else state.dygraph_smooth_eligible = false;
4237 state.dygraph_instance = new Dygraph(state.element_chart,
4238 data.result.data, state.dygraph_options);
4240 state.dygraph_force_zoom = false;
4241 state.dygraph_user_action = false;
4242 state.dygraph_last_rendered = new Date().getTime();
4246 // ----------------------------------------------------------------------------------------------------------------
4249 NETDATA.morrisInitialize = function(callback) {
4250 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4252 // morris requires raphael
4253 if(!NETDATA.chartLibraries.raphael.initialized) {
4254 if(NETDATA.chartLibraries.raphael.enabled) {
4255 NETDATA.raphaelInitialize(function() {
4256 NETDATA.morrisInitialize(callback);
4260 NETDATA.chartLibraries.morris.enabled = false;
4261 if(typeof callback === "function")
4266 NETDATA._loadCSS(NETDATA.morris_css);
4269 url: NETDATA.morris_js,
4272 xhrFields: { withCredentials: true } // required for the cookie
4275 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4278 NETDATA.chartLibraries.morris.enabled = false;
4279 NETDATA.error(100, NETDATA.morris_js);
4281 .always(function() {
4282 if(typeof callback === "function")
4288 NETDATA.chartLibraries.morris.enabled = false;
4289 if(typeof callback === "function")
4294 NETDATA.morrisChartUpdate = function(state, data) {
4295 state.morris_instance.setData(data.result.data);
4299 NETDATA.morrisChartCreate = function(state, data) {
4301 state.morris_options = {
4302 element: state.element_chart.id,
4303 data: data.result.data,
4305 ykeys: data.dimension_names,
4306 labels: data.dimension_names,
4312 continuousLine: false,
4313 behaveLikeLine: false
4316 if(state.chart.chart_type === 'line')
4317 state.morris_instance = new Morris.Line(state.morris_options);
4319 else if(state.chart.chart_type === 'area') {
4320 state.morris_options.behaveLikeLine = true;
4321 state.morris_instance = new Morris.Area(state.morris_options);
4324 state.morris_instance = new Morris.Area(state.morris_options);
4329 // ----------------------------------------------------------------------------------------------------------------
4332 NETDATA.raphaelInitialize = function(callback) {
4333 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4335 url: NETDATA.raphael_js,
4338 xhrFields: { withCredentials: true } // required for the cookie
4341 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4344 NETDATA.chartLibraries.raphael.enabled = false;
4345 NETDATA.error(100, NETDATA.raphael_js);
4347 .always(function() {
4348 if(typeof callback === "function")
4353 NETDATA.chartLibraries.raphael.enabled = false;
4354 if(typeof callback === "function")
4359 NETDATA.raphaelChartUpdate = function(state, data) {
4360 $(state.element_chart).raphael(data.result, {
4361 width: state.chartWidth(),
4362 height: state.chartHeight()
4368 NETDATA.raphaelChartCreate = function(state, data) {
4369 $(state.element_chart).raphael(data.result, {
4370 width: state.chartWidth(),
4371 height: state.chartHeight()
4377 // ----------------------------------------------------------------------------------------------------------------
4380 NETDATA.c3Initialize = function(callback) {
4381 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4384 if(!NETDATA.chartLibraries.d3.initialized) {
4385 if(NETDATA.chartLibraries.d3.enabled) {
4386 NETDATA.d3Initialize(function() {
4387 NETDATA.c3Initialize(callback);
4391 NETDATA.chartLibraries.c3.enabled = false;
4392 if(typeof callback === "function")
4397 NETDATA._loadCSS(NETDATA.c3_css);
4403 xhrFields: { withCredentials: true } // required for the cookie
4406 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4409 NETDATA.chartLibraries.c3.enabled = false;
4410 NETDATA.error(100, NETDATA.c3_js);
4412 .always(function() {
4413 if(typeof callback === "function")
4419 NETDATA.chartLibraries.c3.enabled = false;
4420 if(typeof callback === "function")
4425 NETDATA.c3ChartUpdate = function(state, data) {
4426 state.c3_instance.destroy();
4427 return NETDATA.c3ChartCreate(state, data);
4429 //state.c3_instance.load({
4430 // rows: data.result,
4437 NETDATA.c3ChartCreate = function(state, data) {
4439 state.element_chart.id = 'c3-' + state.uuid;
4440 // console.log('id = ' + state.element_chart.id);
4442 state.c3_instance = c3.generate({
4443 bindto: '#' + state.element_chart.id,
4445 width: state.chartWidth(),
4446 height: state.chartHeight()
4449 pattern: state.chartColors()
4454 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4460 format: function(x) {
4461 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4488 // console.log(state.c3_instance);
4493 // ----------------------------------------------------------------------------------------------------------------
4496 NETDATA.d3Initialize = function(callback) {
4497 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4502 xhrFields: { withCredentials: true } // required for the cookie
4505 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4508 NETDATA.chartLibraries.d3.enabled = false;
4509 NETDATA.error(100, NETDATA.d3_js);
4511 .always(function() {
4512 if(typeof callback === "function")
4517 NETDATA.chartLibraries.d3.enabled = false;
4518 if(typeof callback === "function")
4523 NETDATA.d3ChartUpdate = function(state, data) {
4527 NETDATA.d3ChartCreate = function(state, data) {
4531 // ----------------------------------------------------------------------------------------------------------------
4534 NETDATA.googleInitialize = function(callback) {
4535 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4537 url: NETDATA.google_js,
4540 xhrFields: { withCredentials: true } // required for the cookie
4543 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4544 google.load('visualization', '1.1', {
4545 'packages': ['corechart', 'controls'],
4546 'callback': callback
4550 NETDATA.chartLibraries.google.enabled = false;
4551 NETDATA.error(100, NETDATA.google_js);
4552 if(typeof callback === "function")
4557 NETDATA.chartLibraries.google.enabled = false;
4558 if(typeof callback === "function")
4563 NETDATA.googleChartUpdate = function(state, data) {
4564 var datatable = new google.visualization.DataTable(data.result);
4565 state.google_instance.draw(datatable, state.google_options);
4569 NETDATA.googleChartCreate = function(state, data) {
4570 var datatable = new google.visualization.DataTable(data.result);
4572 state.google_options = {
4573 colors: state.chartColors(),
4575 // do not set width, height - the chart resizes itself
4576 //width: state.chartWidth(),
4577 //height: state.chartHeight(),
4582 // title: "Time of Day",
4583 // format:'HH:mm:ss',
4584 viewWindowMode: 'maximized',
4596 viewWindowMode: 'pretty',
4611 focusTarget: 'category',
4618 titlePosition: 'out',
4629 curveType: 'function',
4634 switch(state.chart.chart_type) {
4636 state.google_options.vAxis.viewWindowMode = 'maximized';
4637 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4638 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4642 state.google_options.isStacked = true;
4643 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4644 state.google_options.vAxis.viewWindowMode = 'maximized';
4645 state.google_options.vAxis.minValue = null;
4646 state.google_options.vAxis.maxValue = null;
4647 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4652 state.google_options.lineWidth = 2;
4653 state.google_instance = new google.visualization.LineChart(state.element_chart);
4657 state.google_instance.draw(datatable, state.google_options);
4661 // ----------------------------------------------------------------------------------------------------------------
4663 NETDATA.percentFromValueMax = function(value, max) {
4664 if(value === null) value = 0;
4665 if(max < value) max = value;
4669 pcent = Math.round(value * 100 / max);
4670 if(pcent === 0 && value > 0) pcent = 1;
4676 // ----------------------------------------------------------------------------------------------------------------
4679 NETDATA.easypiechartInitialize = function(callback) {
4680 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4682 url: NETDATA.easypiechart_js,
4685 xhrFields: { withCredentials: true } // required for the cookie
4688 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4691 NETDATA.chartLibraries.easypiechart.enabled = false;
4692 NETDATA.error(100, NETDATA.easypiechart_js);
4694 .always(function() {
4695 if(typeof callback === "function")
4700 NETDATA.chartLibraries.easypiechart.enabled = false;
4701 if(typeof callback === "function")
4706 NETDATA.easypiechartClearSelection = function(state) {
4707 if(typeof state.easyPieChartEvent !== 'undefined') {
4708 if(state.easyPieChartEvent.timer !== null)
4709 clearTimeout(state.easyPieChartEvent.timer);
4711 state.easyPieChartEvent.timer = null;
4714 if(state.isAutoRefreshable() === true && state.data !== null) {
4715 NETDATA.easypiechartChartUpdate(state, state.data);
4718 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4719 state.easyPieChart_instance.update(0);
4721 state.easyPieChart_instance.enableAnimation();
4726 NETDATA.easypiechartSetSelection = function(state, t) {
4727 if(state.timeIsVisible(t) !== true)
4728 return NETDATA.easypiechartClearSelection(state);
4730 var slot = state.calculateRowForTime(t);
4731 if(slot < 0 || slot >= state.data.result.length)
4732 return NETDATA.easypiechartClearSelection(state);
4734 if(typeof state.easyPieChartEvent === 'undefined') {
4735 state.easyPieChartEvent = {
4742 var value = state.data.result[state.data.result.length - 1 - slot];
4743 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4744 var pcent = NETDATA.percentFromValueMax(value, max);
4746 state.easyPieChartEvent.value = value;
4747 state.easyPieChartEvent.pcent = pcent;
4748 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4750 if(state.easyPieChartEvent.timer === null) {
4751 state.easyPieChart_instance.disableAnimation();
4753 state.easyPieChartEvent.timer = setTimeout(function() {
4754 state.easyPieChartEvent.timer = null;
4755 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4756 }, NETDATA.options.current.charts_selection_animation_delay);
4762 NETDATA.easypiechartChartUpdate = function(state, data) {
4763 var value, max, pcent;
4765 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4771 value = data.result[0];
4772 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4773 pcent = NETDATA.percentFromValueMax(value, max);
4776 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4777 state.easyPieChart_instance.update(pcent);
4781 NETDATA.easypiechartChartCreate = function(state, data) {
4782 var self = $(state.element);
4783 var chart = $(state.element_chart);
4785 var value = data.result[0];
4786 var max = self.data('easypiechart-max-value') || null;
4787 var adjust = self.data('easypiechart-adjust') || null;
4791 state.easyPieChartMax = null;
4794 state.easyPieChartMax = max;
4796 var pcent = NETDATA.percentFromValueMax(value, max);
4798 chart.data('data-percent', pcent);
4802 case 'width': size = state.chartHeight(); break;
4803 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4804 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4806 default: size = state.chartWidth(); break;
4808 state.element.style.width = size + 'px';
4809 state.element.style.height = size + 'px';
4811 var stroke = Math.floor(size / 22);
4812 if(stroke < 3) stroke = 2;
4814 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4815 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4816 state.easyPieChartLabel = document.createElement('span');
4817 state.easyPieChartLabel.className = 'easyPieChartLabel';
4818 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4819 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4820 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4821 state.element_chart.appendChild(state.easyPieChartLabel);
4823 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4824 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4825 state.easyPieChartTitle = document.createElement('span');
4826 state.easyPieChartTitle.className = 'easyPieChartTitle';
4827 state.easyPieChartTitle.innerHTML = state.title;
4828 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4829 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4830 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4831 state.element_chart.appendChild(state.easyPieChartTitle);
4833 var unitfontsize = Math.round(titlefontsize * 0.9);
4834 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4835 state.easyPieChartUnits = document.createElement('span');
4836 state.easyPieChartUnits.className = 'easyPieChartUnits';
4837 state.easyPieChartUnits.innerHTML = state.units;
4838 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4839 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4840 state.element_chart.appendChild(state.easyPieChartUnits);
4842 chart.easyPieChart({
4843 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4844 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4845 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4846 scaleLength: self.data('easypiechart-scalelength') || 5,
4847 lineCap: self.data('easypiechart-linecap') || 'round',
4848 lineWidth: self.data('easypiechart-linewidth') || stroke,
4849 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4850 size: self.data('easypiechart-size') || size,
4851 rotate: self.data('easypiechart-rotate') || 0,
4852 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4853 easing: self.data('easypiechart-easing') || undefined
4856 // when we just re-create the chart
4857 // do not animate the first update
4859 if(typeof state.easyPieChart_instance !== 'undefined')
4862 state.easyPieChart_instance = chart.data('easyPieChart');
4863 if(animate === false) state.easyPieChart_instance.disableAnimation();
4864 state.easyPieChart_instance.update(pcent);
4865 if(animate === false) state.easyPieChart_instance.enableAnimation();
4869 // ----------------------------------------------------------------------------------------------------------------
4872 NETDATA.gaugeInitialize = function(callback) {
4873 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4875 url: NETDATA.gauge_js,
4878 xhrFields: { withCredentials: true } // required for the cookie
4881 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4884 NETDATA.chartLibraries.gauge.enabled = false;
4885 NETDATA.error(100, NETDATA.gauge_js);
4887 .always(function() {
4888 if(typeof callback === "function")
4893 NETDATA.chartLibraries.gauge.enabled = false;
4894 if(typeof callback === "function")
4899 NETDATA.gaugeAnimation = function(state, status) {
4902 if(typeof status === 'boolean' && status === false)
4904 else if(typeof status === 'number')
4907 state.gauge_instance.animationSpeed = speed;
4908 state.___gaugeOld__.speed = speed;
4911 NETDATA.gaugeSet = function(state, value, min, max) {
4912 if(typeof value !== 'number') value = 0;
4913 if(typeof min !== 'number') min = 0;
4914 if(typeof max !== 'number') max = 0;
4915 if(value > max) max = value;
4916 if(value < min) min = value;
4925 // gauge.js has an issue if the needle
4926 // is smaller than min or larger than max
4927 // when we set the new values
4928 // the needle will go crazy
4930 // to prevent it, we always feed it
4931 // with a percentage, so that the needle
4932 // is always between min and max
4933 var pcent = (value - min) * 100 / (max - min);
4935 // these should never happen
4936 if(pcent < 0) pcent = 0;
4937 if(pcent > 100) pcent = 100;
4939 state.gauge_instance.set(pcent);
4941 state.___gaugeOld__.value = value;
4942 state.___gaugeOld__.min = min;
4943 state.___gaugeOld__.max = max;
4946 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4947 if(state.___gaugeOld__.valueLabel !== value) {
4948 state.___gaugeOld__.valueLabel = value;
4949 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4951 if(state.___gaugeOld__.minLabel !== min) {
4952 state.___gaugeOld__.minLabel = min;
4953 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4955 if(state.___gaugeOld__.maxLabel !== max) {
4956 state.___gaugeOld__.maxLabel = max;
4957 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4961 NETDATA.gaugeClearSelection = function(state) {
4962 if(typeof state.gaugeEvent !== 'undefined') {
4963 if(state.gaugeEvent.timer !== null)
4964 clearTimeout(state.gaugeEvent.timer);
4966 state.gaugeEvent.timer = null;
4969 if(state.isAutoRefreshable() === true && state.data !== null) {
4970 NETDATA.gaugeChartUpdate(state, state.data);
4973 NETDATA.gaugeAnimation(state, false);
4974 NETDATA.gaugeSet(state, null, null, null);
4975 NETDATA.gaugeSetLabels(state, null, null, null);
4978 NETDATA.gaugeAnimation(state, true);
4982 NETDATA.gaugeSetSelection = function(state, t) {
4983 if(state.timeIsVisible(t) !== true)
4984 return NETDATA.gaugeClearSelection(state);
4986 var slot = state.calculateRowForTime(t);
4987 if(slot < 0 || slot >= state.data.result.length)
4988 return NETDATA.gaugeClearSelection(state);
4990 if(typeof state.gaugeEvent === 'undefined') {
4991 state.gaugeEvent = {
4999 var value = state.data.result[state.data.result.length - 1 - slot];
5000 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5003 state.gaugeEvent.value = value;
5004 state.gaugeEvent.max = max;
5005 state.gaugeEvent.min = min;
5006 NETDATA.gaugeSetLabels(state, value, min, max);
5008 if(state.gaugeEvent.timer === null) {
5009 NETDATA.gaugeAnimation(state, false);
5011 state.gaugeEvent.timer = setTimeout(function() {
5012 state.gaugeEvent.timer = null;
5013 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5014 }, NETDATA.options.current.charts_selection_animation_delay);
5020 NETDATA.gaugeChartUpdate = function(state, data) {
5021 var value, min, max;
5023 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5027 NETDATA.gaugeSetLabels(state, null, null, null);
5030 value = data.result[0];
5032 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5033 if(value > max) max = value;
5034 NETDATA.gaugeSetLabels(state, value, min, max);
5037 NETDATA.gaugeSet(state, value, min, max);
5041 NETDATA.gaugeChartCreate = function(state, data) {
5042 var self = $(state.element);
5043 // var chart = $(state.element_chart);
5045 var value = data.result[0];
5046 var max = self.data('gauge-max-value') || null;
5047 var adjust = self.data('gauge-adjust') || null;
5048 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5049 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5050 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5051 var stopColor = self.data('gauge-stop-color') || void 0;
5052 var generateGradient = self.data('gauge-generate-gradient') || false;
5056 state.gaugeMax = null;
5059 state.gaugeMax = max;
5061 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5063 // case 'width': width = height * ratio; break;
5065 // default: height = width / ratio; break;
5067 //state.element.style.width = width.toString() + 'px';
5068 //state.element.style.height = height.toString() + 'px';
5073 lines: 12, // The number of lines to draw
5074 angle: 0.15, // The length of each line
5075 lineWidth: 0.44, // 0.44 The line thickness
5077 length: 0.8, // 0.9 The radius of the inner circle
5078 strokeWidth: 0.035, // The rotation offset
5079 color: pointerColor // Fill color
5081 colorStart: startColor, // Colors
5082 colorStop: stopColor, // just experiment with them
5083 strokeColor: strokeColor, // to see which ones work best for you
5085 generateGradient: (generateGradient === true)?true:false,
5089 if (generateGradient.constructor === Array) {
5091 // data-gauge-generate-gradient="[0, 50, 100]"
5092 // data-gauge-gradient-percent-color-0="#FFFFFF"
5093 // data-gauge-gradient-percent-color-50="#999900"
5094 // data-gauge-gradient-percent-color-100="#000000"
5096 options.percentColors = new Array();
5097 var len = generateGradient.length;
5099 var pcent = generateGradient[len];
5100 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5101 if(color !== false) {
5102 var a = new Array();
5105 options.percentColors.unshift(a);
5108 if(options.percentColors.length === 0)
5109 delete options.percentColors;
5111 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5112 options.percentColors = [
5113 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5114 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5115 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5116 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5117 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5118 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5119 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5120 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5121 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5122 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5123 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5126 state.gauge_canvas = document.createElement('canvas');
5127 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5128 state.gauge_canvas.className = 'gaugeChart';
5129 state.gauge_canvas.width = width;
5130 state.gauge_canvas.height = height;
5131 state.element_chart.appendChild(state.gauge_canvas);
5133 var valuefontsize = Math.floor(height / 6);
5134 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5135 state.gaugeChartLabel = document.createElement('span');
5136 state.gaugeChartLabel.className = 'gaugeChartLabel';
5137 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5138 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5139 state.element_chart.appendChild(state.gaugeChartLabel);
5141 var titlefontsize = Math.round(valuefontsize / 2);
5143 state.gaugeChartTitle = document.createElement('span');
5144 state.gaugeChartTitle.className = 'gaugeChartTitle';
5145 state.gaugeChartTitle.innerHTML = state.title;
5146 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5147 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5148 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5149 state.element_chart.appendChild(state.gaugeChartTitle);
5151 var unitfontsize = Math.round(titlefontsize * 0.9);
5152 state.gaugeChartUnits = document.createElement('span');
5153 state.gaugeChartUnits.className = 'gaugeChartUnits';
5154 state.gaugeChartUnits.innerHTML = state.units;
5155 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5156 state.element_chart.appendChild(state.gaugeChartUnits);
5158 state.gaugeChartMin = document.createElement('span');
5159 state.gaugeChartMin.className = 'gaugeChartMin';
5160 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5161 state.element_chart.appendChild(state.gaugeChartMin);
5163 state.gaugeChartMax = document.createElement('span');
5164 state.gaugeChartMax.className = 'gaugeChartMax';
5165 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5166 state.element_chart.appendChild(state.gaugeChartMax);
5168 // when we just re-create the chart
5169 // do not animate the first update
5171 if(typeof state.gauge_instance !== 'undefined')
5174 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5176 state.___gaugeOld__ = {
5185 // we will always feed a percentage
5186 state.gauge_instance.minValue = 0;
5187 state.gauge_instance.maxValue = 100;
5189 NETDATA.gaugeAnimation(state, animate);
5190 NETDATA.gaugeSet(state, value, 0, max);
5191 NETDATA.gaugeSetLabels(state, value, 0, max);
5192 NETDATA.gaugeAnimation(state, true);
5196 // ----------------------------------------------------------------------------------------------------------------
5197 // Charts Libraries Registration
5199 NETDATA.chartLibraries = {
5201 initialize: NETDATA.dygraphInitialize,
5202 create: NETDATA.dygraphChartCreate,
5203 update: NETDATA.dygraphChartUpdate,
5204 resize: function(state) {
5205 if(typeof state.dygraph_instance.resize === 'function')
5206 state.dygraph_instance.resize();
5208 setSelection: NETDATA.dygraphSetSelection,
5209 clearSelection: NETDATA.dygraphClearSelection,
5210 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5213 format: function(state) { return 'json'; },
5214 options: function(state) { return 'ms|flip'; },
5215 legend: function(state) {
5216 if(this.isSparkline(state) === false)
5217 return 'right-side';
5221 autoresize: function(state) { return true; },
5222 max_updates_to_recreate: function(state) { return 5000; },
5223 track_colors: function(state) { return true; },
5224 pixels_per_point: function(state) {
5225 if(this.isSparkline(state) === false)
5231 isSparkline: function(state) {
5232 if(typeof state.dygraph_sparkline === 'undefined') {
5233 var t = $(state.element).data('dygraph-theme');
5234 if(t === 'sparkline')
5235 state.dygraph_sparkline = true;
5237 state.dygraph_sparkline = false;
5239 return state.dygraph_sparkline;
5243 initialize: NETDATA.sparklineInitialize,
5244 create: NETDATA.sparklineChartCreate,
5245 update: NETDATA.sparklineChartUpdate,
5247 setSelection: undefined, // function(state, t) { return true; },
5248 clearSelection: undefined, // function(state) { return true; },
5249 toolboxPanAndZoom: null,
5252 format: function(state) { return 'array'; },
5253 options: function(state) { return 'flip|abs'; },
5254 legend: function(state) { return null; },
5255 autoresize: function(state) { return false; },
5256 max_updates_to_recreate: function(state) { return 5000; },
5257 track_colors: function(state) { return false; },
5258 pixels_per_point: function(state) { return 3; }
5261 initialize: NETDATA.peityInitialize,
5262 create: NETDATA.peityChartCreate,
5263 update: NETDATA.peityChartUpdate,
5265 setSelection: undefined, // function(state, t) { return true; },
5266 clearSelection: undefined, // function(state) { return true; },
5267 toolboxPanAndZoom: null,
5270 format: function(state) { return 'ssvcomma'; },
5271 options: function(state) { return 'null2zero|flip|abs'; },
5272 legend: function(state) { return null; },
5273 autoresize: function(state) { return false; },
5274 max_updates_to_recreate: function(state) { return 5000; },
5275 track_colors: function(state) { return false; },
5276 pixels_per_point: function(state) { return 3; }
5279 initialize: NETDATA.morrisInitialize,
5280 create: NETDATA.morrisChartCreate,
5281 update: NETDATA.morrisChartUpdate,
5283 setSelection: undefined, // function(state, t) { return true; },
5284 clearSelection: undefined, // function(state) { return true; },
5285 toolboxPanAndZoom: null,
5288 format: function(state) { return 'json'; },
5289 options: function(state) { return 'objectrows|ms'; },
5290 legend: function(state) { return null; },
5291 autoresize: function(state) { return false; },
5292 max_updates_to_recreate: function(state) { return 50; },
5293 track_colors: function(state) { return false; },
5294 pixels_per_point: function(state) { return 15; }
5297 initialize: NETDATA.googleInitialize,
5298 create: NETDATA.googleChartCreate,
5299 update: NETDATA.googleChartUpdate,
5301 setSelection: undefined, //function(state, t) { return true; },
5302 clearSelection: undefined, //function(state) { return true; },
5303 toolboxPanAndZoom: null,
5306 format: function(state) { return 'datatable'; },
5307 options: function(state) { return ''; },
5308 legend: function(state) { return null; },
5309 autoresize: function(state) { return false; },
5310 max_updates_to_recreate: function(state) { return 300; },
5311 track_colors: function(state) { return false; },
5312 pixels_per_point: function(state) { return 4; }
5315 initialize: NETDATA.raphaelInitialize,
5316 create: NETDATA.raphaelChartCreate,
5317 update: NETDATA.raphaelChartUpdate,
5319 setSelection: undefined, // function(state, t) { return true; },
5320 clearSelection: undefined, // function(state) { return true; },
5321 toolboxPanAndZoom: null,
5324 format: function(state) { return 'json'; },
5325 options: function(state) { return ''; },
5326 legend: function(state) { return null; },
5327 autoresize: function(state) { return false; },
5328 max_updates_to_recreate: function(state) { return 5000; },
5329 track_colors: function(state) { return false; },
5330 pixels_per_point: function(state) { return 3; }
5333 initialize: NETDATA.c3Initialize,
5334 create: NETDATA.c3ChartCreate,
5335 update: NETDATA.c3ChartUpdate,
5337 setSelection: undefined, // function(state, t) { return true; },
5338 clearSelection: undefined, // function(state) { return true; },
5339 toolboxPanAndZoom: null,
5342 format: function(state) { return 'csvjsonarray'; },
5343 options: function(state) { return 'milliseconds'; },
5344 legend: function(state) { return null; },
5345 autoresize: function(state) { return false; },
5346 max_updates_to_recreate: function(state) { return 5000; },
5347 track_colors: function(state) { return false; },
5348 pixels_per_point: function(state) { return 15; }
5351 initialize: NETDATA.d3Initialize,
5352 create: NETDATA.d3ChartCreate,
5353 update: NETDATA.d3ChartUpdate,
5355 setSelection: undefined, // function(state, t) { return true; },
5356 clearSelection: undefined, // function(state) { return true; },
5357 toolboxPanAndZoom: null,
5360 format: function(state) { return 'json'; },
5361 options: function(state) { return ''; },
5362 legend: function(state) { return null; },
5363 autoresize: function(state) { return false; },
5364 max_updates_to_recreate: function(state) { return 5000; },
5365 track_colors: function(state) { return false; },
5366 pixels_per_point: function(state) { return 3; }
5369 initialize: NETDATA.easypiechartInitialize,
5370 create: NETDATA.easypiechartChartCreate,
5371 update: NETDATA.easypiechartChartUpdate,
5373 setSelection: NETDATA.easypiechartSetSelection,
5374 clearSelection: NETDATA.easypiechartClearSelection,
5375 toolboxPanAndZoom: null,
5378 format: function(state) { return 'array'; },
5379 options: function(state) { return 'absolute'; },
5380 legend: function(state) { return null; },
5381 autoresize: function(state) { return false; },
5382 max_updates_to_recreate: function(state) { return 5000; },
5383 track_colors: function(state) { return true; },
5384 pixels_per_point: function(state) { return 3; },
5388 initialize: NETDATA.gaugeInitialize,
5389 create: NETDATA.gaugeChartCreate,
5390 update: NETDATA.gaugeChartUpdate,
5392 setSelection: NETDATA.gaugeSetSelection,
5393 clearSelection: NETDATA.gaugeClearSelection,
5394 toolboxPanAndZoom: null,
5397 format: function(state) { return 'array'; },
5398 options: function(state) { return 'absolute'; },
5399 legend: function(state) { return null; },
5400 autoresize: function(state) { return false; },
5401 max_updates_to_recreate: function(state) { return 5000; },
5402 track_colors: function(state) { return true; },
5403 pixels_per_point: function(state) { return 3; },
5408 NETDATA.registerChartLibrary = function(library, url) {
5409 if(NETDATA.options.debug.libraries === true)
5410 console.log("registering chart library: " + library);
5412 NETDATA.chartLibraries[library].url = url;
5413 NETDATA.chartLibraries[library].initialized = true;
5414 NETDATA.chartLibraries[library].enabled = true;
5417 // ----------------------------------------------------------------------------------------------------------------
5418 // Load required JS libraries and CSS
5420 NETDATA.requiredJs = [
5422 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5423 isAlreadyLoaded: function() {
5424 // check if bootstrap is loaded
5425 if(typeof $().emulateTransitionEnd == 'function')
5428 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5436 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5437 isAlreadyLoaded: function() { return false; }
5440 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5441 isAlreadyLoaded: function() { return false; }
5445 NETDATA.requiredCSS = [
5447 url: NETDATA.themes.current.bootstrap_css,
5448 isAlreadyLoaded: function() {
5449 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5456 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5457 isAlreadyLoaded: function() { return false; }
5460 url: NETDATA.themes.current.dashboard_css,
5461 isAlreadyLoaded: function() { return false; }
5464 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5465 isAlreadyLoaded: function() { return false; }
5469 NETDATA.loadRequiredJs = function(index, callback) {
5470 if(index >= NETDATA.requiredJs.length) {
5471 if(typeof callback === 'function')
5476 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5477 NETDATA.loadRequiredJs(++index, callback);
5481 if(NETDATA.options.debug.main_loop === true)
5482 console.log('loading ' + NETDATA.requiredJs[index].url);
5485 url: NETDATA.requiredJs[index].url,
5488 xhrFields: { withCredentials: true } // required for the cookie
5490 .success(function() {
5491 if(NETDATA.options.debug.main_loop === true)
5492 console.log('loaded ' + NETDATA.requiredJs[index].url);
5494 NETDATA.loadRequiredJs(++index, callback);
5497 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5501 NETDATA.loadRequiredCSS = function(index) {
5502 if(index >= NETDATA.requiredCSS.length)
5505 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5506 NETDATA.loadRequiredCSS(++index);
5510 if(NETDATA.options.debug.main_loop === true)
5511 console.log('loading ' + NETDATA.requiredCSS[index].url);
5513 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5514 NETDATA.loadRequiredCSS(++index);
5518 // ----------------------------------------------------------------------------------------------------------------
5519 // Registry of netdata hosts
5521 NETDATA.registry = {
5522 server: null, // the netdata registry server
5523 person_guid: null, // the unique ID of this browser / user
5524 machine_guid: null, // the unique ID the netdata server that served dashboard.js
5525 hostname: null, // the hostname of the netdata server that served dashboard.js
5526 machines: null, // the user's other URLs
5527 machines_array: null, // the user's other URLs in an array
5530 parsePersonUrls: function(person_urls) {
5531 // console.log(person_urls);
5532 NETDATA.registry.person_urls = person_urls;
5535 NETDATA.registry.machines = {};
5536 NETDATA.registry.machines_array = new Array();
5538 var now = new Date().getTime();
5539 var apu = person_urls;
5542 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5543 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5549 accesses: apu[i][3],
5551 alternate_urls: new Array()
5553 obj.alternate_urls.push(apu[i][1]);
5555 NETDATA.registry.machines[apu[i][0]] = obj;
5556 NETDATA.registry.machines_array.push(obj);
5559 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5561 var pu = NETDATA.registry.machines[apu[i][0]];
5562 if(pu.last_t < apu[i][2]) {
5564 pu.last_t = apu[i][2];
5565 pu.name = apu[i][4];
5567 pu.accesses += apu[i][3];
5568 pu.alternate_urls.push(apu[i][1]);
5573 if(typeof netdataRegistryCallback === 'function')
5574 netdataRegistryCallback(NETDATA.registry.machines_array);
5578 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5581 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5583 NETDATA.registry.server = data.registry;
5584 NETDATA.registry.machine_guid = data.machine_guid;
5585 NETDATA.registry.hostname = data.hostname;
5587 NETDATA.registry.access(2, function (person_urls) {
5588 NETDATA.registry.parsePersonUrls(person_urls);
5595 hello: function(host, callback) {
5596 while(host.slice(-1) === '/')
5597 host = host.substring(0, host.length - 1);
5599 // send HELLO to a netdata server:
5600 // 1. verifies the server is reachable
5601 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5603 url: host + '/api/v1/registry?action=hello',
5606 xhrFields: { withCredentials: true } // required for the cookie
5608 .done(function(data) {
5609 if(typeof data.status !== 'string' || data.status !== 'ok') {
5610 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5614 if(typeof callback === 'function')
5618 NETDATA.error(407, host);
5620 if(typeof callback === 'function')
5625 access: function(max_redirects, callback) {
5626 // send ACCESS to a netdata registry:
5627 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5628 // 2. it responds with a list of netdata servers we know
5629 // the registry identifies us using a cookie it sets the first time we access it
5630 // the registry may respond with a redirect URL to send us to another registry
5632 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),
5635 xhrFields: { withCredentials: true } // required for the cookie
5637 .done(function(data) {
5638 var redirect = null;
5639 if(typeof data.registry === 'string')
5640 redirect = data.registry;
5642 if(typeof data.status !== 'string' || data.status !== 'ok') {
5643 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5647 if(data === null && redirect !== null && max_redirects > 0) {
5648 NETDATA.registry.server = redirect;
5649 NETDATA.registry.access(max_redirects - 1, callback);
5652 if(typeof data.person_guid === 'string')
5653 NETDATA.registry.person_guid = data.person_guid;
5655 if(typeof callback === 'function')
5656 callback(data.urls);
5660 NETDATA.error(410, NETDATA.registry.server);
5662 if(typeof callback === 'function')
5667 delete: function(delete_url, callback) {
5668 // send DELETE to a netdata registry:
5670 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),
5673 xhrFields: { withCredentials: true } // required for the cookie
5675 .done(function(data) {
5676 if(typeof data.status !== 'string' || data.status !== 'ok') {
5677 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5681 if(typeof callback === 'function')
5685 NETDATA.error(412, NETDATA.registry.server);
5687 if(typeof callback === 'function')
5692 switch: function(new_person_guid, callback) {
5695 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,
5698 xhrFields: { withCredentials: true } // required for the cookie
5700 .done(function(data) {
5701 if(typeof data.status !== 'string' || data.status !== 'ok') {
5702 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5706 if(typeof callback === 'function')
5710 NETDATA.error(414, NETDATA.registry.server);
5712 if(typeof callback === 'function')
5718 // ----------------------------------------------------------------------------------------------------------------
5721 NETDATA.errorReset();
5722 NETDATA.loadRequiredCSS(0);
5724 NETDATA._loadjQuery(function() {
5725 NETDATA.loadRequiredJs(0, function() {
5726 if(typeof $().emulateTransitionEnd !== 'function') {
5727 // bootstrap is not available
5728 NETDATA.options.current.show_help = false;
5731 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5732 if(NETDATA.options.debug.main_loop === true)
5733 console.log('starting chart refresh thread');
5740 // window.NETDATA = NETDATA;
5741 // })(window, document);