1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
16 // You can also set the default netdata server, using the following.
17 // When this variable is not set, we assume the page is hosted on your
18 // netdata server already.
19 // var netdataServer = "http://yourhost:19999"; // set your NetData server
21 //(function(window, document, undefined) {
22 // fix IE issue with console
23 if(!window.console){ window.console = {log: function(){} }; }
26 var NETDATA = window.NETDATA || {};
28 // ----------------------------------------------------------------------------------------------------------------
29 // Detect the netdata server
31 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
32 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
33 NETDATA._scriptSource = function() {
36 if(typeof document.currentScript !== 'undefined') {
37 script = document.currentScript;
40 var all_scripts = document.getElementsByTagName('script');
41 script = all_scripts[all_scripts.length - 1];
44 if (typeof script.getAttribute.length !== 'undefined')
47 script = script.getAttribute('src', -1);
52 if(typeof netdataServer !== 'undefined')
53 NETDATA.serverDefault = netdataServer;
55 var s = NETDATA._scriptSource();
56 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
59 if(NETDATA.serverDefault === null)
60 NETDATA.serverDefault = '';
61 else if(NETDATA.serverDefault.slice(-1) !== '/')
62 NETDATA.serverDefault += '/';
64 // default URLs for all the external files we need
65 // make them RELATIVE so that the whole thing can also be
66 // installed under a web server
67 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
68 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
69 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
70 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
71 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
72 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
73 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
74 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
75 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
76 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
77 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
78 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
79 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
80 NETDATA.google_js = 'https://www.google.com/jsapi';
84 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
85 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
86 background: '#FFFFFF',
87 foreground: '#000000',
90 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
91 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
92 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
93 '#329262', '#3B3EAC' ],
94 easypiechart_track: '#f0f0f0',
95 easypiechart_scale: '#dfe0e0',
96 gauge_pointer: '#C0C0C0',
97 gauge_stroke: '#F0F0F0',
101 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
102 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
103 background: '#272b30',
104 foreground: '#C8C8C8',
107 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
108 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
109 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
110 '#a6a479', '#a66da8' ],
112 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
113 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
114 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
115 '#329262', '#3B3EFF' ],
116 easypiechart_track: '#373b40',
117 easypiechart_scale: '#373b40',
118 gauge_pointer: '#474b50',
119 gauge_stroke: '#373b40',
120 gauge_gradient: false
124 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
125 NETDATA.themes.current = NETDATA.themes[netdataTheme];
127 NETDATA.themes.current = NETDATA.themes.default;
129 NETDATA.colors = NETDATA.themes.current.colors;
131 // these are the colors Google Charts are using
132 // we have them here to attempt emulate their look and feel on the other chart libraries
133 // http://there4.io/2012/05/02/google-chart-color-list/
134 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
135 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
136 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
138 // an alternative set
139 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
140 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
141 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
143 // ----------------------------------------------------------------------------------------------------------------
144 // the defaults for all charts
146 // if the user does not specify any of these, the following will be used
148 NETDATA.chartDefaults = {
149 host: NETDATA.serverDefault, // the server to get data from
150 width: '100%', // the chart width - can be null
151 height: '100%', // the chart height - can be null
152 min_width: null, // the chart minimum width - can be null
153 library: 'dygraph', // the graphing library to use
154 method: 'average', // the grouping method
155 before: 0, // panning
156 after: -600, // panning
157 pixels_per_point: 1, // the detail of the chart
158 fill_luminance: 0.8 // luminance of colors in solit areas
161 // ----------------------------------------------------------------------------------------------------------------
165 pauseCallback: null, // a callback when we are really paused
167 pause: false, // when enabled we don't auto-refresh the charts
169 targets: null, // an array of all the state objects that are
170 // currently active (independently of their
171 // viewport visibility)
173 updated_dom: true, // when true, the DOM has been updated with
174 // new elements we have to check.
176 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
177 // rendering charts continiously.
178 // used with .current.fast_render_timeframe
180 page_is_visible: true, // when true, this page is visible
182 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
183 // auto-refresher for some time (when a chart is
184 // performing pan or zoom, we need to stop refreshing
185 // all other charts, to have the maximum speed for
186 // rendering the chart that is panned or zoomed).
187 // Used with .current.global_pan_sync_time
189 last_resized: new Date().getTime(), // the timestamp of the last resize request
191 crossDomainAjax: false, // enable this to request crossDomain AJAX
193 last_page_scroll: 0, // the timestamp the last time the page was scrolled
195 // the current profile
196 // we may have many...
198 pixels_per_point: 1, // the minimum pixels per point for all charts
199 // increase this to speed javascript up
200 // each chart library has its own limit too
201 // the max of this and the chart library is used
202 // the final is calculated every time, so a change
203 // here will have immediate effect on the next chart
206 idle_between_charts: 100, // ms - how much time to wait between chart updates
208 fast_render_timeframe: 200, // ms - render continously until this time of continious
209 // rendering has been reached
210 // this setting is used to make it render e.g. 10
211 // charts at once, sleep idle_between_charts time
212 // and continue for another 10 charts.
214 idle_between_loops: 500, // ms - if all charts have been updated, wait this
215 // time before starting again.
217 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
219 idle_lost_focus: 500, // ms - when the window does not have focus, check
220 // if focus has been regained, every this time
222 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
223 // autorefreshing of charts is paused for this amount
226 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
227 // of time before setting up synchronized selections
230 sync_selection: true, // enable or disable selection sync
232 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
234 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
236 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
238 update_only_visible: true, // enable or disable visibility management
240 parallel_refresher: true, // enable parallel refresh of charts
242 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
244 destroy_on_hide: false, // destroy charts when they are not visible
246 show_help: true, // when enabled the charts will show some help
247 show_help_delay_show_ms: 500,
248 show_help_delay_hide_ms: 0,
250 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
252 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
253 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
255 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
257 smooth_plot: true, // enable smooth plot, where possible
259 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
261 color_fill_opacity_line: 1.0,
262 color_fill_opacity_area: 0.2,
263 color_fill_opacity_stacked: 0.8,
265 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
266 pan_and_zoom_factor_multiplier_control: 2.0,
267 pan_and_zoom_factor_multiplier_shift: 3.0,
268 pan_and_zoom_factor_multiplier_alt: 4.0,
270 setOptionCallback: function() { ; }
278 chart_data_url: false,
279 chart_errors: false, // FIXME
288 // ----------------------------------------------------------------------------------------------------------------
289 // local storage options
291 NETDATA.localStorage = {
294 callback: {} // only used for resetting back to defaults
297 NETDATA.localStorageGet = function(key, def, callback) {
300 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
301 NETDATA.localStorage.default[key.toString()] = def;
302 NETDATA.localStorage.callback[key.toString()] = callback;
305 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
307 // console.log('localStorage: loading "' + key.toString() + '"');
308 ret = localStorage.getItem(key.toString());
309 if(ret === null || ret === 'undefined') {
310 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
311 localStorage.setItem(key.toString(), JSON.stringify(def));
315 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
316 ret = JSON.parse(ret);
317 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
321 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
326 if(typeof ret === 'undefined' || ret === 'undefined') {
327 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
331 NETDATA.localStorage.current[key.toString()] = ret;
335 NETDATA.localStorageSet = function(key, value, callback) {
336 if(typeof value === 'undefined' || value === 'undefined') {
337 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
340 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
341 NETDATA.localStorage.default[key.toString()] = value;
342 NETDATA.localStorage.current[key.toString()] = value;
343 NETDATA.localStorage.callback[key.toString()] = callback;
346 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
347 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
349 localStorage.setItem(key.toString(), JSON.stringify(value));
352 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
356 NETDATA.localStorage.current[key.toString()] = value;
360 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
362 if(typeof obj[i] === 'object') {
363 //console.log('object ' + prefix + '.' + i.toString());
364 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
368 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
372 NETDATA.setOption = function(key, value) {
373 if(key.toString() === 'setOptionCallback') {
374 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
375 NETDATA.options.current[key.toString()] = value;
376 NETDATA.options.current.setOptionCallback();
379 else if(NETDATA.options.current[key.toString()] !== value) {
380 var name = 'options.' + key.toString();
382 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
383 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
385 //console.log(NETDATA.localStorage);
386 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
387 //console.log(NETDATA.options);
388 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
390 if(typeof NETDATA.options.current.setOptionCallback === 'function')
391 NETDATA.options.current.setOptionCallback();
397 NETDATA.getOption = function(key) {
398 return NETDATA.options.current[key.toString()];
401 // read settings from local storage
402 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
404 // always start with this option enabled.
405 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
407 NETDATA.resetOptions = function() {
408 for(var i in NETDATA.localStorage.default) {
409 var a = i.split('.');
411 if(a[0] === 'options') {
412 if(a[1] === 'setOptionCallback') continue;
413 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
414 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
416 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
418 else if(a[0] === 'chart_heights') {
419 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
420 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
426 // ----------------------------------------------------------------------------------------------------------------
428 if(NETDATA.options.debug.main_loop === true)
429 console.log('welcome to NETDATA');
431 NETDATA.onresize = function() {
432 NETDATA.options.last_resized = new Date().getTime();
436 NETDATA.onscroll = function() {
437 // console.log('onscroll');
439 NETDATA.options.last_page_scroll = new Date().getTime();
440 if(NETDATA.options.targets === null) return;
442 // when the user scrolls he sees that we have
443 // hidden all the not-visible charts
444 // using this little function we try to switch
445 // the charts back to visible quickly
446 var targets = NETDATA.options.targets;
447 var len = targets.length;
448 while(len--) targets[len].isVisible();
451 window.onresize = NETDATA.onresize;
452 window.onscroll = NETDATA.onscroll;
454 // ----------------------------------------------------------------------------------------------------------------
457 NETDATA.errorCodes = {
458 100: { message: "Cannot load chart library", alert: true },
459 101: { message: "Cannot load jQuery", alert: true },
460 402: { message: "Chart library not found", alert: false },
461 403: { message: "Chart library not enabled/is failed", alert: false },
462 404: { message: "Chart not found", alert: false }
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);
478 if(typeof netdataErrorCallback === 'function') {
479 ret = netdataErrorCallback('system', code, msg);
482 if(ret && NETDATA.errorCodes[code].alert)
483 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
486 NETDATA.errorReset = function() {
487 NETDATA.errorLast.code = 0;
488 NETDATA.errorLast.message = "You are doing fine!";
489 NETDATA.errorLast.datetime = 0;
492 // ----------------------------------------------------------------------------------------------------------------
495 // When multiple charts need the same chart, we avoid downloading it
496 // multiple times (and having it in browser memory multiple time)
497 // by using this registry.
499 // Every time we download a chart definition, we save it here with .add()
500 // Then we try to get it back with .get(). If that fails, we download it.
502 NETDATA.chartRegistry = {
505 fixid: function(id) {
506 return id.replace(/:/g, "_").replace(/\//g, "_");
509 add: function(host, id, data) {
510 host = this.fixid(host);
513 if(typeof this.charts[host] === 'undefined')
514 this.charts[host] = {};
516 //console.log('added ' + host + '/' + id);
517 this.charts[host][id] = data;
520 get: function(host, id) {
521 host = this.fixid(host);
524 if(typeof this.charts[host] === 'undefined')
527 if(typeof this.charts[host][id] === 'undefined')
530 //console.log('cached ' + host + '/' + id);
531 return this.charts[host][id];
534 downloadAll: function(host, callback) {
535 while(host.slice(-1) === '/')
536 host = host.substring(0, host.length - 1);
541 url: host + '/api/v1/charts',
542 crossDomain: NETDATA.options.crossDomainAjax,
546 .done(function(data) {
548 var h = NETDATA.chartRegistry.fixid(host);
549 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
550 self.charts[h] = data.charts;
552 if(typeof callback === 'function')
556 if(typeof callback === 'function')
562 // ----------------------------------------------------------------------------------------------------------------
563 // Global Pan and Zoom on charts
565 // Using this structure are synchronize all the charts, so that
566 // when you pan or zoom one, all others are automatically refreshed
567 // to the same timespan.
569 NETDATA.globalPanAndZoom = {
570 seq: 0, // timestamp ms
571 // every time a chart is panned or zoomed
572 // we set the timestamp here
573 // then we use it as a sequence number
574 // to find if other charts are syncronized
577 master: null, // the master chart (state), to which all others
580 force_before_ms: null, // the timespan to sync all other charts
581 force_after_ms: null,
584 setMaster: function(state, after, before) {
585 if(NETDATA.options.current.sync_pan_and_zoom === false)
588 if(this.master !== null && this.master !== state)
589 this.master.resetChart(true, true);
591 var now = new Date().getTime();
594 this.force_after_ms = after;
595 this.force_before_ms = before;
596 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
600 clearMaster: function() {
601 if(this.master !== null) {
602 var st = this.master;
609 this.force_after_ms = null;
610 this.force_before_ms = null;
611 NETDATA.options.auto_refresher_stop_until = 0;
614 // is the given state the master of the global
615 // pan and zoom sync?
616 isMaster: function(state) {
617 if(this.master === state) return true;
621 // are we currently have a global pan and zoom sync?
622 isActive: function() {
623 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
627 // check if a chart, other than the master
628 // needs to be refreshed, due to the global pan and zoom
629 shouldBeAutoRefreshed: function(state) {
630 if(this.master === null || this.seq === 0)
633 //if(state.needsRecreation())
636 if(state.tm.pan_and_zoom_seq === this.seq)
643 // ----------------------------------------------------------------------------------------------------------------
644 // dimensions selection
647 // move color assignment to dimensions, here
649 dimensionStatus = function(parent, label, name_div, value_div, color) {
650 this.enabled = false;
651 this.parent = parent;
653 this.name_div = null;
654 this.value_div = null;
655 this.color = NETDATA.themes.current.foreground;
657 if(parent.selected_count > parent.unselected_count)
658 this.selected = true;
660 this.selected = false;
662 this.setOptions(name_div, value_div, color);
665 dimensionStatus.prototype.invalidate = function() {
666 this.name_div = null;
667 this.value_div = null;
668 this.enabled = false;
671 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
674 if(this.name_div != name_div) {
675 this.name_div = name_div;
676 this.name_div.title = this.label;
677 this.name_div.style.color = this.color;
678 if(this.selected === false)
679 this.name_div.className = 'netdata-legend-name not-selected';
681 this.name_div.className = 'netdata-legend-name selected';
684 if(this.value_div != value_div) {
685 this.value_div = value_div;
686 this.value_div.title = this.label;
687 this.value_div.style.color = this.color;
688 if(this.selected === false)
689 this.value_div.className = 'netdata-legend-value not-selected';
691 this.value_div.className = 'netdata-legend-value selected';
698 dimensionStatus.prototype.setHandler = function() {
699 if(this.enabled === false) return;
703 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
704 this.name_div.onclick = this.value_div.onclick = function(e) {
706 if(ds.isSelected()) {
708 if(e.shiftKey === true || e.ctrlKey === true) {
709 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
712 if(ds.parent.countSelected() === 0)
713 ds.parent.selectAll();
716 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
717 if(ds.parent.countSelected() === 1) {
718 ds.parent.selectAll();
721 ds.parent.selectNone();
727 // this is not selected
728 if(e.shiftKey === true || e.ctrlKey === true) {
729 // control or shift key is pressed -> select this too
733 // no key is pressed -> select only this
734 ds.parent.selectNone();
739 ds.parent.state.redrawChart();
743 dimensionStatus.prototype.select = function() {
744 if(this.enabled === false) return;
746 this.name_div.className = 'netdata-legend-name selected';
747 this.value_div.className = 'netdata-legend-value selected';
748 this.selected = true;
751 dimensionStatus.prototype.unselect = function() {
752 if(this.enabled === false) return;
754 this.name_div.className = 'netdata-legend-name not-selected';
755 this.value_div.className = 'netdata-legend-value hidden';
756 this.selected = false;
759 dimensionStatus.prototype.isSelected = function() {
760 return(this.enabled === true && this.selected === true);
763 // ----------------------------------------------------------------------------------------------------------------
765 dimensionsVisibility = function(state) {
768 this.dimensions = {};
769 this.selected_count = 0;
770 this.unselected_count = 0;
773 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
774 if(typeof this.dimensions[label] === 'undefined') {
776 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
779 this.dimensions[label].setOptions(name_div, value_div, color);
781 return this.dimensions[label];
784 dimensionsVisibility.prototype.dimensionGet = function(label) {
785 return this.dimensions[label];
788 dimensionsVisibility.prototype.invalidateAll = function() {
789 for(var d in this.dimensions)
790 this.dimensions[d].invalidate();
793 dimensionsVisibility.prototype.selectAll = function() {
794 for(var d in this.dimensions)
795 this.dimensions[d].select();
798 dimensionsVisibility.prototype.countSelected = function() {
800 for(var d in this.dimensions)
801 if(this.dimensions[d].isSelected()) i++;
806 dimensionsVisibility.prototype.selectNone = function() {
807 for(var d in this.dimensions)
808 this.dimensions[d].unselect();
811 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
812 var ret = new Array();
813 this.selected_count = 0;
814 this.unselected_count = 0;
816 for(var i = 0, len = array.length; i < len ; i++) {
817 var ds = this.dimensions[array[i]];
818 if(typeof ds === 'undefined') {
819 // console.log(array[i] + ' is not found');
824 if(ds.isSelected()) {
826 this.selected_count++;
830 this.unselected_count++;
834 if(this.selected_count === 0 && this.unselected_count !== 0) {
836 return this.selected2BooleanArray(array);
843 // ----------------------------------------------------------------------------------------------------------------
844 // global selection sync
846 NETDATA.globalSelectionSync = {
853 if(this.state !== null)
854 this.state.globalSelectionSyncStop();
858 if(this.state !== null) {
859 this.state.globalSelectionSyncDelay();
864 // ----------------------------------------------------------------------------------------------------------------
865 // Our state object, where all per-chart values are stored
867 chartState = function(element) {
868 var self = $(element);
869 this.element = element;
872 // all private functions should use 'that', instead of 'this'
876 * show an error instead of the chart
878 var error = function(msg) {
881 if(typeof netdataErrorCallback === 'function') {
882 ret = netdataErrorCallback('chart', that.id, msg);
886 that.element.innerHTML = that.id + ': ' + msg;
887 that.enabled = false;
888 that.current = that.pan;
892 // GUID - a unique identifier for the chart
893 this.uuid = NETDATA.guid();
895 // string - the name of chart
896 this.id = self.data('netdata');
898 // string - the key for localStorage settings
899 this.settings_id = self.data('id') || null;
901 // the user given dimensions of the element
902 this.width = self.data('width') || NETDATA.chartDefaults.width;
903 this.height = self.data('height') || NETDATA.chartDefaults.height;
905 if(this.settings_id !== null) {
906 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
907 // this is the callback that will be called
908 // if and when the user resets all localStorage variables
911 resizeChartToHeight(height);
915 // string - the netdata server URL, without any path
916 this.host = self.data('host') || NETDATA.chartDefaults.host;
918 // make sure the host does not end with /
919 // all netdata API requests use absolute paths
920 while(this.host.slice(-1) === '/')
921 this.host = this.host.substring(0, this.host.length - 1);
923 // string - the grouping method requested by the user
924 this.method = self.data('method') || NETDATA.chartDefaults.method;
926 // the time-range requested by the user
927 this.after = self.data('after') || NETDATA.chartDefaults.after;
928 this.before = self.data('before') || NETDATA.chartDefaults.before;
930 // the pixels per point requested by the user
931 this.pixels_per_point = self.data('pixels-per-point') || 1;
932 this.points = self.data('points') || null;
934 // the dimensions requested by the user
935 this.dimensions = self.data('dimensions') || null;
937 // the chart library requested by the user
938 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
940 // object - the chart library used
945 this.colors_assigned = {};
946 this.colors_available = null;
948 // the element already created by the user
949 this.element_message = null;
951 // the element with the chart
952 this.element_chart = null;
954 // the element with the legend of the chart (if created by us)
955 this.element_legend = null;
956 this.element_legend_childs = {
966 this.chart_url = null; // string - the url to download chart info
967 this.chart = null; // object - the chart as downloaded from the server
969 this.title = self.data('title') || null; // the title of the chart
970 this.units = self.data('units') || null; // the units of the chart dimensions
971 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
973 this.validated = false; // boolean - has the chart been validated?
974 this.enabled = true; // boolean - is the chart enabled for refresh?
975 this.paused = false; // boolean - is the chart paused for any reason?
976 this.selected = false; // boolean - is the chart shown a selection?
977 this.debug = false; // boolean - console.log() debug info about this chart
979 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
980 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
981 this.requested_after = null; // milliseconds - the timestamp of the request after param
982 this.requested_before = null; // milliseconds - the timestamp of the request before param
983 this.requested_padding = null;
985 this.view_before = 0;
990 force_update_at: 0, // the timestamp to force the update at
991 force_before_ms: null,
997 force_update_at: 0, // the timestamp to force the update at
998 force_before_ms: null,
1004 force_update_at: 0, // the timestamp to force the update at
1005 force_before_ms: null,
1006 force_after_ms: null
1009 // this is a pointer to one of the sub-classes below
1011 this.current = this.auto;
1013 // check the requested library is available
1014 // we don't initialize it here - it will be initialized when
1015 // this chart will be first used
1016 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1017 NETDATA.error(402, that.library_name);
1018 error('chart library "' + that.library_name + '" is not found');
1021 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1022 NETDATA.error(403, that.library_name);
1023 error('chart library "' + that.library_name + '" is not enabled');
1027 that.library = NETDATA.chartLibraries[that.library_name];
1029 // milliseconds - the time the last refresh took
1030 this.refresh_dt_ms = 0;
1032 // if we need to report the rendering speed
1033 // find the element that needs to be updated
1034 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1036 if(refresh_dt_element_name !== null)
1037 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1039 this.refresh_dt_element = null;
1041 this.dimensions_visibility = new dimensionsVisibility(this);
1043 this._updating = false;
1045 // ============================================================================================================
1046 // PRIVATE FUNCTIONS
1048 var createDOM = function() {
1049 if(that.enabled === false) return;
1051 if(that.element_message !== null) that.element_message.innerHTML = '';
1052 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1053 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1055 that.element.innerHTML = '';
1057 that.element_message = document.createElement('div');
1058 that.element_message.className = ' netdata-message hidden';
1059 that.element.appendChild(that.element_message);
1061 that.element_chart = document.createElement('div');
1062 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1063 that.element.appendChild(that.element_chart);
1065 if(that.hasLegend() === true) {
1066 that.element.className = "netdata-container-with-legend";
1067 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1069 that.element_legend = document.createElement('div');
1070 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1071 that.element.appendChild(that.element_legend);
1074 that.element.className = "netdata-container";
1075 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1077 that.element_legend = null;
1079 that.element_legend_childs.series = null;
1081 if(typeof(that.width) === 'string')
1082 $(that.element).css('width', that.width);
1083 else if(typeof(that.width) === 'number')
1084 $(that.element).css('width', that.width + 'px');
1086 if(typeof(that.library.aspect_ratio) === 'undefined') {
1087 if(typeof(that.height) === 'string')
1088 $(that.element).css('height', that.height);
1089 else if(typeof(that.height) === 'number')
1090 $(that.element).css('height', that.height + 'px');
1093 var w = that.element.offsetWidth;
1094 if(w === null || w === 0) {
1095 // the div is hidden
1096 // this is resize the chart when next viewed
1097 that.tm.last_resized = 0;
1100 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1103 if(NETDATA.chartDefaults.min_width !== null)
1104 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1106 that.tm.last_dom_created = new Date().getTime();
1112 * initialize state variables
1113 * destroy all (possibly) created state elements
1114 * create the basic DOM for a chart
1116 var init = function() {
1117 if(that.enabled === false) return;
1119 that.paused = false;
1120 that.selected = false;
1122 that.chart_created = false; // boolean - is the library.create() been called?
1123 that.updates_counter = 0; // numeric - the number of refreshes made so far
1124 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1125 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1128 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1129 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1130 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1132 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1133 last_updated: 0, // the timestamp the chart last updated with data
1134 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1136 // Used with NETDATA.globalPanAndZoom.seq
1137 last_visible_check: 0, // the time we last checked if it is visible
1138 last_resized: 0, // the time the chart was resized
1139 last_hidden: 0, // the time the chart was hidden
1140 last_unhidden: 0, // the time the chart was unhidden
1141 last_autorefreshed: 0 // the time the chart was last refreshed
1144 that.data = null; // the last data as downloaded from the netdata server
1145 that.data_url = 'invalid://'; // string - the last url used to update the chart
1146 that.data_points = 0; // number - the number of points returned from netdata
1147 that.data_after = 0; // milliseconds - the first timestamp of the data
1148 that.data_before = 0; // milliseconds - the last timestamp of the data
1149 that.data_update_every = 0; // milliseconds - the frequency to update the data
1151 that.tm.last_initialized = new Date().getTime();
1154 that.setMode('auto');
1157 var maxMessageFontSize = function() {
1158 // normally we want a font size, as tall as the element
1159 var h = that.element_message.clientHeight;
1161 // but give it some air, 20% let's say, or 5 pixels min
1162 var lost = Math.max(h * 0.2, 5);
1165 // center the text, verically
1166 var paddingTop = (lost - 5) / 2;
1168 // but check the width too
1169 // it should fit 10 characters in it
1170 var w = that.element_message.clientWidth / 10;
1172 paddingTop += (h - w) / 2;
1176 // and don't make it too huge
1177 // 5% of the screen size is good
1178 if(h > screen.height / 20) {
1179 paddingTop += (h - (screen.height / 20)) / 2;
1180 h = screen.height / 20;
1184 that.element_message.style.fontSize = h.toString() + 'px';
1185 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1188 var showMessage = function(msg) {
1189 that.element_message.className = 'netdata-message';
1190 that.element_message.innerHTML = msg;
1191 that.element_message.style.fontSize = 'x-small';
1192 that.element_message.style.paddingTop = '0px';
1193 that.___messageHidden___ = undefined;
1196 var showMessageIcon = function(icon) {
1197 that.element_message.innerHTML = icon;
1198 that.element_message.className = 'netdata-message icon';
1199 maxMessageFontSize();
1200 that.___messageHidden___ = undefined;
1203 var hideMessage = function() {
1204 if(typeof that.___messageHidden___ === 'undefined') {
1205 that.___messageHidden___ = true;
1206 that.element_message.className = 'netdata-message hidden';
1210 var showRendering = function() {
1212 if(that.chart !== null) {
1213 if(that.chart.chart_type === 'line')
1214 icon = '<i class="fa fa-line-chart"></i>';
1216 icon = '<i class="fa fa-area-chart"></i>';
1219 icon = '<i class="fa fa-area-chart"></i>';
1221 showMessageIcon(icon + ' netdata');
1224 var showLoading = function() {
1225 if(that.chart_created === false) {
1226 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1232 var isHidden = function() {
1233 if(typeof that.___chartIsHidden___ !== 'undefined')
1239 // hide the chart, when it is not visible - called from isVisible()
1240 var hideChart = function() {
1241 // hide it, if it is not already hidden
1242 if(isHidden() === true) return;
1244 if(that.chart_created === true) {
1245 // we should destroy it
1246 if(NETDATA.options.current.destroy_on_hide === true) {
1251 that.element_chart.style.display = 'none';
1252 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1253 that.tm.last_hidden = new Date().getTime();
1257 that.___chartIsHidden___ = true;
1260 // unhide the chart, when it is visible - called from isVisible()
1261 var unhideChart = function() {
1262 if(isHidden() === false) return;
1264 that.___chartIsHidden___ = undefined;
1265 that.updates_since_last_unhide = 0;
1267 if(that.chart_created === false) {
1268 // we need to re-initialize it, to show our background
1269 // logo in bootstrap tabs, until the chart loads
1273 that.tm.last_unhidden = new Date().getTime();
1274 that.element_chart.style.display = '';
1275 if(that.element_legend !== null) that.element_legend.style.display = '';
1281 var canBeRendered = function() {
1282 if(isHidden() === true || that.isVisible(true) === false)
1288 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1289 var callChartLibraryUpdateSafely = function(data) {
1292 if(canBeRendered() === false)
1295 if(NETDATA.options.debug.chart_errors === true)
1296 status = that.library.update(that, data);
1299 status = that.library.update(that, data);
1306 if(status === false) {
1307 error('chart failed to be updated as ' + that.library_name);
1314 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1315 var callChartLibraryCreateSafely = function(data) {
1318 if(canBeRendered() === false)
1321 if(NETDATA.options.debug.chart_errors === true)
1322 status = that.library.create(that, data);
1325 status = that.library.create(that, data);
1332 if(status === false) {
1333 error('chart failed to be created as ' + that.library_name);
1337 that.chart_created = true;
1338 that.updates_since_last_creation = 0;
1342 // ----------------------------------------------------------------------------------------------------------------
1345 // resizeChart() - private
1346 // to be called just before the chart library to make sure that
1347 // a properly sized dom is available
1348 var resizeChart = function() {
1349 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1350 if(that.chart_created === false) return;
1352 if(that.needsRecreation()) {
1355 else if(typeof that.library.resize === 'function') {
1356 that.library.resize(that);
1358 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1359 $(that.element_legend_childs.nano).nanoScroller();
1361 maxMessageFontSize();
1364 that.tm.last_resized = new Date().getTime();
1368 // this is the actual chart resize algorithm
1370 // - resize the entire container
1371 // - update the internal states
1372 // - resize the chart as the div changes height
1373 // - update the scrollbar of the legend
1374 var resizeChartToHeight = function(h) {
1376 that.element.style.height = h;
1378 if(that.settings_id !== null)
1379 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1381 var now = new Date().getTime();
1382 NETDATA.options.last_page_scroll = now;
1383 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1386 that.tm.last_resized = 0;
1390 this.resizeHandler = function(e) {
1393 if(typeof this.event_resize === 'undefined'
1394 || this.event_resize.chart_original_w === 'undefined'
1395 || this.event_resize.chart_original_h === 'undefined')
1396 this.event_resize = {
1397 chart_original_w: this.element.clientWidth,
1398 chart_original_h: this.element.clientHeight,
1402 if(e.type === 'touchstart') {
1403 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1404 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1407 this.event_resize.mouse_start_x = e.clientX;
1408 this.event_resize.mouse_start_y = e.clientY;
1411 this.event_resize.chart_start_w = this.element.clientWidth;
1412 this.event_resize.chart_start_h = this.element.clientHeight;
1413 this.event_resize.chart_last_w = this.element.clientWidth;
1414 this.event_resize.chart_last_h = this.element.clientHeight;
1416 var now = new Date().getTime();
1417 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1418 // double click / double tap event
1420 // the optimal height of the chart
1421 // showing the entire legend
1422 var optimal = this.event_resize.chart_last_h
1423 + this.element_legend_childs.content.scrollHeight
1424 - this.element_legend_childs.content.clientHeight;
1426 // if we are not optimal, be optimal
1427 if(this.event_resize.chart_last_h != optimal)
1428 resizeChartToHeight(optimal.toString() + 'px');
1430 // else if we do not have the original height
1431 // reset to the original height
1432 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1433 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1436 this.event_resize.last = now;
1438 // process movement event
1439 document.onmousemove =
1440 document.ontouchmove =
1441 this.element_legend_childs.resize_handler.onmousemove =
1442 this.element_legend_childs.resize_handler.ontouchmove =
1447 case 'mousemove': y = e.clientY; break;
1448 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1452 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1454 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1455 resizeChartToHeight(newH.toString() + 'px');
1456 that.event_resize.chart_last_h = newH;
1461 // process end event
1462 document.onmouseup =
1463 document.ontouchend =
1464 this.element_legend_childs.resize_handler.onmouseup =
1465 this.element_legend_childs.resize_handler.ontouchend =
1467 // remove all the hooks
1468 document.onmouseup =
1469 document.onmousemove =
1470 document.ontouchmove =
1471 document.ontouchend =
1472 that.element_legend_childs.resize_handler.onmousemove =
1473 that.element_legend_childs.resize_handler.ontouchmove =
1474 that.element_legend_childs.resize_handler.onmouseout =
1475 that.element_legend_childs.resize_handler.onmouseup =
1476 that.element_legend_childs.resize_handler.ontouchend =
1479 // allow auto-refreshes
1480 NETDATA.options.auto_refresher_stop_until = 0;
1486 var noDataToShow = function() {
1487 showMessageIcon('<i class="fa fa-warning"></i> empty');
1488 that.legendUpdateDOM();
1489 that.tm.last_autorefreshed = new Date().getTime();
1490 // that.data_update_every = 30 * 1000;
1491 //that.element_chart.style.display = 'none';
1492 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1493 //that.___chartIsHidden___ = true;
1496 // ============================================================================================================
1499 this.error = function(msg) {
1503 this.setMode = function(m) {
1504 if(this.current !== null && this.current.name === m) return;
1507 this.current = this.auto;
1508 else if(m === 'pan')
1509 this.current = this.pan;
1510 else if(m === 'zoom')
1511 this.current = this.zoom;
1513 this.current = this.auto;
1515 this.current.force_update_at = 0;
1516 this.current.force_before_ms = null;
1517 this.current.force_after_ms = null;
1519 this.tm.last_mode_switch = new Date().getTime();
1522 // ----------------------------------------------------------------------------------------------------------------
1523 // global selection sync
1525 // prevent to global selection sync for some time
1526 this.globalSelectionSyncDelay = function(ms) {
1527 if(NETDATA.options.current.sync_selection === false)
1530 if(typeof ms === 'number')
1531 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1533 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1536 // can we globally apply selection sync?
1537 this.globalSelectionSyncAbility = function() {
1538 if(NETDATA.options.current.sync_selection === false)
1541 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1547 this.globalSelectionSyncIsMaster = function() {
1548 if(NETDATA.globalSelectionSync.state === this)
1554 // this chart is the master of the global selection sync
1555 this.globalSelectionSyncBeMaster = function() {
1557 if(this.globalSelectionSyncIsMaster()) {
1558 if(this.debug === true)
1559 this.log('sync: I am the master already.');
1564 if(NETDATA.globalSelectionSync.state) {
1565 if(this.debug === true)
1566 this.log('sync: I am not the sync master. Resetting global sync.');
1568 this.globalSelectionSyncStop();
1571 // become the master
1572 if(this.debug === true)
1573 this.log('sync: becoming sync master.');
1575 this.selected = true;
1576 NETDATA.globalSelectionSync.state = this;
1578 // find the all slaves
1579 var targets = NETDATA.options.targets;
1580 var len = targets.length;
1585 if(this.debug === true)
1586 st.log('sync: not adding me to sync');
1588 else if(st.globalSelectionSyncIsEligible()) {
1589 if(this.debug === true)
1590 st.log('sync: adding to sync as slave');
1592 st.globalSelectionSyncBeSlave();
1596 // this.globalSelectionSyncDelay(100);
1599 // can the chart participate to the global selection sync as a slave?
1600 this.globalSelectionSyncIsEligible = function() {
1601 if(this.enabled === true
1602 && this.library !== null
1603 && typeof this.library.setSelection === 'function'
1604 && this.isVisible() === true
1605 && this.chart_created === true)
1611 // this chart becomes a slave of the global selection sync
1612 this.globalSelectionSyncBeSlave = function() {
1613 if(NETDATA.globalSelectionSync.state !== this)
1614 NETDATA.globalSelectionSync.slaves.push(this);
1617 // sync all the visible charts to the given time
1618 // this is to be called from the chart libraries
1619 this.globalSelectionSync = function(t) {
1620 if(this.globalSelectionSyncAbility() === false) {
1621 if(this.debug === true)
1622 this.log('sync: cannot sync (yet?).');
1627 if(this.globalSelectionSyncIsMaster() === false) {
1628 if(this.debug === true)
1629 this.log('sync: trying to be sync master.');
1631 this.globalSelectionSyncBeMaster();
1633 if(this.globalSelectionSyncAbility() === false) {
1634 if(this.debug === true)
1635 this.log('sync: cannot sync (yet?).');
1641 NETDATA.globalSelectionSync.last_t = t;
1642 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1647 // stop syncing all charts to the given time
1648 this.globalSelectionSyncStop = function() {
1649 if(NETDATA.globalSelectionSync.slaves.length) {
1650 if(this.debug === true)
1651 this.log('sync: cleaning up...');
1653 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1655 if(that.debug === true)
1656 st.log('sync: not adding me to sync stop');
1659 if(that.debug === true)
1660 st.log('sync: removed slave from sync');
1662 st.clearSelection();
1666 NETDATA.globalSelectionSync.last_t = 0;
1667 NETDATA.globalSelectionSync.slaves = [];
1668 NETDATA.globalSelectionSync.state = null;
1671 this.clearSelection();
1674 this.setSelection = function(t) {
1675 if(typeof this.library.setSelection === 'function') {
1676 if(this.library.setSelection(this, t) === true)
1677 this.selected = true;
1679 this.selected = false;
1681 else this.selected = true;
1683 if(this.selected === true && this.debug === true)
1684 this.log('selection set to ' + t.toString());
1686 return this.selected;
1689 this.clearSelection = function() {
1690 if(this.selected === true) {
1691 if(typeof this.library.clearSelection === 'function') {
1692 if(this.library.clearSelection(this) === true)
1693 this.selected = false;
1695 this.selected = true;
1697 else this.selected = false;
1699 if(this.selected === false && this.debug === true)
1700 this.log('selection cleared');
1705 return this.selected;
1708 // find if a timestamp (ms) is shown in the current chart
1709 this.timeIsVisible = function(t) {
1710 if(t >= this.data_after && t <= this.data_before)
1715 this.calculateRowForTime = function(t) {
1716 if(this.timeIsVisible(t) === false) return -1;
1717 return Math.floor((t - this.data_after) / this.data_update_every);
1720 // ----------------------------------------------------------------------------------------------------------------
1723 this.log = function(msg) {
1724 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1727 this.pauseChart = function() {
1728 if(this.paused === false) {
1729 if(this.debug === true)
1730 this.log('pauseChart()');
1736 this.unpauseChart = function() {
1737 if(this.paused === true) {
1738 if(this.debug === true)
1739 this.log('unpauseChart()');
1741 this.paused = false;
1745 this.resetChart = function(dont_clear_master, dont_update) {
1746 if(this.debug === true)
1747 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1749 if(typeof dont_clear_master === 'undefined')
1750 dont_clear_master = false;
1752 if(typeof dont_update === 'undefined')
1753 dont_update = false;
1755 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1756 if(this.debug === true)
1757 this.log('resetChart() diverting to clearMaster().');
1758 // this will call us back with master === true
1759 NETDATA.globalPanAndZoom.clearMaster();
1763 this.clearSelection();
1765 this.tm.pan_and_zoom_seq = 0;
1767 this.setMode('auto');
1768 this.current.force_update_at = 0;
1769 this.current.force_before_ms = null;
1770 this.current.force_after_ms = null;
1771 this.tm.last_autorefreshed = 0;
1772 this.paused = false;
1773 this.selected = false;
1774 this.enabled = true;
1775 // this.debug = false;
1777 // do not update the chart here
1778 // or the chart will flip-flop when it is the master
1779 // of a selection sync and another chart becomes
1782 if(dont_update !== true && this.isVisible() === true) {
1787 this.updateChartPanOrZoom = function(after, before) {
1788 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1791 if(this.debug === true)
1794 if(before < after) {
1795 if(this.debug === true)
1796 this.log(logme + 'flipped parameters, rejecting it.');
1801 if(typeof this.fixed_min_duration === 'undefined')
1802 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1804 var min_duration = this.fixed_min_duration;
1805 var current_duration = Math.round(this.view_before - this.view_after);
1807 // round the numbers
1808 after = Math.round(after);
1809 before = Math.round(before);
1811 // align them to update_every
1812 // stretching them further away
1813 after -= after % this.data_update_every;
1814 before += this.data_update_every - (before % this.data_update_every);
1816 // the final wanted duration
1817 var wanted_duration = before - after;
1819 // to allow panning, accept just a point below our minimum
1820 if((current_duration - this.data_update_every) < min_duration)
1821 min_duration = current_duration - this.data_update_every;
1823 // we do it, but we adjust to minimum size and return false
1824 // when the wanted size is below the current and the minimum
1826 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1827 if(this.debug === true)
1828 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1830 min_duration = this.fixed_min_duration;
1832 var dt = (min_duration - wanted_duration) / 2;
1835 wanted_duration = before - after;
1839 var tolerance = this.data_update_every * 2;
1840 var movement = Math.abs(before - this.view_before);
1842 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1843 if(this.debug === true)
1844 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);
1848 if(this.current.name === 'auto') {
1849 this.log(logme + 'caller called me with mode: ' + this.current.name);
1850 this.setMode('pan');
1853 if(this.debug === true)
1854 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);
1856 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1857 this.current.force_after_ms = after;
1858 this.current.force_before_ms = before;
1859 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1863 this.legendFormatValue = function(value) {
1864 if(value === null || value === 'undefined') return '-';
1865 if(typeof value !== 'number') return value;
1867 var abs = Math.abs(value);
1868 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1869 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1870 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1871 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1872 return (Math.round(value * 10000) / 10000).toLocaleString();
1875 this.legendSetLabelValue = function(label, value) {
1876 var series = this.element_legend_childs.series[label];
1877 if(typeof series === 'undefined') return;
1878 if(series.value === null && series.user === null) return;
1880 // if the value has not changed, skip DOM update
1881 //if(series.last === value) return;
1884 if(typeof value === 'number') {
1885 var v = Math.abs(value);
1886 s = r = this.legendFormatValue(value);
1888 if(typeof series.last === 'number') {
1889 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1890 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1891 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1893 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1898 series.last = value;
1901 if(series.value !== null) series.value.innerHTML = s;
1902 if(series.user !== null) series.user.innerHTML = r;
1905 this.legendSetDate = function(ms) {
1906 if(typeof ms !== 'number') {
1907 this.legendShowUndefined();
1911 var d = new Date(ms);
1913 if(this.element_legend_childs.title_date)
1914 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1916 if(this.element_legend_childs.title_time)
1917 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1919 if(this.element_legend_childs.title_units)
1920 this.element_legend_childs.title_units.innerHTML = this.units;
1923 this.legendShowUndefined = function() {
1924 if(this.element_legend_childs.title_date)
1925 this.element_legend_childs.title_date.innerHTML = ' ';
1927 if(this.element_legend_childs.title_time)
1928 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1930 if(this.element_legend_childs.title_units)
1931 this.element_legend_childs.title_units.innerHTML = ' ';
1933 if(this.data && this.element_legend_childs.series !== null) {
1934 var labels = this.data.dimension_names;
1935 var i = labels.length;
1937 var label = labels[i];
1939 if(typeof label === 'undefined') continue;
1940 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1941 this.legendSetLabelValue(label, null);
1946 this.legendShowLatestValues = function() {
1947 if(this.chart === null) return;
1948 if(this.selected) return;
1950 if(this.data === null || this.element_legend_childs.series === null) {
1951 this.legendShowUndefined();
1955 var show_undefined = true;
1956 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1957 show_undefined = false;
1959 if(show_undefined) {
1960 this.legendShowUndefined();
1964 this.legendSetDate(this.view_before);
1966 var labels = this.data.dimension_names;
1967 var i = labels.length;
1969 var label = labels[i];
1971 if(typeof label === 'undefined') continue;
1972 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1975 this.legendSetLabelValue(label, null);
1977 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1981 this.legendReset = function() {
1982 this.legendShowLatestValues();
1985 // this should be called just ONCE per dimension per chart
1986 this._chartDimensionColor = function(label) {
1987 if(this.colors === null) this.chartColors();
1989 if(typeof this.colors_assigned[label] === 'undefined') {
1990 if(this.colors_available.length === 0) {
1991 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1992 this.colors_available.push(NETDATA.themes.current.colors[i]);
1995 this.colors_assigned[label] = this.colors_available.shift();
1997 if(this.debug === true)
1998 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2001 if(this.debug === true)
2002 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2005 this.colors.push(this.colors_assigned[label]);
2006 return this.colors_assigned[label];
2009 this.chartColors = function() {
2010 if(this.colors !== null) return this.colors;
2012 this.colors = new Array();
2013 this.colors_available = new Array();
2016 var c = $(this.element).data('colors');
2017 // this.log('read colors: ' + c);
2018 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2019 if(typeof c !== 'string') {
2020 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2027 for(i = 0, len = c.length; i < len ; i++) {
2029 this.colors_available.push(c[i]);
2030 // this.log('adding color: ' + c[i]);
2036 // push all the standard colors too
2037 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2038 this.colors_available.push(NETDATA.themes.current.colors[i]);
2043 this.legendUpdateDOM = function() {
2046 // check that the legend DOM is up to date for the downloaded dimensions
2047 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2048 // this.log('the legend does not have any series - requesting legend update');
2051 else if(this.data === null) {
2052 // this.log('the chart does not have any data - requesting legend update');
2055 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2059 var labels = this.data.dimension_names.toString();
2060 if(labels !== this.element_legend_childs.series.labels_key) {
2063 if(this.debug === true)
2064 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2068 if(needed === false) {
2069 // make sure colors available
2072 // do we have to update the current values?
2073 // we do this, only when the visible chart is current
2074 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2075 if(this.debug === true)
2076 this.log('chart is in latest position... updating values on legend...');
2078 //var labels = this.data.dimension_names;
2079 //var i = labels.length;
2081 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2085 if(this.colors === null) {
2086 // this is the first time we update the chart
2087 // let's assign colors to all dimensions
2088 if(this.library.track_colors() === true)
2089 for(var dim in this.chart.dimensions)
2090 this._chartDimensionColor(this.chart.dimensions[dim].name);
2092 // we will re-generate the colors for the chart
2093 // based on the selected dimensions
2096 if(this.debug === true)
2097 this.log('updating Legend DOM');
2099 // mark all dimensions as invalid
2100 this.dimensions_visibility.invalidateAll();
2102 var genLabel = function(state, parent, name, count) {
2103 var color = state._chartDimensionColor(name);
2105 var user_element = null;
2106 var user_id = self.data('show-value-of-' + name + '-at') || null;
2107 if(user_id !== null) {
2108 user_element = document.getElementById(user_id) || null;
2109 if(user_element === null)
2110 state.log('Cannot find element with id: ' + user_id);
2113 state.element_legend_childs.series[name] = {
2114 name: document.createElement('span'),
2115 value: document.createElement('span'),
2120 var label = state.element_legend_childs.series[name];
2122 // create the dimension visibility tracking for this label
2123 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2125 var rgb = NETDATA.colorHex2Rgb(color);
2126 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2127 + state.chart.chart_type
2128 + '" style="background-color: '
2129 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2130 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2132 var text = document.createTextNode(' ' + name);
2133 label.name.appendChild(text);
2136 parent.appendChild(document.createElement('br'));
2138 parent.appendChild(label.name);
2139 parent.appendChild(label.value);
2142 var content = document.createElement('div');
2144 if(this.hasLegend()) {
2145 this.element_legend_childs = {
2147 resize_handler: document.createElement('div'),
2148 toolbox: document.createElement('div'),
2149 toolbox_left: document.createElement('div'),
2150 toolbox_right: document.createElement('div'),
2151 toolbox_reset: document.createElement('div'),
2152 toolbox_zoomin: document.createElement('div'),
2153 toolbox_zoomout: document.createElement('div'),
2154 toolbox_volume: document.createElement('div'),
2155 title_date: document.createElement('span'),
2156 title_time: document.createElement('span'),
2157 title_units: document.createElement('span'),
2158 nano: document.createElement('div'),
2160 paneClass: 'netdata-legend-series-pane',
2161 sliderClass: 'netdata-legend-series-slider',
2162 contentClass: 'netdata-legend-series-content',
2163 enabledClass: '__enabled',
2164 flashedClass: '__flashed',
2165 activeClass: '__active',
2167 alwaysVisible: true,
2173 this.element_legend.innerHTML = '';
2175 if(this.library.toolboxPanAndZoom !== null) {
2177 function get_pan_and_zoom_step(event) {
2179 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2181 else if (event.shiftKey)
2182 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2184 else if (event.altKey)
2185 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2188 return NETDATA.options.current.pan_and_zoom_factor;
2191 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2192 this.element.appendChild(this.element_legend_childs.toolbox);
2194 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2195 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2196 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2197 this.element_legend_childs.toolbox_left.onclick = function(e) {
2200 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2201 var before = that.view_before - step;
2202 var after = that.view_after - step;
2203 if(after >= that.netdata_first)
2204 that.library.toolboxPanAndZoom(that, after, before);
2206 if(NETDATA.options.current.show_help === true)
2207 $(this.element_legend_childs.toolbox_left).popover({
2212 placement: 'bottom',
2213 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2215 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>'
2219 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2220 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2221 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2222 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2224 NETDATA.resetAllCharts(that);
2226 if(NETDATA.options.current.show_help === true)
2227 $(this.element_legend_childs.toolbox_reset).popover({
2232 placement: 'bottom',
2233 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2234 title: 'Chart Reset',
2235 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>'
2238 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2239 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2240 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2241 this.element_legend_childs.toolbox_right.onclick = function(e) {
2243 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2244 var before = that.view_before + step;
2245 var after = that.view_after + step;
2246 if(before <= that.netdata_last)
2247 that.library.toolboxPanAndZoom(that, after, before);
2249 if(NETDATA.options.current.show_help === true)
2250 $(this.element_legend_childs.toolbox_right).popover({
2255 placement: 'bottom',
2256 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2258 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>'
2262 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2263 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2264 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2265 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2267 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2268 var before = that.view_before - dt;
2269 var after = that.view_after + dt;
2270 that.library.toolboxPanAndZoom(that, after, before);
2272 if(NETDATA.options.current.show_help === true)
2273 $(this.element_legend_childs.toolbox_zoomin).popover({
2278 placement: 'bottom',
2279 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2280 title: 'Chart Zoom In',
2281 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>'
2284 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2285 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2286 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2287 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2289 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);
2290 var before = that.view_before + dt;
2291 var after = that.view_after - dt;
2293 that.library.toolboxPanAndZoom(that, after, before);
2295 if(NETDATA.options.current.show_help === true)
2296 $(this.element_legend_childs.toolbox_zoomout).popover({
2301 placement: 'bottom',
2302 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2303 title: 'Chart Zoom Out',
2304 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>'
2307 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2308 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2309 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2310 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2311 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2312 //e.preventDefault();
2313 //alert('clicked toolbox_volume on ' + that.id);
2317 this.element_legend_childs.toolbox = null;
2318 this.element_legend_childs.toolbox_left = null;
2319 this.element_legend_childs.toolbox_reset = null;
2320 this.element_legend_childs.toolbox_right = null;
2321 this.element_legend_childs.toolbox_zoomin = null;
2322 this.element_legend_childs.toolbox_zoomout = null;
2323 this.element_legend_childs.toolbox_volume = null;
2326 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2327 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2328 this.element.appendChild(this.element_legend_childs.resize_handler);
2329 if(NETDATA.options.current.show_help === true)
2330 $(this.element_legend_childs.resize_handler).popover({
2335 placement: 'bottom',
2336 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2337 title: 'Chart Resize',
2338 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>'
2342 this.element_legend_childs.resize_handler.onmousedown =
2344 that.resizeHandler(e);
2348 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2349 that.resizeHandler(e);
2352 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2353 this.element_legend.appendChild(this.element_legend_childs.title_date);
2355 this.element_legend.appendChild(document.createElement('br'));
2357 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2358 this.element_legend.appendChild(this.element_legend_childs.title_time);
2360 this.element_legend.appendChild(document.createElement('br'));
2362 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2363 this.element_legend.appendChild(this.element_legend_childs.title_units);
2365 this.element_legend.appendChild(document.createElement('br'));
2367 this.element_legend_childs.nano.className = 'netdata-legend-series';
2368 this.element_legend.appendChild(this.element_legend_childs.nano);
2370 content.className = 'netdata-legend-series-content';
2371 this.element_legend_childs.nano.appendChild(content);
2373 if(NETDATA.options.current.show_help === true)
2374 $(content).popover({
2379 placement: 'bottom',
2380 title: 'Chart Legend',
2381 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2382 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>'
2386 this.element_legend_childs = {
2388 resize_handler: null,
2391 toolbox_right: null,
2392 toolbox_reset: null,
2393 toolbox_zoomin: null,
2394 toolbox_zoomout: null,
2395 toolbox_volume: null,
2406 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2407 if(this.debug === true)
2408 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2410 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2411 genLabel(this, content, this.data.dimension_names[i], i);
2415 var tmp = new Array();
2416 for(var dim in this.chart.dimensions) {
2417 tmp.push(this.chart.dimensions[dim].name);
2418 genLabel(this, content, this.chart.dimensions[dim].name, i);
2420 this.element_legend_childs.series.labels_key = tmp.toString();
2421 if(this.debug === true)
2422 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2425 // create a hidden div to be used for hidding
2426 // the original legend of the chart library
2427 var el = document.createElement('div');
2428 if(this.element_legend !== null)
2429 this.element_legend.appendChild(el);
2430 el.style.display = 'none';
2432 this.element_legend_childs.hidden = document.createElement('div');
2433 el.appendChild(this.element_legend_childs.hidden);
2435 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2436 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2438 this.legendShowLatestValues();
2441 this.hasLegend = function() {
2442 if(typeof this.___hasLegendCache___ !== 'undefined')
2443 return this.___hasLegendCache___;
2446 if(this.library && this.library.legend(this) === 'right-side') {
2447 var legend = $(this.element).data('legend') || 'yes';
2448 if(legend === 'yes') leg = true;
2451 this.___hasLegendCache___ = leg;
2455 this.legendWidth = function() {
2456 return (this.hasLegend())?140:0;
2459 this.legendHeight = function() {
2460 return $(this.element).height();
2463 this.chartWidth = function() {
2464 return $(this.element).width() - this.legendWidth();
2467 this.chartHeight = function() {
2468 return $(this.element).height();
2471 this.chartPixelsPerPoint = function() {
2472 // force an options provided detail
2473 var px = this.pixels_per_point;
2475 if(this.library && px < this.library.pixels_per_point(this))
2476 px = this.library.pixels_per_point(this);
2478 if(px < NETDATA.options.current.pixels_per_point)
2479 px = NETDATA.options.current.pixels_per_point;
2484 this.needsRecreation = function() {
2486 this.chart_created === true
2488 && this.library.autoresize() === false
2489 && this.tm.last_resized < NETDATA.options.last_resized
2493 this.chartURL = function() {
2494 var after, before, points_multiplier = 1;
2495 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2496 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2498 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2499 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2500 this.view_after = after * 1000;
2501 this.view_before = before * 1000;
2503 this.requested_padding = null;
2504 points_multiplier = 1;
2506 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2507 this.tm.pan_and_zoom_seq = 0;
2509 before = Math.round(this.current.force_before_ms / 1000);
2510 after = Math.round(this.current.force_after_ms / 1000);
2511 this.view_after = after * 1000;
2512 this.view_before = before * 1000;
2514 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2515 this.requested_padding = Math.round((before - after) / 2);
2516 after -= this.requested_padding;
2517 before += this.requested_padding;
2518 this.requested_padding *= 1000;
2519 points_multiplier = 2;
2522 this.current.force_before_ms = null;
2523 this.current.force_after_ms = null;
2526 this.tm.pan_and_zoom_seq = 0;
2528 before = this.before;
2530 this.view_after = after * 1000;
2531 this.view_before = before * 1000;
2533 this.requested_padding = null;
2534 points_multiplier = 1;
2537 this.requested_after = after * 1000;
2538 this.requested_before = before * 1000;
2540 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2542 // build the data URL
2543 this.data_url = this.host + this.chart.data_url;
2544 this.data_url += "&format=" + this.library.format();
2545 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2546 this.data_url += "&group=" + this.method;
2547 this.data_url += "&options=" + this.library.options(this);
2548 this.data_url += '|jsonwrap';
2550 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2551 this.data_url += '|nonzero';
2553 if(this.append_options !== null)
2554 this.data_url += '|' + this.append_options.toString();
2557 this.data_url += "&after=" + after.toString();
2560 this.data_url += "&before=" + before.toString();
2563 this.data_url += "&dimensions=" + this.dimensions;
2565 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2566 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2569 this.redrawChart = function() {
2570 if(this.data !== null)
2571 this.updateChartWithData(this.data);
2574 this.updateChartWithData = function(data) {
2575 if(this.debug === true)
2576 this.log('updateChartWithData() called.');
2578 this._updating = false;
2580 // this may force the chart to be re-created
2584 this.updates_counter++;
2585 this.updates_since_last_unhide++;
2586 this.updates_since_last_creation++;
2588 var started = new Date().getTime();
2590 // if the result is JSON, find the latest update-every
2591 this.data_update_every = data.view_update_every * 1000;
2592 this.data_after = data.after * 1000;
2593 this.data_before = data.before * 1000;
2594 this.netdata_first = data.first_entry * 1000;
2595 this.netdata_last = data.last_entry * 1000;
2596 this.data_points = data.points;
2599 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2600 if(this.view_after < this.data_after) {
2601 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2602 this.view_after = this.data_after;
2605 if(this.view_before > this.data_before) {
2606 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2607 this.view_before = this.data_before;
2611 this.view_after = this.data_after;
2612 this.view_before = this.data_before;
2615 if(this.debug === true) {
2616 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2618 if(this.current.force_after_ms)
2619 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2621 this.log('STATUS: forced : unset');
2623 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2624 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2625 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2626 this.log('STATUS: points : ' + (this.data_points).toString());
2629 if(this.data_points === 0) {
2634 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2635 if(this.debug === true)
2636 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2638 this.chart_created = false;
2641 // check and update the legend
2642 this.legendUpdateDOM();
2644 if(this.chart_created === true
2645 && typeof this.library.update === 'function') {
2647 if(this.debug === true)
2648 this.log('updating chart...');
2650 if(callChartLibraryUpdateSafely(data) === false)
2654 if(this.debug === true)
2655 this.log('creating chart...');
2657 if(callChartLibraryCreateSafely(data) === false)
2661 this.legendShowLatestValues();
2662 if(this.selected === true)
2663 NETDATA.globalSelectionSync.stop();
2665 // update the performance counters
2666 var now = new Date().getTime();
2667 this.tm.last_updated = now;
2669 // don't update last_autorefreshed if this chart is
2670 // forced to be updated with global PanAndZoom
2671 if(NETDATA.globalPanAndZoom.isActive())
2672 this.tm.last_autorefreshed = 0;
2674 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2675 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2677 this.tm.last_autorefreshed = now;
2680 this.refresh_dt_ms = now - started;
2681 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2683 if(this.refresh_dt_element !== null)
2684 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2687 this.updateChart = function(callback) {
2688 if(this.debug === true)
2689 this.log('updateChart() called.');
2691 if(this._updating === true) {
2692 if(this.debug === true)
2693 this.log('I am already updating...');
2695 if(typeof callback === 'function') callback();
2699 // due to late initialization of charts and libraries
2700 // we need to check this too
2701 if(this.enabled === false) {
2702 if(this.debug === true)
2703 this.log('I am not enabled');
2705 if(typeof callback === 'function') callback();
2709 if(canBeRendered() === false) {
2710 if(typeof callback === 'function') callback();
2714 if(this.chart === null) {
2715 this.getChart(function() { that.updateChart(callback); });
2719 if(this.library.initialized === false) {
2720 if(this.library.enabled === true) {
2721 this.library.initialize(function() { that.updateChart(callback); });
2725 error('chart library "' + this.library_name + '" is not available.');
2726 if(typeof callback === 'function') callback();
2731 this.clearSelection();
2734 if(this.debug === true)
2735 this.log('updating from ' + this.data_url);
2737 this._updating = true;
2739 this.xhr = $.ajax( {
2741 crossDomain: NETDATA.options.crossDomainAjax,
2745 .success(function(data) {
2746 if(that.debug === true)
2747 that.log('data received. updating chart.');
2749 that.updateChartWithData(data);
2752 error('data download failed for url: ' + that.data_url);
2754 .always(function() {
2755 that._updating = false;
2756 if(typeof callback === 'function') callback();
2762 this.isVisible = function(nocache) {
2763 if(typeof nocache === 'undefined')
2766 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2768 // caching - we do not evaluate the charts visibility
2769 // if the page has not been scrolled since the last check
2770 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2771 return this.___isVisible___;
2773 this.tm.last_visible_check = new Date().getTime();
2775 var wh = window.innerHeight;
2776 var x = this.element.getBoundingClientRect();
2780 if(x.width === 0 || x.height === 0) {
2782 this.___isVisible___ = false;
2783 return this.___isVisible___;
2786 if(x.top < 0 && -x.top > x.height) {
2787 // the chart is entirely above
2788 ret = -x.top - x.height;
2790 else if(x.top > wh) {
2791 // the chart is entirely below
2795 if(ret > tolerance) {
2796 // the chart is too far
2799 this.___isVisible___ = false;
2800 return this.___isVisible___;
2803 // the chart is inside or very close
2806 this.___isVisible___ = true;
2807 return this.___isVisible___;
2811 this.isAutoRefreshed = function() {
2812 return (this.current.autorefresh);
2815 this.canBeAutoRefreshed = function() {
2816 var now = new Date().getTime();
2818 if(this.enabled === false) {
2819 if(this.debug === true)
2820 this.log('I am not enabled');
2825 if(this.library === null || this.library.enabled === false) {
2826 error('charting library "' + this.library_name + '" is not available');
2827 if(this.debug === true)
2828 this.log('My chart library ' + this.library_name + ' is not available');
2833 if(this.isVisible() === false) {
2834 if(NETDATA.options.debug.visibility === true || this.debug === true)
2835 this.log('I am not visible');
2840 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2841 if(this.debug === true)
2842 this.log('timed force update detected - allowing this update');
2844 this.current.force_update_at = 0;
2848 if(this.isAutoRefreshed() === true) {
2849 // allow the first update, even if the page is not visible
2850 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2851 if(NETDATA.options.debug.focus === true || this.debug === true)
2852 this.log('canBeAutoRefreshed(): page does not have focus');
2857 if(this.needsRecreation() === true) {
2858 if(this.debug === true)
2859 this.log('canBeAutoRefreshed(): needs re-creation.');
2864 // options valid only for autoRefresh()
2865 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2866 if(NETDATA.globalPanAndZoom.isActive()) {
2867 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2868 if(this.debug === true)
2869 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2874 if(this.debug === true)
2875 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2881 if(this.selected === true) {
2882 if(this.debug === true)
2883 this.log('canBeAutoRefreshed(): I have a selection in place.');
2888 if(this.paused === true) {
2889 if(this.debug === true)
2890 this.log('canBeAutoRefreshed(): I am paused.');
2895 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2896 if(this.debug === true)
2897 this.log('canBeAutoRefreshed(): It is time to update me.');
2907 this.autoRefresh = function(callback) {
2908 if(this.canBeAutoRefreshed() === true) {
2909 this.updateChart(callback);
2912 if(typeof callback !== 'undefined')
2917 this._defaultsFromDownloadedChart = function(chart) {
2919 this.chart_url = chart.url;
2920 this.data_update_every = chart.update_every * 1000;
2921 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2922 this.tm.last_info_downloaded = new Date().getTime();
2924 if(this.title === null)
2925 this.title = chart.title;
2927 if(this.units === null)
2928 this.units = chart.units;
2931 // fetch the chart description from the netdata server
2932 this.getChart = function(callback) {
2933 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2935 this._defaultsFromDownloadedChart(this.chart);
2936 if(typeof callback === 'function') callback();
2939 this.chart_url = "/api/v1/chart?chart=" + this.id;
2941 if(this.debug === true)
2942 this.log('downloading ' + this.chart_url);
2945 url: this.host + this.chart_url,
2946 crossDomain: NETDATA.options.crossDomainAjax,
2950 .done(function(chart) {
2951 chart.url = that.chart_url;
2952 that._defaultsFromDownloadedChart(chart);
2953 NETDATA.chartRegistry.add(that.host, that.id, chart);
2956 NETDATA.error(404, that.chart_url);
2957 error('chart not found on url "' + that.chart_url + '"');
2959 .always(function() {
2960 if(typeof callback === 'function') callback();
2965 // ============================================================================================================
2971 NETDATA.resetAllCharts = function(state) {
2972 // first clear the global selection sync
2973 // to make sure no chart is in selected state
2974 state.globalSelectionSyncStop();
2976 // there are 2 possibilities here
2977 // a. state is the global Pan and Zoom master
2978 // b. state is not the global Pan and Zoom master
2980 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2983 // clear the global Pan and Zoom
2984 // this will also refresh the master
2985 // and unblock any charts currently mirroring the master
2986 NETDATA.globalPanAndZoom.clearMaster();
2988 // if we were not the master, reset our status too
2989 // this is required because most probably the mouse
2990 // is over this chart, blocking it from auto-refreshing
2991 if(master === false && (state.paused === true || state.selected === true))
2995 // get or create a chart state, given a DOM element
2996 NETDATA.chartState = function(element) {
2997 var state = $(element).data('netdata-state-object') || null;
2998 if(state === null) {
2999 state = new chartState(element);
3000 $(element).data('netdata-state-object', state);
3005 // ----------------------------------------------------------------------------------------------------------------
3006 // Library functions
3008 // Load a script without jquery
3009 // This is used to load jquery - after it is loaded, we use jquery
3010 NETDATA._loadjQuery = function(callback) {
3011 if(typeof jQuery === 'undefined') {
3012 if(NETDATA.options.debug.main_loop === true)
3013 console.log('loading ' + NETDATA.jQuery);
3015 var script = document.createElement('script');
3016 script.type = 'text/javascript';
3017 script.async = true;
3018 script.src = NETDATA.jQuery;
3020 // script.onabort = onError;
3021 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3022 if(typeof callback === "function")
3023 script.onload = callback;
3025 var s = document.getElementsByTagName('script')[0];
3026 s.parentNode.insertBefore(script, s);
3028 else if(typeof callback === "function")
3032 NETDATA._loadCSS = function(filename) {
3033 // don't use jQuery here
3034 // styles are loaded before jQuery
3035 // to eliminate showing an unstyled page to the user
3037 var fileref = document.createElement("link");
3038 fileref.setAttribute("rel", "stylesheet");
3039 fileref.setAttribute("type", "text/css");
3040 fileref.setAttribute("href", filename);
3042 if (typeof fileref !== 'undefined')
3043 document.getElementsByTagName("head")[0].appendChild(fileref);
3046 NETDATA.colorHex2Rgb = function(hex) {
3047 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3048 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3049 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3050 return r + r + g + g + b + b;
3053 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3055 r: parseInt(result[1], 16),
3056 g: parseInt(result[2], 16),
3057 b: parseInt(result[3], 16)
3061 NETDATA.colorLuminance = function(hex, lum) {
3062 // validate hex string
3063 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3065 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3069 // convert to decimal and change luminosity
3070 var rgb = "#", c, i;
3071 for (i = 0; i < 3; i++) {
3072 c = parseInt(hex.substr(i*2,2), 16);
3073 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3074 rgb += ("00"+c).substr(c.length);
3080 NETDATA.guid = function() {
3082 return Math.floor((1 + Math.random()) * 0x10000)
3087 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3090 NETDATA.zeropad = function(x) {
3091 if(x > -10 && x < 10) return '0' + x.toString();
3092 else return x.toString();
3095 // user function to signal us the DOM has been
3097 NETDATA.updatedDom = function() {
3098 NETDATA.options.updated_dom = true;
3101 NETDATA.ready = function(callback) {
3102 NETDATA.options.pauseCallback = callback;
3105 NETDATA.pause = function(callback) {
3106 if(NETDATA.options.pause === true)
3109 NETDATA.options.pauseCallback = callback;
3112 NETDATA.unpause = function() {
3113 NETDATA.options.pauseCallback = null;
3114 NETDATA.options.updated_dom = true;
3115 NETDATA.options.pause = false;
3118 // ----------------------------------------------------------------------------------------------------------------
3120 // this is purely sequencial charts refresher
3121 // it is meant to be autonomous
3122 NETDATA.chartRefresherNoParallel = function(index) {
3123 if(NETDATA.options.debug.mail_loop === true)
3124 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3126 if(NETDATA.options.updated_dom === true) {
3127 // the dom has been updated
3128 // get the dom parts again
3129 NETDATA.parseDom(NETDATA.chartRefresher);
3132 if(index >= NETDATA.options.targets.length) {
3133 if(NETDATA.options.debug.main_loop === true)
3134 console.log('waiting to restart main loop...');
3136 NETDATA.options.auto_refresher_fast_weight = 0;
3138 setTimeout(function() {
3139 NETDATA.chartRefresher();
3140 }, NETDATA.options.current.idle_between_loops);
3143 var state = NETDATA.options.targets[index];
3145 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3146 if(NETDATA.options.debug.main_loop === true)
3147 console.log('fast rendering...');
3149 state.autoRefresh(function() {
3150 NETDATA.chartRefresherNoParallel(++index);
3154 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3155 NETDATA.options.auto_refresher_fast_weight = 0;
3157 setTimeout(function() {
3158 state.autoRefresh(function() {
3159 NETDATA.chartRefresherNoParallel(++index);
3161 }, NETDATA.options.current.idle_between_charts);
3166 // this is part of the parallel refresher
3167 // its cause is to refresh sequencially all the charts
3168 // that depend on chart library initialization
3169 // it will call the parallel refresher back
3170 // as soon as it sees a chart that its chart library
3172 NETDATA.chartRefresher_uninitialized = function() {
3173 if(NETDATA.options.updated_dom === true) {
3174 // the dom has been updated
3175 // get the dom parts again
3176 NETDATA.parseDom(NETDATA.chartRefresher);
3180 if(NETDATA.options.sequencial.length === 0)
3181 NETDATA.chartRefresher();
3183 var state = NETDATA.options.sequencial.pop();
3184 if(state.library.initialized === true)
3185 NETDATA.chartRefresher();
3187 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3191 NETDATA.chartRefresherWaitTime = function() {
3192 return NETDATA.options.current.idle_parallel_loops;
3195 // the default refresher
3196 // it will create 2 sets of charts:
3197 // - the ones that can be refreshed in parallel
3198 // - the ones that depend on something else
3199 // the first set will be executed in parallel
3200 // the second will be given to NETDATA.chartRefresher_uninitialized()
3201 NETDATA.chartRefresher = function() {
3202 if(NETDATA.options.pause === true) {
3203 // console.log('auto-refresher is paused');
3204 setTimeout(NETDATA.chartRefresher,
3205 NETDATA.chartRefresherWaitTime());
3209 if(typeof NETDATA.options.pauseCallback === 'function') {
3210 // console.log('auto-refresher is calling pauseCallback');
3211 NETDATA.options.pause = true;
3212 NETDATA.options.pauseCallback();
3213 NETDATA.chartRefresher();
3217 if(NETDATA.options.current.parallel_refresher === false) {
3218 NETDATA.chartRefresherNoParallel(0);
3222 if(NETDATA.options.updated_dom === true) {
3223 // the dom has been updated
3224 // get the dom parts again
3225 NETDATA.parseDom(NETDATA.chartRefresher);
3229 var parallel = new Array();
3230 var targets = NETDATA.options.targets;
3231 var len = targets.length;
3233 if(targets[len].isVisible() === false)
3236 var state = targets[len];
3237 if(state.library.initialized === false) {
3238 if(state.library.enabled === true) {
3239 state.library.initialize(NETDATA.chartRefresher);
3243 state.error('chart library "' + state.library_name + '" is not enabled.');
3247 parallel.unshift(state);
3250 if(parallel.length > 0) {
3251 var parallel_jobs = parallel.length;
3253 // this will execute the jobs in parallel
3254 $(parallel).each(function() {
3255 this.autoRefresh(function() {
3258 if(parallel_jobs === 0) {
3259 setTimeout(NETDATA.chartRefresher,
3260 NETDATA.chartRefresherWaitTime());
3266 setTimeout(NETDATA.chartRefresher,
3267 NETDATA.chartRefresherWaitTime());
3271 NETDATA.parseDom = function(callback) {
3272 NETDATA.options.last_page_scroll = new Date().getTime();
3273 NETDATA.options.updated_dom = false;
3275 var targets = $('div[data-netdata]'); //.filter(':visible');
3277 if(NETDATA.options.debug.main_loop === true)
3278 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3280 NETDATA.options.targets = new Array();
3281 var len = targets.length;
3283 // the initialization will take care of sizing
3284 // and the "loading..." message
3285 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3288 if(typeof callback === 'function') callback();
3291 // this is the main function - where everything starts
3292 NETDATA.start = function() {
3293 // this should be called only once
3295 NETDATA.options.page_is_visible = true;
3297 $(window).blur(function() {
3298 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3299 NETDATA.options.page_is_visible = false;
3300 if(NETDATA.options.debug.focus === true)
3301 console.log('Lost Focus!');
3305 $(window).focus(function() {
3306 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3307 NETDATA.options.page_is_visible = true;
3308 if(NETDATA.options.debug.focus === true)
3309 console.log('Focus restored!');
3313 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3314 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3315 NETDATA.options.page_is_visible = false;
3316 if(NETDATA.options.debug.focus === true)
3317 console.log('Document has no focus!');
3321 // bootstrap tab switching
3322 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3324 // bootstrap modal switching
3325 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3326 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3328 NETDATA.parseDom(NETDATA.chartRefresher);
3331 // ----------------------------------------------------------------------------------------------------------------
3334 NETDATA.peityInitialize = function(callback) {
3335 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3337 url: NETDATA.peity_js,
3342 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3345 NETDATA.chartLibraries.peity.enabled = false;
3346 NETDATA.error(100, NETDATA.peity_js);
3348 .always(function() {
3349 if(typeof callback === "function")
3354 NETDATA.chartLibraries.peity.enabled = false;
3355 if(typeof callback === "function")
3360 NETDATA.peityChartUpdate = function(state, data) {
3361 state.peity_instance.innerHTML = data.result;
3363 if(state.peity_options.stroke !== state.chartColors()[0]) {
3364 state.peity_options.stroke = state.chartColors()[0];
3365 if(state.chart.chart_type === 'line')
3366 state.peity_options.fill = NETDATA.themes.current.background;
3368 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3371 $(state.peity_instance).peity('line', state.peity_options);
3375 NETDATA.peityChartCreate = function(state, data) {
3376 state.peity_instance = document.createElement('div');
3377 state.element_chart.appendChild(state.peity_instance);
3379 var self = $(state.element);
3380 state.peity_options = {
3381 stroke: NETDATA.themes.current.foreground,
3382 strokeWidth: self.data('peity-strokewidth') || 1,
3383 width: state.chartWidth(),
3384 height: state.chartHeight(),
3385 fill: NETDATA.themes.current.foreground
3388 NETDATA.peityChartUpdate(state, data);
3392 // ----------------------------------------------------------------------------------------------------------------
3395 NETDATA.sparklineInitialize = function(callback) {
3396 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3398 url: NETDATA.sparkline_js,
3403 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3406 NETDATA.chartLibraries.sparkline.enabled = false;
3407 NETDATA.error(100, NETDATA.sparkline_js);
3409 .always(function() {
3410 if(typeof callback === "function")
3415 NETDATA.chartLibraries.sparkline.enabled = false;
3416 if(typeof callback === "function")
3421 NETDATA.sparklineChartUpdate = function(state, data) {
3422 state.sparkline_options.width = state.chartWidth();
3423 state.sparkline_options.height = state.chartHeight();
3425 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3429 NETDATA.sparklineChartCreate = function(state, data) {
3430 var self = $(state.element);
3431 var type = self.data('sparkline-type') || 'line';
3432 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3433 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3434 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3435 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3436 var composite = self.data('sparkline-composite') || undefined;
3437 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3438 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3439 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3440 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3441 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3442 var spotColor = self.data('sparkline-spotcolor') || undefined;
3443 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3444 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3445 var spotRadius = self.data('sparkline-spotradius') || undefined;
3446 var valueSpots = self.data('sparkline-valuespots') || undefined;
3447 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3448 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3449 var lineWidth = self.data('sparkline-linewidth') || undefined;
3450 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3451 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3452 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3453 var xvalues = self.data('sparkline-xvalues') || undefined;
3454 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3455 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3456 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3457 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3458 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3459 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3460 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3461 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3462 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3463 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3464 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3465 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3466 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3467 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3468 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3469 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3470 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3471 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3472 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3473 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3474 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3475 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3477 state.sparkline_options = {
3479 lineColor: lineColor,
3480 fillColor: fillColor,
3481 chartRangeMin: chartRangeMin,
3482 chartRangeMax: chartRangeMax,
3483 composite: composite,
3484 enableTagOptions: enableTagOptions,
3485 tagOptionPrefix: tagOptionPrefix,
3486 tagValuesAttribute: tagValuesAttribute,
3487 disableHiddenCheck: disableHiddenCheck,
3488 defaultPixelsPerValue: defaultPixelsPerValue,
3489 spotColor: spotColor,
3490 minSpotColor: minSpotColor,
3491 maxSpotColor: maxSpotColor,
3492 spotRadius: spotRadius,
3493 valueSpots: valueSpots,
3494 highlightSpotColor: highlightSpotColor,
3495 highlightLineColor: highlightLineColor,
3496 lineWidth: lineWidth,
3497 normalRangeMin: normalRangeMin,
3498 normalRangeMax: normalRangeMax,
3499 drawNormalOnTop: drawNormalOnTop,
3501 chartRangeClip: chartRangeClip,
3502 chartRangeMinX: chartRangeMinX,
3503 chartRangeMaxX: chartRangeMaxX,
3504 disableInteraction: disableInteraction,
3505 disableTooltips: disableTooltips,
3506 disableHighlight: disableHighlight,
3507 highlightLighten: highlightLighten,
3508 highlightColor: highlightColor,
3509 tooltipContainer: tooltipContainer,
3510 tooltipClassname: tooltipClassname,
3511 tooltipChartTitle: state.title,
3512 tooltipFormat: tooltipFormat,
3513 tooltipPrefix: tooltipPrefix,
3514 tooltipSuffix: tooltipSuffix,
3515 tooltipSkipNull: tooltipSkipNull,
3516 tooltipValueLookups: tooltipValueLookups,
3517 tooltipFormatFieldlist: tooltipFormatFieldlist,
3518 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3519 numberFormatter: numberFormatter,
3520 numberDigitGroupSep: numberDigitGroupSep,
3521 numberDecimalMark: numberDecimalMark,
3522 numberDigitGroupCount: numberDigitGroupCount,
3523 animatedZooms: animatedZooms,
3524 width: state.chartWidth(),
3525 height: state.chartHeight()
3528 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3532 // ----------------------------------------------------------------------------------------------------------------
3539 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3540 if(after < state.netdata_first)
3541 after = state.netdata_first;
3543 if(before > state.netdata_last)
3544 before = state.netdata_last;
3546 state.setMode('zoom');
3547 state.globalSelectionSyncStop();
3548 state.globalSelectionSyncDelay();
3549 state.dygraph_user_action = true;
3550 state.dygraph_force_zoom = true;
3551 state.updateChartPanOrZoom(after, before);
3552 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3555 NETDATA.dygraphSetSelection = function(state, t) {
3556 if(typeof state.dygraph_instance !== 'undefined') {
3557 var r = state.calculateRowForTime(t);
3559 state.dygraph_instance.setSelection(r);
3561 state.dygraph_instance.clearSelection();
3562 state.legendShowUndefined();
3569 NETDATA.dygraphClearSelection = function(state, t) {
3570 if(typeof state.dygraph_instance !== 'undefined') {
3571 state.dygraph_instance.clearSelection();
3576 NETDATA.dygraphSmoothInitialize = function(callback) {
3578 url: NETDATA.dygraph_smooth_js,
3583 NETDATA.dygraph.smooth = true;
3584 smoothPlotter.smoothing = 0.3;
3587 NETDATA.dygraph.smooth = false;
3589 .always(function() {
3590 if(typeof callback === "function")
3595 NETDATA.dygraphInitialize = function(callback) {
3596 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3598 url: NETDATA.dygraph_js,
3603 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3606 NETDATA.chartLibraries.dygraph.enabled = false;
3607 NETDATA.error(100, NETDATA.dygraph_js);
3609 .always(function() {
3610 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3611 NETDATA.dygraphSmoothInitialize(callback);
3612 else if(typeof callback === "function")
3617 NETDATA.chartLibraries.dygraph.enabled = false;
3618 if(typeof callback === "function")
3623 NETDATA.dygraphChartUpdate = function(state, data) {
3624 var dygraph = state.dygraph_instance;
3626 if(typeof dygraph === 'undefined')
3627 return NETDATA.dygraphChartCreate(state, data);
3629 // when the chart is not visible, and hidden
3630 // if there is a window resize, dygraph detects
3631 // its element size as 0x0.
3632 // this will make it re-appear properly
3634 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3638 file: data.result.data,
3639 colors: state.chartColors(),
3640 labels: data.result.labels,
3641 labelsDivWidth: state.chartWidth() - 70,
3642 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3645 if(state.dygraph_force_zoom === true) {
3646 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3647 state.log('dygraphChartUpdate() forced zoom update');
3649 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3650 options.valueRange = null;
3651 options.isZoomedIgnoreProgrammaticZoom = true;
3652 state.dygraph_force_zoom = false;
3654 else if(state.current.name !== 'auto') {
3655 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3656 state.log('dygraphChartUpdate() loose update');
3659 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3660 state.log('dygraphChartUpdate() strict update');
3662 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3663 options.valueRange = null;
3664 options.isZoomedIgnoreProgrammaticZoom = true;
3667 if(state.dygraph_smooth_eligible === true) {
3668 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3669 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3670 NETDATA.dygraphChartCreate(state, data);
3675 dygraph.updateOptions(options);
3677 state.dygraph_last_rendered = new Date().getTime();
3681 NETDATA.dygraphChartCreate = function(state, data) {
3682 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3683 state.log('dygraphChartCreate()');
3685 var self = $(state.element);
3687 var chart_type = state.chart.chart_type;
3688 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3689 chart_type = self.data('dygraph-type') || chart_type;
3691 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3692 smooth = self.data('dygraph-smooth') || smooth;
3694 if(NETDATA.dygraph.smooth === false)
3697 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3698 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3700 state.dygraph_options = {
3701 colors: self.data('dygraph-colors') || state.chartColors(),
3703 // leave a few pixels empty on the right of the chart
3704 rightGap: self.data('dygraph-rightgap') || 5,
3705 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3706 showRoller: self.data('dygraph-showroller') || false,
3708 title: self.data('dygraph-title') || state.title,
3709 titleHeight: self.data('dygraph-titleheight') || 19,
3711 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3712 labels: data.result.labels,
3713 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3714 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3715 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3716 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3717 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3720 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3721 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3723 ylabel: state.units,
3724 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3726 // the function to plot the chart
3729 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3730 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3731 strokePattern: self.data('dygraph-strokepattern') || undefined,
3733 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3734 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3735 drawPoints: self.data('dygraph-drawpoints') || false,
3737 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3738 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3740 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3741 pointSize: self.data('dygraph-pointsize') || 1,
3743 // enabling this makes the chart with little square lines
3744 stepPlot: self.data('dygraph-stepplot') || false,
3746 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3747 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3748 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3750 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3751 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3752 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3753 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3755 drawAxis: self.data('dygraph-drawaxis') || true,
3756 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3757 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3758 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3760 drawGrid: self.data('dygraph-drawgrid') || true,
3761 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3762 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3763 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3764 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3765 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3767 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3768 sigFigs: self.data('dygraph-sigfigs') || null,
3769 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3770 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3772 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3773 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3774 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3776 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3777 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3781 ticker: Dygraph.dateTicker,
3782 axisLabelFormatter: function (d, gran) {
3783 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3785 valueFormatter: function (ms) {
3786 var d = new Date(ms);
3787 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3788 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3793 valueFormatter: function (x) {
3794 // we format legends with the state object
3795 // no need to do anything here
3796 // return (Math.round(x*100) / 100).toLocaleString();
3797 // return state.legendFormatValue(x);
3802 legendFormatter: function(data) {
3803 var elements = state.element_legend_childs;
3805 // if the hidden div is not there
3806 // we are not managing the legend
3807 if(elements.hidden === null) return;
3809 if (typeof data.x !== 'undefined') {
3810 state.legendSetDate(data.x);
3811 var i = data.series.length;
3813 var series = data.series[i];
3814 if(!series.isVisible) continue;
3815 state.legendSetLabelValue(series.label, series.y);
3821 drawCallback: function(dygraph, is_initial) {
3822 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3823 state.dygraph_user_action = false;
3825 var x_range = dygraph.xAxisRange();
3826 var after = Math.round(x_range[0]);
3827 var before = Math.round(x_range[1]);
3829 if(NETDATA.options.debug.dygraph === true)
3830 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3832 if(before <= state.netdata_last && after >= state.netdata_first)
3833 state.updateChartPanOrZoom(after, before);
3836 zoomCallback: function(minDate, maxDate, yRanges) {
3837 if(NETDATA.options.debug.dygraph === true)
3838 state.log('dygraphZoomCallback()');
3840 state.globalSelectionSyncStop();
3841 state.globalSelectionSyncDelay();
3842 state.setMode('zoom');
3844 // refresh it to the greatest possible zoom level
3845 state.dygraph_user_action = true;
3846 state.dygraph_force_zoom = true;
3847 state.updateChartPanOrZoom(minDate, maxDate);
3849 highlightCallback: function(event, x, points, row, seriesName) {
3850 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3851 state.log('dygraphHighlightCallback()');
3855 // there is a bug in dygraph when the chart is zoomed enough
3856 // the time it thinks is selected is wrong
3857 // here we calculate the time t based on the row number selected
3859 var t = state.data_after + row * state.data_update_every;
3860 // 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);
3862 state.globalSelectionSync(x);
3864 // fix legend zIndex using the internal structures of dygraph legend module
3865 // this works, but it is a hack!
3866 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3868 unhighlightCallback: function(event) {
3869 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3870 state.log('dygraphUnhighlightCallback()');
3872 state.unpauseChart();
3873 state.globalSelectionSyncStop();
3875 interactionModel : {
3876 mousedown: function(event, dygraph, context) {
3877 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3878 state.log('interactionModel.mousedown()');
3880 state.dygraph_user_action = true;
3881 state.globalSelectionSyncStop();
3883 if(NETDATA.options.debug.dygraph === true)
3884 state.log('dygraphMouseDown()');
3886 // Right-click should not initiate a zoom.
3887 if(event.button && event.button === 2) return;
3889 context.initializeMouseDown(event, dygraph, context);
3891 if(event.button && event.button === 1) {
3892 if (event.altKey || event.shiftKey) {
3893 state.setMode('pan');
3894 state.globalSelectionSyncDelay();
3895 Dygraph.startPan(event, dygraph, context);
3898 state.setMode('zoom');
3899 state.globalSelectionSyncDelay();
3900 Dygraph.startZoom(event, dygraph, context);
3904 if (event.altKey || event.shiftKey) {
3905 state.setMode('zoom');
3906 state.globalSelectionSyncDelay();
3907 Dygraph.startZoom(event, dygraph, context);
3910 state.setMode('pan');
3911 state.globalSelectionSyncDelay();
3912 Dygraph.startPan(event, dygraph, context);
3916 mousemove: function(event, dygraph, context) {
3917 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3918 state.log('interactionModel.mousemove()');
3920 if(context.isPanning) {
3921 state.dygraph_user_action = true;
3922 state.globalSelectionSyncStop();
3923 state.globalSelectionSyncDelay();
3924 state.setMode('pan');
3925 Dygraph.movePan(event, dygraph, context);
3927 else if(context.isZooming) {
3928 state.dygraph_user_action = true;
3929 state.globalSelectionSyncStop();
3930 state.globalSelectionSyncDelay();
3931 state.setMode('zoom');
3932 Dygraph.moveZoom(event, dygraph, context);
3935 mouseup: function(event, dygraph, context) {
3936 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3937 state.log('interactionModel.mouseup()');
3939 if (context.isPanning) {
3940 state.dygraph_user_action = true;
3941 state.globalSelectionSyncDelay();
3942 Dygraph.endPan(event, dygraph, context);
3944 else if (context.isZooming) {
3945 state.dygraph_user_action = true;
3946 state.globalSelectionSyncDelay();
3947 Dygraph.endZoom(event, dygraph, context);
3950 click: function(event, dygraph, context) {
3951 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3952 state.log('interactionModel.click()');
3954 event.preventDefault();
3956 dblclick: function(event, dygraph, context) {
3957 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3958 state.log('interactionModel.dblclick()');
3959 NETDATA.resetAllCharts(state);
3961 mousewheel: function(event, dygraph, context) {
3962 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3963 state.log('interactionModel.mousewheel()');
3965 // Take the offset of a mouse event on the dygraph canvas and
3966 // convert it to a pair of percentages from the bottom left.
3967 // (Not top left, bottom is where the lower value is.)
3968 function offsetToPercentage(g, offsetX, offsetY) {
3969 // This is calculating the pixel offset of the leftmost date.
3970 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3971 var yar0 = g.yAxisRange(0);
3973 // This is calculating the pixel of the higest value. (Top pixel)
3974 var yOffset = g.toDomCoords(null, yar0[1])[1];
3976 // x y w and h are relative to the corner of the drawing area,
3977 // so that the upper corner of the drawing area is (0, 0).
3978 var x = offsetX - xOffset;
3979 var y = offsetY - yOffset;
3981 // This is computing the rightmost pixel, effectively defining the
3983 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3985 // This is computing the lowest pixel, effectively defining the height.
3986 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3988 // Percentage from the left.
3989 var xPct = w === 0 ? 0 : (x / w);
3990 // Percentage from the top.
3991 var yPct = h === 0 ? 0 : (y / h);
3993 // The (1-) part below changes it from "% distance down from the top"
3994 // to "% distance up from the bottom".
3995 return [xPct, (1-yPct)];
3998 // Adjusts [x, y] toward each other by zoomInPercentage%
3999 // Split it so the left/bottom axis gets xBias/yBias of that change and
4000 // tight/top gets (1-xBias)/(1-yBias) of that change.
4002 // If a bias is missing it splits it down the middle.
4003 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4004 xBias = xBias || 0.5;
4005 yBias = yBias || 0.5;
4007 function adjustAxis(axis, zoomInPercentage, bias) {
4008 var delta = axis[1] - axis[0];
4009 var increment = delta * zoomInPercentage;
4010 var foo = [increment * bias, increment * (1-bias)];
4012 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4015 var yAxes = g.yAxisRanges();
4017 for (var i = 0; i < yAxes.length; i++) {
4018 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4021 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4024 if(event.altKey || event.shiftKey) {
4025 state.dygraph_user_action = true;
4027 state.globalSelectionSyncStop();
4028 state.globalSelectionSyncDelay();
4030 // http://dygraphs.com/gallery/interaction-api.js
4031 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4032 var percentage = normal / 50;
4034 if (!(event.offsetX && event.offsetY)){
4035 event.offsetX = event.layerX - event.target.offsetLeft;
4036 event.offsetY = event.layerY - event.target.offsetTop;
4039 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4040 var xPct = percentages[0];
4041 var yPct = percentages[1];
4043 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4045 var after = new_x_range[0];
4046 var before = new_x_range[1];
4048 var first = state.netdata_first + state.data_update_every;
4049 var last = state.netdata_last + state.data_update_every;
4052 after -= (before - last);
4059 state.setMode('zoom');
4060 if(state.updateChartPanOrZoom(after, before) === true)
4061 dygraph.updateOptions({ dateWindow: [ after, before ] });
4063 event.preventDefault();
4066 touchstart: function(event, dygraph, context) {
4067 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4068 state.log('interactionModel.touchstart()');
4070 state.dygraph_user_action = true;
4071 state.setMode('zoom');
4074 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4076 // we overwrite the touch directions at the end, to overwrite
4077 // the internal default of dygraphs
4078 context.touchDirections = { x: true, y: false };
4080 state.dygraph_last_touch_start = new Date().getTime();
4081 state.dygraph_last_touch_move = 0;
4083 if(typeof event.touches[0].pageX === 'number')
4084 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4086 state.dygraph_last_touch_page_x = 0;
4088 touchmove: function(event, dygraph, context) {
4089 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4090 state.log('interactionModel.touchmove()');
4092 state.dygraph_user_action = true;
4093 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4095 state.dygraph_last_touch_move = new Date().getTime();
4097 touchend: function(event, dygraph, context) {
4098 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4099 state.log('interactionModel.touchend()');
4101 state.dygraph_user_action = true;
4102 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4104 // if it didn't move, it is a selection
4105 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4106 // internal api of dygraphs
4107 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4108 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4109 if(NETDATA.dygraphSetSelection(state, t) === true)
4110 state.globalSelectionSync(t);
4113 // if it was double tap within double click time, reset the charts
4114 var now = new Date().getTime();
4115 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4116 if(state.dygraph_last_touch_move === 0) {
4117 var dt = now - state.dygraph_last_touch_end;
4118 if(dt <= NETDATA.options.current.double_click_speed)
4119 NETDATA.resetAllCharts(state);
4123 // remember the timestamp of the last touch end
4124 state.dygraph_last_touch_end = now;
4129 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4130 state.dygraph_options.drawGrid = false;
4131 state.dygraph_options.drawAxis = false;
4132 state.dygraph_options.title = undefined;
4133 state.dygraph_options.units = undefined;
4134 state.dygraph_options.ylabel = undefined;
4135 state.dygraph_options.yLabelWidth = 0;
4136 state.dygraph_options.labelsDivWidth = 120;
4137 state.dygraph_options.labelsDivStyles.width = '120px';
4138 state.dygraph_options.labelsSeparateLines = true;
4139 state.dygraph_options.rightGap = 0;
4142 if(smooth === true) {
4143 state.dygraph_smooth_eligible = true;
4145 if(NETDATA.options.current.smooth_plot === true)
4146 state.dygraph_options.plotter = smoothPlotter;
4148 else state.dygraph_smooth_eligible = false;
4150 state.dygraph_instance = new Dygraph(state.element_chart,
4151 data.result.data, state.dygraph_options);
4153 state.dygraph_force_zoom = false;
4154 state.dygraph_user_action = false;
4155 state.dygraph_last_rendered = new Date().getTime();
4159 // ----------------------------------------------------------------------------------------------------------------
4162 NETDATA.morrisInitialize = function(callback) {
4163 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4165 // morris requires raphael
4166 if(!NETDATA.chartLibraries.raphael.initialized) {
4167 if(NETDATA.chartLibraries.raphael.enabled) {
4168 NETDATA.raphaelInitialize(function() {
4169 NETDATA.morrisInitialize(callback);
4173 NETDATA.chartLibraries.morris.enabled = false;
4174 if(typeof callback === "function")
4179 NETDATA._loadCSS(NETDATA.morris_css);
4182 url: NETDATA.morris_js,
4187 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4190 NETDATA.chartLibraries.morris.enabled = false;
4191 NETDATA.error(100, NETDATA.morris_js);
4193 .always(function() {
4194 if(typeof callback === "function")
4200 NETDATA.chartLibraries.morris.enabled = false;
4201 if(typeof callback === "function")
4206 NETDATA.morrisChartUpdate = function(state, data) {
4207 state.morris_instance.setData(data.result.data);
4211 NETDATA.morrisChartCreate = function(state, data) {
4213 state.morris_options = {
4214 element: state.element_chart.id,
4215 data: data.result.data,
4217 ykeys: data.dimension_names,
4218 labels: data.dimension_names,
4224 continuousLine: false,
4225 behaveLikeLine: false
4228 if(state.chart.chart_type === 'line')
4229 state.morris_instance = new Morris.Line(state.morris_options);
4231 else if(state.chart.chart_type === 'area') {
4232 state.morris_options.behaveLikeLine = true;
4233 state.morris_instance = new Morris.Area(state.morris_options);
4236 state.morris_instance = new Morris.Area(state.morris_options);
4241 // ----------------------------------------------------------------------------------------------------------------
4244 NETDATA.raphaelInitialize = function(callback) {
4245 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4247 url: NETDATA.raphael_js,
4252 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4255 NETDATA.chartLibraries.raphael.enabled = false;
4256 NETDATA.error(100, NETDATA.raphael_js);
4258 .always(function() {
4259 if(typeof callback === "function")
4264 NETDATA.chartLibraries.raphael.enabled = false;
4265 if(typeof callback === "function")
4270 NETDATA.raphaelChartUpdate = function(state, data) {
4271 $(state.element_chart).raphael(data.result, {
4272 width: state.chartWidth(),
4273 height: state.chartHeight()
4279 NETDATA.raphaelChartCreate = function(state, data) {
4280 $(state.element_chart).raphael(data.result, {
4281 width: state.chartWidth(),
4282 height: state.chartHeight()
4288 // ----------------------------------------------------------------------------------------------------------------
4291 NETDATA.c3Initialize = function(callback) {
4292 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4295 if(!NETDATA.chartLibraries.d3.initialized) {
4296 if(NETDATA.chartLibraries.d3.enabled) {
4297 NETDATA.d3Initialize(function() {
4298 NETDATA.c3Initialize(callback);
4302 NETDATA.chartLibraries.c3.enabled = false;
4303 if(typeof callback === "function")
4308 NETDATA._loadCSS(NETDATA.c3_css);
4316 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4319 NETDATA.chartLibraries.c3.enabled = false;
4320 NETDATA.error(100, NETDATA.c3_js);
4322 .always(function() {
4323 if(typeof callback === "function")
4329 NETDATA.chartLibraries.c3.enabled = false;
4330 if(typeof callback === "function")
4335 NETDATA.c3ChartUpdate = function(state, data) {
4336 state.c3_instance.destroy();
4337 return NETDATA.c3ChartCreate(state, data);
4339 //state.c3_instance.load({
4340 // rows: data.result,
4347 NETDATA.c3ChartCreate = function(state, data) {
4349 state.element_chart.id = 'c3-' + state.uuid;
4350 // console.log('id = ' + state.element_chart.id);
4352 state.c3_instance = c3.generate({
4353 bindto: '#' + state.element_chart.id,
4355 width: state.chartWidth(),
4356 height: state.chartHeight()
4359 pattern: state.chartColors()
4364 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4370 format: function(x) {
4371 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4398 // console.log(state.c3_instance);
4403 // ----------------------------------------------------------------------------------------------------------------
4406 NETDATA.d3Initialize = function(callback) {
4407 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4414 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4417 NETDATA.chartLibraries.d3.enabled = false;
4418 NETDATA.error(100, NETDATA.d3_js);
4420 .always(function() {
4421 if(typeof callback === "function")
4426 NETDATA.chartLibraries.d3.enabled = false;
4427 if(typeof callback === "function")
4432 NETDATA.d3ChartUpdate = function(state, data) {
4436 NETDATA.d3ChartCreate = function(state, data) {
4440 // ----------------------------------------------------------------------------------------------------------------
4443 NETDATA.googleInitialize = function(callback) {
4444 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4446 url: NETDATA.google_js,
4451 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4452 google.load('visualization', '1.1', {
4453 'packages': ['corechart', 'controls'],
4454 'callback': callback
4458 NETDATA.chartLibraries.google.enabled = false;
4459 NETDATA.error(100, NETDATA.google_js);
4460 if(typeof callback === "function")
4465 NETDATA.chartLibraries.google.enabled = false;
4466 if(typeof callback === "function")
4471 NETDATA.googleChartUpdate = function(state, data) {
4472 var datatable = new google.visualization.DataTable(data.result);
4473 state.google_instance.draw(datatable, state.google_options);
4477 NETDATA.googleChartCreate = function(state, data) {
4478 var datatable = new google.visualization.DataTable(data.result);
4480 state.google_options = {
4481 colors: state.chartColors(),
4483 // do not set width, height - the chart resizes itself
4484 //width: state.chartWidth(),
4485 //height: state.chartHeight(),
4490 // title: "Time of Day",
4491 // format:'HH:mm:ss',
4492 viewWindowMode: 'maximized',
4504 viewWindowMode: 'pretty',
4519 focusTarget: 'category',
4526 titlePosition: 'out',
4537 curveType: 'function',
4542 switch(state.chart.chart_type) {
4544 state.google_options.vAxis.viewWindowMode = 'maximized';
4545 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4546 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4550 state.google_options.isStacked = true;
4551 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4552 state.google_options.vAxis.viewWindowMode = 'maximized';
4553 state.google_options.vAxis.minValue = null;
4554 state.google_options.vAxis.maxValue = null;
4555 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4560 state.google_options.lineWidth = 2;
4561 state.google_instance = new google.visualization.LineChart(state.element_chart);
4565 state.google_instance.draw(datatable, state.google_options);
4569 // ----------------------------------------------------------------------------------------------------------------
4571 NETDATA.percentFromValueMax = function(value, max) {
4572 if(value === null) value = 0;
4573 if(max < value) max = value;
4577 pcent = Math.round(value * 100 / max);
4578 if(pcent === 0 && value > 0) pcent = 1;
4584 // ----------------------------------------------------------------------------------------------------------------
4587 NETDATA.easypiechartInitialize = function(callback) {
4588 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4590 url: NETDATA.easypiechart_js,
4595 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4598 NETDATA.chartLibraries.easypiechart.enabled = false;
4599 NETDATA.error(100, NETDATA.easypiechart_js);
4601 .always(function() {
4602 if(typeof callback === "function")
4607 NETDATA.chartLibraries.easypiechart.enabled = false;
4608 if(typeof callback === "function")
4613 NETDATA.easypiechartClearSelection = function(state) {
4614 if(typeof state.easyPieChartEvent !== 'undefined') {
4615 if(state.easyPieChartEvent.timer !== null)
4616 clearTimeout(state.easyPieChartEvent.timer);
4618 state.easyPieChartEvent.timer = null;
4621 if(state.isAutoRefreshed() === true && state.data !== null) {
4622 NETDATA.easypiechartChartUpdate(state, state.data);
4625 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4626 state.easyPieChart_instance.update(0);
4628 state.easyPieChart_instance.enableAnimation();
4633 NETDATA.easypiechartSetSelection = function(state, t) {
4634 if(state.timeIsVisible(t) !== true)
4635 return NETDATA.easypiechartClearSelection(state);
4637 var slot = state.calculateRowForTime(t);
4638 if(slot < 0 || slot >= state.data.result.length)
4639 return NETDATA.easypiechartClearSelection(state);
4641 if(typeof state.easyPieChartEvent === 'undefined') {
4642 state.easyPieChartEvent = {
4649 var value = state.data.result[state.data.result.length - 1 - slot];
4650 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4651 var pcent = NETDATA.percentFromValueMax(value, max);
4653 state.easyPieChartEvent.value = value;
4654 state.easyPieChartEvent.pcent = pcent;
4655 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4657 if(state.easyPieChartEvent.timer === null) {
4658 state.easyPieChart_instance.disableAnimation();
4660 state.easyPieChartEvent.timer = setTimeout(function() {
4661 state.easyPieChartEvent.timer = null;
4662 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4663 }, NETDATA.options.current.charts_selection_animation_delay);
4669 NETDATA.easypiechartChartUpdate = function(state, data) {
4670 var value, max, pcent;
4672 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4678 value = data.result[0];
4679 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4680 pcent = NETDATA.percentFromValueMax(value, max);
4683 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4684 state.easyPieChart_instance.update(pcent);
4688 NETDATA.easypiechartChartCreate = function(state, data) {
4689 var self = $(state.element);
4690 var chart = $(state.element_chart);
4692 var value = data.result[0];
4693 var max = self.data('easypiechart-max-value') || null;
4694 var adjust = self.data('easypiechart-adjust') || null;
4698 state.easyPieChartMax = null;
4701 state.easyPieChartMax = max;
4703 var pcent = NETDATA.percentFromValueMax(value, max);
4705 chart.data('data-percent', pcent);
4709 case 'width': size = state.chartHeight(); break;
4710 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4711 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4713 default: size = state.chartWidth(); break;
4715 state.element.style.width = size + 'px';
4716 state.element.style.height = size + 'px';
4718 var stroke = Math.floor(size / 22);
4719 if(stroke < 3) stroke = 2;
4721 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4722 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4723 state.easyPieChartLabel = document.createElement('span');
4724 state.easyPieChartLabel.className = 'easyPieChartLabel';
4725 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4726 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4727 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4728 state.element_chart.appendChild(state.easyPieChartLabel);
4730 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4731 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4732 state.easyPieChartTitle = document.createElement('span');
4733 state.easyPieChartTitle.className = 'easyPieChartTitle';
4734 state.easyPieChartTitle.innerHTML = state.title;
4735 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4736 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4737 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4738 state.element_chart.appendChild(state.easyPieChartTitle);
4740 var unitfontsize = Math.round(titlefontsize * 0.9);
4741 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4742 state.easyPieChartUnits = document.createElement('span');
4743 state.easyPieChartUnits.className = 'easyPieChartUnits';
4744 state.easyPieChartUnits.innerHTML = state.units;
4745 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4746 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4747 state.element_chart.appendChild(state.easyPieChartUnits);
4749 chart.easyPieChart({
4750 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4751 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4752 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4753 scaleLength: self.data('easypiechart-scalelength') || 5,
4754 lineCap: self.data('easypiechart-linecap') || 'round',
4755 lineWidth: self.data('easypiechart-linewidth') || stroke,
4756 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4757 size: self.data('easypiechart-size') || size,
4758 rotate: self.data('easypiechart-rotate') || 0,
4759 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4760 easing: self.data('easypiechart-easing') || undefined
4763 // when we just re-create the chart
4764 // do not animate the first update
4766 if(typeof state.easyPieChart_instance !== 'undefined')
4769 state.easyPieChart_instance = chart.data('easyPieChart');
4770 if(animate === false) state.easyPieChart_instance.disableAnimation();
4771 state.easyPieChart_instance.update(pcent);
4772 if(animate === false) state.easyPieChart_instance.enableAnimation();
4776 // ----------------------------------------------------------------------------------------------------------------
4779 NETDATA.gaugeInitialize = function(callback) {
4780 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4782 url: NETDATA.gauge_js,
4787 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4790 NETDATA.chartLibraries.gauge.enabled = false;
4791 NETDATA.error(100, NETDATA.gauge_js);
4793 .always(function() {
4794 if(typeof callback === "function")
4799 NETDATA.chartLibraries.gauge.enabled = false;
4800 if(typeof callback === "function")
4805 NETDATA.gaugeAnimation = function(state, status) {
4808 if(typeof status === 'boolean' && status === false)
4810 else if(typeof status === 'number')
4813 state.gauge_instance.animationSpeed = speed;
4814 state.___gaugeOld__.speed = speed;
4817 NETDATA.gaugeSet = function(state, value, min, max) {
4818 if(typeof value !== 'number') value = 0;
4819 if(typeof min !== 'number') min = 0;
4820 if(typeof max !== 'number') max = 0;
4821 if(value > max) max = value;
4822 if(value < min) min = value;
4831 // gauge.js has an issue if the needle
4832 // is smaller than min or larger than max
4833 // when we set the new values
4834 // the needle will go crazy
4836 // to prevent it, we always feed it
4837 // with a percentage, so that the needle
4838 // is always between min and max
4839 var pcent = (value - min) * 100 / (max - min);
4841 // these should never happen
4842 if(pcent < 0) pcent = 0;
4843 if(pcent > 100) pcent = 100;
4845 state.gauge_instance.set(pcent);
4847 state.___gaugeOld__.value = value;
4848 state.___gaugeOld__.min = min;
4849 state.___gaugeOld__.max = max;
4852 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4853 if(state.___gaugeOld__.valueLabel !== value) {
4854 state.___gaugeOld__.valueLabel = value;
4855 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4857 if(state.___gaugeOld__.minLabel !== min) {
4858 state.___gaugeOld__.minLabel = min;
4859 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4861 if(state.___gaugeOld__.maxLabel !== max) {
4862 state.___gaugeOld__.maxLabel = max;
4863 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4867 NETDATA.gaugeClearSelection = function(state) {
4868 if(typeof state.gaugeEvent !== 'undefined') {
4869 if(state.gaugeEvent.timer !== null)
4870 clearTimeout(state.gaugeEvent.timer);
4872 state.gaugeEvent.timer = null;
4875 if(state.isAutoRefreshed() === true && state.data !== null) {
4876 NETDATA.gaugeChartUpdate(state, state.data);
4879 NETDATA.gaugeAnimation(state, false);
4880 NETDATA.gaugeSet(state, null, null, null);
4881 NETDATA.gaugeSetLabels(state, null, null, null);
4884 NETDATA.gaugeAnimation(state, true);
4888 NETDATA.gaugeSetSelection = function(state, t) {
4889 if(state.timeIsVisible(t) !== true)
4890 return NETDATA.gaugeClearSelection(state);
4892 var slot = state.calculateRowForTime(t);
4893 if(slot < 0 || slot >= state.data.result.length)
4894 return NETDATA.gaugeClearSelection(state);
4896 if(typeof state.gaugeEvent === 'undefined') {
4897 state.gaugeEvent = {
4905 var value = state.data.result[state.data.result.length - 1 - slot];
4906 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4909 state.gaugeEvent.value = value;
4910 state.gaugeEvent.max = max;
4911 state.gaugeEvent.min = min;
4912 NETDATA.gaugeSetLabels(state, value, min, max);
4914 if(state.gaugeEvent.timer === null) {
4915 NETDATA.gaugeAnimation(state, false);
4917 state.gaugeEvent.timer = setTimeout(function() {
4918 state.gaugeEvent.timer = null;
4919 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4920 }, NETDATA.options.current.charts_selection_animation_delay);
4926 NETDATA.gaugeChartUpdate = function(state, data) {
4927 var value, min, max;
4929 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4933 NETDATA.gaugeSetLabels(state, null, null, null);
4936 value = data.result[0];
4938 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4939 if(value > max) max = value;
4940 NETDATA.gaugeSetLabels(state, value, min, max);
4943 NETDATA.gaugeSet(state, value, min, max);
4947 NETDATA.gaugeChartCreate = function(state, data) {
4948 var self = $(state.element);
4949 // var chart = $(state.element_chart);
4951 var value = data.result[0];
4952 var max = self.data('gauge-max-value') || null;
4953 var adjust = self.data('gauge-adjust') || null;
4954 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4955 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4956 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4957 var stopColor = self.data('gauge-stop-color') || void 0;
4958 var generateGradient = self.data('gauge-generate-gradient') || false;
4962 state.gaugeMax = null;
4965 state.gaugeMax = max;
4967 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4969 // case 'width': width = height * ratio; break;
4971 // default: height = width / ratio; break;
4973 //state.element.style.width = width.toString() + 'px';
4974 //state.element.style.height = height.toString() + 'px';
4979 lines: 12, // The number of lines to draw
4980 angle: 0.15, // The length of each line
4981 lineWidth: 0.44, // 0.44 The line thickness
4983 length: 0.8, // 0.9 The radius of the inner circle
4984 strokeWidth: 0.035, // The rotation offset
4985 color: pointerColor // Fill color
4987 colorStart: startColor, // Colors
4988 colorStop: stopColor, // just experiment with them
4989 strokeColor: strokeColor, // to see which ones work best for you
4991 generateGradient: generateGradient,
4995 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4996 options.percentColors = [
4997 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4998 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4999 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5000 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5001 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5002 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5003 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5004 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5005 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5006 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5007 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5010 state.gauge_canvas = document.createElement('canvas');
5011 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5012 state.gauge_canvas.className = 'gaugeChart';
5013 state.gauge_canvas.width = width;
5014 state.gauge_canvas.height = height;
5015 state.element_chart.appendChild(state.gauge_canvas);
5017 var valuefontsize = Math.floor(height / 6);
5018 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5019 state.gaugeChartLabel = document.createElement('span');
5020 state.gaugeChartLabel.className = 'gaugeChartLabel';
5021 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5022 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5023 state.element_chart.appendChild(state.gaugeChartLabel);
5025 var titlefontsize = Math.round(valuefontsize / 2);
5027 state.gaugeChartTitle = document.createElement('span');
5028 state.gaugeChartTitle.className = 'gaugeChartTitle';
5029 state.gaugeChartTitle.innerHTML = state.title;
5030 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5031 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5032 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5033 state.element_chart.appendChild(state.gaugeChartTitle);
5035 var unitfontsize = Math.round(titlefontsize * 0.9);
5036 state.gaugeChartUnits = document.createElement('span');
5037 state.gaugeChartUnits.className = 'gaugeChartUnits';
5038 state.gaugeChartUnits.innerHTML = state.units;
5039 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5040 state.element_chart.appendChild(state.gaugeChartUnits);
5042 state.gaugeChartMin = document.createElement('span');
5043 state.gaugeChartMin.className = 'gaugeChartMin';
5044 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5045 state.element_chart.appendChild(state.gaugeChartMin);
5047 state.gaugeChartMax = document.createElement('span');
5048 state.gaugeChartMax.className = 'gaugeChartMax';
5049 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5050 state.element_chart.appendChild(state.gaugeChartMax);
5052 // when we just re-create the chart
5053 // do not animate the first update
5055 if(typeof state.gauge_instance !== 'undefined')
5058 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5060 state.___gaugeOld__ = {
5069 // we will always feed a percentage
5070 state.gauge_instance.minValue = 0;
5071 state.gauge_instance.maxValue = 100;
5073 NETDATA.gaugeAnimation(state, animate);
5074 NETDATA.gaugeSet(state, value, 0, max);
5075 NETDATA.gaugeSetLabels(state, value, 0, max);
5076 NETDATA.gaugeAnimation(state, true);
5080 // ----------------------------------------------------------------------------------------------------------------
5081 // Charts Libraries Registration
5083 NETDATA.chartLibraries = {
5085 initialize: NETDATA.dygraphInitialize,
5086 create: NETDATA.dygraphChartCreate,
5087 update: NETDATA.dygraphChartUpdate,
5088 resize: function(state) {
5089 if(typeof state.dygraph_instance.resize === 'function')
5090 state.dygraph_instance.resize();
5092 setSelection: NETDATA.dygraphSetSelection,
5093 clearSelection: NETDATA.dygraphClearSelection,
5094 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5097 format: function(state) { return 'json'; },
5098 options: function(state) { return 'ms|flip'; },
5099 legend: function(state) {
5100 if(this.isSparkline(state) === false)
5101 return 'right-side';
5105 autoresize: function(state) { return true; },
5106 max_updates_to_recreate: function(state) { return 5000; },
5107 track_colors: function(state) { return true; },
5108 pixels_per_point: function(state) {
5109 if(this.isSparkline(state) === false)
5115 isSparkline: function(state) {
5116 if(typeof state.dygraph_sparkline === 'undefined') {
5117 var t = $(state.element).data('dygraph-theme');
5118 if(t === 'sparkline')
5119 state.dygraph_sparkline = true;
5121 state.dygraph_sparkline = false;
5123 return state.dygraph_sparkline;
5127 initialize: NETDATA.sparklineInitialize,
5128 create: NETDATA.sparklineChartCreate,
5129 update: NETDATA.sparklineChartUpdate,
5131 setSelection: undefined, // function(state, t) { return true; },
5132 clearSelection: undefined, // function(state) { return true; },
5133 toolboxPanAndZoom: null,
5136 format: function(state) { return 'array'; },
5137 options: function(state) { return 'flip|abs'; },
5138 legend: function(state) { return null; },
5139 autoresize: function(state) { return false; },
5140 max_updates_to_recreate: function(state) { return 5000; },
5141 track_colors: function(state) { return false; },
5142 pixels_per_point: function(state) { return 3; }
5145 initialize: NETDATA.peityInitialize,
5146 create: NETDATA.peityChartCreate,
5147 update: NETDATA.peityChartUpdate,
5149 setSelection: undefined, // function(state, t) { return true; },
5150 clearSelection: undefined, // function(state) { return true; },
5151 toolboxPanAndZoom: null,
5154 format: function(state) { return 'ssvcomma'; },
5155 options: function(state) { return 'null2zero|flip|abs'; },
5156 legend: function(state) { return null; },
5157 autoresize: function(state) { return false; },
5158 max_updates_to_recreate: function(state) { return 5000; },
5159 track_colors: function(state) { return false; },
5160 pixels_per_point: function(state) { return 3; }
5163 initialize: NETDATA.morrisInitialize,
5164 create: NETDATA.morrisChartCreate,
5165 update: NETDATA.morrisChartUpdate,
5167 setSelection: undefined, // function(state, t) { return true; },
5168 clearSelection: undefined, // function(state) { return true; },
5169 toolboxPanAndZoom: null,
5172 format: function(state) { return 'json'; },
5173 options: function(state) { return 'objectrows|ms'; },
5174 legend: function(state) { return null; },
5175 autoresize: function(state) { return false; },
5176 max_updates_to_recreate: function(state) { return 50; },
5177 track_colors: function(state) { return false; },
5178 pixels_per_point: function(state) { return 15; }
5181 initialize: NETDATA.googleInitialize,
5182 create: NETDATA.googleChartCreate,
5183 update: NETDATA.googleChartUpdate,
5185 setSelection: undefined, //function(state, t) { return true; },
5186 clearSelection: undefined, //function(state) { return true; },
5187 toolboxPanAndZoom: null,
5190 format: function(state) { return 'datatable'; },
5191 options: function(state) { return ''; },
5192 legend: function(state) { return null; },
5193 autoresize: function(state) { return false; },
5194 max_updates_to_recreate: function(state) { return 300; },
5195 track_colors: function(state) { return false; },
5196 pixels_per_point: function(state) { return 4; }
5199 initialize: NETDATA.raphaelInitialize,
5200 create: NETDATA.raphaelChartCreate,
5201 update: NETDATA.raphaelChartUpdate,
5203 setSelection: undefined, // function(state, t) { return true; },
5204 clearSelection: undefined, // function(state) { return true; },
5205 toolboxPanAndZoom: null,
5208 format: function(state) { return 'json'; },
5209 options: function(state) { return ''; },
5210 legend: function(state) { return null; },
5211 autoresize: function(state) { return false; },
5212 max_updates_to_recreate: function(state) { return 5000; },
5213 track_colors: function(state) { return false; },
5214 pixels_per_point: function(state) { return 3; }
5217 initialize: NETDATA.c3Initialize,
5218 create: NETDATA.c3ChartCreate,
5219 update: NETDATA.c3ChartUpdate,
5221 setSelection: undefined, // function(state, t) { return true; },
5222 clearSelection: undefined, // function(state) { return true; },
5223 toolboxPanAndZoom: null,
5226 format: function(state) { return 'csvjsonarray'; },
5227 options: function(state) { return 'milliseconds'; },
5228 legend: function(state) { return null; },
5229 autoresize: function(state) { return false; },
5230 max_updates_to_recreate: function(state) { return 5000; },
5231 track_colors: function(state) { return false; },
5232 pixels_per_point: function(state) { return 15; }
5235 initialize: NETDATA.d3Initialize,
5236 create: NETDATA.d3ChartCreate,
5237 update: NETDATA.d3ChartUpdate,
5239 setSelection: undefined, // function(state, t) { return true; },
5240 clearSelection: undefined, // function(state) { return true; },
5241 toolboxPanAndZoom: null,
5244 format: function(state) { return 'json'; },
5245 options: function(state) { return ''; },
5246 legend: function(state) { return null; },
5247 autoresize: function(state) { return false; },
5248 max_updates_to_recreate: function(state) { return 5000; },
5249 track_colors: function(state) { return false; },
5250 pixels_per_point: function(state) { return 3; }
5253 initialize: NETDATA.easypiechartInitialize,
5254 create: NETDATA.easypiechartChartCreate,
5255 update: NETDATA.easypiechartChartUpdate,
5257 setSelection: NETDATA.easypiechartSetSelection,
5258 clearSelection: NETDATA.easypiechartClearSelection,
5259 toolboxPanAndZoom: null,
5262 format: function(state) { return 'array'; },
5263 options: function(state) { return 'absolute'; },
5264 legend: function(state) { return null; },
5265 autoresize: function(state) { return false; },
5266 max_updates_to_recreate: function(state) { return 5000; },
5267 track_colors: function(state) { return true; },
5268 pixels_per_point: function(state) { return 3; },
5272 initialize: NETDATA.gaugeInitialize,
5273 create: NETDATA.gaugeChartCreate,
5274 update: NETDATA.gaugeChartUpdate,
5276 setSelection: NETDATA.gaugeSetSelection,
5277 clearSelection: NETDATA.gaugeClearSelection,
5278 toolboxPanAndZoom: null,
5281 format: function(state) { return 'array'; },
5282 options: function(state) { return 'absolute'; },
5283 legend: function(state) { return null; },
5284 autoresize: function(state) { return false; },
5285 max_updates_to_recreate: function(state) { return 5000; },
5286 track_colors: function(state) { return true; },
5287 pixels_per_point: function(state) { return 3; },
5292 NETDATA.registerChartLibrary = function(library, url) {
5293 if(NETDATA.options.debug.libraries === true)
5294 console.log("registering chart library: " + library);
5296 NETDATA.chartLibraries[library].url = url;
5297 NETDATA.chartLibraries[library].initialized = true;
5298 NETDATA.chartLibraries[library].enabled = true;
5301 // ----------------------------------------------------------------------------------------------------------------
5304 NETDATA.requiredJs = [
5306 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5307 isAlreadyLoaded: function() {
5308 if(typeof $().emulateTransitionEnd == 'function')
5311 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5319 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5320 isAlreadyLoaded: function() { return false; }
5323 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5324 isAlreadyLoaded: function() { return false; }
5328 NETDATA.requiredCSS = [
5330 url: NETDATA.themes.current.bootstrap_css,
5331 isAlreadyLoaded: function() {
5332 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5339 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5340 isAlreadyLoaded: function() { return false; }
5343 url: NETDATA.themes.current.dashboard_css,
5344 isAlreadyLoaded: function() { return false; }
5347 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5348 isAlreadyLoaded: function() { return false; }
5352 NETDATA.loadRequiredJs = function(index, callback) {
5353 if(index >= NETDATA.requiredJs.length) {
5354 if(typeof callback === 'function')
5359 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5360 NETDATA.loadRequiredJs(++index, callback);
5364 if(NETDATA.options.debug.main_loop === true)
5365 console.log('loading ' + NETDATA.requiredJs[index].url);
5368 url: NETDATA.requiredJs[index].url,
5372 .success(function() {
5373 if(NETDATA.options.debug.main_loop === true)
5374 console.log('loaded ' + NETDATA.requiredJs[index].url);
5376 NETDATA.loadRequiredJs(++index, callback);
5379 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5383 NETDATA.loadRequiredCSS = function(index) {
5384 if(index >= NETDATA.requiredCSS.length)
5387 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5388 NETDATA.loadRequiredCSS(++index);
5392 if(NETDATA.options.debug.main_loop === true)
5393 console.log('loading ' + NETDATA.requiredCSS[index].url);
5395 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5396 NETDATA.loadRequiredCSS(++index);
5399 NETDATA.errorReset();
5400 NETDATA.loadRequiredCSS(0);
5402 NETDATA._loadjQuery(function() {
5403 NETDATA.loadRequiredJs(0, function() {
5404 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5405 if(NETDATA.options.debug.main_loop === true)
5406 console.log('starting chart refresh thread');
5413 // window.NETDATA = NETDATA;
5414 // })(window, document);