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 = function(code,msg) {} // 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) {
22 // fix IE issue with console
23 if(!window.console){ window.console = {log: function(){} }; }
26 var NETDATA = window.NETDATA || {};
28 // ----------------------------------------------------------------------------------------------------------------
29 // Detect the netdata server
31 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
32 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
33 NETDATA._scriptSource = function() {
36 if(typeof document.currentScript !== 'undefined') {
37 script = document.currentScript;
40 var all_scripts = document.getElementsByTagName('script');
41 script = all_scripts[all_scripts.length - 1];
44 if (typeof script.getAttribute.length !== 'undefined')
47 script = script.getAttribute('src', -1);
52 if(typeof netdataServer !== 'undefined')
53 NETDATA.serverDefault = netdataServer;
55 var s = NETDATA._scriptSource();
56 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
59 if(NETDATA.serverDefault === null)
60 NETDATA.serverDefault = '';
61 else if(NETDATA.serverDefault.slice(-1) !== '/')
62 NETDATA.serverDefault += '/';
64 // default URLs for all the external files we need
65 // make them RELATIVE so that the whole thing can also be
66 // installed under a web server
67 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
68 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
69 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
70 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
71 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
72 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
73 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
74 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
75 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
76 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
77 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
78 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
79 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
80 NETDATA.google_js = 'https://www.google.com/jsapi';
84 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
85 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
86 background: '#FFFFFF',
87 foreground: '#000000',
90 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
91 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
92 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
93 '#329262', '#3B3EAC' ],
94 easypiechart_track: '#f0f0f0',
95 easypiechart_scale: '#dfe0e0',
96 gauge_pointer: '#C0C0C0',
97 gauge_stroke: '#F0F0F0',
101 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
102 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
103 background: '#272b30',
104 foreground: '#C8C8C8',
107 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
108 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
109 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
110 '#a6a479', '#a66da8' ],
112 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
113 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
114 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
115 '#329262', '#3B3EFF' ],
116 easypiechart_track: '#373b40',
117 easypiechart_scale: '#373b40',
118 gauge_pointer: '#474b50',
119 gauge_stroke: '#373b40',
120 gauge_gradient: false
124 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
125 NETDATA.themes.current = NETDATA.themes[netdataTheme];
127 NETDATA.themes.current = NETDATA.themes.default;
129 NETDATA.colors = NETDATA.themes.current.colors;
131 // these are the colors Google Charts are using
132 // we have them here to attempt emulate their look and feel on the other chart libraries
133 // http://there4.io/2012/05/02/google-chart-color-list/
134 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
135 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
136 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
138 // an alternative set
139 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
140 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
141 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
143 // ----------------------------------------------------------------------------------------------------------------
144 // the defaults for all charts
146 // if the user does not specify any of these, the following will be used
148 NETDATA.chartDefaults = {
149 host: NETDATA.serverDefault, // the server to get data from
150 width: '100%', // the chart width - can be null
151 height: '100%', // the chart height - can be null
152 min_width: null, // the chart minimum width - can be null
153 library: 'dygraph', // the graphing library to use
154 method: 'average', // the grouping method
155 before: 0, // panning
156 after: -600, // panning
157 pixels_per_point: 1, // the detail of the chart
158 fill_luminance: 0.8 // luminance of colors in solit areas
161 // ----------------------------------------------------------------------------------------------------------------
165 pauseCallback: null, // a callback when we are really paused
167 pause: false, // when enabled we don't auto-refresh the charts
169 targets: null, // an array of all the state objects that are
170 // currently active (independently of their
171 // viewport visibility)
173 updated_dom: true, // when true, the DOM has been updated with
174 // new elements we have to check.
176 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
177 // rendering charts continiously.
178 // used with .current.fast_render_timeframe
180 page_is_visible: true, // when true, this page is visible
182 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
183 // auto-refresher for some time (when a chart is
184 // performing pan or zoom, we need to stop refreshing
185 // all other charts, to have the maximum speed for
186 // rendering the chart that is panned or zoomed).
187 // Used with .current.global_pan_sync_time
189 last_resized: new Date().getTime(), // the timestamp of the last resize request
191 crossDomainAjax: false, // enable this to request crossDomain AJAX
193 last_page_scroll: 0, // the timestamp the last time the page was scrolled
195 // the current profile
196 // we may have many...
198 pixels_per_point: 1, // the minimum pixels per point for all charts
199 // increase this to speed javascript up
200 // each chart library has its own limit too
201 // the max of this and the chart library is used
202 // the final is calculated every time, so a change
203 // here will have immediate effect on the next chart
206 idle_between_charts: 100, // ms - how much time to wait between chart updates
208 fast_render_timeframe: 200, // ms - render continously until this time of continious
209 // rendering has been reached
210 // this setting is used to make it render e.g. 10
211 // charts at once, sleep idle_between_charts time
212 // and continue for another 10 charts.
214 idle_between_loops: 500, // ms - if all charts have been updated, wait this
215 // time before starting again.
217 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
219 idle_lost_focus: 500, // ms - when the window does not have focus, check
220 // if focus has been regained, every this time
222 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
223 // autorefreshing of charts is paused for this amount
226 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
227 // of time before setting up synchronized selections
230 sync_selection: true, // enable or disable selection sync
232 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
234 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
236 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
238 update_only_visible: true, // enable or disable visibility management
240 parallel_refresher: true, // enable parallel refresh of charts
242 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
244 destroy_on_hide: false, // destroy charts when they are not visible
246 show_help: true, // when enabled the charts will show some help
247 show_help_delay_show_ms: 500,
248 show_help_delay_hide_ms: 0,
250 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
252 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
253 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
255 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
257 smooth_plot: true, // enable smooth plot, where possible
259 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
261 color_fill_opacity_line: 1.0,
262 color_fill_opacity_area: 0.2,
263 color_fill_opacity_stacked: 0.8,
265 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
266 pan_and_zoom_factor_multiplier_control: 2.0,
267 pan_and_zoom_factor_multiplier_shift: 3.0,
268 pan_and_zoom_factor_multiplier_alt: 4.0,
270 setOptionCallback: function() { ; }
278 chart_data_url: false,
279 chart_errors: false, // FIXME
288 // ----------------------------------------------------------------------------------------------------------------
289 // local storage options
291 NETDATA.localStorage = {
294 callback: {} // only used for resetting back to defaults
297 NETDATA.localStorageGet = function(key, def, callback) {
300 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
301 NETDATA.localStorage.default[key.toString()] = def;
302 NETDATA.localStorage.callback[key.toString()] = callback;
305 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
307 // console.log('localStorage: loading "' + key.toString() + '"');
308 ret = localStorage.getItem(key.toString());
309 if(ret === null || ret === 'undefined') {
310 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
311 localStorage.setItem(key.toString(), JSON.stringify(def));
315 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
316 ret = JSON.parse(ret);
317 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
321 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
326 if(typeof ret === 'undefined' || ret === 'undefined') {
327 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
331 NETDATA.localStorage.current[key.toString()] = ret;
335 NETDATA.localStorageSet = function(key, value, callback) {
336 if(typeof value === 'undefined' || value === 'undefined') {
337 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
340 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
341 NETDATA.localStorage.default[key.toString()] = value;
342 NETDATA.localStorage.current[key.toString()] = value;
343 NETDATA.localStorage.callback[key.toString()] = callback;
346 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
347 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
349 localStorage.setItem(key.toString(), JSON.stringify(value));
352 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
356 NETDATA.localStorage.current[key.toString()] = value;
360 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
362 if(typeof obj[i] === 'object') {
363 //console.log('object ' + prefix + '.' + i.toString());
364 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
368 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
372 NETDATA.setOption = function(key, value) {
373 if(key.toString() === 'setOptionCallback') {
374 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
375 NETDATA.options.current[key.toString()] = value;
376 NETDATA.options.current.setOptionCallback();
379 else if(NETDATA.options.current[key.toString()] !== value) {
380 var name = 'options.' + key.toString();
382 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
383 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
385 //console.log(NETDATA.localStorage);
386 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
387 //console.log(NETDATA.options);
388 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
390 if(typeof NETDATA.options.current.setOptionCallback === 'function')
391 NETDATA.options.current.setOptionCallback();
397 NETDATA.getOption = function(key) {
398 return NETDATA.options.current[key.toString()];
401 // read settings from local storage
402 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
404 // always start with this option enabled.
405 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
407 NETDATA.resetOptions = function() {
408 for(var i in NETDATA.localStorage.default) {
409 var a = i.split('.');
411 if(a[0] === 'options') {
412 if(a[1] === 'setOptionCallback') continue;
413 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
414 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
416 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
418 else if(a[0] === 'chart_heights') {
419 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
420 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
426 // ----------------------------------------------------------------------------------------------------------------
428 if(NETDATA.options.debug.main_loop === true)
429 console.log('welcome to NETDATA');
431 NETDATA.onresize = function() {
432 NETDATA.options.last_resized = new Date().getTime();
436 NETDATA.onscroll = function() {
437 // console.log('onscroll');
439 NETDATA.options.last_page_scroll = new Date().getTime();
440 if(NETDATA.options.targets === null) return;
442 // when the user scrolls he sees that we have
443 // hidden all the not-visible charts
444 // using this little function we try to switch
445 // the charts back to visible quickly
446 var targets = NETDATA.options.targets;
447 var len = targets.length;
448 while(len--) targets[len].isVisible();
451 window.onresize = NETDATA.onresize;
452 window.onscroll = NETDATA.onscroll;
454 // ----------------------------------------------------------------------------------------------------------------
457 NETDATA.errorCodes = {
458 100: { message: "Cannot load chart library", alert: true },
459 101: { message: "Cannot load jQuery", alert: true },
460 402: { message: "Chart library not found", alert: false },
461 403: { message: "Chart library not enabled/is failed", alert: false },
462 404: { message: "Chart not found", alert: false }
464 NETDATA.errorLast = {
470 NETDATA.error = function(code, msg) {
471 NETDATA.errorLast.code = code;
472 NETDATA.errorLast.message = msg;
473 NETDATA.errorLast.datetime = new Date().getTime();
475 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
477 if(typeof netdataErrorCallback === 'function') {
478 netdataErrorCallback(code,msg);
481 if(NETDATA.errorCodes[code].alert)
482 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
485 NETDATA.errorReset = function() {
486 NETDATA.errorLast.code = 0;
487 NETDATA.errorLast.message = "You are doing fine!";
488 NETDATA.errorLast.datetime = 0;
491 // ----------------------------------------------------------------------------------------------------------------
494 // When multiple charts need the same chart, we avoid downloading it
495 // multiple times (and having it in browser memory multiple time)
496 // by using this registry.
498 // Every time we download a chart definition, we save it here with .add()
499 // Then we try to get it back with .get(). If that fails, we download it.
501 NETDATA.chartRegistry = {
504 fixid: function(id) {
505 return id.replace(/:/g, "_").replace(/\//g, "_");
508 add: function(host, id, data) {
509 host = this.fixid(host);
512 if(typeof this.charts[host] === 'undefined')
513 this.charts[host] = {};
515 //console.log('added ' + host + '/' + id);
516 this.charts[host][id] = data;
519 get: function(host, id) {
520 host = this.fixid(host);
523 if(typeof this.charts[host] === 'undefined')
526 if(typeof this.charts[host][id] === 'undefined')
529 //console.log('cached ' + host + '/' + id);
530 return this.charts[host][id];
533 downloadAll: function(host, callback) {
534 while(host.slice(-1) === '/')
535 host = host.substring(0, host.length - 1);
540 url: host + '/api/v1/charts',
541 crossDomain: NETDATA.options.crossDomainAjax,
545 .done(function(data) {
547 var h = NETDATA.chartRegistry.fixid(host);
548 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
549 self.charts[h] = data.charts;
551 if(typeof callback === 'function')
555 if(typeof callback === 'function')
561 // ----------------------------------------------------------------------------------------------------------------
562 // Global Pan and Zoom on charts
564 // Using this structure are synchronize all the charts, so that
565 // when you pan or zoom one, all others are automatically refreshed
566 // to the same timespan.
568 NETDATA.globalPanAndZoom = {
569 seq: 0, // timestamp ms
570 // every time a chart is panned or zoomed
571 // we set the timestamp here
572 // then we use it as a sequence number
573 // to find if other charts are syncronized
576 master: null, // the master chart (state), to which all others
579 force_before_ms: null, // the timespan to sync all other charts
580 force_after_ms: null,
583 setMaster: function(state, after, before) {
584 if(NETDATA.options.current.sync_pan_and_zoom === false)
587 if(this.master !== null && this.master !== state)
588 this.master.resetChart(true, true);
590 var now = new Date().getTime();
593 this.force_after_ms = after;
594 this.force_before_ms = before;
595 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
599 clearMaster: function() {
600 if(this.master !== null) {
601 var st = this.master;
608 this.force_after_ms = null;
609 this.force_before_ms = null;
610 NETDATA.options.auto_refresher_stop_until = 0;
613 // is the given state the master of the global
614 // pan and zoom sync?
615 isMaster: function(state) {
616 if(this.master === state) return true;
620 // are we currently have a global pan and zoom sync?
621 isActive: function() {
622 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
626 // check if a chart, other than the master
627 // needs to be refreshed, due to the global pan and zoom
628 shouldBeAutoRefreshed: function(state) {
629 if(this.master === null || this.seq === 0)
632 //if(state.needsRecreation())
635 if(state.tm.pan_and_zoom_seq === this.seq)
642 // ----------------------------------------------------------------------------------------------------------------
643 // dimensions selection
646 // move color assignment to dimensions, here
648 dimensionStatus = function(parent, label, name_div, value_div, color) {
649 this.enabled = false;
650 this.parent = parent;
652 this.name_div = null;
653 this.value_div = null;
654 this.color = NETDATA.themes.current.foreground;
656 if(parent.selected_count > parent.unselected_count)
657 this.selected = true;
659 this.selected = false;
661 this.setOptions(name_div, value_div, color);
664 dimensionStatus.prototype.invalidate = function() {
665 this.name_div = null;
666 this.value_div = null;
667 this.enabled = false;
670 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
673 if(this.name_div != name_div) {
674 this.name_div = name_div;
675 this.name_div.title = this.label;
676 this.name_div.style.color = this.color;
677 if(this.selected === false)
678 this.name_div.className = 'netdata-legend-name not-selected';
680 this.name_div.className = 'netdata-legend-name selected';
683 if(this.value_div != value_div) {
684 this.value_div = value_div;
685 this.value_div.title = this.label;
686 this.value_div.style.color = this.color;
687 if(this.selected === false)
688 this.value_div.className = 'netdata-legend-value not-selected';
690 this.value_div.className = 'netdata-legend-value selected';
697 dimensionStatus.prototype.setHandler = function() {
698 if(this.enabled === false) return;
702 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
703 this.name_div.onclick = this.value_div.onclick = function(e) {
705 if(ds.isSelected()) {
707 if(e.shiftKey === true || e.ctrlKey === true) {
708 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
711 if(ds.parent.countSelected() === 0)
712 ds.parent.selectAll();
715 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
716 if(ds.parent.countSelected() === 1) {
717 ds.parent.selectAll();
720 ds.parent.selectNone();
726 // this is not selected
727 if(e.shiftKey === true || e.ctrlKey === true) {
728 // control or shift key is pressed -> select this too
732 // no key is pressed -> select only this
733 ds.parent.selectNone();
738 ds.parent.state.redrawChart();
742 dimensionStatus.prototype.select = function() {
743 if(this.enabled === false) return;
745 this.name_div.className = 'netdata-legend-name selected';
746 this.value_div.className = 'netdata-legend-value selected';
747 this.selected = true;
750 dimensionStatus.prototype.unselect = function() {
751 if(this.enabled === false) return;
753 this.name_div.className = 'netdata-legend-name not-selected';
754 this.value_div.className = 'netdata-legend-value hidden';
755 this.selected = false;
758 dimensionStatus.prototype.isSelected = function() {
759 return(this.enabled === true && this.selected === true);
762 // ----------------------------------------------------------------------------------------------------------------
764 dimensionsVisibility = function(state) {
767 this.dimensions = {};
768 this.selected_count = 0;
769 this.unselected_count = 0;
772 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
773 if(typeof this.dimensions[label] === 'undefined') {
775 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
778 this.dimensions[label].setOptions(name_div, value_div, color);
780 return this.dimensions[label];
783 dimensionsVisibility.prototype.dimensionGet = function(label) {
784 return this.dimensions[label];
787 dimensionsVisibility.prototype.invalidateAll = function() {
788 for(var d in this.dimensions)
789 this.dimensions[d].invalidate();
792 dimensionsVisibility.prototype.selectAll = function() {
793 for(var d in this.dimensions)
794 this.dimensions[d].select();
797 dimensionsVisibility.prototype.countSelected = function() {
799 for(var d in this.dimensions)
800 if(this.dimensions[d].isSelected()) i++;
805 dimensionsVisibility.prototype.selectNone = function() {
806 for(var d in this.dimensions)
807 this.dimensions[d].unselect();
810 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
811 var ret = new Array();
812 this.selected_count = 0;
813 this.unselected_count = 0;
815 for(var i = 0, len = array.length; i < len ; i++) {
816 var ds = this.dimensions[array[i]];
817 if(typeof ds === 'undefined') {
818 // console.log(array[i] + ' is not found');
823 if(ds.isSelected()) {
825 this.selected_count++;
829 this.unselected_count++;
833 if(this.selected_count === 0 && this.unselected_count !== 0) {
835 return this.selected2BooleanArray(array);
842 // ----------------------------------------------------------------------------------------------------------------
843 // global selection sync
845 NETDATA.globalSelectionSync = {
852 if(this.state !== null)
853 this.state.globalSelectionSyncStop();
857 if(this.state !== null) {
858 this.state.globalSelectionSyncDelay();
863 // ----------------------------------------------------------------------------------------------------------------
864 // Our state object, where all per-chart values are stored
866 chartState = function(element) {
867 var self = $(element);
868 this.element = element;
871 // all private functions should use 'that', instead of 'this'
875 * show an error instead of the chart
877 var error = function(msg) {
879 if(typeof netdataErrorCallback === 'function') {
880 netdataErrorCallback("", msg);
883 that.element.innerHTML = that.id + ': ' + msg;
884 that.enabled = false;
885 that.current = that.pan;
888 // GUID - a unique identifier for the chart
889 this.uuid = NETDATA.guid();
891 // string - the name of chart
892 this.id = self.data('netdata');
894 // string - the key for localStorage settings
895 this.settings_id = self.data('id') || null;
897 // the user given dimensions of the element
898 this.width = self.data('width') || NETDATA.chartDefaults.width;
899 this.height = self.data('height') || NETDATA.chartDefaults.height;
901 if(this.settings_id !== null) {
902 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
903 // this is the callback that will be called
904 // if and when the user resets all localStorage variables
907 resizeChartToHeight(height);
911 // string - the netdata server URL, without any path
912 this.host = self.data('host') || NETDATA.chartDefaults.host;
914 // make sure the host does not end with /
915 // all netdata API requests use absolute paths
916 while(this.host.slice(-1) === '/')
917 this.host = this.host.substring(0, this.host.length - 1);
919 // string - the grouping method requested by the user
920 this.method = self.data('method') || NETDATA.chartDefaults.method;
922 // the time-range requested by the user
923 this.after = self.data('after') || NETDATA.chartDefaults.after;
924 this.before = self.data('before') || NETDATA.chartDefaults.before;
926 // the pixels per point requested by the user
927 this.pixels_per_point = self.data('pixels-per-point') || 1;
928 this.points = self.data('points') || null;
930 // the dimensions requested by the user
931 this.dimensions = self.data('dimensions') || null;
933 // the chart library requested by the user
934 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
936 // object - the chart library used
941 this.colors_assigned = {};
942 this.colors_available = null;
944 // the element already created by the user
945 this.element_message = null;
947 // the element with the chart
948 this.element_chart = null;
950 // the element with the legend of the chart (if created by us)
951 this.element_legend = null;
952 this.element_legend_childs = {
962 this.chart_url = null; // string - the url to download chart info
963 this.chart = null; // object - the chart as downloaded from the server
965 this.title = self.data('title') || null; // the title of the chart
966 this.units = self.data('units') || null; // the units of the chart dimensions
967 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
969 this.validated = false; // boolean - has the chart been validated?
970 this.enabled = true; // boolean - is the chart enabled for refresh?
971 this.paused = false; // boolean - is the chart paused for any reason?
972 this.selected = false; // boolean - is the chart shown a selection?
973 this.debug = false; // boolean - console.log() debug info about this chart
975 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
976 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
977 this.requested_after = null; // milliseconds - the timestamp of the request after param
978 this.requested_before = null; // milliseconds - the timestamp of the request before param
979 this.requested_padding = null;
981 this.view_before = 0;
986 force_update_at: 0, // the timestamp to force the update at
987 force_before_ms: null,
993 force_update_at: 0, // the timestamp to force the update at
994 force_before_ms: null,
1000 force_update_at: 0, // the timestamp to force the update at
1001 force_before_ms: null,
1002 force_after_ms: null
1005 // this is a pointer to one of the sub-classes below
1007 this.current = this.auto;
1009 // check the requested library is available
1010 // we don't initialize it here - it will be initialized when
1011 // this chart will be first used
1012 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1013 NETDATA.error(402, that.library_name);
1014 error('chart library "' + that.library_name + '" is not found');
1017 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1018 NETDATA.error(403, that.library_name);
1019 error('chart library "' + that.library_name + '" is not enabled');
1023 that.library = NETDATA.chartLibraries[that.library_name];
1025 // milliseconds - the time the last refresh took
1026 this.refresh_dt_ms = 0;
1028 // if we need to report the rendering speed
1029 // find the element that needs to be updated
1030 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1032 if(refresh_dt_element_name !== null)
1033 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1035 this.refresh_dt_element = null;
1037 this.dimensions_visibility = new dimensionsVisibility(this);
1039 this._updating = false;
1041 // ============================================================================================================
1042 // PRIVATE FUNCTIONS
1044 var createDOM = function() {
1045 if(that.enabled === false) return;
1047 if(that.element_message !== null) that.element_message.innerHTML = '';
1048 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1049 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1051 that.element.innerHTML = '';
1053 that.element_message = document.createElement('div');
1054 that.element_message.className = ' netdata-message hidden';
1055 that.element.appendChild(that.element_message);
1057 that.element_chart = document.createElement('div');
1058 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1059 that.element.appendChild(that.element_chart);
1061 if(that.hasLegend() === true) {
1062 that.element.className = "netdata-container-with-legend";
1063 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1065 that.element_legend = document.createElement('div');
1066 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1067 that.element.appendChild(that.element_legend);
1070 that.element.className = "netdata-container";
1071 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1073 that.element_legend = null;
1075 that.element_legend_childs.series = null;
1077 if(typeof(that.width) === 'string')
1078 $(that.element).css('width', that.width);
1079 else if(typeof(that.width) === 'number')
1080 $(that.element).css('width', that.width + 'px');
1082 if(typeof(that.library.aspect_ratio) === 'undefined') {
1083 if(typeof(that.height) === 'string')
1084 $(that.element).css('height', that.height);
1085 else if(typeof(that.height) === 'number')
1086 $(that.element).css('height', that.height + 'px');
1089 var w = that.element.offsetWidth;
1090 if(w === null || w === 0) {
1091 // the div is hidden
1092 // this is resize the chart when next viewed
1093 that.tm.last_resized = 0;
1096 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1099 if(NETDATA.chartDefaults.min_width !== null)
1100 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1102 that.tm.last_dom_created = new Date().getTime();
1108 * initialize state variables
1109 * destroy all (possibly) created state elements
1110 * create the basic DOM for a chart
1112 var init = function() {
1113 if(that.enabled === false) return;
1115 that.paused = false;
1116 that.selected = false;
1118 that.chart_created = false; // boolean - is the library.create() been called?
1119 that.updates_counter = 0; // numeric - the number of refreshes made so far
1120 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1121 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1124 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1125 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1126 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1128 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1129 last_updated: 0, // the timestamp the chart last updated with data
1130 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1132 // Used with NETDATA.globalPanAndZoom.seq
1133 last_visible_check: 0, // the time we last checked if it is visible
1134 last_resized: 0, // the time the chart was resized
1135 last_hidden: 0, // the time the chart was hidden
1136 last_unhidden: 0, // the time the chart was unhidden
1137 last_autorefreshed: 0 // the time the chart was last refreshed
1140 that.data = null; // the last data as downloaded from the netdata server
1141 that.data_url = 'invalid://'; // string - the last url used to update the chart
1142 that.data_points = 0; // number - the number of points returned from netdata
1143 that.data_after = 0; // milliseconds - the first timestamp of the data
1144 that.data_before = 0; // milliseconds - the last timestamp of the data
1145 that.data_update_every = 0; // milliseconds - the frequency to update the data
1147 that.tm.last_initialized = new Date().getTime();
1150 that.setMode('auto');
1153 var maxMessageFontSize = function() {
1154 // normally we want a font size, as tall as the element
1155 var h = that.element_message.clientHeight;
1157 // but give it some air, 20% let's say, or 5 pixels min
1158 var lost = Math.max(h * 0.2, 5);
1161 // center the text, verically
1162 var paddingTop = (lost - 5) / 2;
1164 // but check the width too
1165 // it should fit 10 characters in it
1166 var w = that.element_message.clientWidth / 10;
1168 paddingTop += (h - w) / 2;
1172 // and don't make it too huge
1173 // 5% of the screen size is good
1174 if(h > screen.height / 20) {
1175 paddingTop += (h - (screen.height / 20)) / 2;
1176 h = screen.height / 20;
1180 that.element_message.style.fontSize = h.toString() + 'px';
1181 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1184 var showMessage = function(msg) {
1185 that.element_message.className = 'netdata-message';
1186 that.element_message.innerHTML = msg;
1187 that.element_message.style.fontSize = 'x-small';
1188 that.element_message.style.paddingTop = '0px';
1189 that.___messageHidden___ = undefined;
1192 var showMessageIcon = function(icon) {
1193 that.element_message.innerHTML = icon;
1194 that.element_message.className = 'netdata-message icon';
1195 maxMessageFontSize();
1196 that.___messageHidden___ = undefined;
1199 var hideMessage = function() {
1200 if(typeof that.___messageHidden___ === 'undefined') {
1201 that.___messageHidden___ = true;
1202 that.element_message.className = 'netdata-message hidden';
1206 var showRendering = function() {
1208 if(that.chart !== null) {
1209 if(that.chart.chart_type === 'line')
1210 icon = '<i class="fa fa-line-chart"></i>';
1212 icon = '<i class="fa fa-area-chart"></i>';
1215 icon = '<i class="fa fa-area-chart"></i>';
1217 showMessageIcon(icon + ' netdata');
1220 var showLoading = function() {
1221 if(that.chart_created === false) {
1222 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1228 var isHidden = function() {
1229 if(typeof that.___chartIsHidden___ !== 'undefined')
1235 // hide the chart, when it is not visible - called from isVisible()
1236 var hideChart = function() {
1237 // hide it, if it is not already hidden
1238 if(isHidden() === true) return;
1240 if(that.chart_created === true) {
1241 // we should destroy it
1242 if(NETDATA.options.current.destroy_on_hide === true) {
1247 that.element_chart.style.display = 'none';
1248 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1249 that.tm.last_hidden = new Date().getTime();
1253 that.___chartIsHidden___ = true;
1256 // unhide the chart, when it is visible - called from isVisible()
1257 var unhideChart = function() {
1258 if(isHidden() === false) return;
1260 that.___chartIsHidden___ = undefined;
1261 that.updates_since_last_unhide = 0;
1263 if(that.chart_created === false) {
1264 // we need to re-initialize it, to show our background
1265 // logo in bootstrap tabs, until the chart loads
1269 that.tm.last_unhidden = new Date().getTime();
1270 that.element_chart.style.display = '';
1271 if(that.element_legend !== null) that.element_legend.style.display = '';
1277 var canBeRendered = function() {
1278 if(isHidden() === true || that.isVisible(true) === false)
1284 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1285 var callChartLibraryUpdateSafely = function(data) {
1288 if(canBeRendered() === false)
1291 if(NETDATA.options.debug.chart_errors === true)
1292 status = that.library.update(that, data);
1295 status = that.library.update(that, data);
1302 if(status === false) {
1303 error('chart failed to be updated as ' + that.library_name);
1310 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1311 var callChartLibraryCreateSafely = function(data) {
1314 if(canBeRendered() === false)
1317 if(NETDATA.options.debug.chart_errors === true)
1318 status = that.library.create(that, data);
1321 status = that.library.create(that, data);
1328 if(status === false) {
1329 error('chart failed to be created as ' + that.library_name);
1333 that.chart_created = true;
1334 that.updates_since_last_creation = 0;
1338 // ----------------------------------------------------------------------------------------------------------------
1341 // resizeChart() - private
1342 // to be called just before the chart library to make sure that
1343 // a properly sized dom is available
1344 var resizeChart = function() {
1345 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1346 if(that.chart_created === false) return;
1348 if(that.needsRecreation()) {
1351 else if(typeof that.library.resize === 'function') {
1352 that.library.resize(that);
1354 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1355 $(that.element_legend_childs.nano).nanoScroller();
1357 maxMessageFontSize();
1360 that.tm.last_resized = new Date().getTime();
1364 // this is the actual chart resize algorithm
1366 // - resize the entire container
1367 // - update the internal states
1368 // - resize the chart as the div changes height
1369 // - update the scrollbar of the legend
1370 var resizeChartToHeight = function(h) {
1372 that.element.style.height = h;
1374 if(that.settings_id !== null)
1375 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1377 var now = new Date().getTime();
1378 NETDATA.options.last_page_scroll = now;
1379 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1382 that.tm.last_resized = 0;
1386 this.resizeHandler = function(e) {
1389 if(typeof this.event_resize === 'undefined'
1390 || this.event_resize.chart_original_w === 'undefined'
1391 || this.event_resize.chart_original_h === 'undefined')
1392 this.event_resize = {
1393 chart_original_w: this.element.clientWidth,
1394 chart_original_h: this.element.clientHeight,
1398 if(e.type === 'touchstart') {
1399 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1400 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1403 this.event_resize.mouse_start_x = e.clientX;
1404 this.event_resize.mouse_start_y = e.clientY;
1407 this.event_resize.chart_start_w = this.element.clientWidth;
1408 this.event_resize.chart_start_h = this.element.clientHeight;
1409 this.event_resize.chart_last_w = this.element.clientWidth;
1410 this.event_resize.chart_last_h = this.element.clientHeight;
1412 var now = new Date().getTime();
1413 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1414 // double click / double tap event
1416 // the optimal height of the chart
1417 // showing the entire legend
1418 var optimal = this.event_resize.chart_last_h
1419 + this.element_legend_childs.content.scrollHeight
1420 - this.element_legend_childs.content.clientHeight;
1422 // if we are not optimal, be optimal
1423 if(this.event_resize.chart_last_h != optimal)
1424 resizeChartToHeight(optimal.toString() + 'px');
1426 // else if we do not have the original height
1427 // reset to the original height
1428 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1429 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1432 this.event_resize.last = now;
1434 // process movement event
1435 document.onmousemove =
1436 document.ontouchmove =
1437 this.element_legend_childs.resize_handler.onmousemove =
1438 this.element_legend_childs.resize_handler.ontouchmove =
1443 case 'mousemove': y = e.clientY; break;
1444 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1448 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1450 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1451 resizeChartToHeight(newH.toString() + 'px');
1452 that.event_resize.chart_last_h = newH;
1457 // process end event
1458 document.onmouseup =
1459 document.ontouchend =
1460 this.element_legend_childs.resize_handler.onmouseup =
1461 this.element_legend_childs.resize_handler.ontouchend =
1463 // remove all the hooks
1464 document.onmouseup =
1465 document.onmousemove =
1466 document.ontouchmove =
1467 document.ontouchend =
1468 that.element_legend_childs.resize_handler.onmousemove =
1469 that.element_legend_childs.resize_handler.ontouchmove =
1470 that.element_legend_childs.resize_handler.onmouseout =
1471 that.element_legend_childs.resize_handler.onmouseup =
1472 that.element_legend_childs.resize_handler.ontouchend =
1475 // allow auto-refreshes
1476 NETDATA.options.auto_refresher_stop_until = 0;
1482 var noDataToShow = function() {
1483 showMessageIcon('<i class="fa fa-warning"></i> empty');
1484 that.legendUpdateDOM();
1485 that.tm.last_autorefreshed = new Date().getTime();
1486 // that.data_update_every = 30 * 1000;
1487 //that.element_chart.style.display = 'none';
1488 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1489 //that.___chartIsHidden___ = true;
1492 // ============================================================================================================
1495 this.error = function(msg) {
1499 this.setMode = function(m) {
1500 if(this.current !== null && this.current.name === m) return;
1503 this.current = this.auto;
1504 else if(m === 'pan')
1505 this.current = this.pan;
1506 else if(m === 'zoom')
1507 this.current = this.zoom;
1509 this.current = this.auto;
1511 this.current.force_update_at = 0;
1512 this.current.force_before_ms = null;
1513 this.current.force_after_ms = null;
1515 this.tm.last_mode_switch = new Date().getTime();
1518 // ----------------------------------------------------------------------------------------------------------------
1519 // global selection sync
1521 // prevent to global selection sync for some time
1522 this.globalSelectionSyncDelay = function(ms) {
1523 if(NETDATA.options.current.sync_selection === false)
1526 if(typeof ms === 'number')
1527 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1529 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1532 // can we globally apply selection sync?
1533 this.globalSelectionSyncAbility = function() {
1534 if(NETDATA.options.current.sync_selection === false)
1537 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1543 this.globalSelectionSyncIsMaster = function() {
1544 if(NETDATA.globalSelectionSync.state === this)
1550 // this chart is the master of the global selection sync
1551 this.globalSelectionSyncBeMaster = function() {
1553 if(this.globalSelectionSyncIsMaster()) {
1554 if(this.debug === true)
1555 this.log('sync: I am the master already.');
1560 if(NETDATA.globalSelectionSync.state) {
1561 if(this.debug === true)
1562 this.log('sync: I am not the sync master. Resetting global sync.');
1564 this.globalSelectionSyncStop();
1567 // become the master
1568 if(this.debug === true)
1569 this.log('sync: becoming sync master.');
1571 this.selected = true;
1572 NETDATA.globalSelectionSync.state = this;
1574 // find the all slaves
1575 var targets = NETDATA.options.targets;
1576 var len = targets.length;
1581 if(this.debug === true)
1582 st.log('sync: not adding me to sync');
1584 else if(st.globalSelectionSyncIsEligible()) {
1585 if(this.debug === true)
1586 st.log('sync: adding to sync as slave');
1588 st.globalSelectionSyncBeSlave();
1592 // this.globalSelectionSyncDelay(100);
1595 // can the chart participate to the global selection sync as a slave?
1596 this.globalSelectionSyncIsEligible = function() {
1597 if(this.enabled === true
1598 && this.library !== null
1599 && typeof this.library.setSelection === 'function'
1600 && this.isVisible() === true
1601 && this.chart_created === true)
1607 // this chart becomes a slave of the global selection sync
1608 this.globalSelectionSyncBeSlave = function() {
1609 if(NETDATA.globalSelectionSync.state !== this)
1610 NETDATA.globalSelectionSync.slaves.push(this);
1613 // sync all the visible charts to the given time
1614 // this is to be called from the chart libraries
1615 this.globalSelectionSync = function(t) {
1616 if(this.globalSelectionSyncAbility() === false) {
1617 if(this.debug === true)
1618 this.log('sync: cannot sync (yet?).');
1623 if(this.globalSelectionSyncIsMaster() === false) {
1624 if(this.debug === true)
1625 this.log('sync: trying to be sync master.');
1627 this.globalSelectionSyncBeMaster();
1629 if(this.globalSelectionSyncAbility() === false) {
1630 if(this.debug === true)
1631 this.log('sync: cannot sync (yet?).');
1637 NETDATA.globalSelectionSync.last_t = t;
1638 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1643 // stop syncing all charts to the given time
1644 this.globalSelectionSyncStop = function() {
1645 if(NETDATA.globalSelectionSync.slaves.length) {
1646 if(this.debug === true)
1647 this.log('sync: cleaning up...');
1649 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1651 if(that.debug === true)
1652 st.log('sync: not adding me to sync stop');
1655 if(that.debug === true)
1656 st.log('sync: removed slave from sync');
1658 st.clearSelection();
1662 NETDATA.globalSelectionSync.last_t = 0;
1663 NETDATA.globalSelectionSync.slaves = [];
1664 NETDATA.globalSelectionSync.state = null;
1667 this.clearSelection();
1670 this.setSelection = function(t) {
1671 if(typeof this.library.setSelection === 'function') {
1672 if(this.library.setSelection(this, t) === true)
1673 this.selected = true;
1675 this.selected = false;
1677 else this.selected = true;
1679 if(this.selected === true && this.debug === true)
1680 this.log('selection set to ' + t.toString());
1682 return this.selected;
1685 this.clearSelection = function() {
1686 if(this.selected === true) {
1687 if(typeof this.library.clearSelection === 'function') {
1688 if(this.library.clearSelection(this) === true)
1689 this.selected = false;
1691 this.selected = true;
1693 else this.selected = false;
1695 if(this.selected === false && this.debug === true)
1696 this.log('selection cleared');
1701 return this.selected;
1704 // find if a timestamp (ms) is shown in the current chart
1705 this.timeIsVisible = function(t) {
1706 if(t >= this.data_after && t <= this.data_before)
1711 this.calculateRowForTime = function(t) {
1712 if(this.timeIsVisible(t) === false) return -1;
1713 return Math.floor((t - this.data_after) / this.data_update_every);
1716 // ----------------------------------------------------------------------------------------------------------------
1719 this.log = function(msg) {
1720 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1723 this.pauseChart = function() {
1724 if(this.paused === false) {
1725 if(this.debug === true)
1726 this.log('pauseChart()');
1732 this.unpauseChart = function() {
1733 if(this.paused === true) {
1734 if(this.debug === true)
1735 this.log('unpauseChart()');
1737 this.paused = false;
1741 this.resetChart = function(dont_clear_master, dont_update) {
1742 if(this.debug === true)
1743 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1745 if(typeof dont_clear_master === 'undefined')
1746 dont_clear_master = false;
1748 if(typeof dont_update === 'undefined')
1749 dont_update = false;
1751 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1752 if(this.debug === true)
1753 this.log('resetChart() diverting to clearMaster().');
1754 // this will call us back with master === true
1755 NETDATA.globalPanAndZoom.clearMaster();
1759 this.clearSelection();
1761 this.tm.pan_and_zoom_seq = 0;
1763 this.setMode('auto');
1764 this.current.force_update_at = 0;
1765 this.current.force_before_ms = null;
1766 this.current.force_after_ms = null;
1767 this.tm.last_autorefreshed = 0;
1768 this.paused = false;
1769 this.selected = false;
1770 this.enabled = true;
1771 // this.debug = false;
1773 // do not update the chart here
1774 // or the chart will flip-flop when it is the master
1775 // of a selection sync and another chart becomes
1778 if(dont_update !== true && this.isVisible() === true) {
1783 this.updateChartPanOrZoom = function(after, before) {
1784 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1787 if(this.debug === true)
1790 if(before < after) {
1791 if(this.debug === true)
1792 this.log(logme + 'flipped parameters, rejecting it.');
1797 if(typeof this.fixed_min_duration === 'undefined')
1798 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1800 var min_duration = this.fixed_min_duration;
1801 var current_duration = Math.round(this.view_before - this.view_after);
1803 // round the numbers
1804 after = Math.round(after);
1805 before = Math.round(before);
1807 // align them to update_every
1808 // stretching them further away
1809 after -= after % this.data_update_every;
1810 before += this.data_update_every - (before % this.data_update_every);
1812 // the final wanted duration
1813 var wanted_duration = before - after;
1815 // to allow panning, accept just a point below our minimum
1816 if((current_duration - this.data_update_every) < min_duration)
1817 min_duration = current_duration - this.data_update_every;
1819 // we do it, but we adjust to minimum size and return false
1820 // when the wanted size is below the current and the minimum
1822 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1823 if(this.debug === true)
1824 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1826 min_duration = this.fixed_min_duration;
1828 var dt = (min_duration - wanted_duration) / 2;
1831 wanted_duration = before - after;
1835 var tolerance = this.data_update_every * 2;
1836 var movement = Math.abs(before - this.view_before);
1838 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1839 if(this.debug === true)
1840 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);
1844 if(this.current.name === 'auto') {
1845 this.log(logme + 'caller called me with mode: ' + this.current.name);
1846 this.setMode('pan');
1849 if(this.debug === true)
1850 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);
1852 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1853 this.current.force_after_ms = after;
1854 this.current.force_before_ms = before;
1855 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1859 this.legendFormatValue = function(value) {
1860 if(value === null || value === 'undefined') return '-';
1861 if(typeof value !== 'number') return value;
1863 var abs = Math.abs(value);
1864 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1865 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1866 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1867 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1868 return (Math.round(value * 10000) / 10000).toLocaleString();
1871 this.legendSetLabelValue = function(label, value) {
1872 var series = this.element_legend_childs.series[label];
1873 if(typeof series === 'undefined') return;
1874 if(series.value === null && series.user === null) return;
1876 // if the value has not changed, skip DOM update
1877 //if(series.last === value) return;
1880 if(typeof value === 'number') {
1881 var v = Math.abs(value);
1882 s = r = this.legendFormatValue(value);
1884 if(typeof series.last === 'number') {
1885 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1886 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1887 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1889 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1894 series.last = value;
1897 if(series.value !== null) series.value.innerHTML = s;
1898 if(series.user !== null) series.user.innerHTML = r;
1901 this.legendSetDate = function(ms) {
1902 if(typeof ms !== 'number') {
1903 this.legendShowUndefined();
1907 var d = new Date(ms);
1909 if(this.element_legend_childs.title_date)
1910 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1912 if(this.element_legend_childs.title_time)
1913 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1915 if(this.element_legend_childs.title_units)
1916 this.element_legend_childs.title_units.innerHTML = this.units;
1919 this.legendShowUndefined = function() {
1920 if(this.element_legend_childs.title_date)
1921 this.element_legend_childs.title_date.innerHTML = ' ';
1923 if(this.element_legend_childs.title_time)
1924 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1926 if(this.element_legend_childs.title_units)
1927 this.element_legend_childs.title_units.innerHTML = ' ';
1929 if(this.data && this.element_legend_childs.series !== null) {
1930 var labels = this.data.dimension_names;
1931 var i = labels.length;
1933 var label = labels[i];
1935 if(typeof label === 'undefined') continue;
1936 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1937 this.legendSetLabelValue(label, null);
1942 this.legendShowLatestValues = function() {
1943 if(this.chart === null) return;
1944 if(this.selected) return;
1946 if(this.data === null || this.element_legend_childs.series === null) {
1947 this.legendShowUndefined();
1951 var show_undefined = true;
1952 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1953 show_undefined = false;
1955 if(show_undefined) {
1956 this.legendShowUndefined();
1960 this.legendSetDate(this.view_before);
1962 var labels = this.data.dimension_names;
1963 var i = labels.length;
1965 var label = labels[i];
1967 if(typeof label === 'undefined') continue;
1968 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1971 this.legendSetLabelValue(label, null);
1973 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1977 this.legendReset = function() {
1978 this.legendShowLatestValues();
1981 // this should be called just ONCE per dimension per chart
1982 this._chartDimensionColor = function(label) {
1983 if(this.colors === null) this.chartColors();
1985 if(typeof this.colors_assigned[label] === 'undefined') {
1986 if(this.colors_available.length === 0) {
1987 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1988 this.colors_available.push(NETDATA.themes.current.colors[i]);
1991 this.colors_assigned[label] = this.colors_available.shift();
1993 if(this.debug === true)
1994 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1997 if(this.debug === true)
1998 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2001 this.colors.push(this.colors_assigned[label]);
2002 return this.colors_assigned[label];
2005 this.chartColors = function() {
2006 if(this.colors !== null) return this.colors;
2008 this.colors = new Array();
2009 this.colors_available = new Array();
2012 var c = $(this.element).data('colors');
2013 // this.log('read colors: ' + c);
2014 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2015 if(typeof c !== 'string') {
2016 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2023 for(i = 0, len = c.length; i < len ; i++) {
2025 this.colors_available.push(c[i]);
2026 // this.log('adding color: ' + c[i]);
2032 // push all the standard colors too
2033 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2034 this.colors_available.push(NETDATA.themes.current.colors[i]);
2039 this.legendUpdateDOM = function() {
2042 // check that the legend DOM is up to date for the downloaded dimensions
2043 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2044 // this.log('the legend does not have any series - requesting legend update');
2047 else if(this.data === null) {
2048 // this.log('the chart does not have any data - requesting legend update');
2051 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2055 var labels = this.data.dimension_names.toString();
2056 if(labels !== this.element_legend_childs.series.labels_key) {
2059 if(this.debug === true)
2060 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2064 if(needed === false) {
2065 // make sure colors available
2068 // do we have to update the current values?
2069 // we do this, only when the visible chart is current
2070 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2071 if(this.debug === true)
2072 this.log('chart is in latest position... updating values on legend...');
2074 //var labels = this.data.dimension_names;
2075 //var i = labels.length;
2077 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2081 if(this.colors === null) {
2082 // this is the first time we update the chart
2083 // let's assign colors to all dimensions
2084 if(this.library.track_colors() === true)
2085 for(var dim in this.chart.dimensions)
2086 this._chartDimensionColor(this.chart.dimensions[dim].name);
2088 // we will re-generate the colors for the chart
2089 // based on the selected dimensions
2092 if(this.debug === true)
2093 this.log('updating Legend DOM');
2095 // mark all dimensions as invalid
2096 this.dimensions_visibility.invalidateAll();
2098 var genLabel = function(state, parent, name, count) {
2099 var color = state._chartDimensionColor(name);
2101 var user_element = null;
2102 var user_id = self.data('show-value-of-' + name + '-at') || null;
2103 if(user_id !== null) {
2104 user_element = document.getElementById(user_id) || null;
2105 if(user_element === null)
2106 state.log('Cannot find element with id: ' + user_id);
2109 state.element_legend_childs.series[name] = {
2110 name: document.createElement('span'),
2111 value: document.createElement('span'),
2116 var label = state.element_legend_childs.series[name];
2118 // create the dimension visibility tracking for this label
2119 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2121 var rgb = NETDATA.colorHex2Rgb(color);
2122 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2123 + state.chart.chart_type
2124 + '" style="background-color: '
2125 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2126 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2128 var text = document.createTextNode(' ' + name);
2129 label.name.appendChild(text);
2132 parent.appendChild(document.createElement('br'));
2134 parent.appendChild(label.name);
2135 parent.appendChild(label.value);
2138 var content = document.createElement('div');
2140 if(this.hasLegend()) {
2141 this.element_legend_childs = {
2143 resize_handler: document.createElement('div'),
2144 toolbox: document.createElement('div'),
2145 toolbox_left: document.createElement('div'),
2146 toolbox_right: document.createElement('div'),
2147 toolbox_reset: document.createElement('div'),
2148 toolbox_zoomin: document.createElement('div'),
2149 toolbox_zoomout: document.createElement('div'),
2150 toolbox_volume: document.createElement('div'),
2151 title_date: document.createElement('span'),
2152 title_time: document.createElement('span'),
2153 title_units: document.createElement('span'),
2154 nano: document.createElement('div'),
2156 paneClass: 'netdata-legend-series-pane',
2157 sliderClass: 'netdata-legend-series-slider',
2158 contentClass: 'netdata-legend-series-content',
2159 enabledClass: '__enabled',
2160 flashedClass: '__flashed',
2161 activeClass: '__active',
2163 alwaysVisible: true,
2169 this.element_legend.innerHTML = '';
2171 if(this.library.toolboxPanAndZoom !== null) {
2173 function get_pan_and_zoom_step(event) {
2175 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2177 else if (event.shiftKey)
2178 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2180 else if (event.altKey)
2181 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2184 return NETDATA.options.current.pan_and_zoom_factor;
2187 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2188 this.element.appendChild(this.element_legend_childs.toolbox);
2190 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2191 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2192 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2193 this.element_legend_childs.toolbox_left.onclick = function(e) {
2196 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2197 var before = that.view_before - step;
2198 var after = that.view_after - step;
2199 if(after >= that.netdata_first)
2200 that.library.toolboxPanAndZoom(that, after, before);
2202 if(NETDATA.options.current.show_help === true)
2203 $(this.element_legend_childs.toolbox_left).popover({
2208 placement: 'bottom',
2209 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2211 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>'
2215 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2216 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2217 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2218 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2220 NETDATA.resetAllCharts(that);
2222 if(NETDATA.options.current.show_help === true)
2223 $(this.element_legend_childs.toolbox_reset).popover({
2228 placement: 'bottom',
2229 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2230 title: 'Chart Reset',
2231 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>'
2234 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2235 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2236 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2237 this.element_legend_childs.toolbox_right.onclick = function(e) {
2239 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2240 var before = that.view_before + step;
2241 var after = that.view_after + step;
2242 if(before <= that.netdata_last)
2243 that.library.toolboxPanAndZoom(that, after, before);
2245 if(NETDATA.options.current.show_help === true)
2246 $(this.element_legend_childs.toolbox_right).popover({
2251 placement: 'bottom',
2252 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2254 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>'
2258 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2259 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2260 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2261 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2263 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2264 var before = that.view_before - dt;
2265 var after = that.view_after + dt;
2266 that.library.toolboxPanAndZoom(that, after, before);
2268 if(NETDATA.options.current.show_help === true)
2269 $(this.element_legend_childs.toolbox_zoomin).popover({
2274 placement: 'bottom',
2275 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2276 title: 'Chart Zoom In',
2277 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>'
2280 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2281 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2282 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2283 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2285 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);
2286 var before = that.view_before + dt;
2287 var after = that.view_after - dt;
2289 that.library.toolboxPanAndZoom(that, after, before);
2291 if(NETDATA.options.current.show_help === true)
2292 $(this.element_legend_childs.toolbox_zoomout).popover({
2297 placement: 'bottom',
2298 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2299 title: 'Chart Zoom Out',
2300 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>'
2303 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2304 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2305 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2306 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2307 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2308 //e.preventDefault();
2309 //alert('clicked toolbox_volume on ' + that.id);
2313 this.element_legend_childs.toolbox = null;
2314 this.element_legend_childs.toolbox_left = null;
2315 this.element_legend_childs.toolbox_reset = null;
2316 this.element_legend_childs.toolbox_right = null;
2317 this.element_legend_childs.toolbox_zoomin = null;
2318 this.element_legend_childs.toolbox_zoomout = null;
2319 this.element_legend_childs.toolbox_volume = null;
2322 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2323 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2324 this.element.appendChild(this.element_legend_childs.resize_handler);
2325 if(NETDATA.options.current.show_help === true)
2326 $(this.element_legend_childs.resize_handler).popover({
2331 placement: 'bottom',
2332 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2333 title: 'Chart Resize',
2334 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>'
2338 this.element_legend_childs.resize_handler.onmousedown =
2340 that.resizeHandler(e);
2344 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2345 that.resizeHandler(e);
2348 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2349 this.element_legend.appendChild(this.element_legend_childs.title_date);
2351 this.element_legend.appendChild(document.createElement('br'));
2353 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2354 this.element_legend.appendChild(this.element_legend_childs.title_time);
2356 this.element_legend.appendChild(document.createElement('br'));
2358 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2359 this.element_legend.appendChild(this.element_legend_childs.title_units);
2361 this.element_legend.appendChild(document.createElement('br'));
2363 this.element_legend_childs.nano.className = 'netdata-legend-series';
2364 this.element_legend.appendChild(this.element_legend_childs.nano);
2366 content.className = 'netdata-legend-series-content';
2367 this.element_legend_childs.nano.appendChild(content);
2369 if(NETDATA.options.current.show_help === true)
2370 $(content).popover({
2375 placement: 'bottom',
2376 title: 'Chart Legend',
2377 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2378 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>'
2382 this.element_legend_childs = {
2384 resize_handler: null,
2387 toolbox_right: null,
2388 toolbox_reset: null,
2389 toolbox_zoomin: null,
2390 toolbox_zoomout: null,
2391 toolbox_volume: null,
2402 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2403 if(this.debug === true)
2404 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2406 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2407 genLabel(this, content, this.data.dimension_names[i], i);
2411 var tmp = new Array();
2412 for(var dim in this.chart.dimensions) {
2413 tmp.push(this.chart.dimensions[dim].name);
2414 genLabel(this, content, this.chart.dimensions[dim].name, i);
2416 this.element_legend_childs.series.labels_key = tmp.toString();
2417 if(this.debug === true)
2418 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2421 // create a hidden div to be used for hidding
2422 // the original legend of the chart library
2423 var el = document.createElement('div');
2424 if(this.element_legend !== null)
2425 this.element_legend.appendChild(el);
2426 el.style.display = 'none';
2428 this.element_legend_childs.hidden = document.createElement('div');
2429 el.appendChild(this.element_legend_childs.hidden);
2431 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2432 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2434 this.legendShowLatestValues();
2437 this.hasLegend = function() {
2438 if(typeof this.___hasLegendCache___ !== 'undefined')
2439 return this.___hasLegendCache___;
2442 if(this.library && this.library.legend(this) === 'right-side') {
2443 var legend = $(this.element).data('legend') || 'yes';
2444 if(legend === 'yes') leg = true;
2447 this.___hasLegendCache___ = leg;
2451 this.legendWidth = function() {
2452 return (this.hasLegend())?140:0;
2455 this.legendHeight = function() {
2456 return $(this.element).height();
2459 this.chartWidth = function() {
2460 return $(this.element).width() - this.legendWidth();
2463 this.chartHeight = function() {
2464 return $(this.element).height();
2467 this.chartPixelsPerPoint = function() {
2468 // force an options provided detail
2469 var px = this.pixels_per_point;
2471 if(this.library && px < this.library.pixels_per_point(this))
2472 px = this.library.pixels_per_point(this);
2474 if(px < NETDATA.options.current.pixels_per_point)
2475 px = NETDATA.options.current.pixels_per_point;
2480 this.needsRecreation = function() {
2482 this.chart_created === true
2484 && this.library.autoresize() === false
2485 && this.tm.last_resized < NETDATA.options.last_resized
2489 this.chartURL = function() {
2490 var after, before, points_multiplier = 1;
2491 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2492 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2494 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2495 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2496 this.view_after = after * 1000;
2497 this.view_before = before * 1000;
2499 this.requested_padding = null;
2500 points_multiplier = 1;
2502 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2503 this.tm.pan_and_zoom_seq = 0;
2505 before = Math.round(this.current.force_before_ms / 1000);
2506 after = Math.round(this.current.force_after_ms / 1000);
2507 this.view_after = after * 1000;
2508 this.view_before = before * 1000;
2510 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2511 this.requested_padding = Math.round((before - after) / 2);
2512 after -= this.requested_padding;
2513 before += this.requested_padding;
2514 this.requested_padding *= 1000;
2515 points_multiplier = 2;
2518 this.current.force_before_ms = null;
2519 this.current.force_after_ms = null;
2522 this.tm.pan_and_zoom_seq = 0;
2524 before = this.before;
2526 this.view_after = after * 1000;
2527 this.view_before = before * 1000;
2529 this.requested_padding = null;
2530 points_multiplier = 1;
2533 this.requested_after = after * 1000;
2534 this.requested_before = before * 1000;
2536 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2538 // build the data URL
2539 this.data_url = this.host + this.chart.data_url;
2540 this.data_url += "&format=" + this.library.format();
2541 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2542 this.data_url += "&group=" + this.method;
2543 this.data_url += "&options=" + this.library.options(this);
2544 this.data_url += '|jsonwrap';
2546 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2547 this.data_url += '|nonzero';
2549 if(this.append_options !== null)
2550 this.data_url += '|' + this.append_options.toString();
2553 this.data_url += "&after=" + after.toString();
2556 this.data_url += "&before=" + before.toString();
2559 this.data_url += "&dimensions=" + this.dimensions;
2561 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2562 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2565 this.redrawChart = function() {
2566 if(this.data !== null)
2567 this.updateChartWithData(this.data);
2570 this.updateChartWithData = function(data) {
2571 if(this.debug === true)
2572 this.log('updateChartWithData() called.');
2574 this._updating = false;
2576 // this may force the chart to be re-created
2580 this.updates_counter++;
2581 this.updates_since_last_unhide++;
2582 this.updates_since_last_creation++;
2584 var started = new Date().getTime();
2586 // if the result is JSON, find the latest update-every
2587 this.data_update_every = data.view_update_every * 1000;
2588 this.data_after = data.after * 1000;
2589 this.data_before = data.before * 1000;
2590 this.netdata_first = data.first_entry * 1000;
2591 this.netdata_last = data.last_entry * 1000;
2592 this.data_points = data.points;
2595 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2596 if(this.view_after < this.data_after) {
2597 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2598 this.view_after = this.data_after;
2601 if(this.view_before > this.data_before) {
2602 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2603 this.view_before = this.data_before;
2607 this.view_after = this.data_after;
2608 this.view_before = this.data_before;
2611 if(this.debug === true) {
2612 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2614 if(this.current.force_after_ms)
2615 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2617 this.log('STATUS: forced : unset');
2619 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2620 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2621 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2622 this.log('STATUS: points : ' + (this.data_points).toString());
2625 if(this.data_points === 0) {
2630 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2631 if(this.debug === true)
2632 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2634 this.chart_created = false;
2637 // check and update the legend
2638 this.legendUpdateDOM();
2640 if(this.chart_created === true
2641 && typeof this.library.update === 'function') {
2643 if(this.debug === true)
2644 this.log('updating chart...');
2646 if(callChartLibraryUpdateSafely(data) === false)
2650 if(this.debug === true)
2651 this.log('creating chart...');
2653 if(callChartLibraryCreateSafely(data) === false)
2657 this.legendShowLatestValues();
2658 if(this.selected === true)
2659 NETDATA.globalSelectionSync.stop();
2661 // update the performance counters
2662 var now = new Date().getTime();
2663 this.tm.last_updated = now;
2665 // don't update last_autorefreshed if this chart is
2666 // forced to be updated with global PanAndZoom
2667 if(NETDATA.globalPanAndZoom.isActive())
2668 this.tm.last_autorefreshed = 0;
2670 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2671 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2673 this.tm.last_autorefreshed = now;
2676 this.refresh_dt_ms = now - started;
2677 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2679 if(this.refresh_dt_element !== null)
2680 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2683 this.updateChart = function(callback) {
2684 if(this.debug === true)
2685 this.log('updateChart() called.');
2687 if(this._updating === true) {
2688 if(this.debug === true)
2689 this.log('I am already updating...');
2691 if(typeof callback === 'function') callback();
2695 // due to late initialization of charts and libraries
2696 // we need to check this too
2697 if(this.enabled === false) {
2698 if(this.debug === true)
2699 this.log('I am not enabled');
2701 if(typeof callback === 'function') callback();
2705 if(canBeRendered() === false) {
2706 if(typeof callback === 'function') callback();
2710 if(this.chart === null) {
2711 this.getChart(function() { that.updateChart(callback); });
2715 if(this.library.initialized === false) {
2716 if(this.library.enabled === true) {
2717 this.library.initialize(function() { that.updateChart(callback); });
2721 error('chart library "' + this.library_name + '" is not available.');
2722 if(typeof callback === 'function') callback();
2727 this.clearSelection();
2730 if(this.debug === true)
2731 this.log('updating from ' + this.data_url);
2733 this._updating = true;
2735 this.xhr = $.ajax( {
2737 crossDomain: NETDATA.options.crossDomainAjax,
2741 .success(function(data) {
2742 if(that.debug === true)
2743 that.log('data received. updating chart.');
2745 that.updateChartWithData(data);
2748 error('data download failed for url: ' + that.data_url);
2750 .always(function() {
2751 that._updating = false;
2752 if(typeof callback === 'function') callback();
2758 this.isVisible = function(nocache) {
2759 if(typeof nocache === 'undefined')
2762 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2764 // caching - we do not evaluate the charts visibility
2765 // if the page has not been scrolled since the last check
2766 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2767 return this.___isVisible___;
2769 this.tm.last_visible_check = new Date().getTime();
2771 var wh = window.innerHeight;
2772 var x = this.element.getBoundingClientRect();
2776 if(x.width === 0 || x.height === 0) {
2778 this.___isVisible___ = false;
2779 return this.___isVisible___;
2782 if(x.top < 0 && -x.top > x.height) {
2783 // the chart is entirely above
2784 ret = -x.top - x.height;
2786 else if(x.top > wh) {
2787 // the chart is entirely below
2791 if(ret > tolerance) {
2792 // the chart is too far
2795 this.___isVisible___ = false;
2796 return this.___isVisible___;
2799 // the chart is inside or very close
2802 this.___isVisible___ = true;
2803 return this.___isVisible___;
2807 this.isAutoRefreshed = function() {
2808 return (this.current.autorefresh);
2811 this.canBeAutoRefreshed = function() {
2812 var now = new Date().getTime();
2814 if(this.enabled === false) {
2815 if(this.debug === true)
2816 this.log('I am not enabled');
2821 if(this.library === null || this.library.enabled === false) {
2822 error('charting library "' + this.library_name + '" is not available');
2823 if(this.debug === true)
2824 this.log('My chart library ' + this.library_name + ' is not available');
2829 if(this.isVisible() === false) {
2830 if(NETDATA.options.debug.visibility === true || this.debug === true)
2831 this.log('I am not visible');
2836 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2837 if(this.debug === true)
2838 this.log('timed force update detected - allowing this update');
2840 this.current.force_update_at = 0;
2844 if(this.isAutoRefreshed() === true) {
2845 // allow the first update, even if the page is not visible
2846 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2847 if(NETDATA.options.debug.focus === true || this.debug === true)
2848 this.log('canBeAutoRefreshed(): page does not have focus');
2853 if(this.needsRecreation() === true) {
2854 if(this.debug === true)
2855 this.log('canBeAutoRefreshed(): needs re-creation.');
2860 // options valid only for autoRefresh()
2861 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2862 if(NETDATA.globalPanAndZoom.isActive()) {
2863 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2864 if(this.debug === true)
2865 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2870 if(this.debug === true)
2871 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2877 if(this.selected === true) {
2878 if(this.debug === true)
2879 this.log('canBeAutoRefreshed(): I have a selection in place.');
2884 if(this.paused === true) {
2885 if(this.debug === true)
2886 this.log('canBeAutoRefreshed(): I am paused.');
2891 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2892 if(this.debug === true)
2893 this.log('canBeAutoRefreshed(): It is time to update me.');
2903 this.autoRefresh = function(callback) {
2904 if(this.canBeAutoRefreshed() === true) {
2905 this.updateChart(callback);
2908 if(typeof callback !== 'undefined')
2913 this._defaultsFromDownloadedChart = function(chart) {
2915 this.chart_url = chart.url;
2916 this.data_update_every = chart.update_every * 1000;
2917 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2918 this.tm.last_info_downloaded = new Date().getTime();
2920 if(this.title === null)
2921 this.title = chart.title;
2923 if(this.units === null)
2924 this.units = chart.units;
2927 // fetch the chart description from the netdata server
2928 this.getChart = function(callback) {
2929 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2931 this._defaultsFromDownloadedChart(this.chart);
2932 if(typeof callback === 'function') callback();
2935 this.chart_url = "/api/v1/chart?chart=" + this.id;
2937 if(this.debug === true)
2938 this.log('downloading ' + this.chart_url);
2941 url: this.host + this.chart_url,
2942 crossDomain: NETDATA.options.crossDomainAjax,
2946 .done(function(chart) {
2947 chart.url = that.chart_url;
2948 that._defaultsFromDownloadedChart(chart);
2949 NETDATA.chartRegistry.add(that.host, that.id, chart);
2952 NETDATA.error(404, that.chart_url);
2953 error('chart not found on url "' + that.chart_url + '"');
2955 .always(function() {
2956 if(typeof callback === 'function') callback();
2961 // ============================================================================================================
2967 NETDATA.resetAllCharts = function(state) {
2968 // first clear the global selection sync
2969 // to make sure no chart is in selected state
2970 state.globalSelectionSyncStop();
2972 // there are 2 possibilities here
2973 // a. state is the global Pan and Zoom master
2974 // b. state is not the global Pan and Zoom master
2976 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2979 // clear the global Pan and Zoom
2980 // this will also refresh the master
2981 // and unblock any charts currently mirroring the master
2982 NETDATA.globalPanAndZoom.clearMaster();
2984 // if we were not the master, reset our status too
2985 // this is required because most probably the mouse
2986 // is over this chart, blocking it from auto-refreshing
2987 if(master === false && (state.paused === true || state.selected === true))
2991 // get or create a chart state, given a DOM element
2992 NETDATA.chartState = function(element) {
2993 var state = $(element).data('netdata-state-object') || null;
2994 if(state === null) {
2995 state = new chartState(element);
2996 $(element).data('netdata-state-object', state);
3001 // ----------------------------------------------------------------------------------------------------------------
3002 // Library functions
3004 // Load a script without jquery
3005 // This is used to load jquery - after it is loaded, we use jquery
3006 NETDATA._loadjQuery = function(callback) {
3007 if(typeof jQuery === 'undefined') {
3008 if(NETDATA.options.debug.main_loop === true)
3009 console.log('loading ' + NETDATA.jQuery);
3011 var script = document.createElement('script');
3012 script.type = 'text/javascript';
3013 script.async = true;
3014 script.src = NETDATA.jQuery;
3016 // script.onabort = onError;
3017 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3018 if(typeof callback === "function")
3019 script.onload = callback;
3021 var s = document.getElementsByTagName('script')[0];
3022 s.parentNode.insertBefore(script, s);
3024 else if(typeof callback === "function")
3028 NETDATA._loadCSS = function(filename) {
3029 // don't use jQuery here
3030 // styles are loaded before jQuery
3031 // to eliminate showing an unstyled page to the user
3033 var fileref = document.createElement("link");
3034 fileref.setAttribute("rel", "stylesheet");
3035 fileref.setAttribute("type", "text/css");
3036 fileref.setAttribute("href", filename);
3038 if (typeof fileref !== 'undefined')
3039 document.getElementsByTagName("head")[0].appendChild(fileref);
3042 NETDATA.colorHex2Rgb = function(hex) {
3043 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3044 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3045 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3046 return r + r + g + g + b + b;
3049 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3051 r: parseInt(result[1], 16),
3052 g: parseInt(result[2], 16),
3053 b: parseInt(result[3], 16)
3057 NETDATA.colorLuminance = function(hex, lum) {
3058 // validate hex string
3059 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3061 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3065 // convert to decimal and change luminosity
3066 var rgb = "#", c, i;
3067 for (i = 0; i < 3; i++) {
3068 c = parseInt(hex.substr(i*2,2), 16);
3069 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3070 rgb += ("00"+c).substr(c.length);
3076 NETDATA.guid = function() {
3078 return Math.floor((1 + Math.random()) * 0x10000)
3083 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3086 NETDATA.zeropad = function(x) {
3087 if(x > -10 && x < 10) return '0' + x.toString();
3088 else return x.toString();
3091 // user function to signal us the DOM has been
3093 NETDATA.updatedDom = function() {
3094 NETDATA.options.updated_dom = true;
3097 NETDATA.ready = function(callback) {
3098 NETDATA.options.pauseCallback = callback;
3101 NETDATA.pause = function(callback) {
3102 if(NETDATA.options.pause === true)
3105 NETDATA.options.pauseCallback = callback;
3108 NETDATA.unpause = function() {
3109 NETDATA.options.pauseCallback = null;
3110 NETDATA.options.updated_dom = true;
3111 NETDATA.options.pause = false;
3114 // ----------------------------------------------------------------------------------------------------------------
3116 // this is purely sequencial charts refresher
3117 // it is meant to be autonomous
3118 NETDATA.chartRefresherNoParallel = function(index) {
3119 if(NETDATA.options.debug.mail_loop === true)
3120 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3122 if(NETDATA.options.updated_dom === true) {
3123 // the dom has been updated
3124 // get the dom parts again
3125 NETDATA.parseDom(NETDATA.chartRefresher);
3128 if(index >= NETDATA.options.targets.length) {
3129 if(NETDATA.options.debug.main_loop === true)
3130 console.log('waiting to restart main loop...');
3132 NETDATA.options.auto_refresher_fast_weight = 0;
3134 setTimeout(function() {
3135 NETDATA.chartRefresher();
3136 }, NETDATA.options.current.idle_between_loops);
3139 var state = NETDATA.options.targets[index];
3141 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3142 if(NETDATA.options.debug.main_loop === true)
3143 console.log('fast rendering...');
3145 state.autoRefresh(function() {
3146 NETDATA.chartRefresherNoParallel(++index);
3150 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3151 NETDATA.options.auto_refresher_fast_weight = 0;
3153 setTimeout(function() {
3154 state.autoRefresh(function() {
3155 NETDATA.chartRefresherNoParallel(++index);
3157 }, NETDATA.options.current.idle_between_charts);
3162 // this is part of the parallel refresher
3163 // its cause is to refresh sequencially all the charts
3164 // that depend on chart library initialization
3165 // it will call the parallel refresher back
3166 // as soon as it sees a chart that its chart library
3168 NETDATA.chartRefresher_uninitialized = function() {
3169 if(NETDATA.options.updated_dom === true) {
3170 // the dom has been updated
3171 // get the dom parts again
3172 NETDATA.parseDom(NETDATA.chartRefresher);
3176 if(NETDATA.options.sequencial.length === 0)
3177 NETDATA.chartRefresher();
3179 var state = NETDATA.options.sequencial.pop();
3180 if(state.library.initialized === true)
3181 NETDATA.chartRefresher();
3183 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3187 NETDATA.chartRefresherWaitTime = function() {
3188 return NETDATA.options.current.idle_parallel_loops;
3191 // the default refresher
3192 // it will create 2 sets of charts:
3193 // - the ones that can be refreshed in parallel
3194 // - the ones that depend on something else
3195 // the first set will be executed in parallel
3196 // the second will be given to NETDATA.chartRefresher_uninitialized()
3197 NETDATA.chartRefresher = function() {
3198 if(NETDATA.options.pause === true) {
3199 // console.log('auto-refresher is paused');
3200 setTimeout(NETDATA.chartRefresher,
3201 NETDATA.chartRefresherWaitTime());
3205 if(typeof NETDATA.options.pauseCallback === 'function') {
3206 // console.log('auto-refresher is calling pauseCallback');
3207 NETDATA.options.pause = true;
3208 NETDATA.options.pauseCallback();
3209 NETDATA.chartRefresher();
3213 if(NETDATA.options.current.parallel_refresher === false) {
3214 NETDATA.chartRefresherNoParallel(0);
3218 if(NETDATA.options.updated_dom === true) {
3219 // the dom has been updated
3220 // get the dom parts again
3221 NETDATA.parseDom(NETDATA.chartRefresher);
3225 var parallel = new Array();
3226 var targets = NETDATA.options.targets;
3227 var len = targets.length;
3229 if(targets[len].isVisible() === false)
3232 var state = targets[len];
3233 if(state.library.initialized === false) {
3234 if(state.library.enabled === true) {
3235 state.library.initialize(NETDATA.chartRefresher);
3239 state.error('chart library "' + state.library_name + '" is not enabled.');
3243 parallel.unshift(state);
3246 if(parallel.length > 0) {
3247 var parallel_jobs = parallel.length;
3249 // this will execute the jobs in parallel
3250 $(parallel).each(function() {
3251 this.autoRefresh(function() {
3254 if(parallel_jobs === 0) {
3255 setTimeout(NETDATA.chartRefresher,
3256 NETDATA.chartRefresherWaitTime());
3262 setTimeout(NETDATA.chartRefresher,
3263 NETDATA.chartRefresherWaitTime());
3267 NETDATA.parseDom = function(callback) {
3268 NETDATA.options.last_page_scroll = new Date().getTime();
3269 NETDATA.options.updated_dom = false;
3271 var targets = $('div[data-netdata]'); //.filter(':visible');
3273 if(NETDATA.options.debug.main_loop === true)
3274 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3276 NETDATA.options.targets = new Array();
3277 var len = targets.length;
3279 // the initialization will take care of sizing
3280 // and the "loading..." message
3281 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3284 if(typeof callback === 'function') callback();
3287 // this is the main function - where everything starts
3288 NETDATA.start = function() {
3289 // this should be called only once
3291 NETDATA.options.page_is_visible = true;
3293 $(window).blur(function() {
3294 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3295 NETDATA.options.page_is_visible = false;
3296 if(NETDATA.options.debug.focus === true)
3297 console.log('Lost Focus!');
3301 $(window).focus(function() {
3302 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3303 NETDATA.options.page_is_visible = true;
3304 if(NETDATA.options.debug.focus === true)
3305 console.log('Focus restored!');
3309 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3310 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3311 NETDATA.options.page_is_visible = false;
3312 if(NETDATA.options.debug.focus === true)
3313 console.log('Document has no focus!');
3317 // bootstrap tab switching
3318 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3320 // bootstrap modal switching
3321 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3322 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3324 NETDATA.parseDom(NETDATA.chartRefresher);
3327 // ----------------------------------------------------------------------------------------------------------------
3330 NETDATA.peityInitialize = function(callback) {
3331 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3333 url: NETDATA.peity_js,
3338 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3341 NETDATA.chartLibraries.peity.enabled = false;
3342 NETDATA.error(100, NETDATA.peity_js);
3344 .always(function() {
3345 if(typeof callback === "function")
3350 NETDATA.chartLibraries.peity.enabled = false;
3351 if(typeof callback === "function")
3356 NETDATA.peityChartUpdate = function(state, data) {
3357 state.peity_instance.innerHTML = data.result;
3359 if(state.peity_options.stroke !== state.chartColors()[0]) {
3360 state.peity_options.stroke = state.chartColors()[0];
3361 if(state.chart.chart_type === 'line')
3362 state.peity_options.fill = NETDATA.themes.current.background;
3364 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3367 $(state.peity_instance).peity('line', state.peity_options);
3371 NETDATA.peityChartCreate = function(state, data) {
3372 state.peity_instance = document.createElement('div');
3373 state.element_chart.appendChild(state.peity_instance);
3375 var self = $(state.element);
3376 state.peity_options = {
3377 stroke: NETDATA.themes.current.foreground,
3378 strokeWidth: self.data('peity-strokewidth') || 1,
3379 width: state.chartWidth(),
3380 height: state.chartHeight(),
3381 fill: NETDATA.themes.current.foreground
3384 NETDATA.peityChartUpdate(state, data);
3388 // ----------------------------------------------------------------------------------------------------------------
3391 NETDATA.sparklineInitialize = function(callback) {
3392 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3394 url: NETDATA.sparkline_js,
3399 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3402 NETDATA.chartLibraries.sparkline.enabled = false;
3403 NETDATA.error(100, NETDATA.sparkline_js);
3405 .always(function() {
3406 if(typeof callback === "function")
3411 NETDATA.chartLibraries.sparkline.enabled = false;
3412 if(typeof callback === "function")
3417 NETDATA.sparklineChartUpdate = function(state, data) {
3418 state.sparkline_options.width = state.chartWidth();
3419 state.sparkline_options.height = state.chartHeight();
3421 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3425 NETDATA.sparklineChartCreate = function(state, data) {
3426 var self = $(state.element);
3427 var type = self.data('sparkline-type') || 'line';
3428 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3429 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3430 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3431 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3432 var composite = self.data('sparkline-composite') || undefined;
3433 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3434 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3435 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3436 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3437 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3438 var spotColor = self.data('sparkline-spotcolor') || undefined;
3439 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3440 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3441 var spotRadius = self.data('sparkline-spotradius') || undefined;
3442 var valueSpots = self.data('sparkline-valuespots') || undefined;
3443 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3444 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3445 var lineWidth = self.data('sparkline-linewidth') || undefined;
3446 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3447 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3448 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3449 var xvalues = self.data('sparkline-xvalues') || undefined;
3450 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3451 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3452 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3453 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3454 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3455 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3456 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3457 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3458 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3459 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3460 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3461 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3462 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3463 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3464 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3465 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3466 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3467 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3468 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3469 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3470 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3471 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3473 state.sparkline_options = {
3475 lineColor: lineColor,
3476 fillColor: fillColor,
3477 chartRangeMin: chartRangeMin,
3478 chartRangeMax: chartRangeMax,
3479 composite: composite,
3480 enableTagOptions: enableTagOptions,
3481 tagOptionPrefix: tagOptionPrefix,
3482 tagValuesAttribute: tagValuesAttribute,
3483 disableHiddenCheck: disableHiddenCheck,
3484 defaultPixelsPerValue: defaultPixelsPerValue,
3485 spotColor: spotColor,
3486 minSpotColor: minSpotColor,
3487 maxSpotColor: maxSpotColor,
3488 spotRadius: spotRadius,
3489 valueSpots: valueSpots,
3490 highlightSpotColor: highlightSpotColor,
3491 highlightLineColor: highlightLineColor,
3492 lineWidth: lineWidth,
3493 normalRangeMin: normalRangeMin,
3494 normalRangeMax: normalRangeMax,
3495 drawNormalOnTop: drawNormalOnTop,
3497 chartRangeClip: chartRangeClip,
3498 chartRangeMinX: chartRangeMinX,
3499 chartRangeMaxX: chartRangeMaxX,
3500 disableInteraction: disableInteraction,
3501 disableTooltips: disableTooltips,
3502 disableHighlight: disableHighlight,
3503 highlightLighten: highlightLighten,
3504 highlightColor: highlightColor,
3505 tooltipContainer: tooltipContainer,
3506 tooltipClassname: tooltipClassname,
3507 tooltipChartTitle: state.title,
3508 tooltipFormat: tooltipFormat,
3509 tooltipPrefix: tooltipPrefix,
3510 tooltipSuffix: tooltipSuffix,
3511 tooltipSkipNull: tooltipSkipNull,
3512 tooltipValueLookups: tooltipValueLookups,
3513 tooltipFormatFieldlist: tooltipFormatFieldlist,
3514 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3515 numberFormatter: numberFormatter,
3516 numberDigitGroupSep: numberDigitGroupSep,
3517 numberDecimalMark: numberDecimalMark,
3518 numberDigitGroupCount: numberDigitGroupCount,
3519 animatedZooms: animatedZooms,
3520 width: state.chartWidth(),
3521 height: state.chartHeight()
3524 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3528 // ----------------------------------------------------------------------------------------------------------------
3535 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3536 if(after < state.netdata_first)
3537 after = state.netdata_first;
3539 if(before > state.netdata_last)
3540 before = state.netdata_last;
3542 state.setMode('zoom');
3543 state.globalSelectionSyncStop();
3544 state.globalSelectionSyncDelay();
3545 state.dygraph_user_action = true;
3546 state.dygraph_force_zoom = true;
3547 state.updateChartPanOrZoom(after, before);
3548 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3551 NETDATA.dygraphSetSelection = function(state, t) {
3552 if(typeof state.dygraph_instance !== 'undefined') {
3553 var r = state.calculateRowForTime(t);
3555 state.dygraph_instance.setSelection(r);
3557 state.dygraph_instance.clearSelection();
3558 state.legendShowUndefined();
3565 NETDATA.dygraphClearSelection = function(state, t) {
3566 if(typeof state.dygraph_instance !== 'undefined') {
3567 state.dygraph_instance.clearSelection();
3572 NETDATA.dygraphSmoothInitialize = function(callback) {
3574 url: NETDATA.dygraph_smooth_js,
3579 NETDATA.dygraph.smooth = true;
3580 smoothPlotter.smoothing = 0.3;
3583 NETDATA.dygraph.smooth = false;
3585 .always(function() {
3586 if(typeof callback === "function")
3591 NETDATA.dygraphInitialize = function(callback) {
3592 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3594 url: NETDATA.dygraph_js,
3599 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3602 NETDATA.chartLibraries.dygraph.enabled = false;
3603 NETDATA.error(100, NETDATA.dygraph_js);
3605 .always(function() {
3606 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3607 NETDATA.dygraphSmoothInitialize(callback);
3608 else if(typeof callback === "function")
3613 NETDATA.chartLibraries.dygraph.enabled = false;
3614 if(typeof callback === "function")
3619 NETDATA.dygraphChartUpdate = function(state, data) {
3620 var dygraph = state.dygraph_instance;
3622 if(typeof dygraph === 'undefined')
3623 return NETDATA.dygraphChartCreate(state, data);
3625 // when the chart is not visible, and hidden
3626 // if there is a window resize, dygraph detects
3627 // its element size as 0x0.
3628 // this will make it re-appear properly
3630 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3634 file: data.result.data,
3635 colors: state.chartColors(),
3636 labels: data.result.labels,
3637 labelsDivWidth: state.chartWidth() - 70,
3638 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3641 if(state.dygraph_force_zoom === true) {
3642 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3643 state.log('dygraphChartUpdate() forced zoom update');
3645 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3646 options.valueRange = null;
3647 options.isZoomedIgnoreProgrammaticZoom = true;
3648 state.dygraph_force_zoom = false;
3650 else if(state.current.name !== 'auto') {
3651 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3652 state.log('dygraphChartUpdate() loose update');
3655 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3656 state.log('dygraphChartUpdate() strict update');
3658 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3659 options.valueRange = null;
3660 options.isZoomedIgnoreProgrammaticZoom = true;
3663 if(state.dygraph_smooth_eligible === true) {
3664 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3665 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3666 NETDATA.dygraphChartCreate(state, data);
3671 dygraph.updateOptions(options);
3673 state.dygraph_last_rendered = new Date().getTime();
3677 NETDATA.dygraphChartCreate = function(state, data) {
3678 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3679 state.log('dygraphChartCreate()');
3681 var self = $(state.element);
3683 var chart_type = state.chart.chart_type;
3684 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3685 chart_type = self.data('dygraph-type') || chart_type;
3687 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3688 smooth = self.data('dygraph-smooth') || smooth;
3690 if(NETDATA.dygraph.smooth === false)
3693 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3694 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3696 state.dygraph_options = {
3697 colors: self.data('dygraph-colors') || state.chartColors(),
3699 // leave a few pixels empty on the right of the chart
3700 rightGap: self.data('dygraph-rightgap') || 5,
3701 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3702 showRoller: self.data('dygraph-showroller') || false,
3704 title: self.data('dygraph-title') || state.title,
3705 titleHeight: self.data('dygraph-titleheight') || 19,
3707 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3708 labels: data.result.labels,
3709 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3710 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3711 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3712 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3713 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3716 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3717 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3719 ylabel: state.units,
3720 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3722 // the function to plot the chart
3725 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3726 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3727 strokePattern: self.data('dygraph-strokepattern') || undefined,
3729 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3730 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3731 drawPoints: self.data('dygraph-drawpoints') || false,
3733 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3734 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3736 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3737 pointSize: self.data('dygraph-pointsize') || 1,
3739 // enabling this makes the chart with little square lines
3740 stepPlot: self.data('dygraph-stepplot') || false,
3742 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3743 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3744 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3746 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3747 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3748 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3749 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3751 drawAxis: self.data('dygraph-drawaxis') || true,
3752 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3753 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3754 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3756 drawGrid: self.data('dygraph-drawgrid') || true,
3757 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3758 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3759 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3760 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3761 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3763 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3764 sigFigs: self.data('dygraph-sigfigs') || null,
3765 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3766 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3768 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3769 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3770 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3772 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3773 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3777 ticker: Dygraph.dateTicker,
3778 axisLabelFormatter: function (d, gran) {
3779 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3781 valueFormatter: function (ms) {
3782 var d = new Date(ms);
3783 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3784 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3789 valueFormatter: function (x) {
3790 // we format legends with the state object
3791 // no need to do anything here
3792 // return (Math.round(x*100) / 100).toLocaleString();
3793 // return state.legendFormatValue(x);
3798 legendFormatter: function(data) {
3799 var elements = state.element_legend_childs;
3801 // if the hidden div is not there
3802 // we are not managing the legend
3803 if(elements.hidden === null) return;
3805 if (typeof data.x !== 'undefined') {
3806 state.legendSetDate(data.x);
3807 var i = data.series.length;
3809 var series = data.series[i];
3810 if(!series.isVisible) continue;
3811 state.legendSetLabelValue(series.label, series.y);
3817 drawCallback: function(dygraph, is_initial) {
3818 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3819 state.dygraph_user_action = false;
3821 var x_range = dygraph.xAxisRange();
3822 var after = Math.round(x_range[0]);
3823 var before = Math.round(x_range[1]);
3825 if(NETDATA.options.debug.dygraph === true)
3826 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3828 if(before <= state.netdata_last && after >= state.netdata_first)
3829 state.updateChartPanOrZoom(after, before);
3832 zoomCallback: function(minDate, maxDate, yRanges) {
3833 if(NETDATA.options.debug.dygraph === true)
3834 state.log('dygraphZoomCallback()');
3836 state.globalSelectionSyncStop();
3837 state.globalSelectionSyncDelay();
3838 state.setMode('zoom');
3840 // refresh it to the greatest possible zoom level
3841 state.dygraph_user_action = true;
3842 state.dygraph_force_zoom = true;
3843 state.updateChartPanOrZoom(minDate, maxDate);
3845 highlightCallback: function(event, x, points, row, seriesName) {
3846 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3847 state.log('dygraphHighlightCallback()');
3851 // there is a bug in dygraph when the chart is zoomed enough
3852 // the time it thinks is selected is wrong
3853 // here we calculate the time t based on the row number selected
3855 var t = state.data_after + row * state.data_update_every;
3856 // 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);
3858 state.globalSelectionSync(x);
3860 // fix legend zIndex using the internal structures of dygraph legend module
3861 // this works, but it is a hack!
3862 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3864 unhighlightCallback: function(event) {
3865 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3866 state.log('dygraphUnhighlightCallback()');
3868 state.unpauseChart();
3869 state.globalSelectionSyncStop();
3871 interactionModel : {
3872 mousedown: function(event, dygraph, context) {
3873 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3874 state.log('interactionModel.mousedown()');
3876 state.dygraph_user_action = true;
3877 state.globalSelectionSyncStop();
3879 if(NETDATA.options.debug.dygraph === true)
3880 state.log('dygraphMouseDown()');
3882 // Right-click should not initiate a zoom.
3883 if(event.button && event.button === 2) return;
3885 context.initializeMouseDown(event, dygraph, context);
3887 if(event.button && event.button === 1) {
3888 if (event.altKey || event.shiftKey) {
3889 state.setMode('pan');
3890 state.globalSelectionSyncDelay();
3891 Dygraph.startPan(event, dygraph, context);
3894 state.setMode('zoom');
3895 state.globalSelectionSyncDelay();
3896 Dygraph.startZoom(event, dygraph, context);
3900 if (event.altKey || event.shiftKey) {
3901 state.setMode('zoom');
3902 state.globalSelectionSyncDelay();
3903 Dygraph.startZoom(event, dygraph, context);
3906 state.setMode('pan');
3907 state.globalSelectionSyncDelay();
3908 Dygraph.startPan(event, dygraph, context);
3912 mousemove: function(event, dygraph, context) {
3913 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3914 state.log('interactionModel.mousemove()');
3916 if(context.isPanning) {
3917 state.dygraph_user_action = true;
3918 state.globalSelectionSyncStop();
3919 state.globalSelectionSyncDelay();
3920 state.setMode('pan');
3921 Dygraph.movePan(event, dygraph, context);
3923 else if(context.isZooming) {
3924 state.dygraph_user_action = true;
3925 state.globalSelectionSyncStop();
3926 state.globalSelectionSyncDelay();
3927 state.setMode('zoom');
3928 Dygraph.moveZoom(event, dygraph, context);
3931 mouseup: function(event, dygraph, context) {
3932 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3933 state.log('interactionModel.mouseup()');
3935 if (context.isPanning) {
3936 state.dygraph_user_action = true;
3937 state.globalSelectionSyncDelay();
3938 Dygraph.endPan(event, dygraph, context);
3940 else if (context.isZooming) {
3941 state.dygraph_user_action = true;
3942 state.globalSelectionSyncDelay();
3943 Dygraph.endZoom(event, dygraph, context);
3946 click: function(event, dygraph, context) {
3947 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3948 state.log('interactionModel.click()');
3950 event.preventDefault();
3952 dblclick: function(event, dygraph, context) {
3953 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3954 state.log('interactionModel.dblclick()');
3955 NETDATA.resetAllCharts(state);
3957 mousewheel: function(event, dygraph, context) {
3958 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3959 state.log('interactionModel.mousewheel()');
3961 // Take the offset of a mouse event on the dygraph canvas and
3962 // convert it to a pair of percentages from the bottom left.
3963 // (Not top left, bottom is where the lower value is.)
3964 function offsetToPercentage(g, offsetX, offsetY) {
3965 // This is calculating the pixel offset of the leftmost date.
3966 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3967 var yar0 = g.yAxisRange(0);
3969 // This is calculating the pixel of the higest value. (Top pixel)
3970 var yOffset = g.toDomCoords(null, yar0[1])[1];
3972 // x y w and h are relative to the corner of the drawing area,
3973 // so that the upper corner of the drawing area is (0, 0).
3974 var x = offsetX - xOffset;
3975 var y = offsetY - yOffset;
3977 // This is computing the rightmost pixel, effectively defining the
3979 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3981 // This is computing the lowest pixel, effectively defining the height.
3982 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3984 // Percentage from the left.
3985 var xPct = w === 0 ? 0 : (x / w);
3986 // Percentage from the top.
3987 var yPct = h === 0 ? 0 : (y / h);
3989 // The (1-) part below changes it from "% distance down from the top"
3990 // to "% distance up from the bottom".
3991 return [xPct, (1-yPct)];
3994 // Adjusts [x, y] toward each other by zoomInPercentage%
3995 // Split it so the left/bottom axis gets xBias/yBias of that change and
3996 // tight/top gets (1-xBias)/(1-yBias) of that change.
3998 // If a bias is missing it splits it down the middle.
3999 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4000 xBias = xBias || 0.5;
4001 yBias = yBias || 0.5;
4003 function adjustAxis(axis, zoomInPercentage, bias) {
4004 var delta = axis[1] - axis[0];
4005 var increment = delta * zoomInPercentage;
4006 var foo = [increment * bias, increment * (1-bias)];
4008 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4011 var yAxes = g.yAxisRanges();
4013 for (var i = 0; i < yAxes.length; i++) {
4014 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4017 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4020 if(event.altKey || event.shiftKey) {
4021 state.dygraph_user_action = true;
4023 state.globalSelectionSyncStop();
4024 state.globalSelectionSyncDelay();
4026 // http://dygraphs.com/gallery/interaction-api.js
4027 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4028 var percentage = normal / 50;
4030 if (!(event.offsetX && event.offsetY)){
4031 event.offsetX = event.layerX - event.target.offsetLeft;
4032 event.offsetY = event.layerY - event.target.offsetTop;
4035 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4036 var xPct = percentages[0];
4037 var yPct = percentages[1];
4039 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4041 var after = new_x_range[0];
4042 var before = new_x_range[1];
4044 var first = state.netdata_first + state.data_update_every;
4045 var last = state.netdata_last + state.data_update_every;
4048 after -= (before - last);
4055 state.setMode('zoom');
4056 if(state.updateChartPanOrZoom(after, before) === true)
4057 dygraph.updateOptions({ dateWindow: [ after, before ] });
4059 event.preventDefault();
4062 touchstart: function(event, dygraph, context) {
4063 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4064 state.log('interactionModel.touchstart()');
4066 state.dygraph_user_action = true;
4067 state.setMode('zoom');
4070 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4072 // we overwrite the touch directions at the end, to overwrite
4073 // the internal default of dygraphs
4074 context.touchDirections = { x: true, y: false };
4076 state.dygraph_last_touch_start = new Date().getTime();
4077 state.dygraph_last_touch_move = 0;
4079 if(typeof event.touches[0].pageX === 'number')
4080 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4082 state.dygraph_last_touch_page_x = 0;
4084 touchmove: function(event, dygraph, context) {
4085 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4086 state.log('interactionModel.touchmove()');
4088 state.dygraph_user_action = true;
4089 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4091 state.dygraph_last_touch_move = new Date().getTime();
4093 touchend: function(event, dygraph, context) {
4094 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4095 state.log('interactionModel.touchend()');
4097 state.dygraph_user_action = true;
4098 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4100 // if it didn't move, it is a selection
4101 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4102 // internal api of dygraphs
4103 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4104 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4105 if(NETDATA.dygraphSetSelection(state, t) === true)
4106 state.globalSelectionSync(t);
4109 // if it was double tap within double click time, reset the charts
4110 var now = new Date().getTime();
4111 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4112 if(state.dygraph_last_touch_move === 0) {
4113 var dt = now - state.dygraph_last_touch_end;
4114 if(dt <= NETDATA.options.current.double_click_speed)
4115 NETDATA.resetAllCharts(state);
4119 // remember the timestamp of the last touch end
4120 state.dygraph_last_touch_end = now;
4125 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4126 state.dygraph_options.drawGrid = false;
4127 state.dygraph_options.drawAxis = false;
4128 state.dygraph_options.title = undefined;
4129 state.dygraph_options.units = undefined;
4130 state.dygraph_options.ylabel = undefined;
4131 state.dygraph_options.yLabelWidth = 0;
4132 state.dygraph_options.labelsDivWidth = 120;
4133 state.dygraph_options.labelsDivStyles.width = '120px';
4134 state.dygraph_options.labelsSeparateLines = true;
4135 state.dygraph_options.rightGap = 0;
4138 if(smooth === true) {
4139 state.dygraph_smooth_eligible = true;
4141 if(NETDATA.options.current.smooth_plot === true)
4142 state.dygraph_options.plotter = smoothPlotter;
4144 else state.dygraph_smooth_eligible = false;
4146 state.dygraph_instance = new Dygraph(state.element_chart,
4147 data.result.data, state.dygraph_options);
4149 state.dygraph_force_zoom = false;
4150 state.dygraph_user_action = false;
4151 state.dygraph_last_rendered = new Date().getTime();
4155 // ----------------------------------------------------------------------------------------------------------------
4158 NETDATA.morrisInitialize = function(callback) {
4159 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4161 // morris requires raphael
4162 if(!NETDATA.chartLibraries.raphael.initialized) {
4163 if(NETDATA.chartLibraries.raphael.enabled) {
4164 NETDATA.raphaelInitialize(function() {
4165 NETDATA.morrisInitialize(callback);
4169 NETDATA.chartLibraries.morris.enabled = false;
4170 if(typeof callback === "function")
4175 NETDATA._loadCSS(NETDATA.morris_css);
4178 url: NETDATA.morris_js,
4183 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4186 NETDATA.chartLibraries.morris.enabled = false;
4187 NETDATA.error(100, NETDATA.morris_js);
4189 .always(function() {
4190 if(typeof callback === "function")
4196 NETDATA.chartLibraries.morris.enabled = false;
4197 if(typeof callback === "function")
4202 NETDATA.morrisChartUpdate = function(state, data) {
4203 state.morris_instance.setData(data.result.data);
4207 NETDATA.morrisChartCreate = function(state, data) {
4209 state.morris_options = {
4210 element: state.element_chart.id,
4211 data: data.result.data,
4213 ykeys: data.dimension_names,
4214 labels: data.dimension_names,
4220 continuousLine: false,
4221 behaveLikeLine: false
4224 if(state.chart.chart_type === 'line')
4225 state.morris_instance = new Morris.Line(state.morris_options);
4227 else if(state.chart.chart_type === 'area') {
4228 state.morris_options.behaveLikeLine = true;
4229 state.morris_instance = new Morris.Area(state.morris_options);
4232 state.morris_instance = new Morris.Area(state.morris_options);
4237 // ----------------------------------------------------------------------------------------------------------------
4240 NETDATA.raphaelInitialize = function(callback) {
4241 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4243 url: NETDATA.raphael_js,
4248 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4251 NETDATA.chartLibraries.raphael.enabled = false;
4252 NETDATA.error(100, NETDATA.raphael_js);
4254 .always(function() {
4255 if(typeof callback === "function")
4260 NETDATA.chartLibraries.raphael.enabled = false;
4261 if(typeof callback === "function")
4266 NETDATA.raphaelChartUpdate = function(state, data) {
4267 $(state.element_chart).raphael(data.result, {
4268 width: state.chartWidth(),
4269 height: state.chartHeight()
4275 NETDATA.raphaelChartCreate = function(state, data) {
4276 $(state.element_chart).raphael(data.result, {
4277 width: state.chartWidth(),
4278 height: state.chartHeight()
4284 // ----------------------------------------------------------------------------------------------------------------
4287 NETDATA.c3Initialize = function(callback) {
4288 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4291 if(!NETDATA.chartLibraries.d3.initialized) {
4292 if(NETDATA.chartLibraries.d3.enabled) {
4293 NETDATA.d3Initialize(function() {
4294 NETDATA.c3Initialize(callback);
4298 NETDATA.chartLibraries.c3.enabled = false;
4299 if(typeof callback === "function")
4304 NETDATA._loadCSS(NETDATA.c3_css);
4312 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4315 NETDATA.chartLibraries.c3.enabled = false;
4316 NETDATA.error(100, NETDATA.c3_js);
4318 .always(function() {
4319 if(typeof callback === "function")
4325 NETDATA.chartLibraries.c3.enabled = false;
4326 if(typeof callback === "function")
4331 NETDATA.c3ChartUpdate = function(state, data) {
4332 state.c3_instance.destroy();
4333 return NETDATA.c3ChartCreate(state, data);
4335 //state.c3_instance.load({
4336 // rows: data.result,
4343 NETDATA.c3ChartCreate = function(state, data) {
4345 state.element_chart.id = 'c3-' + state.uuid;
4346 // console.log('id = ' + state.element_chart.id);
4348 state.c3_instance = c3.generate({
4349 bindto: '#' + state.element_chart.id,
4351 width: state.chartWidth(),
4352 height: state.chartHeight()
4355 pattern: state.chartColors()
4360 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4366 format: function(x) {
4367 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4394 // console.log(state.c3_instance);
4399 // ----------------------------------------------------------------------------------------------------------------
4402 NETDATA.d3Initialize = function(callback) {
4403 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4410 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4413 NETDATA.chartLibraries.d3.enabled = false;
4414 NETDATA.error(100, NETDATA.d3_js);
4416 .always(function() {
4417 if(typeof callback === "function")
4422 NETDATA.chartLibraries.d3.enabled = false;
4423 if(typeof callback === "function")
4428 NETDATA.d3ChartUpdate = function(state, data) {
4432 NETDATA.d3ChartCreate = function(state, data) {
4436 // ----------------------------------------------------------------------------------------------------------------
4439 NETDATA.googleInitialize = function(callback) {
4440 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4442 url: NETDATA.google_js,
4447 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4448 google.load('visualization', '1.1', {
4449 'packages': ['corechart', 'controls'],
4450 'callback': callback
4454 NETDATA.chartLibraries.google.enabled = false;
4455 NETDATA.error(100, NETDATA.google_js);
4456 if(typeof callback === "function")
4461 NETDATA.chartLibraries.google.enabled = false;
4462 if(typeof callback === "function")
4467 NETDATA.googleChartUpdate = function(state, data) {
4468 var datatable = new google.visualization.DataTable(data.result);
4469 state.google_instance.draw(datatable, state.google_options);
4473 NETDATA.googleChartCreate = function(state, data) {
4474 var datatable = new google.visualization.DataTable(data.result);
4476 state.google_options = {
4477 colors: state.chartColors(),
4479 // do not set width, height - the chart resizes itself
4480 //width: state.chartWidth(),
4481 //height: state.chartHeight(),
4486 // title: "Time of Day",
4487 // format:'HH:mm:ss',
4488 viewWindowMode: 'maximized',
4500 viewWindowMode: 'pretty',
4515 focusTarget: 'category',
4522 titlePosition: 'out',
4533 curveType: 'function',
4538 switch(state.chart.chart_type) {
4540 state.google_options.vAxis.viewWindowMode = 'maximized';
4541 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4542 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4546 state.google_options.isStacked = true;
4547 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4548 state.google_options.vAxis.viewWindowMode = 'maximized';
4549 state.google_options.vAxis.minValue = null;
4550 state.google_options.vAxis.maxValue = null;
4551 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4556 state.google_options.lineWidth = 2;
4557 state.google_instance = new google.visualization.LineChart(state.element_chart);
4561 state.google_instance.draw(datatable, state.google_options);
4565 // ----------------------------------------------------------------------------------------------------------------
4567 NETDATA.percentFromValueMax = function(value, max) {
4568 if(value === null) value = 0;
4569 if(max < value) max = value;
4573 pcent = Math.round(value * 100 / max);
4574 if(pcent === 0 && value > 0) pcent = 1;
4580 // ----------------------------------------------------------------------------------------------------------------
4583 NETDATA.easypiechartInitialize = function(callback) {
4584 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4586 url: NETDATA.easypiechart_js,
4591 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4594 NETDATA.chartLibraries.easypiechart.enabled = false;
4595 NETDATA.error(100, NETDATA.easypiechart_js);
4597 .always(function() {
4598 if(typeof callback === "function")
4603 NETDATA.chartLibraries.easypiechart.enabled = false;
4604 if(typeof callback === "function")
4609 NETDATA.easypiechartClearSelection = function(state) {
4610 if(typeof state.easyPieChartEvent !== 'undefined') {
4611 if(state.easyPieChartEvent.timer !== null)
4612 clearTimeout(state.easyPieChartEvent.timer);
4614 state.easyPieChartEvent.timer = null;
4617 if(state.isAutoRefreshed() === true && state.data !== null) {
4618 NETDATA.easypiechartChartUpdate(state, state.data);
4621 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4622 state.easyPieChart_instance.update(0);
4624 state.easyPieChart_instance.enableAnimation();
4629 NETDATA.easypiechartSetSelection = function(state, t) {
4630 if(state.timeIsVisible(t) !== true)
4631 return NETDATA.easypiechartClearSelection(state);
4633 var slot = state.calculateRowForTime(t);
4634 if(slot < 0 || slot >= state.data.result.length)
4635 return NETDATA.easypiechartClearSelection(state);
4637 if(typeof state.easyPieChartEvent === 'undefined') {
4638 state.easyPieChartEvent = {
4645 var value = state.data.result[state.data.result.length - 1 - slot];
4646 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4647 var pcent = NETDATA.percentFromValueMax(value, max);
4649 state.easyPieChartEvent.value = value;
4650 state.easyPieChartEvent.pcent = pcent;
4651 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4653 if(state.easyPieChartEvent.timer === null) {
4654 state.easyPieChart_instance.disableAnimation();
4656 state.easyPieChartEvent.timer = setTimeout(function() {
4657 state.easyPieChartEvent.timer = null;
4658 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4659 }, NETDATA.options.current.charts_selection_animation_delay);
4665 NETDATA.easypiechartChartUpdate = function(state, data) {
4666 var value, max, pcent;
4668 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4674 value = data.result[0];
4675 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4676 pcent = NETDATA.percentFromValueMax(value, max);
4679 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4680 state.easyPieChart_instance.update(pcent);
4684 NETDATA.easypiechartChartCreate = function(state, data) {
4685 var self = $(state.element);
4686 var chart = $(state.element_chart);
4688 var value = data.result[0];
4689 var max = self.data('easypiechart-max-value') || null;
4690 var adjust = self.data('easypiechart-adjust') || null;
4694 state.easyPieChartMax = null;
4697 state.easyPieChartMax = max;
4699 var pcent = NETDATA.percentFromValueMax(value, max);
4701 chart.data('data-percent', pcent);
4705 case 'width': size = state.chartHeight(); break;
4706 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4707 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4709 default: size = state.chartWidth(); break;
4711 state.element.style.width = size + 'px';
4712 state.element.style.height = size + 'px';
4714 var stroke = Math.floor(size / 22);
4715 if(stroke < 3) stroke = 2;
4717 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4718 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4719 state.easyPieChartLabel = document.createElement('span');
4720 state.easyPieChartLabel.className = 'easyPieChartLabel';
4721 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4722 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4723 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4724 state.element_chart.appendChild(state.easyPieChartLabel);
4726 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4727 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4728 state.easyPieChartTitle = document.createElement('span');
4729 state.easyPieChartTitle.className = 'easyPieChartTitle';
4730 state.easyPieChartTitle.innerHTML = state.title;
4731 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4732 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4733 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4734 state.element_chart.appendChild(state.easyPieChartTitle);
4736 var unitfontsize = Math.round(titlefontsize * 0.9);
4737 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4738 state.easyPieChartUnits = document.createElement('span');
4739 state.easyPieChartUnits.className = 'easyPieChartUnits';
4740 state.easyPieChartUnits.innerHTML = state.units;
4741 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4742 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4743 state.element_chart.appendChild(state.easyPieChartUnits);
4745 chart.easyPieChart({
4746 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4747 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4748 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4749 scaleLength: self.data('easypiechart-scalelength') || 5,
4750 lineCap: self.data('easypiechart-linecap') || 'round',
4751 lineWidth: self.data('easypiechart-linewidth') || stroke,
4752 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4753 size: self.data('easypiechart-size') || size,
4754 rotate: self.data('easypiechart-rotate') || 0,
4755 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4756 easing: self.data('easypiechart-easing') || undefined
4759 // when we just re-create the chart
4760 // do not animate the first update
4762 if(typeof state.easyPieChart_instance !== 'undefined')
4765 state.easyPieChart_instance = chart.data('easyPieChart');
4766 if(animate === false) state.easyPieChart_instance.disableAnimation();
4767 state.easyPieChart_instance.update(pcent);
4768 if(animate === false) state.easyPieChart_instance.enableAnimation();
4772 // ----------------------------------------------------------------------------------------------------------------
4775 NETDATA.gaugeInitialize = function(callback) {
4776 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4778 url: NETDATA.gauge_js,
4783 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4786 NETDATA.chartLibraries.gauge.enabled = false;
4787 NETDATA.error(100, NETDATA.gauge_js);
4789 .always(function() {
4790 if(typeof callback === "function")
4795 NETDATA.chartLibraries.gauge.enabled = false;
4796 if(typeof callback === "function")
4801 NETDATA.gaugeAnimation = function(state, status) {
4804 if(typeof status === 'boolean' && status === false)
4806 else if(typeof status === 'number')
4809 state.gauge_instance.animationSpeed = speed;
4810 state.___gaugeOld__.speed = speed;
4813 NETDATA.gaugeSet = function(state, value, min, max) {
4814 if(typeof value !== 'number') value = 0;
4815 if(typeof min !== 'number') min = 0;
4816 if(typeof max !== 'number') max = 0;
4817 if(value > max) max = value;
4818 if(value < min) min = value;
4827 // gauge.js has an issue if the needle
4828 // is smaller than min or larger than max
4829 // when we set the new values
4830 // the needle will go crazy
4832 // to prevent it, we always feed it
4833 // with a percentage, so that the needle
4834 // is always between min and max
4835 var pcent = (value - min) * 100 / (max - min);
4837 // these should never happen
4838 if(pcent < 0) pcent = 0;
4839 if(pcent > 100) pcent = 100;
4841 state.gauge_instance.set(pcent);
4843 state.___gaugeOld__.value = value;
4844 state.___gaugeOld__.min = min;
4845 state.___gaugeOld__.max = max;
4848 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4849 if(state.___gaugeOld__.valueLabel !== value) {
4850 state.___gaugeOld__.valueLabel = value;
4851 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4853 if(state.___gaugeOld__.minLabel !== min) {
4854 state.___gaugeOld__.minLabel = min;
4855 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4857 if(state.___gaugeOld__.maxLabel !== max) {
4858 state.___gaugeOld__.maxLabel = max;
4859 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4863 NETDATA.gaugeClearSelection = function(state) {
4864 if(typeof state.gaugeEvent !== 'undefined') {
4865 if(state.gaugeEvent.timer !== null)
4866 clearTimeout(state.gaugeEvent.timer);
4868 state.gaugeEvent.timer = null;
4871 if(state.isAutoRefreshed() === true && state.data !== null) {
4872 NETDATA.gaugeChartUpdate(state, state.data);
4875 NETDATA.gaugeAnimation(state, false);
4876 NETDATA.gaugeSet(state, null, null, null);
4877 NETDATA.gaugeSetLabels(state, null, null, null);
4880 NETDATA.gaugeAnimation(state, true);
4884 NETDATA.gaugeSetSelection = function(state, t) {
4885 if(state.timeIsVisible(t) !== true)
4886 return NETDATA.gaugeClearSelection(state);
4888 var slot = state.calculateRowForTime(t);
4889 if(slot < 0 || slot >= state.data.result.length)
4890 return NETDATA.gaugeClearSelection(state);
4892 if(typeof state.gaugeEvent === 'undefined') {
4893 state.gaugeEvent = {
4901 var value = state.data.result[state.data.result.length - 1 - slot];
4902 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4905 state.gaugeEvent.value = value;
4906 state.gaugeEvent.max = max;
4907 state.gaugeEvent.min = min;
4908 NETDATA.gaugeSetLabels(state, value, min, max);
4910 if(state.gaugeEvent.timer === null) {
4911 NETDATA.gaugeAnimation(state, false);
4913 state.gaugeEvent.timer = setTimeout(function() {
4914 state.gaugeEvent.timer = null;
4915 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4916 }, NETDATA.options.current.charts_selection_animation_delay);
4922 NETDATA.gaugeChartUpdate = function(state, data) {
4923 var value, min, max;
4925 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4929 NETDATA.gaugeSetLabels(state, null, null, null);
4932 value = data.result[0];
4934 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4935 if(value > max) max = value;
4936 NETDATA.gaugeSetLabels(state, value, min, max);
4939 NETDATA.gaugeSet(state, value, min, max);
4943 NETDATA.gaugeChartCreate = function(state, data) {
4944 var self = $(state.element);
4945 // var chart = $(state.element_chart);
4947 var value = data.result[0];
4948 var max = self.data('gauge-max-value') || null;
4949 var adjust = self.data('gauge-adjust') || null;
4950 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4951 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4952 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4953 var stopColor = self.data('gauge-stop-color') || void 0;
4954 var generateGradient = self.data('gauge-generate-gradient') || false;
4958 state.gaugeMax = null;
4961 state.gaugeMax = max;
4963 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4965 // case 'width': width = height * ratio; break;
4967 // default: height = width / ratio; break;
4969 //state.element.style.width = width.toString() + 'px';
4970 //state.element.style.height = height.toString() + 'px';
4975 lines: 12, // The number of lines to draw
4976 angle: 0.15, // The length of each line
4977 lineWidth: 0.44, // 0.44 The line thickness
4979 length: 0.8, // 0.9 The radius of the inner circle
4980 strokeWidth: 0.035, // The rotation offset
4981 color: pointerColor // Fill color
4983 colorStart: startColor, // Colors
4984 colorStop: stopColor, // just experiment with them
4985 strokeColor: strokeColor, // to see which ones work best for you
4987 generateGradient: generateGradient,
4991 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4992 options.percentColors = [
4993 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4994 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4995 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4996 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4997 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4998 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4999 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5000 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5001 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5002 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5003 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5006 state.gauge_canvas = document.createElement('canvas');
5007 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5008 state.gauge_canvas.className = 'gaugeChart';
5009 state.gauge_canvas.width = width;
5010 state.gauge_canvas.height = height;
5011 state.element_chart.appendChild(state.gauge_canvas);
5013 var valuefontsize = Math.floor(height / 6);
5014 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5015 state.gaugeChartLabel = document.createElement('span');
5016 state.gaugeChartLabel.className = 'gaugeChartLabel';
5017 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5018 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5019 state.element_chart.appendChild(state.gaugeChartLabel);
5021 var titlefontsize = Math.round(valuefontsize / 2);
5023 state.gaugeChartTitle = document.createElement('span');
5024 state.gaugeChartTitle.className = 'gaugeChartTitle';
5025 state.gaugeChartTitle.innerHTML = state.title;
5026 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5027 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5028 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5029 state.element_chart.appendChild(state.gaugeChartTitle);
5031 var unitfontsize = Math.round(titlefontsize * 0.9);
5032 state.gaugeChartUnits = document.createElement('span');
5033 state.gaugeChartUnits.className = 'gaugeChartUnits';
5034 state.gaugeChartUnits.innerHTML = state.units;
5035 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5036 state.element_chart.appendChild(state.gaugeChartUnits);
5038 state.gaugeChartMin = document.createElement('span');
5039 state.gaugeChartMin.className = 'gaugeChartMin';
5040 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5041 state.element_chart.appendChild(state.gaugeChartMin);
5043 state.gaugeChartMax = document.createElement('span');
5044 state.gaugeChartMax.className = 'gaugeChartMax';
5045 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5046 state.element_chart.appendChild(state.gaugeChartMax);
5048 // when we just re-create the chart
5049 // do not animate the first update
5051 if(typeof state.gauge_instance !== 'undefined')
5054 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5056 state.___gaugeOld__ = {
5065 // we will always feed a percentage
5066 state.gauge_instance.minValue = 0;
5067 state.gauge_instance.maxValue = 100;
5069 NETDATA.gaugeAnimation(state, animate);
5070 NETDATA.gaugeSet(state, value, 0, max);
5071 NETDATA.gaugeSetLabels(state, value, 0, max);
5072 NETDATA.gaugeAnimation(state, true);
5076 // ----------------------------------------------------------------------------------------------------------------
5077 // Charts Libraries Registration
5079 NETDATA.chartLibraries = {
5081 initialize: NETDATA.dygraphInitialize,
5082 create: NETDATA.dygraphChartCreate,
5083 update: NETDATA.dygraphChartUpdate,
5084 resize: function(state) {
5085 if(typeof state.dygraph_instance.resize === 'function')
5086 state.dygraph_instance.resize();
5088 setSelection: NETDATA.dygraphSetSelection,
5089 clearSelection: NETDATA.dygraphClearSelection,
5090 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5093 format: function(state) { return 'json'; },
5094 options: function(state) { return 'ms|flip'; },
5095 legend: function(state) {
5096 if(this.isSparkline(state) === false)
5097 return 'right-side';
5101 autoresize: function(state) { return true; },
5102 max_updates_to_recreate: function(state) { return 5000; },
5103 track_colors: function(state) { return true; },
5104 pixels_per_point: function(state) {
5105 if(this.isSparkline(state) === false)
5111 isSparkline: function(state) {
5112 if(typeof state.dygraph_sparkline === 'undefined') {
5113 var t = $(state.element).data('dygraph-theme');
5114 if(t === 'sparkline')
5115 state.dygraph_sparkline = true;
5117 state.dygraph_sparkline = false;
5119 return state.dygraph_sparkline;
5123 initialize: NETDATA.sparklineInitialize,
5124 create: NETDATA.sparklineChartCreate,
5125 update: NETDATA.sparklineChartUpdate,
5127 setSelection: undefined, // function(state, t) { return true; },
5128 clearSelection: undefined, // function(state) { return true; },
5129 toolboxPanAndZoom: null,
5132 format: function(state) { return 'array'; },
5133 options: function(state) { return 'flip|abs'; },
5134 legend: function(state) { return null; },
5135 autoresize: function(state) { return false; },
5136 max_updates_to_recreate: function(state) { return 5000; },
5137 track_colors: function(state) { return false; },
5138 pixels_per_point: function(state) { return 3; }
5141 initialize: NETDATA.peityInitialize,
5142 create: NETDATA.peityChartCreate,
5143 update: NETDATA.peityChartUpdate,
5145 setSelection: undefined, // function(state, t) { return true; },
5146 clearSelection: undefined, // function(state) { return true; },
5147 toolboxPanAndZoom: null,
5150 format: function(state) { return 'ssvcomma'; },
5151 options: function(state) { return 'null2zero|flip|abs'; },
5152 legend: function(state) { return null; },
5153 autoresize: function(state) { return false; },
5154 max_updates_to_recreate: function(state) { return 5000; },
5155 track_colors: function(state) { return false; },
5156 pixels_per_point: function(state) { return 3; }
5159 initialize: NETDATA.morrisInitialize,
5160 create: NETDATA.morrisChartCreate,
5161 update: NETDATA.morrisChartUpdate,
5163 setSelection: undefined, // function(state, t) { return true; },
5164 clearSelection: undefined, // function(state) { return true; },
5165 toolboxPanAndZoom: null,
5168 format: function(state) { return 'json'; },
5169 options: function(state) { return 'objectrows|ms'; },
5170 legend: function(state) { return null; },
5171 autoresize: function(state) { return false; },
5172 max_updates_to_recreate: function(state) { return 50; },
5173 track_colors: function(state) { return false; },
5174 pixels_per_point: function(state) { return 15; }
5177 initialize: NETDATA.googleInitialize,
5178 create: NETDATA.googleChartCreate,
5179 update: NETDATA.googleChartUpdate,
5181 setSelection: undefined, //function(state, t) { return true; },
5182 clearSelection: undefined, //function(state) { return true; },
5183 toolboxPanAndZoom: null,
5186 format: function(state) { return 'datatable'; },
5187 options: function(state) { return ''; },
5188 legend: function(state) { return null; },
5189 autoresize: function(state) { return false; },
5190 max_updates_to_recreate: function(state) { return 300; },
5191 track_colors: function(state) { return false; },
5192 pixels_per_point: function(state) { return 4; }
5195 initialize: NETDATA.raphaelInitialize,
5196 create: NETDATA.raphaelChartCreate,
5197 update: NETDATA.raphaelChartUpdate,
5199 setSelection: undefined, // function(state, t) { return true; },
5200 clearSelection: undefined, // function(state) { return true; },
5201 toolboxPanAndZoom: null,
5204 format: function(state) { return 'json'; },
5205 options: function(state) { return ''; },
5206 legend: function(state) { return null; },
5207 autoresize: function(state) { return false; },
5208 max_updates_to_recreate: function(state) { return 5000; },
5209 track_colors: function(state) { return false; },
5210 pixels_per_point: function(state) { return 3; }
5213 initialize: NETDATA.c3Initialize,
5214 create: NETDATA.c3ChartCreate,
5215 update: NETDATA.c3ChartUpdate,
5217 setSelection: undefined, // function(state, t) { return true; },
5218 clearSelection: undefined, // function(state) { return true; },
5219 toolboxPanAndZoom: null,
5222 format: function(state) { return 'csvjsonarray'; },
5223 options: function(state) { return 'milliseconds'; },
5224 legend: function(state) { return null; },
5225 autoresize: function(state) { return false; },
5226 max_updates_to_recreate: function(state) { return 5000; },
5227 track_colors: function(state) { return false; },
5228 pixels_per_point: function(state) { return 15; }
5231 initialize: NETDATA.d3Initialize,
5232 create: NETDATA.d3ChartCreate,
5233 update: NETDATA.d3ChartUpdate,
5235 setSelection: undefined, // function(state, t) { return true; },
5236 clearSelection: undefined, // function(state) { return true; },
5237 toolboxPanAndZoom: null,
5240 format: function(state) { return 'json'; },
5241 options: function(state) { return ''; },
5242 legend: function(state) { return null; },
5243 autoresize: function(state) { return false; },
5244 max_updates_to_recreate: function(state) { return 5000; },
5245 track_colors: function(state) { return false; },
5246 pixels_per_point: function(state) { return 3; }
5249 initialize: NETDATA.easypiechartInitialize,
5250 create: NETDATA.easypiechartChartCreate,
5251 update: NETDATA.easypiechartChartUpdate,
5253 setSelection: NETDATA.easypiechartSetSelection,
5254 clearSelection: NETDATA.easypiechartClearSelection,
5255 toolboxPanAndZoom: null,
5258 format: function(state) { return 'array'; },
5259 options: function(state) { return 'absolute'; },
5260 legend: function(state) { return null; },
5261 autoresize: function(state) { return false; },
5262 max_updates_to_recreate: function(state) { return 5000; },
5263 track_colors: function(state) { return true; },
5264 pixels_per_point: function(state) { return 3; },
5268 initialize: NETDATA.gaugeInitialize,
5269 create: NETDATA.gaugeChartCreate,
5270 update: NETDATA.gaugeChartUpdate,
5272 setSelection: NETDATA.gaugeSetSelection,
5273 clearSelection: NETDATA.gaugeClearSelection,
5274 toolboxPanAndZoom: null,
5277 format: function(state) { return 'array'; },
5278 options: function(state) { return 'absolute'; },
5279 legend: function(state) { return null; },
5280 autoresize: function(state) { return false; },
5281 max_updates_to_recreate: function(state) { return 5000; },
5282 track_colors: function(state) { return true; },
5283 pixels_per_point: function(state) { return 3; },
5288 NETDATA.registerChartLibrary = function(library, url) {
5289 if(NETDATA.options.debug.libraries === true)
5290 console.log("registering chart library: " + library);
5292 NETDATA.chartLibraries[library].url = url;
5293 NETDATA.chartLibraries[library].initialized = true;
5294 NETDATA.chartLibraries[library].enabled = true;
5297 // ----------------------------------------------------------------------------------------------------------------
5300 NETDATA.requiredJs = [
5302 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5303 isAlreadyLoaded: function() {
5304 if(typeof $().emulateTransitionEnd == 'function')
5307 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5315 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5316 isAlreadyLoaded: function() { return false; }
5319 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5320 isAlreadyLoaded: function() { return false; }
5324 NETDATA.requiredCSS = [
5326 url: NETDATA.themes.current.bootstrap_css,
5327 isAlreadyLoaded: function() {
5328 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5335 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5336 isAlreadyLoaded: function() { return false; }
5339 url: NETDATA.themes.current.dashboard_css,
5340 isAlreadyLoaded: function() { return false; }
5343 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5344 isAlreadyLoaded: function() { return false; }
5348 NETDATA.loadRequiredJs = function(index, callback) {
5349 if(index >= NETDATA.requiredJs.length) {
5350 if(typeof callback === 'function')
5355 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5356 NETDATA.loadRequiredJs(++index, callback);
5360 if(NETDATA.options.debug.main_loop === true)
5361 console.log('loading ' + NETDATA.requiredJs[index].url);
5364 url: NETDATA.requiredJs[index].url,
5368 .success(function() {
5369 if(NETDATA.options.debug.main_loop === true)
5370 console.log('loaded ' + NETDATA.requiredJs[index].url);
5372 NETDATA.loadRequiredJs(++index, callback);
5375 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5379 NETDATA.loadRequiredCSS = function(index) {
5380 if(index >= NETDATA.requiredCSS.length)
5383 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5384 NETDATA.loadRequiredCSS(++index);
5388 if(NETDATA.options.debug.main_loop === true)
5389 console.log('loading ' + NETDATA.requiredCSS[index].url);
5391 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5392 NETDATA.loadRequiredCSS(++index);
5395 NETDATA.errorReset();
5396 NETDATA.loadRequiredCSS(0);
5398 NETDATA._loadjQuery(function() {
5399 NETDATA.loadRequiredJs(0, function() {
5400 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5401 if(NETDATA.options.debug.main_loop === true)
5402 console.log('starting chart refresh thread');
5409 // window.NETDATA = NETDATA;
5410 // })(window, document);