1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
16 // You can also set the default netdata server, using the following.
17 // When this variable is not set, we assume the page is hosted on your
18 // netdata server already.
19 // var netdataServer = "http://yourhost:19999"; // set your NetData server
21 //(function(window, document, undefined) {
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 },
463 405: { message: "Cannot download charts index from server", alert: true },
464 406: { message: "Invalid charts index downloaded from server", alert: true }
466 NETDATA.errorLast = {
472 NETDATA.error = function(code, msg) {
473 NETDATA.errorLast.code = code;
474 NETDATA.errorLast.message = msg;
475 NETDATA.errorLast.datetime = new Date().getTime();
477 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
480 if(typeof netdataErrorCallback === 'function') {
481 ret = netdataErrorCallback('system', code, msg);
484 if(ret && NETDATA.errorCodes[code].alert)
485 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
488 NETDATA.errorReset = function() {
489 NETDATA.errorLast.code = 0;
490 NETDATA.errorLast.message = "You are doing fine!";
491 NETDATA.errorLast.datetime = 0;
494 // ----------------------------------------------------------------------------------------------------------------
497 // When multiple charts need the same chart, we avoid downloading it
498 // multiple times (and having it in browser memory multiple time)
499 // by using this registry.
501 // Every time we download a chart definition, we save it here with .add()
502 // Then we try to get it back with .get(). If that fails, we download it.
504 NETDATA.chartRegistry = {
507 fixid: function(id) {
508 return id.replace(/:/g, "_").replace(/\//g, "_");
511 add: function(host, id, data) {
512 host = this.fixid(host);
515 if(typeof this.charts[host] === 'undefined')
516 this.charts[host] = {};
518 //console.log('added ' + host + '/' + id);
519 this.charts[host][id] = data;
522 get: function(host, id) {
523 host = this.fixid(host);
526 if(typeof this.charts[host] === 'undefined')
529 if(typeof this.charts[host][id] === 'undefined')
532 //console.log('cached ' + host + '/' + id);
533 return this.charts[host][id];
536 downloadAll: function(host, callback) {
537 while(host.slice(-1) === '/')
538 host = host.substring(0, host.length - 1);
543 url: host + '/api/v1/charts',
544 crossDomain: NETDATA.options.crossDomainAjax,
548 .done(function(data) {
550 var h = NETDATA.chartRegistry.fixid(host);
551 self.charts[h] = data.charts;
553 else NETDATA.error(406, host + '/api/v1/charts');
555 if(typeof callback === 'function')
559 NETDATA.error(405, host + '/api/v1/charts');
561 if(typeof callback === 'function')
567 // ----------------------------------------------------------------------------------------------------------------
568 // Global Pan and Zoom on charts
570 // Using this structure are synchronize all the charts, so that
571 // when you pan or zoom one, all others are automatically refreshed
572 // to the same timespan.
574 NETDATA.globalPanAndZoom = {
575 seq: 0, // timestamp ms
576 // every time a chart is panned or zoomed
577 // we set the timestamp here
578 // then we use it as a sequence number
579 // to find if other charts are syncronized
582 master: null, // the master chart (state), to which all others
585 force_before_ms: null, // the timespan to sync all other charts
586 force_after_ms: null,
589 setMaster: function(state, after, before) {
590 if(NETDATA.options.current.sync_pan_and_zoom === false)
593 if(this.master !== null && this.master !== state)
594 this.master.resetChart(true, true);
596 var now = new Date().getTime();
599 this.force_after_ms = after;
600 this.force_before_ms = before;
601 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
605 clearMaster: function() {
606 if(this.master !== null) {
607 var st = this.master;
614 this.force_after_ms = null;
615 this.force_before_ms = null;
616 NETDATA.options.auto_refresher_stop_until = 0;
619 // is the given state the master of the global
620 // pan and zoom sync?
621 isMaster: function(state) {
622 if(this.master === state) return true;
626 // are we currently have a global pan and zoom sync?
627 isActive: function() {
628 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
632 // check if a chart, other than the master
633 // needs to be refreshed, due to the global pan and zoom
634 shouldBeAutoRefreshed: function(state) {
635 if(this.master === null || this.seq === 0)
638 //if(state.needsRecreation())
641 if(state.tm.pan_and_zoom_seq === this.seq)
648 // ----------------------------------------------------------------------------------------------------------------
649 // dimensions selection
652 // move color assignment to dimensions, here
654 dimensionStatus = function(parent, label, name_div, value_div, color) {
655 this.enabled = false;
656 this.parent = parent;
658 this.name_div = null;
659 this.value_div = null;
660 this.color = NETDATA.themes.current.foreground;
662 if(parent.selected_count > parent.unselected_count)
663 this.selected = true;
665 this.selected = false;
667 this.setOptions(name_div, value_div, color);
670 dimensionStatus.prototype.invalidate = function() {
671 this.name_div = null;
672 this.value_div = null;
673 this.enabled = false;
676 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
679 if(this.name_div != name_div) {
680 this.name_div = name_div;
681 this.name_div.title = this.label;
682 this.name_div.style.color = this.color;
683 if(this.selected === false)
684 this.name_div.className = 'netdata-legend-name not-selected';
686 this.name_div.className = 'netdata-legend-name selected';
689 if(this.value_div != value_div) {
690 this.value_div = value_div;
691 this.value_div.title = this.label;
692 this.value_div.style.color = this.color;
693 if(this.selected === false)
694 this.value_div.className = 'netdata-legend-value not-selected';
696 this.value_div.className = 'netdata-legend-value selected';
703 dimensionStatus.prototype.setHandler = function() {
704 if(this.enabled === false) return;
708 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
709 this.name_div.onclick = this.value_div.onclick = function(e) {
711 if(ds.isSelected()) {
713 if(e.shiftKey === true || e.ctrlKey === true) {
714 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
717 if(ds.parent.countSelected() === 0)
718 ds.parent.selectAll();
721 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
722 if(ds.parent.countSelected() === 1) {
723 ds.parent.selectAll();
726 ds.parent.selectNone();
732 // this is not selected
733 if(e.shiftKey === true || e.ctrlKey === true) {
734 // control or shift key is pressed -> select this too
738 // no key is pressed -> select only this
739 ds.parent.selectNone();
744 ds.parent.state.redrawChart();
748 dimensionStatus.prototype.select = function() {
749 if(this.enabled === false) return;
751 this.name_div.className = 'netdata-legend-name selected';
752 this.value_div.className = 'netdata-legend-value selected';
753 this.selected = true;
756 dimensionStatus.prototype.unselect = function() {
757 if(this.enabled === false) return;
759 this.name_div.className = 'netdata-legend-name not-selected';
760 this.value_div.className = 'netdata-legend-value hidden';
761 this.selected = false;
764 dimensionStatus.prototype.isSelected = function() {
765 return(this.enabled === true && this.selected === true);
768 // ----------------------------------------------------------------------------------------------------------------
770 dimensionsVisibility = function(state) {
773 this.dimensions = {};
774 this.selected_count = 0;
775 this.unselected_count = 0;
778 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
779 if(typeof this.dimensions[label] === 'undefined') {
781 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
784 this.dimensions[label].setOptions(name_div, value_div, color);
786 return this.dimensions[label];
789 dimensionsVisibility.prototype.dimensionGet = function(label) {
790 return this.dimensions[label];
793 dimensionsVisibility.prototype.invalidateAll = function() {
794 for(var d in this.dimensions)
795 this.dimensions[d].invalidate();
798 dimensionsVisibility.prototype.selectAll = function() {
799 for(var d in this.dimensions)
800 this.dimensions[d].select();
803 dimensionsVisibility.prototype.countSelected = function() {
805 for(var d in this.dimensions)
806 if(this.dimensions[d].isSelected()) i++;
811 dimensionsVisibility.prototype.selectNone = function() {
812 for(var d in this.dimensions)
813 this.dimensions[d].unselect();
816 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
817 var ret = new Array();
818 this.selected_count = 0;
819 this.unselected_count = 0;
821 for(var i = 0, len = array.length; i < len ; i++) {
822 var ds = this.dimensions[array[i]];
823 if(typeof ds === 'undefined') {
824 // console.log(array[i] + ' is not found');
829 if(ds.isSelected()) {
831 this.selected_count++;
835 this.unselected_count++;
839 if(this.selected_count === 0 && this.unselected_count !== 0) {
841 return this.selected2BooleanArray(array);
848 // ----------------------------------------------------------------------------------------------------------------
849 // global selection sync
851 NETDATA.globalSelectionSync = {
858 if(this.state !== null)
859 this.state.globalSelectionSyncStop();
863 if(this.state !== null) {
864 this.state.globalSelectionSyncDelay();
869 // ----------------------------------------------------------------------------------------------------------------
870 // Our state object, where all per-chart values are stored
872 chartState = function(element) {
873 var self = $(element);
874 this.element = element;
877 // all private functions should use 'that', instead of 'this'
881 * show an error instead of the chart
883 var error = function(msg) {
886 if(typeof netdataErrorCallback === 'function') {
887 ret = netdataErrorCallback('chart', that.id, msg);
891 that.element.innerHTML = that.id + ': ' + msg;
892 that.enabled = false;
893 that.current = that.pan;
897 // GUID - a unique identifier for the chart
898 this.uuid = NETDATA.guid();
900 // string - the name of chart
901 this.id = self.data('netdata');
903 // string - the key for localStorage settings
904 this.settings_id = self.data('id') || null;
906 // the user given dimensions of the element
907 this.width = self.data('width') || NETDATA.chartDefaults.width;
908 this.height = self.data('height') || NETDATA.chartDefaults.height;
910 if(this.settings_id !== null) {
911 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
912 // this is the callback that will be called
913 // if and when the user resets all localStorage variables
916 resizeChartToHeight(height);
920 // string - the netdata server URL, without any path
921 this.host = self.data('host') || NETDATA.chartDefaults.host;
923 // make sure the host does not end with /
924 // all netdata API requests use absolute paths
925 while(this.host.slice(-1) === '/')
926 this.host = this.host.substring(0, this.host.length - 1);
928 // string - the grouping method requested by the user
929 this.method = self.data('method') || NETDATA.chartDefaults.method;
931 // the time-range requested by the user
932 this.after = self.data('after') || NETDATA.chartDefaults.after;
933 this.before = self.data('before') || NETDATA.chartDefaults.before;
935 // the pixels per point requested by the user
936 this.pixels_per_point = self.data('pixels-per-point') || 1;
937 this.points = self.data('points') || null;
939 // the dimensions requested by the user
940 this.dimensions = self.data('dimensions') || null;
942 // the chart library requested by the user
943 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
945 // object - the chart library used
950 this.colors_assigned = {};
951 this.colors_available = null;
953 // the element already created by the user
954 this.element_message = null;
956 // the element with the chart
957 this.element_chart = null;
959 // the element with the legend of the chart (if created by us)
960 this.element_legend = null;
961 this.element_legend_childs = {
971 this.chart_url = null; // string - the url to download chart info
972 this.chart = null; // object - the chart as downloaded from the server
974 this.title = self.data('title') || null; // the title of the chart
975 this.units = self.data('units') || null; // the units of the chart dimensions
976 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
978 this.validated = false; // boolean - has the chart been validated?
979 this.enabled = true; // boolean - is the chart enabled for refresh?
980 this.paused = false; // boolean - is the chart paused for any reason?
981 this.selected = false; // boolean - is the chart shown a selection?
982 this.debug = false; // boolean - console.log() debug info about this chart
984 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
985 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
986 this.requested_after = null; // milliseconds - the timestamp of the request after param
987 this.requested_before = null; // milliseconds - the timestamp of the request before param
988 this.requested_padding = null;
990 this.view_before = 0;
995 force_update_at: 0, // the timestamp to force the update at
996 force_before_ms: null,
1002 force_update_at: 0, // the timestamp to force the update at
1003 force_before_ms: null,
1004 force_after_ms: null
1009 force_update_at: 0, // the timestamp to force the update at
1010 force_before_ms: null,
1011 force_after_ms: null
1014 // this is a pointer to one of the sub-classes below
1016 this.current = this.auto;
1018 // check the requested library is available
1019 // we don't initialize it here - it will be initialized when
1020 // this chart will be first used
1021 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1022 NETDATA.error(402, that.library_name);
1023 error('chart library "' + that.library_name + '" is not found');
1026 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1027 NETDATA.error(403, that.library_name);
1028 error('chart library "' + that.library_name + '" is not enabled');
1032 that.library = NETDATA.chartLibraries[that.library_name];
1034 // milliseconds - the time the last refresh took
1035 this.refresh_dt_ms = 0;
1037 // if we need to report the rendering speed
1038 // find the element that needs to be updated
1039 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1041 if(refresh_dt_element_name !== null)
1042 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1044 this.refresh_dt_element = null;
1046 this.dimensions_visibility = new dimensionsVisibility(this);
1048 this._updating = false;
1050 // ============================================================================================================
1051 // PRIVATE FUNCTIONS
1053 var createDOM = function() {
1054 if(that.enabled === false) return;
1056 if(that.element_message !== null) that.element_message.innerHTML = '';
1057 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1058 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1060 that.element.innerHTML = '';
1062 that.element_message = document.createElement('div');
1063 that.element_message.className = ' netdata-message hidden';
1064 that.element.appendChild(that.element_message);
1066 that.element_chart = document.createElement('div');
1067 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1068 that.element.appendChild(that.element_chart);
1070 if(that.hasLegend() === true) {
1071 that.element.className = "netdata-container-with-legend";
1072 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1074 that.element_legend = document.createElement('div');
1075 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1076 that.element.appendChild(that.element_legend);
1079 that.element.className = "netdata-container";
1080 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1082 that.element_legend = null;
1084 that.element_legend_childs.series = null;
1086 if(typeof(that.width) === 'string')
1087 $(that.element).css('width', that.width);
1088 else if(typeof(that.width) === 'number')
1089 $(that.element).css('width', that.width + 'px');
1091 if(typeof(that.library.aspect_ratio) === 'undefined') {
1092 if(typeof(that.height) === 'string')
1093 $(that.element).css('height', that.height);
1094 else if(typeof(that.height) === 'number')
1095 $(that.element).css('height', that.height + 'px');
1098 var w = that.element.offsetWidth;
1099 if(w === null || w === 0) {
1100 // the div is hidden
1101 // this is resize the chart when next viewed
1102 that.tm.last_resized = 0;
1105 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1108 if(NETDATA.chartDefaults.min_width !== null)
1109 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1111 that.tm.last_dom_created = new Date().getTime();
1117 * initialize state variables
1118 * destroy all (possibly) created state elements
1119 * create the basic DOM for a chart
1121 var init = function() {
1122 if(that.enabled === false) return;
1124 that.paused = false;
1125 that.selected = false;
1127 that.chart_created = false; // boolean - is the library.create() been called?
1128 that.updates_counter = 0; // numeric - the number of refreshes made so far
1129 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1130 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1133 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1134 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1135 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1137 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1138 last_updated: 0, // the timestamp the chart last updated with data
1139 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1141 // Used with NETDATA.globalPanAndZoom.seq
1142 last_visible_check: 0, // the time we last checked if it is visible
1143 last_resized: 0, // the time the chart was resized
1144 last_hidden: 0, // the time the chart was hidden
1145 last_unhidden: 0, // the time the chart was unhidden
1146 last_autorefreshed: 0 // the time the chart was last refreshed
1149 that.data = null; // the last data as downloaded from the netdata server
1150 that.data_url = 'invalid://'; // string - the last url used to update the chart
1151 that.data_points = 0; // number - the number of points returned from netdata
1152 that.data_after = 0; // milliseconds - the first timestamp of the data
1153 that.data_before = 0; // milliseconds - the last timestamp of the data
1154 that.data_update_every = 0; // milliseconds - the frequency to update the data
1156 that.tm.last_initialized = new Date().getTime();
1159 that.setMode('auto');
1162 var maxMessageFontSize = function() {
1163 // normally we want a font size, as tall as the element
1164 var h = that.element_message.clientHeight;
1166 // but give it some air, 20% let's say, or 5 pixels min
1167 var lost = Math.max(h * 0.2, 5);
1170 // center the text, verically
1171 var paddingTop = (lost - 5) / 2;
1173 // but check the width too
1174 // it should fit 10 characters in it
1175 var w = that.element_message.clientWidth / 10;
1177 paddingTop += (h - w) / 2;
1181 // and don't make it too huge
1182 // 5% of the screen size is good
1183 if(h > screen.height / 20) {
1184 paddingTop += (h - (screen.height / 20)) / 2;
1185 h = screen.height / 20;
1189 that.element_message.style.fontSize = h.toString() + 'px';
1190 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1193 var showMessage = function(msg) {
1194 that.element_message.className = 'netdata-message';
1195 that.element_message.innerHTML = msg;
1196 that.element_message.style.fontSize = 'x-small';
1197 that.element_message.style.paddingTop = '0px';
1198 that.___messageHidden___ = undefined;
1201 var showMessageIcon = function(icon) {
1202 that.element_message.innerHTML = icon;
1203 that.element_message.className = 'netdata-message icon';
1204 maxMessageFontSize();
1205 that.___messageHidden___ = undefined;
1208 var hideMessage = function() {
1209 if(typeof that.___messageHidden___ === 'undefined') {
1210 that.___messageHidden___ = true;
1211 that.element_message.className = 'netdata-message hidden';
1215 var showRendering = function() {
1217 if(that.chart !== null) {
1218 if(that.chart.chart_type === 'line')
1219 icon = '<i class="fa fa-line-chart"></i>';
1221 icon = '<i class="fa fa-area-chart"></i>';
1224 icon = '<i class="fa fa-area-chart"></i>';
1226 showMessageIcon(icon + ' netdata');
1229 var showLoading = function() {
1230 if(that.chart_created === false) {
1231 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1237 var isHidden = function() {
1238 if(typeof that.___chartIsHidden___ !== 'undefined')
1244 // hide the chart, when it is not visible - called from isVisible()
1245 var hideChart = function() {
1246 // hide it, if it is not already hidden
1247 if(isHidden() === true) return;
1249 if(that.chart_created === true) {
1250 // we should destroy it
1251 if(NETDATA.options.current.destroy_on_hide === true) {
1256 that.element_chart.style.display = 'none';
1257 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1258 that.tm.last_hidden = new Date().getTime();
1262 that.___chartIsHidden___ = true;
1265 // unhide the chart, when it is visible - called from isVisible()
1266 var unhideChart = function() {
1267 if(isHidden() === false) return;
1269 that.___chartIsHidden___ = undefined;
1270 that.updates_since_last_unhide = 0;
1272 if(that.chart_created === false) {
1273 // we need to re-initialize it, to show our background
1274 // logo in bootstrap tabs, until the chart loads
1278 that.tm.last_unhidden = new Date().getTime();
1279 that.element_chart.style.display = '';
1280 if(that.element_legend !== null) that.element_legend.style.display = '';
1286 var canBeRendered = function() {
1287 if(isHidden() === true || that.isVisible(true) === false)
1293 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1294 var callChartLibraryUpdateSafely = function(data) {
1297 if(canBeRendered() === false)
1300 if(NETDATA.options.debug.chart_errors === true)
1301 status = that.library.update(that, data);
1304 status = that.library.update(that, data);
1311 if(status === false) {
1312 error('chart failed to be updated as ' + that.library_name);
1319 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1320 var callChartLibraryCreateSafely = function(data) {
1323 if(canBeRendered() === false)
1326 if(NETDATA.options.debug.chart_errors === true)
1327 status = that.library.create(that, data);
1330 status = that.library.create(that, data);
1337 if(status === false) {
1338 error('chart failed to be created as ' + that.library_name);
1342 that.chart_created = true;
1343 that.updates_since_last_creation = 0;
1347 // ----------------------------------------------------------------------------------------------------------------
1350 // resizeChart() - private
1351 // to be called just before the chart library to make sure that
1352 // a properly sized dom is available
1353 var resizeChart = function() {
1354 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1355 if(that.chart_created === false) return;
1357 if(that.needsRecreation()) {
1360 else if(typeof that.library.resize === 'function') {
1361 that.library.resize(that);
1363 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1364 $(that.element_legend_childs.nano).nanoScroller();
1366 maxMessageFontSize();
1369 that.tm.last_resized = new Date().getTime();
1373 // this is the actual chart resize algorithm
1375 // - resize the entire container
1376 // - update the internal states
1377 // - resize the chart as the div changes height
1378 // - update the scrollbar of the legend
1379 var resizeChartToHeight = function(h) {
1381 that.element.style.height = h;
1383 if(that.settings_id !== null)
1384 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1386 var now = new Date().getTime();
1387 NETDATA.options.last_page_scroll = now;
1388 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1391 that.tm.last_resized = 0;
1395 this.resizeHandler = function(e) {
1398 if(typeof this.event_resize === 'undefined'
1399 || this.event_resize.chart_original_w === 'undefined'
1400 || this.event_resize.chart_original_h === 'undefined')
1401 this.event_resize = {
1402 chart_original_w: this.element.clientWidth,
1403 chart_original_h: this.element.clientHeight,
1407 if(e.type === 'touchstart') {
1408 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1409 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1412 this.event_resize.mouse_start_x = e.clientX;
1413 this.event_resize.mouse_start_y = e.clientY;
1416 this.event_resize.chart_start_w = this.element.clientWidth;
1417 this.event_resize.chart_start_h = this.element.clientHeight;
1418 this.event_resize.chart_last_w = this.element.clientWidth;
1419 this.event_resize.chart_last_h = this.element.clientHeight;
1421 var now = new Date().getTime();
1422 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1423 // double click / double tap event
1425 // the optimal height of the chart
1426 // showing the entire legend
1427 var optimal = this.event_resize.chart_last_h
1428 + this.element_legend_childs.content.scrollHeight
1429 - this.element_legend_childs.content.clientHeight;
1431 // if we are not optimal, be optimal
1432 if(this.event_resize.chart_last_h != optimal)
1433 resizeChartToHeight(optimal.toString() + 'px');
1435 // else if we do not have the original height
1436 // reset to the original height
1437 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1438 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1441 this.event_resize.last = now;
1443 // process movement event
1444 document.onmousemove =
1445 document.ontouchmove =
1446 this.element_legend_childs.resize_handler.onmousemove =
1447 this.element_legend_childs.resize_handler.ontouchmove =
1452 case 'mousemove': y = e.clientY; break;
1453 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1457 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1459 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1460 resizeChartToHeight(newH.toString() + 'px');
1461 that.event_resize.chart_last_h = newH;
1466 // process end event
1467 document.onmouseup =
1468 document.ontouchend =
1469 this.element_legend_childs.resize_handler.onmouseup =
1470 this.element_legend_childs.resize_handler.ontouchend =
1472 // remove all the hooks
1473 document.onmouseup =
1474 document.onmousemove =
1475 document.ontouchmove =
1476 document.ontouchend =
1477 that.element_legend_childs.resize_handler.onmousemove =
1478 that.element_legend_childs.resize_handler.ontouchmove =
1479 that.element_legend_childs.resize_handler.onmouseout =
1480 that.element_legend_childs.resize_handler.onmouseup =
1481 that.element_legend_childs.resize_handler.ontouchend =
1484 // allow auto-refreshes
1485 NETDATA.options.auto_refresher_stop_until = 0;
1491 var noDataToShow = function() {
1492 showMessageIcon('<i class="fa fa-warning"></i> empty');
1493 that.legendUpdateDOM();
1494 that.tm.last_autorefreshed = new Date().getTime();
1495 // that.data_update_every = 30 * 1000;
1496 //that.element_chart.style.display = 'none';
1497 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1498 //that.___chartIsHidden___ = true;
1501 // ============================================================================================================
1504 this.error = function(msg) {
1508 this.setMode = function(m) {
1509 if(this.current !== null && this.current.name === m) return;
1512 this.current = this.auto;
1513 else if(m === 'pan')
1514 this.current = this.pan;
1515 else if(m === 'zoom')
1516 this.current = this.zoom;
1518 this.current = this.auto;
1520 this.current.force_update_at = 0;
1521 this.current.force_before_ms = null;
1522 this.current.force_after_ms = null;
1524 this.tm.last_mode_switch = new Date().getTime();
1527 // ----------------------------------------------------------------------------------------------------------------
1528 // global selection sync
1530 // prevent to global selection sync for some time
1531 this.globalSelectionSyncDelay = function(ms) {
1532 if(NETDATA.options.current.sync_selection === false)
1535 if(typeof ms === 'number')
1536 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1538 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1541 // can we globally apply selection sync?
1542 this.globalSelectionSyncAbility = function() {
1543 if(NETDATA.options.current.sync_selection === false)
1546 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1552 this.globalSelectionSyncIsMaster = function() {
1553 if(NETDATA.globalSelectionSync.state === this)
1559 // this chart is the master of the global selection sync
1560 this.globalSelectionSyncBeMaster = function() {
1562 if(this.globalSelectionSyncIsMaster()) {
1563 if(this.debug === true)
1564 this.log('sync: I am the master already.');
1569 if(NETDATA.globalSelectionSync.state) {
1570 if(this.debug === true)
1571 this.log('sync: I am not the sync master. Resetting global sync.');
1573 this.globalSelectionSyncStop();
1576 // become the master
1577 if(this.debug === true)
1578 this.log('sync: becoming sync master.');
1580 this.selected = true;
1581 NETDATA.globalSelectionSync.state = this;
1583 // find the all slaves
1584 var targets = NETDATA.options.targets;
1585 var len = targets.length;
1590 if(this.debug === true)
1591 st.log('sync: not adding me to sync');
1593 else if(st.globalSelectionSyncIsEligible()) {
1594 if(this.debug === true)
1595 st.log('sync: adding to sync as slave');
1597 st.globalSelectionSyncBeSlave();
1601 // this.globalSelectionSyncDelay(100);
1604 // can the chart participate to the global selection sync as a slave?
1605 this.globalSelectionSyncIsEligible = function() {
1606 if(this.enabled === true
1607 && this.library !== null
1608 && typeof this.library.setSelection === 'function'
1609 && this.isVisible() === true
1610 && this.chart_created === true)
1616 // this chart becomes a slave of the global selection sync
1617 this.globalSelectionSyncBeSlave = function() {
1618 if(NETDATA.globalSelectionSync.state !== this)
1619 NETDATA.globalSelectionSync.slaves.push(this);
1622 // sync all the visible charts to the given time
1623 // this is to be called from the chart libraries
1624 this.globalSelectionSync = function(t) {
1625 if(this.globalSelectionSyncAbility() === false) {
1626 if(this.debug === true)
1627 this.log('sync: cannot sync (yet?).');
1632 if(this.globalSelectionSyncIsMaster() === false) {
1633 if(this.debug === true)
1634 this.log('sync: trying to be sync master.');
1636 this.globalSelectionSyncBeMaster();
1638 if(this.globalSelectionSyncAbility() === false) {
1639 if(this.debug === true)
1640 this.log('sync: cannot sync (yet?).');
1646 NETDATA.globalSelectionSync.last_t = t;
1647 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1652 // stop syncing all charts to the given time
1653 this.globalSelectionSyncStop = function() {
1654 if(NETDATA.globalSelectionSync.slaves.length) {
1655 if(this.debug === true)
1656 this.log('sync: cleaning up...');
1658 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1660 if(that.debug === true)
1661 st.log('sync: not adding me to sync stop');
1664 if(that.debug === true)
1665 st.log('sync: removed slave from sync');
1667 st.clearSelection();
1671 NETDATA.globalSelectionSync.last_t = 0;
1672 NETDATA.globalSelectionSync.slaves = [];
1673 NETDATA.globalSelectionSync.state = null;
1676 this.clearSelection();
1679 this.setSelection = function(t) {
1680 if(typeof this.library.setSelection === 'function') {
1681 if(this.library.setSelection(this, t) === true)
1682 this.selected = true;
1684 this.selected = false;
1686 else this.selected = true;
1688 if(this.selected === true && this.debug === true)
1689 this.log('selection set to ' + t.toString());
1691 return this.selected;
1694 this.clearSelection = function() {
1695 if(this.selected === true) {
1696 if(typeof this.library.clearSelection === 'function') {
1697 if(this.library.clearSelection(this) === true)
1698 this.selected = false;
1700 this.selected = true;
1702 else this.selected = false;
1704 if(this.selected === false && this.debug === true)
1705 this.log('selection cleared');
1710 return this.selected;
1713 // find if a timestamp (ms) is shown in the current chart
1714 this.timeIsVisible = function(t) {
1715 if(t >= this.data_after && t <= this.data_before)
1720 this.calculateRowForTime = function(t) {
1721 if(this.timeIsVisible(t) === false) return -1;
1722 return Math.floor((t - this.data_after) / this.data_update_every);
1725 // ----------------------------------------------------------------------------------------------------------------
1728 this.log = function(msg) {
1729 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1732 this.pauseChart = function() {
1733 if(this.paused === false) {
1734 if(this.debug === true)
1735 this.log('pauseChart()');
1741 this.unpauseChart = function() {
1742 if(this.paused === true) {
1743 if(this.debug === true)
1744 this.log('unpauseChart()');
1746 this.paused = false;
1750 this.resetChart = function(dont_clear_master, dont_update) {
1751 if(this.debug === true)
1752 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1754 if(typeof dont_clear_master === 'undefined')
1755 dont_clear_master = false;
1757 if(typeof dont_update === 'undefined')
1758 dont_update = false;
1760 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1761 if(this.debug === true)
1762 this.log('resetChart() diverting to clearMaster().');
1763 // this will call us back with master === true
1764 NETDATA.globalPanAndZoom.clearMaster();
1768 this.clearSelection();
1770 this.tm.pan_and_zoom_seq = 0;
1772 this.setMode('auto');
1773 this.current.force_update_at = 0;
1774 this.current.force_before_ms = null;
1775 this.current.force_after_ms = null;
1776 this.tm.last_autorefreshed = 0;
1777 this.paused = false;
1778 this.selected = false;
1779 this.enabled = true;
1780 // this.debug = false;
1782 // do not update the chart here
1783 // or the chart will flip-flop when it is the master
1784 // of a selection sync and another chart becomes
1787 if(dont_update !== true && this.isVisible() === true) {
1792 this.updateChartPanOrZoom = function(after, before) {
1793 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1796 if(this.debug === true)
1799 if(before < after) {
1800 if(this.debug === true)
1801 this.log(logme + 'flipped parameters, rejecting it.');
1806 if(typeof this.fixed_min_duration === 'undefined')
1807 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1809 var min_duration = this.fixed_min_duration;
1810 var current_duration = Math.round(this.view_before - this.view_after);
1812 // round the numbers
1813 after = Math.round(after);
1814 before = Math.round(before);
1816 // align them to update_every
1817 // stretching them further away
1818 after -= after % this.data_update_every;
1819 before += this.data_update_every - (before % this.data_update_every);
1821 // the final wanted duration
1822 var wanted_duration = before - after;
1824 // to allow panning, accept just a point below our minimum
1825 if((current_duration - this.data_update_every) < min_duration)
1826 min_duration = current_duration - this.data_update_every;
1828 // we do it, but we adjust to minimum size and return false
1829 // when the wanted size is below the current and the minimum
1831 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1832 if(this.debug === true)
1833 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1835 min_duration = this.fixed_min_duration;
1837 var dt = (min_duration - wanted_duration) / 2;
1840 wanted_duration = before - after;
1844 var tolerance = this.data_update_every * 2;
1845 var movement = Math.abs(before - this.view_before);
1847 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1848 if(this.debug === true)
1849 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);
1853 if(this.current.name === 'auto') {
1854 this.log(logme + 'caller called me with mode: ' + this.current.name);
1855 this.setMode('pan');
1858 if(this.debug === true)
1859 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);
1861 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1862 this.current.force_after_ms = after;
1863 this.current.force_before_ms = before;
1864 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1868 this.legendFormatValue = function(value) {
1869 if(value === null || value === 'undefined') return '-';
1870 if(typeof value !== 'number') return value;
1872 var abs = Math.abs(value);
1873 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1874 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1875 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1876 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1877 return (Math.round(value * 10000) / 10000).toLocaleString();
1880 this.legendSetLabelValue = function(label, value) {
1881 var series = this.element_legend_childs.series[label];
1882 if(typeof series === 'undefined') return;
1883 if(series.value === null && series.user === null) return;
1885 // if the value has not changed, skip DOM update
1886 //if(series.last === value) return;
1889 if(typeof value === 'number') {
1890 var v = Math.abs(value);
1891 s = r = this.legendFormatValue(value);
1893 if(typeof series.last === 'number') {
1894 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1895 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1896 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1898 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1903 series.last = value;
1906 if(series.value !== null) series.value.innerHTML = s;
1907 if(series.user !== null) series.user.innerHTML = r;
1910 this.legendSetDate = function(ms) {
1911 if(typeof ms !== 'number') {
1912 this.legendShowUndefined();
1916 var d = new Date(ms);
1918 if(this.element_legend_childs.title_date)
1919 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1921 if(this.element_legend_childs.title_time)
1922 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1924 if(this.element_legend_childs.title_units)
1925 this.element_legend_childs.title_units.innerHTML = this.units;
1928 this.legendShowUndefined = function() {
1929 if(this.element_legend_childs.title_date)
1930 this.element_legend_childs.title_date.innerHTML = ' ';
1932 if(this.element_legend_childs.title_time)
1933 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1935 if(this.element_legend_childs.title_units)
1936 this.element_legend_childs.title_units.innerHTML = ' ';
1938 if(this.data && this.element_legend_childs.series !== null) {
1939 var labels = this.data.dimension_names;
1940 var i = labels.length;
1942 var label = labels[i];
1944 if(typeof label === 'undefined') continue;
1945 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1946 this.legendSetLabelValue(label, null);
1951 this.legendShowLatestValues = function() {
1952 if(this.chart === null) return;
1953 if(this.selected) return;
1955 if(this.data === null || this.element_legend_childs.series === null) {
1956 this.legendShowUndefined();
1960 var show_undefined = true;
1961 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1962 show_undefined = false;
1964 if(show_undefined) {
1965 this.legendShowUndefined();
1969 this.legendSetDate(this.view_before);
1971 var labels = this.data.dimension_names;
1972 var i = labels.length;
1974 var label = labels[i];
1976 if(typeof label === 'undefined') continue;
1977 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1980 this.legendSetLabelValue(label, null);
1982 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1986 this.legendReset = function() {
1987 this.legendShowLatestValues();
1990 // this should be called just ONCE per dimension per chart
1991 this._chartDimensionColor = function(label) {
1992 if(this.colors === null) this.chartColors();
1994 if(typeof this.colors_assigned[label] === 'undefined') {
1995 if(this.colors_available.length === 0) {
1996 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1997 this.colors_available.push(NETDATA.themes.current.colors[i]);
2000 this.colors_assigned[label] = this.colors_available.shift();
2002 if(this.debug === true)
2003 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2006 if(this.debug === true)
2007 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2010 this.colors.push(this.colors_assigned[label]);
2011 return this.colors_assigned[label];
2014 this.chartColors = function() {
2015 if(this.colors !== null) return this.colors;
2017 this.colors = new Array();
2018 this.colors_available = new Array();
2021 var c = $(this.element).data('colors');
2022 // this.log('read colors: ' + c);
2023 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2024 if(typeof c !== 'string') {
2025 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2032 for(i = 0, len = c.length; i < len ; i++) {
2034 this.colors_available.push(c[i]);
2035 // this.log('adding color: ' + c[i]);
2041 // push all the standard colors too
2042 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2043 this.colors_available.push(NETDATA.themes.current.colors[i]);
2048 this.legendUpdateDOM = function() {
2051 // check that the legend DOM is up to date for the downloaded dimensions
2052 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2053 // this.log('the legend does not have any series - requesting legend update');
2056 else if(this.data === null) {
2057 // this.log('the chart does not have any data - requesting legend update');
2060 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2064 var labels = this.data.dimension_names.toString();
2065 if(labels !== this.element_legend_childs.series.labels_key) {
2068 if(this.debug === true)
2069 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2073 if(needed === false) {
2074 // make sure colors available
2077 // do we have to update the current values?
2078 // we do this, only when the visible chart is current
2079 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2080 if(this.debug === true)
2081 this.log('chart is in latest position... updating values on legend...');
2083 //var labels = this.data.dimension_names;
2084 //var i = labels.length;
2086 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2090 if(this.colors === null) {
2091 // this is the first time we update the chart
2092 // let's assign colors to all dimensions
2093 if(this.library.track_colors() === true)
2094 for(var dim in this.chart.dimensions)
2095 this._chartDimensionColor(this.chart.dimensions[dim].name);
2097 // we will re-generate the colors for the chart
2098 // based on the selected dimensions
2101 if(this.debug === true)
2102 this.log('updating Legend DOM');
2104 // mark all dimensions as invalid
2105 this.dimensions_visibility.invalidateAll();
2107 var genLabel = function(state, parent, name, count) {
2108 var color = state._chartDimensionColor(name);
2110 var user_element = null;
2111 var user_id = self.data('show-value-of-' + name + '-at') || null;
2112 if(user_id !== null) {
2113 user_element = document.getElementById(user_id) || null;
2114 if(user_element === null)
2115 state.log('Cannot find element with id: ' + user_id);
2118 state.element_legend_childs.series[name] = {
2119 name: document.createElement('span'),
2120 value: document.createElement('span'),
2125 var label = state.element_legend_childs.series[name];
2127 // create the dimension visibility tracking for this label
2128 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2130 var rgb = NETDATA.colorHex2Rgb(color);
2131 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2132 + state.chart.chart_type
2133 + '" style="background-color: '
2134 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2135 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2137 var text = document.createTextNode(' ' + name);
2138 label.name.appendChild(text);
2141 parent.appendChild(document.createElement('br'));
2143 parent.appendChild(label.name);
2144 parent.appendChild(label.value);
2147 var content = document.createElement('div');
2149 if(this.hasLegend()) {
2150 this.element_legend_childs = {
2152 resize_handler: document.createElement('div'),
2153 toolbox: document.createElement('div'),
2154 toolbox_left: document.createElement('div'),
2155 toolbox_right: document.createElement('div'),
2156 toolbox_reset: document.createElement('div'),
2157 toolbox_zoomin: document.createElement('div'),
2158 toolbox_zoomout: document.createElement('div'),
2159 toolbox_volume: document.createElement('div'),
2160 title_date: document.createElement('span'),
2161 title_time: document.createElement('span'),
2162 title_units: document.createElement('span'),
2163 nano: document.createElement('div'),
2165 paneClass: 'netdata-legend-series-pane',
2166 sliderClass: 'netdata-legend-series-slider',
2167 contentClass: 'netdata-legend-series-content',
2168 enabledClass: '__enabled',
2169 flashedClass: '__flashed',
2170 activeClass: '__active',
2172 alwaysVisible: true,
2178 this.element_legend.innerHTML = '';
2180 if(this.library.toolboxPanAndZoom !== null) {
2182 function get_pan_and_zoom_step(event) {
2184 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2186 else if (event.shiftKey)
2187 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2189 else if (event.altKey)
2190 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2193 return NETDATA.options.current.pan_and_zoom_factor;
2196 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2197 this.element.appendChild(this.element_legend_childs.toolbox);
2199 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2200 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2201 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2202 this.element_legend_childs.toolbox_left.onclick = function(e) {
2205 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2206 var before = that.view_before - step;
2207 var after = that.view_after - step;
2208 if(after >= that.netdata_first)
2209 that.library.toolboxPanAndZoom(that, after, before);
2211 if(NETDATA.options.current.show_help === true)
2212 $(this.element_legend_childs.toolbox_left).popover({
2217 placement: 'bottom',
2218 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2220 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>'
2224 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2225 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2226 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2227 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2229 NETDATA.resetAllCharts(that);
2231 if(NETDATA.options.current.show_help === true)
2232 $(this.element_legend_childs.toolbox_reset).popover({
2237 placement: 'bottom',
2238 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2239 title: 'Chart Reset',
2240 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>'
2243 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2244 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2245 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2246 this.element_legend_childs.toolbox_right.onclick = function(e) {
2248 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2249 var before = that.view_before + step;
2250 var after = that.view_after + step;
2251 if(before <= that.netdata_last)
2252 that.library.toolboxPanAndZoom(that, after, before);
2254 if(NETDATA.options.current.show_help === true)
2255 $(this.element_legend_childs.toolbox_right).popover({
2260 placement: 'bottom',
2261 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2263 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>'
2267 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2268 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2269 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2270 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2272 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2273 var before = that.view_before - dt;
2274 var after = that.view_after + dt;
2275 that.library.toolboxPanAndZoom(that, after, before);
2277 if(NETDATA.options.current.show_help === true)
2278 $(this.element_legend_childs.toolbox_zoomin).popover({
2283 placement: 'bottom',
2284 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2285 title: 'Chart Zoom In',
2286 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>'
2289 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2290 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2291 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2292 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2294 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);
2295 var before = that.view_before + dt;
2296 var after = that.view_after - dt;
2298 that.library.toolboxPanAndZoom(that, after, before);
2300 if(NETDATA.options.current.show_help === true)
2301 $(this.element_legend_childs.toolbox_zoomout).popover({
2306 placement: 'bottom',
2307 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2308 title: 'Chart Zoom Out',
2309 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>'
2312 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2313 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2314 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2315 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2316 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2317 //e.preventDefault();
2318 //alert('clicked toolbox_volume on ' + that.id);
2322 this.element_legend_childs.toolbox = null;
2323 this.element_legend_childs.toolbox_left = null;
2324 this.element_legend_childs.toolbox_reset = null;
2325 this.element_legend_childs.toolbox_right = null;
2326 this.element_legend_childs.toolbox_zoomin = null;
2327 this.element_legend_childs.toolbox_zoomout = null;
2328 this.element_legend_childs.toolbox_volume = null;
2331 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2332 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2333 this.element.appendChild(this.element_legend_childs.resize_handler);
2334 if(NETDATA.options.current.show_help === true)
2335 $(this.element_legend_childs.resize_handler).popover({
2340 placement: 'bottom',
2341 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2342 title: 'Chart Resize',
2343 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>'
2347 this.element_legend_childs.resize_handler.onmousedown =
2349 that.resizeHandler(e);
2353 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2354 that.resizeHandler(e);
2357 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2358 this.element_legend.appendChild(this.element_legend_childs.title_date);
2360 this.element_legend.appendChild(document.createElement('br'));
2362 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2363 this.element_legend.appendChild(this.element_legend_childs.title_time);
2365 this.element_legend.appendChild(document.createElement('br'));
2367 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2368 this.element_legend.appendChild(this.element_legend_childs.title_units);
2370 this.element_legend.appendChild(document.createElement('br'));
2372 this.element_legend_childs.nano.className = 'netdata-legend-series';
2373 this.element_legend.appendChild(this.element_legend_childs.nano);
2375 content.className = 'netdata-legend-series-content';
2376 this.element_legend_childs.nano.appendChild(content);
2378 if(NETDATA.options.current.show_help === true)
2379 $(content).popover({
2384 placement: 'bottom',
2385 title: 'Chart Legend',
2386 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2387 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>'
2391 this.element_legend_childs = {
2393 resize_handler: null,
2396 toolbox_right: null,
2397 toolbox_reset: null,
2398 toolbox_zoomin: null,
2399 toolbox_zoomout: null,
2400 toolbox_volume: null,
2411 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2412 if(this.debug === true)
2413 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2415 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2416 genLabel(this, content, this.data.dimension_names[i], i);
2420 var tmp = new Array();
2421 for(var dim in this.chart.dimensions) {
2422 tmp.push(this.chart.dimensions[dim].name);
2423 genLabel(this, content, this.chart.dimensions[dim].name, i);
2425 this.element_legend_childs.series.labels_key = tmp.toString();
2426 if(this.debug === true)
2427 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2430 // create a hidden div to be used for hidding
2431 // the original legend of the chart library
2432 var el = document.createElement('div');
2433 if(this.element_legend !== null)
2434 this.element_legend.appendChild(el);
2435 el.style.display = 'none';
2437 this.element_legend_childs.hidden = document.createElement('div');
2438 el.appendChild(this.element_legend_childs.hidden);
2440 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2441 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2443 this.legendShowLatestValues();
2446 this.hasLegend = function() {
2447 if(typeof this.___hasLegendCache___ !== 'undefined')
2448 return this.___hasLegendCache___;
2451 if(this.library && this.library.legend(this) === 'right-side') {
2452 var legend = $(this.element).data('legend') || 'yes';
2453 if(legend === 'yes') leg = true;
2456 this.___hasLegendCache___ = leg;
2460 this.legendWidth = function() {
2461 return (this.hasLegend())?140:0;
2464 this.legendHeight = function() {
2465 return $(this.element).height();
2468 this.chartWidth = function() {
2469 return $(this.element).width() - this.legendWidth();
2472 this.chartHeight = function() {
2473 return $(this.element).height();
2476 this.chartPixelsPerPoint = function() {
2477 // force an options provided detail
2478 var px = this.pixels_per_point;
2480 if(this.library && px < this.library.pixels_per_point(this))
2481 px = this.library.pixels_per_point(this);
2483 if(px < NETDATA.options.current.pixels_per_point)
2484 px = NETDATA.options.current.pixels_per_point;
2489 this.needsRecreation = function() {
2491 this.chart_created === true
2493 && this.library.autoresize() === false
2494 && this.tm.last_resized < NETDATA.options.last_resized
2498 this.chartURL = function() {
2499 var after, before, points_multiplier = 1;
2500 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2501 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2503 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2504 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2505 this.view_after = after * 1000;
2506 this.view_before = before * 1000;
2508 this.requested_padding = null;
2509 points_multiplier = 1;
2511 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2512 this.tm.pan_and_zoom_seq = 0;
2514 before = Math.round(this.current.force_before_ms / 1000);
2515 after = Math.round(this.current.force_after_ms / 1000);
2516 this.view_after = after * 1000;
2517 this.view_before = before * 1000;
2519 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2520 this.requested_padding = Math.round((before - after) / 2);
2521 after -= this.requested_padding;
2522 before += this.requested_padding;
2523 this.requested_padding *= 1000;
2524 points_multiplier = 2;
2527 this.current.force_before_ms = null;
2528 this.current.force_after_ms = null;
2531 this.tm.pan_and_zoom_seq = 0;
2533 before = this.before;
2535 this.view_after = after * 1000;
2536 this.view_before = before * 1000;
2538 this.requested_padding = null;
2539 points_multiplier = 1;
2542 this.requested_after = after * 1000;
2543 this.requested_before = before * 1000;
2545 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2547 // build the data URL
2548 this.data_url = this.host + this.chart.data_url;
2549 this.data_url += "&format=" + this.library.format();
2550 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2551 this.data_url += "&group=" + this.method;
2552 this.data_url += "&options=" + this.library.options(this);
2553 this.data_url += '|jsonwrap';
2555 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2556 this.data_url += '|nonzero';
2558 if(this.append_options !== null)
2559 this.data_url += '|' + this.append_options.toString();
2562 this.data_url += "&after=" + after.toString();
2565 this.data_url += "&before=" + before.toString();
2568 this.data_url += "&dimensions=" + this.dimensions;
2570 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2571 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2574 this.redrawChart = function() {
2575 if(this.data !== null)
2576 this.updateChartWithData(this.data);
2579 this.updateChartWithData = function(data) {
2580 if(this.debug === true)
2581 this.log('updateChartWithData() called.');
2583 this._updating = false;
2585 // this may force the chart to be re-created
2589 this.updates_counter++;
2590 this.updates_since_last_unhide++;
2591 this.updates_since_last_creation++;
2593 var started = new Date().getTime();
2595 // if the result is JSON, find the latest update-every
2596 this.data_update_every = data.view_update_every * 1000;
2597 this.data_after = data.after * 1000;
2598 this.data_before = data.before * 1000;
2599 this.netdata_first = data.first_entry * 1000;
2600 this.netdata_last = data.last_entry * 1000;
2601 this.data_points = data.points;
2604 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2605 if(this.view_after < this.data_after) {
2606 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2607 this.view_after = this.data_after;
2610 if(this.view_before > this.data_before) {
2611 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2612 this.view_before = this.data_before;
2616 this.view_after = this.data_after;
2617 this.view_before = this.data_before;
2620 if(this.debug === true) {
2621 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2623 if(this.current.force_after_ms)
2624 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2626 this.log('STATUS: forced : unset');
2628 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2629 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2630 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2631 this.log('STATUS: points : ' + (this.data_points).toString());
2634 if(this.data_points === 0) {
2639 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2640 if(this.debug === true)
2641 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2643 this.chart_created = false;
2646 // check and update the legend
2647 this.legendUpdateDOM();
2649 if(this.chart_created === true
2650 && typeof this.library.update === 'function') {
2652 if(this.debug === true)
2653 this.log('updating chart...');
2655 if(callChartLibraryUpdateSafely(data) === false)
2659 if(this.debug === true)
2660 this.log('creating chart...');
2662 if(callChartLibraryCreateSafely(data) === false)
2666 this.legendShowLatestValues();
2667 if(this.selected === true)
2668 NETDATA.globalSelectionSync.stop();
2670 // update the performance counters
2671 var now = new Date().getTime();
2672 this.tm.last_updated = now;
2674 // don't update last_autorefreshed if this chart is
2675 // forced to be updated with global PanAndZoom
2676 if(NETDATA.globalPanAndZoom.isActive())
2677 this.tm.last_autorefreshed = 0;
2679 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2680 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2682 this.tm.last_autorefreshed = now;
2685 this.refresh_dt_ms = now - started;
2686 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2688 if(this.refresh_dt_element !== null)
2689 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2692 this.updateChart = function(callback) {
2693 if(this.debug === true)
2694 this.log('updateChart() called.');
2696 if(this._updating === true) {
2697 if(this.debug === true)
2698 this.log('I am already updating...');
2700 if(typeof callback === 'function') callback();
2704 // due to late initialization of charts and libraries
2705 // we need to check this too
2706 if(this.enabled === false) {
2707 if(this.debug === true)
2708 this.log('I am not enabled');
2710 if(typeof callback === 'function') callback();
2714 if(canBeRendered() === false) {
2715 if(typeof callback === 'function') callback();
2719 if(this.chart === null) {
2720 this.getChart(function() { that.updateChart(callback); });
2724 if(this.library.initialized === false) {
2725 if(this.library.enabled === true) {
2726 this.library.initialize(function() { that.updateChart(callback); });
2730 error('chart library "' + this.library_name + '" is not available.');
2731 if(typeof callback === 'function') callback();
2736 this.clearSelection();
2739 if(this.debug === true)
2740 this.log('updating from ' + this.data_url);
2742 this._updating = true;
2744 this.xhr = $.ajax( {
2746 crossDomain: NETDATA.options.crossDomainAjax,
2750 .success(function(data) {
2751 if(that.debug === true)
2752 that.log('data received. updating chart.');
2754 that.updateChartWithData(data);
2757 error('data download failed for url: ' + that.data_url);
2759 .always(function() {
2760 that._updating = false;
2761 if(typeof callback === 'function') callback();
2767 this.isVisible = function(nocache) {
2768 if(typeof nocache === 'undefined')
2771 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2773 // caching - we do not evaluate the charts visibility
2774 // if the page has not been scrolled since the last check
2775 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2776 return this.___isVisible___;
2778 this.tm.last_visible_check = new Date().getTime();
2780 var wh = window.innerHeight;
2781 var x = this.element.getBoundingClientRect();
2785 if(x.width === 0 || x.height === 0) {
2787 this.___isVisible___ = false;
2788 return this.___isVisible___;
2791 if(x.top < 0 && -x.top > x.height) {
2792 // the chart is entirely above
2793 ret = -x.top - x.height;
2795 else if(x.top > wh) {
2796 // the chart is entirely below
2800 if(ret > tolerance) {
2801 // the chart is too far
2804 this.___isVisible___ = false;
2805 return this.___isVisible___;
2808 // the chart is inside or very close
2811 this.___isVisible___ = true;
2812 return this.___isVisible___;
2816 this.isAutoRefreshed = function() {
2817 return (this.current.autorefresh);
2820 this.canBeAutoRefreshed = function() {
2821 var now = new Date().getTime();
2823 if(this.enabled === false) {
2824 if(this.debug === true)
2825 this.log('I am not enabled');
2830 if(this.library === null || this.library.enabled === false) {
2831 error('charting library "' + this.library_name + '" is not available');
2832 if(this.debug === true)
2833 this.log('My chart library ' + this.library_name + ' is not available');
2838 if(this.isVisible() === false) {
2839 if(NETDATA.options.debug.visibility === true || this.debug === true)
2840 this.log('I am not visible');
2845 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2846 if(this.debug === true)
2847 this.log('timed force update detected - allowing this update');
2849 this.current.force_update_at = 0;
2853 if(this.isAutoRefreshed() === true) {
2854 // allow the first update, even if the page is not visible
2855 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2856 if(NETDATA.options.debug.focus === true || this.debug === true)
2857 this.log('canBeAutoRefreshed(): page does not have focus');
2862 if(this.needsRecreation() === true) {
2863 if(this.debug === true)
2864 this.log('canBeAutoRefreshed(): needs re-creation.');
2869 // options valid only for autoRefresh()
2870 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2871 if(NETDATA.globalPanAndZoom.isActive()) {
2872 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2873 if(this.debug === true)
2874 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2879 if(this.debug === true)
2880 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2886 if(this.selected === true) {
2887 if(this.debug === true)
2888 this.log('canBeAutoRefreshed(): I have a selection in place.');
2893 if(this.paused === true) {
2894 if(this.debug === true)
2895 this.log('canBeAutoRefreshed(): I am paused.');
2900 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2901 if(this.debug === true)
2902 this.log('canBeAutoRefreshed(): It is time to update me.');
2912 this.autoRefresh = function(callback) {
2913 if(this.canBeAutoRefreshed() === true) {
2914 this.updateChart(callback);
2917 if(typeof callback !== 'undefined')
2922 this._defaultsFromDownloadedChart = function(chart) {
2924 this.chart_url = chart.url;
2925 this.data_update_every = chart.update_every * 1000;
2926 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2927 this.tm.last_info_downloaded = new Date().getTime();
2929 if(this.title === null)
2930 this.title = chart.title;
2932 if(this.units === null)
2933 this.units = chart.units;
2936 // fetch the chart description from the netdata server
2937 this.getChart = function(callback) {
2938 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2940 this._defaultsFromDownloadedChart(this.chart);
2941 if(typeof callback === 'function') callback();
2944 this.chart_url = "/api/v1/chart?chart=" + this.id;
2946 if(this.debug === true)
2947 this.log('downloading ' + this.chart_url);
2950 url: this.host + this.chart_url,
2951 crossDomain: NETDATA.options.crossDomainAjax,
2955 .done(function(chart) {
2956 chart.url = that.chart_url;
2957 that._defaultsFromDownloadedChart(chart);
2958 NETDATA.chartRegistry.add(that.host, that.id, chart);
2961 NETDATA.error(404, that.chart_url);
2962 error('chart not found on url "' + that.chart_url + '"');
2964 .always(function() {
2965 if(typeof callback === 'function') callback();
2970 // ============================================================================================================
2976 NETDATA.resetAllCharts = function(state) {
2977 // first clear the global selection sync
2978 // to make sure no chart is in selected state
2979 state.globalSelectionSyncStop();
2981 // there are 2 possibilities here
2982 // a. state is the global Pan and Zoom master
2983 // b. state is not the global Pan and Zoom master
2985 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2988 // clear the global Pan and Zoom
2989 // this will also refresh the master
2990 // and unblock any charts currently mirroring the master
2991 NETDATA.globalPanAndZoom.clearMaster();
2993 // if we were not the master, reset our status too
2994 // this is required because most probably the mouse
2995 // is over this chart, blocking it from auto-refreshing
2996 if(master === false && (state.paused === true || state.selected === true))
3000 // get or create a chart state, given a DOM element
3001 NETDATA.chartState = function(element) {
3002 var state = $(element).data('netdata-state-object') || null;
3003 if(state === null) {
3004 state = new chartState(element);
3005 $(element).data('netdata-state-object', state);
3010 // ----------------------------------------------------------------------------------------------------------------
3011 // Library functions
3013 // Load a script without jquery
3014 // This is used to load jquery - after it is loaded, we use jquery
3015 NETDATA._loadjQuery = function(callback) {
3016 if(typeof jQuery === 'undefined') {
3017 if(NETDATA.options.debug.main_loop === true)
3018 console.log('loading ' + NETDATA.jQuery);
3020 var script = document.createElement('script');
3021 script.type = 'text/javascript';
3022 script.async = true;
3023 script.src = NETDATA.jQuery;
3025 // script.onabort = onError;
3026 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3027 if(typeof callback === "function")
3028 script.onload = callback;
3030 var s = document.getElementsByTagName('script')[0];
3031 s.parentNode.insertBefore(script, s);
3033 else if(typeof callback === "function")
3037 NETDATA._loadCSS = function(filename) {
3038 // don't use jQuery here
3039 // styles are loaded before jQuery
3040 // to eliminate showing an unstyled page to the user
3042 var fileref = document.createElement("link");
3043 fileref.setAttribute("rel", "stylesheet");
3044 fileref.setAttribute("type", "text/css");
3045 fileref.setAttribute("href", filename);
3047 if (typeof fileref !== 'undefined')
3048 document.getElementsByTagName("head")[0].appendChild(fileref);
3051 NETDATA.colorHex2Rgb = function(hex) {
3052 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3053 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3054 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3055 return r + r + g + g + b + b;
3058 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3060 r: parseInt(result[1], 16),
3061 g: parseInt(result[2], 16),
3062 b: parseInt(result[3], 16)
3066 NETDATA.colorLuminance = function(hex, lum) {
3067 // validate hex string
3068 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3070 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3074 // convert to decimal and change luminosity
3075 var rgb = "#", c, i;
3076 for (i = 0; i < 3; i++) {
3077 c = parseInt(hex.substr(i*2,2), 16);
3078 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3079 rgb += ("00"+c).substr(c.length);
3085 NETDATA.guid = function() {
3087 return Math.floor((1 + Math.random()) * 0x10000)
3092 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3095 NETDATA.zeropad = function(x) {
3096 if(x > -10 && x < 10) return '0' + x.toString();
3097 else return x.toString();
3100 // user function to signal us the DOM has been
3102 NETDATA.updatedDom = function() {
3103 NETDATA.options.updated_dom = true;
3106 NETDATA.ready = function(callback) {
3107 NETDATA.options.pauseCallback = callback;
3110 NETDATA.pause = function(callback) {
3111 if(NETDATA.options.pause === true)
3114 NETDATA.options.pauseCallback = callback;
3117 NETDATA.unpause = function() {
3118 NETDATA.options.pauseCallback = null;
3119 NETDATA.options.updated_dom = true;
3120 NETDATA.options.pause = false;
3123 // ----------------------------------------------------------------------------------------------------------------
3125 // this is purely sequencial charts refresher
3126 // it is meant to be autonomous
3127 NETDATA.chartRefresherNoParallel = function(index) {
3128 if(NETDATA.options.debug.mail_loop === true)
3129 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3131 if(NETDATA.options.updated_dom === true) {
3132 // the dom has been updated
3133 // get the dom parts again
3134 NETDATA.parseDom(NETDATA.chartRefresher);
3137 if(index >= NETDATA.options.targets.length) {
3138 if(NETDATA.options.debug.main_loop === true)
3139 console.log('waiting to restart main loop...');
3141 NETDATA.options.auto_refresher_fast_weight = 0;
3143 setTimeout(function() {
3144 NETDATA.chartRefresher();
3145 }, NETDATA.options.current.idle_between_loops);
3148 var state = NETDATA.options.targets[index];
3150 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3151 if(NETDATA.options.debug.main_loop === true)
3152 console.log('fast rendering...');
3154 state.autoRefresh(function() {
3155 NETDATA.chartRefresherNoParallel(++index);
3159 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3160 NETDATA.options.auto_refresher_fast_weight = 0;
3162 setTimeout(function() {
3163 state.autoRefresh(function() {
3164 NETDATA.chartRefresherNoParallel(++index);
3166 }, NETDATA.options.current.idle_between_charts);
3171 // this is part of the parallel refresher
3172 // its cause is to refresh sequencially all the charts
3173 // that depend on chart library initialization
3174 // it will call the parallel refresher back
3175 // as soon as it sees a chart that its chart library
3177 NETDATA.chartRefresher_uninitialized = function() {
3178 if(NETDATA.options.updated_dom === true) {
3179 // the dom has been updated
3180 // get the dom parts again
3181 NETDATA.parseDom(NETDATA.chartRefresher);
3185 if(NETDATA.options.sequencial.length === 0)
3186 NETDATA.chartRefresher();
3188 var state = NETDATA.options.sequencial.pop();
3189 if(state.library.initialized === true)
3190 NETDATA.chartRefresher();
3192 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3196 NETDATA.chartRefresherWaitTime = function() {
3197 return NETDATA.options.current.idle_parallel_loops;
3200 // the default refresher
3201 // it will create 2 sets of charts:
3202 // - the ones that can be refreshed in parallel
3203 // - the ones that depend on something else
3204 // the first set will be executed in parallel
3205 // the second will be given to NETDATA.chartRefresher_uninitialized()
3206 NETDATA.chartRefresher = function() {
3207 if(NETDATA.options.pause === true) {
3208 // console.log('auto-refresher is paused');
3209 setTimeout(NETDATA.chartRefresher,
3210 NETDATA.chartRefresherWaitTime());
3214 if(typeof NETDATA.options.pauseCallback === 'function') {
3215 // console.log('auto-refresher is calling pauseCallback');
3216 NETDATA.options.pause = true;
3217 NETDATA.options.pauseCallback();
3218 NETDATA.chartRefresher();
3222 if(NETDATA.options.current.parallel_refresher === false) {
3223 NETDATA.chartRefresherNoParallel(0);
3227 if(NETDATA.options.updated_dom === true) {
3228 // the dom has been updated
3229 // get the dom parts again
3230 NETDATA.parseDom(NETDATA.chartRefresher);
3234 var parallel = new Array();
3235 var targets = NETDATA.options.targets;
3236 var len = targets.length;
3238 if(targets[len].isVisible() === false)
3241 var state = targets[len];
3242 if(state.library.initialized === false) {
3243 if(state.library.enabled === true) {
3244 state.library.initialize(NETDATA.chartRefresher);
3248 state.error('chart library "' + state.library_name + '" is not enabled.');
3252 parallel.unshift(state);
3255 if(parallel.length > 0) {
3256 var parallel_jobs = parallel.length;
3258 // this will execute the jobs in parallel
3259 $(parallel).each(function() {
3260 this.autoRefresh(function() {
3263 if(parallel_jobs === 0) {
3264 setTimeout(NETDATA.chartRefresher,
3265 NETDATA.chartRefresherWaitTime());
3271 setTimeout(NETDATA.chartRefresher,
3272 NETDATA.chartRefresherWaitTime());
3276 NETDATA.parseDom = function(callback) {
3277 NETDATA.options.last_page_scroll = new Date().getTime();
3278 NETDATA.options.updated_dom = false;
3280 var targets = $('div[data-netdata]'); //.filter(':visible');
3282 if(NETDATA.options.debug.main_loop === true)
3283 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3285 NETDATA.options.targets = new Array();
3286 var len = targets.length;
3288 // the initialization will take care of sizing
3289 // and the "loading..." message
3290 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3293 if(typeof callback === 'function') callback();
3296 // this is the main function - where everything starts
3297 NETDATA.start = function() {
3298 // this should be called only once
3300 NETDATA.options.page_is_visible = true;
3302 $(window).blur(function() {
3303 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3304 NETDATA.options.page_is_visible = false;
3305 if(NETDATA.options.debug.focus === true)
3306 console.log('Lost Focus!');
3310 $(window).focus(function() {
3311 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3312 NETDATA.options.page_is_visible = true;
3313 if(NETDATA.options.debug.focus === true)
3314 console.log('Focus restored!');
3318 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3319 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3320 NETDATA.options.page_is_visible = false;
3321 if(NETDATA.options.debug.focus === true)
3322 console.log('Document has no focus!');
3326 // bootstrap tab switching
3327 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3329 // bootstrap modal switching
3330 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3331 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3333 NETDATA.parseDom(NETDATA.chartRefresher);
3336 // ----------------------------------------------------------------------------------------------------------------
3339 NETDATA.peityInitialize = function(callback) {
3340 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3342 url: NETDATA.peity_js,
3347 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3350 NETDATA.chartLibraries.peity.enabled = false;
3351 NETDATA.error(100, NETDATA.peity_js);
3353 .always(function() {
3354 if(typeof callback === "function")
3359 NETDATA.chartLibraries.peity.enabled = false;
3360 if(typeof callback === "function")
3365 NETDATA.peityChartUpdate = function(state, data) {
3366 state.peity_instance.innerHTML = data.result;
3368 if(state.peity_options.stroke !== state.chartColors()[0]) {
3369 state.peity_options.stroke = state.chartColors()[0];
3370 if(state.chart.chart_type === 'line')
3371 state.peity_options.fill = NETDATA.themes.current.background;
3373 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3376 $(state.peity_instance).peity('line', state.peity_options);
3380 NETDATA.peityChartCreate = function(state, data) {
3381 state.peity_instance = document.createElement('div');
3382 state.element_chart.appendChild(state.peity_instance);
3384 var self = $(state.element);
3385 state.peity_options = {
3386 stroke: NETDATA.themes.current.foreground,
3387 strokeWidth: self.data('peity-strokewidth') || 1,
3388 width: state.chartWidth(),
3389 height: state.chartHeight(),
3390 fill: NETDATA.themes.current.foreground
3393 NETDATA.peityChartUpdate(state, data);
3397 // ----------------------------------------------------------------------------------------------------------------
3400 NETDATA.sparklineInitialize = function(callback) {
3401 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3403 url: NETDATA.sparkline_js,
3408 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3411 NETDATA.chartLibraries.sparkline.enabled = false;
3412 NETDATA.error(100, NETDATA.sparkline_js);
3414 .always(function() {
3415 if(typeof callback === "function")
3420 NETDATA.chartLibraries.sparkline.enabled = false;
3421 if(typeof callback === "function")
3426 NETDATA.sparklineChartUpdate = function(state, data) {
3427 state.sparkline_options.width = state.chartWidth();
3428 state.sparkline_options.height = state.chartHeight();
3430 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3434 NETDATA.sparklineChartCreate = function(state, data) {
3435 var self = $(state.element);
3436 var type = self.data('sparkline-type') || 'line';
3437 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3438 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3439 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3440 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3441 var composite = self.data('sparkline-composite') || undefined;
3442 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3443 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3444 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3445 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3446 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3447 var spotColor = self.data('sparkline-spotcolor') || undefined;
3448 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3449 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3450 var spotRadius = self.data('sparkline-spotradius') || undefined;
3451 var valueSpots = self.data('sparkline-valuespots') || undefined;
3452 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3453 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3454 var lineWidth = self.data('sparkline-linewidth') || undefined;
3455 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3456 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3457 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3458 var xvalues = self.data('sparkline-xvalues') || undefined;
3459 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3460 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3461 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3462 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3463 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3464 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3465 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3466 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3467 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3468 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3469 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3470 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3471 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3472 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3473 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3474 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3475 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3476 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3477 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3478 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3479 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3480 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3482 state.sparkline_options = {
3484 lineColor: lineColor,
3485 fillColor: fillColor,
3486 chartRangeMin: chartRangeMin,
3487 chartRangeMax: chartRangeMax,
3488 composite: composite,
3489 enableTagOptions: enableTagOptions,
3490 tagOptionPrefix: tagOptionPrefix,
3491 tagValuesAttribute: tagValuesAttribute,
3492 disableHiddenCheck: disableHiddenCheck,
3493 defaultPixelsPerValue: defaultPixelsPerValue,
3494 spotColor: spotColor,
3495 minSpotColor: minSpotColor,
3496 maxSpotColor: maxSpotColor,
3497 spotRadius: spotRadius,
3498 valueSpots: valueSpots,
3499 highlightSpotColor: highlightSpotColor,
3500 highlightLineColor: highlightLineColor,
3501 lineWidth: lineWidth,
3502 normalRangeMin: normalRangeMin,
3503 normalRangeMax: normalRangeMax,
3504 drawNormalOnTop: drawNormalOnTop,
3506 chartRangeClip: chartRangeClip,
3507 chartRangeMinX: chartRangeMinX,
3508 chartRangeMaxX: chartRangeMaxX,
3509 disableInteraction: disableInteraction,
3510 disableTooltips: disableTooltips,
3511 disableHighlight: disableHighlight,
3512 highlightLighten: highlightLighten,
3513 highlightColor: highlightColor,
3514 tooltipContainer: tooltipContainer,
3515 tooltipClassname: tooltipClassname,
3516 tooltipChartTitle: state.title,
3517 tooltipFormat: tooltipFormat,
3518 tooltipPrefix: tooltipPrefix,
3519 tooltipSuffix: tooltipSuffix,
3520 tooltipSkipNull: tooltipSkipNull,
3521 tooltipValueLookups: tooltipValueLookups,
3522 tooltipFormatFieldlist: tooltipFormatFieldlist,
3523 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3524 numberFormatter: numberFormatter,
3525 numberDigitGroupSep: numberDigitGroupSep,
3526 numberDecimalMark: numberDecimalMark,
3527 numberDigitGroupCount: numberDigitGroupCount,
3528 animatedZooms: animatedZooms,
3529 width: state.chartWidth(),
3530 height: state.chartHeight()
3533 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3537 // ----------------------------------------------------------------------------------------------------------------
3544 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3545 if(after < state.netdata_first)
3546 after = state.netdata_first;
3548 if(before > state.netdata_last)
3549 before = state.netdata_last;
3551 state.setMode('zoom');
3552 state.globalSelectionSyncStop();
3553 state.globalSelectionSyncDelay();
3554 state.dygraph_user_action = true;
3555 state.dygraph_force_zoom = true;
3556 state.updateChartPanOrZoom(after, before);
3557 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3560 NETDATA.dygraphSetSelection = function(state, t) {
3561 if(typeof state.dygraph_instance !== 'undefined') {
3562 var r = state.calculateRowForTime(t);
3564 state.dygraph_instance.setSelection(r);
3566 state.dygraph_instance.clearSelection();
3567 state.legendShowUndefined();
3574 NETDATA.dygraphClearSelection = function(state, t) {
3575 if(typeof state.dygraph_instance !== 'undefined') {
3576 state.dygraph_instance.clearSelection();
3581 NETDATA.dygraphSmoothInitialize = function(callback) {
3583 url: NETDATA.dygraph_smooth_js,
3588 NETDATA.dygraph.smooth = true;
3589 smoothPlotter.smoothing = 0.3;
3592 NETDATA.dygraph.smooth = false;
3594 .always(function() {
3595 if(typeof callback === "function")
3600 NETDATA.dygraphInitialize = function(callback) {
3601 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3603 url: NETDATA.dygraph_js,
3608 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3611 NETDATA.chartLibraries.dygraph.enabled = false;
3612 NETDATA.error(100, NETDATA.dygraph_js);
3614 .always(function() {
3615 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3616 NETDATA.dygraphSmoothInitialize(callback);
3617 else if(typeof callback === "function")
3622 NETDATA.chartLibraries.dygraph.enabled = false;
3623 if(typeof callback === "function")
3628 NETDATA.dygraphChartUpdate = function(state, data) {
3629 var dygraph = state.dygraph_instance;
3631 if(typeof dygraph === 'undefined')
3632 return NETDATA.dygraphChartCreate(state, data);
3634 // when the chart is not visible, and hidden
3635 // if there is a window resize, dygraph detects
3636 // its element size as 0x0.
3637 // this will make it re-appear properly
3639 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3643 file: data.result.data,
3644 colors: state.chartColors(),
3645 labels: data.result.labels,
3646 labelsDivWidth: state.chartWidth() - 70,
3647 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3650 if(state.dygraph_force_zoom === true) {
3651 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3652 state.log('dygraphChartUpdate() forced zoom update');
3654 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3655 options.valueRange = null;
3656 options.isZoomedIgnoreProgrammaticZoom = true;
3657 state.dygraph_force_zoom = false;
3659 else if(state.current.name !== 'auto') {
3660 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3661 state.log('dygraphChartUpdate() loose update');
3664 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3665 state.log('dygraphChartUpdate() strict update');
3667 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3668 options.valueRange = null;
3669 options.isZoomedIgnoreProgrammaticZoom = true;
3672 if(state.dygraph_smooth_eligible === true) {
3673 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3674 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3675 NETDATA.dygraphChartCreate(state, data);
3680 dygraph.updateOptions(options);
3682 state.dygraph_last_rendered = new Date().getTime();
3686 NETDATA.dygraphChartCreate = function(state, data) {
3687 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3688 state.log('dygraphChartCreate()');
3690 var self = $(state.element);
3692 var chart_type = state.chart.chart_type;
3693 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3694 chart_type = self.data('dygraph-type') || chart_type;
3696 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3697 smooth = self.data('dygraph-smooth') || smooth;
3699 if(NETDATA.dygraph.smooth === false)
3702 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3703 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3705 state.dygraph_options = {
3706 colors: self.data('dygraph-colors') || state.chartColors(),
3708 // leave a few pixels empty on the right of the chart
3709 rightGap: self.data('dygraph-rightgap') || 5,
3710 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3711 showRoller: self.data('dygraph-showroller') || false,
3713 title: self.data('dygraph-title') || state.title,
3714 titleHeight: self.data('dygraph-titleheight') || 19,
3716 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3717 labels: data.result.labels,
3718 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3719 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3720 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3721 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3722 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3725 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3726 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3728 ylabel: state.units,
3729 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3731 // the function to plot the chart
3734 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3735 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3736 strokePattern: self.data('dygraph-strokepattern') || undefined,
3738 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3739 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3740 drawPoints: self.data('dygraph-drawpoints') || false,
3742 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3743 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3745 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3746 pointSize: self.data('dygraph-pointsize') || 1,
3748 // enabling this makes the chart with little square lines
3749 stepPlot: self.data('dygraph-stepplot') || false,
3751 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3752 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3753 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3755 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3756 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3757 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3758 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3760 drawAxis: self.data('dygraph-drawaxis') || true,
3761 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3762 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3763 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3765 drawGrid: self.data('dygraph-drawgrid') || true,
3766 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3767 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3768 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3769 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3770 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3772 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3773 sigFigs: self.data('dygraph-sigfigs') || null,
3774 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3775 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3777 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3778 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3779 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3781 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3782 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3786 ticker: Dygraph.dateTicker,
3787 axisLabelFormatter: function (d, gran) {
3788 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3790 valueFormatter: function (ms) {
3791 var d = new Date(ms);
3792 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3793 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3798 valueFormatter: function (x) {
3799 // we format legends with the state object
3800 // no need to do anything here
3801 // return (Math.round(x*100) / 100).toLocaleString();
3802 // return state.legendFormatValue(x);
3807 legendFormatter: function(data) {
3808 var elements = state.element_legend_childs;
3810 // if the hidden div is not there
3811 // we are not managing the legend
3812 if(elements.hidden === null) return;
3814 if (typeof data.x !== 'undefined') {
3815 state.legendSetDate(data.x);
3816 var i = data.series.length;
3818 var series = data.series[i];
3819 if(!series.isVisible) continue;
3820 state.legendSetLabelValue(series.label, series.y);
3826 drawCallback: function(dygraph, is_initial) {
3827 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3828 state.dygraph_user_action = false;
3830 var x_range = dygraph.xAxisRange();
3831 var after = Math.round(x_range[0]);
3832 var before = Math.round(x_range[1]);
3834 if(NETDATA.options.debug.dygraph === true)
3835 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3837 if(before <= state.netdata_last && after >= state.netdata_first)
3838 state.updateChartPanOrZoom(after, before);
3841 zoomCallback: function(minDate, maxDate, yRanges) {
3842 if(NETDATA.options.debug.dygraph === true)
3843 state.log('dygraphZoomCallback()');
3845 state.globalSelectionSyncStop();
3846 state.globalSelectionSyncDelay();
3847 state.setMode('zoom');
3849 // refresh it to the greatest possible zoom level
3850 state.dygraph_user_action = true;
3851 state.dygraph_force_zoom = true;
3852 state.updateChartPanOrZoom(minDate, maxDate);
3854 highlightCallback: function(event, x, points, row, seriesName) {
3855 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3856 state.log('dygraphHighlightCallback()');
3860 // there is a bug in dygraph when the chart is zoomed enough
3861 // the time it thinks is selected is wrong
3862 // here we calculate the time t based on the row number selected
3864 var t = state.data_after + row * state.data_update_every;
3865 // 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);
3867 state.globalSelectionSync(x);
3869 // fix legend zIndex using the internal structures of dygraph legend module
3870 // this works, but it is a hack!
3871 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3873 unhighlightCallback: function(event) {
3874 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3875 state.log('dygraphUnhighlightCallback()');
3877 state.unpauseChart();
3878 state.globalSelectionSyncStop();
3880 interactionModel : {
3881 mousedown: function(event, dygraph, context) {
3882 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3883 state.log('interactionModel.mousedown()');
3885 state.dygraph_user_action = true;
3886 state.globalSelectionSyncStop();
3888 if(NETDATA.options.debug.dygraph === true)
3889 state.log('dygraphMouseDown()');
3891 // Right-click should not initiate a zoom.
3892 if(event.button && event.button === 2) return;
3894 context.initializeMouseDown(event, dygraph, context);
3896 if(event.button && event.button === 1) {
3897 if (event.altKey || event.shiftKey) {
3898 state.setMode('pan');
3899 state.globalSelectionSyncDelay();
3900 Dygraph.startPan(event, dygraph, context);
3903 state.setMode('zoom');
3904 state.globalSelectionSyncDelay();
3905 Dygraph.startZoom(event, dygraph, context);
3909 if (event.altKey || event.shiftKey) {
3910 state.setMode('zoom');
3911 state.globalSelectionSyncDelay();
3912 Dygraph.startZoom(event, dygraph, context);
3915 state.setMode('pan');
3916 state.globalSelectionSyncDelay();
3917 Dygraph.startPan(event, dygraph, context);
3921 mousemove: function(event, dygraph, context) {
3922 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3923 state.log('interactionModel.mousemove()');
3925 if(context.isPanning) {
3926 state.dygraph_user_action = true;
3927 state.globalSelectionSyncStop();
3928 state.globalSelectionSyncDelay();
3929 state.setMode('pan');
3930 Dygraph.movePan(event, dygraph, context);
3932 else if(context.isZooming) {
3933 state.dygraph_user_action = true;
3934 state.globalSelectionSyncStop();
3935 state.globalSelectionSyncDelay();
3936 state.setMode('zoom');
3937 Dygraph.moveZoom(event, dygraph, context);
3940 mouseup: function(event, dygraph, context) {
3941 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3942 state.log('interactionModel.mouseup()');
3944 if (context.isPanning) {
3945 state.dygraph_user_action = true;
3946 state.globalSelectionSyncDelay();
3947 Dygraph.endPan(event, dygraph, context);
3949 else if (context.isZooming) {
3950 state.dygraph_user_action = true;
3951 state.globalSelectionSyncDelay();
3952 Dygraph.endZoom(event, dygraph, context);
3955 click: function(event, dygraph, context) {
3956 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3957 state.log('interactionModel.click()');
3959 event.preventDefault();
3961 dblclick: function(event, dygraph, context) {
3962 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3963 state.log('interactionModel.dblclick()');
3964 NETDATA.resetAllCharts(state);
3966 mousewheel: function(event, dygraph, context) {
3967 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3968 state.log('interactionModel.mousewheel()');
3970 // Take the offset of a mouse event on the dygraph canvas and
3971 // convert it to a pair of percentages from the bottom left.
3972 // (Not top left, bottom is where the lower value is.)
3973 function offsetToPercentage(g, offsetX, offsetY) {
3974 // This is calculating the pixel offset of the leftmost date.
3975 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3976 var yar0 = g.yAxisRange(0);
3978 // This is calculating the pixel of the higest value. (Top pixel)
3979 var yOffset = g.toDomCoords(null, yar0[1])[1];
3981 // x y w and h are relative to the corner of the drawing area,
3982 // so that the upper corner of the drawing area is (0, 0).
3983 var x = offsetX - xOffset;
3984 var y = offsetY - yOffset;
3986 // This is computing the rightmost pixel, effectively defining the
3988 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3990 // This is computing the lowest pixel, effectively defining the height.
3991 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3993 // Percentage from the left.
3994 var xPct = w === 0 ? 0 : (x / w);
3995 // Percentage from the top.
3996 var yPct = h === 0 ? 0 : (y / h);
3998 // The (1-) part below changes it from "% distance down from the top"
3999 // to "% distance up from the bottom".
4000 return [xPct, (1-yPct)];
4003 // Adjusts [x, y] toward each other by zoomInPercentage%
4004 // Split it so the left/bottom axis gets xBias/yBias of that change and
4005 // tight/top gets (1-xBias)/(1-yBias) of that change.
4007 // If a bias is missing it splits it down the middle.
4008 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4009 xBias = xBias || 0.5;
4010 yBias = yBias || 0.5;
4012 function adjustAxis(axis, zoomInPercentage, bias) {
4013 var delta = axis[1] - axis[0];
4014 var increment = delta * zoomInPercentage;
4015 var foo = [increment * bias, increment * (1-bias)];
4017 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4020 var yAxes = g.yAxisRanges();
4022 for (var i = 0; i < yAxes.length; i++) {
4023 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4026 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4029 if(event.altKey || event.shiftKey) {
4030 state.dygraph_user_action = true;
4032 state.globalSelectionSyncStop();
4033 state.globalSelectionSyncDelay();
4035 // http://dygraphs.com/gallery/interaction-api.js
4036 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4037 var percentage = normal / 50;
4039 if (!(event.offsetX && event.offsetY)){
4040 event.offsetX = event.layerX - event.target.offsetLeft;
4041 event.offsetY = event.layerY - event.target.offsetTop;
4044 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4045 var xPct = percentages[0];
4046 var yPct = percentages[1];
4048 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4050 var after = new_x_range[0];
4051 var before = new_x_range[1];
4053 var first = state.netdata_first + state.data_update_every;
4054 var last = state.netdata_last + state.data_update_every;
4057 after -= (before - last);
4064 state.setMode('zoom');
4065 if(state.updateChartPanOrZoom(after, before) === true)
4066 dygraph.updateOptions({ dateWindow: [ after, before ] });
4068 event.preventDefault();
4071 touchstart: function(event, dygraph, context) {
4072 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4073 state.log('interactionModel.touchstart()');
4075 state.dygraph_user_action = true;
4076 state.setMode('zoom');
4079 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4081 // we overwrite the touch directions at the end, to overwrite
4082 // the internal default of dygraphs
4083 context.touchDirections = { x: true, y: false };
4085 state.dygraph_last_touch_start = new Date().getTime();
4086 state.dygraph_last_touch_move = 0;
4088 if(typeof event.touches[0].pageX === 'number')
4089 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4091 state.dygraph_last_touch_page_x = 0;
4093 touchmove: function(event, dygraph, context) {
4094 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4095 state.log('interactionModel.touchmove()');
4097 state.dygraph_user_action = true;
4098 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4100 state.dygraph_last_touch_move = new Date().getTime();
4102 touchend: function(event, dygraph, context) {
4103 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4104 state.log('interactionModel.touchend()');
4106 state.dygraph_user_action = true;
4107 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4109 // if it didn't move, it is a selection
4110 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4111 // internal api of dygraphs
4112 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4113 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4114 if(NETDATA.dygraphSetSelection(state, t) === true)
4115 state.globalSelectionSync(t);
4118 // if it was double tap within double click time, reset the charts
4119 var now = new Date().getTime();
4120 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4121 if(state.dygraph_last_touch_move === 0) {
4122 var dt = now - state.dygraph_last_touch_end;
4123 if(dt <= NETDATA.options.current.double_click_speed)
4124 NETDATA.resetAllCharts(state);
4128 // remember the timestamp of the last touch end
4129 state.dygraph_last_touch_end = now;
4134 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4135 state.dygraph_options.drawGrid = false;
4136 state.dygraph_options.drawAxis = false;
4137 state.dygraph_options.title = undefined;
4138 state.dygraph_options.units = undefined;
4139 state.dygraph_options.ylabel = undefined;
4140 state.dygraph_options.yLabelWidth = 0;
4141 state.dygraph_options.labelsDivWidth = 120;
4142 state.dygraph_options.labelsDivStyles.width = '120px';
4143 state.dygraph_options.labelsSeparateLines = true;
4144 state.dygraph_options.rightGap = 0;
4147 if(smooth === true) {
4148 state.dygraph_smooth_eligible = true;
4150 if(NETDATA.options.current.smooth_plot === true)
4151 state.dygraph_options.plotter = smoothPlotter;
4153 else state.dygraph_smooth_eligible = false;
4155 state.dygraph_instance = new Dygraph(state.element_chart,
4156 data.result.data, state.dygraph_options);
4158 state.dygraph_force_zoom = false;
4159 state.dygraph_user_action = false;
4160 state.dygraph_last_rendered = new Date().getTime();
4164 // ----------------------------------------------------------------------------------------------------------------
4167 NETDATA.morrisInitialize = function(callback) {
4168 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4170 // morris requires raphael
4171 if(!NETDATA.chartLibraries.raphael.initialized) {
4172 if(NETDATA.chartLibraries.raphael.enabled) {
4173 NETDATA.raphaelInitialize(function() {
4174 NETDATA.morrisInitialize(callback);
4178 NETDATA.chartLibraries.morris.enabled = false;
4179 if(typeof callback === "function")
4184 NETDATA._loadCSS(NETDATA.morris_css);
4187 url: NETDATA.morris_js,
4192 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4195 NETDATA.chartLibraries.morris.enabled = false;
4196 NETDATA.error(100, NETDATA.morris_js);
4198 .always(function() {
4199 if(typeof callback === "function")
4205 NETDATA.chartLibraries.morris.enabled = false;
4206 if(typeof callback === "function")
4211 NETDATA.morrisChartUpdate = function(state, data) {
4212 state.morris_instance.setData(data.result.data);
4216 NETDATA.morrisChartCreate = function(state, data) {
4218 state.morris_options = {
4219 element: state.element_chart.id,
4220 data: data.result.data,
4222 ykeys: data.dimension_names,
4223 labels: data.dimension_names,
4229 continuousLine: false,
4230 behaveLikeLine: false
4233 if(state.chart.chart_type === 'line')
4234 state.morris_instance = new Morris.Line(state.morris_options);
4236 else if(state.chart.chart_type === 'area') {
4237 state.morris_options.behaveLikeLine = true;
4238 state.morris_instance = new Morris.Area(state.morris_options);
4241 state.morris_instance = new Morris.Area(state.morris_options);
4246 // ----------------------------------------------------------------------------------------------------------------
4249 NETDATA.raphaelInitialize = function(callback) {
4250 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4252 url: NETDATA.raphael_js,
4257 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4260 NETDATA.chartLibraries.raphael.enabled = false;
4261 NETDATA.error(100, NETDATA.raphael_js);
4263 .always(function() {
4264 if(typeof callback === "function")
4269 NETDATA.chartLibraries.raphael.enabled = false;
4270 if(typeof callback === "function")
4275 NETDATA.raphaelChartUpdate = function(state, data) {
4276 $(state.element_chart).raphael(data.result, {
4277 width: state.chartWidth(),
4278 height: state.chartHeight()
4284 NETDATA.raphaelChartCreate = function(state, data) {
4285 $(state.element_chart).raphael(data.result, {
4286 width: state.chartWidth(),
4287 height: state.chartHeight()
4293 // ----------------------------------------------------------------------------------------------------------------
4296 NETDATA.c3Initialize = function(callback) {
4297 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4300 if(!NETDATA.chartLibraries.d3.initialized) {
4301 if(NETDATA.chartLibraries.d3.enabled) {
4302 NETDATA.d3Initialize(function() {
4303 NETDATA.c3Initialize(callback);
4307 NETDATA.chartLibraries.c3.enabled = false;
4308 if(typeof callback === "function")
4313 NETDATA._loadCSS(NETDATA.c3_css);
4321 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4324 NETDATA.chartLibraries.c3.enabled = false;
4325 NETDATA.error(100, NETDATA.c3_js);
4327 .always(function() {
4328 if(typeof callback === "function")
4334 NETDATA.chartLibraries.c3.enabled = false;
4335 if(typeof callback === "function")
4340 NETDATA.c3ChartUpdate = function(state, data) {
4341 state.c3_instance.destroy();
4342 return NETDATA.c3ChartCreate(state, data);
4344 //state.c3_instance.load({
4345 // rows: data.result,
4352 NETDATA.c3ChartCreate = function(state, data) {
4354 state.element_chart.id = 'c3-' + state.uuid;
4355 // console.log('id = ' + state.element_chart.id);
4357 state.c3_instance = c3.generate({
4358 bindto: '#' + state.element_chart.id,
4360 width: state.chartWidth(),
4361 height: state.chartHeight()
4364 pattern: state.chartColors()
4369 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4375 format: function(x) {
4376 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4403 // console.log(state.c3_instance);
4408 // ----------------------------------------------------------------------------------------------------------------
4411 NETDATA.d3Initialize = function(callback) {
4412 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4419 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4422 NETDATA.chartLibraries.d3.enabled = false;
4423 NETDATA.error(100, NETDATA.d3_js);
4425 .always(function() {
4426 if(typeof callback === "function")
4431 NETDATA.chartLibraries.d3.enabled = false;
4432 if(typeof callback === "function")
4437 NETDATA.d3ChartUpdate = function(state, data) {
4441 NETDATA.d3ChartCreate = function(state, data) {
4445 // ----------------------------------------------------------------------------------------------------------------
4448 NETDATA.googleInitialize = function(callback) {
4449 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4451 url: NETDATA.google_js,
4456 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4457 google.load('visualization', '1.1', {
4458 'packages': ['corechart', 'controls'],
4459 'callback': callback
4463 NETDATA.chartLibraries.google.enabled = false;
4464 NETDATA.error(100, NETDATA.google_js);
4465 if(typeof callback === "function")
4470 NETDATA.chartLibraries.google.enabled = false;
4471 if(typeof callback === "function")
4476 NETDATA.googleChartUpdate = function(state, data) {
4477 var datatable = new google.visualization.DataTable(data.result);
4478 state.google_instance.draw(datatable, state.google_options);
4482 NETDATA.googleChartCreate = function(state, data) {
4483 var datatable = new google.visualization.DataTable(data.result);
4485 state.google_options = {
4486 colors: state.chartColors(),
4488 // do not set width, height - the chart resizes itself
4489 //width: state.chartWidth(),
4490 //height: state.chartHeight(),
4495 // title: "Time of Day",
4496 // format:'HH:mm:ss',
4497 viewWindowMode: 'maximized',
4509 viewWindowMode: 'pretty',
4524 focusTarget: 'category',
4531 titlePosition: 'out',
4542 curveType: 'function',
4547 switch(state.chart.chart_type) {
4549 state.google_options.vAxis.viewWindowMode = 'maximized';
4550 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4551 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4555 state.google_options.isStacked = true;
4556 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4557 state.google_options.vAxis.viewWindowMode = 'maximized';
4558 state.google_options.vAxis.minValue = null;
4559 state.google_options.vAxis.maxValue = null;
4560 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4565 state.google_options.lineWidth = 2;
4566 state.google_instance = new google.visualization.LineChart(state.element_chart);
4570 state.google_instance.draw(datatable, state.google_options);
4574 // ----------------------------------------------------------------------------------------------------------------
4576 NETDATA.percentFromValueMax = function(value, max) {
4577 if(value === null) value = 0;
4578 if(max < value) max = value;
4582 pcent = Math.round(value * 100 / max);
4583 if(pcent === 0 && value > 0) pcent = 1;
4589 // ----------------------------------------------------------------------------------------------------------------
4592 NETDATA.easypiechartInitialize = function(callback) {
4593 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4595 url: NETDATA.easypiechart_js,
4600 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4603 NETDATA.chartLibraries.easypiechart.enabled = false;
4604 NETDATA.error(100, NETDATA.easypiechart_js);
4606 .always(function() {
4607 if(typeof callback === "function")
4612 NETDATA.chartLibraries.easypiechart.enabled = false;
4613 if(typeof callback === "function")
4618 NETDATA.easypiechartClearSelection = function(state) {
4619 if(typeof state.easyPieChartEvent !== 'undefined') {
4620 if(state.easyPieChartEvent.timer !== null)
4621 clearTimeout(state.easyPieChartEvent.timer);
4623 state.easyPieChartEvent.timer = null;
4626 if(state.isAutoRefreshed() === true && state.data !== null) {
4627 NETDATA.easypiechartChartUpdate(state, state.data);
4630 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4631 state.easyPieChart_instance.update(0);
4633 state.easyPieChart_instance.enableAnimation();
4638 NETDATA.easypiechartSetSelection = function(state, t) {
4639 if(state.timeIsVisible(t) !== true)
4640 return NETDATA.easypiechartClearSelection(state);
4642 var slot = state.calculateRowForTime(t);
4643 if(slot < 0 || slot >= state.data.result.length)
4644 return NETDATA.easypiechartClearSelection(state);
4646 if(typeof state.easyPieChartEvent === 'undefined') {
4647 state.easyPieChartEvent = {
4654 var value = state.data.result[state.data.result.length - 1 - slot];
4655 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4656 var pcent = NETDATA.percentFromValueMax(value, max);
4658 state.easyPieChartEvent.value = value;
4659 state.easyPieChartEvent.pcent = pcent;
4660 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4662 if(state.easyPieChartEvent.timer === null) {
4663 state.easyPieChart_instance.disableAnimation();
4665 state.easyPieChartEvent.timer = setTimeout(function() {
4666 state.easyPieChartEvent.timer = null;
4667 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4668 }, NETDATA.options.current.charts_selection_animation_delay);
4674 NETDATA.easypiechartChartUpdate = function(state, data) {
4675 var value, max, pcent;
4677 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4683 value = data.result[0];
4684 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4685 pcent = NETDATA.percentFromValueMax(value, max);
4688 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4689 state.easyPieChart_instance.update(pcent);
4693 NETDATA.easypiechartChartCreate = function(state, data) {
4694 var self = $(state.element);
4695 var chart = $(state.element_chart);
4697 var value = data.result[0];
4698 var max = self.data('easypiechart-max-value') || null;
4699 var adjust = self.data('easypiechart-adjust') || null;
4703 state.easyPieChartMax = null;
4706 state.easyPieChartMax = max;
4708 var pcent = NETDATA.percentFromValueMax(value, max);
4710 chart.data('data-percent', pcent);
4714 case 'width': size = state.chartHeight(); break;
4715 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4716 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4718 default: size = state.chartWidth(); break;
4720 state.element.style.width = size + 'px';
4721 state.element.style.height = size + 'px';
4723 var stroke = Math.floor(size / 22);
4724 if(stroke < 3) stroke = 2;
4726 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4727 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4728 state.easyPieChartLabel = document.createElement('span');
4729 state.easyPieChartLabel.className = 'easyPieChartLabel';
4730 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4731 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4732 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4733 state.element_chart.appendChild(state.easyPieChartLabel);
4735 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4736 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4737 state.easyPieChartTitle = document.createElement('span');
4738 state.easyPieChartTitle.className = 'easyPieChartTitle';
4739 state.easyPieChartTitle.innerHTML = state.title;
4740 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4741 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4742 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4743 state.element_chart.appendChild(state.easyPieChartTitle);
4745 var unitfontsize = Math.round(titlefontsize * 0.9);
4746 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4747 state.easyPieChartUnits = document.createElement('span');
4748 state.easyPieChartUnits.className = 'easyPieChartUnits';
4749 state.easyPieChartUnits.innerHTML = state.units;
4750 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4751 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4752 state.element_chart.appendChild(state.easyPieChartUnits);
4754 chart.easyPieChart({
4755 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4756 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4757 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4758 scaleLength: self.data('easypiechart-scalelength') || 5,
4759 lineCap: self.data('easypiechart-linecap') || 'round',
4760 lineWidth: self.data('easypiechart-linewidth') || stroke,
4761 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4762 size: self.data('easypiechart-size') || size,
4763 rotate: self.data('easypiechart-rotate') || 0,
4764 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4765 easing: self.data('easypiechart-easing') || undefined
4768 // when we just re-create the chart
4769 // do not animate the first update
4771 if(typeof state.easyPieChart_instance !== 'undefined')
4774 state.easyPieChart_instance = chart.data('easyPieChart');
4775 if(animate === false) state.easyPieChart_instance.disableAnimation();
4776 state.easyPieChart_instance.update(pcent);
4777 if(animate === false) state.easyPieChart_instance.enableAnimation();
4781 // ----------------------------------------------------------------------------------------------------------------
4784 NETDATA.gaugeInitialize = function(callback) {
4785 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4787 url: NETDATA.gauge_js,
4792 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4795 NETDATA.chartLibraries.gauge.enabled = false;
4796 NETDATA.error(100, NETDATA.gauge_js);
4798 .always(function() {
4799 if(typeof callback === "function")
4804 NETDATA.chartLibraries.gauge.enabled = false;
4805 if(typeof callback === "function")
4810 NETDATA.gaugeAnimation = function(state, status) {
4813 if(typeof status === 'boolean' && status === false)
4815 else if(typeof status === 'number')
4818 state.gauge_instance.animationSpeed = speed;
4819 state.___gaugeOld__.speed = speed;
4822 NETDATA.gaugeSet = function(state, value, min, max) {
4823 if(typeof value !== 'number') value = 0;
4824 if(typeof min !== 'number') min = 0;
4825 if(typeof max !== 'number') max = 0;
4826 if(value > max) max = value;
4827 if(value < min) min = value;
4836 // gauge.js has an issue if the needle
4837 // is smaller than min or larger than max
4838 // when we set the new values
4839 // the needle will go crazy
4841 // to prevent it, we always feed it
4842 // with a percentage, so that the needle
4843 // is always between min and max
4844 var pcent = (value - min) * 100 / (max - min);
4846 // these should never happen
4847 if(pcent < 0) pcent = 0;
4848 if(pcent > 100) pcent = 100;
4850 state.gauge_instance.set(pcent);
4852 state.___gaugeOld__.value = value;
4853 state.___gaugeOld__.min = min;
4854 state.___gaugeOld__.max = max;
4857 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4858 if(state.___gaugeOld__.valueLabel !== value) {
4859 state.___gaugeOld__.valueLabel = value;
4860 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4862 if(state.___gaugeOld__.minLabel !== min) {
4863 state.___gaugeOld__.minLabel = min;
4864 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4866 if(state.___gaugeOld__.maxLabel !== max) {
4867 state.___gaugeOld__.maxLabel = max;
4868 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4872 NETDATA.gaugeClearSelection = function(state) {
4873 if(typeof state.gaugeEvent !== 'undefined') {
4874 if(state.gaugeEvent.timer !== null)
4875 clearTimeout(state.gaugeEvent.timer);
4877 state.gaugeEvent.timer = null;
4880 if(state.isAutoRefreshed() === true && state.data !== null) {
4881 NETDATA.gaugeChartUpdate(state, state.data);
4884 NETDATA.gaugeAnimation(state, false);
4885 NETDATA.gaugeSet(state, null, null, null);
4886 NETDATA.gaugeSetLabels(state, null, null, null);
4889 NETDATA.gaugeAnimation(state, true);
4893 NETDATA.gaugeSetSelection = function(state, t) {
4894 if(state.timeIsVisible(t) !== true)
4895 return NETDATA.gaugeClearSelection(state);
4897 var slot = state.calculateRowForTime(t);
4898 if(slot < 0 || slot >= state.data.result.length)
4899 return NETDATA.gaugeClearSelection(state);
4901 if(typeof state.gaugeEvent === 'undefined') {
4902 state.gaugeEvent = {
4910 var value = state.data.result[state.data.result.length - 1 - slot];
4911 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4914 state.gaugeEvent.value = value;
4915 state.gaugeEvent.max = max;
4916 state.gaugeEvent.min = min;
4917 NETDATA.gaugeSetLabels(state, value, min, max);
4919 if(state.gaugeEvent.timer === null) {
4920 NETDATA.gaugeAnimation(state, false);
4922 state.gaugeEvent.timer = setTimeout(function() {
4923 state.gaugeEvent.timer = null;
4924 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4925 }, NETDATA.options.current.charts_selection_animation_delay);
4931 NETDATA.gaugeChartUpdate = function(state, data) {
4932 var value, min, max;
4934 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4938 NETDATA.gaugeSetLabels(state, null, null, null);
4941 value = data.result[0];
4943 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4944 if(value > max) max = value;
4945 NETDATA.gaugeSetLabels(state, value, min, max);
4948 NETDATA.gaugeSet(state, value, min, max);
4952 NETDATA.gaugeChartCreate = function(state, data) {
4953 var self = $(state.element);
4954 // var chart = $(state.element_chart);
4956 var value = data.result[0];
4957 var max = self.data('gauge-max-value') || null;
4958 var adjust = self.data('gauge-adjust') || null;
4959 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4960 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4961 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4962 var stopColor = self.data('gauge-stop-color') || void 0;
4963 var generateGradient = self.data('gauge-generate-gradient') || false;
4967 state.gaugeMax = null;
4970 state.gaugeMax = max;
4972 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4974 // case 'width': width = height * ratio; break;
4976 // default: height = width / ratio; break;
4978 //state.element.style.width = width.toString() + 'px';
4979 //state.element.style.height = height.toString() + 'px';
4984 lines: 12, // The number of lines to draw
4985 angle: 0.15, // The length of each line
4986 lineWidth: 0.44, // 0.44 The line thickness
4988 length: 0.8, // 0.9 The radius of the inner circle
4989 strokeWidth: 0.035, // The rotation offset
4990 color: pointerColor // Fill color
4992 colorStart: startColor, // Colors
4993 colorStop: stopColor, // just experiment with them
4994 strokeColor: strokeColor, // to see which ones work best for you
4996 generateGradient: generateGradient,
5000 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5001 options.percentColors = [
5002 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5003 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5004 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5005 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5006 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5007 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5008 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5009 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5010 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5011 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5012 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5015 state.gauge_canvas = document.createElement('canvas');
5016 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5017 state.gauge_canvas.className = 'gaugeChart';
5018 state.gauge_canvas.width = width;
5019 state.gauge_canvas.height = height;
5020 state.element_chart.appendChild(state.gauge_canvas);
5022 var valuefontsize = Math.floor(height / 6);
5023 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5024 state.gaugeChartLabel = document.createElement('span');
5025 state.gaugeChartLabel.className = 'gaugeChartLabel';
5026 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5027 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5028 state.element_chart.appendChild(state.gaugeChartLabel);
5030 var titlefontsize = Math.round(valuefontsize / 2);
5032 state.gaugeChartTitle = document.createElement('span');
5033 state.gaugeChartTitle.className = 'gaugeChartTitle';
5034 state.gaugeChartTitle.innerHTML = state.title;
5035 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5036 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5037 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5038 state.element_chart.appendChild(state.gaugeChartTitle);
5040 var unitfontsize = Math.round(titlefontsize * 0.9);
5041 state.gaugeChartUnits = document.createElement('span');
5042 state.gaugeChartUnits.className = 'gaugeChartUnits';
5043 state.gaugeChartUnits.innerHTML = state.units;
5044 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5045 state.element_chart.appendChild(state.gaugeChartUnits);
5047 state.gaugeChartMin = document.createElement('span');
5048 state.gaugeChartMin.className = 'gaugeChartMin';
5049 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5050 state.element_chart.appendChild(state.gaugeChartMin);
5052 state.gaugeChartMax = document.createElement('span');
5053 state.gaugeChartMax.className = 'gaugeChartMax';
5054 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5055 state.element_chart.appendChild(state.gaugeChartMax);
5057 // when we just re-create the chart
5058 // do not animate the first update
5060 if(typeof state.gauge_instance !== 'undefined')
5063 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5065 state.___gaugeOld__ = {
5074 // we will always feed a percentage
5075 state.gauge_instance.minValue = 0;
5076 state.gauge_instance.maxValue = 100;
5078 NETDATA.gaugeAnimation(state, animate);
5079 NETDATA.gaugeSet(state, value, 0, max);
5080 NETDATA.gaugeSetLabels(state, value, 0, max);
5081 NETDATA.gaugeAnimation(state, true);
5085 // ----------------------------------------------------------------------------------------------------------------
5086 // Charts Libraries Registration
5088 NETDATA.chartLibraries = {
5090 initialize: NETDATA.dygraphInitialize,
5091 create: NETDATA.dygraphChartCreate,
5092 update: NETDATA.dygraphChartUpdate,
5093 resize: function(state) {
5094 if(typeof state.dygraph_instance.resize === 'function')
5095 state.dygraph_instance.resize();
5097 setSelection: NETDATA.dygraphSetSelection,
5098 clearSelection: NETDATA.dygraphClearSelection,
5099 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5102 format: function(state) { return 'json'; },
5103 options: function(state) { return 'ms|flip'; },
5104 legend: function(state) {
5105 if(this.isSparkline(state) === false)
5106 return 'right-side';
5110 autoresize: function(state) { return true; },
5111 max_updates_to_recreate: function(state) { return 5000; },
5112 track_colors: function(state) { return true; },
5113 pixels_per_point: function(state) {
5114 if(this.isSparkline(state) === false)
5120 isSparkline: function(state) {
5121 if(typeof state.dygraph_sparkline === 'undefined') {
5122 var t = $(state.element).data('dygraph-theme');
5123 if(t === 'sparkline')
5124 state.dygraph_sparkline = true;
5126 state.dygraph_sparkline = false;
5128 return state.dygraph_sparkline;
5132 initialize: NETDATA.sparklineInitialize,
5133 create: NETDATA.sparklineChartCreate,
5134 update: NETDATA.sparklineChartUpdate,
5136 setSelection: undefined, // function(state, t) { return true; },
5137 clearSelection: undefined, // function(state) { return true; },
5138 toolboxPanAndZoom: null,
5141 format: function(state) { return 'array'; },
5142 options: function(state) { return 'flip|abs'; },
5143 legend: function(state) { return null; },
5144 autoresize: function(state) { return false; },
5145 max_updates_to_recreate: function(state) { return 5000; },
5146 track_colors: function(state) { return false; },
5147 pixels_per_point: function(state) { return 3; }
5150 initialize: NETDATA.peityInitialize,
5151 create: NETDATA.peityChartCreate,
5152 update: NETDATA.peityChartUpdate,
5154 setSelection: undefined, // function(state, t) { return true; },
5155 clearSelection: undefined, // function(state) { return true; },
5156 toolboxPanAndZoom: null,
5159 format: function(state) { return 'ssvcomma'; },
5160 options: function(state) { return 'null2zero|flip|abs'; },
5161 legend: function(state) { return null; },
5162 autoresize: function(state) { return false; },
5163 max_updates_to_recreate: function(state) { return 5000; },
5164 track_colors: function(state) { return false; },
5165 pixels_per_point: function(state) { return 3; }
5168 initialize: NETDATA.morrisInitialize,
5169 create: NETDATA.morrisChartCreate,
5170 update: NETDATA.morrisChartUpdate,
5172 setSelection: undefined, // function(state, t) { return true; },
5173 clearSelection: undefined, // function(state) { return true; },
5174 toolboxPanAndZoom: null,
5177 format: function(state) { return 'json'; },
5178 options: function(state) { return 'objectrows|ms'; },
5179 legend: function(state) { return null; },
5180 autoresize: function(state) { return false; },
5181 max_updates_to_recreate: function(state) { return 50; },
5182 track_colors: function(state) { return false; },
5183 pixels_per_point: function(state) { return 15; }
5186 initialize: NETDATA.googleInitialize,
5187 create: NETDATA.googleChartCreate,
5188 update: NETDATA.googleChartUpdate,
5190 setSelection: undefined, //function(state, t) { return true; },
5191 clearSelection: undefined, //function(state) { return true; },
5192 toolboxPanAndZoom: null,
5195 format: function(state) { return 'datatable'; },
5196 options: function(state) { return ''; },
5197 legend: function(state) { return null; },
5198 autoresize: function(state) { return false; },
5199 max_updates_to_recreate: function(state) { return 300; },
5200 track_colors: function(state) { return false; },
5201 pixels_per_point: function(state) { return 4; }
5204 initialize: NETDATA.raphaelInitialize,
5205 create: NETDATA.raphaelChartCreate,
5206 update: NETDATA.raphaelChartUpdate,
5208 setSelection: undefined, // function(state, t) { return true; },
5209 clearSelection: undefined, // function(state) { return true; },
5210 toolboxPanAndZoom: null,
5213 format: function(state) { return 'json'; },
5214 options: function(state) { return ''; },
5215 legend: function(state) { return null; },
5216 autoresize: function(state) { return false; },
5217 max_updates_to_recreate: function(state) { return 5000; },
5218 track_colors: function(state) { return false; },
5219 pixels_per_point: function(state) { return 3; }
5222 initialize: NETDATA.c3Initialize,
5223 create: NETDATA.c3ChartCreate,
5224 update: NETDATA.c3ChartUpdate,
5226 setSelection: undefined, // function(state, t) { return true; },
5227 clearSelection: undefined, // function(state) { return true; },
5228 toolboxPanAndZoom: null,
5231 format: function(state) { return 'csvjsonarray'; },
5232 options: function(state) { return 'milliseconds'; },
5233 legend: function(state) { return null; },
5234 autoresize: function(state) { return false; },
5235 max_updates_to_recreate: function(state) { return 5000; },
5236 track_colors: function(state) { return false; },
5237 pixels_per_point: function(state) { return 15; }
5240 initialize: NETDATA.d3Initialize,
5241 create: NETDATA.d3ChartCreate,
5242 update: NETDATA.d3ChartUpdate,
5244 setSelection: undefined, // function(state, t) { return true; },
5245 clearSelection: undefined, // function(state) { return true; },
5246 toolboxPanAndZoom: null,
5249 format: function(state) { return 'json'; },
5250 options: function(state) { return ''; },
5251 legend: function(state) { return null; },
5252 autoresize: function(state) { return false; },
5253 max_updates_to_recreate: function(state) { return 5000; },
5254 track_colors: function(state) { return false; },
5255 pixels_per_point: function(state) { return 3; }
5258 initialize: NETDATA.easypiechartInitialize,
5259 create: NETDATA.easypiechartChartCreate,
5260 update: NETDATA.easypiechartChartUpdate,
5262 setSelection: NETDATA.easypiechartSetSelection,
5263 clearSelection: NETDATA.easypiechartClearSelection,
5264 toolboxPanAndZoom: null,
5267 format: function(state) { return 'array'; },
5268 options: function(state) { return 'absolute'; },
5269 legend: function(state) { return null; },
5270 autoresize: function(state) { return false; },
5271 max_updates_to_recreate: function(state) { return 5000; },
5272 track_colors: function(state) { return true; },
5273 pixels_per_point: function(state) { return 3; },
5277 initialize: NETDATA.gaugeInitialize,
5278 create: NETDATA.gaugeChartCreate,
5279 update: NETDATA.gaugeChartUpdate,
5281 setSelection: NETDATA.gaugeSetSelection,
5282 clearSelection: NETDATA.gaugeClearSelection,
5283 toolboxPanAndZoom: null,
5286 format: function(state) { return 'array'; },
5287 options: function(state) { return 'absolute'; },
5288 legend: function(state) { return null; },
5289 autoresize: function(state) { return false; },
5290 max_updates_to_recreate: function(state) { return 5000; },
5291 track_colors: function(state) { return true; },
5292 pixels_per_point: function(state) { return 3; },
5297 NETDATA.registerChartLibrary = function(library, url) {
5298 if(NETDATA.options.debug.libraries === true)
5299 console.log("registering chart library: " + library);
5301 NETDATA.chartLibraries[library].url = url;
5302 NETDATA.chartLibraries[library].initialized = true;
5303 NETDATA.chartLibraries[library].enabled = true;
5306 // ----------------------------------------------------------------------------------------------------------------
5309 NETDATA.requiredJs = [
5311 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5312 isAlreadyLoaded: function() {
5313 if(typeof $().emulateTransitionEnd == 'function')
5316 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5324 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5325 isAlreadyLoaded: function() { return false; }
5328 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5329 isAlreadyLoaded: function() { return false; }
5333 NETDATA.requiredCSS = [
5335 url: NETDATA.themes.current.bootstrap_css,
5336 isAlreadyLoaded: function() {
5337 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5344 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5345 isAlreadyLoaded: function() { return false; }
5348 url: NETDATA.themes.current.dashboard_css,
5349 isAlreadyLoaded: function() { return false; }
5352 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5353 isAlreadyLoaded: function() { return false; }
5357 NETDATA.loadRequiredJs = function(index, callback) {
5358 if(index >= NETDATA.requiredJs.length) {
5359 if(typeof callback === 'function')
5364 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5365 NETDATA.loadRequiredJs(++index, callback);
5369 if(NETDATA.options.debug.main_loop === true)
5370 console.log('loading ' + NETDATA.requiredJs[index].url);
5373 url: NETDATA.requiredJs[index].url,
5377 .success(function() {
5378 if(NETDATA.options.debug.main_loop === true)
5379 console.log('loaded ' + NETDATA.requiredJs[index].url);
5381 NETDATA.loadRequiredJs(++index, callback);
5384 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5388 NETDATA.loadRequiredCSS = function(index) {
5389 if(index >= NETDATA.requiredCSS.length)
5392 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5393 NETDATA.loadRequiredCSS(++index);
5397 if(NETDATA.options.debug.main_loop === true)
5398 console.log('loading ' + NETDATA.requiredCSS[index].url);
5400 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5401 NETDATA.loadRequiredCSS(++index);
5404 NETDATA.errorReset();
5405 NETDATA.loadRequiredCSS(0);
5407 NETDATA._loadjQuery(function() {
5408 NETDATA.loadRequiredJs(0, function() {
5409 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5410 if(NETDATA.options.debug.main_loop === true)
5411 console.log('starting chart refresh thread');
5418 // window.NETDATA = NETDATA;
5419 // })(window, document);