1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = function(code,msg) {} // Callback function that will be invoked upon error
16 // You can also set the default netdata server, using the following.
17 // When this variable is not set, we assume the page is hosted on your
18 // netdata server already.
19 // var netdataServer = "http://yourhost:19999"; // set your NetData server
21 //(function(window, document, undefined) {
22 // fix IE issue with console
23 if(!window.console){ window.console = {log: function(){} }; }
26 var NETDATA = window.NETDATA || {};
28 // ----------------------------------------------------------------------------------------------------------------
29 // Detect the netdata server
31 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
32 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
33 NETDATA._scriptSource = function() {
36 if(typeof document.currentScript !== 'undefined') {
37 script = document.currentScript;
40 var all_scripts = document.getElementsByTagName('script');
41 script = all_scripts[all_scripts.length - 1];
44 if (typeof script.getAttribute.length !== 'undefined')
47 script = script.getAttribute('src', -1);
52 if(typeof netdataServer !== 'undefined')
53 NETDATA.serverDefault = netdataServer;
55 var s = NETDATA._scriptSource();
56 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
59 if(NETDATA.serverDefault === null)
60 NETDATA.serverDefault = '';
61 else if(NETDATA.serverDefault.slice(-1) !== '/')
62 NETDATA.serverDefault += '/';
64 // default URLs for all the external files we need
65 // make them RELATIVE so that the whole thing can also be
66 // installed under a web server
67 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
68 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
69 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
70 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
71 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
72 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
73 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
74 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
75 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
76 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
77 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
78 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
79 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
80 NETDATA.google_js = 'https://www.google.com/jsapi';
84 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
85 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
86 background: '#FFFFFF',
87 foreground: '#000000',
90 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
91 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
92 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
93 '#329262', '#3B3EAC' ],
94 easypiechart_track: '#f0f0f0',
95 easypiechart_scale: '#dfe0e0',
96 gauge_pointer: '#C0C0C0',
97 gauge_stroke: '#F0F0F0',
101 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
102 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
103 background: '#272b30',
104 foreground: '#C8C8C8',
107 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
108 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
109 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
110 '#a6a479', '#a66da8' ],
112 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
113 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
114 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
115 '#329262', '#3B3EFF' ],
116 easypiechart_track: '#373b40',
117 easypiechart_scale: '#373b40',
118 gauge_pointer: '#474b50',
119 gauge_stroke: '#373b40',
120 gauge_gradient: false
124 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
125 NETDATA.themes.current = NETDATA.themes[netdataTheme];
127 NETDATA.themes.current = NETDATA.themes.default;
129 NETDATA.colors = NETDATA.themes.current.colors;
131 // these are the colors Google Charts are using
132 // we have them here to attempt emulate their look and feel on the other chart libraries
133 // http://there4.io/2012/05/02/google-chart-color-list/
134 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
135 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
136 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
138 // an alternative set
139 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
140 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
141 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
143 // ----------------------------------------------------------------------------------------------------------------
144 // the defaults for all charts
146 // if the user does not specify any of these, the following will be used
148 NETDATA.chartDefaults = {
149 host: NETDATA.serverDefault, // the server to get data from
150 width: '100%', // the chart width - can be null
151 height: '100%', // the chart height - can be null
152 min_width: null, // the chart minimum width - can be null
153 library: 'dygraph', // the graphing library to use
154 method: 'average', // the grouping method
155 before: 0, // panning
156 after: -600, // panning
157 pixels_per_point: 1, // the detail of the chart
158 fill_luminance: 0.8 // luminance of colors in solit areas
161 // ----------------------------------------------------------------------------------------------------------------
165 pauseCallback: null, // a callback when we are really paused
167 pause: false, // when enabled we don't auto-refresh the charts
169 targets: null, // an array of all the state objects that are
170 // currently active (independently of their
171 // viewport visibility)
173 updated_dom: true, // when true, the DOM has been updated with
174 // new elements we have to check.
176 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
177 // rendering charts continiously.
178 // used with .current.fast_render_timeframe
180 page_is_visible: true, // when true, this page is visible
182 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
183 // auto-refresher for some time (when a chart is
184 // performing pan or zoom, we need to stop refreshing
185 // all other charts, to have the maximum speed for
186 // rendering the chart that is panned or zoomed).
187 // Used with .current.global_pan_sync_time
189 last_resized: new Date().getTime(), // the timestamp of the last resize request
191 crossDomainAjax: false, // enable this to request crossDomain AJAX
193 last_page_scroll: 0, // the timestamp the last time the page was scrolled
195 // the current profile
196 // we may have many...
198 pixels_per_point: 1, // the minimum pixels per point for all charts
199 // increase this to speed javascript up
200 // each chart library has its own limit too
201 // the max of this and the chart library is used
202 // the final is calculated every time, so a change
203 // here will have immediate effect on the next chart
206 idle_between_charts: 100, // ms - how much time to wait between chart updates
208 fast_render_timeframe: 200, // ms - render continously until this time of continious
209 // rendering has been reached
210 // this setting is used to make it render e.g. 10
211 // charts at once, sleep idle_between_charts time
212 // and continue for another 10 charts.
214 idle_between_loops: 500, // ms - if all charts have been updated, wait this
215 // time before starting again.
217 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
219 idle_lost_focus: 500, // ms - when the window does not have focus, check
220 // if focus has been regained, every this time
222 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
223 // autorefreshing of charts is paused for this amount
226 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
227 // of time before setting up synchronized selections
230 sync_selection: true, // enable or disable selection sync
232 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
234 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
236 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
238 update_only_visible: true, // enable or disable visibility management
240 parallel_refresher: true, // enable parallel refresh of charts
242 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
244 destroy_on_hide: false, // destroy charts when they are not visible
246 show_help: true, // when enabled the charts will show some help
247 show_help_delay_show_ms: 500,
248 show_help_delay_hide_ms: 0,
250 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
252 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
253 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
255 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
257 smooth_plot: true, // enable smooth plot, where possible
259 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
261 color_fill_opacity_line: 1.0,
262 color_fill_opacity_area: 0.2,
263 color_fill_opacity_stacked: 0.8,
265 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
266 pan_and_zoom_factor_multiplier_control: 2.0,
267 pan_and_zoom_factor_multiplier_shift: 3.0,
268 pan_and_zoom_factor_multiplier_alt: 4.0,
270 setOptionCallback: function() { ; }
278 chart_data_url: false,
279 chart_errors: false, // FIXME
288 // ----------------------------------------------------------------------------------------------------------------
289 // local storage options
291 NETDATA.localStorage = {
294 callback: {} // only used for resetting back to defaults
297 NETDATA.localStorageGet = function(key, def, callback) {
300 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
301 NETDATA.localStorage.default[key.toString()] = def;
302 NETDATA.localStorage.callback[key.toString()] = callback;
305 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
307 // console.log('localStorage: loading "' + key.toString() + '"');
308 ret = localStorage.getItem(key.toString());
309 if(ret === null || ret === 'undefined') {
310 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
311 localStorage.setItem(key.toString(), JSON.stringify(def));
315 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
316 ret = JSON.parse(ret);
317 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
321 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
326 if(typeof ret === 'undefined' || ret === 'undefined') {
327 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
331 NETDATA.localStorage.current[key.toString()] = ret;
335 NETDATA.localStorageSet = function(key, value, callback) {
336 if(typeof value === 'undefined' || value === 'undefined') {
337 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
340 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
341 NETDATA.localStorage.default[key.toString()] = value;
342 NETDATA.localStorage.current[key.toString()] = value;
343 NETDATA.localStorage.callback[key.toString()] = callback;
346 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
347 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
349 localStorage.setItem(key.toString(), JSON.stringify(value));
352 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
356 NETDATA.localStorage.current[key.toString()] = value;
360 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
362 if(typeof obj[i] === 'object') {
363 //console.log('object ' + prefix + '.' + i.toString());
364 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
368 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
372 NETDATA.setOption = function(key, value) {
373 if(key.toString() === 'setOptionCallback') {
374 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
375 NETDATA.options.current[key.toString()] = value;
376 NETDATA.options.current.setOptionCallback();
379 else if(NETDATA.options.current[key.toString()] !== value) {
380 var name = 'options.' + key.toString();
382 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
383 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
385 //console.log(NETDATA.localStorage);
386 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
387 //console.log(NETDATA.options);
388 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
390 if(typeof NETDATA.options.current.setOptionCallback === 'function')
391 NETDATA.options.current.setOptionCallback();
397 NETDATA.getOption = function(key) {
398 return NETDATA.options.current[key.toString()];
401 // read settings from local storage
402 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
404 // always start with this option enabled.
405 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
407 NETDATA.resetOptions = function() {
408 for(var i in NETDATA.localStorage.default) {
409 var a = i.split('.');
411 if(a[0] === 'options') {
412 if(a[1] === 'setOptionCallback') continue;
413 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
414 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
416 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
418 else if(a[0] === 'chart_heights') {
419 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
420 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
426 // ----------------------------------------------------------------------------------------------------------------
428 if(NETDATA.options.debug.main_loop === true)
429 console.log('welcome to NETDATA');
431 NETDATA.onresize = function() {
432 NETDATA.options.last_resized = new Date().getTime();
436 NETDATA.onscroll = function() {
437 // console.log('onscroll');
439 NETDATA.options.last_page_scroll = new Date().getTime();
440 if(NETDATA.options.targets === null) return;
442 // when the user scrolls he sees that we have
443 // hidden all the not-visible charts
444 // using this little function we try to switch
445 // the charts back to visible quickly
446 var targets = NETDATA.options.targets;
447 var len = targets.length;
448 while(len--) targets[len].isVisible();
451 window.onresize = NETDATA.onresize;
452 window.onscroll = NETDATA.onscroll;
454 // ----------------------------------------------------------------------------------------------------------------
457 NETDATA.errorCodes = {
458 100: { message: "Cannot load chart library", alert: true },
459 101: { message: "Cannot load jQuery", alert: true },
460 402: { message: "Chart library not found", alert: false },
461 403: { message: "Chart library not enabled/is failed", alert: false },
462 404: { message: "Chart not found", alert: false }
464 NETDATA.errorLast = {
470 NETDATA.error = function(code, msg) {
471 NETDATA.errorLast.code = code;
472 NETDATA.errorLast.message = msg;
473 NETDATA.errorLast.datetime = new Date().getTime();
475 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
477 if(typeof netdataErrorCallback === 'function') {
478 netdataErrorCallback(code,msg);
481 if(NETDATA.errorCodes[code].alert)
482 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
485 NETDATA.errorReset = function() {
486 NETDATA.errorLast.code = 0;
487 NETDATA.errorLast.message = "You are doing fine!";
488 NETDATA.errorLast.datetime = 0;
491 // ----------------------------------------------------------------------------------------------------------------
494 // When multiple charts need the same chart, we avoid downloading it
495 // multiple times (and having it in browser memory multiple time)
496 // by using this registry.
498 // Every time we download a chart definition, we save it here with .add()
499 // Then we try to get it back with .get(). If that fails, we download it.
501 NETDATA.chartRegistry = {
504 fixid: function(id) {
505 return id.replace(/:/g, "_").replace(/\//g, "_");
508 add: function(host, id, data) {
509 host = this.fixid(host);
512 if(typeof this.charts[host] === 'undefined')
513 this.charts[host] = {};
515 //console.log('added ' + host + '/' + id);
516 this.charts[host][id] = data;
519 get: function(host, id) {
520 host = this.fixid(host);
523 if(typeof this.charts[host] === 'undefined')
526 if(typeof this.charts[host][id] === 'undefined')
529 //console.log('cached ' + host + '/' + id);
530 return this.charts[host][id];
533 downloadAll: function(host, callback) {
534 while(host.slice(-1) === '/')
535 host = host.substring(0, host.length - 1);
540 url: host + '/api/v1/charts',
541 crossDomain: NETDATA.options.crossDomainAjax,
545 .done(function(data) {
546 var h = NETDATA.chartRegistry.fixid(host);
547 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
548 self.charts[h] = data.charts;
549 if(typeof callback === 'function')
553 if(typeof callback === 'function')
559 // ----------------------------------------------------------------------------------------------------------------
560 // Global Pan and Zoom on charts
562 // Using this structure are synchronize all the charts, so that
563 // when you pan or zoom one, all others are automatically refreshed
564 // to the same timespan.
566 NETDATA.globalPanAndZoom = {
567 seq: 0, // timestamp ms
568 // every time a chart is panned or zoomed
569 // we set the timestamp here
570 // then we use it as a sequence number
571 // to find if other charts are syncronized
574 master: null, // the master chart (state), to which all others
577 force_before_ms: null, // the timespan to sync all other charts
578 force_after_ms: null,
581 setMaster: function(state, after, before) {
582 if(NETDATA.options.current.sync_pan_and_zoom === false)
585 if(this.master !== null && this.master !== state)
586 this.master.resetChart(true, true);
588 var now = new Date().getTime();
591 this.force_after_ms = after;
592 this.force_before_ms = before;
593 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
597 clearMaster: function() {
598 if(this.master !== null) {
599 var st = this.master;
606 this.force_after_ms = null;
607 this.force_before_ms = null;
608 NETDATA.options.auto_refresher_stop_until = 0;
611 // is the given state the master of the global
612 // pan and zoom sync?
613 isMaster: function(state) {
614 if(this.master === state) return true;
618 // are we currently have a global pan and zoom sync?
619 isActive: function() {
620 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
624 // check if a chart, other than the master
625 // needs to be refreshed, due to the global pan and zoom
626 shouldBeAutoRefreshed: function(state) {
627 if(this.master === null || this.seq === 0)
630 //if(state.needsRecreation())
633 if(state.tm.pan_and_zoom_seq === this.seq)
640 // ----------------------------------------------------------------------------------------------------------------
641 // dimensions selection
644 // move color assignment to dimensions, here
646 dimensionStatus = function(parent, label, name_div, value_div, color) {
647 this.enabled = false;
648 this.parent = parent;
650 this.name_div = null;
651 this.value_div = null;
652 this.color = NETDATA.themes.current.foreground;
654 if(parent.selected_count > parent.unselected_count)
655 this.selected = true;
657 this.selected = false;
659 this.setOptions(name_div, value_div, color);
662 dimensionStatus.prototype.invalidate = function() {
663 this.name_div = null;
664 this.value_div = null;
665 this.enabled = false;
668 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
671 if(this.name_div != name_div) {
672 this.name_div = name_div;
673 this.name_div.title = this.label;
674 this.name_div.style.color = this.color;
675 if(this.selected === false)
676 this.name_div.className = 'netdata-legend-name not-selected';
678 this.name_div.className = 'netdata-legend-name selected';
681 if(this.value_div != value_div) {
682 this.value_div = value_div;
683 this.value_div.title = this.label;
684 this.value_div.style.color = this.color;
685 if(this.selected === false)
686 this.value_div.className = 'netdata-legend-value not-selected';
688 this.value_div.className = 'netdata-legend-value selected';
695 dimensionStatus.prototype.setHandler = function() {
696 if(this.enabled === false) return;
700 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
701 this.name_div.onclick = this.value_div.onclick = function(e) {
703 if(ds.isSelected()) {
705 if(e.shiftKey === true || e.ctrlKey === true) {
706 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
709 if(ds.parent.countSelected() === 0)
710 ds.parent.selectAll();
713 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
714 if(ds.parent.countSelected() === 1) {
715 ds.parent.selectAll();
718 ds.parent.selectNone();
724 // this is not selected
725 if(e.shiftKey === true || e.ctrlKey === true) {
726 // control or shift key is pressed -> select this too
730 // no key is pressed -> select only this
731 ds.parent.selectNone();
736 ds.parent.state.redrawChart();
740 dimensionStatus.prototype.select = function() {
741 if(this.enabled === false) return;
743 this.name_div.className = 'netdata-legend-name selected';
744 this.value_div.className = 'netdata-legend-value selected';
745 this.selected = true;
748 dimensionStatus.prototype.unselect = function() {
749 if(this.enabled === false) return;
751 this.name_div.className = 'netdata-legend-name not-selected';
752 this.value_div.className = 'netdata-legend-value hidden';
753 this.selected = false;
756 dimensionStatus.prototype.isSelected = function() {
757 return(this.enabled === true && this.selected === true);
760 // ----------------------------------------------------------------------------------------------------------------
762 dimensionsVisibility = function(state) {
765 this.dimensions = {};
766 this.selected_count = 0;
767 this.unselected_count = 0;
770 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
771 if(typeof this.dimensions[label] === 'undefined') {
773 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
776 this.dimensions[label].setOptions(name_div, value_div, color);
778 return this.dimensions[label];
781 dimensionsVisibility.prototype.dimensionGet = function(label) {
782 return this.dimensions[label];
785 dimensionsVisibility.prototype.invalidateAll = function() {
786 for(var d in this.dimensions)
787 this.dimensions[d].invalidate();
790 dimensionsVisibility.prototype.selectAll = function() {
791 for(var d in this.dimensions)
792 this.dimensions[d].select();
795 dimensionsVisibility.prototype.countSelected = function() {
797 for(var d in this.dimensions)
798 if(this.dimensions[d].isSelected()) i++;
803 dimensionsVisibility.prototype.selectNone = function() {
804 for(var d in this.dimensions)
805 this.dimensions[d].unselect();
808 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
809 var ret = new Array();
810 this.selected_count = 0;
811 this.unselected_count = 0;
813 for(var i = 0, len = array.length; i < len ; i++) {
814 var ds = this.dimensions[array[i]];
815 if(typeof ds === 'undefined') {
816 // console.log(array[i] + ' is not found');
821 if(ds.isSelected()) {
823 this.selected_count++;
827 this.unselected_count++;
831 if(this.selected_count === 0 && this.unselected_count !== 0) {
833 return this.selected2BooleanArray(array);
840 // ----------------------------------------------------------------------------------------------------------------
841 // global selection sync
843 NETDATA.globalSelectionSync = {
850 if(this.state !== null)
851 this.state.globalSelectionSyncStop();
855 if(this.state !== null) {
856 this.state.globalSelectionSyncDelay();
861 // ----------------------------------------------------------------------------------------------------------------
862 // Our state object, where all per-chart values are stored
864 chartState = function(element) {
865 var self = $(element);
866 this.element = element;
869 // all private functions should use 'that', instead of 'this'
873 * show an error instead of the chart
875 var error = function(msg) {
877 if(typeof netdataErrorCallback === 'function') {
878 netdataErrorCallback("", msg);
881 that.element.innerHTML = that.id + ': ' + msg;
882 that.enabled = false;
883 that.current = that.pan;
886 // GUID - a unique identifier for the chart
887 this.uuid = NETDATA.guid();
889 // string - the name of chart
890 this.id = self.data('netdata');
892 // string - the key for localStorage settings
893 this.settings_id = self.data('id') || null;
895 // the user given dimensions of the element
896 this.width = self.data('width') || NETDATA.chartDefaults.width;
897 this.height = self.data('height') || NETDATA.chartDefaults.height;
899 if(this.settings_id !== null) {
900 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
901 // this is the callback that will be called
902 // if and when the user resets all localStorage variables
905 resizeChartToHeight(height);
909 // string - the netdata server URL, without any path
910 this.host = self.data('host') || NETDATA.chartDefaults.host;
912 // make sure the host does not end with /
913 // all netdata API requests use absolute paths
914 while(this.host.slice(-1) === '/')
915 this.host = this.host.substring(0, this.host.length - 1);
917 // string - the grouping method requested by the user
918 this.method = self.data('method') || NETDATA.chartDefaults.method;
920 // the time-range requested by the user
921 this.after = self.data('after') || NETDATA.chartDefaults.after;
922 this.before = self.data('before') || NETDATA.chartDefaults.before;
924 // the pixels per point requested by the user
925 this.pixels_per_point = self.data('pixels-per-point') || 1;
926 this.points = self.data('points') || null;
928 // the dimensions requested by the user
929 this.dimensions = self.data('dimensions') || null;
931 // the chart library requested by the user
932 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
934 // object - the chart library used
939 this.colors_assigned = {};
940 this.colors_available = null;
942 // the element already created by the user
943 this.element_message = null;
945 // the element with the chart
946 this.element_chart = null;
948 // the element with the legend of the chart (if created by us)
949 this.element_legend = null;
950 this.element_legend_childs = {
960 this.chart_url = null; // string - the url to download chart info
961 this.chart = null; // object - the chart as downloaded from the server
963 this.title = self.data('title') || null; // the title of the chart
964 this.units = self.data('units') || null; // the units of the chart dimensions
965 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
967 this.validated = false; // boolean - has the chart been validated?
968 this.enabled = true; // boolean - is the chart enabled for refresh?
969 this.paused = false; // boolean - is the chart paused for any reason?
970 this.selected = false; // boolean - is the chart shown a selection?
971 this.debug = false; // boolean - console.log() debug info about this chart
973 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
974 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
975 this.requested_after = null; // milliseconds - the timestamp of the request after param
976 this.requested_before = null; // milliseconds - the timestamp of the request before param
977 this.requested_padding = null;
979 this.view_before = 0;
984 force_update_at: 0, // the timestamp to force the update at
985 force_before_ms: null,
991 force_update_at: 0, // the timestamp to force the update at
992 force_before_ms: null,
998 force_update_at: 0, // the timestamp to force the update at
999 force_before_ms: null,
1000 force_after_ms: null
1003 // this is a pointer to one of the sub-classes below
1005 this.current = this.auto;
1007 // check the requested library is available
1008 // we don't initialize it here - it will be initialized when
1009 // this chart will be first used
1010 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1011 NETDATA.error(402, that.library_name);
1012 error('chart library "' + that.library_name + '" is not found');
1015 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1016 NETDATA.error(403, that.library_name);
1017 error('chart library "' + that.library_name + '" is not enabled');
1021 that.library = NETDATA.chartLibraries[that.library_name];
1023 // milliseconds - the time the last refresh took
1024 this.refresh_dt_ms = 0;
1026 // if we need to report the rendering speed
1027 // find the element that needs to be updated
1028 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1030 if(refresh_dt_element_name !== null)
1031 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1033 this.refresh_dt_element = null;
1035 this.dimensions_visibility = new dimensionsVisibility(this);
1037 this._updating = false;
1039 // ============================================================================================================
1040 // PRIVATE FUNCTIONS
1042 var createDOM = function() {
1043 if(that.enabled === false) return;
1045 if(that.element_message !== null) that.element_message.innerHTML = '';
1046 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1047 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1049 that.element.innerHTML = '';
1051 that.element_message = document.createElement('div');
1052 that.element_message.className = ' netdata-message hidden';
1053 that.element.appendChild(that.element_message);
1055 that.element_chart = document.createElement('div');
1056 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1057 that.element.appendChild(that.element_chart);
1059 if(that.hasLegend() === true) {
1060 that.element.className = "netdata-container-with-legend";
1061 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1063 that.element_legend = document.createElement('div');
1064 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1065 that.element.appendChild(that.element_legend);
1068 that.element.className = "netdata-container";
1069 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1071 that.element_legend = null;
1073 that.element_legend_childs.series = null;
1075 if(typeof(that.width) === 'string')
1076 $(that.element).css('width', that.width);
1077 else if(typeof(that.width) === 'number')
1078 $(that.element).css('width', that.width + 'px');
1080 if(typeof(that.library.aspect_ratio) === 'undefined') {
1081 if(typeof(that.height) === 'string')
1082 $(that.element).css('height', that.height);
1083 else if(typeof(that.height) === 'number')
1084 $(that.element).css('height', that.height + 'px');
1087 var w = that.element.offsetWidth;
1088 if(w === null || w === 0) {
1089 // the div is hidden
1090 // this is resize the chart when next viewed
1091 that.tm.last_resized = 0;
1094 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1097 if(NETDATA.chartDefaults.min_width !== null)
1098 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1100 that.tm.last_dom_created = new Date().getTime();
1106 * initialize state variables
1107 * destroy all (possibly) created state elements
1108 * create the basic DOM for a chart
1110 var init = function() {
1111 if(that.enabled === false) return;
1113 that.paused = false;
1114 that.selected = false;
1116 that.chart_created = false; // boolean - is the library.create() been called?
1117 that.updates_counter = 0; // numeric - the number of refreshes made so far
1118 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1119 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1122 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1123 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1124 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1126 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1127 last_updated: 0, // the timestamp the chart last updated with data
1128 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1130 // Used with NETDATA.globalPanAndZoom.seq
1131 last_visible_check: 0, // the time we last checked if it is visible
1132 last_resized: 0, // the time the chart was resized
1133 last_hidden: 0, // the time the chart was hidden
1134 last_unhidden: 0, // the time the chart was unhidden
1135 last_autorefreshed: 0 // the time the chart was last refreshed
1138 that.data = null; // the last data as downloaded from the netdata server
1139 that.data_url = 'invalid://'; // string - the last url used to update the chart
1140 that.data_points = 0; // number - the number of points returned from netdata
1141 that.data_after = 0; // milliseconds - the first timestamp of the data
1142 that.data_before = 0; // milliseconds - the last timestamp of the data
1143 that.data_update_every = 0; // milliseconds - the frequency to update the data
1145 that.tm.last_initialized = new Date().getTime();
1148 that.setMode('auto');
1151 var maxMessageFontSize = function() {
1152 // normally we want a font size, as tall as the element
1153 var h = that.element_message.clientHeight;
1155 // but give it some air, 20% let's say, or 5 pixels min
1156 var lost = Math.max(h * 0.2, 5);
1159 // center the text, verically
1160 var paddingTop = (lost - 5) / 2;
1162 // but check the width too
1163 // it should fit 10 characters in it
1164 var w = that.element_message.clientWidth / 10;
1166 paddingTop += (h - w) / 2;
1170 // and don't make it too huge
1171 // 5% of the screen size is good
1172 if(h > screen.height / 20) {
1173 paddingTop += (h - (screen.height / 20)) / 2;
1174 h = screen.height / 20;
1178 that.element_message.style.fontSize = h.toString() + 'px';
1179 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1182 var showMessage = function(msg) {
1183 that.element_message.className = 'netdata-message';
1184 that.element_message.innerHTML = msg;
1185 that.element_message.style.fontSize = 'x-small';
1186 that.element_message.style.paddingTop = '0px';
1187 that.___messageHidden___ = undefined;
1190 var showMessageIcon = function(icon) {
1191 that.element_message.innerHTML = icon;
1192 that.element_message.className = 'netdata-message icon';
1193 maxMessageFontSize();
1194 that.___messageHidden___ = undefined;
1197 var hideMessage = function() {
1198 if(typeof that.___messageHidden___ === 'undefined') {
1199 that.___messageHidden___ = true;
1200 that.element_message.className = 'netdata-message hidden';
1204 var showRendering = function() {
1206 if(that.chart !== null) {
1207 if(that.chart.chart_type === 'line')
1208 icon = '<i class="fa fa-line-chart"></i>';
1210 icon = '<i class="fa fa-area-chart"></i>';
1213 icon = '<i class="fa fa-area-chart"></i>';
1215 showMessageIcon(icon + ' netdata');
1218 var showLoading = function() {
1219 if(that.chart_created === false) {
1220 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1226 var isHidden = function() {
1227 if(typeof that.___chartIsHidden___ !== 'undefined')
1233 // hide the chart, when it is not visible - called from isVisible()
1234 var hideChart = function() {
1235 // hide it, if it is not already hidden
1236 if(isHidden() === true) return;
1238 if(that.chart_created === true) {
1239 // we should destroy it
1240 if(NETDATA.options.current.destroy_on_hide === true) {
1245 that.element_chart.style.display = 'none';
1246 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1247 that.tm.last_hidden = new Date().getTime();
1251 that.___chartIsHidden___ = true;
1254 // unhide the chart, when it is visible - called from isVisible()
1255 var unhideChart = function() {
1256 if(isHidden() === false) return;
1258 that.___chartIsHidden___ = undefined;
1259 that.updates_since_last_unhide = 0;
1261 if(that.chart_created === false) {
1262 // we need to re-initialize it, to show our background
1263 // logo in bootstrap tabs, until the chart loads
1267 that.tm.last_unhidden = new Date().getTime();
1268 that.element_chart.style.display = '';
1269 if(that.element_legend !== null) that.element_legend.style.display = '';
1275 var canBeRendered = function() {
1276 if(isHidden() === true || that.isVisible(true) === false)
1282 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1283 var callChartLibraryUpdateSafely = function(data) {
1286 if(canBeRendered() === false)
1289 if(NETDATA.options.debug.chart_errors === true)
1290 status = that.library.update(that, data);
1293 status = that.library.update(that, data);
1300 if(status === false) {
1301 error('chart failed to be updated as ' + that.library_name);
1308 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1309 var callChartLibraryCreateSafely = function(data) {
1312 if(canBeRendered() === false)
1315 if(NETDATA.options.debug.chart_errors === true)
1316 status = that.library.create(that, data);
1319 status = that.library.create(that, data);
1326 if(status === false) {
1327 error('chart failed to be created as ' + that.library_name);
1331 that.chart_created = true;
1332 that.updates_since_last_creation = 0;
1336 // ----------------------------------------------------------------------------------------------------------------
1339 // resizeChart() - private
1340 // to be called just before the chart library to make sure that
1341 // a properly sized dom is available
1342 var resizeChart = function() {
1343 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1344 if(that.chart_created === false) return;
1346 if(that.needsRecreation()) {
1349 else if(typeof that.library.resize === 'function') {
1350 that.library.resize(that);
1352 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1353 $(that.element_legend_childs.nano).nanoScroller();
1355 maxMessageFontSize();
1358 that.tm.last_resized = new Date().getTime();
1362 // this is the actual chart resize algorithm
1364 // - resize the entire container
1365 // - update the internal states
1366 // - resize the chart as the div changes height
1367 // - update the scrollbar of the legend
1368 var resizeChartToHeight = function(h) {
1370 that.element.style.height = h;
1372 if(that.settings_id !== null)
1373 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1375 var now = new Date().getTime();
1376 NETDATA.options.last_page_scroll = now;
1377 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1380 that.tm.last_resized = 0;
1384 this.resizeHandler = function(e) {
1387 if(typeof this.event_resize === 'undefined'
1388 || this.event_resize.chart_original_w === 'undefined'
1389 || this.event_resize.chart_original_h === 'undefined')
1390 this.event_resize = {
1391 chart_original_w: this.element.clientWidth,
1392 chart_original_h: this.element.clientHeight,
1396 if(e.type === 'touchstart') {
1397 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1398 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1401 this.event_resize.mouse_start_x = e.clientX;
1402 this.event_resize.mouse_start_y = e.clientY;
1405 this.event_resize.chart_start_w = this.element.clientWidth;
1406 this.event_resize.chart_start_h = this.element.clientHeight;
1407 this.event_resize.chart_last_w = this.element.clientWidth;
1408 this.event_resize.chart_last_h = this.element.clientHeight;
1410 var now = new Date().getTime();
1411 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1412 // double click / double tap event
1414 // the optimal height of the chart
1415 // showing the entire legend
1416 var optimal = this.event_resize.chart_last_h
1417 + this.element_legend_childs.content.scrollHeight
1418 - this.element_legend_childs.content.clientHeight;
1420 // if we are not optimal, be optimal
1421 if(this.event_resize.chart_last_h != optimal)
1422 resizeChartToHeight(optimal.toString() + 'px');
1424 // else if we do not have the original height
1425 // reset to the original height
1426 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1427 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1430 this.event_resize.last = now;
1432 // process movement event
1433 document.onmousemove =
1434 document.ontouchmove =
1435 this.element_legend_childs.resize_handler.onmousemove =
1436 this.element_legend_childs.resize_handler.ontouchmove =
1441 case 'mousemove': y = e.clientY; break;
1442 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1446 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1448 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1449 resizeChartToHeight(newH.toString() + 'px');
1450 that.event_resize.chart_last_h = newH;
1455 // process end event
1456 document.onmouseup =
1457 document.ontouchend =
1458 this.element_legend_childs.resize_handler.onmouseup =
1459 this.element_legend_childs.resize_handler.ontouchend =
1461 // remove all the hooks
1462 document.onmouseup =
1463 document.onmousemove =
1464 document.ontouchmove =
1465 document.ontouchend =
1466 that.element_legend_childs.resize_handler.onmousemove =
1467 that.element_legend_childs.resize_handler.ontouchmove =
1468 that.element_legend_childs.resize_handler.onmouseout =
1469 that.element_legend_childs.resize_handler.onmouseup =
1470 that.element_legend_childs.resize_handler.ontouchend =
1473 // allow auto-refreshes
1474 NETDATA.options.auto_refresher_stop_until = 0;
1480 var noDataToShow = function() {
1481 showMessageIcon('<i class="fa fa-warning"></i> empty');
1482 that.legendUpdateDOM();
1483 that.tm.last_autorefreshed = new Date().getTime();
1484 // that.data_update_every = 30 * 1000;
1485 //that.element_chart.style.display = 'none';
1486 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1487 //that.___chartIsHidden___ = true;
1490 // ============================================================================================================
1493 this.error = function(msg) {
1497 this.setMode = function(m) {
1498 if(this.current !== null && this.current.name === m) return;
1501 this.current = this.auto;
1502 else if(m === 'pan')
1503 this.current = this.pan;
1504 else if(m === 'zoom')
1505 this.current = this.zoom;
1507 this.current = this.auto;
1509 this.current.force_update_at = 0;
1510 this.current.force_before_ms = null;
1511 this.current.force_after_ms = null;
1513 this.tm.last_mode_switch = new Date().getTime();
1516 // ----------------------------------------------------------------------------------------------------------------
1517 // global selection sync
1519 // prevent to global selection sync for some time
1520 this.globalSelectionSyncDelay = function(ms) {
1521 if(NETDATA.options.current.sync_selection === false)
1524 if(typeof ms === 'number')
1525 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1527 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1530 // can we globally apply selection sync?
1531 this.globalSelectionSyncAbility = function() {
1532 if(NETDATA.options.current.sync_selection === false)
1535 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1541 this.globalSelectionSyncIsMaster = function() {
1542 if(NETDATA.globalSelectionSync.state === this)
1548 // this chart is the master of the global selection sync
1549 this.globalSelectionSyncBeMaster = function() {
1551 if(this.globalSelectionSyncIsMaster()) {
1552 if(this.debug === true)
1553 this.log('sync: I am the master already.');
1558 if(NETDATA.globalSelectionSync.state) {
1559 if(this.debug === true)
1560 this.log('sync: I am not the sync master. Resetting global sync.');
1562 this.globalSelectionSyncStop();
1565 // become the master
1566 if(this.debug === true)
1567 this.log('sync: becoming sync master.');
1569 this.selected = true;
1570 NETDATA.globalSelectionSync.state = this;
1572 // find the all slaves
1573 var targets = NETDATA.options.targets;
1574 var len = targets.length;
1579 if(this.debug === true)
1580 st.log('sync: not adding me to sync');
1582 else if(st.globalSelectionSyncIsEligible()) {
1583 if(this.debug === true)
1584 st.log('sync: adding to sync as slave');
1586 st.globalSelectionSyncBeSlave();
1590 // this.globalSelectionSyncDelay(100);
1593 // can the chart participate to the global selection sync as a slave?
1594 this.globalSelectionSyncIsEligible = function() {
1595 if(this.enabled === true
1596 && this.library !== null
1597 && typeof this.library.setSelection === 'function'
1598 && this.isVisible() === true
1599 && this.chart_created === true)
1605 // this chart becomes a slave of the global selection sync
1606 this.globalSelectionSyncBeSlave = function() {
1607 if(NETDATA.globalSelectionSync.state !== this)
1608 NETDATA.globalSelectionSync.slaves.push(this);
1611 // sync all the visible charts to the given time
1612 // this is to be called from the chart libraries
1613 this.globalSelectionSync = function(t) {
1614 if(this.globalSelectionSyncAbility() === false) {
1615 if(this.debug === true)
1616 this.log('sync: cannot sync (yet?).');
1621 if(this.globalSelectionSyncIsMaster() === false) {
1622 if(this.debug === true)
1623 this.log('sync: trying to be sync master.');
1625 this.globalSelectionSyncBeMaster();
1627 if(this.globalSelectionSyncAbility() === false) {
1628 if(this.debug === true)
1629 this.log('sync: cannot sync (yet?).');
1635 NETDATA.globalSelectionSync.last_t = t;
1636 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1641 // stop syncing all charts to the given time
1642 this.globalSelectionSyncStop = function() {
1643 if(NETDATA.globalSelectionSync.slaves.length) {
1644 if(this.debug === true)
1645 this.log('sync: cleaning up...');
1647 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1649 if(that.debug === true)
1650 st.log('sync: not adding me to sync stop');
1653 if(that.debug === true)
1654 st.log('sync: removed slave from sync');
1656 st.clearSelection();
1660 NETDATA.globalSelectionSync.last_t = 0;
1661 NETDATA.globalSelectionSync.slaves = [];
1662 NETDATA.globalSelectionSync.state = null;
1665 this.clearSelection();
1668 this.setSelection = function(t) {
1669 if(typeof this.library.setSelection === 'function') {
1670 if(this.library.setSelection(this, t) === true)
1671 this.selected = true;
1673 this.selected = false;
1675 else this.selected = true;
1677 if(this.selected === true && this.debug === true)
1678 this.log('selection set to ' + t.toString());
1680 return this.selected;
1683 this.clearSelection = function() {
1684 if(this.selected === true) {
1685 if(typeof this.library.clearSelection === 'function') {
1686 if(this.library.clearSelection(this) === true)
1687 this.selected = false;
1689 this.selected = true;
1691 else this.selected = false;
1693 if(this.selected === false && this.debug === true)
1694 this.log('selection cleared');
1699 return this.selected;
1702 // find if a timestamp (ms) is shown in the current chart
1703 this.timeIsVisible = function(t) {
1704 if(t >= this.data_after && t <= this.data_before)
1709 this.calculateRowForTime = function(t) {
1710 if(this.timeIsVisible(t) === false) return -1;
1711 return Math.floor((t - this.data_after) / this.data_update_every);
1714 // ----------------------------------------------------------------------------------------------------------------
1717 this.log = function(msg) {
1718 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1721 this.pauseChart = function() {
1722 if(this.paused === false) {
1723 if(this.debug === true)
1724 this.log('pauseChart()');
1730 this.unpauseChart = function() {
1731 if(this.paused === true) {
1732 if(this.debug === true)
1733 this.log('unpauseChart()');
1735 this.paused = false;
1739 this.resetChart = function(dont_clear_master, dont_update) {
1740 if(this.debug === true)
1741 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1743 if(typeof dont_clear_master === 'undefined')
1744 dont_clear_master = false;
1746 if(typeof dont_update === 'undefined')
1747 dont_update = false;
1749 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1750 if(this.debug === true)
1751 this.log('resetChart() diverting to clearMaster().');
1752 // this will call us back with master === true
1753 NETDATA.globalPanAndZoom.clearMaster();
1757 this.clearSelection();
1759 this.tm.pan_and_zoom_seq = 0;
1761 this.setMode('auto');
1762 this.current.force_update_at = 0;
1763 this.current.force_before_ms = null;
1764 this.current.force_after_ms = null;
1765 this.tm.last_autorefreshed = 0;
1766 this.paused = false;
1767 this.selected = false;
1768 this.enabled = true;
1769 // this.debug = false;
1771 // do not update the chart here
1772 // or the chart will flip-flop when it is the master
1773 // of a selection sync and another chart becomes
1776 if(dont_update !== true && this.isVisible() === true) {
1781 this.updateChartPanOrZoom = function(after, before) {
1782 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1785 if(this.debug === true)
1788 if(before < after) {
1789 if(this.debug === true)
1790 this.log(logme + 'flipped parameters, rejecting it.');
1795 if(typeof this.fixed_min_duration === 'undefined')
1796 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1798 var min_duration = this.fixed_min_duration;
1799 var current_duration = Math.round(this.view_before - this.view_after);
1801 // round the numbers
1802 after = Math.round(after);
1803 before = Math.round(before);
1805 // align them to update_every
1806 // stretching them further away
1807 after -= after % this.data_update_every;
1808 before += this.data_update_every - (before % this.data_update_every);
1810 // the final wanted duration
1811 var wanted_duration = before - after;
1813 // to allow panning, accept just a point below our minimum
1814 if((current_duration - this.data_update_every) < min_duration)
1815 min_duration = current_duration - this.data_update_every;
1817 // we do it, but we adjust to minimum size and return false
1818 // when the wanted size is below the current and the minimum
1820 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1821 if(this.debug === true)
1822 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1824 min_duration = this.fixed_min_duration;
1826 var dt = (min_duration - wanted_duration) / 2;
1829 wanted_duration = before - after;
1833 var tolerance = this.data_update_every * 2;
1834 var movement = Math.abs(before - this.view_before);
1836 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1837 if(this.debug === true)
1838 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);
1842 if(this.current.name === 'auto') {
1843 this.log(logme + 'caller called me with mode: ' + this.current.name);
1844 this.setMode('pan');
1847 if(this.debug === true)
1848 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);
1850 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1851 this.current.force_after_ms = after;
1852 this.current.force_before_ms = before;
1853 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1857 this.legendFormatValue = function(value) {
1858 if(value === null || value === 'undefined') return '-';
1859 if(typeof value !== 'number') return value;
1861 var abs = Math.abs(value);
1862 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1863 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1864 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1865 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1866 return (Math.round(value * 10000) / 10000).toLocaleString();
1869 this.legendSetLabelValue = function(label, value) {
1870 var series = this.element_legend_childs.series[label];
1871 if(typeof series === 'undefined') return;
1872 if(series.value === null && series.user === null) return;
1874 // if the value has not changed, skip DOM update
1875 //if(series.last === value) return;
1878 if(typeof value === 'number') {
1879 var v = Math.abs(value);
1880 s = r = this.legendFormatValue(value);
1882 if(typeof series.last === 'number') {
1883 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1884 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1885 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1887 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1892 series.last = value;
1895 if(series.value !== null) series.value.innerHTML = s;
1896 if(series.user !== null) series.user.innerHTML = r;
1899 this.legendSetDate = function(ms) {
1900 if(typeof ms !== 'number') {
1901 this.legendShowUndefined();
1905 var d = new Date(ms);
1907 if(this.element_legend_childs.title_date)
1908 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1910 if(this.element_legend_childs.title_time)
1911 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1913 if(this.element_legend_childs.title_units)
1914 this.element_legend_childs.title_units.innerHTML = this.units;
1917 this.legendShowUndefined = function() {
1918 if(this.element_legend_childs.title_date)
1919 this.element_legend_childs.title_date.innerHTML = ' ';
1921 if(this.element_legend_childs.title_time)
1922 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1924 if(this.element_legend_childs.title_units)
1925 this.element_legend_childs.title_units.innerHTML = ' ';
1927 if(this.data && this.element_legend_childs.series !== null) {
1928 var labels = this.data.dimension_names;
1929 var i = labels.length;
1931 var label = labels[i];
1933 if(typeof label === 'undefined') continue;
1934 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1935 this.legendSetLabelValue(label, null);
1940 this.legendShowLatestValues = function() {
1941 if(this.chart === null) return;
1942 if(this.selected) return;
1944 if(this.data === null || this.element_legend_childs.series === null) {
1945 this.legendShowUndefined();
1949 var show_undefined = true;
1950 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1951 show_undefined = false;
1953 if(show_undefined) {
1954 this.legendShowUndefined();
1958 this.legendSetDate(this.view_before);
1960 var labels = this.data.dimension_names;
1961 var i = labels.length;
1963 var label = labels[i];
1965 if(typeof label === 'undefined') continue;
1966 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1969 this.legendSetLabelValue(label, null);
1971 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1975 this.legendReset = function() {
1976 this.legendShowLatestValues();
1979 // this should be called just ONCE per dimension per chart
1980 this._chartDimensionColor = function(label) {
1981 if(this.colors === null) this.chartColors();
1983 if(typeof this.colors_assigned[label] === 'undefined') {
1984 if(this.colors_available.length === 0) {
1985 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1986 this.colors_available.push(NETDATA.themes.current.colors[i]);
1989 this.colors_assigned[label] = this.colors_available.shift();
1991 if(this.debug === true)
1992 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1995 if(this.debug === true)
1996 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1999 this.colors.push(this.colors_assigned[label]);
2000 return this.colors_assigned[label];
2003 this.chartColors = function() {
2004 if(this.colors !== null) return this.colors;
2006 this.colors = new Array();
2007 this.colors_available = new Array();
2010 var c = $(this.element).data('colors');
2011 // this.log('read colors: ' + c);
2012 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2013 if(typeof c !== 'string') {
2014 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2021 for(i = 0, len = c.length; i < len ; i++) {
2023 this.colors_available.push(c[i]);
2024 // this.log('adding color: ' + c[i]);
2030 // push all the standard colors too
2031 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2032 this.colors_available.push(NETDATA.themes.current.colors[i]);
2037 this.legendUpdateDOM = function() {
2040 // check that the legend DOM is up to date for the downloaded dimensions
2041 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2042 // this.log('the legend does not have any series - requesting legend update');
2045 else if(this.data === null) {
2046 // this.log('the chart does not have any data - requesting legend update');
2049 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2053 var labels = this.data.dimension_names.toString();
2054 if(labels !== this.element_legend_childs.series.labels_key) {
2057 if(this.debug === true)
2058 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2062 if(needed === false) {
2063 // make sure colors available
2066 // do we have to update the current values?
2067 // we do this, only when the visible chart is current
2068 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2069 if(this.debug === true)
2070 this.log('chart is in latest position... updating values on legend...');
2072 //var labels = this.data.dimension_names;
2073 //var i = labels.length;
2075 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2079 if(this.colors === null) {
2080 // this is the first time we update the chart
2081 // let's assign colors to all dimensions
2082 if(this.library.track_colors() === true)
2083 for(var dim in this.chart.dimensions)
2084 this._chartDimensionColor(this.chart.dimensions[dim].name);
2086 // we will re-generate the colors for the chart
2087 // based on the selected dimensions
2090 if(this.debug === true)
2091 this.log('updating Legend DOM');
2093 // mark all dimensions as invalid
2094 this.dimensions_visibility.invalidateAll();
2096 var genLabel = function(state, parent, name, count) {
2097 var color = state._chartDimensionColor(name);
2099 var user_element = null;
2100 var user_id = self.data('show-value-of-' + name + '-at') || null;
2101 if(user_id !== null) {
2102 user_element = document.getElementById(user_id) || null;
2103 if(user_element === null)
2104 state.log('Cannot find element with id: ' + user_id);
2107 state.element_legend_childs.series[name] = {
2108 name: document.createElement('span'),
2109 value: document.createElement('span'),
2114 var label = state.element_legend_childs.series[name];
2116 // create the dimension visibility tracking for this label
2117 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2119 var rgb = NETDATA.colorHex2Rgb(color);
2120 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2121 + state.chart.chart_type
2122 + '" style="background-color: '
2123 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2124 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2126 var text = document.createTextNode(' ' + name);
2127 label.name.appendChild(text);
2130 parent.appendChild(document.createElement('br'));
2132 parent.appendChild(label.name);
2133 parent.appendChild(label.value);
2136 var content = document.createElement('div');
2138 if(this.hasLegend()) {
2139 this.element_legend_childs = {
2141 resize_handler: document.createElement('div'),
2142 toolbox: document.createElement('div'),
2143 toolbox_left: document.createElement('div'),
2144 toolbox_right: document.createElement('div'),
2145 toolbox_reset: document.createElement('div'),
2146 toolbox_zoomin: document.createElement('div'),
2147 toolbox_zoomout: document.createElement('div'),
2148 toolbox_volume: document.createElement('div'),
2149 title_date: document.createElement('span'),
2150 title_time: document.createElement('span'),
2151 title_units: document.createElement('span'),
2152 nano: document.createElement('div'),
2154 paneClass: 'netdata-legend-series-pane',
2155 sliderClass: 'netdata-legend-series-slider',
2156 contentClass: 'netdata-legend-series-content',
2157 enabledClass: '__enabled',
2158 flashedClass: '__flashed',
2159 activeClass: '__active',
2161 alwaysVisible: true,
2167 this.element_legend.innerHTML = '';
2169 if(this.library.toolboxPanAndZoom !== null) {
2171 function get_pan_and_zoom_step(event) {
2173 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2175 else if (event.shiftKey)
2176 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2178 else if (event.altKey)
2179 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2182 return NETDATA.options.current.pan_and_zoom_factor;
2185 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2186 this.element.appendChild(this.element_legend_childs.toolbox);
2188 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2189 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2190 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2191 this.element_legend_childs.toolbox_left.onclick = function(e) {
2194 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2195 var before = that.view_before - step;
2196 var after = that.view_after - step;
2197 if(after >= that.netdata_first)
2198 that.library.toolboxPanAndZoom(that, after, before);
2200 if(NETDATA.options.current.show_help === true)
2201 $(this.element_legend_childs.toolbox_left).popover({
2206 placement: 'bottom',
2207 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2209 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>'
2213 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2214 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2215 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2216 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2218 NETDATA.resetAllCharts(that);
2220 if(NETDATA.options.current.show_help === true)
2221 $(this.element_legend_childs.toolbox_reset).popover({
2226 placement: 'bottom',
2227 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2228 title: 'Chart Reset',
2229 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>'
2232 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2233 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2234 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2235 this.element_legend_childs.toolbox_right.onclick = function(e) {
2237 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2238 var before = that.view_before + step;
2239 var after = that.view_after + step;
2240 if(before <= that.netdata_last)
2241 that.library.toolboxPanAndZoom(that, after, before);
2243 if(NETDATA.options.current.show_help === true)
2244 $(this.element_legend_childs.toolbox_right).popover({
2249 placement: 'bottom',
2250 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2252 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>'
2256 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2257 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2258 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2259 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2261 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2262 var before = that.view_before - dt;
2263 var after = that.view_after + dt;
2264 that.library.toolboxPanAndZoom(that, after, before);
2266 if(NETDATA.options.current.show_help === true)
2267 $(this.element_legend_childs.toolbox_zoomin).popover({
2272 placement: 'bottom',
2273 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2274 title: 'Chart Zoom In',
2275 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>'
2278 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2279 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2280 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2281 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2283 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);
2284 var before = that.view_before + dt;
2285 var after = that.view_after - dt;
2287 that.library.toolboxPanAndZoom(that, after, before);
2289 if(NETDATA.options.current.show_help === true)
2290 $(this.element_legend_childs.toolbox_zoomout).popover({
2295 placement: 'bottom',
2296 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2297 title: 'Chart Zoom Out',
2298 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>'
2301 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2302 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2303 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2304 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2305 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2306 //e.preventDefault();
2307 //alert('clicked toolbox_volume on ' + that.id);
2311 this.element_legend_childs.toolbox = null;
2312 this.element_legend_childs.toolbox_left = null;
2313 this.element_legend_childs.toolbox_reset = null;
2314 this.element_legend_childs.toolbox_right = null;
2315 this.element_legend_childs.toolbox_zoomin = null;
2316 this.element_legend_childs.toolbox_zoomout = null;
2317 this.element_legend_childs.toolbox_volume = null;
2320 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2321 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2322 this.element.appendChild(this.element_legend_childs.resize_handler);
2323 if(NETDATA.options.current.show_help === true)
2324 $(this.element_legend_childs.resize_handler).popover({
2329 placement: 'bottom',
2330 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2331 title: 'Chart Resize',
2332 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>'
2336 this.element_legend_childs.resize_handler.onmousedown =
2338 that.resizeHandler(e);
2342 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2343 that.resizeHandler(e);
2346 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2347 this.element_legend.appendChild(this.element_legend_childs.title_date);
2349 this.element_legend.appendChild(document.createElement('br'));
2351 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2352 this.element_legend.appendChild(this.element_legend_childs.title_time);
2354 this.element_legend.appendChild(document.createElement('br'));
2356 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2357 this.element_legend.appendChild(this.element_legend_childs.title_units);
2359 this.element_legend.appendChild(document.createElement('br'));
2361 this.element_legend_childs.nano.className = 'netdata-legend-series';
2362 this.element_legend.appendChild(this.element_legend_childs.nano);
2364 content.className = 'netdata-legend-series-content';
2365 this.element_legend_childs.nano.appendChild(content);
2367 if(NETDATA.options.current.show_help === true)
2368 $(content).popover({
2373 placement: 'bottom',
2374 title: 'Chart Legend',
2375 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2376 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>'
2380 this.element_legend_childs = {
2382 resize_handler: null,
2385 toolbox_right: null,
2386 toolbox_reset: null,
2387 toolbox_zoomin: null,
2388 toolbox_zoomout: null,
2389 toolbox_volume: null,
2400 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2401 if(this.debug === true)
2402 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2404 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2405 genLabel(this, content, this.data.dimension_names[i], i);
2409 var tmp = new Array();
2410 for(var dim in this.chart.dimensions) {
2411 tmp.push(this.chart.dimensions[dim].name);
2412 genLabel(this, content, this.chart.dimensions[dim].name, i);
2414 this.element_legend_childs.series.labels_key = tmp.toString();
2415 if(this.debug === true)
2416 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2419 // create a hidden div to be used for hidding
2420 // the original legend of the chart library
2421 var el = document.createElement('div');
2422 if(this.element_legend !== null)
2423 this.element_legend.appendChild(el);
2424 el.style.display = 'none';
2426 this.element_legend_childs.hidden = document.createElement('div');
2427 el.appendChild(this.element_legend_childs.hidden);
2429 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2430 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2432 this.legendShowLatestValues();
2435 this.hasLegend = function() {
2436 if(typeof this.___hasLegendCache___ !== 'undefined')
2437 return this.___hasLegendCache___;
2440 if(this.library && this.library.legend(this) === 'right-side') {
2441 var legend = $(this.element).data('legend') || 'yes';
2442 if(legend === 'yes') leg = true;
2445 this.___hasLegendCache___ = leg;
2449 this.legendWidth = function() {
2450 return (this.hasLegend())?140:0;
2453 this.legendHeight = function() {
2454 return $(this.element).height();
2457 this.chartWidth = function() {
2458 return $(this.element).width() - this.legendWidth();
2461 this.chartHeight = function() {
2462 return $(this.element).height();
2465 this.chartPixelsPerPoint = function() {
2466 // force an options provided detail
2467 var px = this.pixels_per_point;
2469 if(this.library && px < this.library.pixels_per_point(this))
2470 px = this.library.pixels_per_point(this);
2472 if(px < NETDATA.options.current.pixels_per_point)
2473 px = NETDATA.options.current.pixels_per_point;
2478 this.needsRecreation = function() {
2480 this.chart_created === true
2482 && this.library.autoresize() === false
2483 && this.tm.last_resized < NETDATA.options.last_resized
2487 this.chartURL = function() {
2488 var after, before, points_multiplier = 1;
2489 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2490 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2492 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2493 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2494 this.view_after = after * 1000;
2495 this.view_before = before * 1000;
2497 this.requested_padding = null;
2498 points_multiplier = 1;
2500 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2501 this.tm.pan_and_zoom_seq = 0;
2503 before = Math.round(this.current.force_before_ms / 1000);
2504 after = Math.round(this.current.force_after_ms / 1000);
2505 this.view_after = after * 1000;
2506 this.view_before = before * 1000;
2508 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2509 this.requested_padding = Math.round((before - after) / 2);
2510 after -= this.requested_padding;
2511 before += this.requested_padding;
2512 this.requested_padding *= 1000;
2513 points_multiplier = 2;
2516 this.current.force_before_ms = null;
2517 this.current.force_after_ms = null;
2520 this.tm.pan_and_zoom_seq = 0;
2522 before = this.before;
2524 this.view_after = after * 1000;
2525 this.view_before = before * 1000;
2527 this.requested_padding = null;
2528 points_multiplier = 1;
2531 this.requested_after = after * 1000;
2532 this.requested_before = before * 1000;
2534 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2536 // build the data URL
2537 this.data_url = this.host + this.chart.data_url;
2538 this.data_url += "&format=" + this.library.format();
2539 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2540 this.data_url += "&group=" + this.method;
2541 this.data_url += "&options=" + this.library.options(this);
2542 this.data_url += '|jsonwrap';
2544 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2545 this.data_url += '|nonzero';
2547 if(this.append_options !== null)
2548 this.data_url += '|' + this.append_options.toString();
2551 this.data_url += "&after=" + after.toString();
2554 this.data_url += "&before=" + before.toString();
2557 this.data_url += "&dimensions=" + this.dimensions;
2559 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2560 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2563 this.redrawChart = function() {
2564 if(this.data !== null)
2565 this.updateChartWithData(this.data);
2568 this.updateChartWithData = function(data) {
2569 if(this.debug === true)
2570 this.log('updateChartWithData() called.');
2572 this._updating = false;
2574 // this may force the chart to be re-created
2578 this.updates_counter++;
2579 this.updates_since_last_unhide++;
2580 this.updates_since_last_creation++;
2582 var started = new Date().getTime();
2584 // if the result is JSON, find the latest update-every
2585 this.data_update_every = data.view_update_every * 1000;
2586 this.data_after = data.after * 1000;
2587 this.data_before = data.before * 1000;
2588 this.netdata_first = data.first_entry * 1000;
2589 this.netdata_last = data.last_entry * 1000;
2590 this.data_points = data.points;
2593 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2594 if(this.view_after < this.data_after) {
2595 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2596 this.view_after = this.data_after;
2599 if(this.view_before > this.data_before) {
2600 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2601 this.view_before = this.data_before;
2605 this.view_after = this.data_after;
2606 this.view_before = this.data_before;
2609 if(this.debug === true) {
2610 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2612 if(this.current.force_after_ms)
2613 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2615 this.log('STATUS: forced : unset');
2617 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2618 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2619 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2620 this.log('STATUS: points : ' + (this.data_points).toString());
2623 if(this.data_points === 0) {
2628 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2629 if(this.debug === true)
2630 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2632 this.chart_created = false;
2635 // check and update the legend
2636 this.legendUpdateDOM();
2638 if(this.chart_created === true
2639 && typeof this.library.update === 'function') {
2641 if(this.debug === true)
2642 this.log('updating chart...');
2644 if(callChartLibraryUpdateSafely(data) === false)
2648 if(this.debug === true)
2649 this.log('creating chart...');
2651 if(callChartLibraryCreateSafely(data) === false)
2655 this.legendShowLatestValues();
2656 if(this.selected === true)
2657 NETDATA.globalSelectionSync.stop();
2659 // update the performance counters
2660 var now = new Date().getTime();
2661 this.tm.last_updated = now;
2663 // don't update last_autorefreshed if this chart is
2664 // forced to be updated with global PanAndZoom
2665 if(NETDATA.globalPanAndZoom.isActive())
2666 this.tm.last_autorefreshed = 0;
2668 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2669 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2671 this.tm.last_autorefreshed = now;
2674 this.refresh_dt_ms = now - started;
2675 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2677 if(this.refresh_dt_element !== null)
2678 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2681 this.updateChart = function(callback) {
2682 if(this.debug === true)
2683 this.log('updateChart() called.');
2685 if(this._updating === true) {
2686 if(this.debug === true)
2687 this.log('I am already updating...');
2689 if(typeof callback === 'function') callback();
2693 // due to late initialization of charts and libraries
2694 // we need to check this too
2695 if(this.enabled === false) {
2696 if(this.debug === true)
2697 this.log('I am not enabled');
2699 if(typeof callback === 'function') callback();
2703 if(canBeRendered() === false) {
2704 if(typeof callback === 'function') callback();
2708 if(this.chart === null) {
2709 this.getChart(function() { that.updateChart(callback); });
2713 if(this.library.initialized === false) {
2714 if(this.library.enabled === true) {
2715 this.library.initialize(function() { that.updateChart(callback); });
2719 error('chart library "' + this.library_name + '" is not available.');
2720 if(typeof callback === 'function') callback();
2725 this.clearSelection();
2728 if(this.debug === true)
2729 this.log('updating from ' + this.data_url);
2731 this._updating = true;
2733 this.xhr = $.ajax( {
2735 crossDomain: NETDATA.options.crossDomainAjax,
2739 .success(function(data) {
2740 if(that.debug === true)
2741 that.log('data received. updating chart.');
2743 that.updateChartWithData(data);
2746 error('data download failed for url: ' + that.data_url);
2748 .always(function() {
2749 that._updating = false;
2750 if(typeof callback === 'function') callback();
2756 this.isVisible = function(nocache) {
2757 if(typeof nocache === 'undefined')
2760 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2762 // caching - we do not evaluate the charts visibility
2763 // if the page has not been scrolled since the last check
2764 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2765 return this.___isVisible___;
2767 this.tm.last_visible_check = new Date().getTime();
2769 var wh = window.innerHeight;
2770 var x = this.element.getBoundingClientRect();
2774 if(x.width === 0 || x.height === 0) {
2776 this.___isVisible___ = false;
2777 return this.___isVisible___;
2780 if(x.top < 0 && -x.top > x.height) {
2781 // the chart is entirely above
2782 ret = -x.top - x.height;
2784 else if(x.top > wh) {
2785 // the chart is entirely below
2789 if(ret > tolerance) {
2790 // the chart is too far
2793 this.___isVisible___ = false;
2794 return this.___isVisible___;
2797 // the chart is inside or very close
2800 this.___isVisible___ = true;
2801 return this.___isVisible___;
2805 this.isAutoRefreshed = function() {
2806 return (this.current.autorefresh);
2809 this.canBeAutoRefreshed = function() {
2810 var now = new Date().getTime();
2812 if(this.enabled === false) {
2813 if(this.debug === true)
2814 this.log('I am not enabled');
2819 if(this.library === null || this.library.enabled === false) {
2820 error('charting library "' + this.library_name + '" is not available');
2821 if(this.debug === true)
2822 this.log('My chart library ' + this.library_name + ' is not available');
2827 if(this.isVisible() === false) {
2828 if(NETDATA.options.debug.visibility === true || this.debug === true)
2829 this.log('I am not visible');
2834 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2835 if(this.debug === true)
2836 this.log('timed force update detected - allowing this update');
2838 this.current.force_update_at = 0;
2842 if(this.isAutoRefreshed() === true) {
2843 // allow the first update, even if the page is not visible
2844 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2845 if(NETDATA.options.debug.focus === true || this.debug === true)
2846 this.log('canBeAutoRefreshed(): page does not have focus');
2851 if(this.needsRecreation() === true) {
2852 if(this.debug === true)
2853 this.log('canBeAutoRefreshed(): needs re-creation.');
2858 // options valid only for autoRefresh()
2859 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2860 if(NETDATA.globalPanAndZoom.isActive()) {
2861 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2862 if(this.debug === true)
2863 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2868 if(this.debug === true)
2869 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2875 if(this.selected === true) {
2876 if(this.debug === true)
2877 this.log('canBeAutoRefreshed(): I have a selection in place.');
2882 if(this.paused === true) {
2883 if(this.debug === true)
2884 this.log('canBeAutoRefreshed(): I am paused.');
2889 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2890 if(this.debug === true)
2891 this.log('canBeAutoRefreshed(): It is time to update me.');
2901 this.autoRefresh = function(callback) {
2902 if(this.canBeAutoRefreshed() === true) {
2903 this.updateChart(callback);
2906 if(typeof callback !== 'undefined')
2911 this._defaultsFromDownloadedChart = function(chart) {
2913 this.chart_url = chart.url;
2914 this.data_update_every = chart.update_every * 1000;
2915 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2916 this.tm.last_info_downloaded = new Date().getTime();
2918 if(this.title === null)
2919 this.title = chart.title;
2921 if(this.units === null)
2922 this.units = chart.units;
2925 // fetch the chart description from the netdata server
2926 this.getChart = function(callback) {
2927 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2929 this._defaultsFromDownloadedChart(this.chart);
2930 if(typeof callback === 'function') callback();
2933 this.chart_url = "/api/v1/chart?chart=" + this.id;
2935 if(this.debug === true)
2936 this.log('downloading ' + this.chart_url);
2939 url: this.host + this.chart_url,
2940 crossDomain: NETDATA.options.crossDomainAjax,
2944 .done(function(chart) {
2945 chart.url = that.chart_url;
2946 that._defaultsFromDownloadedChart(chart);
2947 NETDATA.chartRegistry.add(that.host, that.id, chart);
2950 NETDATA.error(404, that.chart_url);
2951 error('chart not found on url "' + that.chart_url + '"');
2953 .always(function() {
2954 if(typeof callback === 'function') callback();
2959 // ============================================================================================================
2965 NETDATA.resetAllCharts = function(state) {
2966 // first clear the global selection sync
2967 // to make sure no chart is in selected state
2968 state.globalSelectionSyncStop();
2970 // there are 2 possibilities here
2971 // a. state is the global Pan and Zoom master
2972 // b. state is not the global Pan and Zoom master
2974 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2977 // clear the global Pan and Zoom
2978 // this will also refresh the master
2979 // and unblock any charts currently mirroring the master
2980 NETDATA.globalPanAndZoom.clearMaster();
2982 // if we were not the master, reset our status too
2983 // this is required because most probably the mouse
2984 // is over this chart, blocking it from auto-refreshing
2985 if(master === false && (state.paused === true || state.selected === true))
2989 // get or create a chart state, given a DOM element
2990 NETDATA.chartState = function(element) {
2991 var state = $(element).data('netdata-state-object') || null;
2992 if(state === null) {
2993 state = new chartState(element);
2994 $(element).data('netdata-state-object', state);
2999 // ----------------------------------------------------------------------------------------------------------------
3000 // Library functions
3002 // Load a script without jquery
3003 // This is used to load jquery - after it is loaded, we use jquery
3004 NETDATA._loadjQuery = function(callback) {
3005 if(typeof jQuery === 'undefined') {
3006 if(NETDATA.options.debug.main_loop === true)
3007 console.log('loading ' + NETDATA.jQuery);
3009 var script = document.createElement('script');
3010 script.type = 'text/javascript';
3011 script.async = true;
3012 script.src = NETDATA.jQuery;
3014 // script.onabort = onError;
3015 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3016 if(typeof callback === "function")
3017 script.onload = callback;
3019 var s = document.getElementsByTagName('script')[0];
3020 s.parentNode.insertBefore(script, s);
3022 else if(typeof callback === "function")
3026 NETDATA._loadCSS = function(filename) {
3027 // don't use jQuery here
3028 // styles are loaded before jQuery
3029 // to eliminate showing an unstyled page to the user
3031 var fileref = document.createElement("link");
3032 fileref.setAttribute("rel", "stylesheet");
3033 fileref.setAttribute("type", "text/css");
3034 fileref.setAttribute("href", filename);
3036 if (typeof fileref !== 'undefined')
3037 document.getElementsByTagName("head")[0].appendChild(fileref);
3040 NETDATA.colorHex2Rgb = function(hex) {
3041 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3042 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3043 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3044 return r + r + g + g + b + b;
3047 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3049 r: parseInt(result[1], 16),
3050 g: parseInt(result[2], 16),
3051 b: parseInt(result[3], 16)
3055 NETDATA.colorLuminance = function(hex, lum) {
3056 // validate hex string
3057 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3059 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3063 // convert to decimal and change luminosity
3064 var rgb = "#", c, i;
3065 for (i = 0; i < 3; i++) {
3066 c = parseInt(hex.substr(i*2,2), 16);
3067 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3068 rgb += ("00"+c).substr(c.length);
3074 NETDATA.guid = function() {
3076 return Math.floor((1 + Math.random()) * 0x10000)
3081 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3084 NETDATA.zeropad = function(x) {
3085 if(x > -10 && x < 10) return '0' + x.toString();
3086 else return x.toString();
3089 // user function to signal us the DOM has been
3091 NETDATA.updatedDom = function() {
3092 NETDATA.options.updated_dom = true;
3095 NETDATA.ready = function(callback) {
3096 NETDATA.options.pauseCallback = callback;
3099 NETDATA.pause = function(callback) {
3100 if(NETDATA.options.pause === true)
3103 NETDATA.options.pauseCallback = callback;
3106 NETDATA.unpause = function() {
3107 NETDATA.options.pauseCallback = null;
3108 NETDATA.options.updated_dom = true;
3109 NETDATA.options.pause = false;
3112 // ----------------------------------------------------------------------------------------------------------------
3114 // this is purely sequencial charts refresher
3115 // it is meant to be autonomous
3116 NETDATA.chartRefresherNoParallel = function(index) {
3117 if(NETDATA.options.debug.mail_loop === true)
3118 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3120 if(NETDATA.options.updated_dom === true) {
3121 // the dom has been updated
3122 // get the dom parts again
3123 NETDATA.parseDom(NETDATA.chartRefresher);
3126 if(index >= NETDATA.options.targets.length) {
3127 if(NETDATA.options.debug.main_loop === true)
3128 console.log('waiting to restart main loop...');
3130 NETDATA.options.auto_refresher_fast_weight = 0;
3132 setTimeout(function() {
3133 NETDATA.chartRefresher();
3134 }, NETDATA.options.current.idle_between_loops);
3137 var state = NETDATA.options.targets[index];
3139 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3140 if(NETDATA.options.debug.main_loop === true)
3141 console.log('fast rendering...');
3143 state.autoRefresh(function() {
3144 NETDATA.chartRefresherNoParallel(++index);
3148 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3149 NETDATA.options.auto_refresher_fast_weight = 0;
3151 setTimeout(function() {
3152 state.autoRefresh(function() {
3153 NETDATA.chartRefresherNoParallel(++index);
3155 }, NETDATA.options.current.idle_between_charts);
3160 // this is part of the parallel refresher
3161 // its cause is to refresh sequencially all the charts
3162 // that depend on chart library initialization
3163 // it will call the parallel refresher back
3164 // as soon as it sees a chart that its chart library
3166 NETDATA.chartRefresher_uninitialized = function() {
3167 if(NETDATA.options.updated_dom === true) {
3168 // the dom has been updated
3169 // get the dom parts again
3170 NETDATA.parseDom(NETDATA.chartRefresher);
3174 if(NETDATA.options.sequencial.length === 0)
3175 NETDATA.chartRefresher();
3177 var state = NETDATA.options.sequencial.pop();
3178 if(state.library.initialized === true)
3179 NETDATA.chartRefresher();
3181 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3185 NETDATA.chartRefresherWaitTime = function() {
3186 return NETDATA.options.current.idle_parallel_loops;
3189 // the default refresher
3190 // it will create 2 sets of charts:
3191 // - the ones that can be refreshed in parallel
3192 // - the ones that depend on something else
3193 // the first set will be executed in parallel
3194 // the second will be given to NETDATA.chartRefresher_uninitialized()
3195 NETDATA.chartRefresher = function() {
3196 if(NETDATA.options.pause === true) {
3197 // console.log('auto-refresher is paused');
3198 setTimeout(NETDATA.chartRefresher,
3199 NETDATA.chartRefresherWaitTime());
3203 if(typeof NETDATA.options.pauseCallback === 'function') {
3204 // console.log('auto-refresher is calling pauseCallback');
3205 NETDATA.options.pause = true;
3206 NETDATA.options.pauseCallback();
3207 NETDATA.chartRefresher();
3211 if(NETDATA.options.current.parallel_refresher === false) {
3212 NETDATA.chartRefresherNoParallel(0);
3216 if(NETDATA.options.updated_dom === true) {
3217 // the dom has been updated
3218 // get the dom parts again
3219 NETDATA.parseDom(NETDATA.chartRefresher);
3223 var parallel = new Array();
3224 var targets = NETDATA.options.targets;
3225 var len = targets.length;
3227 if(targets[len].isVisible() === false)
3230 var state = targets[len];
3231 if(state.library.initialized === false) {
3232 if(state.library.enabled === true) {
3233 state.library.initialize(NETDATA.chartRefresher);
3237 state.error('chart library "' + state.library_name + '" is not enabled.');
3241 parallel.unshift(state);
3244 if(parallel.length > 0) {
3245 var parallel_jobs = parallel.length;
3247 // this will execute the jobs in parallel
3248 $(parallel).each(function() {
3249 this.autoRefresh(function() {
3252 if(parallel_jobs === 0) {
3253 setTimeout(NETDATA.chartRefresher,
3254 NETDATA.chartRefresherWaitTime());
3260 setTimeout(NETDATA.chartRefresher,
3261 NETDATA.chartRefresherWaitTime());
3265 NETDATA.parseDom = function(callback) {
3266 NETDATA.options.last_page_scroll = new Date().getTime();
3267 NETDATA.options.updated_dom = false;
3269 var targets = $('div[data-netdata]'); //.filter(':visible');
3271 if(NETDATA.options.debug.main_loop === true)
3272 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3274 NETDATA.options.targets = new Array();
3275 var len = targets.length;
3277 // the initialization will take care of sizing
3278 // and the "loading..." message
3279 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3282 if(typeof callback === 'function') callback();
3285 // this is the main function - where everything starts
3286 NETDATA.start = function() {
3287 // this should be called only once
3289 NETDATA.options.page_is_visible = true;
3291 $(window).blur(function() {
3292 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3293 NETDATA.options.page_is_visible = false;
3294 if(NETDATA.options.debug.focus === true)
3295 console.log('Lost Focus!');
3299 $(window).focus(function() {
3300 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3301 NETDATA.options.page_is_visible = true;
3302 if(NETDATA.options.debug.focus === true)
3303 console.log('Focus restored!');
3307 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3308 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3309 NETDATA.options.page_is_visible = false;
3310 if(NETDATA.options.debug.focus === true)
3311 console.log('Document has no focus!');
3315 // bootstrap tab switching
3316 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3318 // bootstrap modal switching
3319 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3320 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3322 NETDATA.parseDom(NETDATA.chartRefresher);
3325 // ----------------------------------------------------------------------------------------------------------------
3328 NETDATA.peityInitialize = function(callback) {
3329 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3331 url: NETDATA.peity_js,
3336 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3339 NETDATA.chartLibraries.peity.enabled = false;
3340 NETDATA.error(100, NETDATA.peity_js);
3342 .always(function() {
3343 if(typeof callback === "function")
3348 NETDATA.chartLibraries.peity.enabled = false;
3349 if(typeof callback === "function")
3354 NETDATA.peityChartUpdate = function(state, data) {
3355 state.peity_instance.innerHTML = data.result;
3357 if(state.peity_options.stroke !== state.chartColors()[0]) {
3358 state.peity_options.stroke = state.chartColors()[0];
3359 if(state.chart.chart_type === 'line')
3360 state.peity_options.fill = NETDATA.themes.current.background;
3362 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3365 $(state.peity_instance).peity('line', state.peity_options);
3369 NETDATA.peityChartCreate = function(state, data) {
3370 state.peity_instance = document.createElement('div');
3371 state.element_chart.appendChild(state.peity_instance);
3373 var self = $(state.element);
3374 state.peity_options = {
3375 stroke: NETDATA.themes.current.foreground,
3376 strokeWidth: self.data('peity-strokewidth') || 1,
3377 width: state.chartWidth(),
3378 height: state.chartHeight(),
3379 fill: NETDATA.themes.current.foreground
3382 NETDATA.peityChartUpdate(state, data);
3386 // ----------------------------------------------------------------------------------------------------------------
3389 NETDATA.sparklineInitialize = function(callback) {
3390 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3392 url: NETDATA.sparkline_js,
3397 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3400 NETDATA.chartLibraries.sparkline.enabled = false;
3401 NETDATA.error(100, NETDATA.sparkline_js);
3403 .always(function() {
3404 if(typeof callback === "function")
3409 NETDATA.chartLibraries.sparkline.enabled = false;
3410 if(typeof callback === "function")
3415 NETDATA.sparklineChartUpdate = function(state, data) {
3416 state.sparkline_options.width = state.chartWidth();
3417 state.sparkline_options.height = state.chartHeight();
3419 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3423 NETDATA.sparklineChartCreate = function(state, data) {
3424 var self = $(state.element);
3425 var type = self.data('sparkline-type') || 'line';
3426 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3427 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3428 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3429 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3430 var composite = self.data('sparkline-composite') || undefined;
3431 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3432 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3433 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3434 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3435 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3436 var spotColor = self.data('sparkline-spotcolor') || undefined;
3437 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3438 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3439 var spotRadius = self.data('sparkline-spotradius') || undefined;
3440 var valueSpots = self.data('sparkline-valuespots') || undefined;
3441 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3442 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3443 var lineWidth = self.data('sparkline-linewidth') || undefined;
3444 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3445 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3446 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3447 var xvalues = self.data('sparkline-xvalues') || undefined;
3448 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3449 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3450 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3451 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3452 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3453 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3454 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3455 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3456 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3457 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3458 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3459 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3460 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3461 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3462 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3463 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3464 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3465 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3466 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3467 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3468 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3469 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3471 state.sparkline_options = {
3473 lineColor: lineColor,
3474 fillColor: fillColor,
3475 chartRangeMin: chartRangeMin,
3476 chartRangeMax: chartRangeMax,
3477 composite: composite,
3478 enableTagOptions: enableTagOptions,
3479 tagOptionPrefix: tagOptionPrefix,
3480 tagValuesAttribute: tagValuesAttribute,
3481 disableHiddenCheck: disableHiddenCheck,
3482 defaultPixelsPerValue: defaultPixelsPerValue,
3483 spotColor: spotColor,
3484 minSpotColor: minSpotColor,
3485 maxSpotColor: maxSpotColor,
3486 spotRadius: spotRadius,
3487 valueSpots: valueSpots,
3488 highlightSpotColor: highlightSpotColor,
3489 highlightLineColor: highlightLineColor,
3490 lineWidth: lineWidth,
3491 normalRangeMin: normalRangeMin,
3492 normalRangeMax: normalRangeMax,
3493 drawNormalOnTop: drawNormalOnTop,
3495 chartRangeClip: chartRangeClip,
3496 chartRangeMinX: chartRangeMinX,
3497 chartRangeMaxX: chartRangeMaxX,
3498 disableInteraction: disableInteraction,
3499 disableTooltips: disableTooltips,
3500 disableHighlight: disableHighlight,
3501 highlightLighten: highlightLighten,
3502 highlightColor: highlightColor,
3503 tooltipContainer: tooltipContainer,
3504 tooltipClassname: tooltipClassname,
3505 tooltipChartTitle: state.title,
3506 tooltipFormat: tooltipFormat,
3507 tooltipPrefix: tooltipPrefix,
3508 tooltipSuffix: tooltipSuffix,
3509 tooltipSkipNull: tooltipSkipNull,
3510 tooltipValueLookups: tooltipValueLookups,
3511 tooltipFormatFieldlist: tooltipFormatFieldlist,
3512 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3513 numberFormatter: numberFormatter,
3514 numberDigitGroupSep: numberDigitGroupSep,
3515 numberDecimalMark: numberDecimalMark,
3516 numberDigitGroupCount: numberDigitGroupCount,
3517 animatedZooms: animatedZooms,
3518 width: state.chartWidth(),
3519 height: state.chartHeight()
3522 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3526 // ----------------------------------------------------------------------------------------------------------------
3533 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3534 if(after < state.netdata_first)
3535 after = state.netdata_first;
3537 if(before > state.netdata_last)
3538 before = state.netdata_last;
3540 state.setMode('zoom');
3541 state.globalSelectionSyncStop();
3542 state.globalSelectionSyncDelay();
3543 state.dygraph_user_action = true;
3544 state.dygraph_force_zoom = true;
3545 state.updateChartPanOrZoom(after, before);
3546 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3549 NETDATA.dygraphSetSelection = function(state, t) {
3550 if(typeof state.dygraph_instance !== 'undefined') {
3551 var r = state.calculateRowForTime(t);
3553 state.dygraph_instance.setSelection(r);
3555 state.dygraph_instance.clearSelection();
3556 state.legendShowUndefined();
3563 NETDATA.dygraphClearSelection = function(state, t) {
3564 if(typeof state.dygraph_instance !== 'undefined') {
3565 state.dygraph_instance.clearSelection();
3570 NETDATA.dygraphSmoothInitialize = function(callback) {
3572 url: NETDATA.dygraph_smooth_js,
3577 NETDATA.dygraph.smooth = true;
3578 smoothPlotter.smoothing = 0.3;
3581 NETDATA.dygraph.smooth = false;
3583 .always(function() {
3584 if(typeof callback === "function")
3589 NETDATA.dygraphInitialize = function(callback) {
3590 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3592 url: NETDATA.dygraph_js,
3597 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3600 NETDATA.chartLibraries.dygraph.enabled = false;
3601 NETDATA.error(100, NETDATA.dygraph_js);
3603 .always(function() {
3604 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3605 NETDATA.dygraphSmoothInitialize(callback);
3606 else if(typeof callback === "function")
3611 NETDATA.chartLibraries.dygraph.enabled = false;
3612 if(typeof callback === "function")
3617 NETDATA.dygraphChartUpdate = function(state, data) {
3618 var dygraph = state.dygraph_instance;
3620 if(typeof dygraph === 'undefined')
3621 return NETDATA.dygraphChartCreate(state, data);
3623 // when the chart is not visible, and hidden
3624 // if there is a window resize, dygraph detects
3625 // its element size as 0x0.
3626 // this will make it re-appear properly
3628 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3632 file: data.result.data,
3633 colors: state.chartColors(),
3634 labels: data.result.labels,
3635 labelsDivWidth: state.chartWidth() - 70,
3636 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3639 if(state.dygraph_force_zoom === true) {
3640 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3641 state.log('dygraphChartUpdate() forced zoom update');
3643 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3644 options.valueRange = null;
3645 options.isZoomedIgnoreProgrammaticZoom = true;
3646 state.dygraph_force_zoom = false;
3648 else if(state.current.name !== 'auto') {
3649 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3650 state.log('dygraphChartUpdate() loose update');
3653 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3654 state.log('dygraphChartUpdate() strict update');
3656 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3657 options.valueRange = null;
3658 options.isZoomedIgnoreProgrammaticZoom = true;
3661 if(state.dygraph_smooth_eligible === true) {
3662 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3663 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3664 NETDATA.dygraphChartCreate(state, data);
3669 dygraph.updateOptions(options);
3671 state.dygraph_last_rendered = new Date().getTime();
3675 NETDATA.dygraphChartCreate = function(state, data) {
3676 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3677 state.log('dygraphChartCreate()');
3679 var self = $(state.element);
3681 var chart_type = state.chart.chart_type;
3682 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3683 chart_type = self.data('dygraph-type') || chart_type;
3685 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3686 smooth = self.data('dygraph-smooth') || smooth;
3688 if(NETDATA.dygraph.smooth === false)
3691 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3692 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3694 state.dygraph_options = {
3695 colors: self.data('dygraph-colors') || state.chartColors(),
3697 // leave a few pixels empty on the right of the chart
3698 rightGap: self.data('dygraph-rightgap') || 5,
3699 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3700 showRoller: self.data('dygraph-showroller') || false,
3702 title: self.data('dygraph-title') || state.title,
3703 titleHeight: self.data('dygraph-titleheight') || 19,
3705 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3706 labels: data.result.labels,
3707 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3708 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3709 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3710 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3711 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3714 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3715 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3717 ylabel: state.units,
3718 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3720 // the function to plot the chart
3723 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3724 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3725 strokePattern: self.data('dygraph-strokepattern') || undefined,
3727 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3728 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3729 drawPoints: self.data('dygraph-drawpoints') || false,
3731 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3732 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3734 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3735 pointSize: self.data('dygraph-pointsize') || 1,
3737 // enabling this makes the chart with little square lines
3738 stepPlot: self.data('dygraph-stepplot') || false,
3740 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3741 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3742 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3744 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3745 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3746 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3747 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3749 drawAxis: self.data('dygraph-drawaxis') || true,
3750 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3751 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3752 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3754 drawGrid: self.data('dygraph-drawgrid') || true,
3755 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3756 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3757 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3758 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3759 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3761 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3762 sigFigs: self.data('dygraph-sigfigs') || null,
3763 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3764 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3766 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3767 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3768 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3770 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3771 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3775 ticker: Dygraph.dateTicker,
3776 axisLabelFormatter: function (d, gran) {
3777 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3779 valueFormatter: function (ms) {
3780 var d = new Date(ms);
3781 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3782 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3787 valueFormatter: function (x) {
3788 // we format legends with the state object
3789 // no need to do anything here
3790 // return (Math.round(x*100) / 100).toLocaleString();
3791 // return state.legendFormatValue(x);
3796 legendFormatter: function(data) {
3797 var elements = state.element_legend_childs;
3799 // if the hidden div is not there
3800 // we are not managing the legend
3801 if(elements.hidden === null) return;
3803 if (typeof data.x !== 'undefined') {
3804 state.legendSetDate(data.x);
3805 var i = data.series.length;
3807 var series = data.series[i];
3808 if(!series.isVisible) continue;
3809 state.legendSetLabelValue(series.label, series.y);
3815 drawCallback: function(dygraph, is_initial) {
3816 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3817 state.dygraph_user_action = false;
3819 var x_range = dygraph.xAxisRange();
3820 var after = Math.round(x_range[0]);
3821 var before = Math.round(x_range[1]);
3823 if(NETDATA.options.debug.dygraph === true)
3824 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3826 if(before <= state.netdata_last && after >= state.netdata_first)
3827 state.updateChartPanOrZoom(after, before);
3830 zoomCallback: function(minDate, maxDate, yRanges) {
3831 if(NETDATA.options.debug.dygraph === true)
3832 state.log('dygraphZoomCallback()');
3834 state.globalSelectionSyncStop();
3835 state.globalSelectionSyncDelay();
3836 state.setMode('zoom');
3838 // refresh it to the greatest possible zoom level
3839 state.dygraph_user_action = true;
3840 state.dygraph_force_zoom = true;
3841 state.updateChartPanOrZoom(minDate, maxDate);
3843 highlightCallback: function(event, x, points, row, seriesName) {
3844 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3845 state.log('dygraphHighlightCallback()');
3849 // there is a bug in dygraph when the chart is zoomed enough
3850 // the time it thinks is selected is wrong
3851 // here we calculate the time t based on the row number selected
3853 var t = state.data_after + row * state.data_update_every;
3854 // 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);
3856 state.globalSelectionSync(x);
3858 // fix legend zIndex using the internal structures of dygraph legend module
3859 // this works, but it is a hack!
3860 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3862 unhighlightCallback: function(event) {
3863 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3864 state.log('dygraphUnhighlightCallback()');
3866 state.unpauseChart();
3867 state.globalSelectionSyncStop();
3869 interactionModel : {
3870 mousedown: function(event, dygraph, context) {
3871 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3872 state.log('interactionModel.mousedown()');
3874 state.dygraph_user_action = true;
3875 state.globalSelectionSyncStop();
3877 if(NETDATA.options.debug.dygraph === true)
3878 state.log('dygraphMouseDown()');
3880 // Right-click should not initiate a zoom.
3881 if(event.button && event.button === 2) return;
3883 context.initializeMouseDown(event, dygraph, context);
3885 if(event.button && event.button === 1) {
3886 if (event.altKey || event.shiftKey) {
3887 state.setMode('pan');
3888 state.globalSelectionSyncDelay();
3889 Dygraph.startPan(event, dygraph, context);
3892 state.setMode('zoom');
3893 state.globalSelectionSyncDelay();
3894 Dygraph.startZoom(event, dygraph, context);
3898 if (event.altKey || event.shiftKey) {
3899 state.setMode('zoom');
3900 state.globalSelectionSyncDelay();
3901 Dygraph.startZoom(event, dygraph, context);
3904 state.setMode('pan');
3905 state.globalSelectionSyncDelay();
3906 Dygraph.startPan(event, dygraph, context);
3910 mousemove: function(event, dygraph, context) {
3911 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3912 state.log('interactionModel.mousemove()');
3914 if(context.isPanning) {
3915 state.dygraph_user_action = true;
3916 state.globalSelectionSyncStop();
3917 state.globalSelectionSyncDelay();
3918 state.setMode('pan');
3919 Dygraph.movePan(event, dygraph, context);
3921 else if(context.isZooming) {
3922 state.dygraph_user_action = true;
3923 state.globalSelectionSyncStop();
3924 state.globalSelectionSyncDelay();
3925 state.setMode('zoom');
3926 Dygraph.moveZoom(event, dygraph, context);
3929 mouseup: function(event, dygraph, context) {
3930 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3931 state.log('interactionModel.mouseup()');
3933 if (context.isPanning) {
3934 state.dygraph_user_action = true;
3935 state.globalSelectionSyncDelay();
3936 Dygraph.endPan(event, dygraph, context);
3938 else if (context.isZooming) {
3939 state.dygraph_user_action = true;
3940 state.globalSelectionSyncDelay();
3941 Dygraph.endZoom(event, dygraph, context);
3944 click: function(event, dygraph, context) {
3945 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3946 state.log('interactionModel.click()');
3948 event.preventDefault();
3950 dblclick: function(event, dygraph, context) {
3951 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3952 state.log('interactionModel.dblclick()');
3953 NETDATA.resetAllCharts(state);
3955 mousewheel: function(event, dygraph, context) {
3956 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3957 state.log('interactionModel.mousewheel()');
3959 // Take the offset of a mouse event on the dygraph canvas and
3960 // convert it to a pair of percentages from the bottom left.
3961 // (Not top left, bottom is where the lower value is.)
3962 function offsetToPercentage(g, offsetX, offsetY) {
3963 // This is calculating the pixel offset of the leftmost date.
3964 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3965 var yar0 = g.yAxisRange(0);
3967 // This is calculating the pixel of the higest value. (Top pixel)
3968 var yOffset = g.toDomCoords(null, yar0[1])[1];
3970 // x y w and h are relative to the corner of the drawing area,
3971 // so that the upper corner of the drawing area is (0, 0).
3972 var x = offsetX - xOffset;
3973 var y = offsetY - yOffset;
3975 // This is computing the rightmost pixel, effectively defining the
3977 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3979 // This is computing the lowest pixel, effectively defining the height.
3980 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3982 // Percentage from the left.
3983 var xPct = w === 0 ? 0 : (x / w);
3984 // Percentage from the top.
3985 var yPct = h === 0 ? 0 : (y / h);
3987 // The (1-) part below changes it from "% distance down from the top"
3988 // to "% distance up from the bottom".
3989 return [xPct, (1-yPct)];
3992 // Adjusts [x, y] toward each other by zoomInPercentage%
3993 // Split it so the left/bottom axis gets xBias/yBias of that change and
3994 // tight/top gets (1-xBias)/(1-yBias) of that change.
3996 // If a bias is missing it splits it down the middle.
3997 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3998 xBias = xBias || 0.5;
3999 yBias = yBias || 0.5;
4001 function adjustAxis(axis, zoomInPercentage, bias) {
4002 var delta = axis[1] - axis[0];
4003 var increment = delta * zoomInPercentage;
4004 var foo = [increment * bias, increment * (1-bias)];
4006 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4009 var yAxes = g.yAxisRanges();
4011 for (var i = 0; i < yAxes.length; i++) {
4012 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4015 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4018 if(event.altKey || event.shiftKey) {
4019 state.dygraph_user_action = true;
4021 state.globalSelectionSyncStop();
4022 state.globalSelectionSyncDelay();
4024 // http://dygraphs.com/gallery/interaction-api.js
4025 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4026 var percentage = normal / 50;
4028 if (!(event.offsetX && event.offsetY)){
4029 event.offsetX = event.layerX - event.target.offsetLeft;
4030 event.offsetY = event.layerY - event.target.offsetTop;
4033 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4034 var xPct = percentages[0];
4035 var yPct = percentages[1];
4037 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4039 var after = new_x_range[0];
4040 var before = new_x_range[1];
4042 var first = state.netdata_first + state.data_update_every;
4043 var last = state.netdata_last + state.data_update_every;
4046 after -= (before - last);
4053 state.setMode('zoom');
4054 if(state.updateChartPanOrZoom(after, before) === true)
4055 dygraph.updateOptions({ dateWindow: [ after, before ] });
4057 event.preventDefault();
4060 touchstart: function(event, dygraph, context) {
4061 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4062 state.log('interactionModel.touchstart()');
4064 state.dygraph_user_action = true;
4065 state.setMode('zoom');
4068 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4070 // we overwrite the touch directions at the end, to overwrite
4071 // the internal default of dygraphs
4072 context.touchDirections = { x: true, y: false };
4074 state.dygraph_last_touch_start = new Date().getTime();
4075 state.dygraph_last_touch_move = 0;
4077 if(typeof event.touches[0].pageX === 'number')
4078 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4080 state.dygraph_last_touch_page_x = 0;
4082 touchmove: function(event, dygraph, context) {
4083 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4084 state.log('interactionModel.touchmove()');
4086 state.dygraph_user_action = true;
4087 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4089 state.dygraph_last_touch_move = new Date().getTime();
4091 touchend: function(event, dygraph, context) {
4092 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4093 state.log('interactionModel.touchend()');
4095 state.dygraph_user_action = true;
4096 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4098 // if it didn't move, it is a selection
4099 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4100 // internal api of dygraphs
4101 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4102 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4103 if(NETDATA.dygraphSetSelection(state, t) === true)
4104 state.globalSelectionSync(t);
4107 // if it was double tap within double click time, reset the charts
4108 var now = new Date().getTime();
4109 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4110 if(state.dygraph_last_touch_move === 0) {
4111 var dt = now - state.dygraph_last_touch_end;
4112 if(dt <= NETDATA.options.current.double_click_speed)
4113 NETDATA.resetAllCharts(state);
4117 // remember the timestamp of the last touch end
4118 state.dygraph_last_touch_end = now;
4123 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4124 state.dygraph_options.drawGrid = false;
4125 state.dygraph_options.drawAxis = false;
4126 state.dygraph_options.title = undefined;
4127 state.dygraph_options.units = undefined;
4128 state.dygraph_options.ylabel = undefined;
4129 state.dygraph_options.yLabelWidth = 0;
4130 state.dygraph_options.labelsDivWidth = 120;
4131 state.dygraph_options.labelsDivStyles.width = '120px';
4132 state.dygraph_options.labelsSeparateLines = true;
4133 state.dygraph_options.rightGap = 0;
4136 if(smooth === true) {
4137 state.dygraph_smooth_eligible = true;
4139 if(NETDATA.options.current.smooth_plot === true)
4140 state.dygraph_options.plotter = smoothPlotter;
4142 else state.dygraph_smooth_eligible = false;
4144 state.dygraph_instance = new Dygraph(state.element_chart,
4145 data.result.data, state.dygraph_options);
4147 state.dygraph_force_zoom = false;
4148 state.dygraph_user_action = false;
4149 state.dygraph_last_rendered = new Date().getTime();
4153 // ----------------------------------------------------------------------------------------------------------------
4156 NETDATA.morrisInitialize = function(callback) {
4157 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4159 // morris requires raphael
4160 if(!NETDATA.chartLibraries.raphael.initialized) {
4161 if(NETDATA.chartLibraries.raphael.enabled) {
4162 NETDATA.raphaelInitialize(function() {
4163 NETDATA.morrisInitialize(callback);
4167 NETDATA.chartLibraries.morris.enabled = false;
4168 if(typeof callback === "function")
4173 NETDATA._loadCSS(NETDATA.morris_css);
4176 url: NETDATA.morris_js,
4181 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4184 NETDATA.chartLibraries.morris.enabled = false;
4185 NETDATA.error(100, NETDATA.morris_js);
4187 .always(function() {
4188 if(typeof callback === "function")
4194 NETDATA.chartLibraries.morris.enabled = false;
4195 if(typeof callback === "function")
4200 NETDATA.morrisChartUpdate = function(state, data) {
4201 state.morris_instance.setData(data.result.data);
4205 NETDATA.morrisChartCreate = function(state, data) {
4207 state.morris_options = {
4208 element: state.element_chart.id,
4209 data: data.result.data,
4211 ykeys: data.dimension_names,
4212 labels: data.dimension_names,
4218 continuousLine: false,
4219 behaveLikeLine: false
4222 if(state.chart.chart_type === 'line')
4223 state.morris_instance = new Morris.Line(state.morris_options);
4225 else if(state.chart.chart_type === 'area') {
4226 state.morris_options.behaveLikeLine = true;
4227 state.morris_instance = new Morris.Area(state.morris_options);
4230 state.morris_instance = new Morris.Area(state.morris_options);
4235 // ----------------------------------------------------------------------------------------------------------------
4238 NETDATA.raphaelInitialize = function(callback) {
4239 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4241 url: NETDATA.raphael_js,
4246 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4249 NETDATA.chartLibraries.raphael.enabled = false;
4250 NETDATA.error(100, NETDATA.raphael_js);
4252 .always(function() {
4253 if(typeof callback === "function")
4258 NETDATA.chartLibraries.raphael.enabled = false;
4259 if(typeof callback === "function")
4264 NETDATA.raphaelChartUpdate = function(state, data) {
4265 $(state.element_chart).raphael(data.result, {
4266 width: state.chartWidth(),
4267 height: state.chartHeight()
4273 NETDATA.raphaelChartCreate = function(state, data) {
4274 $(state.element_chart).raphael(data.result, {
4275 width: state.chartWidth(),
4276 height: state.chartHeight()
4282 // ----------------------------------------------------------------------------------------------------------------
4285 NETDATA.c3Initialize = function(callback) {
4286 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4289 if(!NETDATA.chartLibraries.d3.initialized) {
4290 if(NETDATA.chartLibraries.d3.enabled) {
4291 NETDATA.d3Initialize(function() {
4292 NETDATA.c3Initialize(callback);
4296 NETDATA.chartLibraries.c3.enabled = false;
4297 if(typeof callback === "function")
4302 NETDATA._loadCSS(NETDATA.c3_css);
4310 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4313 NETDATA.chartLibraries.c3.enabled = false;
4314 NETDATA.error(100, NETDATA.c3_js);
4316 .always(function() {
4317 if(typeof callback === "function")
4323 NETDATA.chartLibraries.c3.enabled = false;
4324 if(typeof callback === "function")
4329 NETDATA.c3ChartUpdate = function(state, data) {
4330 state.c3_instance.destroy();
4331 return NETDATA.c3ChartCreate(state, data);
4333 //state.c3_instance.load({
4334 // rows: data.result,
4341 NETDATA.c3ChartCreate = function(state, data) {
4343 state.element_chart.id = 'c3-' + state.uuid;
4344 // console.log('id = ' + state.element_chart.id);
4346 state.c3_instance = c3.generate({
4347 bindto: '#' + state.element_chart.id,
4349 width: state.chartWidth(),
4350 height: state.chartHeight()
4353 pattern: state.chartColors()
4358 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4364 format: function(x) {
4365 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4392 // console.log(state.c3_instance);
4397 // ----------------------------------------------------------------------------------------------------------------
4400 NETDATA.d3Initialize = function(callback) {
4401 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4408 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4411 NETDATA.chartLibraries.d3.enabled = false;
4412 NETDATA.error(100, NETDATA.d3_js);
4414 .always(function() {
4415 if(typeof callback === "function")
4420 NETDATA.chartLibraries.d3.enabled = false;
4421 if(typeof callback === "function")
4426 NETDATA.d3ChartUpdate = function(state, data) {
4430 NETDATA.d3ChartCreate = function(state, data) {
4434 // ----------------------------------------------------------------------------------------------------------------
4437 NETDATA.googleInitialize = function(callback) {
4438 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4440 url: NETDATA.google_js,
4445 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4446 google.load('visualization', '1.1', {
4447 'packages': ['corechart', 'controls'],
4448 'callback': callback
4452 NETDATA.chartLibraries.google.enabled = false;
4453 NETDATA.error(100, NETDATA.google_js);
4454 if(typeof callback === "function")
4459 NETDATA.chartLibraries.google.enabled = false;
4460 if(typeof callback === "function")
4465 NETDATA.googleChartUpdate = function(state, data) {
4466 var datatable = new google.visualization.DataTable(data.result);
4467 state.google_instance.draw(datatable, state.google_options);
4471 NETDATA.googleChartCreate = function(state, data) {
4472 var datatable = new google.visualization.DataTable(data.result);
4474 state.google_options = {
4475 colors: state.chartColors(),
4477 // do not set width, height - the chart resizes itself
4478 //width: state.chartWidth(),
4479 //height: state.chartHeight(),
4484 // title: "Time of Day",
4485 // format:'HH:mm:ss',
4486 viewWindowMode: 'maximized',
4498 viewWindowMode: 'pretty',
4513 focusTarget: 'category',
4520 titlePosition: 'out',
4531 curveType: 'function',
4536 switch(state.chart.chart_type) {
4538 state.google_options.vAxis.viewWindowMode = 'maximized';
4539 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4540 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4544 state.google_options.isStacked = true;
4545 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4546 state.google_options.vAxis.viewWindowMode = 'maximized';
4547 state.google_options.vAxis.minValue = null;
4548 state.google_options.vAxis.maxValue = null;
4549 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4554 state.google_options.lineWidth = 2;
4555 state.google_instance = new google.visualization.LineChart(state.element_chart);
4559 state.google_instance.draw(datatable, state.google_options);
4563 // ----------------------------------------------------------------------------------------------------------------
4565 NETDATA.percentFromValueMax = function(value, max) {
4566 if(value === null) value = 0;
4567 if(max < value) max = value;
4571 pcent = Math.round(value * 100 / max);
4572 if(pcent === 0 && value > 0) pcent = 1;
4578 // ----------------------------------------------------------------------------------------------------------------
4581 NETDATA.easypiechartInitialize = function(callback) {
4582 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4584 url: NETDATA.easypiechart_js,
4589 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4592 NETDATA.chartLibraries.easypiechart.enabled = false;
4593 NETDATA.error(100, NETDATA.easypiechart_js);
4595 .always(function() {
4596 if(typeof callback === "function")
4601 NETDATA.chartLibraries.easypiechart.enabled = false;
4602 if(typeof callback === "function")
4607 NETDATA.easypiechartClearSelection = function(state) {
4608 if(typeof state.easyPieChartEvent !== 'undefined') {
4609 if(state.easyPieChartEvent.timer !== null)
4610 clearTimeout(state.easyPieChartEvent.timer);
4612 state.easyPieChartEvent.timer = null;
4615 if(state.isAutoRefreshed() === true && state.data !== null) {
4616 NETDATA.easypiechartChartUpdate(state, state.data);
4619 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4620 state.easyPieChart_instance.update(0);
4622 state.easyPieChart_instance.enableAnimation();
4627 NETDATA.easypiechartSetSelection = function(state, t) {
4628 if(state.timeIsVisible(t) !== true)
4629 return NETDATA.easypiechartClearSelection(state);
4631 var slot = state.calculateRowForTime(t);
4632 if(slot < 0 || slot >= state.data.result.length)
4633 return NETDATA.easypiechartClearSelection(state);
4635 if(typeof state.easyPieChartEvent === 'undefined') {
4636 state.easyPieChartEvent = {
4643 var value = state.data.result[state.data.result.length - 1 - slot];
4644 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4645 var pcent = NETDATA.percentFromValueMax(value, max);
4647 state.easyPieChartEvent.value = value;
4648 state.easyPieChartEvent.pcent = pcent;
4649 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4651 if(state.easyPieChartEvent.timer === null) {
4652 state.easyPieChart_instance.disableAnimation();
4654 state.easyPieChartEvent.timer = setTimeout(function() {
4655 state.easyPieChartEvent.timer = null;
4656 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4657 }, NETDATA.options.current.charts_selection_animation_delay);
4663 NETDATA.easypiechartChartUpdate = function(state, data) {
4664 var value, max, pcent;
4666 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4672 value = data.result[0];
4673 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4674 pcent = NETDATA.percentFromValueMax(value, max);
4677 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4678 state.easyPieChart_instance.update(pcent);
4682 NETDATA.easypiechartChartCreate = function(state, data) {
4683 var self = $(state.element);
4684 var chart = $(state.element_chart);
4686 var value = data.result[0];
4687 var max = self.data('easypiechart-max-value') || null;
4688 var adjust = self.data('easypiechart-adjust') || null;
4692 state.easyPieChartMax = null;
4695 state.easyPieChartMax = max;
4697 var pcent = NETDATA.percentFromValueMax(value, max);
4699 chart.data('data-percent', pcent);
4703 case 'width': size = state.chartHeight(); break;
4704 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4705 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4707 default: size = state.chartWidth(); break;
4709 state.element.style.width = size + 'px';
4710 state.element.style.height = size + 'px';
4712 var stroke = Math.floor(size / 22);
4713 if(stroke < 3) stroke = 2;
4715 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4716 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4717 state.easyPieChartLabel = document.createElement('span');
4718 state.easyPieChartLabel.className = 'easyPieChartLabel';
4719 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4720 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4721 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4722 state.element_chart.appendChild(state.easyPieChartLabel);
4724 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4725 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4726 state.easyPieChartTitle = document.createElement('span');
4727 state.easyPieChartTitle.className = 'easyPieChartTitle';
4728 state.easyPieChartTitle.innerHTML = state.title;
4729 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4730 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4731 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4732 state.element_chart.appendChild(state.easyPieChartTitle);
4734 var unitfontsize = Math.round(titlefontsize * 0.9);
4735 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4736 state.easyPieChartUnits = document.createElement('span');
4737 state.easyPieChartUnits.className = 'easyPieChartUnits';
4738 state.easyPieChartUnits.innerHTML = state.units;
4739 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4740 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4741 state.element_chart.appendChild(state.easyPieChartUnits);
4743 chart.easyPieChart({
4744 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4745 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4746 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4747 scaleLength: self.data('easypiechart-scalelength') || 5,
4748 lineCap: self.data('easypiechart-linecap') || 'round',
4749 lineWidth: self.data('easypiechart-linewidth') || stroke,
4750 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4751 size: self.data('easypiechart-size') || size,
4752 rotate: self.data('easypiechart-rotate') || 0,
4753 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4754 easing: self.data('easypiechart-easing') || undefined
4757 // when we just re-create the chart
4758 // do not animate the first update
4760 if(typeof state.easyPieChart_instance !== 'undefined')
4763 state.easyPieChart_instance = chart.data('easyPieChart');
4764 if(animate === false) state.easyPieChart_instance.disableAnimation();
4765 state.easyPieChart_instance.update(pcent);
4766 if(animate === false) state.easyPieChart_instance.enableAnimation();
4770 // ----------------------------------------------------------------------------------------------------------------
4773 NETDATA.gaugeInitialize = function(callback) {
4774 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4776 url: NETDATA.gauge_js,
4781 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4784 NETDATA.chartLibraries.gauge.enabled = false;
4785 NETDATA.error(100, NETDATA.gauge_js);
4787 .always(function() {
4788 if(typeof callback === "function")
4793 NETDATA.chartLibraries.gauge.enabled = false;
4794 if(typeof callback === "function")
4799 NETDATA.gaugeAnimation = function(state, status) {
4802 if(typeof status === 'boolean' && status === false)
4804 else if(typeof status === 'number')
4807 state.gauge_instance.animationSpeed = speed;
4808 state.___gaugeOld__.speed = speed;
4811 NETDATA.gaugeSet = function(state, value, min, max) {
4812 if(typeof value !== 'number') value = 0;
4813 if(typeof min !== 'number') min = 0;
4814 if(typeof max !== 'number') max = 0;
4815 if(value > max) max = value;
4816 if(value < min) min = value;
4825 // gauge.js has an issue if the needle
4826 // is smaller than min or larger than max
4827 // when we set the new values
4828 // the needle will go crazy
4830 // to prevent it, we always feed it
4831 // with a percentage, so that the needle
4832 // is always between min and max
4833 var pcent = (value - min) * 100 / (max - min);
4835 // these should never happen
4836 if(pcent < 0) pcent = 0;
4837 if(pcent > 100) pcent = 100;
4839 state.gauge_instance.set(pcent);
4841 state.___gaugeOld__.value = value;
4842 state.___gaugeOld__.min = min;
4843 state.___gaugeOld__.max = max;
4846 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4847 if(state.___gaugeOld__.valueLabel !== value) {
4848 state.___gaugeOld__.valueLabel = value;
4849 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4851 if(state.___gaugeOld__.minLabel !== min) {
4852 state.___gaugeOld__.minLabel = min;
4853 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4855 if(state.___gaugeOld__.maxLabel !== max) {
4856 state.___gaugeOld__.maxLabel = max;
4857 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4861 NETDATA.gaugeClearSelection = function(state) {
4862 if(typeof state.gaugeEvent !== 'undefined') {
4863 if(state.gaugeEvent.timer !== null)
4864 clearTimeout(state.gaugeEvent.timer);
4866 state.gaugeEvent.timer = null;
4869 if(state.isAutoRefreshed() === true && state.data !== null) {
4870 NETDATA.gaugeChartUpdate(state, state.data);
4873 NETDATA.gaugeAnimation(state, false);
4874 NETDATA.gaugeSet(state, null, null, null);
4875 NETDATA.gaugeSetLabels(state, null, null, null);
4878 NETDATA.gaugeAnimation(state, true);
4882 NETDATA.gaugeSetSelection = function(state, t) {
4883 if(state.timeIsVisible(t) !== true)
4884 return NETDATA.gaugeClearSelection(state);
4886 var slot = state.calculateRowForTime(t);
4887 if(slot < 0 || slot >= state.data.result.length)
4888 return NETDATA.gaugeClearSelection(state);
4890 if(typeof state.gaugeEvent === 'undefined') {
4891 state.gaugeEvent = {
4899 var value = state.data.result[state.data.result.length - 1 - slot];
4900 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4903 state.gaugeEvent.value = value;
4904 state.gaugeEvent.max = max;
4905 state.gaugeEvent.min = min;
4906 NETDATA.gaugeSetLabels(state, value, min, max);
4908 if(state.gaugeEvent.timer === null) {
4909 NETDATA.gaugeAnimation(state, false);
4911 state.gaugeEvent.timer = setTimeout(function() {
4912 state.gaugeEvent.timer = null;
4913 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4914 }, NETDATA.options.current.charts_selection_animation_delay);
4920 NETDATA.gaugeChartUpdate = function(state, data) {
4921 var value, min, max;
4923 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4927 NETDATA.gaugeSetLabels(state, null, null, null);
4930 value = data.result[0];
4932 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4933 if(value > max) max = value;
4934 NETDATA.gaugeSetLabels(state, value, min, max);
4937 NETDATA.gaugeSet(state, value, min, max);
4941 NETDATA.gaugeChartCreate = function(state, data) {
4942 var self = $(state.element);
4943 // var chart = $(state.element_chart);
4945 var value = data.result[0];
4946 var max = self.data('gauge-max-value') || null;
4947 var adjust = self.data('gauge-adjust') || null;
4948 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4949 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4950 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4951 var stopColor = self.data('gauge-stop-color') || void 0;
4952 var generateGradient = self.data('gauge-generate-gradient') || false;
4956 state.gaugeMax = null;
4959 state.gaugeMax = max;
4961 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4963 // case 'width': width = height * ratio; break;
4965 // default: height = width / ratio; break;
4967 //state.element.style.width = width.toString() + 'px';
4968 //state.element.style.height = height.toString() + 'px';
4973 lines: 12, // The number of lines to draw
4974 angle: 0.15, // The length of each line
4975 lineWidth: 0.44, // 0.44 The line thickness
4977 length: 0.8, // 0.9 The radius of the inner circle
4978 strokeWidth: 0.035, // The rotation offset
4979 color: pointerColor // Fill color
4981 colorStart: startColor, // Colors
4982 colorStop: stopColor, // just experiment with them
4983 strokeColor: strokeColor, // to see which ones work best for you
4985 generateGradient: generateGradient,
4989 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4990 options.percentColors = [
4991 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4992 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4993 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4994 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4995 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4996 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4997 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4998 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4999 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5000 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5001 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5004 state.gauge_canvas = document.createElement('canvas');
5005 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5006 state.gauge_canvas.className = 'gaugeChart';
5007 state.gauge_canvas.width = width;
5008 state.gauge_canvas.height = height;
5009 state.element_chart.appendChild(state.gauge_canvas);
5011 var valuefontsize = Math.floor(height / 6);
5012 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5013 state.gaugeChartLabel = document.createElement('span');
5014 state.gaugeChartLabel.className = 'gaugeChartLabel';
5015 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5016 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5017 state.element_chart.appendChild(state.gaugeChartLabel);
5019 var titlefontsize = Math.round(valuefontsize / 2);
5021 state.gaugeChartTitle = document.createElement('span');
5022 state.gaugeChartTitle.className = 'gaugeChartTitle';
5023 state.gaugeChartTitle.innerHTML = state.title;
5024 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5025 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5026 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5027 state.element_chart.appendChild(state.gaugeChartTitle);
5029 var unitfontsize = Math.round(titlefontsize * 0.9);
5030 state.gaugeChartUnits = document.createElement('span');
5031 state.gaugeChartUnits.className = 'gaugeChartUnits';
5032 state.gaugeChartUnits.innerHTML = state.units;
5033 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5034 state.element_chart.appendChild(state.gaugeChartUnits);
5036 state.gaugeChartMin = document.createElement('span');
5037 state.gaugeChartMin.className = 'gaugeChartMin';
5038 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5039 state.element_chart.appendChild(state.gaugeChartMin);
5041 state.gaugeChartMax = document.createElement('span');
5042 state.gaugeChartMax.className = 'gaugeChartMax';
5043 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5044 state.element_chart.appendChild(state.gaugeChartMax);
5046 // when we just re-create the chart
5047 // do not animate the first update
5049 if(typeof state.gauge_instance !== 'undefined')
5052 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5054 state.___gaugeOld__ = {
5063 // we will always feed a percentage
5064 state.gauge_instance.minValue = 0;
5065 state.gauge_instance.maxValue = 100;
5067 NETDATA.gaugeAnimation(state, animate);
5068 NETDATA.gaugeSet(state, value, 0, max);
5069 NETDATA.gaugeSetLabels(state, value, 0, max);
5070 NETDATA.gaugeAnimation(state, true);
5074 // ----------------------------------------------------------------------------------------------------------------
5075 // Charts Libraries Registration
5077 NETDATA.chartLibraries = {
5079 initialize: NETDATA.dygraphInitialize,
5080 create: NETDATA.dygraphChartCreate,
5081 update: NETDATA.dygraphChartUpdate,
5082 resize: function(state) {
5083 if(typeof state.dygraph_instance.resize === 'function')
5084 state.dygraph_instance.resize();
5086 setSelection: NETDATA.dygraphSetSelection,
5087 clearSelection: NETDATA.dygraphClearSelection,
5088 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5091 format: function(state) { return 'json'; },
5092 options: function(state) { return 'ms|flip'; },
5093 legend: function(state) {
5094 if(this.isSparkline(state) === false)
5095 return 'right-side';
5099 autoresize: function(state) { return true; },
5100 max_updates_to_recreate: function(state) { return 5000; },
5101 track_colors: function(state) { return true; },
5102 pixels_per_point: function(state) {
5103 if(this.isSparkline(state) === false)
5109 isSparkline: function(state) {
5110 if(typeof state.dygraph_sparkline === 'undefined') {
5111 var t = $(state.element).data('dygraph-theme');
5112 if(t === 'sparkline')
5113 state.dygraph_sparkline = true;
5115 state.dygraph_sparkline = false;
5117 return state.dygraph_sparkline;
5121 initialize: NETDATA.sparklineInitialize,
5122 create: NETDATA.sparklineChartCreate,
5123 update: NETDATA.sparklineChartUpdate,
5125 setSelection: undefined, // function(state, t) { return true; },
5126 clearSelection: undefined, // function(state) { return true; },
5127 toolboxPanAndZoom: null,
5130 format: function(state) { return 'array'; },
5131 options: function(state) { return 'flip|abs'; },
5132 legend: function(state) { return null; },
5133 autoresize: function(state) { return false; },
5134 max_updates_to_recreate: function(state) { return 5000; },
5135 track_colors: function(state) { return false; },
5136 pixels_per_point: function(state) { return 3; }
5139 initialize: NETDATA.peityInitialize,
5140 create: NETDATA.peityChartCreate,
5141 update: NETDATA.peityChartUpdate,
5143 setSelection: undefined, // function(state, t) { return true; },
5144 clearSelection: undefined, // function(state) { return true; },
5145 toolboxPanAndZoom: null,
5148 format: function(state) { return 'ssvcomma'; },
5149 options: function(state) { return 'null2zero|flip|abs'; },
5150 legend: function(state) { return null; },
5151 autoresize: function(state) { return false; },
5152 max_updates_to_recreate: function(state) { return 5000; },
5153 track_colors: function(state) { return false; },
5154 pixels_per_point: function(state) { return 3; }
5157 initialize: NETDATA.morrisInitialize,
5158 create: NETDATA.morrisChartCreate,
5159 update: NETDATA.morrisChartUpdate,
5161 setSelection: undefined, // function(state, t) { return true; },
5162 clearSelection: undefined, // function(state) { return true; },
5163 toolboxPanAndZoom: null,
5166 format: function(state) { return 'json'; },
5167 options: function(state) { return 'objectrows|ms'; },
5168 legend: function(state) { return null; },
5169 autoresize: function(state) { return false; },
5170 max_updates_to_recreate: function(state) { return 50; },
5171 track_colors: function(state) { return false; },
5172 pixels_per_point: function(state) { return 15; }
5175 initialize: NETDATA.googleInitialize,
5176 create: NETDATA.googleChartCreate,
5177 update: NETDATA.googleChartUpdate,
5179 setSelection: undefined, //function(state, t) { return true; },
5180 clearSelection: undefined, //function(state) { return true; },
5181 toolboxPanAndZoom: null,
5184 format: function(state) { return 'datatable'; },
5185 options: function(state) { return ''; },
5186 legend: function(state) { return null; },
5187 autoresize: function(state) { return false; },
5188 max_updates_to_recreate: function(state) { return 300; },
5189 track_colors: function(state) { return false; },
5190 pixels_per_point: function(state) { return 4; }
5193 initialize: NETDATA.raphaelInitialize,
5194 create: NETDATA.raphaelChartCreate,
5195 update: NETDATA.raphaelChartUpdate,
5197 setSelection: undefined, // function(state, t) { return true; },
5198 clearSelection: undefined, // function(state) { return true; },
5199 toolboxPanAndZoom: null,
5202 format: function(state) { return 'json'; },
5203 options: function(state) { return ''; },
5204 legend: function(state) { return null; },
5205 autoresize: function(state) { return false; },
5206 max_updates_to_recreate: function(state) { return 5000; },
5207 track_colors: function(state) { return false; },
5208 pixels_per_point: function(state) { return 3; }
5211 initialize: NETDATA.c3Initialize,
5212 create: NETDATA.c3ChartCreate,
5213 update: NETDATA.c3ChartUpdate,
5215 setSelection: undefined, // function(state, t) { return true; },
5216 clearSelection: undefined, // function(state) { return true; },
5217 toolboxPanAndZoom: null,
5220 format: function(state) { return 'csvjsonarray'; },
5221 options: function(state) { return 'milliseconds'; },
5222 legend: function(state) { return null; },
5223 autoresize: function(state) { return false; },
5224 max_updates_to_recreate: function(state) { return 5000; },
5225 track_colors: function(state) { return false; },
5226 pixels_per_point: function(state) { return 15; }
5229 initialize: NETDATA.d3Initialize,
5230 create: NETDATA.d3ChartCreate,
5231 update: NETDATA.d3ChartUpdate,
5233 setSelection: undefined, // function(state, t) { return true; },
5234 clearSelection: undefined, // function(state) { return true; },
5235 toolboxPanAndZoom: null,
5238 format: function(state) { return 'json'; },
5239 options: function(state) { return ''; },
5240 legend: function(state) { return null; },
5241 autoresize: function(state) { return false; },
5242 max_updates_to_recreate: function(state) { return 5000; },
5243 track_colors: function(state) { return false; },
5244 pixels_per_point: function(state) { return 3; }
5247 initialize: NETDATA.easypiechartInitialize,
5248 create: NETDATA.easypiechartChartCreate,
5249 update: NETDATA.easypiechartChartUpdate,
5251 setSelection: NETDATA.easypiechartSetSelection,
5252 clearSelection: NETDATA.easypiechartClearSelection,
5253 toolboxPanAndZoom: null,
5256 format: function(state) { return 'array'; },
5257 options: function(state) { return 'absolute'; },
5258 legend: function(state) { return null; },
5259 autoresize: function(state) { return false; },
5260 max_updates_to_recreate: function(state) { return 5000; },
5261 track_colors: function(state) { return true; },
5262 pixels_per_point: function(state) { return 3; },
5266 initialize: NETDATA.gaugeInitialize,
5267 create: NETDATA.gaugeChartCreate,
5268 update: NETDATA.gaugeChartUpdate,
5270 setSelection: NETDATA.gaugeSetSelection,
5271 clearSelection: NETDATA.gaugeClearSelection,
5272 toolboxPanAndZoom: null,
5275 format: function(state) { return 'array'; },
5276 options: function(state) { return 'absolute'; },
5277 legend: function(state) { return null; },
5278 autoresize: function(state) { return false; },
5279 max_updates_to_recreate: function(state) { return 5000; },
5280 track_colors: function(state) { return true; },
5281 pixels_per_point: function(state) { return 3; },
5286 NETDATA.registerChartLibrary = function(library, url) {
5287 if(NETDATA.options.debug.libraries === true)
5288 console.log("registering chart library: " + library);
5290 NETDATA.chartLibraries[library].url = url;
5291 NETDATA.chartLibraries[library].initialized = true;
5292 NETDATA.chartLibraries[library].enabled = true;
5295 // ----------------------------------------------------------------------------------------------------------------
5298 NETDATA.requiredJs = [
5300 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5301 isAlreadyLoaded: function() {
5302 if(typeof $().emulateTransitionEnd == 'function')
5305 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5313 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5314 isAlreadyLoaded: function() { return false; }
5317 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5318 isAlreadyLoaded: function() { return false; }
5322 NETDATA.requiredCSS = [
5324 url: NETDATA.themes.current.bootstrap_css,
5325 isAlreadyLoaded: function() {
5326 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5333 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5334 isAlreadyLoaded: function() { return false; }
5337 url: NETDATA.themes.current.dashboard_css,
5338 isAlreadyLoaded: function() { return false; }
5341 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5342 isAlreadyLoaded: function() { return false; }
5346 NETDATA.loadRequiredJs = function(index, callback) {
5347 if(index >= NETDATA.requiredJs.length) {
5348 if(typeof callback === 'function')
5353 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5354 NETDATA.loadRequiredJs(++index, callback);
5358 if(NETDATA.options.debug.main_loop === true)
5359 console.log('loading ' + NETDATA.requiredJs[index].url);
5362 url: NETDATA.requiredJs[index].url,
5366 .success(function() {
5367 if(NETDATA.options.debug.main_loop === true)
5368 console.log('loaded ' + NETDATA.requiredJs[index].url);
5370 NETDATA.loadRequiredJs(++index, callback);
5373 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5377 NETDATA.loadRequiredCSS = function(index) {
5378 if(index >= NETDATA.requiredCSS.length)
5381 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5382 NETDATA.loadRequiredCSS(++index);
5386 if(NETDATA.options.debug.main_loop === true)
5387 console.log('loading ' + NETDATA.requiredCSS[index].url);
5389 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5390 NETDATA.loadRequiredCSS(++index);
5393 NETDATA.errorReset();
5394 NETDATA.loadRequiredCSS(0);
5396 NETDATA._loadjQuery(function() {
5397 NETDATA.loadRequiredJs(0, function() {
5398 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5399 if(NETDATA.options.debug.main_loop === true)
5400 console.log('starting chart refresh thread');
5407 // window.NETDATA = NETDATA;
5408 // })(window, document);