// SPDX-License-Identifier: GPL-3.0-or-later
// DO NOT EDIT: This file is automatically generated from the source files in src/
// ----------------------------------------------------------------------------
// You can set the following variables before loading this script:
// 'use strict';
/*global netdataNoDygraphs *//* boolean, disable dygraph charts
* (default: false) */
/*global netdataNoSparklines *//* boolean, disable sparkline charts
* (default: false) */
/*global netdataNoPeitys *//* boolean, disable peity charts
* (default: false) */
/*global netdataNoGoogleCharts *//* boolean, disable google charts
* (default: false) */
/*global netdataNoMorris *//* boolean, disable morris charts
* (default: false) */
/*global netdataNoEasyPieChart *//* boolean, disable easypiechart charts
* (default: false) */
/*global netdataNoGauge *//* boolean, disable gauge.js charts
* (default: false) */
/*global netdataNoD3 *//* boolean, disable d3 charts
* (default: false) */
/*global netdataNoC3 *//* boolean, disable c3 charts
* (default: false) */
/*global netdataNoD3pie *//* boolean, disable d3pie charts
* (default: false) */
/*global netdataNoBootstrap *//* boolean, disable bootstrap - disables help too
* (default: false) */
/*global netdataNoFontAwesome *//* boolean, disable fontawesome (do not load it)
* (default: false) */
/*global netdataIcons *//* object, overwrite netdata fontawesome icons
* (default: null) */
/*global netdataDontStart *//* boolean, do not start the thread to process the charts
* (default: false) */
/*global netdataErrorCallback *//* function, callback to be called when the dashboard encounters an error
* (default: null) */
/*global netdataRegistry:true *//* boolean, use the netdata registry
* (default: false) */
/*global netdataNoRegistry *//* boolean, included only for compatibility with existing custom dashboard
* (obsolete - do not use this any more) */
/*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
* (default: null) */
/*global netdataShowHelp:true *//* boolean, disable charts help
* (default: true) */
/*global netdataShowAlarms:true *//* boolean, enable alarms checks and notifications
* (default: false) */
/*global netdataRegistryAfterMs:true *//* ms, delay registry use at started
* (default: 1500) */
/*global netdataCallback *//* function, callback to be called when netdata is ready to start
* (default: null)
* netdata will be running while this is called
* (call NETDATA.pause to stop it) */
/*global netdataPrepCallback *//* function, callback to be called before netdata does anything else
* (default: null) */
/*global netdataServer *//* string, the URL of the netdata server to use
* (default: the URL the page is hosted at) */
/*global netdataServerStatic *//* string, the URL of the netdata server to use for static files
* (default: netdataServer) */
/*global netdataSnapshotData *//* object, a netdata snapshot loaded
* (default: null) */
/*global netdataAlarmsRecipients *//* array, an array of alarm recipients to show notifications for
* (default: null) */
/*global netdataAlarmsRemember *//* boolen, keep our position in the alarm log at browser local storage
* (default: true) */
/*global netdataAlarmsActiveCallback *//* function, a hook for the alarm logs
* (default: undefined) */
/*global netdataAlarmsNotifCallback *//* function, a hook for alarm notifications
* (default: undefined) */
/*global netdataIntersectionObserver *//* boolean, enable or disable the use of intersection observer
* (default: true) */
/*global netdataCheckXSS *//* boolean, enable or disable checking for XSS issues
* (default: false) */
// ----------------------------------------------------------------------------
// global namespace
// Should stay var!
var NETDATA = window.NETDATA || {};
(function(window, document, $, undefined) {
// *** src/dashboard.js/utils.js
NETDATA.name2id = function (s) {
return s
.replace(/ /g, '_')
.replace(/:/g, '_')
.replace(/\(/g, '_')
.replace(/\)/g, '_')
.replace(/\./g, '_')
.replace(/\//g, '_');
};
NETDATA.encodeURIComponent = function (s) {
if (typeof(s) === 'string') {
return encodeURIComponent(s);
}
return s;
};
/// A heuristic for detecting slow devices.
let isSlowDeviceResult = undefined;
const isSlowDevice = function () {
if (!isSlowDeviceResult) {
return isSlowDeviceResult;
}
try {
let ua = navigator.userAgent.toLowerCase();
let iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
let android = /android/.test(ua) && !window.MSStream;
isSlowDeviceResult = (iOS || android);
} catch (e) {
isSlowDeviceResult = false;
}
return isSlowDeviceResult;
};
NETDATA.guid = function () {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
};
NETDATA.zeropad = function (x) {
if (x > -10 && x < 10) {
return '0' + x.toString();
} else {
return x.toString();
}
};
NETDATA.seconds4human = function (seconds, options) {
let defaultOptions = {
now: 'now',
space: ' ',
negative_suffix: 'ago',
day: 'day',
days: 'days',
hour: 'hour',
hours: 'hours',
minute: 'min',
minutes: 'mins',
second: 'sec',
seconds: 'secs',
and: 'and'
};
if (typeof options !== 'object') {
options = defaultOptions;
} else {
for (var x in defaultOptions) {
if (typeof options[x] !== 'string') {
options[x] = defaultOptions[x];
}
}
}
if (typeof seconds === 'string') {
seconds = parseInt(seconds, 10);
}
if (seconds === 0) {
return options.now;
}
let suffix = '';
if (seconds < 0) {
seconds = -seconds;
if (options.negative_suffix !== '') {
suffix = options.space + options.negative_suffix;
}
}
let days = Math.floor(seconds / 86400);
seconds -= (days * 86400);
let hours = Math.floor(seconds / 3600);
seconds -= (hours * 3600);
let minutes = Math.floor(seconds / 60);
seconds -= (minutes * 60);
let strings = [];
if (days > 1) {
strings.push(days.toString() + options.space + options.days);
} else if (days === 1) {
strings.push(days.toString() + options.space + options.day);
}
if (hours > 1) {
strings.push(hours.toString() + options.space + options.hours);
} else if (hours === 1) {
strings.push(hours.toString() + options.space + options.hour);
}
if (minutes > 1) {
strings.push(minutes.toString() + options.space + options.minutes);
} else if (minutes === 1) {
strings.push(minutes.toString() + options.space + options.minute);
}
if (seconds > 1) {
strings.push(Math.floor(seconds).toString() + options.space + options.seconds);
} else if (seconds === 1) {
strings.push(Math.floor(seconds).toString() + options.space + options.second);
}
if (strings.length === 1) {
return strings.pop() + suffix;
}
let last = strings.pop();
return strings.join(", ") + " " + options.and + " " + last + suffix;
};
// ----------------------------------------------------------------------------------------------------------------
// element data attributes
NETDATA.dataAttribute = function (element, attribute, def) {
let key = 'data-' + attribute.toString();
if (element.hasAttribute(key)) {
let data = element.getAttribute(key);
if (data === 'true') {
return true;
}
if (data === 'false') {
return false;
}
if (data === 'null') {
return null;
}
// Only convert to a number if it doesn't change the string
if (data === +data + '') {
return +data;
}
if (/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test(data)) {
return JSON.parse(data);
}
return data;
} else {
return def;
}
};
NETDATA.dataAttributeBoolean = function (element, attribute, def) {
let value = NETDATA.dataAttribute(element, attribute, def);
if (value === true || value === false) // gmosx: Love this :)
{
return value;
}
if (typeof(value) === 'string') {
if (value === 'yes' || value === 'on') {
return true;
}
if (value === '' || value === 'no' || value === 'off' || value === 'null') {
return false;
}
return def;
}
if (typeof(value) === 'number') {
return value !== 0;
}
return def;
};
// ----------------------------------------------------------------------------------------------------------------
// fast numbers formatting
NETDATA.fastNumberFormat = {
formattersFixed: [],
formattersZeroBased: [],
// this is the fastest and the preferred
getIntlNumberFormat: function (min, max) {
let key = max;
if (min === max) {
if (typeof this.formattersFixed[key] === 'undefined') {
this.formattersFixed[key] = new Intl.NumberFormat(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
return this.formattersFixed[key];
} else if (min === 0) {
if (typeof this.formattersZeroBased[key] === 'undefined') {
this.formattersZeroBased[key] = new Intl.NumberFormat(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
return this.formattersZeroBased[key];
} else {
// this is never used
// it is added just for completeness
return new Intl.NumberFormat(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
},
// this respects locale
getLocaleString: function (min, max) {
let key = max;
if (min === max) {
if (typeof this.formattersFixed[key] === 'undefined') {
this.formattersFixed[key] = {
format: function (value) {
return value.toLocaleString(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
};
}
return this.formattersFixed[key];
} else if (min === 0) {
if (typeof this.formattersZeroBased[key] === 'undefined') {
this.formattersZeroBased[key] = {
format: function (value) {
return value.toLocaleString(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
};
}
return this.formattersZeroBased[key];
} else {
return {
format: function (value) {
return value.toLocaleString(undefined, {
// style: 'decimal',
// minimumIntegerDigits: 1,
// minimumSignificantDigits: 1,
// maximumSignificantDigits: 1,
useGrouping: true,
minimumFractionDigits: min,
maximumFractionDigits: max
});
}
};
}
},
// the fallback
getFixed: function (min, max) {
let key = max;
if (min === max) {
if (typeof this.formattersFixed[key] === 'undefined') {
this.formattersFixed[key] = {
format: function (value) {
if (value === 0) {
return "0";
}
return value.toFixed(max);
}
};
}
return this.formattersFixed[key];
} else if (min === 0) {
if (typeof this.formattersZeroBased[key] === 'undefined') {
this.formattersZeroBased[key] = {
format: function (value) {
if (value === 0) {
return "0";
}
return value.toFixed(max);
}
};
}
return this.formattersZeroBased[key];
} else {
return {
format: function (value) {
if (value === 0) {
return "0";
}
return value.toFixed(max);
}
};
}
},
testIntlNumberFormat: function () {
let value = 1.12345;
let e1 = "1.12", e2 = "1,12";
let s = "";
try {
let x = new Intl.NumberFormat(undefined, {
useGrouping: true,
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
s = x.format(value);
} catch (e) {
s = "";
}
// console.log('NumberFormat: ', s);
return (s === e1 || s === e2);
},
testLocaleString: function () {
let value = 1.12345;
let e1 = "1.12", e2 = "1,12";
let s = "";
try {
s = value.toLocaleString(undefined, {
useGrouping: true,
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
} catch (e) {
s = "";
}
// console.log('localeString: ', s);
return (s === e1 || s === e2);
},
// on first run we decide which formatter to use
get: function (min, max) {
if (this.testIntlNumberFormat()) {
// console.log('numberformat');
this.get = this.getIntlNumberFormat;
} else if (this.testLocaleString()) {
// console.log('localestring');
this.get = this.getLocaleString;
} else {
// console.log('fixed');
this.get = this.getFixed;
}
return this.get(min, max);
}
};
// ----------------------------------------------------------------------------------------------------------------
// Detect the netdata server
// http://stackoverflow.com/questions/984510/what-is-my-script-src-url
// http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
NETDATA._scriptSource = function () {
let script = null;
if (typeof document.currentScript !== 'undefined') {
script = document.currentScript;
} else {
const all_scripts = document.getElementsByTagName('script');
script = all_scripts[all_scripts.length - 1];
}
if (typeof script.getAttribute.length !== 'undefined') {
script = script.src;
} else {
script = script.getAttribute('src', -1);
}
return script;
};
// *** src/dashboard.js/server-detection.js
if (typeof netdataServer !== 'undefined') {
NETDATA.serverDefault = netdataServer;
} else {
let s = NETDATA._scriptSource();
if (s) {
NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)?$/g, "");
} else {
console.log('WARNING: Cannot detect the URL of the netdata server.');
NETDATA.serverDefault = null;
}
}
if (NETDATA.serverDefault === null) {
NETDATA.serverDefault = '';
} else if (NETDATA.serverDefault.slice(-1) !== '/') {
NETDATA.serverDefault += '/';
}
if (typeof netdataServerStatic !== 'undefined' && netdataServerStatic !== null && netdataServerStatic !== '') {
NETDATA.serverStatic = netdataServerStatic;
if (NETDATA.serverStatic.slice(-1) !== '/') {
NETDATA.serverStatic += '/';
}
} else {
NETDATA.serverStatic = NETDATA.serverDefault;
}
// *** src/dashboard.js/dependencies.js
// default URLs for all the external files we need
// make them RELATIVE so that the whole thing can also be
// installed under a web server
NETDATA.jQuery = NETDATA.serverStatic + 'lib/jquery-2.2.4.min.js';
NETDATA.peity_js = NETDATA.serverStatic + 'lib/jquery.peity-3.2.0.min.js';
NETDATA.sparkline_js = NETDATA.serverStatic + 'lib/jquery.sparkline-2.1.2.min.js';
NETDATA.easypiechart_js = NETDATA.serverStatic + 'lib/jquery.easypiechart-97b5824.min.js';
NETDATA.gauge_js = NETDATA.serverStatic + 'lib/gauge-1.3.2.min.js';
NETDATA.dygraph_js = NETDATA.serverStatic + 'lib/dygraph-c91c859.min.js';
NETDATA.dygraph_smooth_js = NETDATA.serverStatic + 'lib/dygraph-smooth-plotter-c91c859.js';
// NETDATA.raphael_js = NETDATA.serverStatic + 'lib/raphael-2.2.4-min.js';
// NETDATA.c3_js = NETDATA.serverStatic + 'lib/c3-0.4.18.min.js';
// NETDATA.c3_css = NETDATA.serverStatic + 'css/c3-0.4.18.min.css';
NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-3.js';
NETDATA.d3_js = NETDATA.serverStatic + 'lib/d3-4.12.2.min.js';
// NETDATA.morris_js = NETDATA.serverStatic + 'lib/morris-0.5.1.min.js';
// NETDATA.morris_css = NETDATA.serverStatic + 'css/morris-0.5.1.css';
NETDATA.google_js = 'https://www.google.com/jsapi';
// Error Handling
NETDATA.errorCodes = {
100: {message: "Cannot load chart library", alert: true},
101: {message: "Cannot load jQuery", alert: true},
402: {message: "Chart library not found", alert: false},
403: {message: "Chart library not enabled/is failed", alert: false},
404: {message: "Chart not found", alert: false},
405: {message: "Cannot download charts index from server", alert: true},
406: {message: "Invalid charts index downloaded from server", alert: true},
407: {message: "Cannot HELLO netdata server", alert: false},
408: {message: "Netdata servers sent invalid response to HELLO", alert: false},
409: {message: "Cannot ACCESS netdata registry", alert: false},
410: {message: "Netdata registry ACCESS failed", alert: false},
411: {message: "Netdata registry server send invalid response to DELETE ", alert: false},
412: {message: "Netdata registry DELETE failed", alert: false},
413: {message: "Netdata registry server send invalid response to SWITCH ", alert: false},
414: {message: "Netdata registry SWITCH failed", alert: false},
415: {message: "Netdata alarms download failed", alert: false},
416: {message: "Netdata alarms log download failed", alert: false},
417: {message: "Netdata registry server send invalid response to SEARCH ", alert: false},
418: {message: "Netdata registry SEARCH failed", alert: false}
};
NETDATA.errorLast = {
code: 0,
message: "",
datetime: 0
};
NETDATA.error = function (code, msg) {
NETDATA.errorLast.code = code;
NETDATA.errorLast.message = msg;
NETDATA.errorLast.datetime = Date.now();
console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
let ret = true;
if (typeof netdataErrorCallback === 'function') {
ret = netdataErrorCallback('system', code, msg);
}
if (ret && NETDATA.errorCodes[code].alert) {
alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
}
};
NETDATA.errorReset = function () {
NETDATA.errorLast.code = 0;
NETDATA.errorLast.message = "You are doing fine!";
NETDATA.errorLast.datetime = 0;
};
// *** src/dashboard.js/compatibility.js
// Compatibility fixes.
// fix IE issue with console
if (!window.console) {
window.console = {
log: function () {
}
};
}
// if string.endsWith is not defined, define it
if (typeof String.prototype.endsWith !== 'function') {
String.prototype.endsWith = function (s) {
if (s.length > this.length) {
return false;
}
return this.slice(-s.length) === s;
};
}
// if string.startsWith is not defined, define it
if (typeof String.prototype.startsWith !== 'function') {
String.prototype.startsWith = function (s) {
if (s.length > this.length) {
return false;
}
return this.slice(s.length) === s;
};
}
// ----------------------------------------------------------------------------------------------------------------
// XSS checks
NETDATA.xss = {
enabled: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
enabled_for_data: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
string: function (s) {
return s.toString()
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
object: function (name, obj, ignore_regex) {
if (typeof ignore_regex !== 'undefined' && ignore_regex.test(name)) {
// console.log('XSS: ignoring "' + name + '"');
return obj;
}
switch (typeof(obj)) {
case 'string':
const ret = this.string(obj);
if (ret !== obj) {
console.log('XSS protection changed string ' + name + ' from "' + obj + '" to "' + ret + '"');
}
return ret;
case 'object':
if (obj === null) {
return obj;
}
if (Array.isArray(obj)) {
// console.log('checking array "' + name + '"');
let len = obj.length;
while (len--) {
obj[len] = this.object(name + '[' + len + ']', obj[len], ignore_regex);
}
} else {
// console.log('checking object "' + name + '"');
for (var i in obj) {
if (obj.hasOwnProperty(i) === false) {
continue;
}
if (this.string(i) !== i) {
console.log('XSS protection removed invalid object member "' + name + '.' + i + '"');
delete obj[i];
} else {
obj[i] = this.object(name + '.' + i, obj[i], ignore_regex);
}
}
}
return obj;
default:
return obj;
}
},
checkOptional: function (name, obj, ignore_regex) {
if (this.enabled) {
//console.log('XSS: checking optional "' + name + '"...');
return this.object(name, obj, ignore_regex);
}
return obj;
},
checkAlways: function (name, obj, ignore_regex) {
//console.log('XSS: checking always "' + name + '"...');
return this.object(name, obj, ignore_regex);
},
checkData: function (name, obj, ignore_regex) {
if (this.enabled_for_data) {
//console.log('XSS: checking data "' + name + '"...');
return this.object(name, obj, ignore_regex);
}
return obj;
}
};
NETDATA.colorHex2Rgb = function (hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
NETDATA.colorLuminance = function (hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
let rgb = "#";
for (let i = 0; i < 3; i++) {
let c = parseInt(hex.substr(i * 2, 2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00" + c).substr(c.length);
}
return rgb;
};
NETDATA.unitsConversion = {
keys: {}, // keys for data-common-units
latest: {}, // latest selected units for data-common-units
globalReset: function () {
this.keys = {};
this.latest = {};
},
scalableUnits: {
'packets/s': {
'pps': 1,
'Kpps': 1000,
'Mpps': 1000000
},
'pps': {
'pps': 1,
'Kpps': 1000,
'Mpps': 1000000
},
'kilobits/s': {
'bits/s': 1 / 1000,
'kilobits/s': 1,
'megabits/s': 1000,
'gigabits/s': 1000000,
'terabits/s': 1000000000
},
'kilobytes/s': {
'bytes/s': 1 / 1024,
'kilobytes/s': 1,
'megabytes/s': 1024,
'gigabytes/s': 1024 * 1024,
'terabytes/s': 1024 * 1024 * 1024
},
'KB/s': {
'B/s': 1 / 1024,
'KB/s': 1,
'MB/s': 1024,
'GB/s': 1024 * 1024,
'TB/s': 1024 * 1024 * 1024
},
'KiB/s': {
'B/s': 1 / 1024,
'KiB/s': 1,
'MiB/s': 1024,
'GiB/s': 1024 * 1024,
'TiB/s': 1024 * 1024 * 1024
},
'B': {
'B': 1,
'KiB': 1024,
'MiB': 1024 * 1024,
'GiB': 1024 * 1024 * 1024,
'TiB': 1024 * 1024 * 1024 * 1024,
'PiB': 1024 * 1024 * 1024 * 1024 * 1024
},
'KB': {
'B': 1 / 1024,
'KB': 1,
'MB': 1024,
'GB': 1024 * 1024,
'TB': 1024 * 1024 * 1024
},
'KiB': {
'B': 1 / 1024,
'KiB': 1,
'MiB': 1024,
'GiB': 1024 * 1024,
'TiB': 1024 * 1024 * 1024
},
'MB': {
'B': 1 / (1024 * 1024),
'KB': 1 / 1024,
'MB': 1,
'GB': 1024,
'TB': 1024 * 1024,
'PB': 1024 * 1024 * 1024
},
'MiB': {
'B': 1 / (1024 * 1024),
'KiB': 1 / 1024,
'MiB': 1,
'GiB': 1024,
'TiB': 1024 * 1024,
'PiB': 1024 * 1024 * 1024
},
'GB': {
'B': 1 / (1024 * 1024 * 1024),
'KB': 1 / (1024 * 1024),
'MB': 1 / 1024,
'GB': 1,
'TB': 1024,
'PB': 1024 * 1024,
'EB': 1024 * 1024 * 1024
},
'GiB': {
'B': 1 / (1024 * 1024 * 1024),
'KiB': 1 / (1024 * 1024),
'MiB': 1 / 1024,
'GiB': 1,
'TiB': 1024,
'PiB': 1024 * 1024,
'EiB': 1024 * 1024 * 1024
}
/*
'milliseconds': {
'seconds': 1000
},
'seconds': {
'milliseconds': 0.001,
'seconds': 1,
'minutes': 60,
'hours': 3600,
'days': 86400
}
*/
},
convertibleUnits: {
'Celsius': {
'Fahrenheit': {
check: function (max) {
void(max);
return NETDATA.options.current.temperature === 'fahrenheit';
},
convert: function (value) {
return value * 9 / 5 + 32;
}
}
},
'celsius': {
'fahrenheit': {
check: function (max) {
void(max);
return NETDATA.options.current.temperature === 'fahrenheit';
},
convert: function (value) {
return value * 9 / 5 + 32;
}
}
},
'seconds': {
'time': {
check: function (max) {
void(max);
return NETDATA.options.current.seconds_as_time;
},
convert: function (seconds) {
return NETDATA.unitsConversion.seconds2time(seconds);
}
}
},
'milliseconds': {
'milliseconds': {
check: function (max) {
return NETDATA.options.current.seconds_as_time && max < 1000;
},
convert: function (milliseconds) {
let tms = Math.round(milliseconds * 10);
milliseconds = Math.floor(tms / 10);
tms -= milliseconds * 10;
return (milliseconds).toString() + '.' + tms.toString();
}
},
'seconds': {
check: function (max) {
return NETDATA.options.current.seconds_as_time && max >= 1000 && max < 60000;
},
convert: function (milliseconds) {
milliseconds = Math.round(milliseconds);
let seconds = Math.floor(milliseconds / 1000);
milliseconds -= seconds * 1000;
milliseconds = Math.round(milliseconds / 10);
return seconds.toString() + '.'
+ NETDATA.zeropad(milliseconds);
}
},
'M:SS.ms': {
check: function (max) {
return NETDATA.options.current.seconds_as_time && max >= 60000;
},
convert: function (milliseconds) {
milliseconds = Math.round(milliseconds);
let minutes = Math.floor(milliseconds / 60000);
milliseconds -= minutes * 60000;
let seconds = Math.floor(milliseconds / 1000);
milliseconds -= seconds * 1000;
milliseconds = Math.round(milliseconds / 10);
return minutes.toString() + ':'
+ NETDATA.zeropad(seconds) + '.'
+ NETDATA.zeropad(milliseconds);
}
}
}
},
seconds2time: function (seconds) {
seconds = Math.abs(seconds);
let days = Math.floor(seconds / 86400);
seconds -= days * 86400;
let hours = Math.floor(seconds / 3600);
seconds -= hours * 3600;
let minutes = Math.floor(seconds / 60);
seconds -= minutes * 60;
seconds = Math.round(seconds);
let ms_txt = '';
/*
let ms = seconds - Math.floor(seconds);
seconds -= ms;
ms = Math.round(ms * 1000);
if (ms > 1) {
if (ms < 10)
ms_txt = '.00' + ms.toString();
else if (ms < 100)
ms_txt = '.0' + ms.toString();
else
ms_txt = '.' + ms.toString();
}
*/
return ((days > 0) ? days.toString() + 'd:' : '').toString()
+ NETDATA.zeropad(hours) + ':'
+ NETDATA.zeropad(minutes) + ':'
+ NETDATA.zeropad(seconds)
+ ms_txt;
},
// get a function that converts the units
// + every time units are switched call the callback
get: function (uuid, min, max, units, desired_units, common_units_name, switch_units_callback) {
// validate the parameters
if (typeof units === 'undefined') {
units = 'undefined';
}
// check if we support units conversion
if (typeof this.scalableUnits[units] === 'undefined' && typeof this.convertibleUnits[units] === 'undefined') {
// we can't convert these units
//console.log('DEBUG: ' + uuid.toString() + ' can\'t convert units: ' + units.toString());
return function (value) {
return value;
};
}
// check if the caller wants the original units
if (typeof desired_units === 'undefined' || desired_units === null || desired_units === 'original' || desired_units === units) {
//console.log('DEBUG: ' + uuid.toString() + ' original units wanted');
switch_units_callback(units);
return function (value) {
return value;
};
}
// now we know we can convert the units
// and the caller wants some kind of conversion
let tunits = null;
let tdivider = 0;
if (typeof this.scalableUnits[units] !== 'undefined') {
// units that can be scaled
// we decide a divider
// console.log('NETDATA.unitsConversion.get(' + units.toString() + ', ' + desired_units.toString() + ', function()) decide divider with min = ' + min.toString() + ', max = ' + max.toString());
if (desired_units === 'auto') {
// the caller wants to auto-scale the units
// find the absolute maximum value that is rendered on the chart
// based on this we decide the scale
min = Math.abs(min);
max = Math.abs(max);
if (min > max) {
max = min;
}
// find the smallest scale that provides integers
// for (x in this.scalableUnits[units]) {
// if (this.scalableUnits[units].hasOwnProperty(x)) {
// let m = this.scalableUnits[units][x];
// if (m <= max && m > tdivider) {
// tunits = x;
// tdivider = m;
// }
// }
// }
const sunit = this.scalableUnits[units];
for (var x of Object.keys(sunit)) {
let m = sunit[x];
if (m <= max && m > tdivider) {
tunits = x;
tdivider = m;
}
}
if (tunits === null || tdivider <= 0) {
// we couldn't find one
//console.log('DEBUG: ' + uuid.toString() + ' cannot find an auto-scaling candidate for units: ' + units.toString() + ' (max: ' + max.toString() + ')');
switch_units_callback(units);
return function (value) {
return value;
};
}
if (typeof common_units_name === 'string' && typeof uuid === 'string') {
// the caller wants several charts to have the same units
// data-common-units
let common_units_key = common_units_name + '-' + units;
// add our divider into the list of keys
let t = this.keys[common_units_key];
if (typeof t === 'undefined') {
this.keys[common_units_key] = {};
t = this.keys[common_units_key];
}
t[uuid] = {
units: tunits,
divider: tdivider
};
// find the max divider of all charts
let common_units = t[uuid];
for (var x in t) {
if (t.hasOwnProperty(x) && t[x].divider > common_units.divider) {
common_units = t[x];
}
}
// save our common_max to the latest keys
let latest = this.latest[common_units_key];
if (typeof latest === 'undefined') {
this.latest[common_units_key] = {};
latest = this.latest[common_units_key];
}
latest.units = common_units.units;
latest.divider = common_units.divider;
tunits = latest.units;
tdivider = latest.divider;
//console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', common-units=' + common_units_name.toString() + ((t[uuid].divider !== tdivider)?' USED COMMON, mine was ' + t[uuid].units:' set common').toString());
// apply it to this chart
switch_units_callback(tunits);
return function (value) {
if (tdivider !== latest.divider) {
// another chart switched our common units
// we should switch them too
//console.log('DEBUG: ' + uuid + ' switching units due to a common-units change, from ' + tunits.toString() + ' to ' + latest.units.toString());
tunits = latest.units;
tdivider = latest.divider;
switch_units_callback(tunits);
}
return value / tdivider;
};
} else {
// the caller did not give data-common-units
// this chart auto-scales independently of all others
//console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', autonomously');
switch_units_callback(tunits);
return function (value) {
return value / tdivider;
};
}
} else {
// the caller wants specific units
if (typeof this.scalableUnits[units][desired_units] !== 'undefined') {
// all good, set the new units
tdivider = this.scalableUnits[units][desired_units];
// console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + desired_units.toString() + ' with divider ' + tdivider.toString() + ', by reference');
switch_units_callback(desired_units);
return function (value) {
return value / tdivider;
};
} else {
// oops! switch back to original units
console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
switch_units_callback(units);
return function (value) {
return value;
};
}
}
} else if (typeof this.convertibleUnits[units] !== 'undefined') {
// units that can be converted
if (desired_units === 'auto') {
for (var x in this.convertibleUnits[units]) {
if (this.convertibleUnits[units].hasOwnProperty(x)) {
if (this.convertibleUnits[units][x].check(max)) {
//console.log('DEBUG: ' + uuid.toString() + ' converting ' + units.toString() + ' to: ' + x.toString());
switch_units_callback(x);
return this.convertibleUnits[units][x].convert;
}
}
}
// none checked ok
//console.log('DEBUG: ' + uuid.toString() + ' no conversion available for ' + units.toString() + ' to: ' + desired_units.toString());
switch_units_callback(units);
return function (value) {
return value;
};
} else if (typeof this.convertibleUnits[units][desired_units] !== 'undefined') {
switch_units_callback(desired_units);
return this.convertibleUnits[units][desired_units].convert;
} else {
console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
switch_units_callback(units);
return function (value) {
return value;
};
}
} else {
// hm... did we forget to implement the new type?
console.log(`Unmatched unit conversion method for units ${units.toString()}`);
switch_units_callback(units);
return function (value) {
return value;
};
}
}
};
NETDATA.icons = {
left: '',
reset: '',
right: '',
zoomIn: '',
zoomOut: '',
resize: '',
lineChart: '',
areaChart: '',
noChart: '',
loading: '',
noData: ''
};
if (typeof netdataIcons === 'object') {
// for (let icon in NETDATA.icons) {
// if (NETDATA.icons.hasOwnProperty(icon) && typeof(netdataIcons[icon]) === 'string')
// NETDATA.icons[icon] = netdataIcons[icon];
// }
for (var icon of Object.keys(NETDATA.icons)) {
if (typeof(netdataIcons[icon]) === 'string') {
NETDATA.icons[icon] = netdataIcons[icon]
}
}
}
if (typeof netdataSnapshotData === 'undefined') {
netdataSnapshotData = null;
}
if (typeof netdataShowHelp === 'undefined') {
netdataShowHelp = true;
}
if (typeof netdataShowAlarms === 'undefined') {
netdataShowAlarms = false;
}
if (typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0) {
netdataRegistryAfterMs = 0; // 1500;
}
if (typeof netdataRegistry === 'undefined') {
// backward compatibility
netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
}
if (netdataRegistry === false && typeof netdataRegistryCallback === 'function') {
netdataRegistry = true;
}
// ----------------------------------------------------------------------------------------------------------------
// the defaults for all charts
// if the user does not specify any of these, the following will be used
NETDATA.chartDefaults = {
width: '100%', // the chart width - can be null
height: '100%', // the chart height - can be null
min_width: null, // the chart minimum width - can be null
library: 'dygraph', // the graphing library to use
method: 'average', // the grouping method
before: 0, // panning
after: -600, // panning
pixels_per_point: 1, // the detail of the chart
fill_luminance: 0.8 // luminance of colors in solid areas
};
// ----------------------------------------------------------------------------------------------------------------
// global options
NETDATA.options = {
pauseCallback: null, // a callback when we are really paused
pause: false, // when enabled we don't auto-refresh the charts
targets: [], // an array of all the state objects that are
// currently active (independently of their
// viewport visibility)
updated_dom: true, // when true, the DOM has been updated with
// new elements we have to check.
auto_refresher_fast_weight: 0, // this is the current time in ms, spent
// rendering charts continuously.
// used with .current.fast_render_timeframe
page_is_visible: true, // when true, this page is visible
auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
// auto-refresher for some time (when a chart is
// performing pan or zoom, we need to stop refreshing
// all other charts, to have the maximum speed for
// rendering the chart that is panned or zoomed).
// Used with .current.global_pan_sync_time
on_scroll_refresher_stop_until: 0, // timestamp in ms - used to stop evaluating
// charts for some time, after a page scroll
last_page_resize: Date.now(), // the timestamp of the last resize request
last_page_scroll: 0, // the timestamp the last time the page was scrolled
browser_timezone: 'unknown', // timezone detected by javascript
server_timezone: 'unknown', // timezone reported by the server
force_data_points: 0, // force the number of points to be returned for charts
fake_chart_rendering: false, // when set to true, the dashboard will download data but will not render the charts
passive_events: null, // true if the browser supports passive events
// the current profile
// we may have many...
current: {
units: 'auto', // can be 'auto' or 'original'
temperature: 'celsius', // can be 'celsius' or 'fahrenheit'
seconds_as_time: true, // show seconds as DDd:HH:MM:SS ?
timezone: 'default', // the timezone to use, or 'default'
user_set_server_timezone: 'default', // as set by the user on the dashboard
legend_toolbox: true, // show the legend toolbox on charts
resize_charts: true, // show the resize handler on charts
pixels_per_point: isSlowDevice() ? 5 : 1, // the minimum pixels per point for all charts
// increase this to speed javascript up
// each chart library has its own limit too
// the max of this and the chart library is used
// the final is calculated every time, so a change
// here will have immediate effect on the next chart
// update
idle_between_charts: 100, // ms - how much time to wait between chart updates
fast_render_timeframe: 200, // ms - render continuously until this time of continuous
// rendering has been reached
// this setting is used to make it render e.g. 10
// charts at once, sleep idle_between_charts time
// and continue for another 10 charts.
idle_between_loops: 500, // ms - if all charts have been updated, wait this
// time before starting again.
idle_parallel_loops: 100, // ms - the time between parallel refresher updates
idle_lost_focus: 500, // ms - when the window does not have focus, check
// if focus has been regained, every this time
global_pan_sync_time: 300, // ms - when you pan or zoom a chart, the background
// auto-refreshing of charts is paused for this amount
// of time
sync_selection_delay: 400, // ms - when you pan or zoom a chart, wait this amount
// of time before setting up synchronized selections
// on hover.
sync_selection: true, // enable or disable selection sync
pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
sync_pan_and_zoom: true, // enable or disable pan and zoom sync
pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
update_only_visible: true, // enable or disable visibility management / used for printing
parallel_refresher: !isSlowDevice(), // enable parallel refresh of charts
concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
destroy_on_hide: isSlowDevice(), // destroy charts when they are not visible
show_help: netdataShowHelp, // when enabled the charts will show some help
show_help_delay_show_ms: 500,
show_help_delay_hide_ms: 0,
eliminate_zero_dimensions: true, // do not show dimensions with just zeros
stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
smooth_plot: !isSlowDevice(), // enable smooth plot, where possible
color_fill_opacity_line: 1.0,
color_fill_opacity_area: 0.2,
color_fill_opacity_stacked: 0.8,
pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
pan_and_zoom_factor_multiplier_control: 2.0,
pan_and_zoom_factor_multiplier_shift: 3.0,
pan_and_zoom_factor_multiplier_alt: 4.0,
abort_ajax_on_scroll: false, // kill pending ajax page scroll
async_on_scroll: false, // sync/async onscroll handler
onscroll_worker_duration_threshold: 30, // time in ms, for async scroll handler
retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
setOptionCallback: function () {
}
},
debug: {
show_boxes: false,
main_loop: false,
focus: false,
visibility: false,
chart_data_url: false,
chart_errors: true, // remember to set it to false before merging
chart_timing: false,
chart_calls: false,
libraries: false,
dygraph: false,
globalSelectionSync: false,
globalPanAndZoom: false
}
};
NETDATA.statistics = {
refreshes_total: 0,
refreshes_active: 0,
refreshes_active_max: 0
};
// local storage options
NETDATA.localStorage = {
default: {},
current: {},
callback: {} // only used for resetting back to defaults
};
NETDATA.localStorageTested = -1;
NETDATA.localStorageTest = function () {
if (NETDATA.localStorageTested !== -1) {
return NETDATA.localStorageTested;
}
if (typeof Storage !== "undefined" && typeof localStorage === 'object') {
let test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
NETDATA.localStorageTested = true;
} catch (e) {
NETDATA.localStorageTested = false;
}
} else {
NETDATA.localStorageTested = false;
}
return NETDATA.localStorageTested;
};
NETDATA.localStorageGet = function (key, def, callback) {
let ret = def;
if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
NETDATA.localStorage.default[key.toString()] = def;
NETDATA.localStorage.callback[key.toString()] = callback;
}
if (NETDATA.localStorageTest()) {
try {
// console.log('localStorage: loading "' + key.toString() + '"');
ret = localStorage.getItem(key.toString());
// console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
if (ret === null || ret === 'undefined') {
// console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
localStorage.setItem(key.toString(), JSON.stringify(def));
ret = def;
} else {
// console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
ret = JSON.parse(ret);
// console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
}
} catch (error) {
console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
ret = def;
}
}
if (typeof ret === 'undefined' || ret === 'undefined') {
console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
ret = def;
}
NETDATA.localStorage.current[key.toString()] = ret;
return ret;
};
NETDATA.localStorageSet = function (key, value, callback) {
if (typeof value === 'undefined' || value === 'undefined') {
console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
}
if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
NETDATA.localStorage.default[key.toString()] = value;
NETDATA.localStorage.current[key.toString()] = value;
NETDATA.localStorage.callback[key.toString()] = callback;
}
if (NETDATA.localStorageTest()) {
// console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
try {
localStorage.setItem(key.toString(), JSON.stringify(value));
} catch (e) {
console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
}
}
NETDATA.localStorage.current[key.toString()] = value;
return value;
};
NETDATA.localStorageGetRecursive = function (obj, prefix, callback) {
let keys = Object.keys(obj);
let len = keys.length;
while (len--) {
let i = keys[len];
if (typeof obj[i] === 'object') {
//console.log('object ' + prefix + '.' + i.toString());
NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
continue;
}
obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
}
};
NETDATA.setOption = function (key, value) {
if (key.toString() === 'setOptionCallback') {
if (typeof NETDATA.options.current.setOptionCallback === 'function') {
NETDATA.options.current[key.toString()] = value;
NETDATA.options.current.setOptionCallback();
}
} else if (NETDATA.options.current[key.toString()] !== value) {
let name = 'options.' + key.toString();
if (typeof NETDATA.localStorage.default[name.toString()] === 'undefined') {
console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
}
//console.log(NETDATA.localStorage);
//console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
//console.log(NETDATA.options);
NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
if (typeof NETDATA.options.current.setOptionCallback === 'function') {
NETDATA.options.current.setOptionCallback();
}
}
return true;
};
NETDATA.getOption = function (key) {
return NETDATA.options.current[key.toString()];
};
// read settings from local storage
NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
// always start with this option enabled.
NETDATA.setOption('stop_updates_when_focus_is_lost', true);
NETDATA.resetOptions = function () {
let keys = Object.keys(NETDATA.localStorage.default);
let len = keys.length;
while (len--) {
let i = keys[len];
let a = i.split('.');
if (a[0] === 'options') {
if (a[1] === 'setOptionCallback') {
continue;
}
if (typeof NETDATA.localStorage.default[i] === 'undefined') {
continue;
}
if (NETDATA.options.current[i] === NETDATA.localStorage.default[i]) {
continue;
}
NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
} else if (a[0] === 'chart_heights') {
if (typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
}
}
}
NETDATA.dateTime.init(NETDATA.options.current.timezone);
};
// *** src/dashboard.js/timeout.js
// TODO: Better name needed
NETDATA.timeout = {
// by default, these are just wrappers to setTimeout() / clearTimeout()
step: function (callback) {
return window.setTimeout(callback, 1000 / 60);
},
set: function (callback, delay) {
return window.setTimeout(callback, delay);
},
clear: function (id) {
return window.clearTimeout(id);
},
init: function () {
let custom = true;
if (window.requestAnimationFrame) {
this.step = function (callback) {
return window.requestAnimationFrame(callback);
};
this.clear = function (handle) {
return window.cancelAnimationFrame(handle.value);
};
// } else if (window.webkitRequestAnimationFrame) {
// this.step = function (callback) {
// return window.webkitRequestAnimationFrame(callback);
// };
// if (window.webkitCancelAnimationFrame) {
// this.clear = function (handle) {
// return window.webkitCancelAnimationFrame(handle.value);
// };
// } else if (window.webkitCancelRequestAnimationFrame) {
// this.clear = function (handle) {
// return window.webkitCancelRequestAnimationFrame(handle.value);
// };
// }
// } else if (window.mozRequestAnimationFrame) {
// this.step = function (callback) {
// return window.mozRequestAnimationFrame(callback);
// };
// this.clear = function (handle) {
// return window.mozCancelRequestAnimationFrame(handle.value);
// };
// } else if (window.oRequestAnimationFrame) {
// this.step = function (callback) {
// return window.oRequestAnimationFrame(callback);
// };
// this.clear = function (handle) {
// return window.oCancelRequestAnimationFrame(handle.value);
// };
// } else if (window.msRequestAnimationFrame) {
// this.step = function (callback) {
// return window.msRequestAnimationFrame(callback);
// };
// this.clear = function (handle) {
// return window.msCancelRequestAnimationFrame(handle.value);
// };
} else {
custom = false;
}
if (custom) {
// we have installed custom .step() / .clear() functions
// overwrite the .set() too
this.set = function (callback, delay) {
let start = Date.now(),
handle = new Object();
const loop = () => {
let current = Date.now(),
delta = current - start;
if (delta >= delay) {
callback.call();
} else {
handle.value = this.step(loop);
}
}
handle.value = this.step(loop);
return handle;
};
}
}
};
NETDATA.timeout.init();
// Codacy declarations
/* global netdataTheme */
NETDATA.themes = {
white: {
bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-3.3.7.css',
dashboard_css: NETDATA.serverStatic + 'dashboard.css?v20180210-1',
background: '#FFFFFF',
foreground: '#000000',
grid: '#F0F0F0',
axis: '#F0F0F0',
highlight: '#F5F5F5',
colors: ['#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
'#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
'#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
'#329262', '#3B3EAC'],
easypiechart_track: '#f0f0f0',
easypiechart_scale: '#dfe0e0',
gauge_pointer: '#C0C0C0',
gauge_stroke: '#F0F0F0',
gauge_gradient: false,
d3pie: {
title: '#333333',
subtitle: '#666666',
footer: '#888888',
other: '#aaaaaa',
mainlabel: '#333333',
percentage: '#dddddd',
value: '#aaaa22',
tooltip_bg: '#000000',
tooltip_fg: '#efefef',
segment_stroke: "#ffffff",
gradient_color: '#000000'
}
},
slate: {
bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
dashboard_css: NETDATA.serverStatic + 'dashboard.slate.css?v20180210-1',
background: '#272b30',
foreground: '#C8C8C8',
grid: '#283236',
axis: '#283236',
highlight: '#383838',
/* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
'#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
'#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
'#a6a479', '#a66da8' ],
*/
colors: ['#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
'#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
'#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
'#329262', '#3B3EFF'],
easypiechart_track: '#373b40',
easypiechart_scale: '#373b40',
gauge_pointer: '#474b50',
gauge_stroke: '#373b40',
gauge_gradient: false,
d3pie: {
title: '#C8C8C8',
subtitle: '#283236',
footer: '#283236',
other: '#283236',
mainlabel: '#C8C8C8',
percentage: '#dddddd',
value: '#cccc44',
tooltip_bg: '#272b30',
tooltip_fg: '#C8C8C8',
segment_stroke: "#283236",
gradient_color: '#000000'
}
}
};
if (typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined') {
NETDATA.themes.current = NETDATA.themes[netdataTheme];
} else {
NETDATA.themes.current = NETDATA.themes.white;
}
NETDATA.colors = NETDATA.themes.current.colors;
// these are the colors Google Charts are using
// we have them here to attempt emulate their look and feel on the other chart libraries
// http://there4.io/2012/05/02/google-chart-color-list/
//NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
// '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
// '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
// an alternative set
// http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
// (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
//NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
// dygraph
// Codacy declarations
/* global smoothPlotter */
/* global Dygraph */
NETDATA.dygraph = {
smooth: false
};
NETDATA.dygraphToolboxPanAndZoom = function (state, after, before) {
if (after < state.netdata_first) {
after = state.netdata_first;
}
if (before > state.netdata_last) {
before = state.netdata_last;
}
state.setMode('zoom');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
state.tmp.dygraph_force_zoom = true;
// state.log('toolboxPanAndZoom');
state.updateChartPanOrZoom(after, before);
NETDATA.globalPanAndZoom.setMaster(state, after, before);
};
NETDATA.dygraphSetSelection = function (state, t) {
if (typeof state.tmp.dygraph_instance !== 'undefined') {
let r = state.calculateRowForTime(t);
if (r !== -1) {
state.tmp.dygraph_instance.setSelection(r);
return true;
} else {
state.tmp.dygraph_instance.clearSelection();
state.legendShowUndefined();
}
}
return false;
};
NETDATA.dygraphClearSelection = function (state) {
if (typeof state.tmp.dygraph_instance !== 'undefined') {
state.tmp.dygraph_instance.clearSelection();
}
return true;
};
NETDATA.dygraphSmoothInitialize = function (callback) {
$.ajax({
url: NETDATA.dygraph_smooth_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.dygraph.smooth = true;
smoothPlotter.smoothing = 0.3;
})
.fail(function () {
NETDATA.dygraph.smooth = false;
})
.always(function () {
if (typeof callback === "function") {
return callback();
}
});
};
NETDATA.dygraphInitialize = function (callback) {
if (typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
$.ajax({
url: NETDATA.dygraph_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
})
.fail(function () {
NETDATA.chartLibraries.dygraph.enabled = false;
NETDATA.error(100, NETDATA.dygraph_js);
})
.always(function () {
if (NETDATA.chartLibraries.dygraph.enabled && NETDATA.options.current.smooth_plot) {
NETDATA.dygraphSmoothInitialize(callback);
} else if (typeof callback === "function") {
return callback();
}
});
} else {
NETDATA.chartLibraries.dygraph.enabled = false;
if (typeof callback === "function") {
return callback();
}
}
};
NETDATA.dygraphChartUpdate = function (state, data) {
let dygraph = state.tmp.dygraph_instance;
if (typeof dygraph === 'undefined') {
return NETDATA.dygraphChartCreate(state, data);
}
// when the chart is not visible, and hidden
// if there is a window resize, dygraph detects
// its element size as 0x0.
// this will make it re-appear properly
if (state.tm.last_unhidden > state.tmp.dygraph_last_rendered) {
dygraph.resize();
}
let options = {
file: data.result.data,
colors: state.chartColors(),
labels: data.result.labels,
//labelsDivWidth: state.chartWidth() - 70,
includeZero: state.tmp.dygraph_include_zero,
visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
};
if (state.tmp.dygraph_chart_type === 'stacked') {
if (options.includeZero && state.dimensions_visibility.countSelected() < options.visibility.length) {
options.includeZero = 0;
}
}
if (!NETDATA.chartLibraries.dygraph.isSparkline(state)) {
options.ylabel = state.units_current; // (state.units_desired === 'auto')?"":state.units_current;
}
if (state.tmp.dygraph_force_zoom) {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('dygraphChartUpdate() forced zoom update');
}
options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null;
//options.isZoomedIgnoreProgrammaticZoom = true;
state.tmp.dygraph_force_zoom = false;
} else if (state.current.name !== 'auto') {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('dygraphChartUpdate() loose update');
}
} else {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('dygraphChartUpdate() strict update');
}
options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null;
//options.isZoomedIgnoreProgrammaticZoom = true;
}
options.valueRange = state.tmp.dygraph_options.valueRange;
let oldMax = null, oldMin = null;
if (state.tmp.__commonMin !== null) {
state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0];
oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
}
if (state.tmp.__commonMax !== null) {
state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1];
oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
}
if (state.tmp.dygraph_smooth_eligible) {
if ((NETDATA.options.current.smooth_plot && state.tmp.dygraph_options.plotter !== smoothPlotter)
|| (NETDATA.options.current.smooth_plot === false && state.tmp.dygraph_options.plotter === smoothPlotter)) {
NETDATA.dygraphChartCreate(state, data);
return;
}
}
if (netdataSnapshotData !== null && NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(state) === false) {
// pan and zoom on snapshots
options.dateWindow = [NETDATA.globalPanAndZoom.force_after_ms, NETDATA.globalPanAndZoom.force_before_ms];
//options.isZoomedIgnoreProgrammaticZoom = true;
}
if (NETDATA.chartLibraries.dygraph.isLogScale(state)) {
if (Array.isArray(options.valueRange) && options.valueRange[0] <= 0) {
options.valueRange[0] = null;
}
}
dygraph.updateOptions(options);
let redraw = false;
if (oldMin !== null && oldMin > state.tmp.dygraph_instance.axes_[0].extremeRange[0]) {
state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0];
options.valueRange[0] = NETDATA.commonMin.get(state);
redraw = true;
}
if (oldMax !== null && oldMax < state.tmp.dygraph_instance.axes_[0].extremeRange[1]) {
state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1];
options.valueRange[1] = NETDATA.commonMax.get(state);
redraw = true;
}
if (redraw) {
// state.log('forcing redraw to adapt to common- min/max');
dygraph.updateOptions(options);
}
state.tmp.dygraph_last_rendered = Date.now();
return true;
};
NETDATA.dygraphChartCreate = function (state, data) {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('dygraphChartCreate()');
}
state.tmp.dygraph_chart_type = NETDATA.dataAttribute(state.element, 'dygraph-type', state.chart.chart_type);
if (state.tmp.dygraph_chart_type === 'stacked' && data.dimensions === 1) {
state.tmp.dygraph_chart_type = 'area';
}
if (state.tmp.dygraph_chart_type === 'stacked' && NETDATA.chartLibraries.dygraph.isLogScale(state)) {
state.tmp.dygraph_chart_type = 'area';
}
let highlightCircleSize = NETDATA.chartLibraries.dygraph.isSparkline(state) ? 3 : 4;
let smooth = NETDATA.dygraph.smooth
? (NETDATA.dataAttributeBoolean(state.element, 'dygraph-smooth', (state.tmp.dygraph_chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false)))
: false;
state.tmp.dygraph_include_zero = NETDATA.dataAttribute(state.element, 'dygraph-includezero', (state.tmp.dygraph_chart_type === 'stacked'));
let drawAxis = NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawaxis', true);
state.tmp.dygraph_options = {
colors: NETDATA.dataAttribute(state.element, 'dygraph-colors', state.chartColors()),
// leave a few pixels empty on the right of the chart
rightGap: NETDATA.dataAttribute(state.element, 'dygraph-rightgap', 5),
showRangeSelector: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showrangeselector', false),
showRoller: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showroller', false),
title: NETDATA.dataAttribute(state.element, 'dygraph-title', state.title),
titleHeight: NETDATA.dataAttribute(state.element, 'dygraph-titleheight', 19),
legend: NETDATA.dataAttribute(state.element, 'dygraph-legend', 'always'), // we need this to get selection events
labels: data.result.labels,
labelsDiv: NETDATA.dataAttribute(state.element, 'dygraph-labelsdiv', state.element_legend_childs.hidden),
//labelsDivStyles: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivstyles', { 'fontSize':'1px' }),
//labelsDivWidth: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivwidth', state.chartWidth() - 70),
labelsSeparateLines: NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsseparatelines', true),
labelsShowZeroValues: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsshowzerovalues', true),
labelsKMB: false,
labelsKMG2: false,
showLabelsOnHighlight: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showlabelsonhighlight', true),
hideOverlayOnMouseOut: NETDATA.dataAttributeBoolean(state.element, 'dygraph-hideoverlayonmouseout', true),
includeZero: state.tmp.dygraph_include_zero,
xRangePad: NETDATA.dataAttribute(state.element, 'dygraph-xrangepad', 0),
yRangePad: NETDATA.dataAttribute(state.element, 'dygraph-yrangepad', 1),
valueRange: NETDATA.dataAttribute(state.element, 'dygraph-valuerange', [null, null]),
ylabel: state.units_current, // (state.units_desired === 'auto')?"":state.units_current,
yLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-ylabelwidth', 12),
// the function to plot the chart
plotter: null,
// The width of the lines connecting data points.
// This can be used to increase the contrast or some graphs.
strokeWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokewidth', ((state.tmp.dygraph_chart_type === 'stacked') ? 0.1 : ((smooth === true) ? 1.5 : 0.7))),
strokePattern: NETDATA.dataAttribute(state.element, 'dygraph-strokepattern', undefined),
// The size of the dot to draw on each point in pixels (see drawPoints).
// A dot is always drawn when a point is "isolated",
// i.e. there is a missing point on either side of it.
// This also controls the size of those dots.
drawPoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawpoints', false),
// Draw points at the edges of gaps in the data.
// This improves visibility of small data segments or other data irregularities.
drawGapEdgePoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgapedgepoints', true),
connectSeparatedPoints: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-connectseparatedpoints', false),
pointSize: NETDATA.dataAttribute(state.element, 'dygraph-pointsize', 1),
// enabling this makes the chart with little square lines
stepPlot: NETDATA.dataAttributeBoolean(state.element, 'dygraph-stepplot', false),
// Draw a border around graph lines to make crossing lines more easily
// distinguishable. Useful for graphs with many lines.
strokeBorderColor: NETDATA.dataAttribute(state.element, 'dygraph-strokebordercolor', NETDATA.themes.current.background),
strokeBorderWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokeborderwidth', (state.tmp.dygraph_chart_type === 'stacked') ? 0.0 : 0.0),
fillGraph: NETDATA.dataAttribute(state.element, 'dygraph-fillgraph', (state.tmp.dygraph_chart_type === 'area' || state.tmp.dygraph_chart_type === 'stacked')),
fillAlpha: NETDATA.dataAttribute(state.element, 'dygraph-fillalpha',
((state.tmp.dygraph_chart_type === 'stacked')
? NETDATA.options.current.color_fill_opacity_stacked
: NETDATA.options.current.color_fill_opacity_area)
),
stackedGraph: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraph', (state.tmp.dygraph_chart_type === 'stacked')),
stackedGraphNaNFill: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraphnanfill', 'none'),
drawAxis: drawAxis,
axisLabelFontSize: NETDATA.dataAttribute(state.element, 'dygraph-axislabelfontsize', 10),
axisLineColor: NETDATA.dataAttribute(state.element, 'dygraph-axislinecolor', NETDATA.themes.current.axis),
axisLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-axislinewidth', 1.0),
drawGrid: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgrid', true),
gridLinePattern: NETDATA.dataAttribute(state.element, 'dygraph-gridlinepattern', null),
gridLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-gridlinewidth', 1.0),
gridLineColor: NETDATA.dataAttribute(state.element, 'dygraph-gridlinecolor', NETDATA.themes.current.grid),
maxNumberWidth: NETDATA.dataAttribute(state.element, 'dygraph-maxnumberwidth', 8),
sigFigs: NETDATA.dataAttribute(state.element, 'dygraph-sigfigs', null),
digitsAfterDecimal: NETDATA.dataAttribute(state.element, 'dygraph-digitsafterdecimal', 2),
valueFormatter: NETDATA.dataAttribute(state.element, 'dygraph-valueformatter', undefined),
highlightCircleSize: NETDATA.dataAttribute(state.element, 'dygraph-highlightcirclesize', highlightCircleSize),
highlightSeriesOpts: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesopts', null), // TOO SLOW: { strokeWidth: 1.5 },
highlightSeriesBackgroundAlpha: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesbackgroundalpha', null), // TOO SLOW: (state.tmp.dygraph_chart_type === 'stacked')?0.7:0.5,
pointClickCallback: NETDATA.dataAttribute(state.element, 'dygraph-pointclickcallback', undefined),
visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
logscale: NETDATA.chartLibraries.dygraph.isLogScale(state) ? 'y' : undefined,
axes: {
x: {
pixelsPerLabel: NETDATA.dataAttribute(state.element, 'dygraph-xpixelsperlabel', 50),
ticker: Dygraph.dateTicker,
axisLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-xaxislabelwidth', 60),
drawAxis: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawxaxis', drawAxis),
axisLabelFormatter: function (d, gran) {
void(gran);
return NETDATA.dateTime.xAxisTimeString(d);
}
},
y: {
logscale: NETDATA.chartLibraries.dygraph.isLogScale(state) ? true : undefined,
pixelsPerLabel: NETDATA.dataAttribute(state.element, 'dygraph-ypixelsperlabel', 15),
axisLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-yaxislabelwidth', 50),
drawAxis: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawyaxis', drawAxis),
axisLabelFormatter: function (y) {
// unfortunately, we have to call this every single time
state.legendFormatValueDecimalsFromMinMax(
this.axes_[0].extremeRange[0],
this.axes_[0].extremeRange[1]
);
let old_units = this.user_attrs_.ylabel;
let v = state.legendFormatValue(y);
let new_units = state.units_current;
if (state.units_desired === 'auto' && typeof old_units !== 'undefined' && new_units !== old_units && !NETDATA.chartLibraries.dygraph.isSparkline(state)) {
// console.log(this);
// state.log('units discrepancy: old = ' + old_units + ', new = ' + new_units);
let len = this.plugins_.length;
while (len--) {
// console.log(this.plugins_[len]);
if (typeof this.plugins_[len].plugin.ylabel_div_ !== 'undefined'
&& this.plugins_[len].plugin.ylabel_div_ !== null
&& typeof this.plugins_[len].plugin.ylabel_div_.children !== 'undefined'
&& this.plugins_[len].plugin.ylabel_div_.children !== null
&& typeof this.plugins_[len].plugin.ylabel_div_.children[0].children !== 'undefined'
&& this.plugins_[len].plugin.ylabel_div_.children[0].children !== null
) {
this.plugins_[len].plugin.ylabel_div_.children[0].children[0].innerHTML = new_units;
this.user_attrs_.ylabel = new_units;
break;
}
}
if (len < 0) {
state.log('units discrepancy, but cannot find dygraphs div to change: old = ' + old_units + ', new = ' + new_units);
}
}
return v;
}
}
},
legendFormatter: function (data) {
if (state.tmp.dygraph_mouse_down) {
return;
}
let elements = state.element_legend_childs;
// if the hidden div is not there
// we are not managing the legend
if (elements.hidden === null) {
return;
}
if (typeof data.x !== 'undefined') {
state.legendSetDate(data.x);
let i = data.series.length;
while (i--) {
let series = data.series[i];
if (series.isVisible) {
state.legendSetLabelValue(series.label, series.y);
} else {
state.legendSetLabelValue(series.label, null);
}
}
}
return '';
},
drawCallback: function (dygraph, is_initial) {
// the user has panned the chart and this is called to re-draw the chart
// 1. refresh this chart by adding data to it
// 2. notify all the other charts about the update they need
// to prevent an infinite loop (feedback), we use
// state.tmp.dygraph_user_action
// - when true, this is initiated by a user
// - when false, this is feedback
if (state.current.name !== 'auto' && state.tmp.dygraph_user_action) {
state.tmp.dygraph_user_action = false;
let x_range = dygraph.xAxisRange();
let after = Math.round(x_range[0]);
let before = Math.round(x_range[1]);
if (NETDATA.options.debug.dygraph) {
state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): mode ' + state.current.name + ' ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
//console.log(state);
}
if (before <= state.netdata_last && after >= state.netdata_first) {
// update only when we are within the data limits
state.updateChartPanOrZoom(after, before);
}
}
},
zoomCallback: function (minDate, maxDate, yRanges) {
// the user has selected a range on the chart
// 1. refresh this chart by adding data to it
// 2. notify all the other charts about the update they need
void(yRanges);
if (NETDATA.options.debug.dygraph) {
state.log('dygraphZoomCallback(): ' + state.current.name);
}
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.setMode('zoom');
// refresh it to the greatest possible zoom level
state.tmp.dygraph_user_action = true;
state.tmp.dygraph_force_zoom = true;
state.updateChartPanOrZoom(minDate, maxDate);
},
highlightCallback: function (event, x, points, row, seriesName) {
void(seriesName);
state.pauseChart();
// there is a bug in dygraph when the chart is zoomed enough
// the time it thinks is selected is wrong
// here we calculate the time t based on the row number selected
// which is ok
// let t = state.data_after + row * state.data_update_every;
// 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);
if (state.tmp.dygraph_mouse_down !== true) {
NETDATA.globalSelectionSync.sync(state, x);
}
// fix legend zIndex using the internal structures of dygraph legend module
// this works, but it is a hack!
// state.tmp.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
},
unhighlightCallback: function (event) {
void(event);
if (state.tmp.dygraph_mouse_down) {
return;
}
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('dygraphUnhighlightCallback()');
}
state.unpauseChart();
NETDATA.globalSelectionSync.stop();
},
underlayCallback: function (canvas, area, g) {
// the chart is about to be drawn
// update history_tip_element
if (state.tmp.dygraph_history_tip_element) {
const xHookRightSide = g.toDomXCoord(state.netdata_first);
if (xHookRightSide > area.x) {
state.tmp.dygraph_history_tip_element_displayed = true;
// group the styles for possible better performance
state.tmp.dygraph_history_tip_element.setAttribute(
'style',
`display: block; left: ${area.x}px; right: calc(100% - ${xHookRightSide}px);`
)
} else {
if (state.tmp.dygraph_history_tip_element_displayed) {
// additional check just for performance
// don't update the DOM when it's not needed
state.tmp.dygraph_history_tip_element.style.display = 'none';
state.tmp.dygraph_history_tip_element_displayed = false;
}
}
}
// this function renders global highlighted time-frame
if (NETDATA.globalChartUnderlay.isActive()) {
let after = NETDATA.globalChartUnderlay.after;
let before = NETDATA.globalChartUnderlay.before;
if (after < state.view_after) {
after = state.view_after;
}
if (before > state.view_before) {
before = state.view_before;
}
if (after < before) {
let bottom_left = g.toDomCoords(after, -20);
let top_right = g.toDomCoords(before, +20);
let left = bottom_left[0];
let right = top_right[0];
canvas.fillStyle = NETDATA.themes.current.highlight;
canvas.fillRect(left, area.y, right - left, area.h);
}
}
},
interactionModel: {
mousedown: function (event, dygraph, context) {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.mousedown()');
}
state.tmp.dygraph_user_action = true;
if (NETDATA.options.debug.dygraph) {
state.log('dygraphMouseDown()');
}
// Right-click should not initiate anything.
if (event.button && event.button === 2) {
return;
}
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_mouse_down = true;
context.initializeMouseDown(event, dygraph, context);
//console.log(event);
if (event.button && event.button === 1) {
if (event.shiftKey) {
//console.log('middle mouse button dragging (PAN)');
state.setMode('pan');
// NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_highlight_after = null;
Dygraph.startPan(event, dygraph, context);
} else if (event.altKey || event.ctrlKey || event.metaKey) {
//console.log('middle mouse button highlight');
if (!(event.offsetX && event.offsetY)) {
event.offsetX = event.layerX - event.target.offsetLeft;
event.offsetY = event.layerY - event.target.offsetTop;
}
state.tmp.dygraph_highlight_after = dygraph.toDataXCoord(event.offsetX);
Dygraph.startZoom(event, dygraph, context);
} else {
//console.log('middle mouse button selection for zoom (ZOOM)');
state.setMode('zoom');
// NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_highlight_after = null;
Dygraph.startZoom(event, dygraph, context);
}
} else {
if (event.shiftKey) {
//console.log('left mouse button selection for zoom (ZOOM)');
state.setMode('zoom');
// NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_highlight_after = null;
Dygraph.startZoom(event, dygraph, context);
} else if (event.altKey || event.ctrlKey || event.metaKey) {
//console.log('left mouse button highlight');
if (!(event.offsetX && event.offsetY)) {
event.offsetX = event.layerX - event.target.offsetLeft;
event.offsetY = event.layerY - event.target.offsetTop;
}
state.tmp.dygraph_highlight_after = dygraph.toDataXCoord(event.offsetX);
Dygraph.startZoom(event, dygraph, context);
} else {
//console.log('left mouse button dragging (PAN)');
state.setMode('pan');
// NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_highlight_after = null;
Dygraph.startPan(event, dygraph, context);
}
}
},
mousemove: function (event, dygraph, context) {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.mousemove()');
}
if (state.tmp.dygraph_highlight_after !== null) {
//console.log('highlight selection...');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
Dygraph.moveZoom(event, dygraph, context);
event.preventDefault();
} else if (context.isPanning) {
//console.log('panning...');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
//NETDATA.globalSelectionSync.stop();
//NETDATA.globalSelectionSync.delay();
state.setMode('pan');
context.is2DPan = false;
Dygraph.movePan(event, dygraph, context);
} else if (context.isZooming) {
//console.log('zooming...');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
//NETDATA.globalSelectionSync.stop();
//NETDATA.globalSelectionSync.delay();
state.setMode('zoom');
Dygraph.moveZoom(event, dygraph, context);
}
},
mouseup: function (event, dygraph, context) {
state.tmp.dygraph_mouse_down = false;
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.mouseup()');
}
if (state.tmp.dygraph_highlight_after !== null) {
//console.log('done highlight selection');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
if (!(event.offsetX && event.offsetY)) {
event.offsetX = event.layerX - event.target.offsetLeft;
event.offsetY = event.layerY - event.target.offsetTop;
}
NETDATA.globalChartUnderlay.set(state
, state.tmp.dygraph_highlight_after
, dygraph.toDataXCoord(event.offsetX)
, state.view_after
, state.view_before
);
state.tmp.dygraph_highlight_after = null;
context.isZooming = false;
dygraph.clearZoomRect_();
dygraph.drawGraph_(false);
// refresh all the charts immediately
NETDATA.options.auto_refresher_stop_until = 0;
} else if (context.isPanning) {
//console.log('done panning');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
Dygraph.endPan(event, dygraph, context);
// refresh all the charts immediately
NETDATA.options.auto_refresher_stop_until = 0;
} else if (context.isZooming) {
//console.log('done zomming');
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
Dygraph.endZoom(event, dygraph, context);
// refresh all the charts immediately
NETDATA.options.auto_refresher_stop_until = 0;
}
},
click: function (event, dygraph, context) {
void(dygraph);
void(context);
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.click()');
}
event.preventDefault();
},
dblclick: function (event, dygraph, context) {
void(event);
void(dygraph);
void(context);
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.dblclick()');
}
NETDATA.resetAllCharts(state);
},
wheel: function (event, dygraph, context) {
void(context);
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.wheel()');
}
// Take the offset of a mouse event on the dygraph canvas and
// convert it to a pair of percentages from the bottom left.
// (Not top left, bottom is where the lower value is.)
function offsetToPercentage(g, offsetX, offsetY) {
// This is calculating the pixel offset of the leftmost date.
let xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
let yar0 = g.yAxisRange(0);
// This is calculating the pixel of the highest value. (Top pixel)
let yOffset = g.toDomCoords(null, yar0[1])[1];
// x y w and h are relative to the corner of the drawing area,
// so that the upper corner of the drawing area is (0, 0).
let x = offsetX - xOffset;
let y = offsetY - yOffset;
// This is computing the rightmost pixel, effectively defining the
// width.
let w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
// This is computing the lowest pixel, effectively defining the height.
let h = g.toDomCoords(null, yar0[0])[1] - yOffset;
// Percentage from the left.
let xPct = w === 0 ? 0 : (x / w);
// Percentage from the top.
let yPct = h === 0 ? 0 : (y / h);
// The (1-) part below changes it from "% distance down from the top"
// to "% distance up from the bottom".
return [xPct, (1 - yPct)];
}
// Adjusts [x, y] toward each other by zoomInPercentage%
// Split it so the left/bottom axis gets xBias/yBias of that change and
// tight/top gets (1-xBias)/(1-yBias) of that change.
//
// If a bias is missing it splits it down the middle.
function zoomRange(g, zoomInPercentage, xBias, yBias) {
xBias = xBias || 0.5;
yBias = yBias || 0.5;
function adjustAxis(axis, zoomInPercentage, bias) {
let delta = axis[1] - axis[0];
let increment = delta * zoomInPercentage;
let foo = [increment * bias, increment * (1 - bias)];
return [axis[0] + foo[0], axis[1] - foo[1]];
}
let yAxes = g.yAxisRanges();
let newYAxes = [];
for (let i = 0; i < yAxes.length; i++) {
newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
}
return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
}
if (event.altKey || event.shiftKey) {
state.tmp.dygraph_user_action = true;
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
// http://dygraphs.com/gallery/interaction-api.js
let normal_def;
if (typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
// chrome
{
normal_def = event.wheelDelta / 40;
} else
// firefox
{
normal_def = event.deltaY * -1.2;
}
let normal = (event.detail) ? event.detail * -1 : normal_def;
let percentage = normal / 50;
if (!(event.offsetX && event.offsetY)) {
event.offsetX = event.layerX - event.target.offsetLeft;
event.offsetY = event.layerY - event.target.offsetTop;
}
let percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
let xPct = percentages[0];
let yPct = percentages[1];
let new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
let after = new_x_range[0];
let before = new_x_range[1];
let first = state.netdata_first + state.data_update_every;
let last = state.netdata_last + state.data_update_every;
if (before > last) {
after -= (before - last);
before = last;
}
if (after < first) {
after = first;
}
state.setMode('zoom');
state.updateChartPanOrZoom(after, before, function () {
dygraph.updateOptions({dateWindow: [after, before]});
});
event.preventDefault();
}
},
touchstart: function (event, dygraph, context) {
state.tmp.dygraph_mouse_down = true;
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.touchstart()');
}
state.tmp.dygraph_user_action = true;
state.setMode('zoom');
state.pauseChart();
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
// we overwrite the touch directions at the end, to overwrite
// the internal default of dygraph
context.touchDirections = {x: true, y: false};
state.dygraph_last_touch_start = Date.now();
state.dygraph_last_touch_move = 0;
if (typeof event.touches[0].pageX === 'number') {
state.dygraph_last_touch_page_x = event.touches[0].pageX;
} else {
state.dygraph_last_touch_page_x = 0;
}
},
touchmove: function (event, dygraph, context) {
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.touchmove()');
}
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
state.dygraph_last_touch_move = Date.now();
},
touchend: function (event, dygraph, context) {
state.tmp.dygraph_mouse_down = false;
if (NETDATA.options.debug.dygraph || state.debug) {
state.log('interactionModel.touchend()');
}
NETDATA.globalSelectionSync.stop();
NETDATA.globalSelectionSync.delay();
state.tmp.dygraph_user_action = true;
Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
// if it didn't move, it is a selection
if (state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
NETDATA.globalSelectionSync.dontSyncBefore = 0;
NETDATA.globalSelectionSync.setMaster(state);
// internal api of dygraph
let pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
console.log('pct: ' + pct.toString());
let t = Math.round(state.view_after + (state.view_before - state.view_after) * pct);
if (NETDATA.dygraphSetSelection(state, t)) {
NETDATA.globalSelectionSync.sync(state, t);
}
}
// if it was double tap within double click time, reset the charts
let now = Date.now();
if (typeof state.dygraph_last_touch_end !== 'undefined') {
if (state.dygraph_last_touch_move === 0) {
let dt = now - state.dygraph_last_touch_end;
if (dt <= NETDATA.options.current.double_click_speed) {
NETDATA.resetAllCharts(state);
}
}
}
// remember the timestamp of the last touch end
state.dygraph_last_touch_end = now;
// refresh all the charts immediately
NETDATA.options.auto_refresher_stop_until = 0;
}
}
};
if (NETDATA.chartLibraries.dygraph.isLogScale(state)) {
if (Array.isArray(state.tmp.dygraph_options.valueRange) && state.tmp.dygraph_options.valueRange[0] <= 0) {
state.tmp.dygraph_options.valueRange[0] = null;
}
}
if (NETDATA.chartLibraries.dygraph.isSparkline(state)) {
state.tmp.dygraph_options.drawGrid = false;
state.tmp.dygraph_options.drawAxis = false;
state.tmp.dygraph_options.title = undefined;
state.tmp.dygraph_options.ylabel = undefined;
state.tmp.dygraph_options.yLabelWidth = 0;
//state.tmp.dygraph_options.labelsDivWidth = 120;
//state.tmp.dygraph_options.labelsDivStyles.width = '120px';
state.tmp.dygraph_options.labelsSeparateLines = true;
state.tmp.dygraph_options.rightGap = 0;
state.tmp.dygraph_options.yRangePad = 1;
state.tmp.dygraph_options.axes.x.drawAxis = false;
state.tmp.dygraph_options.axes.y.drawAxis = false;
}
if (smooth) {
state.tmp.dygraph_smooth_eligible = true;
if (NETDATA.options.current.smooth_plot) {
state.tmp.dygraph_options.plotter = smoothPlotter;
}
}
else {
state.tmp.dygraph_smooth_eligible = false;
}
if (netdataSnapshotData !== null && NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(state) === false) {
// pan and zoom on snapshots
state.tmp.dygraph_options.dateWindow = [NETDATA.globalPanAndZoom.force_after_ms, NETDATA.globalPanAndZoom.force_before_ms];
//state.tmp.dygraph_options.isZoomedIgnoreProgrammaticZoom = true;
}
state.tmp.dygraph_instance = new Dygraph(state.element_chart,
data.result.data, state.tmp.dygraph_options);
state.tmp.dygraph_history_tip_element = document.createElement('div');
state.tmp.dygraph_history_tip_element.innerHTML = `
Want to extend your history of real-time metrics?
Configure Netdata's history
or use the DB engine.
`;
state.tmp.dygraph_history_tip_element.className = 'dygraph__history-tip';
state.element_chart.appendChild(state.tmp.dygraph_history_tip_element);
state.tmp.dygraph_force_zoom = false;
state.tmp.dygraph_user_action = false;
state.tmp.dygraph_last_rendered = Date.now();
state.tmp.dygraph_highlight_after = null;
if (state.tmp.dygraph_options.valueRange[0] === null && state.tmp.dygraph_options.valueRange[1] === null) {
if (typeof state.tmp.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
state.tmp.__commonMin = NETDATA.dataAttribute(state.element, 'common-min', null);
state.tmp.__commonMax = NETDATA.dataAttribute(state.element, 'common-max', null);
} else {
state.log('incompatible version of Dygraph detected');
state.tmp.__commonMin = null;
state.tmp.__commonMax = null;
}
} else {
// if the user gave a valueRange, respect it
state.tmp.__commonMin = null;
state.tmp.__commonMax = null;
}
return true;
};
// ----------------------------------------------------------------------------------------------------------------
// sparkline
NETDATA.sparklineInitialize = function (callback) {
if (typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
$.ajax({
url: NETDATA.sparkline_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
})
.fail(function () {
NETDATA.chartLibraries.sparkline.enabled = false;
NETDATA.error(100, NETDATA.sparkline_js);
})
.always(function () {
if (typeof callback === "function") {
return callback();
}
});
} else {
NETDATA.chartLibraries.sparkline.enabled = false;
if (typeof callback === "function") {
return callback();
}
}
};
NETDATA.sparklineChartUpdate = function (state, data) {
state.sparkline_options.width = state.chartWidth();
state.sparkline_options.height = state.chartHeight();
$(state.element_chart).sparkline(data.result, state.sparkline_options);
return true;
};
NETDATA.sparklineChartCreate = function (state, data) {
let type = NETDATA.dataAttribute(state.element, 'sparkline-type', 'line');
let lineColor = NETDATA.dataAttribute(state.element, 'sparkline-linecolor', state.chartCustomColors()[0]);
let fillColor = NETDATA.dataAttribute(state.element, 'sparkline-fillcolor', ((state.chart.chart_type === 'line') ? NETDATA.themes.current.background : NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance)));
let chartRangeMin = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemin', undefined);
let chartRangeMax = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemax', undefined);
let composite = NETDATA.dataAttribute(state.element, 'sparkline-composite', undefined);
let enableTagOptions = NETDATA.dataAttribute(state.element, 'sparkline-enabletagoptions', undefined);
let tagOptionPrefix = NETDATA.dataAttribute(state.element, 'sparkline-tagoptionprefix', undefined);
let tagValuesAttribute = NETDATA.dataAttribute(state.element, 'sparkline-tagvaluesattribute', undefined);
let disableHiddenCheck = NETDATA.dataAttribute(state.element, 'sparkline-disablehiddencheck', undefined);
let defaultPixelsPerValue = NETDATA.dataAttribute(state.element, 'sparkline-defaultpixelspervalue', undefined);
let spotColor = NETDATA.dataAttribute(state.element, 'sparkline-spotcolor', undefined);
let minSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-minspotcolor', undefined);
let maxSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-maxspotcolor', undefined);
let spotRadius = NETDATA.dataAttribute(state.element, 'sparkline-spotradius', undefined);
let valueSpots = NETDATA.dataAttribute(state.element, 'sparkline-valuespots', undefined);
let highlightSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightspotcolor', undefined);
let highlightLineColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightlinecolor', undefined);
let lineWidth = NETDATA.dataAttribute(state.element, 'sparkline-linewidth', undefined);
let normalRangeMin = NETDATA.dataAttribute(state.element, 'sparkline-normalrangemin', undefined);
let normalRangeMax = NETDATA.dataAttribute(state.element, 'sparkline-normalrangemax', undefined);
let drawNormalOnTop = NETDATA.dataAttribute(state.element, 'sparkline-drawnormalontop', undefined);
let xvalues = NETDATA.dataAttribute(state.element, 'sparkline-xvalues', undefined);
let chartRangeClip = NETDATA.dataAttribute(state.element, 'sparkline-chartrangeclip', undefined);
let chartRangeMinX = NETDATA.dataAttribute(state.element, 'sparkline-chartrangeminx', undefined);
let chartRangeMaxX = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemaxx', undefined);
let disableInteraction = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disableinteraction', false);
let disableTooltips = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disabletooltips', false);
let disableHighlight = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disablehighlight', false);
let highlightLighten = NETDATA.dataAttribute(state.element, 'sparkline-highlightlighten', 1.4);
let highlightColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightcolor', undefined);
let tooltipContainer = NETDATA.dataAttribute(state.element, 'sparkline-tooltipcontainer', undefined);
let tooltipClassname = NETDATA.dataAttribute(state.element, 'sparkline-tooltipclassname', undefined);
let tooltipFormat = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformat', undefined);
let tooltipPrefix = NETDATA.dataAttribute(state.element, 'sparkline-tooltipprefix', undefined);
let tooltipSuffix = NETDATA.dataAttribute(state.element, 'sparkline-tooltipsuffix', ' ' + state.units_current);
let tooltipSkipNull = NETDATA.dataAttributeBoolean(state.element, 'sparkline-tooltipskipnull', true);
let tooltipValueLookups = NETDATA.dataAttribute(state.element, 'sparkline-tooltipvaluelookups', undefined);
let tooltipFormatFieldlist = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformatfieldlist', undefined);
let tooltipFormatFieldlistKey = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformatfieldlistkey', undefined);
let numberFormatter = NETDATA.dataAttribute(state.element, 'sparkline-numberformatter', function (n) {
return n.toFixed(2);
});
let numberDigitGroupSep = NETDATA.dataAttribute(state.element, 'sparkline-numberdigitgroupsep', undefined);
let numberDecimalMark = NETDATA.dataAttribute(state.element, 'sparkline-numberdecimalmark', undefined);
let numberDigitGroupCount = NETDATA.dataAttribute(state.element, 'sparkline-numberdigitgroupcount', undefined);
let animatedZooms = NETDATA.dataAttributeBoolean(state.element, 'sparkline-animatedzooms', false);
if (spotColor === 'disable') {
spotColor = '';
}
if (minSpotColor === 'disable') {
minSpotColor = '';
}
if (maxSpotColor === 'disable') {
maxSpotColor = '';
}
// state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
state.sparkline_options = {
type: type,
lineColor: lineColor,
fillColor: fillColor,
chartRangeMin: chartRangeMin,
chartRangeMax: chartRangeMax,
composite: composite,
enableTagOptions: enableTagOptions,
tagOptionPrefix: tagOptionPrefix,
tagValuesAttribute: tagValuesAttribute,
disableHiddenCheck: disableHiddenCheck,
defaultPixelsPerValue: defaultPixelsPerValue,
spotColor: spotColor,
minSpotColor: minSpotColor,
maxSpotColor: maxSpotColor,
spotRadius: spotRadius,
valueSpots: valueSpots,
highlightSpotColor: highlightSpotColor,
highlightLineColor: highlightLineColor,
lineWidth: lineWidth,
normalRangeMin: normalRangeMin,
normalRangeMax: normalRangeMax,
drawNormalOnTop: drawNormalOnTop,
xvalues: xvalues,
chartRangeClip: chartRangeClip,
chartRangeMinX: chartRangeMinX,
chartRangeMaxX: chartRangeMaxX,
disableInteraction: disableInteraction,
disableTooltips: disableTooltips,
disableHighlight: disableHighlight,
highlightLighten: highlightLighten,
highlightColor: highlightColor,
tooltipContainer: tooltipContainer,
tooltipClassname: tooltipClassname,
tooltipChartTitle: state.title,
tooltipFormat: tooltipFormat,
tooltipPrefix: tooltipPrefix,
tooltipSuffix: tooltipSuffix,
tooltipSkipNull: tooltipSkipNull,
tooltipValueLookups: tooltipValueLookups,
tooltipFormatFieldlist: tooltipFormatFieldlist,
tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
numberFormatter: numberFormatter,
numberDigitGroupSep: numberDigitGroupSep,
numberDecimalMark: numberDecimalMark,
numberDigitGroupCount: numberDigitGroupCount,
animatedZooms: animatedZooms,
width: state.chartWidth(),
height: state.chartHeight()
};
$(state.element_chart).sparkline(data.result, state.sparkline_options);
return true;
};
// google charts
// Codacy declarations
/* global google */
NETDATA.googleInitialize = function (callback) {
if (typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
$.ajax({
url: NETDATA.google_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.registerChartLibrary('google', NETDATA.google_js);
google.load('visualization', '1.1', {
'packages': ['corechart', 'controls'],
'callback': callback
});
})
.fail(function () {
NETDATA.chartLibraries.google.enabled = false;
NETDATA.error(100, NETDATA.google_js);
if (typeof callback === "function") {
return callback();
}
});
} else {
NETDATA.chartLibraries.google.enabled = false;
if (typeof callback === "function") {
return callback();
}
}
};
NETDATA.googleChartUpdate = function (state, data) {
let datatable = new google.visualization.DataTable(data.result);
state.google_instance.draw(datatable, state.google_options);
return true;
};
NETDATA.googleChartCreate = function (state, data) {
let datatable = new google.visualization.DataTable(data.result);
state.google_options = {
colors: state.chartColors(),
// do not set width, height - the chart resizes itself
//width: state.chartWidth(),
//height: state.chartHeight(),
lineWidth: 1,
title: state.title,
fontSize: 11,
hAxis: {
// title: "Time of Day",
// format:'HH:mm:ss',
viewWindowMode: 'maximized',
slantedText: false,
format: 'HH:mm:ss',
textStyle: {
fontSize: 9
},
gridlines: {
color: '#EEE'
}
},
vAxis: {
title: state.units_current,
viewWindowMode: 'pretty',
minValue: -0.1,
maxValue: 0.1,
direction: 1,
textStyle: {
fontSize: 9
},
gridlines: {
color: '#EEE'
}
},
chartArea: {
width: '65%',
height: '80%'
},
focusTarget: 'category',
annotation: {
'1': {
style: 'line'
}
},
pointsVisible: 0,
titlePosition: 'out',
titleTextStyle: {
fontSize: 11
},
tooltip: {
isHtml: false,
ignoreBounds: true,
textStyle: {
fontSize: 9
}
},
curveType: 'function',
areaOpacity: 0.3,
isStacked: false
};
switch (state.chart.chart_type) {
case "area":
state.google_options.vAxis.viewWindowMode = 'maximized';
state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
state.google_instance = new google.visualization.AreaChart(state.element_chart);
break;
case "stacked":
state.google_options.isStacked = true;
state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
state.google_options.vAxis.viewWindowMode = 'maximized';
state.google_options.vAxis.minValue = null;
state.google_options.vAxis.maxValue = null;
state.google_instance = new google.visualization.AreaChart(state.element_chart);
break;
default:
case "line":
state.google_options.lineWidth = 2;
state.google_instance = new google.visualization.LineChart(state.element_chart);
break;
}
state.google_instance.draw(datatable, state.google_options);
return true;
};
// gauge.js
NETDATA.gaugeInitialize = function (callback) {
if (typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
$.ajax({
url: NETDATA.gauge_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
})
.fail(function () {
NETDATA.chartLibraries.gauge.enabled = false;
NETDATA.error(100, NETDATA.gauge_js);
})
.always(function () {
if (typeof callback === "function") {
return callback();
}
})
}
else {
NETDATA.chartLibraries.gauge.enabled = false;
if (typeof callback === "function") {
return callback();
}
}
};
NETDATA.gaugeAnimation = function (state, status) {
let speed = 32;
if (typeof status === 'boolean' && status === false) {
speed = 1000000000;
} else if (typeof status === 'number') {
speed = status;
}
// console.log('gauge speed ' + speed);
state.tmp.gauge_instance.animationSpeed = speed;
state.tmp.___gaugeOld__.speed = speed;
};
NETDATA.gaugeSet = function (state, value, min, max) {
if (typeof value !== 'number') {
value = 0;
}
if (typeof min !== 'number') {
min = 0;
}
if (typeof max !== 'number') {
max = 0;
}
if (value > max) {
max = value;
}
if (value < min) {
min = value;
}
if (min > max) {
let t = min;
min = max;
max = t;
}
else if (min === max) {
max = min + 1;
}
state.legendFormatValueDecimalsFromMinMax(min, max);
// gauge.js has an issue if the needle
// is smaller than min or larger than max
// when we set the new values
// the needle will go crazy
// to prevent it, we always feed it
// with a percentage, so that the needle
// is always between min and max
let pcent = (value - min) * 100 / (max - min);
// bug fix for gauge.js 1.3.1
// if the value is the absolute min or max, the chart is broken
if (pcent < 0.001) {
pcent = 0.001;
}
if (pcent > 99.999) {
pcent = 99.999;
}
state.tmp.gauge_instance.set(pcent);
// console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
state.tmp.___gaugeOld__.value = value;
state.tmp.___gaugeOld__.min = min;
state.tmp.___gaugeOld__.max = max;
};
NETDATA.gaugeSetLabels = function (state, value, min, max) {
if (state.tmp.___gaugeOld__.valueLabel !== value) {
state.tmp.___gaugeOld__.valueLabel = value;
state.tmp.gaugeChartLabel.innerText = state.legendFormatValue(value);
}
if (state.tmp.___gaugeOld__.minLabel !== min) {
state.tmp.___gaugeOld__.minLabel = min;
state.tmp.gaugeChartMin.innerText = state.legendFormatValue(min);
}
if (state.tmp.___gaugeOld__.maxLabel !== max) {
state.tmp.___gaugeOld__.maxLabel = max;
state.tmp.gaugeChartMax.innerText = state.legendFormatValue(max);
}
};
NETDATA.gaugeClearSelection = function (state, force) {
if (typeof state.tmp.gaugeEvent !== 'undefined' && typeof state.tmp.gaugeEvent.timer !== 'undefined') {
NETDATA.timeout.clear(state.tmp.gaugeEvent.timer);
state.tmp.gaugeEvent.timer = undefined;
}
if (state.isAutoRefreshable() && state.data !== null && force !== true) {
NETDATA.gaugeChartUpdate(state, state.data);
} else {
NETDATA.gaugeAnimation(state, false);
NETDATA.gaugeSetLabels(state, null, null, null);
NETDATA.gaugeSet(state, null, null, null);
}
NETDATA.gaugeAnimation(state, true);
return true;
};
NETDATA.gaugeSetSelection = function (state, t) {
if (state.timeIsVisible(t) !== true) {
return NETDATA.gaugeClearSelection(state, true);
}
let slot = state.calculateRowForTime(t);
if (slot < 0 || slot >= state.data.result.length) {
return NETDATA.gaugeClearSelection(state, true);
}
if (typeof state.tmp.gaugeEvent === 'undefined') {
state.tmp.gaugeEvent = {
timer: undefined,
value: 0,
min: 0,
max: 0
};
}
let value = state.data.result[state.data.result.length - 1 - slot];
let min = (state.tmp.gaugeMin === null) ? NETDATA.commonMin.get(state) : state.tmp.gaugeMin;
let max = (state.tmp.gaugeMax === null) ? NETDATA.commonMax.get(state) : state.tmp.gaugeMax;
// make sure it is zero based
// but only if it has not been set by the user
if (state.tmp.gaugeMin === null && min > 0) {
min = 0;
}
if (state.tmp.gaugeMax === null && max < 0) {
max = 0;
}
state.tmp.gaugeEvent.value = value;
state.tmp.gaugeEvent.min = min;
state.tmp.gaugeEvent.max = max;
NETDATA.gaugeSetLabels(state, value, min, max);
if (state.tmp.gaugeEvent.timer === undefined) {
NETDATA.gaugeAnimation(state, false);
state.tmp.gaugeEvent.timer = NETDATA.timeout.set(function () {
state.tmp.gaugeEvent.timer = undefined;
NETDATA.gaugeSet(state, state.tmp.gaugeEvent.value, state.tmp.gaugeEvent.min, state.tmp.gaugeEvent.max);
}, 0);
}
return true;
};
NETDATA.gaugeChartUpdate = function (state, data) {
let value, min, max;
if (NETDATA.globalPanAndZoom.isActive() || state.isAutoRefreshable() === false) {
NETDATA.gaugeSetLabels(state, null, null, null);
state.tmp.gauge_instance.set(0);
} else {
value = data.result[0];
min = (state.tmp.gaugeMin === null) ? NETDATA.commonMin.get(state) : state.tmp.gaugeMin;
max = (state.tmp.gaugeMax === null) ? NETDATA.commonMax.get(state) : state.tmp.gaugeMax;
if (value < min) {
min = value;
}
if (value > max) {
max = value;
}
// make sure it is zero based
// but only if it has not been set by the user
if (state.tmp.gaugeMin === null && min > 0) {
min = 0;
}
if (state.tmp.gaugeMax === null && max < 0) {
max = 0;
}
NETDATA.gaugeSet(state, value, min, max);
NETDATA.gaugeSetLabels(state, value, min, max);
}
return true;
};
NETDATA.gaugeChartCreate = function (state, data) {
// let chart = $(state.element_chart);
let value = data.result[0];
let min = NETDATA.dataAttribute(state.element, 'gauge-min-value', null);
let max = NETDATA.dataAttribute(state.element, 'gauge-max-value', null);
// let adjust = NETDATA.dataAttribute(state.element, 'gauge-adjust', null);
let pointerColor = NETDATA.dataAttribute(state.element, 'gauge-pointer-color', NETDATA.themes.current.gauge_pointer);
let strokeColor = NETDATA.dataAttribute(state.element, 'gauge-stroke-color', NETDATA.themes.current.gauge_stroke);
let startColor = NETDATA.dataAttribute(state.element, 'gauge-start-color', state.chartCustomColors()[0]);
let stopColor = NETDATA.dataAttribute(state.element, 'gauge-stop-color', void 0);
let generateGradient = NETDATA.dataAttribute(state.element, 'gauge-generate-gradient', false);
if (min === null) {
min = NETDATA.commonMin.get(state);
state.tmp.gaugeMin = null;
} else {
state.tmp.gaugeMin = min;
}
if (max === null) {
max = NETDATA.commonMax.get(state);
state.tmp.gaugeMax = null;
} else {
state.tmp.gaugeMax = max;
}
// make sure it is zero based
// but only if it has not been set by the user
if (state.tmp.gaugeMin === null && min > 0) {
min = 0;
}
if (state.tmp.gaugeMax === null && max < 0) {
max = 0;
}
let width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
// console.log('gauge width: ' + width.toString() + ', height: ' + height.toString());
//switch(adjust) {
// case 'width': width = height * ratio; break;
// case 'height':
// default: height = width / ratio; break;
//}
//state.element.style.width = width.toString() + 'px';
//state.element.style.height = height.toString() + 'px';
let lum_d = 0.05;
let options = {
lines: 12, // The number of lines to draw
angle: 0.14, // The span of the gauge arc
lineWidth: 0.57, // The line thickness
radiusScale: 1.0, // Relative radius
pointer: {
length: 0.85, // 0.9 The radius of the inner circle
strokeWidth: 0.045, // The rotation offset
color: pointerColor // Fill color
},
limitMax: true, // If false, the max value of the gauge will be updated if value surpass max
limitMin: true, // If true, the min value of the gauge will be fixed unless you set it manually
colorStart: startColor, // Colors
colorStop: stopColor, // just experiment with them
strokeColor: strokeColor, // to see which ones work best for you
generateGradient: (generateGradient === true), // gmosx:
gradientType: 0,
highDpiSupport: true // High resolution support
};
if (generateGradient.constructor === Array) {
// example options:
// data-gauge-generate-gradient="[0, 50, 100]"
// data-gauge-gradient-percent-color-0="#FFFFFF"
// data-gauge-gradient-percent-color-50="#999900"
// data-gauge-gradient-percent-color-100="#000000"
options.percentColors = [];
let len = generateGradient.length;
while (len--) {
let pcent = generateGradient[len];
let color = NETDATA.dataAttribute(state.element, 'gauge-gradient-percent-color-' + pcent.toString(), false);
if (color !== false) {
let a = [];
a[0] = pcent / 100;
a[1] = color;
options.percentColors.unshift(a);
}
}
if (options.percentColors.length === 0) {
delete options.percentColors;
}
} else if (generateGradient === false && NETDATA.themes.current.gauge_gradient) {
//noinspection PointlessArithmeticExpressionJS
options.percentColors = [
[0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
[0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
[0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
[0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
[0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
[0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
[0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
[0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
[0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
[0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
[1.0, NETDATA.colorLuminance(startColor, 0.0)]];
}
state.tmp.gauge_canvas = document.createElement('canvas');
state.tmp.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
state.tmp.gauge_canvas.className = 'gaugeChart';
state.tmp.gauge_canvas.width = width;
state.tmp.gauge_canvas.height = height;
state.element_chart.appendChild(state.tmp.gauge_canvas);
let valuefontsize = Math.floor(height / 5);
let valuetop = Math.round((height - valuefontsize) / 3.2);
state.tmp.gaugeChartLabel = document.createElement('span');
state.tmp.gaugeChartLabel.className = 'gaugeChartLabel';
state.tmp.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
state.tmp.gaugeChartLabel.style.top = valuetop.toString() + 'px';
state.element_chart.appendChild(state.tmp.gaugeChartLabel);
let titlefontsize = Math.round(valuefontsize / 2.1);
let titletop = 0;
state.tmp.gaugeChartTitle = document.createElement('span');
state.tmp.gaugeChartTitle.className = 'gaugeChartTitle';
state.tmp.gaugeChartTitle.innerText = state.title;
state.tmp.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
state.tmp.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
state.tmp.gaugeChartTitle.style.top = titletop.toString() + 'px';
state.element_chart.appendChild(state.tmp.gaugeChartTitle);
let unitfontsize = Math.round(titlefontsize * 0.9);
state.tmp.gaugeChartUnits = document.createElement('span');
state.tmp.gaugeChartUnits.className = 'gaugeChartUnits';
state.tmp.gaugeChartUnits.innerText = state.units_current;
state.tmp.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
state.element_chart.appendChild(state.tmp.gaugeChartUnits);
state.tmp.gaugeChartMin = document.createElement('span');
state.tmp.gaugeChartMin.className = 'gaugeChartMin';
state.tmp.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
state.element_chart.appendChild(state.tmp.gaugeChartMin);
state.tmp.gaugeChartMax = document.createElement('span');
state.tmp.gaugeChartMax.className = 'gaugeChartMax';
state.tmp.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
state.element_chart.appendChild(state.tmp.gaugeChartMax);
// when we just re-create the chart
// do not animate the first update
let animate = true;
if (typeof state.tmp.gauge_instance !== 'undefined') {
animate = false;
}
state.tmp.gauge_instance = new Gauge(state.tmp.gauge_canvas).setOptions(options); // create sexy gauge!
state.tmp.___gaugeOld__ = {
value: value,
min: min,
max: max,
valueLabel: null,
minLabel: null,
maxLabel: null
};
// we will always feed a percentage
state.tmp.gauge_instance.minValue = 0;
state.tmp.gauge_instance.maxValue = 100;
NETDATA.gaugeAnimation(state, animate);
NETDATA.gaugeSet(state, value, min, max);
NETDATA.gaugeSetLabels(state, value, min, max);
NETDATA.gaugeAnimation(state, true);
state.legendSetUnitsString = function (units) {
if (typeof state.tmp.gaugeChartUnits !== 'undefined' && state.tmp.units !== units) {
state.tmp.gaugeChartUnits.innerText = units;
state.tmp.___gaugeOld__.valueLabel = null;
state.tmp.___gaugeOld__.minLabel = null;
state.tmp.___gaugeOld__.maxLabel = null;
state.tmp.units = units;
}
};
state.legendShowUndefined = function () {
if (typeof state.tmp.gauge_instance !== 'undefined') {
NETDATA.gaugeClearSelection(state);
}
};
return true;
};
// ----------------------------------------------------------------------------------------------------------------
NETDATA.easypiechartPercentFromValueMinMax = function (state, value, min, max) {
if (typeof value !== 'number') {
value = 0;
}
if (typeof min !== 'number') {
min = 0;
}
if (typeof max !== 'number') {
max = 0;
}
if (min > max) {
let t = min;
min = max;
max = t;
}
if (min > value) {
min = value;
}
if (max < value) {
max = value;
}
state.legendFormatValueDecimalsFromMinMax(min, max);
if (state.tmp.easyPieChartMin === null && min > 0) {
min = 0;
}
if (state.tmp.easyPieChartMax === null && max < 0) {
max = 0;
}
let pcent;
if (min < 0 && max > 0) {
// it is both positive and negative
// zero at the top center of the chart
max = (-min > max) ? -min : max;
pcent = Math.round(value * 100 / max);
} else if (value >= 0 && min >= 0 && max >= 0) {
// clockwise
pcent = Math.round((value - min) * 100 / (max - min));
if (pcent === 0) {
pcent = 0.1;
}
} else {
// counter clockwise
pcent = Math.round((value - max) * 100 / (max - min));
if (pcent === 0) {
pcent = -0.1;
}
}
return pcent;
};
// ----------------------------------------------------------------------------------------------------------------
// easy-pie-chart
NETDATA.easypiechartInitialize = function (callback) {
if (typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
$.ajax({
url: NETDATA.easypiechart_js,
cache: true,
dataType: "script",
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function () {
NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
})
.fail(function () {
NETDATA.chartLibraries.easypiechart.enabled = false;
NETDATA.error(100, NETDATA.easypiechart_js);
})
.always(function () {
if (typeof callback === "function") {
return callback();
}
})
} else {
NETDATA.chartLibraries.easypiechart.enabled = false;
if (typeof callback === "function") {
return callback();
}
}
};
NETDATA.easypiechartClearSelection = function (state, force) {
if (typeof state.tmp.easyPieChartEvent !== 'undefined' && typeof state.tmp.easyPieChartEvent.timer !== 'undefined') {
NETDATA.timeout.clear(state.tmp.easyPieChartEvent.timer);
state.tmp.easyPieChartEvent.timer = undefined;
}
if (state.isAutoRefreshable() && state.data !== null && force !== true) {
NETDATA.easypiechartChartUpdate(state, state.data);
}
else {
state.tmp.easyPieChartLabel.innerText = state.legendFormatValue(null);
state.tmp.easyPieChart_instance.update(0);
}
state.tmp.easyPieChart_instance.enableAnimation();
return true;
};
NETDATA.easypiechartSetSelection = function (state, t) {
if (state.timeIsVisible(t) !== true) {
return NETDATA.easypiechartClearSelection(state, true);
}
let slot = state.calculateRowForTime(t);
if (slot < 0 || slot >= state.data.result.length) {
return NETDATA.easypiechartClearSelection(state, true);
}
if (typeof state.tmp.easyPieChartEvent === 'undefined') {
state.tmp.easyPieChartEvent = {
timer: undefined,
value: 0,
pcent: 0
};
}
let value = state.data.result[state.data.result.length - 1 - slot];
let min = (state.tmp.easyPieChartMin === null) ? NETDATA.commonMin.get(state) : state.tmp.easyPieChartMin;
let max = (state.tmp.easyPieChartMax === null) ? NETDATA.commonMax.get(state) : state.tmp.easyPieChartMax;
let pcent = NETDATA.easypiechartPercentFromValueMinMax(state, value, min, max);
state.tmp.easyPieChartEvent.value = value;
state.tmp.easyPieChartEvent.pcent = pcent;
state.tmp.easyPieChartLabel.innerText = state.legendFormatValue(value);
if (state.tmp.easyPieChartEvent.timer === undefined) {
state.tmp.easyPieChart_instance.disableAnimation();
state.tmp.easyPieChartEvent.timer = NETDATA.timeout.set(function () {
state.tmp.easyPieChartEvent.timer = undefined;
state.tmp.easyPieChart_instance.update(state.tmp.easyPieChartEvent.pcent);
}, 0);
}
return true;
};
NETDATA.easypiechartChartUpdate = function (state, data) {
let value, min, max, pcent;
if (NETDATA.globalPanAndZoom.isActive() || state.isAutoRefreshable() === false) {
value = null;
pcent = 0;
}
else {
value = data.result[0];
min = (state.tmp.easyPieChartMin === null) ? NETDATA.commonMin.get(state) : state.tmp.easyPieChartMin;
max = (state.tmp.easyPieChartMax === null) ? NETDATA.commonMax.get(state) : state.tmp.easyPieChartMax;
pcent = NETDATA.easypiechartPercentFromValueMinMax(state, value, min, max);
}
state.tmp.easyPieChartLabel.innerText = state.legendFormatValue(value);
state.tmp.easyPieChart_instance.update(pcent);
return true;
};
NETDATA.easypiechartChartCreate = function (state, data) {
let chart = $(state.element_chart);
let value = data.result[0];
let min = NETDATA.dataAttribute(state.element, 'easypiechart-min-value', null);
let max = NETDATA.dataAttribute(state.element, 'easypiechart-max-value', null);
if (min === null) {
min = NETDATA.commonMin.get(state);
state.tmp.easyPieChartMin = null;
}
else {
state.tmp.easyPieChartMin = min;
}
if (max === null) {
max = NETDATA.commonMax.get(state);
state.tmp.easyPieChartMax = null;
}
else {
state.tmp.easyPieChartMax = max;
}
let size = state.chartWidth();
let stroke = Math.floor(size / 22);
if (stroke < 3) {
stroke = 2;
}
let valuefontsize = Math.floor((size * 2 / 3) / 5);
let valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
state.tmp.easyPieChartLabel = document.createElement('span');
state.tmp.easyPieChartLabel.className = 'easyPieChartLabel';
state.tmp.easyPieChartLabel.innerText = state.legendFormatValue(value);
state.tmp.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
state.tmp.easyPieChartLabel.style.top = valuetop.toString() + 'px';
state.element_chart.appendChild(state.tmp.easyPieChartLabel);
let titlefontsize = Math.round(valuefontsize * 1.6 / 3);
let titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
state.tmp.easyPieChartTitle = document.createElement('span');
state.tmp.easyPieChartTitle.className = 'easyPieChartTitle';
state.tmp.easyPieChartTitle.innerText = state.title;
state.tmp.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
state.tmp.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
state.tmp.easyPieChartTitle.style.top = titletop.toString() + 'px';
state.element_chart.appendChild(state.tmp.easyPieChartTitle);
let unitfontsize = Math.round(titlefontsize * 0.9);
let unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
state.tmp.easyPieChartUnits = document.createElement('span');
state.tmp.easyPieChartUnits.className = 'easyPieChartUnits';
state.tmp.easyPieChartUnits.innerText = state.units_current;
state.tmp.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
state.tmp.easyPieChartUnits.style.top = unittop.toString() + 'px';
state.element_chart.appendChild(state.tmp.easyPieChartUnits);
let barColor = NETDATA.dataAttribute(state.element, 'easypiechart-barcolor', undefined);
if (typeof barColor === 'undefined' || barColor === null) {
barColor = state.chartCustomColors()[0];
} else {
//