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
16 // You can also set the default netdata server, using the following.
17 // When this variable is not set, we assume the page is hosted on your
18 // netdata server already.
19 // var netdataServer = "http://yourhost:19999"; // set your NetData server
21 //(function(window, document, undefined) {
23 // ------------------------------------------------------------------------
24 // compatibility fixes
26 // fix IE issue with console
27 if(!window.console) { window.console = { log: function(){} }; }
29 // if string.endsWith is not defined, define it
30 if(typeof String.prototype.endsWith !== 'function') {
31 String.prototype.endsWith = function(s) {
32 if(s.length > this.length) return false;
33 return this.slice(-s.length) === s;
37 // if string.startsWith is not defined, define it
38 if(typeof String.prototype.startsWith !== 'function') {
39 String.prototype.startsWith = function(s) {
40 if(s.length > this.length) return false;
41 return this.slice(s.length) === s;
46 var NETDATA = window.NETDATA || {};
48 // ----------------------------------------------------------------------------------------------------------------
49 // Detect the netdata server
51 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
52 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
53 NETDATA._scriptSource = function() {
56 if(typeof document.currentScript !== 'undefined') {
57 script = document.currentScript;
60 var all_scripts = document.getElementsByTagName('script');
61 script = all_scripts[all_scripts.length - 1];
64 if (typeof script.getAttribute.length !== 'undefined')
67 script = script.getAttribute('src', -1);
72 if(typeof netdataServer !== 'undefined')
73 NETDATA.serverDefault = netdataServer;
75 var s = NETDATA._scriptSource();
76 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
78 console.log('WARNING: Cannot detect the URL of the netdata server.');
79 NETDATA.serverDefault = null;
83 if(NETDATA.serverDefault === null)
84 NETDATA.serverDefault = '';
85 else if(NETDATA.serverDefault.slice(-1) !== '/')
86 NETDATA.serverDefault += '/';
88 // default URLs for all the external files we need
89 // make them RELATIVE so that the whole thing can also be
90 // installed under a web server
91 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
92 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
93 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
94 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
95 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
96 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
97 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
98 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
99 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
100 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
101 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
102 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
103 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
104 NETDATA.google_js = 'https://www.google.com/jsapi';
108 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
109 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
110 background: '#FFFFFF',
111 foreground: '#000000',
114 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
115 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
116 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
117 '#329262', '#3B3EAC' ],
118 easypiechart_track: '#f0f0f0',
119 easypiechart_scale: '#dfe0e0',
120 gauge_pointer: '#C0C0C0',
121 gauge_stroke: '#F0F0F0',
125 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
126 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
127 background: '#272b30',
128 foreground: '#C8C8C8',
131 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
132 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
133 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
134 '#a6a479', '#a66da8' ],
136 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
137 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
138 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
139 '#329262', '#3B3EFF' ],
140 easypiechart_track: '#373b40',
141 easypiechart_scale: '#373b40',
142 gauge_pointer: '#474b50',
143 gauge_stroke: '#373b40',
144 gauge_gradient: false
148 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
149 NETDATA.themes.current = NETDATA.themes[netdataTheme];
151 NETDATA.themes.current = NETDATA.themes.white;
153 NETDATA.colors = NETDATA.themes.current.colors;
155 // these are the colors Google Charts are using
156 // we have them here to attempt emulate their look and feel on the other chart libraries
157 // http://there4.io/2012/05/02/google-chart-color-list/
158 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
159 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
160 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
162 // an alternative set
163 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
164 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
165 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
167 // ----------------------------------------------------------------------------------------------------------------
168 // the defaults for all charts
170 // if the user does not specify any of these, the following will be used
172 NETDATA.chartDefaults = {
173 host: NETDATA.serverDefault, // the server to get data from
174 width: '100%', // the chart width - can be null
175 height: '100%', // the chart height - can be null
176 min_width: null, // the chart minimum width - can be null
177 library: 'dygraph', // the graphing library to use
178 method: 'average', // the grouping method
179 before: 0, // panning
180 after: -600, // panning
181 pixels_per_point: 1, // the detail of the chart
182 fill_luminance: 0.8 // luminance of colors in solit areas
185 // ----------------------------------------------------------------------------------------------------------------
189 pauseCallback: null, // a callback when we are really paused
191 pause: false, // when enabled we don't auto-refresh the charts
193 targets: null, // an array of all the state objects that are
194 // currently active (independently of their
195 // viewport visibility)
197 updated_dom: true, // when true, the DOM has been updated with
198 // new elements we have to check.
200 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
201 // rendering charts continiously.
202 // used with .current.fast_render_timeframe
204 page_is_visible: true, // when true, this page is visible
206 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
207 // auto-refresher for some time (when a chart is
208 // performing pan or zoom, we need to stop refreshing
209 // all other charts, to have the maximum speed for
210 // rendering the chart that is panned or zoomed).
211 // Used with .current.global_pan_sync_time
213 last_resized: new Date().getTime(), // the timestamp of the last resize request
215 crossDomainAjax: false, // enable this to request crossDomain AJAX
217 last_page_scroll: 0, // the timestamp the last time the page was scrolled
219 // the current profile
220 // we may have many...
222 pixels_per_point: 1, // the minimum pixels per point for all charts
223 // increase this to speed javascript up
224 // each chart library has its own limit too
225 // the max of this and the chart library is used
226 // the final is calculated every time, so a change
227 // here will have immediate effect on the next chart
230 idle_between_charts: 100, // ms - how much time to wait between chart updates
232 fast_render_timeframe: 200, // ms - render continously until this time of continious
233 // rendering has been reached
234 // this setting is used to make it render e.g. 10
235 // charts at once, sleep idle_between_charts time
236 // and continue for another 10 charts.
238 idle_between_loops: 500, // ms - if all charts have been updated, wait this
239 // time before starting again.
241 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
243 idle_lost_focus: 500, // ms - when the window does not have focus, check
244 // if focus has been regained, every this time
246 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
247 // autorefreshing of charts is paused for this amount
250 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
251 // of time before setting up synchronized selections
254 sync_selection: true, // enable or disable selection sync
256 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
258 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
260 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
262 update_only_visible: true, // enable or disable visibility management
264 parallel_refresher: true, // enable parallel refresh of charts
266 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
268 destroy_on_hide: false, // destroy charts when they are not visible
270 show_help: true, // when enabled the charts will show some help
271 show_help_delay_show_ms: 500,
272 show_help_delay_hide_ms: 0,
274 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
276 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
277 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
279 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
281 smooth_plot: true, // enable smooth plot, where possible
283 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
285 color_fill_opacity_line: 1.0,
286 color_fill_opacity_area: 0.2,
287 color_fill_opacity_stacked: 0.8,
289 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
290 pan_and_zoom_factor_multiplier_control: 2.0,
291 pan_and_zoom_factor_multiplier_shift: 3.0,
292 pan_and_zoom_factor_multiplier_alt: 4.0,
294 setOptionCallback: function() { ; }
302 chart_data_url: false,
303 chart_errors: false, // FIXME
312 // ----------------------------------------------------------------------------------------------------------------
313 // local storage options
315 NETDATA.localStorage = {
318 callback: {} // only used for resetting back to defaults
321 NETDATA.localStorageGet = function(key, def, callback) {
324 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
325 NETDATA.localStorage.default[key.toString()] = def;
326 NETDATA.localStorage.callback[key.toString()] = callback;
329 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
331 // console.log('localStorage: loading "' + key.toString() + '"');
332 ret = localStorage.getItem(key.toString());
333 if(ret === null || ret === 'undefined') {
334 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
335 localStorage.setItem(key.toString(), JSON.stringify(def));
339 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
340 ret = JSON.parse(ret);
341 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
345 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
350 if(typeof ret === 'undefined' || ret === 'undefined') {
351 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
355 NETDATA.localStorage.current[key.toString()] = ret;
359 NETDATA.localStorageSet = function(key, value, callback) {
360 if(typeof value === 'undefined' || value === 'undefined') {
361 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
364 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
365 NETDATA.localStorage.default[key.toString()] = value;
366 NETDATA.localStorage.current[key.toString()] = value;
367 NETDATA.localStorage.callback[key.toString()] = callback;
370 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
371 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
373 localStorage.setItem(key.toString(), JSON.stringify(value));
376 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
380 NETDATA.localStorage.current[key.toString()] = value;
384 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
386 if(typeof obj[i] === 'object') {
387 //console.log('object ' + prefix + '.' + i.toString());
388 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
392 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
396 NETDATA.setOption = function(key, value) {
397 if(key.toString() === 'setOptionCallback') {
398 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
399 NETDATA.options.current[key.toString()] = value;
400 NETDATA.options.current.setOptionCallback();
403 else if(NETDATA.options.current[key.toString()] !== value) {
404 var name = 'options.' + key.toString();
406 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
407 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
409 //console.log(NETDATA.localStorage);
410 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
411 //console.log(NETDATA.options);
412 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
414 if(typeof NETDATA.options.current.setOptionCallback === 'function')
415 NETDATA.options.current.setOptionCallback();
421 NETDATA.getOption = function(key) {
422 return NETDATA.options.current[key.toString()];
425 // read settings from local storage
426 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
428 // always start with this option enabled.
429 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
431 NETDATA.resetOptions = function() {
432 for(var i in NETDATA.localStorage.default) {
433 var a = i.split('.');
435 if(a[0] === 'options') {
436 if(a[1] === 'setOptionCallback') continue;
437 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
438 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
440 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
442 else if(a[0] === 'chart_heights') {
443 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
444 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
450 // ----------------------------------------------------------------------------------------------------------------
452 if(NETDATA.options.debug.main_loop === true)
453 console.log('welcome to NETDATA');
455 NETDATA.onresize = function() {
456 NETDATA.options.last_resized = new Date().getTime();
460 NETDATA.onscroll = function() {
461 // console.log('onscroll');
463 NETDATA.options.last_page_scroll = new Date().getTime();
464 if(NETDATA.options.targets === null) return;
466 // when the user scrolls he sees that we have
467 // hidden all the not-visible charts
468 // using this little function we try to switch
469 // the charts back to visible quickly
470 var targets = NETDATA.options.targets;
471 var len = targets.length;
472 while(len--) targets[len].isVisible();
475 window.onresize = NETDATA.onresize;
476 window.onscroll = NETDATA.onscroll;
478 // ----------------------------------------------------------------------------------------------------------------
481 NETDATA.errorCodes = {
482 100: { message: "Cannot load chart library", alert: true },
483 101: { message: "Cannot load jQuery", alert: true },
484 402: { message: "Chart library not found", alert: false },
485 403: { message: "Chart library not enabled/is failed", alert: false },
486 404: { message: "Chart not found", alert: false },
487 405: { message: "Cannot download charts index from server", alert: true },
488 406: { message: "Invalid charts index downloaded from server", alert: true }
490 NETDATA.errorLast = {
496 NETDATA.error = function(code, msg) {
497 NETDATA.errorLast.code = code;
498 NETDATA.errorLast.message = msg;
499 NETDATA.errorLast.datetime = new Date().getTime();
501 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
504 if(typeof netdataErrorCallback === 'function') {
505 ret = netdataErrorCallback('system', code, msg);
508 if(ret && NETDATA.errorCodes[code].alert)
509 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
512 NETDATA.errorReset = function() {
513 NETDATA.errorLast.code = 0;
514 NETDATA.errorLast.message = "You are doing fine!";
515 NETDATA.errorLast.datetime = 0;
518 // ----------------------------------------------------------------------------------------------------------------
521 // When multiple charts need the same chart, we avoid downloading it
522 // multiple times (and having it in browser memory multiple time)
523 // by using this registry.
525 // Every time we download a chart definition, we save it here with .add()
526 // Then we try to get it back with .get(). If that fails, we download it.
528 NETDATA.chartRegistry = {
531 fixid: function(id) {
532 return id.replace(/:/g, "_").replace(/\//g, "_");
535 add: function(host, id, data) {
536 host = this.fixid(host);
539 if(typeof this.charts[host] === 'undefined')
540 this.charts[host] = {};
542 //console.log('added ' + host + '/' + id);
543 this.charts[host][id] = data;
546 get: function(host, id) {
547 host = this.fixid(host);
550 if(typeof this.charts[host] === 'undefined')
553 if(typeof this.charts[host][id] === 'undefined')
556 //console.log('cached ' + host + '/' + id);
557 return this.charts[host][id];
560 downloadAll: function(host, callback) {
561 while(host.slice(-1) === '/')
562 host = host.substring(0, host.length - 1);
567 url: host + '/api/v1/charts',
568 crossDomain: NETDATA.options.crossDomainAjax,
572 .done(function(data) {
574 var h = NETDATA.chartRegistry.fixid(host);
575 self.charts[h] = data.charts;
577 else NETDATA.error(406, host + '/api/v1/charts');
579 if(typeof callback === 'function')
583 NETDATA.error(405, host + '/api/v1/charts');
585 if(typeof callback === 'function')
591 // ----------------------------------------------------------------------------------------------------------------
592 // Global Pan and Zoom on charts
594 // Using this structure are synchronize all the charts, so that
595 // when you pan or zoom one, all others are automatically refreshed
596 // to the same timespan.
598 NETDATA.globalPanAndZoom = {
599 seq: 0, // timestamp ms
600 // every time a chart is panned or zoomed
601 // we set the timestamp here
602 // then we use it as a sequence number
603 // to find if other charts are syncronized
606 master: null, // the master chart (state), to which all others
609 force_before_ms: null, // the timespan to sync all other charts
610 force_after_ms: null,
613 setMaster: function(state, after, before) {
614 if(NETDATA.options.current.sync_pan_and_zoom === false)
617 if(this.master !== null && this.master !== state)
618 this.master.resetChart(true, true);
620 var now = new Date().getTime();
623 this.force_after_ms = after;
624 this.force_before_ms = before;
625 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
629 clearMaster: function() {
630 if(this.master !== null) {
631 var st = this.master;
638 this.force_after_ms = null;
639 this.force_before_ms = null;
640 NETDATA.options.auto_refresher_stop_until = 0;
643 // is the given state the master of the global
644 // pan and zoom sync?
645 isMaster: function(state) {
646 if(this.master === state) return true;
650 // are we currently have a global pan and zoom sync?
651 isActive: function() {
652 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
656 // check if a chart, other than the master
657 // needs to be refreshed, due to the global pan and zoom
658 shouldBeAutoRefreshed: function(state) {
659 if(this.master === null || this.seq === 0)
662 //if(state.needsRecreation())
665 if(state.tm.pan_and_zoom_seq === this.seq)
672 // ----------------------------------------------------------------------------------------------------------------
673 // dimensions selection
676 // move color assignment to dimensions, here
678 dimensionStatus = function(parent, label, name_div, value_div, color) {
679 this.enabled = false;
680 this.parent = parent;
682 this.name_div = null;
683 this.value_div = null;
684 this.color = NETDATA.themes.current.foreground;
686 if(parent.selected_count > parent.unselected_count)
687 this.selected = true;
689 this.selected = false;
691 this.setOptions(name_div, value_div, color);
694 dimensionStatus.prototype.invalidate = function() {
695 this.name_div = null;
696 this.value_div = null;
697 this.enabled = false;
700 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
703 if(this.name_div != name_div) {
704 this.name_div = name_div;
705 this.name_div.title = this.label;
706 this.name_div.style.color = this.color;
707 if(this.selected === false)
708 this.name_div.className = 'netdata-legend-name not-selected';
710 this.name_div.className = 'netdata-legend-name selected';
713 if(this.value_div != value_div) {
714 this.value_div = value_div;
715 this.value_div.title = this.label;
716 this.value_div.style.color = this.color;
717 if(this.selected === false)
718 this.value_div.className = 'netdata-legend-value not-selected';
720 this.value_div.className = 'netdata-legend-value selected';
727 dimensionStatus.prototype.setHandler = function() {
728 if(this.enabled === false) return;
732 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
733 this.name_div.onclick = this.value_div.onclick = function(e) {
735 if(ds.isSelected()) {
737 if(e.shiftKey === true || e.ctrlKey === true) {
738 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
741 if(ds.parent.countSelected() === 0)
742 ds.parent.selectAll();
745 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
746 if(ds.parent.countSelected() === 1) {
747 ds.parent.selectAll();
750 ds.parent.selectNone();
756 // this is not selected
757 if(e.shiftKey === true || e.ctrlKey === true) {
758 // control or shift key is pressed -> select this too
762 // no key is pressed -> select only this
763 ds.parent.selectNone();
768 ds.parent.state.redrawChart();
772 dimensionStatus.prototype.select = function() {
773 if(this.enabled === false) return;
775 this.name_div.className = 'netdata-legend-name selected';
776 this.value_div.className = 'netdata-legend-value selected';
777 this.selected = true;
780 dimensionStatus.prototype.unselect = function() {
781 if(this.enabled === false) return;
783 this.name_div.className = 'netdata-legend-name not-selected';
784 this.value_div.className = 'netdata-legend-value hidden';
785 this.selected = false;
788 dimensionStatus.prototype.isSelected = function() {
789 return(this.enabled === true && this.selected === true);
792 // ----------------------------------------------------------------------------------------------------------------
794 dimensionsVisibility = function(state) {
797 this.dimensions = {};
798 this.selected_count = 0;
799 this.unselected_count = 0;
802 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
803 if(typeof this.dimensions[label] === 'undefined') {
805 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
808 this.dimensions[label].setOptions(name_div, value_div, color);
810 return this.dimensions[label];
813 dimensionsVisibility.prototype.dimensionGet = function(label) {
814 return this.dimensions[label];
817 dimensionsVisibility.prototype.invalidateAll = function() {
818 for(var d in this.dimensions)
819 this.dimensions[d].invalidate();
822 dimensionsVisibility.prototype.selectAll = function() {
823 for(var d in this.dimensions)
824 this.dimensions[d].select();
827 dimensionsVisibility.prototype.countSelected = function() {
829 for(var d in this.dimensions)
830 if(this.dimensions[d].isSelected()) i++;
835 dimensionsVisibility.prototype.selectNone = function() {
836 for(var d in this.dimensions)
837 this.dimensions[d].unselect();
840 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
841 var ret = new Array();
842 this.selected_count = 0;
843 this.unselected_count = 0;
845 for(var i = 0, len = array.length; i < len ; i++) {
846 var ds = this.dimensions[array[i]];
847 if(typeof ds === 'undefined') {
848 // console.log(array[i] + ' is not found');
853 if(ds.isSelected()) {
855 this.selected_count++;
859 this.unselected_count++;
863 if(this.selected_count === 0 && this.unselected_count !== 0) {
865 return this.selected2BooleanArray(array);
872 // ----------------------------------------------------------------------------------------------------------------
873 // global selection sync
875 NETDATA.globalSelectionSync = {
882 if(this.state !== null)
883 this.state.globalSelectionSyncStop();
887 if(this.state !== null) {
888 this.state.globalSelectionSyncDelay();
893 // ----------------------------------------------------------------------------------------------------------------
894 // Our state object, where all per-chart values are stored
896 chartState = function(element) {
897 var self = $(element);
898 this.element = element;
901 // all private functions should use 'that', instead of 'this'
905 * show an error instead of the chart
907 var error = function(msg) {
910 if(typeof netdataErrorCallback === 'function') {
911 ret = netdataErrorCallback('chart', that.id, msg);
915 that.element.innerHTML = that.id + ': ' + msg;
916 that.enabled = false;
917 that.current = that.pan;
921 // GUID - a unique identifier for the chart
922 this.uuid = NETDATA.guid();
924 // string - the name of chart
925 this.id = self.data('netdata');
927 // string - the key for localStorage settings
928 this.settings_id = self.data('id') || null;
930 // the user given dimensions of the element
931 this.width = self.data('width') || NETDATA.chartDefaults.width;
932 this.height = self.data('height') || NETDATA.chartDefaults.height;
934 if(this.settings_id !== null) {
935 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
936 // this is the callback that will be called
937 // if and when the user resets all localStorage variables
940 resizeChartToHeight(height);
944 // string - the netdata server URL, without any path
945 this.host = self.data('host') || NETDATA.chartDefaults.host;
947 // make sure the host does not end with /
948 // all netdata API requests use absolute paths
949 while(this.host.slice(-1) === '/')
950 this.host = this.host.substring(0, this.host.length - 1);
952 // string - the grouping method requested by the user
953 this.method = self.data('method') || NETDATA.chartDefaults.method;
955 // the time-range requested by the user
956 this.after = self.data('after') || NETDATA.chartDefaults.after;
957 this.before = self.data('before') || NETDATA.chartDefaults.before;
959 // the pixels per point requested by the user
960 this.pixels_per_point = self.data('pixels-per-point') || 1;
961 this.points = self.data('points') || null;
963 // the dimensions requested by the user
964 this.dimensions = self.data('dimensions') || null;
966 // the chart library requested by the user
967 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
969 // object - the chart library used
974 this.colors_assigned = {};
975 this.colors_available = null;
977 // the element already created by the user
978 this.element_message = null;
980 // the element with the chart
981 this.element_chart = null;
983 // the element with the legend of the chart (if created by us)
984 this.element_legend = null;
985 this.element_legend_childs = {
995 this.chart_url = null; // string - the url to download chart info
996 this.chart = null; // object - the chart as downloaded from the server
998 this.title = self.data('title') || null; // the title of the chart
999 this.units = self.data('units') || null; // the units of the chart dimensions
1000 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1002 this.validated = false; // boolean - has the chart been validated?
1003 this.enabled = true; // boolean - is the chart enabled for refresh?
1004 this.paused = false; // boolean - is the chart paused for any reason?
1005 this.selected = false; // boolean - is the chart shown a selection?
1006 this.debug = false; // boolean - console.log() debug info about this chart
1008 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1009 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1010 this.requested_after = null; // milliseconds - the timestamp of the request after param
1011 this.requested_before = null; // milliseconds - the timestamp of the request before param
1012 this.requested_padding = null;
1013 this.view_after = 0;
1014 this.view_before = 0;
1019 force_update_at: 0, // the timestamp to force the update at
1020 force_before_ms: null,
1021 force_after_ms: null
1026 force_update_at: 0, // the timestamp to force the update at
1027 force_before_ms: null,
1028 force_after_ms: null
1033 force_update_at: 0, // the timestamp to force the update at
1034 force_before_ms: null,
1035 force_after_ms: null
1038 // this is a pointer to one of the sub-classes below
1040 this.current = this.auto;
1042 // check the requested library is available
1043 // we don't initialize it here - it will be initialized when
1044 // this chart will be first used
1045 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1046 NETDATA.error(402, that.library_name);
1047 error('chart library "' + that.library_name + '" is not found');
1050 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1051 NETDATA.error(403, that.library_name);
1052 error('chart library "' + that.library_name + '" is not enabled');
1056 that.library = NETDATA.chartLibraries[that.library_name];
1058 // milliseconds - the time the last refresh took
1059 this.refresh_dt_ms = 0;
1061 // if we need to report the rendering speed
1062 // find the element that needs to be updated
1063 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1065 if(refresh_dt_element_name !== null)
1066 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1068 this.refresh_dt_element = null;
1070 this.dimensions_visibility = new dimensionsVisibility(this);
1072 this._updating = false;
1074 // ============================================================================================================
1075 // PRIVATE FUNCTIONS
1077 var createDOM = function() {
1078 if(that.enabled === false) return;
1080 if(that.element_message !== null) that.element_message.innerHTML = '';
1081 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1082 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1084 that.element.innerHTML = '';
1086 that.element_message = document.createElement('div');
1087 that.element_message.className = ' netdata-message hidden';
1088 that.element.appendChild(that.element_message);
1090 that.element_chart = document.createElement('div');
1091 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1092 that.element.appendChild(that.element_chart);
1094 if(that.hasLegend() === true) {
1095 that.element.className = "netdata-container-with-legend";
1096 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1098 that.element_legend = document.createElement('div');
1099 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1100 that.element.appendChild(that.element_legend);
1103 that.element.className = "netdata-container";
1104 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1106 that.element_legend = null;
1108 that.element_legend_childs.series = null;
1110 if(typeof(that.width) === 'string')
1111 $(that.element).css('width', that.width);
1112 else if(typeof(that.width) === 'number')
1113 $(that.element).css('width', that.width + 'px');
1115 if(typeof(that.library.aspect_ratio) === 'undefined') {
1116 if(typeof(that.height) === 'string')
1117 $(that.element).css('height', that.height);
1118 else if(typeof(that.height) === 'number')
1119 $(that.element).css('height', that.height + 'px');
1122 var w = that.element.offsetWidth;
1123 if(w === null || w === 0) {
1124 // the div is hidden
1125 // this is resize the chart when next viewed
1126 that.tm.last_resized = 0;
1129 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1132 if(NETDATA.chartDefaults.min_width !== null)
1133 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1135 that.tm.last_dom_created = new Date().getTime();
1141 * initialize state variables
1142 * destroy all (possibly) created state elements
1143 * create the basic DOM for a chart
1145 var init = function() {
1146 if(that.enabled === false) return;
1148 that.paused = false;
1149 that.selected = false;
1151 that.chart_created = false; // boolean - is the library.create() been called?
1152 that.updates_counter = 0; // numeric - the number of refreshes made so far
1153 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1154 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1157 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1158 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1159 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1161 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1162 last_updated: 0, // the timestamp the chart last updated with data
1163 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1165 // Used with NETDATA.globalPanAndZoom.seq
1166 last_visible_check: 0, // the time we last checked if it is visible
1167 last_resized: 0, // the time the chart was resized
1168 last_hidden: 0, // the time the chart was hidden
1169 last_unhidden: 0, // the time the chart was unhidden
1170 last_autorefreshed: 0 // the time the chart was last refreshed
1173 that.data = null; // the last data as downloaded from the netdata server
1174 that.data_url = 'invalid://'; // string - the last url used to update the chart
1175 that.data_points = 0; // number - the number of points returned from netdata
1176 that.data_after = 0; // milliseconds - the first timestamp of the data
1177 that.data_before = 0; // milliseconds - the last timestamp of the data
1178 that.data_update_every = 0; // milliseconds - the frequency to update the data
1180 that.tm.last_initialized = new Date().getTime();
1183 that.setMode('auto');
1186 var maxMessageFontSize = function() {
1187 // normally we want a font size, as tall as the element
1188 var h = that.element_message.clientHeight;
1190 // but give it some air, 20% let's say, or 5 pixels min
1191 var lost = Math.max(h * 0.2, 5);
1194 // center the text, verically
1195 var paddingTop = (lost - 5) / 2;
1197 // but check the width too
1198 // it should fit 10 characters in it
1199 var w = that.element_message.clientWidth / 10;
1201 paddingTop += (h - w) / 2;
1205 // and don't make it too huge
1206 // 5% of the screen size is good
1207 if(h > screen.height / 20) {
1208 paddingTop += (h - (screen.height / 20)) / 2;
1209 h = screen.height / 20;
1213 that.element_message.style.fontSize = h.toString() + 'px';
1214 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1217 var showMessage = function(msg) {
1218 that.element_message.className = 'netdata-message';
1219 that.element_message.innerHTML = msg;
1220 that.element_message.style.fontSize = 'x-small';
1221 that.element_message.style.paddingTop = '0px';
1222 that.___messageHidden___ = undefined;
1225 var showMessageIcon = function(icon) {
1226 that.element_message.innerHTML = icon;
1227 that.element_message.className = 'netdata-message icon';
1228 maxMessageFontSize();
1229 that.___messageHidden___ = undefined;
1232 var hideMessage = function() {
1233 if(typeof that.___messageHidden___ === 'undefined') {
1234 that.___messageHidden___ = true;
1235 that.element_message.className = 'netdata-message hidden';
1239 var showRendering = function() {
1241 if(that.chart !== null) {
1242 if(that.chart.chart_type === 'line')
1243 icon = '<i class="fa fa-line-chart"></i>';
1245 icon = '<i class="fa fa-area-chart"></i>';
1248 icon = '<i class="fa fa-area-chart"></i>';
1250 showMessageIcon(icon + ' netdata');
1253 var showLoading = function() {
1254 if(that.chart_created === false) {
1255 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1261 var isHidden = function() {
1262 if(typeof that.___chartIsHidden___ !== 'undefined')
1268 // hide the chart, when it is not visible - called from isVisible()
1269 var hideChart = function() {
1270 // hide it, if it is not already hidden
1271 if(isHidden() === true) return;
1273 if(that.chart_created === true) {
1274 // we should destroy it
1275 if(NETDATA.options.current.destroy_on_hide === true) {
1280 that.element_chart.style.display = 'none';
1281 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1282 that.tm.last_hidden = new Date().getTime();
1286 that.___chartIsHidden___ = true;
1289 // unhide the chart, when it is visible - called from isVisible()
1290 var unhideChart = function() {
1291 if(isHidden() === false) return;
1293 that.___chartIsHidden___ = undefined;
1294 that.updates_since_last_unhide = 0;
1296 if(that.chart_created === false) {
1297 // we need to re-initialize it, to show our background
1298 // logo in bootstrap tabs, until the chart loads
1302 that.tm.last_unhidden = new Date().getTime();
1303 that.element_chart.style.display = '';
1304 if(that.element_legend !== null) that.element_legend.style.display = '';
1310 var canBeRendered = function() {
1311 if(isHidden() === true || that.isVisible(true) === false)
1317 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1318 var callChartLibraryUpdateSafely = function(data) {
1321 if(canBeRendered() === false)
1324 if(NETDATA.options.debug.chart_errors === true)
1325 status = that.library.update(that, data);
1328 status = that.library.update(that, data);
1335 if(status === false) {
1336 error('chart failed to be updated as ' + that.library_name);
1343 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1344 var callChartLibraryCreateSafely = function(data) {
1347 if(canBeRendered() === false)
1350 if(NETDATA.options.debug.chart_errors === true)
1351 status = that.library.create(that, data);
1354 status = that.library.create(that, data);
1361 if(status === false) {
1362 error('chart failed to be created as ' + that.library_name);
1366 that.chart_created = true;
1367 that.updates_since_last_creation = 0;
1371 // ----------------------------------------------------------------------------------------------------------------
1374 // resizeChart() - private
1375 // to be called just before the chart library to make sure that
1376 // a properly sized dom is available
1377 var resizeChart = function() {
1378 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1379 if(that.chart_created === false) return;
1381 if(that.needsRecreation()) {
1384 else if(typeof that.library.resize === 'function') {
1385 that.library.resize(that);
1387 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1388 $(that.element_legend_childs.nano).nanoScroller();
1390 maxMessageFontSize();
1393 that.tm.last_resized = new Date().getTime();
1397 // this is the actual chart resize algorithm
1399 // - resize the entire container
1400 // - update the internal states
1401 // - resize the chart as the div changes height
1402 // - update the scrollbar of the legend
1403 var resizeChartToHeight = function(h) {
1405 that.element.style.height = h;
1407 if(that.settings_id !== null)
1408 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1410 var now = new Date().getTime();
1411 NETDATA.options.last_page_scroll = now;
1412 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1415 that.tm.last_resized = 0;
1419 this.resizeHandler = function(e) {
1422 if(typeof this.event_resize === 'undefined'
1423 || this.event_resize.chart_original_w === 'undefined'
1424 || this.event_resize.chart_original_h === 'undefined')
1425 this.event_resize = {
1426 chart_original_w: this.element.clientWidth,
1427 chart_original_h: this.element.clientHeight,
1431 if(e.type === 'touchstart') {
1432 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1433 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1436 this.event_resize.mouse_start_x = e.clientX;
1437 this.event_resize.mouse_start_y = e.clientY;
1440 this.event_resize.chart_start_w = this.element.clientWidth;
1441 this.event_resize.chart_start_h = this.element.clientHeight;
1442 this.event_resize.chart_last_w = this.element.clientWidth;
1443 this.event_resize.chart_last_h = this.element.clientHeight;
1445 var now = new Date().getTime();
1446 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1447 // double click / double tap event
1449 // the optimal height of the chart
1450 // showing the entire legend
1451 var optimal = this.event_resize.chart_last_h
1452 + this.element_legend_childs.content.scrollHeight
1453 - this.element_legend_childs.content.clientHeight;
1455 // if we are not optimal, be optimal
1456 if(this.event_resize.chart_last_h != optimal)
1457 resizeChartToHeight(optimal.toString() + 'px');
1459 // else if we do not have the original height
1460 // reset to the original height
1461 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1462 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1465 this.event_resize.last = now;
1467 // process movement event
1468 document.onmousemove =
1469 document.ontouchmove =
1470 this.element_legend_childs.resize_handler.onmousemove =
1471 this.element_legend_childs.resize_handler.ontouchmove =
1476 case 'mousemove': y = e.clientY; break;
1477 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1481 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1483 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1484 resizeChartToHeight(newH.toString() + 'px');
1485 that.event_resize.chart_last_h = newH;
1490 // process end event
1491 document.onmouseup =
1492 document.ontouchend =
1493 this.element_legend_childs.resize_handler.onmouseup =
1494 this.element_legend_childs.resize_handler.ontouchend =
1496 // remove all the hooks
1497 document.onmouseup =
1498 document.onmousemove =
1499 document.ontouchmove =
1500 document.ontouchend =
1501 that.element_legend_childs.resize_handler.onmousemove =
1502 that.element_legend_childs.resize_handler.ontouchmove =
1503 that.element_legend_childs.resize_handler.onmouseout =
1504 that.element_legend_childs.resize_handler.onmouseup =
1505 that.element_legend_childs.resize_handler.ontouchend =
1508 // allow auto-refreshes
1509 NETDATA.options.auto_refresher_stop_until = 0;
1515 var noDataToShow = function() {
1516 showMessageIcon('<i class="fa fa-warning"></i> empty');
1517 that.legendUpdateDOM();
1518 that.tm.last_autorefreshed = new Date().getTime();
1519 // that.data_update_every = 30 * 1000;
1520 //that.element_chart.style.display = 'none';
1521 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1522 //that.___chartIsHidden___ = true;
1525 // ============================================================================================================
1528 this.error = function(msg) {
1532 this.setMode = function(m) {
1533 if(this.current !== null && this.current.name === m) return;
1536 this.current = this.auto;
1537 else if(m === 'pan')
1538 this.current = this.pan;
1539 else if(m === 'zoom')
1540 this.current = this.zoom;
1542 this.current = this.auto;
1544 this.current.force_update_at = 0;
1545 this.current.force_before_ms = null;
1546 this.current.force_after_ms = null;
1548 this.tm.last_mode_switch = new Date().getTime();
1551 // ----------------------------------------------------------------------------------------------------------------
1552 // global selection sync
1554 // prevent to global selection sync for some time
1555 this.globalSelectionSyncDelay = function(ms) {
1556 if(NETDATA.options.current.sync_selection === false)
1559 if(typeof ms === 'number')
1560 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1562 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1565 // can we globally apply selection sync?
1566 this.globalSelectionSyncAbility = function() {
1567 if(NETDATA.options.current.sync_selection === false)
1570 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1576 this.globalSelectionSyncIsMaster = function() {
1577 if(NETDATA.globalSelectionSync.state === this)
1583 // this chart is the master of the global selection sync
1584 this.globalSelectionSyncBeMaster = function() {
1586 if(this.globalSelectionSyncIsMaster()) {
1587 if(this.debug === true)
1588 this.log('sync: I am the master already.');
1593 if(NETDATA.globalSelectionSync.state) {
1594 if(this.debug === true)
1595 this.log('sync: I am not the sync master. Resetting global sync.');
1597 this.globalSelectionSyncStop();
1600 // become the master
1601 if(this.debug === true)
1602 this.log('sync: becoming sync master.');
1604 this.selected = true;
1605 NETDATA.globalSelectionSync.state = this;
1607 // find the all slaves
1608 var targets = NETDATA.options.targets;
1609 var len = targets.length;
1614 if(this.debug === true)
1615 st.log('sync: not adding me to sync');
1617 else if(st.globalSelectionSyncIsEligible()) {
1618 if(this.debug === true)
1619 st.log('sync: adding to sync as slave');
1621 st.globalSelectionSyncBeSlave();
1625 // this.globalSelectionSyncDelay(100);
1628 // can the chart participate to the global selection sync as a slave?
1629 this.globalSelectionSyncIsEligible = function() {
1630 if(this.enabled === true
1631 && this.library !== null
1632 && typeof this.library.setSelection === 'function'
1633 && this.isVisible() === true
1634 && this.chart_created === true)
1640 // this chart becomes a slave of the global selection sync
1641 this.globalSelectionSyncBeSlave = function() {
1642 if(NETDATA.globalSelectionSync.state !== this)
1643 NETDATA.globalSelectionSync.slaves.push(this);
1646 // sync all the visible charts to the given time
1647 // this is to be called from the chart libraries
1648 this.globalSelectionSync = function(t) {
1649 if(this.globalSelectionSyncAbility() === false) {
1650 if(this.debug === true)
1651 this.log('sync: cannot sync (yet?).');
1656 if(this.globalSelectionSyncIsMaster() === false) {
1657 if(this.debug === true)
1658 this.log('sync: trying to be sync master.');
1660 this.globalSelectionSyncBeMaster();
1662 if(this.globalSelectionSyncAbility() === false) {
1663 if(this.debug === true)
1664 this.log('sync: cannot sync (yet?).');
1670 NETDATA.globalSelectionSync.last_t = t;
1671 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1676 // stop syncing all charts to the given time
1677 this.globalSelectionSyncStop = function() {
1678 if(NETDATA.globalSelectionSync.slaves.length) {
1679 if(this.debug === true)
1680 this.log('sync: cleaning up...');
1682 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1684 if(that.debug === true)
1685 st.log('sync: not adding me to sync stop');
1688 if(that.debug === true)
1689 st.log('sync: removed slave from sync');
1691 st.clearSelection();
1695 NETDATA.globalSelectionSync.last_t = 0;
1696 NETDATA.globalSelectionSync.slaves = [];
1697 NETDATA.globalSelectionSync.state = null;
1700 this.clearSelection();
1703 this.setSelection = function(t) {
1704 if(typeof this.library.setSelection === 'function') {
1705 if(this.library.setSelection(this, t) === true)
1706 this.selected = true;
1708 this.selected = false;
1710 else this.selected = true;
1712 if(this.selected === true && this.debug === true)
1713 this.log('selection set to ' + t.toString());
1715 return this.selected;
1718 this.clearSelection = function() {
1719 if(this.selected === true) {
1720 if(typeof this.library.clearSelection === 'function') {
1721 if(this.library.clearSelection(this) === true)
1722 this.selected = false;
1724 this.selected = true;
1726 else this.selected = false;
1728 if(this.selected === false && this.debug === true)
1729 this.log('selection cleared');
1734 return this.selected;
1737 // find if a timestamp (ms) is shown in the current chart
1738 this.timeIsVisible = function(t) {
1739 if(t >= this.data_after && t <= this.data_before)
1744 this.calculateRowForTime = function(t) {
1745 if(this.timeIsVisible(t) === false) return -1;
1746 return Math.floor((t - this.data_after) / this.data_update_every);
1749 // ----------------------------------------------------------------------------------------------------------------
1752 this.log = function(msg) {
1753 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1756 this.pauseChart = function() {
1757 if(this.paused === false) {
1758 if(this.debug === true)
1759 this.log('pauseChart()');
1765 this.unpauseChart = function() {
1766 if(this.paused === true) {
1767 if(this.debug === true)
1768 this.log('unpauseChart()');
1770 this.paused = false;
1774 this.resetChart = function(dont_clear_master, dont_update) {
1775 if(this.debug === true)
1776 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1778 if(typeof dont_clear_master === 'undefined')
1779 dont_clear_master = false;
1781 if(typeof dont_update === 'undefined')
1782 dont_update = false;
1784 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1785 if(this.debug === true)
1786 this.log('resetChart() diverting to clearMaster().');
1787 // this will call us back with master === true
1788 NETDATA.globalPanAndZoom.clearMaster();
1792 this.clearSelection();
1794 this.tm.pan_and_zoom_seq = 0;
1796 this.setMode('auto');
1797 this.current.force_update_at = 0;
1798 this.current.force_before_ms = null;
1799 this.current.force_after_ms = null;
1800 this.tm.last_autorefreshed = 0;
1801 this.paused = false;
1802 this.selected = false;
1803 this.enabled = true;
1804 // this.debug = false;
1806 // do not update the chart here
1807 // or the chart will flip-flop when it is the master
1808 // of a selection sync and another chart becomes
1811 if(dont_update !== true && this.isVisible() === true) {
1816 this.updateChartPanOrZoom = function(after, before) {
1817 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1820 if(this.debug === true)
1823 if(before < after) {
1824 if(this.debug === true)
1825 this.log(logme + 'flipped parameters, rejecting it.');
1830 if(typeof this.fixed_min_duration === 'undefined')
1831 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1833 var min_duration = this.fixed_min_duration;
1834 var current_duration = Math.round(this.view_before - this.view_after);
1836 // round the numbers
1837 after = Math.round(after);
1838 before = Math.round(before);
1840 // align them to update_every
1841 // stretching them further away
1842 after -= after % this.data_update_every;
1843 before += this.data_update_every - (before % this.data_update_every);
1845 // the final wanted duration
1846 var wanted_duration = before - after;
1848 // to allow panning, accept just a point below our minimum
1849 if((current_duration - this.data_update_every) < min_duration)
1850 min_duration = current_duration - this.data_update_every;
1852 // we do it, but we adjust to minimum size and return false
1853 // when the wanted size is below the current and the minimum
1855 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1856 if(this.debug === true)
1857 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1859 min_duration = this.fixed_min_duration;
1861 var dt = (min_duration - wanted_duration) / 2;
1864 wanted_duration = before - after;
1868 var tolerance = this.data_update_every * 2;
1869 var movement = Math.abs(before - this.view_before);
1871 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1872 if(this.debug === true)
1873 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);
1877 if(this.current.name === 'auto') {
1878 this.log(logme + 'caller called me with mode: ' + this.current.name);
1879 this.setMode('pan');
1882 if(this.debug === true)
1883 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);
1885 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1886 this.current.force_after_ms = after;
1887 this.current.force_before_ms = before;
1888 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1892 this.legendFormatValue = function(value) {
1893 if(value === null || value === 'undefined') return '-';
1894 if(typeof value !== 'number') return value;
1896 var abs = Math.abs(value);
1897 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1898 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1899 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1900 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1901 return (Math.round(value * 10000) / 10000).toLocaleString();
1904 this.legendSetLabelValue = function(label, value) {
1905 var series = this.element_legend_childs.series[label];
1906 if(typeof series === 'undefined') return;
1907 if(series.value === null && series.user === null) return;
1909 // if the value has not changed, skip DOM update
1910 //if(series.last === value) return;
1913 if(typeof value === 'number') {
1914 var v = Math.abs(value);
1915 s = r = this.legendFormatValue(value);
1917 if(typeof series.last === 'number') {
1918 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1919 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1920 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1922 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1927 series.last = value;
1930 if(series.value !== null) series.value.innerHTML = s;
1931 if(series.user !== null) series.user.innerHTML = r;
1934 this.legendSetDate = function(ms) {
1935 if(typeof ms !== 'number') {
1936 this.legendShowUndefined();
1940 var d = new Date(ms);
1942 if(this.element_legend_childs.title_date)
1943 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1945 if(this.element_legend_childs.title_time)
1946 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1948 if(this.element_legend_childs.title_units)
1949 this.element_legend_childs.title_units.innerHTML = this.units;
1952 this.legendShowUndefined = function() {
1953 if(this.element_legend_childs.title_date)
1954 this.element_legend_childs.title_date.innerHTML = ' ';
1956 if(this.element_legend_childs.title_time)
1957 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1959 if(this.element_legend_childs.title_units)
1960 this.element_legend_childs.title_units.innerHTML = ' ';
1962 if(this.data && this.element_legend_childs.series !== null) {
1963 var labels = this.data.dimension_names;
1964 var i = labels.length;
1966 var label = labels[i];
1968 if(typeof label === 'undefined') continue;
1969 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1970 this.legendSetLabelValue(label, null);
1975 this.legendShowLatestValues = function() {
1976 if(this.chart === null) return;
1977 if(this.selected) return;
1979 if(this.data === null || this.element_legend_childs.series === null) {
1980 this.legendShowUndefined();
1984 var show_undefined = true;
1985 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1986 show_undefined = false;
1988 if(show_undefined) {
1989 this.legendShowUndefined();
1993 this.legendSetDate(this.view_before);
1995 var labels = this.data.dimension_names;
1996 var i = labels.length;
1998 var label = labels[i];
2000 if(typeof label === 'undefined') continue;
2001 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2004 this.legendSetLabelValue(label, null);
2006 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2010 this.legendReset = function() {
2011 this.legendShowLatestValues();
2014 // this should be called just ONCE per dimension per chart
2015 this._chartDimensionColor = function(label) {
2016 if(this.colors === null) this.chartColors();
2018 if(typeof this.colors_assigned[label] === 'undefined') {
2019 if(this.colors_available.length === 0) {
2020 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2021 this.colors_available.push(NETDATA.themes.current.colors[i]);
2024 this.colors_assigned[label] = this.colors_available.shift();
2026 if(this.debug === true)
2027 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2030 if(this.debug === true)
2031 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2034 this.colors.push(this.colors_assigned[label]);
2035 return this.colors_assigned[label];
2038 this.chartColors = function() {
2039 if(this.colors !== null) return this.colors;
2041 this.colors = new Array();
2042 this.colors_available = new Array();
2045 var c = $(this.element).data('colors');
2046 // this.log('read colors: ' + c);
2047 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2048 if(typeof c !== 'string') {
2049 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2056 for(i = 0, len = c.length; i < len ; i++) {
2058 this.colors_available.push(c[i]);
2059 // this.log('adding color: ' + c[i]);
2065 // push all the standard colors too
2066 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2067 this.colors_available.push(NETDATA.themes.current.colors[i]);
2072 this.legendUpdateDOM = function() {
2075 // check that the legend DOM is up to date for the downloaded dimensions
2076 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2077 // this.log('the legend does not have any series - requesting legend update');
2080 else if(this.data === null) {
2081 // this.log('the chart does not have any data - requesting legend update');
2084 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2088 var labels = this.data.dimension_names.toString();
2089 if(labels !== this.element_legend_childs.series.labels_key) {
2092 if(this.debug === true)
2093 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2097 if(needed === false) {
2098 // make sure colors available
2101 // do we have to update the current values?
2102 // we do this, only when the visible chart is current
2103 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2104 if(this.debug === true)
2105 this.log('chart is in latest position... updating values on legend...');
2107 //var labels = this.data.dimension_names;
2108 //var i = labels.length;
2110 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2114 if(this.colors === null) {
2115 // this is the first time we update the chart
2116 // let's assign colors to all dimensions
2117 if(this.library.track_colors() === true)
2118 for(var dim in this.chart.dimensions)
2119 this._chartDimensionColor(this.chart.dimensions[dim].name);
2121 // we will re-generate the colors for the chart
2122 // based on the selected dimensions
2125 if(this.debug === true)
2126 this.log('updating Legend DOM');
2128 // mark all dimensions as invalid
2129 this.dimensions_visibility.invalidateAll();
2131 var genLabel = function(state, parent, name, count) {
2132 var color = state._chartDimensionColor(name);
2134 var user_element = null;
2135 var user_id = self.data('show-value-of-' + name + '-at') || null;
2136 if(user_id !== null) {
2137 user_element = document.getElementById(user_id) || null;
2138 if(user_element === null)
2139 state.log('Cannot find element with id: ' + user_id);
2142 state.element_legend_childs.series[name] = {
2143 name: document.createElement('span'),
2144 value: document.createElement('span'),
2149 var label = state.element_legend_childs.series[name];
2151 // create the dimension visibility tracking for this label
2152 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2154 var rgb = NETDATA.colorHex2Rgb(color);
2155 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2156 + state.chart.chart_type
2157 + '" style="background-color: '
2158 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2159 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2161 var text = document.createTextNode(' ' + name);
2162 label.name.appendChild(text);
2165 parent.appendChild(document.createElement('br'));
2167 parent.appendChild(label.name);
2168 parent.appendChild(label.value);
2171 var content = document.createElement('div');
2173 if(this.hasLegend()) {
2174 this.element_legend_childs = {
2176 resize_handler: document.createElement('div'),
2177 toolbox: document.createElement('div'),
2178 toolbox_left: document.createElement('div'),
2179 toolbox_right: document.createElement('div'),
2180 toolbox_reset: document.createElement('div'),
2181 toolbox_zoomin: document.createElement('div'),
2182 toolbox_zoomout: document.createElement('div'),
2183 toolbox_volume: document.createElement('div'),
2184 title_date: document.createElement('span'),
2185 title_time: document.createElement('span'),
2186 title_units: document.createElement('span'),
2187 nano: document.createElement('div'),
2189 paneClass: 'netdata-legend-series-pane',
2190 sliderClass: 'netdata-legend-series-slider',
2191 contentClass: 'netdata-legend-series-content',
2192 enabledClass: '__enabled',
2193 flashedClass: '__flashed',
2194 activeClass: '__active',
2196 alwaysVisible: true,
2202 this.element_legend.innerHTML = '';
2204 if(this.library.toolboxPanAndZoom !== null) {
2206 function get_pan_and_zoom_step(event) {
2208 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2210 else if (event.shiftKey)
2211 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2213 else if (event.altKey)
2214 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2217 return NETDATA.options.current.pan_and_zoom_factor;
2220 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2221 this.element.appendChild(this.element_legend_childs.toolbox);
2223 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2224 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2225 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2226 this.element_legend_childs.toolbox_left.onclick = function(e) {
2229 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2230 var before = that.view_before - step;
2231 var after = that.view_after - step;
2232 if(after >= that.netdata_first)
2233 that.library.toolboxPanAndZoom(that, after, before);
2235 if(NETDATA.options.current.show_help === true)
2236 $(this.element_legend_childs.toolbox_left).popover({
2241 placement: 'bottom',
2242 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2244 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>'
2248 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2249 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2250 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2251 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2253 NETDATA.resetAllCharts(that);
2255 if(NETDATA.options.current.show_help === true)
2256 $(this.element_legend_childs.toolbox_reset).popover({
2261 placement: 'bottom',
2262 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2263 title: 'Chart Reset',
2264 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>'
2267 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2268 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2269 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2270 this.element_legend_childs.toolbox_right.onclick = function(e) {
2272 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2273 var before = that.view_before + step;
2274 var after = that.view_after + step;
2275 if(before <= that.netdata_last)
2276 that.library.toolboxPanAndZoom(that, after, before);
2278 if(NETDATA.options.current.show_help === true)
2279 $(this.element_legend_childs.toolbox_right).popover({
2284 placement: 'bottom',
2285 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2287 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>'
2291 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2292 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2293 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2294 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2296 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2297 var before = that.view_before - dt;
2298 var after = that.view_after + dt;
2299 that.library.toolboxPanAndZoom(that, after, before);
2301 if(NETDATA.options.current.show_help === true)
2302 $(this.element_legend_childs.toolbox_zoomin).popover({
2307 placement: 'bottom',
2308 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2309 title: 'Chart Zoom In',
2310 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>'
2313 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2314 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2315 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2316 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2318 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);
2319 var before = that.view_before + dt;
2320 var after = that.view_after - dt;
2322 that.library.toolboxPanAndZoom(that, after, before);
2324 if(NETDATA.options.current.show_help === true)
2325 $(this.element_legend_childs.toolbox_zoomout).popover({
2330 placement: 'bottom',
2331 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2332 title: 'Chart Zoom Out',
2333 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>'
2336 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2337 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2338 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2339 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2340 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2341 //e.preventDefault();
2342 //alert('clicked toolbox_volume on ' + that.id);
2346 this.element_legend_childs.toolbox = null;
2347 this.element_legend_childs.toolbox_left = null;
2348 this.element_legend_childs.toolbox_reset = null;
2349 this.element_legend_childs.toolbox_right = null;
2350 this.element_legend_childs.toolbox_zoomin = null;
2351 this.element_legend_childs.toolbox_zoomout = null;
2352 this.element_legend_childs.toolbox_volume = null;
2355 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2356 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2357 this.element.appendChild(this.element_legend_childs.resize_handler);
2358 if(NETDATA.options.current.show_help === true)
2359 $(this.element_legend_childs.resize_handler).popover({
2364 placement: 'bottom',
2365 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2366 title: 'Chart Resize',
2367 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>'
2371 this.element_legend_childs.resize_handler.onmousedown =
2373 that.resizeHandler(e);
2377 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2378 that.resizeHandler(e);
2381 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2382 this.element_legend.appendChild(this.element_legend_childs.title_date);
2384 this.element_legend.appendChild(document.createElement('br'));
2386 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2387 this.element_legend.appendChild(this.element_legend_childs.title_time);
2389 this.element_legend.appendChild(document.createElement('br'));
2391 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2392 this.element_legend.appendChild(this.element_legend_childs.title_units);
2394 this.element_legend.appendChild(document.createElement('br'));
2396 this.element_legend_childs.nano.className = 'netdata-legend-series';
2397 this.element_legend.appendChild(this.element_legend_childs.nano);
2399 content.className = 'netdata-legend-series-content';
2400 this.element_legend_childs.nano.appendChild(content);
2402 if(NETDATA.options.current.show_help === true)
2403 $(content).popover({
2408 placement: 'bottom',
2409 title: 'Chart Legend',
2410 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2411 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>'
2415 this.element_legend_childs = {
2417 resize_handler: null,
2420 toolbox_right: null,
2421 toolbox_reset: null,
2422 toolbox_zoomin: null,
2423 toolbox_zoomout: null,
2424 toolbox_volume: null,
2435 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2436 if(this.debug === true)
2437 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2439 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2440 genLabel(this, content, this.data.dimension_names[i], i);
2444 var tmp = new Array();
2445 for(var dim in this.chart.dimensions) {
2446 tmp.push(this.chart.dimensions[dim].name);
2447 genLabel(this, content, this.chart.dimensions[dim].name, i);
2449 this.element_legend_childs.series.labels_key = tmp.toString();
2450 if(this.debug === true)
2451 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2454 // create a hidden div to be used for hidding
2455 // the original legend of the chart library
2456 var el = document.createElement('div');
2457 if(this.element_legend !== null)
2458 this.element_legend.appendChild(el);
2459 el.style.display = 'none';
2461 this.element_legend_childs.hidden = document.createElement('div');
2462 el.appendChild(this.element_legend_childs.hidden);
2464 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2465 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2467 this.legendShowLatestValues();
2470 this.hasLegend = function() {
2471 if(typeof this.___hasLegendCache___ !== 'undefined')
2472 return this.___hasLegendCache___;
2475 if(this.library && this.library.legend(this) === 'right-side') {
2476 var legend = $(this.element).data('legend') || 'yes';
2477 if(legend === 'yes') leg = true;
2480 this.___hasLegendCache___ = leg;
2484 this.legendWidth = function() {
2485 return (this.hasLegend())?140:0;
2488 this.legendHeight = function() {
2489 return $(this.element).height();
2492 this.chartWidth = function() {
2493 return $(this.element).width() - this.legendWidth();
2496 this.chartHeight = function() {
2497 return $(this.element).height();
2500 this.chartPixelsPerPoint = function() {
2501 // force an options provided detail
2502 var px = this.pixels_per_point;
2504 if(this.library && px < this.library.pixels_per_point(this))
2505 px = this.library.pixels_per_point(this);
2507 if(px < NETDATA.options.current.pixels_per_point)
2508 px = NETDATA.options.current.pixels_per_point;
2513 this.needsRecreation = function() {
2515 this.chart_created === true
2517 && this.library.autoresize() === false
2518 && this.tm.last_resized < NETDATA.options.last_resized
2522 this.chartURL = function() {
2523 var after, before, points_multiplier = 1;
2524 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2525 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2527 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2528 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2529 this.view_after = after * 1000;
2530 this.view_before = before * 1000;
2532 this.requested_padding = null;
2533 points_multiplier = 1;
2535 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2536 this.tm.pan_and_zoom_seq = 0;
2538 before = Math.round(this.current.force_before_ms / 1000);
2539 after = Math.round(this.current.force_after_ms / 1000);
2540 this.view_after = after * 1000;
2541 this.view_before = before * 1000;
2543 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2544 this.requested_padding = Math.round((before - after) / 2);
2545 after -= this.requested_padding;
2546 before += this.requested_padding;
2547 this.requested_padding *= 1000;
2548 points_multiplier = 2;
2551 this.current.force_before_ms = null;
2552 this.current.force_after_ms = null;
2555 this.tm.pan_and_zoom_seq = 0;
2557 before = this.before;
2559 this.view_after = after * 1000;
2560 this.view_before = before * 1000;
2562 this.requested_padding = null;
2563 points_multiplier = 1;
2566 this.requested_after = after * 1000;
2567 this.requested_before = before * 1000;
2569 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2571 // build the data URL
2572 this.data_url = this.host + this.chart.data_url;
2573 this.data_url += "&format=" + this.library.format();
2574 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2575 this.data_url += "&group=" + this.method;
2576 this.data_url += "&options=" + this.library.options(this);
2577 this.data_url += '|jsonwrap';
2579 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2580 this.data_url += '|nonzero';
2582 if(this.append_options !== null)
2583 this.data_url += '|' + this.append_options.toString();
2586 this.data_url += "&after=" + after.toString();
2589 this.data_url += "&before=" + before.toString();
2592 this.data_url += "&dimensions=" + this.dimensions;
2594 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2595 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2598 this.redrawChart = function() {
2599 if(this.data !== null)
2600 this.updateChartWithData(this.data);
2603 this.updateChartWithData = function(data) {
2604 if(this.debug === true)
2605 this.log('updateChartWithData() called.');
2607 this._updating = false;
2609 // this may force the chart to be re-created
2613 this.updates_counter++;
2614 this.updates_since_last_unhide++;
2615 this.updates_since_last_creation++;
2617 var started = new Date().getTime();
2619 // if the result is JSON, find the latest update-every
2620 this.data_update_every = data.view_update_every * 1000;
2621 this.data_after = data.after * 1000;
2622 this.data_before = data.before * 1000;
2623 this.netdata_first = data.first_entry * 1000;
2624 this.netdata_last = data.last_entry * 1000;
2625 this.data_points = data.points;
2628 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2629 if(this.view_after < this.data_after) {
2630 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2631 this.view_after = this.data_after;
2634 if(this.view_before > this.data_before) {
2635 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2636 this.view_before = this.data_before;
2640 this.view_after = this.data_after;
2641 this.view_before = this.data_before;
2644 if(this.debug === true) {
2645 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2647 if(this.current.force_after_ms)
2648 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2650 this.log('STATUS: forced : unset');
2652 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2653 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2654 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2655 this.log('STATUS: points : ' + (this.data_points).toString());
2658 if(this.data_points === 0) {
2663 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2664 if(this.debug === true)
2665 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2667 this.chart_created = false;
2670 // check and update the legend
2671 this.legendUpdateDOM();
2673 if(this.chart_created === true
2674 && typeof this.library.update === 'function') {
2676 if(this.debug === true)
2677 this.log('updating chart...');
2679 if(callChartLibraryUpdateSafely(data) === false)
2683 if(this.debug === true)
2684 this.log('creating chart...');
2686 if(callChartLibraryCreateSafely(data) === false)
2690 this.legendShowLatestValues();
2691 if(this.selected === true)
2692 NETDATA.globalSelectionSync.stop();
2694 // update the performance counters
2695 var now = new Date().getTime();
2696 this.tm.last_updated = now;
2698 // don't update last_autorefreshed if this chart is
2699 // forced to be updated with global PanAndZoom
2700 if(NETDATA.globalPanAndZoom.isActive())
2701 this.tm.last_autorefreshed = 0;
2703 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2704 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2706 this.tm.last_autorefreshed = now;
2709 this.refresh_dt_ms = now - started;
2710 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2712 if(this.refresh_dt_element !== null)
2713 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2716 this.updateChart = function(callback) {
2717 if(this.debug === true)
2718 this.log('updateChart() called.');
2720 if(this._updating === true) {
2721 if(this.debug === true)
2722 this.log('I am already updating...');
2724 if(typeof callback === 'function') callback();
2728 // due to late initialization of charts and libraries
2729 // we need to check this too
2730 if(this.enabled === false) {
2731 if(this.debug === true)
2732 this.log('I am not enabled');
2734 if(typeof callback === 'function') callback();
2738 if(canBeRendered() === false) {
2739 if(typeof callback === 'function') callback();
2743 if(this.chart === null) {
2744 this.getChart(function() { that.updateChart(callback); });
2748 if(this.library.initialized === false) {
2749 if(this.library.enabled === true) {
2750 this.library.initialize(function() { that.updateChart(callback); });
2754 error('chart library "' + this.library_name + '" is not available.');
2755 if(typeof callback === 'function') callback();
2760 this.clearSelection();
2763 if(this.debug === true)
2764 this.log('updating from ' + this.data_url);
2766 this._updating = true;
2768 this.xhr = $.ajax( {
2770 crossDomain: NETDATA.options.crossDomainAjax,
2774 .success(function(data) {
2775 if(that.debug === true)
2776 that.log('data received. updating chart.');
2778 that.updateChartWithData(data);
2781 error('data download failed for url: ' + that.data_url);
2783 .always(function() {
2784 that._updating = false;
2785 if(typeof callback === 'function') callback();
2791 this.isVisible = function(nocache) {
2792 if(typeof nocache === 'undefined')
2795 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2797 // caching - we do not evaluate the charts visibility
2798 // if the page has not been scrolled since the last check
2799 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2800 return this.___isVisible___;
2802 this.tm.last_visible_check = new Date().getTime();
2804 var wh = window.innerHeight;
2805 var x = this.element.getBoundingClientRect();
2809 if(x.width === 0 || x.height === 0) {
2811 this.___isVisible___ = false;
2812 return this.___isVisible___;
2815 if(x.top < 0 && -x.top > x.height) {
2816 // the chart is entirely above
2817 ret = -x.top - x.height;
2819 else if(x.top > wh) {
2820 // the chart is entirely below
2824 if(ret > tolerance) {
2825 // the chart is too far
2828 this.___isVisible___ = false;
2829 return this.___isVisible___;
2832 // the chart is inside or very close
2835 this.___isVisible___ = true;
2836 return this.___isVisible___;
2840 this.isAutoRefreshed = function() {
2841 return (this.current.autorefresh);
2844 this.canBeAutoRefreshed = function() {
2845 var now = new Date().getTime();
2847 if(this.enabled === false) {
2848 if(this.debug === true)
2849 this.log('I am not enabled');
2854 if(this.library === null || this.library.enabled === false) {
2855 error('charting library "' + this.library_name + '" is not available');
2856 if(this.debug === true)
2857 this.log('My chart library ' + this.library_name + ' is not available');
2862 if(this.isVisible() === false) {
2863 if(NETDATA.options.debug.visibility === true || this.debug === true)
2864 this.log('I am not visible');
2869 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2870 if(this.debug === true)
2871 this.log('timed force update detected - allowing this update');
2873 this.current.force_update_at = 0;
2877 if(this.isAutoRefreshed() === true) {
2878 // allow the first update, even if the page is not visible
2879 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2880 if(NETDATA.options.debug.focus === true || this.debug === true)
2881 this.log('canBeAutoRefreshed(): page does not have focus');
2886 if(this.needsRecreation() === true) {
2887 if(this.debug === true)
2888 this.log('canBeAutoRefreshed(): needs re-creation.');
2893 // options valid only for autoRefresh()
2894 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2895 if(NETDATA.globalPanAndZoom.isActive()) {
2896 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2897 if(this.debug === true)
2898 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2903 if(this.debug === true)
2904 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2910 if(this.selected === true) {
2911 if(this.debug === true)
2912 this.log('canBeAutoRefreshed(): I have a selection in place.');
2917 if(this.paused === true) {
2918 if(this.debug === true)
2919 this.log('canBeAutoRefreshed(): I am paused.');
2924 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2925 if(this.debug === true)
2926 this.log('canBeAutoRefreshed(): It is time to update me.');
2936 this.autoRefresh = function(callback) {
2937 if(this.canBeAutoRefreshed() === true) {
2938 this.updateChart(callback);
2941 if(typeof callback !== 'undefined')
2946 this._defaultsFromDownloadedChart = function(chart) {
2948 this.chart_url = chart.url;
2949 this.data_update_every = chart.update_every * 1000;
2950 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2951 this.tm.last_info_downloaded = new Date().getTime();
2953 if(this.title === null)
2954 this.title = chart.title;
2956 if(this.units === null)
2957 this.units = chart.units;
2960 // fetch the chart description from the netdata server
2961 this.getChart = function(callback) {
2962 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2964 this._defaultsFromDownloadedChart(this.chart);
2965 if(typeof callback === 'function') callback();
2968 this.chart_url = "/api/v1/chart?chart=" + this.id;
2970 if(this.debug === true)
2971 this.log('downloading ' + this.chart_url);
2974 url: this.host + this.chart_url,
2975 crossDomain: NETDATA.options.crossDomainAjax,
2979 .done(function(chart) {
2980 chart.url = that.chart_url;
2981 that._defaultsFromDownloadedChart(chart);
2982 NETDATA.chartRegistry.add(that.host, that.id, chart);
2985 NETDATA.error(404, that.chart_url);
2986 error('chart not found on url "' + that.chart_url + '"');
2988 .always(function() {
2989 if(typeof callback === 'function') callback();
2994 // ============================================================================================================
3000 NETDATA.resetAllCharts = function(state) {
3001 // first clear the global selection sync
3002 // to make sure no chart is in selected state
3003 state.globalSelectionSyncStop();
3005 // there are 2 possibilities here
3006 // a. state is the global Pan and Zoom master
3007 // b. state is not the global Pan and Zoom master
3009 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3012 // clear the global Pan and Zoom
3013 // this will also refresh the master
3014 // and unblock any charts currently mirroring the master
3015 NETDATA.globalPanAndZoom.clearMaster();
3017 // if we were not the master, reset our status too
3018 // this is required because most probably the mouse
3019 // is over this chart, blocking it from auto-refreshing
3020 if(master === false && (state.paused === true || state.selected === true))
3024 // get or create a chart state, given a DOM element
3025 NETDATA.chartState = function(element) {
3026 var state = $(element).data('netdata-state-object') || null;
3027 if(state === null) {
3028 state = new chartState(element);
3029 $(element).data('netdata-state-object', state);
3034 // ----------------------------------------------------------------------------------------------------------------
3035 // Library functions
3037 // Load a script without jquery
3038 // This is used to load jquery - after it is loaded, we use jquery
3039 NETDATA._loadjQuery = function(callback) {
3040 if(typeof jQuery === 'undefined') {
3041 if(NETDATA.options.debug.main_loop === true)
3042 console.log('loading ' + NETDATA.jQuery);
3044 var script = document.createElement('script');
3045 script.type = 'text/javascript';
3046 script.async = true;
3047 script.src = NETDATA.jQuery;
3049 // script.onabort = onError;
3050 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3051 if(typeof callback === "function")
3052 script.onload = callback;
3054 var s = document.getElementsByTagName('script')[0];
3055 s.parentNode.insertBefore(script, s);
3057 else if(typeof callback === "function")
3061 NETDATA._loadCSS = function(filename) {
3062 // don't use jQuery here
3063 // styles are loaded before jQuery
3064 // to eliminate showing an unstyled page to the user
3066 var fileref = document.createElement("link");
3067 fileref.setAttribute("rel", "stylesheet");
3068 fileref.setAttribute("type", "text/css");
3069 fileref.setAttribute("href", filename);
3071 if (typeof fileref !== 'undefined')
3072 document.getElementsByTagName("head")[0].appendChild(fileref);
3075 NETDATA.colorHex2Rgb = function(hex) {
3076 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3077 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3078 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3079 return r + r + g + g + b + b;
3082 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3084 r: parseInt(result[1], 16),
3085 g: parseInt(result[2], 16),
3086 b: parseInt(result[3], 16)
3090 NETDATA.colorLuminance = function(hex, lum) {
3091 // validate hex string
3092 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3094 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3098 // convert to decimal and change luminosity
3099 var rgb = "#", c, i;
3100 for (i = 0; i < 3; i++) {
3101 c = parseInt(hex.substr(i*2,2), 16);
3102 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3103 rgb += ("00"+c).substr(c.length);
3109 NETDATA.guid = function() {
3111 return Math.floor((1 + Math.random()) * 0x10000)
3116 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3119 NETDATA.zeropad = function(x) {
3120 if(x > -10 && x < 10) return '0' + x.toString();
3121 else return x.toString();
3124 // user function to signal us the DOM has been
3126 NETDATA.updatedDom = function() {
3127 NETDATA.options.updated_dom = true;
3130 NETDATA.ready = function(callback) {
3131 NETDATA.options.pauseCallback = callback;
3134 NETDATA.pause = function(callback) {
3135 if(NETDATA.options.pause === true)
3138 NETDATA.options.pauseCallback = callback;
3141 NETDATA.unpause = function() {
3142 NETDATA.options.pauseCallback = null;
3143 NETDATA.options.updated_dom = true;
3144 NETDATA.options.pause = false;
3147 // ----------------------------------------------------------------------------------------------------------------
3149 // this is purely sequencial charts refresher
3150 // it is meant to be autonomous
3151 NETDATA.chartRefresherNoParallel = function(index) {
3152 if(NETDATA.options.debug.mail_loop === true)
3153 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3155 if(NETDATA.options.updated_dom === true) {
3156 // the dom has been updated
3157 // get the dom parts again
3158 NETDATA.parseDom(NETDATA.chartRefresher);
3161 if(index >= NETDATA.options.targets.length) {
3162 if(NETDATA.options.debug.main_loop === true)
3163 console.log('waiting to restart main loop...');
3165 NETDATA.options.auto_refresher_fast_weight = 0;
3167 setTimeout(function() {
3168 NETDATA.chartRefresher();
3169 }, NETDATA.options.current.idle_between_loops);
3172 var state = NETDATA.options.targets[index];
3174 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3175 if(NETDATA.options.debug.main_loop === true)
3176 console.log('fast rendering...');
3178 state.autoRefresh(function() {
3179 NETDATA.chartRefresherNoParallel(++index);
3183 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3184 NETDATA.options.auto_refresher_fast_weight = 0;
3186 setTimeout(function() {
3187 state.autoRefresh(function() {
3188 NETDATA.chartRefresherNoParallel(++index);
3190 }, NETDATA.options.current.idle_between_charts);
3195 // this is part of the parallel refresher
3196 // its cause is to refresh sequencially all the charts
3197 // that depend on chart library initialization
3198 // it will call the parallel refresher back
3199 // as soon as it sees a chart that its chart library
3201 NETDATA.chartRefresher_uninitialized = function() {
3202 if(NETDATA.options.updated_dom === true) {
3203 // the dom has been updated
3204 // get the dom parts again
3205 NETDATA.parseDom(NETDATA.chartRefresher);
3209 if(NETDATA.options.sequencial.length === 0)
3210 NETDATA.chartRefresher();
3212 var state = NETDATA.options.sequencial.pop();
3213 if(state.library.initialized === true)
3214 NETDATA.chartRefresher();
3216 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3220 NETDATA.chartRefresherWaitTime = function() {
3221 return NETDATA.options.current.idle_parallel_loops;
3224 // the default refresher
3225 // it will create 2 sets of charts:
3226 // - the ones that can be refreshed in parallel
3227 // - the ones that depend on something else
3228 // the first set will be executed in parallel
3229 // the second will be given to NETDATA.chartRefresher_uninitialized()
3230 NETDATA.chartRefresher = function() {
3231 if(NETDATA.options.pause === true) {
3232 // console.log('auto-refresher is paused');
3233 setTimeout(NETDATA.chartRefresher,
3234 NETDATA.chartRefresherWaitTime());
3238 if(typeof NETDATA.options.pauseCallback === 'function') {
3239 // console.log('auto-refresher is calling pauseCallback');
3240 NETDATA.options.pause = true;
3241 NETDATA.options.pauseCallback();
3242 NETDATA.chartRefresher();
3246 if(NETDATA.options.current.parallel_refresher === false) {
3247 NETDATA.chartRefresherNoParallel(0);
3251 if(NETDATA.options.updated_dom === true) {
3252 // the dom has been updated
3253 // get the dom parts again
3254 NETDATA.parseDom(NETDATA.chartRefresher);
3258 var parallel = new Array();
3259 var targets = NETDATA.options.targets;
3260 var len = targets.length;
3262 if(targets[len].isVisible() === false)
3265 var state = targets[len];
3266 if(state.library.initialized === false) {
3267 if(state.library.enabled === true) {
3268 state.library.initialize(NETDATA.chartRefresher);
3272 state.error('chart library "' + state.library_name + '" is not enabled.');
3276 parallel.unshift(state);
3279 if(parallel.length > 0) {
3280 var parallel_jobs = parallel.length;
3282 // this will execute the jobs in parallel
3283 $(parallel).each(function() {
3284 this.autoRefresh(function() {
3287 if(parallel_jobs === 0) {
3288 setTimeout(NETDATA.chartRefresher,
3289 NETDATA.chartRefresherWaitTime());
3295 setTimeout(NETDATA.chartRefresher,
3296 NETDATA.chartRefresherWaitTime());
3300 NETDATA.parseDom = function(callback) {
3301 NETDATA.options.last_page_scroll = new Date().getTime();
3302 NETDATA.options.updated_dom = false;
3304 var targets = $('div[data-netdata]'); //.filter(':visible');
3306 if(NETDATA.options.debug.main_loop === true)
3307 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3309 NETDATA.options.targets = new Array();
3310 var len = targets.length;
3312 // the initialization will take care of sizing
3313 // and the "loading..." message
3314 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3317 if(typeof callback === 'function') callback();
3320 // this is the main function - where everything starts
3321 NETDATA.start = function() {
3322 // this should be called only once
3324 NETDATA.options.page_is_visible = true;
3326 $(window).blur(function() {
3327 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3328 NETDATA.options.page_is_visible = false;
3329 if(NETDATA.options.debug.focus === true)
3330 console.log('Lost Focus!');
3334 $(window).focus(function() {
3335 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3336 NETDATA.options.page_is_visible = true;
3337 if(NETDATA.options.debug.focus === true)
3338 console.log('Focus restored!');
3342 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3343 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3344 NETDATA.options.page_is_visible = false;
3345 if(NETDATA.options.debug.focus === true)
3346 console.log('Document has no focus!');
3350 // bootstrap tab switching
3351 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3353 // bootstrap modal switching
3354 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3355 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3357 NETDATA.parseDom(NETDATA.chartRefresher);
3360 // ----------------------------------------------------------------------------------------------------------------
3363 NETDATA.peityInitialize = function(callback) {
3364 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3366 url: NETDATA.peity_js,
3371 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3374 NETDATA.chartLibraries.peity.enabled = false;
3375 NETDATA.error(100, NETDATA.peity_js);
3377 .always(function() {
3378 if(typeof callback === "function")
3383 NETDATA.chartLibraries.peity.enabled = false;
3384 if(typeof callback === "function")
3389 NETDATA.peityChartUpdate = function(state, data) {
3390 state.peity_instance.innerHTML = data.result;
3392 if(state.peity_options.stroke !== state.chartColors()[0]) {
3393 state.peity_options.stroke = state.chartColors()[0];
3394 if(state.chart.chart_type === 'line')
3395 state.peity_options.fill = NETDATA.themes.current.background;
3397 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3400 $(state.peity_instance).peity('line', state.peity_options);
3404 NETDATA.peityChartCreate = function(state, data) {
3405 state.peity_instance = document.createElement('div');
3406 state.element_chart.appendChild(state.peity_instance);
3408 var self = $(state.element);
3409 state.peity_options = {
3410 stroke: NETDATA.themes.current.foreground,
3411 strokeWidth: self.data('peity-strokewidth') || 1,
3412 width: state.chartWidth(),
3413 height: state.chartHeight(),
3414 fill: NETDATA.themes.current.foreground
3417 NETDATA.peityChartUpdate(state, data);
3421 // ----------------------------------------------------------------------------------------------------------------
3424 NETDATA.sparklineInitialize = function(callback) {
3425 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3427 url: NETDATA.sparkline_js,
3432 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3435 NETDATA.chartLibraries.sparkline.enabled = false;
3436 NETDATA.error(100, NETDATA.sparkline_js);
3438 .always(function() {
3439 if(typeof callback === "function")
3444 NETDATA.chartLibraries.sparkline.enabled = false;
3445 if(typeof callback === "function")
3450 NETDATA.sparklineChartUpdate = function(state, data) {
3451 state.sparkline_options.width = state.chartWidth();
3452 state.sparkline_options.height = state.chartHeight();
3454 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3458 NETDATA.sparklineChartCreate = function(state, data) {
3459 var self = $(state.element);
3460 var type = self.data('sparkline-type') || 'line';
3461 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3462 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3463 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3464 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3465 var composite = self.data('sparkline-composite') || undefined;
3466 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3467 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3468 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3469 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3470 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3471 var spotColor = self.data('sparkline-spotcolor') || undefined;
3472 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3473 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3474 var spotRadius = self.data('sparkline-spotradius') || undefined;
3475 var valueSpots = self.data('sparkline-valuespots') || undefined;
3476 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3477 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3478 var lineWidth = self.data('sparkline-linewidth') || undefined;
3479 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3480 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3481 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3482 var xvalues = self.data('sparkline-xvalues') || undefined;
3483 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3484 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3485 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3486 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3487 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3488 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3489 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3490 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3491 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3492 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3493 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3494 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3495 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3496 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3497 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3498 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3499 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3500 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3501 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3502 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3503 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3504 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3506 state.sparkline_options = {
3508 lineColor: lineColor,
3509 fillColor: fillColor,
3510 chartRangeMin: chartRangeMin,
3511 chartRangeMax: chartRangeMax,
3512 composite: composite,
3513 enableTagOptions: enableTagOptions,
3514 tagOptionPrefix: tagOptionPrefix,
3515 tagValuesAttribute: tagValuesAttribute,
3516 disableHiddenCheck: disableHiddenCheck,
3517 defaultPixelsPerValue: defaultPixelsPerValue,
3518 spotColor: spotColor,
3519 minSpotColor: minSpotColor,
3520 maxSpotColor: maxSpotColor,
3521 spotRadius: spotRadius,
3522 valueSpots: valueSpots,
3523 highlightSpotColor: highlightSpotColor,
3524 highlightLineColor: highlightLineColor,
3525 lineWidth: lineWidth,
3526 normalRangeMin: normalRangeMin,
3527 normalRangeMax: normalRangeMax,
3528 drawNormalOnTop: drawNormalOnTop,
3530 chartRangeClip: chartRangeClip,
3531 chartRangeMinX: chartRangeMinX,
3532 chartRangeMaxX: chartRangeMaxX,
3533 disableInteraction: disableInteraction,
3534 disableTooltips: disableTooltips,
3535 disableHighlight: disableHighlight,
3536 highlightLighten: highlightLighten,
3537 highlightColor: highlightColor,
3538 tooltipContainer: tooltipContainer,
3539 tooltipClassname: tooltipClassname,
3540 tooltipChartTitle: state.title,
3541 tooltipFormat: tooltipFormat,
3542 tooltipPrefix: tooltipPrefix,
3543 tooltipSuffix: tooltipSuffix,
3544 tooltipSkipNull: tooltipSkipNull,
3545 tooltipValueLookups: tooltipValueLookups,
3546 tooltipFormatFieldlist: tooltipFormatFieldlist,
3547 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3548 numberFormatter: numberFormatter,
3549 numberDigitGroupSep: numberDigitGroupSep,
3550 numberDecimalMark: numberDecimalMark,
3551 numberDigitGroupCount: numberDigitGroupCount,
3552 animatedZooms: animatedZooms,
3553 width: state.chartWidth(),
3554 height: state.chartHeight()
3557 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3561 // ----------------------------------------------------------------------------------------------------------------
3568 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3569 if(after < state.netdata_first)
3570 after = state.netdata_first;
3572 if(before > state.netdata_last)
3573 before = state.netdata_last;
3575 state.setMode('zoom');
3576 state.globalSelectionSyncStop();
3577 state.globalSelectionSyncDelay();
3578 state.dygraph_user_action = true;
3579 state.dygraph_force_zoom = true;
3580 state.updateChartPanOrZoom(after, before);
3581 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3584 NETDATA.dygraphSetSelection = function(state, t) {
3585 if(typeof state.dygraph_instance !== 'undefined') {
3586 var r = state.calculateRowForTime(t);
3588 state.dygraph_instance.setSelection(r);
3590 state.dygraph_instance.clearSelection();
3591 state.legendShowUndefined();
3598 NETDATA.dygraphClearSelection = function(state, t) {
3599 if(typeof state.dygraph_instance !== 'undefined') {
3600 state.dygraph_instance.clearSelection();
3605 NETDATA.dygraphSmoothInitialize = function(callback) {
3607 url: NETDATA.dygraph_smooth_js,
3612 NETDATA.dygraph.smooth = true;
3613 smoothPlotter.smoothing = 0.3;
3616 NETDATA.dygraph.smooth = false;
3618 .always(function() {
3619 if(typeof callback === "function")
3624 NETDATA.dygraphInitialize = function(callback) {
3625 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3627 url: NETDATA.dygraph_js,
3632 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3635 NETDATA.chartLibraries.dygraph.enabled = false;
3636 NETDATA.error(100, NETDATA.dygraph_js);
3638 .always(function() {
3639 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3640 NETDATA.dygraphSmoothInitialize(callback);
3641 else if(typeof callback === "function")
3646 NETDATA.chartLibraries.dygraph.enabled = false;
3647 if(typeof callback === "function")
3652 NETDATA.dygraphChartUpdate = function(state, data) {
3653 var dygraph = state.dygraph_instance;
3655 if(typeof dygraph === 'undefined')
3656 return NETDATA.dygraphChartCreate(state, data);
3658 // when the chart is not visible, and hidden
3659 // if there is a window resize, dygraph detects
3660 // its element size as 0x0.
3661 // this will make it re-appear properly
3663 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3667 file: data.result.data,
3668 colors: state.chartColors(),
3669 labels: data.result.labels,
3670 labelsDivWidth: state.chartWidth() - 70,
3671 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3674 if(state.dygraph_force_zoom === true) {
3675 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3676 state.log('dygraphChartUpdate() forced zoom update');
3678 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3679 options.valueRange = null;
3680 options.isZoomedIgnoreProgrammaticZoom = true;
3681 state.dygraph_force_zoom = false;
3683 else if(state.current.name !== 'auto') {
3684 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3685 state.log('dygraphChartUpdate() loose update');
3688 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3689 state.log('dygraphChartUpdate() strict update');
3691 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3692 options.valueRange = null;
3693 options.isZoomedIgnoreProgrammaticZoom = true;
3696 if(state.dygraph_smooth_eligible === true) {
3697 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3698 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3699 NETDATA.dygraphChartCreate(state, data);
3704 dygraph.updateOptions(options);
3706 state.dygraph_last_rendered = new Date().getTime();
3710 NETDATA.dygraphChartCreate = function(state, data) {
3711 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3712 state.log('dygraphChartCreate()');
3714 var self = $(state.element);
3716 var chart_type = state.chart.chart_type;
3717 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3718 chart_type = self.data('dygraph-type') || chart_type;
3720 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3721 smooth = self.data('dygraph-smooth') || smooth;
3723 if(NETDATA.dygraph.smooth === false)
3726 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3727 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3729 state.dygraph_options = {
3730 colors: self.data('dygraph-colors') || state.chartColors(),
3732 // leave a few pixels empty on the right of the chart
3733 rightGap: self.data('dygraph-rightgap') || 5,
3734 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3735 showRoller: self.data('dygraph-showroller') || false,
3737 title: self.data('dygraph-title') || state.title,
3738 titleHeight: self.data('dygraph-titleheight') || 19,
3740 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3741 labels: data.result.labels,
3742 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3743 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3744 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3745 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3746 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3749 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3750 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3752 ylabel: state.units,
3753 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3755 // the function to plot the chart
3758 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3759 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3760 strokePattern: self.data('dygraph-strokepattern') || undefined,
3762 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3763 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3764 drawPoints: self.data('dygraph-drawpoints') || false,
3766 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3767 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3769 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3770 pointSize: self.data('dygraph-pointsize') || 1,
3772 // enabling this makes the chart with little square lines
3773 stepPlot: self.data('dygraph-stepplot') || false,
3775 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3776 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3777 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3779 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3780 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3781 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3782 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3784 drawAxis: self.data('dygraph-drawaxis') || true,
3785 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3786 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3787 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3789 drawGrid: self.data('dygraph-drawgrid') || true,
3790 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3791 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3792 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3793 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3794 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3796 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3797 sigFigs: self.data('dygraph-sigfigs') || null,
3798 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3799 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3801 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3802 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3803 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3805 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3806 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3810 ticker: Dygraph.dateTicker,
3811 axisLabelFormatter: function (d, gran) {
3812 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3814 valueFormatter: function (ms) {
3815 var d = new Date(ms);
3816 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3817 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3822 valueFormatter: function (x) {
3823 // we format legends with the state object
3824 // no need to do anything here
3825 // return (Math.round(x*100) / 100).toLocaleString();
3826 // return state.legendFormatValue(x);
3831 legendFormatter: function(data) {
3832 var elements = state.element_legend_childs;
3834 // if the hidden div is not there
3835 // we are not managing the legend
3836 if(elements.hidden === null) return;
3838 if (typeof data.x !== 'undefined') {
3839 state.legendSetDate(data.x);
3840 var i = data.series.length;
3842 var series = data.series[i];
3843 if(!series.isVisible) continue;
3844 state.legendSetLabelValue(series.label, series.y);
3850 drawCallback: function(dygraph, is_initial) {
3851 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3852 state.dygraph_user_action = false;
3854 var x_range = dygraph.xAxisRange();
3855 var after = Math.round(x_range[0]);
3856 var before = Math.round(x_range[1]);
3858 if(NETDATA.options.debug.dygraph === true)
3859 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3861 if(before <= state.netdata_last && after >= state.netdata_first)
3862 state.updateChartPanOrZoom(after, before);
3865 zoomCallback: function(minDate, maxDate, yRanges) {
3866 if(NETDATA.options.debug.dygraph === true)
3867 state.log('dygraphZoomCallback()');
3869 state.globalSelectionSyncStop();
3870 state.globalSelectionSyncDelay();
3871 state.setMode('zoom');
3873 // refresh it to the greatest possible zoom level
3874 state.dygraph_user_action = true;
3875 state.dygraph_force_zoom = true;
3876 state.updateChartPanOrZoom(minDate, maxDate);
3878 highlightCallback: function(event, x, points, row, seriesName) {
3879 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3880 state.log('dygraphHighlightCallback()');
3884 // there is a bug in dygraph when the chart is zoomed enough
3885 // the time it thinks is selected is wrong
3886 // here we calculate the time t based on the row number selected
3888 var t = state.data_after + row * state.data_update_every;
3889 // 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);
3891 state.globalSelectionSync(x);
3893 // fix legend zIndex using the internal structures of dygraph legend module
3894 // this works, but it is a hack!
3895 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3897 unhighlightCallback: function(event) {
3898 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3899 state.log('dygraphUnhighlightCallback()');
3901 state.unpauseChart();
3902 state.globalSelectionSyncStop();
3904 interactionModel : {
3905 mousedown: function(event, dygraph, context) {
3906 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3907 state.log('interactionModel.mousedown()');
3909 state.dygraph_user_action = true;
3910 state.globalSelectionSyncStop();
3912 if(NETDATA.options.debug.dygraph === true)
3913 state.log('dygraphMouseDown()');
3915 // Right-click should not initiate a zoom.
3916 if(event.button && event.button === 2) return;
3918 context.initializeMouseDown(event, dygraph, context);
3920 if(event.button && event.button === 1) {
3921 if (event.altKey || event.shiftKey) {
3922 state.setMode('pan');
3923 state.globalSelectionSyncDelay();
3924 Dygraph.startPan(event, dygraph, context);
3927 state.setMode('zoom');
3928 state.globalSelectionSyncDelay();
3929 Dygraph.startZoom(event, dygraph, context);
3933 if (event.altKey || event.shiftKey) {
3934 state.setMode('zoom');
3935 state.globalSelectionSyncDelay();
3936 Dygraph.startZoom(event, dygraph, context);
3939 state.setMode('pan');
3940 state.globalSelectionSyncDelay();
3941 Dygraph.startPan(event, dygraph, context);
3945 mousemove: function(event, dygraph, context) {
3946 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3947 state.log('interactionModel.mousemove()');
3949 if(context.isPanning) {
3950 state.dygraph_user_action = true;
3951 state.globalSelectionSyncStop();
3952 state.globalSelectionSyncDelay();
3953 state.setMode('pan');
3954 Dygraph.movePan(event, dygraph, context);
3956 else if(context.isZooming) {
3957 state.dygraph_user_action = true;
3958 state.globalSelectionSyncStop();
3959 state.globalSelectionSyncDelay();
3960 state.setMode('zoom');
3961 Dygraph.moveZoom(event, dygraph, context);
3964 mouseup: function(event, dygraph, context) {
3965 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3966 state.log('interactionModel.mouseup()');
3968 if (context.isPanning) {
3969 state.dygraph_user_action = true;
3970 state.globalSelectionSyncDelay();
3971 Dygraph.endPan(event, dygraph, context);
3973 else if (context.isZooming) {
3974 state.dygraph_user_action = true;
3975 state.globalSelectionSyncDelay();
3976 Dygraph.endZoom(event, dygraph, context);
3979 click: function(event, dygraph, context) {
3980 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3981 state.log('interactionModel.click()');
3983 event.preventDefault();
3985 dblclick: function(event, dygraph, context) {
3986 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3987 state.log('interactionModel.dblclick()');
3988 NETDATA.resetAllCharts(state);
3990 mousewheel: function(event, dygraph, context) {
3991 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3992 state.log('interactionModel.mousewheel()');
3994 // Take the offset of a mouse event on the dygraph canvas and
3995 // convert it to a pair of percentages from the bottom left.
3996 // (Not top left, bottom is where the lower value is.)
3997 function offsetToPercentage(g, offsetX, offsetY) {
3998 // This is calculating the pixel offset of the leftmost date.
3999 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4000 var yar0 = g.yAxisRange(0);
4002 // This is calculating the pixel of the higest value. (Top pixel)
4003 var yOffset = g.toDomCoords(null, yar0[1])[1];
4005 // x y w and h are relative to the corner of the drawing area,
4006 // so that the upper corner of the drawing area is (0, 0).
4007 var x = offsetX - xOffset;
4008 var y = offsetY - yOffset;
4010 // This is computing the rightmost pixel, effectively defining the
4012 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4014 // This is computing the lowest pixel, effectively defining the height.
4015 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4017 // Percentage from the left.
4018 var xPct = w === 0 ? 0 : (x / w);
4019 // Percentage from the top.
4020 var yPct = h === 0 ? 0 : (y / h);
4022 // The (1-) part below changes it from "% distance down from the top"
4023 // to "% distance up from the bottom".
4024 return [xPct, (1-yPct)];
4027 // Adjusts [x, y] toward each other by zoomInPercentage%
4028 // Split it so the left/bottom axis gets xBias/yBias of that change and
4029 // tight/top gets (1-xBias)/(1-yBias) of that change.
4031 // If a bias is missing it splits it down the middle.
4032 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4033 xBias = xBias || 0.5;
4034 yBias = yBias || 0.5;
4036 function adjustAxis(axis, zoomInPercentage, bias) {
4037 var delta = axis[1] - axis[0];
4038 var increment = delta * zoomInPercentage;
4039 var foo = [increment * bias, increment * (1-bias)];
4041 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4044 var yAxes = g.yAxisRanges();
4046 for (var i = 0; i < yAxes.length; i++) {
4047 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4050 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4053 if(event.altKey || event.shiftKey) {
4054 state.dygraph_user_action = true;
4056 state.globalSelectionSyncStop();
4057 state.globalSelectionSyncDelay();
4059 // http://dygraphs.com/gallery/interaction-api.js
4060 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4061 var percentage = normal / 50;
4063 if (!(event.offsetX && event.offsetY)){
4064 event.offsetX = event.layerX - event.target.offsetLeft;
4065 event.offsetY = event.layerY - event.target.offsetTop;
4068 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4069 var xPct = percentages[0];
4070 var yPct = percentages[1];
4072 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4074 var after = new_x_range[0];
4075 var before = new_x_range[1];
4077 var first = state.netdata_first + state.data_update_every;
4078 var last = state.netdata_last + state.data_update_every;
4081 after -= (before - last);
4088 state.setMode('zoom');
4089 if(state.updateChartPanOrZoom(after, before) === true)
4090 dygraph.updateOptions({ dateWindow: [ after, before ] });
4092 event.preventDefault();
4095 touchstart: function(event, dygraph, context) {
4096 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4097 state.log('interactionModel.touchstart()');
4099 state.dygraph_user_action = true;
4100 state.setMode('zoom');
4103 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4105 // we overwrite the touch directions at the end, to overwrite
4106 // the internal default of dygraphs
4107 context.touchDirections = { x: true, y: false };
4109 state.dygraph_last_touch_start = new Date().getTime();
4110 state.dygraph_last_touch_move = 0;
4112 if(typeof event.touches[0].pageX === 'number')
4113 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4115 state.dygraph_last_touch_page_x = 0;
4117 touchmove: function(event, dygraph, context) {
4118 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4119 state.log('interactionModel.touchmove()');
4121 state.dygraph_user_action = true;
4122 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4124 state.dygraph_last_touch_move = new Date().getTime();
4126 touchend: function(event, dygraph, context) {
4127 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4128 state.log('interactionModel.touchend()');
4130 state.dygraph_user_action = true;
4131 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4133 // if it didn't move, it is a selection
4134 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4135 // internal api of dygraphs
4136 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4137 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4138 if(NETDATA.dygraphSetSelection(state, t) === true)
4139 state.globalSelectionSync(t);
4142 // if it was double tap within double click time, reset the charts
4143 var now = new Date().getTime();
4144 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4145 if(state.dygraph_last_touch_move === 0) {
4146 var dt = now - state.dygraph_last_touch_end;
4147 if(dt <= NETDATA.options.current.double_click_speed)
4148 NETDATA.resetAllCharts(state);
4152 // remember the timestamp of the last touch end
4153 state.dygraph_last_touch_end = now;
4158 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4159 state.dygraph_options.drawGrid = false;
4160 state.dygraph_options.drawAxis = false;
4161 state.dygraph_options.title = undefined;
4162 state.dygraph_options.units = undefined;
4163 state.dygraph_options.ylabel = undefined;
4164 state.dygraph_options.yLabelWidth = 0;
4165 state.dygraph_options.labelsDivWidth = 120;
4166 state.dygraph_options.labelsDivStyles.width = '120px';
4167 state.dygraph_options.labelsSeparateLines = true;
4168 state.dygraph_options.rightGap = 0;
4171 if(smooth === true) {
4172 state.dygraph_smooth_eligible = true;
4174 if(NETDATA.options.current.smooth_plot === true)
4175 state.dygraph_options.plotter = smoothPlotter;
4177 else state.dygraph_smooth_eligible = false;
4179 state.dygraph_instance = new Dygraph(state.element_chart,
4180 data.result.data, state.dygraph_options);
4182 state.dygraph_force_zoom = false;
4183 state.dygraph_user_action = false;
4184 state.dygraph_last_rendered = new Date().getTime();
4188 // ----------------------------------------------------------------------------------------------------------------
4191 NETDATA.morrisInitialize = function(callback) {
4192 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4194 // morris requires raphael
4195 if(!NETDATA.chartLibraries.raphael.initialized) {
4196 if(NETDATA.chartLibraries.raphael.enabled) {
4197 NETDATA.raphaelInitialize(function() {
4198 NETDATA.morrisInitialize(callback);
4202 NETDATA.chartLibraries.morris.enabled = false;
4203 if(typeof callback === "function")
4208 NETDATA._loadCSS(NETDATA.morris_css);
4211 url: NETDATA.morris_js,
4216 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4219 NETDATA.chartLibraries.morris.enabled = false;
4220 NETDATA.error(100, NETDATA.morris_js);
4222 .always(function() {
4223 if(typeof callback === "function")
4229 NETDATA.chartLibraries.morris.enabled = false;
4230 if(typeof callback === "function")
4235 NETDATA.morrisChartUpdate = function(state, data) {
4236 state.morris_instance.setData(data.result.data);
4240 NETDATA.morrisChartCreate = function(state, data) {
4242 state.morris_options = {
4243 element: state.element_chart.id,
4244 data: data.result.data,
4246 ykeys: data.dimension_names,
4247 labels: data.dimension_names,
4253 continuousLine: false,
4254 behaveLikeLine: false
4257 if(state.chart.chart_type === 'line')
4258 state.morris_instance = new Morris.Line(state.morris_options);
4260 else if(state.chart.chart_type === 'area') {
4261 state.morris_options.behaveLikeLine = true;
4262 state.morris_instance = new Morris.Area(state.morris_options);
4265 state.morris_instance = new Morris.Area(state.morris_options);
4270 // ----------------------------------------------------------------------------------------------------------------
4273 NETDATA.raphaelInitialize = function(callback) {
4274 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4276 url: NETDATA.raphael_js,
4281 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4284 NETDATA.chartLibraries.raphael.enabled = false;
4285 NETDATA.error(100, NETDATA.raphael_js);
4287 .always(function() {
4288 if(typeof callback === "function")
4293 NETDATA.chartLibraries.raphael.enabled = false;
4294 if(typeof callback === "function")
4299 NETDATA.raphaelChartUpdate = function(state, data) {
4300 $(state.element_chart).raphael(data.result, {
4301 width: state.chartWidth(),
4302 height: state.chartHeight()
4308 NETDATA.raphaelChartCreate = function(state, data) {
4309 $(state.element_chart).raphael(data.result, {
4310 width: state.chartWidth(),
4311 height: state.chartHeight()
4317 // ----------------------------------------------------------------------------------------------------------------
4320 NETDATA.c3Initialize = function(callback) {
4321 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4324 if(!NETDATA.chartLibraries.d3.initialized) {
4325 if(NETDATA.chartLibraries.d3.enabled) {
4326 NETDATA.d3Initialize(function() {
4327 NETDATA.c3Initialize(callback);
4331 NETDATA.chartLibraries.c3.enabled = false;
4332 if(typeof callback === "function")
4337 NETDATA._loadCSS(NETDATA.c3_css);
4345 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4348 NETDATA.chartLibraries.c3.enabled = false;
4349 NETDATA.error(100, NETDATA.c3_js);
4351 .always(function() {
4352 if(typeof callback === "function")
4358 NETDATA.chartLibraries.c3.enabled = false;
4359 if(typeof callback === "function")
4364 NETDATA.c3ChartUpdate = function(state, data) {
4365 state.c3_instance.destroy();
4366 return NETDATA.c3ChartCreate(state, data);
4368 //state.c3_instance.load({
4369 // rows: data.result,
4376 NETDATA.c3ChartCreate = function(state, data) {
4378 state.element_chart.id = 'c3-' + state.uuid;
4379 // console.log('id = ' + state.element_chart.id);
4381 state.c3_instance = c3.generate({
4382 bindto: '#' + state.element_chart.id,
4384 width: state.chartWidth(),
4385 height: state.chartHeight()
4388 pattern: state.chartColors()
4393 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4399 format: function(x) {
4400 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4427 // console.log(state.c3_instance);
4432 // ----------------------------------------------------------------------------------------------------------------
4435 NETDATA.d3Initialize = function(callback) {
4436 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4443 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4446 NETDATA.chartLibraries.d3.enabled = false;
4447 NETDATA.error(100, NETDATA.d3_js);
4449 .always(function() {
4450 if(typeof callback === "function")
4455 NETDATA.chartLibraries.d3.enabled = false;
4456 if(typeof callback === "function")
4461 NETDATA.d3ChartUpdate = function(state, data) {
4465 NETDATA.d3ChartCreate = function(state, data) {
4469 // ----------------------------------------------------------------------------------------------------------------
4472 NETDATA.googleInitialize = function(callback) {
4473 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4475 url: NETDATA.google_js,
4480 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4481 google.load('visualization', '1.1', {
4482 'packages': ['corechart', 'controls'],
4483 'callback': callback
4487 NETDATA.chartLibraries.google.enabled = false;
4488 NETDATA.error(100, NETDATA.google_js);
4489 if(typeof callback === "function")
4494 NETDATA.chartLibraries.google.enabled = false;
4495 if(typeof callback === "function")
4500 NETDATA.googleChartUpdate = function(state, data) {
4501 var datatable = new google.visualization.DataTable(data.result);
4502 state.google_instance.draw(datatable, state.google_options);
4506 NETDATA.googleChartCreate = function(state, data) {
4507 var datatable = new google.visualization.DataTable(data.result);
4509 state.google_options = {
4510 colors: state.chartColors(),
4512 // do not set width, height - the chart resizes itself
4513 //width: state.chartWidth(),
4514 //height: state.chartHeight(),
4519 // title: "Time of Day",
4520 // format:'HH:mm:ss',
4521 viewWindowMode: 'maximized',
4533 viewWindowMode: 'pretty',
4548 focusTarget: 'category',
4555 titlePosition: 'out',
4566 curveType: 'function',
4571 switch(state.chart.chart_type) {
4573 state.google_options.vAxis.viewWindowMode = 'maximized';
4574 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4575 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4579 state.google_options.isStacked = true;
4580 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4581 state.google_options.vAxis.viewWindowMode = 'maximized';
4582 state.google_options.vAxis.minValue = null;
4583 state.google_options.vAxis.maxValue = null;
4584 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4589 state.google_options.lineWidth = 2;
4590 state.google_instance = new google.visualization.LineChart(state.element_chart);
4594 state.google_instance.draw(datatable, state.google_options);
4598 // ----------------------------------------------------------------------------------------------------------------
4600 NETDATA.percentFromValueMax = function(value, max) {
4601 if(value === null) value = 0;
4602 if(max < value) max = value;
4606 pcent = Math.round(value * 100 / max);
4607 if(pcent === 0 && value > 0) pcent = 1;
4613 // ----------------------------------------------------------------------------------------------------------------
4616 NETDATA.easypiechartInitialize = function(callback) {
4617 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4619 url: NETDATA.easypiechart_js,
4624 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4627 NETDATA.chartLibraries.easypiechart.enabled = false;
4628 NETDATA.error(100, NETDATA.easypiechart_js);
4630 .always(function() {
4631 if(typeof callback === "function")
4636 NETDATA.chartLibraries.easypiechart.enabled = false;
4637 if(typeof callback === "function")
4642 NETDATA.easypiechartClearSelection = function(state) {
4643 if(typeof state.easyPieChartEvent !== 'undefined') {
4644 if(state.easyPieChartEvent.timer !== null)
4645 clearTimeout(state.easyPieChartEvent.timer);
4647 state.easyPieChartEvent.timer = null;
4650 if(state.isAutoRefreshed() === true && state.data !== null) {
4651 NETDATA.easypiechartChartUpdate(state, state.data);
4654 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4655 state.easyPieChart_instance.update(0);
4657 state.easyPieChart_instance.enableAnimation();
4662 NETDATA.easypiechartSetSelection = function(state, t) {
4663 if(state.timeIsVisible(t) !== true)
4664 return NETDATA.easypiechartClearSelection(state);
4666 var slot = state.calculateRowForTime(t);
4667 if(slot < 0 || slot >= state.data.result.length)
4668 return NETDATA.easypiechartClearSelection(state);
4670 if(typeof state.easyPieChartEvent === 'undefined') {
4671 state.easyPieChartEvent = {
4678 var value = state.data.result[state.data.result.length - 1 - slot];
4679 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4680 var pcent = NETDATA.percentFromValueMax(value, max);
4682 state.easyPieChartEvent.value = value;
4683 state.easyPieChartEvent.pcent = pcent;
4684 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4686 if(state.easyPieChartEvent.timer === null) {
4687 state.easyPieChart_instance.disableAnimation();
4689 state.easyPieChartEvent.timer = setTimeout(function() {
4690 state.easyPieChartEvent.timer = null;
4691 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4692 }, NETDATA.options.current.charts_selection_animation_delay);
4698 NETDATA.easypiechartChartUpdate = function(state, data) {
4699 var value, max, pcent;
4701 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4707 value = data.result[0];
4708 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4709 pcent = NETDATA.percentFromValueMax(value, max);
4712 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4713 state.easyPieChart_instance.update(pcent);
4717 NETDATA.easypiechartChartCreate = function(state, data) {
4718 var self = $(state.element);
4719 var chart = $(state.element_chart);
4721 var value = data.result[0];
4722 var max = self.data('easypiechart-max-value') || null;
4723 var adjust = self.data('easypiechart-adjust') || null;
4727 state.easyPieChartMax = null;
4730 state.easyPieChartMax = max;
4732 var pcent = NETDATA.percentFromValueMax(value, max);
4734 chart.data('data-percent', pcent);
4738 case 'width': size = state.chartHeight(); break;
4739 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4740 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4742 default: size = state.chartWidth(); break;
4744 state.element.style.width = size + 'px';
4745 state.element.style.height = size + 'px';
4747 var stroke = Math.floor(size / 22);
4748 if(stroke < 3) stroke = 2;
4750 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4751 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4752 state.easyPieChartLabel = document.createElement('span');
4753 state.easyPieChartLabel.className = 'easyPieChartLabel';
4754 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4755 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4756 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4757 state.element_chart.appendChild(state.easyPieChartLabel);
4759 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4760 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4761 state.easyPieChartTitle = document.createElement('span');
4762 state.easyPieChartTitle.className = 'easyPieChartTitle';
4763 state.easyPieChartTitle.innerHTML = state.title;
4764 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4765 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4766 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4767 state.element_chart.appendChild(state.easyPieChartTitle);
4769 var unitfontsize = Math.round(titlefontsize * 0.9);
4770 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4771 state.easyPieChartUnits = document.createElement('span');
4772 state.easyPieChartUnits.className = 'easyPieChartUnits';
4773 state.easyPieChartUnits.innerHTML = state.units;
4774 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4775 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4776 state.element_chart.appendChild(state.easyPieChartUnits);
4778 chart.easyPieChart({
4779 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4780 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4781 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4782 scaleLength: self.data('easypiechart-scalelength') || 5,
4783 lineCap: self.data('easypiechart-linecap') || 'round',
4784 lineWidth: self.data('easypiechart-linewidth') || stroke,
4785 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4786 size: self.data('easypiechart-size') || size,
4787 rotate: self.data('easypiechart-rotate') || 0,
4788 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4789 easing: self.data('easypiechart-easing') || undefined
4792 // when we just re-create the chart
4793 // do not animate the first update
4795 if(typeof state.easyPieChart_instance !== 'undefined')
4798 state.easyPieChart_instance = chart.data('easyPieChart');
4799 if(animate === false) state.easyPieChart_instance.disableAnimation();
4800 state.easyPieChart_instance.update(pcent);
4801 if(animate === false) state.easyPieChart_instance.enableAnimation();
4805 // ----------------------------------------------------------------------------------------------------------------
4808 NETDATA.gaugeInitialize = function(callback) {
4809 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4811 url: NETDATA.gauge_js,
4816 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4819 NETDATA.chartLibraries.gauge.enabled = false;
4820 NETDATA.error(100, NETDATA.gauge_js);
4822 .always(function() {
4823 if(typeof callback === "function")
4828 NETDATA.chartLibraries.gauge.enabled = false;
4829 if(typeof callback === "function")
4834 NETDATA.gaugeAnimation = function(state, status) {
4837 if(typeof status === 'boolean' && status === false)
4839 else if(typeof status === 'number')
4842 state.gauge_instance.animationSpeed = speed;
4843 state.___gaugeOld__.speed = speed;
4846 NETDATA.gaugeSet = function(state, value, min, max) {
4847 if(typeof value !== 'number') value = 0;
4848 if(typeof min !== 'number') min = 0;
4849 if(typeof max !== 'number') max = 0;
4850 if(value > max) max = value;
4851 if(value < min) min = value;
4860 // gauge.js has an issue if the needle
4861 // is smaller than min or larger than max
4862 // when we set the new values
4863 // the needle will go crazy
4865 // to prevent it, we always feed it
4866 // with a percentage, so that the needle
4867 // is always between min and max
4868 var pcent = (value - min) * 100 / (max - min);
4870 // these should never happen
4871 if(pcent < 0) pcent = 0;
4872 if(pcent > 100) pcent = 100;
4874 state.gauge_instance.set(pcent);
4876 state.___gaugeOld__.value = value;
4877 state.___gaugeOld__.min = min;
4878 state.___gaugeOld__.max = max;
4881 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4882 if(state.___gaugeOld__.valueLabel !== value) {
4883 state.___gaugeOld__.valueLabel = value;
4884 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4886 if(state.___gaugeOld__.minLabel !== min) {
4887 state.___gaugeOld__.minLabel = min;
4888 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4890 if(state.___gaugeOld__.maxLabel !== max) {
4891 state.___gaugeOld__.maxLabel = max;
4892 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4896 NETDATA.gaugeClearSelection = function(state) {
4897 if(typeof state.gaugeEvent !== 'undefined') {
4898 if(state.gaugeEvent.timer !== null)
4899 clearTimeout(state.gaugeEvent.timer);
4901 state.gaugeEvent.timer = null;
4904 if(state.isAutoRefreshed() === true && state.data !== null) {
4905 NETDATA.gaugeChartUpdate(state, state.data);
4908 NETDATA.gaugeAnimation(state, false);
4909 NETDATA.gaugeSet(state, null, null, null);
4910 NETDATA.gaugeSetLabels(state, null, null, null);
4913 NETDATA.gaugeAnimation(state, true);
4917 NETDATA.gaugeSetSelection = function(state, t) {
4918 if(state.timeIsVisible(t) !== true)
4919 return NETDATA.gaugeClearSelection(state);
4921 var slot = state.calculateRowForTime(t);
4922 if(slot < 0 || slot >= state.data.result.length)
4923 return NETDATA.gaugeClearSelection(state);
4925 if(typeof state.gaugeEvent === 'undefined') {
4926 state.gaugeEvent = {
4934 var value = state.data.result[state.data.result.length - 1 - slot];
4935 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4938 state.gaugeEvent.value = value;
4939 state.gaugeEvent.max = max;
4940 state.gaugeEvent.min = min;
4941 NETDATA.gaugeSetLabels(state, value, min, max);
4943 if(state.gaugeEvent.timer === null) {
4944 NETDATA.gaugeAnimation(state, false);
4946 state.gaugeEvent.timer = setTimeout(function() {
4947 state.gaugeEvent.timer = null;
4948 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4949 }, NETDATA.options.current.charts_selection_animation_delay);
4955 NETDATA.gaugeChartUpdate = function(state, data) {
4956 var value, min, max;
4958 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4962 NETDATA.gaugeSetLabels(state, null, null, null);
4965 value = data.result[0];
4967 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4968 if(value > max) max = value;
4969 NETDATA.gaugeSetLabels(state, value, min, max);
4972 NETDATA.gaugeSet(state, value, min, max);
4976 NETDATA.gaugeChartCreate = function(state, data) {
4977 var self = $(state.element);
4978 // var chart = $(state.element_chart);
4980 var value = data.result[0];
4981 var max = self.data('gauge-max-value') || null;
4982 var adjust = self.data('gauge-adjust') || null;
4983 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4984 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4985 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4986 var stopColor = self.data('gauge-stop-color') || void 0;
4987 var generateGradient = self.data('gauge-generate-gradient') || false;
4991 state.gaugeMax = null;
4994 state.gaugeMax = max;
4996 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4998 // case 'width': width = height * ratio; break;
5000 // default: height = width / ratio; break;
5002 //state.element.style.width = width.toString() + 'px';
5003 //state.element.style.height = height.toString() + 'px';
5008 lines: 12, // The number of lines to draw
5009 angle: 0.15, // The length of each line
5010 lineWidth: 0.44, // 0.44 The line thickness
5012 length: 0.8, // 0.9 The radius of the inner circle
5013 strokeWidth: 0.035, // The rotation offset
5014 color: pointerColor // Fill color
5016 colorStart: startColor, // Colors
5017 colorStop: stopColor, // just experiment with them
5018 strokeColor: strokeColor, // to see which ones work best for you
5020 generateGradient: generateGradient,
5024 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5025 options.percentColors = [
5026 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5027 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5028 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5029 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5030 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5031 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5032 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5033 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5034 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5035 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5036 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5039 state.gauge_canvas = document.createElement('canvas');
5040 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5041 state.gauge_canvas.className = 'gaugeChart';
5042 state.gauge_canvas.width = width;
5043 state.gauge_canvas.height = height;
5044 state.element_chart.appendChild(state.gauge_canvas);
5046 var valuefontsize = Math.floor(height / 6);
5047 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5048 state.gaugeChartLabel = document.createElement('span');
5049 state.gaugeChartLabel.className = 'gaugeChartLabel';
5050 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5051 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5052 state.element_chart.appendChild(state.gaugeChartLabel);
5054 var titlefontsize = Math.round(valuefontsize / 2);
5056 state.gaugeChartTitle = document.createElement('span');
5057 state.gaugeChartTitle.className = 'gaugeChartTitle';
5058 state.gaugeChartTitle.innerHTML = state.title;
5059 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5060 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5061 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5062 state.element_chart.appendChild(state.gaugeChartTitle);
5064 var unitfontsize = Math.round(titlefontsize * 0.9);
5065 state.gaugeChartUnits = document.createElement('span');
5066 state.gaugeChartUnits.className = 'gaugeChartUnits';
5067 state.gaugeChartUnits.innerHTML = state.units;
5068 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5069 state.element_chart.appendChild(state.gaugeChartUnits);
5071 state.gaugeChartMin = document.createElement('span');
5072 state.gaugeChartMin.className = 'gaugeChartMin';
5073 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5074 state.element_chart.appendChild(state.gaugeChartMin);
5076 state.gaugeChartMax = document.createElement('span');
5077 state.gaugeChartMax.className = 'gaugeChartMax';
5078 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5079 state.element_chart.appendChild(state.gaugeChartMax);
5081 // when we just re-create the chart
5082 // do not animate the first update
5084 if(typeof state.gauge_instance !== 'undefined')
5087 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5089 state.___gaugeOld__ = {
5098 // we will always feed a percentage
5099 state.gauge_instance.minValue = 0;
5100 state.gauge_instance.maxValue = 100;
5102 NETDATA.gaugeAnimation(state, animate);
5103 NETDATA.gaugeSet(state, value, 0, max);
5104 NETDATA.gaugeSetLabels(state, value, 0, max);
5105 NETDATA.gaugeAnimation(state, true);
5109 // ----------------------------------------------------------------------------------------------------------------
5110 // Charts Libraries Registration
5112 NETDATA.chartLibraries = {
5114 initialize: NETDATA.dygraphInitialize,
5115 create: NETDATA.dygraphChartCreate,
5116 update: NETDATA.dygraphChartUpdate,
5117 resize: function(state) {
5118 if(typeof state.dygraph_instance.resize === 'function')
5119 state.dygraph_instance.resize();
5121 setSelection: NETDATA.dygraphSetSelection,
5122 clearSelection: NETDATA.dygraphClearSelection,
5123 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5126 format: function(state) { return 'json'; },
5127 options: function(state) { return 'ms|flip'; },
5128 legend: function(state) {
5129 if(this.isSparkline(state) === false)
5130 return 'right-side';
5134 autoresize: function(state) { return true; },
5135 max_updates_to_recreate: function(state) { return 5000; },
5136 track_colors: function(state) { return true; },
5137 pixels_per_point: function(state) {
5138 if(this.isSparkline(state) === false)
5144 isSparkline: function(state) {
5145 if(typeof state.dygraph_sparkline === 'undefined') {
5146 var t = $(state.element).data('dygraph-theme');
5147 if(t === 'sparkline')
5148 state.dygraph_sparkline = true;
5150 state.dygraph_sparkline = false;
5152 return state.dygraph_sparkline;
5156 initialize: NETDATA.sparklineInitialize,
5157 create: NETDATA.sparklineChartCreate,
5158 update: NETDATA.sparklineChartUpdate,
5160 setSelection: undefined, // function(state, t) { return true; },
5161 clearSelection: undefined, // function(state) { return true; },
5162 toolboxPanAndZoom: null,
5165 format: function(state) { return 'array'; },
5166 options: function(state) { return 'flip|abs'; },
5167 legend: function(state) { return null; },
5168 autoresize: function(state) { return false; },
5169 max_updates_to_recreate: function(state) { return 5000; },
5170 track_colors: function(state) { return false; },
5171 pixels_per_point: function(state) { return 3; }
5174 initialize: NETDATA.peityInitialize,
5175 create: NETDATA.peityChartCreate,
5176 update: NETDATA.peityChartUpdate,
5178 setSelection: undefined, // function(state, t) { return true; },
5179 clearSelection: undefined, // function(state) { return true; },
5180 toolboxPanAndZoom: null,
5183 format: function(state) { return 'ssvcomma'; },
5184 options: function(state) { return 'null2zero|flip|abs'; },
5185 legend: function(state) { return null; },
5186 autoresize: function(state) { return false; },
5187 max_updates_to_recreate: function(state) { return 5000; },
5188 track_colors: function(state) { return false; },
5189 pixels_per_point: function(state) { return 3; }
5192 initialize: NETDATA.morrisInitialize,
5193 create: NETDATA.morrisChartCreate,
5194 update: NETDATA.morrisChartUpdate,
5196 setSelection: undefined, // function(state, t) { return true; },
5197 clearSelection: undefined, // function(state) { return true; },
5198 toolboxPanAndZoom: null,
5201 format: function(state) { return 'json'; },
5202 options: function(state) { return 'objectrows|ms'; },
5203 legend: function(state) { return null; },
5204 autoresize: function(state) { return false; },
5205 max_updates_to_recreate: function(state) { return 50; },
5206 track_colors: function(state) { return false; },
5207 pixels_per_point: function(state) { return 15; }
5210 initialize: NETDATA.googleInitialize,
5211 create: NETDATA.googleChartCreate,
5212 update: NETDATA.googleChartUpdate,
5214 setSelection: undefined, //function(state, t) { return true; },
5215 clearSelection: undefined, //function(state) { return true; },
5216 toolboxPanAndZoom: null,
5219 format: function(state) { return 'datatable'; },
5220 options: function(state) { return ''; },
5221 legend: function(state) { return null; },
5222 autoresize: function(state) { return false; },
5223 max_updates_to_recreate: function(state) { return 300; },
5224 track_colors: function(state) { return false; },
5225 pixels_per_point: function(state) { return 4; }
5228 initialize: NETDATA.raphaelInitialize,
5229 create: NETDATA.raphaelChartCreate,
5230 update: NETDATA.raphaelChartUpdate,
5232 setSelection: undefined, // function(state, t) { return true; },
5233 clearSelection: undefined, // function(state) { return true; },
5234 toolboxPanAndZoom: null,
5237 format: function(state) { return 'json'; },
5238 options: function(state) { return ''; },
5239 legend: function(state) { return null; },
5240 autoresize: function(state) { return false; },
5241 max_updates_to_recreate: function(state) { return 5000; },
5242 track_colors: function(state) { return false; },
5243 pixels_per_point: function(state) { return 3; }
5246 initialize: NETDATA.c3Initialize,
5247 create: NETDATA.c3ChartCreate,
5248 update: NETDATA.c3ChartUpdate,
5250 setSelection: undefined, // function(state, t) { return true; },
5251 clearSelection: undefined, // function(state) { return true; },
5252 toolboxPanAndZoom: null,
5255 format: function(state) { return 'csvjsonarray'; },
5256 options: function(state) { return 'milliseconds'; },
5257 legend: function(state) { return null; },
5258 autoresize: function(state) { return false; },
5259 max_updates_to_recreate: function(state) { return 5000; },
5260 track_colors: function(state) { return false; },
5261 pixels_per_point: function(state) { return 15; }
5264 initialize: NETDATA.d3Initialize,
5265 create: NETDATA.d3ChartCreate,
5266 update: NETDATA.d3ChartUpdate,
5268 setSelection: undefined, // function(state, t) { return true; },
5269 clearSelection: undefined, // function(state) { return true; },
5270 toolboxPanAndZoom: null,
5273 format: function(state) { return 'json'; },
5274 options: function(state) { return ''; },
5275 legend: function(state) { return null; },
5276 autoresize: function(state) { return false; },
5277 max_updates_to_recreate: function(state) { return 5000; },
5278 track_colors: function(state) { return false; },
5279 pixels_per_point: function(state) { return 3; }
5282 initialize: NETDATA.easypiechartInitialize,
5283 create: NETDATA.easypiechartChartCreate,
5284 update: NETDATA.easypiechartChartUpdate,
5286 setSelection: NETDATA.easypiechartSetSelection,
5287 clearSelection: NETDATA.easypiechartClearSelection,
5288 toolboxPanAndZoom: null,
5291 format: function(state) { return 'array'; },
5292 options: function(state) { return 'absolute'; },
5293 legend: function(state) { return null; },
5294 autoresize: function(state) { return false; },
5295 max_updates_to_recreate: function(state) { return 5000; },
5296 track_colors: function(state) { return true; },
5297 pixels_per_point: function(state) { return 3; },
5301 initialize: NETDATA.gaugeInitialize,
5302 create: NETDATA.gaugeChartCreate,
5303 update: NETDATA.gaugeChartUpdate,
5305 setSelection: NETDATA.gaugeSetSelection,
5306 clearSelection: NETDATA.gaugeClearSelection,
5307 toolboxPanAndZoom: null,
5310 format: function(state) { return 'array'; },
5311 options: function(state) { return 'absolute'; },
5312 legend: function(state) { return null; },
5313 autoresize: function(state) { return false; },
5314 max_updates_to_recreate: function(state) { return 5000; },
5315 track_colors: function(state) { return true; },
5316 pixels_per_point: function(state) { return 3; },
5321 NETDATA.registerChartLibrary = function(library, url) {
5322 if(NETDATA.options.debug.libraries === true)
5323 console.log("registering chart library: " + library);
5325 NETDATA.chartLibraries[library].url = url;
5326 NETDATA.chartLibraries[library].initialized = true;
5327 NETDATA.chartLibraries[library].enabled = true;
5330 // ----------------------------------------------------------------------------------------------------------------
5333 NETDATA.requiredJs = [
5335 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5336 isAlreadyLoaded: function() {
5337 // check if bootstrap is loaded
5338 if(typeof $().emulateTransitionEnd == 'function')
5341 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5349 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5350 isAlreadyLoaded: function() { return false; }
5353 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5354 isAlreadyLoaded: function() { return false; }
5358 NETDATA.requiredCSS = [
5360 url: NETDATA.themes.current.bootstrap_css,
5361 isAlreadyLoaded: function() {
5362 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5369 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5370 isAlreadyLoaded: function() { return false; }
5373 url: NETDATA.themes.current.dashboard_css,
5374 isAlreadyLoaded: function() { return false; }
5377 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5378 isAlreadyLoaded: function() { return false; }
5382 NETDATA.loadRequiredJs = function(index, callback) {
5383 if(index >= NETDATA.requiredJs.length) {
5384 if(typeof callback === 'function')
5389 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5390 NETDATA.loadRequiredJs(++index, callback);
5394 if(NETDATA.options.debug.main_loop === true)
5395 console.log('loading ' + NETDATA.requiredJs[index].url);
5398 url: NETDATA.requiredJs[index].url,
5402 .success(function() {
5403 if(NETDATA.options.debug.main_loop === true)
5404 console.log('loaded ' + NETDATA.requiredJs[index].url);
5406 NETDATA.loadRequiredJs(++index, callback);
5409 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5413 NETDATA.loadRequiredCSS = function(index) {
5414 if(index >= NETDATA.requiredCSS.length)
5417 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5418 NETDATA.loadRequiredCSS(++index);
5422 if(NETDATA.options.debug.main_loop === true)
5423 console.log('loading ' + NETDATA.requiredCSS[index].url);
5425 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5426 NETDATA.loadRequiredCSS(++index);
5429 NETDATA.errorReset();
5430 NETDATA.loadRequiredCSS(0);
5432 NETDATA._loadjQuery(function() {
5433 NETDATA.loadRequiredJs(0, function() {
5434 if(typeof $().emulateTransitionEnd !== 'function') {
5435 // bootstrap is not available
5436 NETDATA.options.current.show_help = false;
5439 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5440 if(NETDATA.options.debug.main_loop === true)
5441 console.log('starting chart refresh thread');
5448 // window.NETDATA = NETDATA;
5449 // })(window, document);