364 lines
12 KiB
JavaScript
364 lines
12 KiB
JavaScript
/*!
|
|
* chartjs-gauge.js v0.3.0
|
|
* https://github.com/haiiaaa/chartjs-gauge/
|
|
* (c) 2021 chartjs-gauge.js Contributors
|
|
* Released under the MIT License
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
|
|
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
|
|
(global = global || self, global.Gauge = factory(global.Chart));
|
|
}(this, (function (Chart) { 'use strict';
|
|
|
|
Chart = Chart && Object.prototype.hasOwnProperty.call(Chart, 'default') ? Chart['default'] : Chart;
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function ownKeys(object, enumerableOnly) {
|
|
var keys = Object.keys(object);
|
|
|
|
if (Object.getOwnPropertySymbols) {
|
|
var symbols = Object.getOwnPropertySymbols(object);
|
|
if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
});
|
|
keys.push.apply(keys, symbols);
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
function _objectSpread2(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
|
|
if (i % 2) {
|
|
ownKeys(Object(source), true).forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
} else if (Object.getOwnPropertyDescriptors) {
|
|
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
} else {
|
|
ownKeys(Object(source)).forEach(function (key) {
|
|
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
});
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
Chart.defaults._set('gauge', {
|
|
needle: {
|
|
// Needle circle radius as the percentage of the chart area width
|
|
radiusPercentage: 2,
|
|
// Needle width as the percentage of the chart area width
|
|
widthPercentage: 3.2,
|
|
// Needle length as the percentage of the interval between inner radius (0%) and outer radius (100%) of the arc
|
|
lengthPercentage: 80,
|
|
// The color of the needle
|
|
color: 'rgba(0, 0, 0, 1)'
|
|
},
|
|
valueLabel: {
|
|
// fontSize: undefined
|
|
display: true,
|
|
formatter: null,
|
|
color: 'rgba(255, 255, 255, 1)',
|
|
backgroundColor: 'rgba(0, 0, 0, 1)',
|
|
borderRadius: 5,
|
|
padding: {
|
|
top: 5,
|
|
right: 5,
|
|
bottom: 5,
|
|
left: 5
|
|
},
|
|
bottomMarginPercentage: 5
|
|
},
|
|
animation: {
|
|
duration: 1000,
|
|
animateRotate: true,
|
|
animateScale: false
|
|
},
|
|
// The percentage of the chart that we cut out of the middle.
|
|
cutoutPercentage: 50,
|
|
// The rotation of the chart, where the first data arc begins.
|
|
rotation: -Math.PI,
|
|
// The total circumference of the chart.
|
|
circumference: Math.PI,
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltips: {
|
|
enabled: false
|
|
}
|
|
});
|
|
|
|
var GaugeController = Chart.controllers.doughnut.extend({
|
|
getValuePercent: function getValuePercent(_ref, value) {
|
|
var minValue = _ref.minValue,
|
|
data = _ref.data;
|
|
var min = minValue || 0;
|
|
var max = [undefined, null].includes(data[data.length - 1]) ? 1 : data[data.length - 1];
|
|
var length = max - min;
|
|
var percent = (value - min) / length;
|
|
return percent;
|
|
},
|
|
getWidth: function getWidth(chart) {
|
|
return chart.chartArea.right - chart.chartArea.left;
|
|
},
|
|
getTranslation: function getTranslation(chart) {
|
|
var chartArea = chart.chartArea,
|
|
offsetX = chart.offsetX,
|
|
offsetY = chart.offsetY;
|
|
var centerX = (chartArea.left + chartArea.right) / 2;
|
|
var centerY = (chartArea.top + chartArea.bottom) / 2;
|
|
var dx = centerX + offsetX;
|
|
var dy = centerY + offsetY;
|
|
return {
|
|
dx: dx,
|
|
dy: dy
|
|
};
|
|
},
|
|
getAngle: function getAngle(_ref2) {
|
|
var chart = _ref2.chart,
|
|
valuePercent = _ref2.valuePercent;
|
|
var _chart$options = chart.options,
|
|
rotation = _chart$options.rotation,
|
|
circumference = _chart$options.circumference;
|
|
return rotation + circumference * valuePercent;
|
|
},
|
|
|
|
/* TODO set min padding, not applied until chart.update() (also chartArea must have been set)
|
|
setBottomPadding(chart) {
|
|
const needleRadius = this.getNeedleRadius(chart);
|
|
const padding = this.chart.config.options.layout.padding;
|
|
if (needleRadius > padding.bottom) {
|
|
padding.bottom = needleRadius;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
*/
|
|
drawNeedle: function drawNeedle(ease) {
|
|
if (!this.chart.animating) {
|
|
// triggered when hovering
|
|
ease = 1;
|
|
}
|
|
|
|
var _this$chart = this.chart,
|
|
ctx = _this$chart.ctx,
|
|
config = _this$chart.config,
|
|
innerRadius = _this$chart.innerRadius,
|
|
outerRadius = _this$chart.outerRadius;
|
|
var dataset = config.data.datasets[this.index];
|
|
|
|
var _this$getMeta = this.getMeta(),
|
|
previous = _this$getMeta.previous;
|
|
|
|
var _config$options$needl = config.options.needle,
|
|
radiusPercentage = _config$options$needl.radiusPercentage,
|
|
widthPercentage = _config$options$needl.widthPercentage,
|
|
lengthPercentage = _config$options$needl.lengthPercentage,
|
|
color = _config$options$needl.color;
|
|
var width = this.getWidth(this.chart);
|
|
var needleRadius = radiusPercentage / 100 * width;
|
|
var needleWidth = widthPercentage / 100 * width;
|
|
var needleLength = lengthPercentage / 100 * (outerRadius - innerRadius) + innerRadius; // center
|
|
|
|
var _this$getTranslation = this.getTranslation(this.chart),
|
|
dx = _this$getTranslation.dx,
|
|
dy = _this$getTranslation.dy; // interpolate
|
|
|
|
|
|
var origin = this.getAngle({
|
|
chart: this.chart,
|
|
valuePercent: previous.valuePercent
|
|
}); // TODO valuePercent is in current.valuePercent also
|
|
|
|
var target = this.getAngle({
|
|
chart: this.chart,
|
|
valuePercent: this.getValuePercent(dataset, dataset.value)
|
|
});
|
|
var angle = origin + (target - origin) * ease; // draw
|
|
|
|
ctx.save();
|
|
ctx.translate(dx, dy);
|
|
ctx.rotate(angle);
|
|
ctx.fillStyle = color; // draw circle
|
|
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 0, needleRadius, needleRadius, 0, 0, 2 * Math.PI);
|
|
ctx.fill(); // draw needle
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, needleWidth / 2);
|
|
ctx.lineTo(needleLength, 0);
|
|
ctx.lineTo(0, -needleWidth / 2);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
},
|
|
drawValueLabel: function drawValueLabel(ease) {
|
|
// eslint-disable-line no-unused-vars
|
|
if (!this.chart.config.options.valueLabel.display) {
|
|
return;
|
|
}
|
|
|
|
var _this$chart2 = this.chart,
|
|
ctx = _this$chart2.ctx,
|
|
config = _this$chart2.config;
|
|
var defaultFontFamily = config.options.defaultFontFamily;
|
|
var dataset = config.data.datasets[this.index];
|
|
var _config$options$value = config.options.valueLabel,
|
|
formatter = _config$options$value.formatter,
|
|
fontSize = _config$options$value.fontSize,
|
|
color = _config$options$value.color,
|
|
backgroundColor = _config$options$value.backgroundColor,
|
|
borderRadius = _config$options$value.borderRadius,
|
|
padding = _config$options$value.padding,
|
|
bottomMarginPercentage = _config$options$value.bottomMarginPercentage;
|
|
var width = this.getWidth(this.chart);
|
|
var bottomMargin = bottomMarginPercentage / 100 * width;
|
|
|
|
var fmt = formatter || function (value) {
|
|
return value;
|
|
};
|
|
|
|
var valueText = fmt(dataset.value).toString();
|
|
ctx.textBaseline = 'middle';
|
|
ctx.textAlign = 'center';
|
|
|
|
if (fontSize) {
|
|
ctx.font = "".concat(fontSize, "px ").concat(defaultFontFamily);
|
|
} // const { width: textWidth, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(valueText);
|
|
// const textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent;
|
|
|
|
|
|
var _ctx$measureText = ctx.measureText(valueText),
|
|
textWidth = _ctx$measureText.width; // approximate height until browsers support advanced TextMetrics
|
|
|
|
|
|
var textHeight = Math.max(ctx.measureText('m').width, ctx.measureText("\uFF37").width);
|
|
var x = -(padding.left + textWidth / 2);
|
|
var y = -(padding.top + textHeight / 2);
|
|
var w = padding.left + textWidth + padding.right;
|
|
var h = padding.top + textHeight + padding.bottom; // center
|
|
|
|
var _this$getTranslation2 = this.getTranslation(this.chart),
|
|
dx = _this$getTranslation2.dx,
|
|
dy = _this$getTranslation2.dy; // add rotation
|
|
|
|
|
|
var rotation = this.chart.options.rotation % (Math.PI * 2.0);
|
|
dx += bottomMargin * Math.cos(rotation + Math.PI / 2);
|
|
dy += bottomMargin * Math.sin(rotation + Math.PI / 2); // draw
|
|
|
|
ctx.save();
|
|
ctx.translate(dx, dy); // draw background
|
|
|
|
ctx.beginPath();
|
|
Chart.helpers.canvas.roundedRect(ctx, x, y, w, h, borderRadius);
|
|
ctx.fillStyle = backgroundColor;
|
|
ctx.fill(); // draw value text
|
|
|
|
ctx.fillStyle = color || config.options.defaultFontColor;
|
|
var magicNumber = 0.075; // manual testing
|
|
|
|
ctx.fillText(valueText, 0, textHeight * magicNumber);
|
|
ctx.restore();
|
|
},
|
|
// overrides
|
|
update: function update(reset) {
|
|
var dataset = this.chart.config.data.datasets[this.index];
|
|
dataset.minValue = dataset.minValue || 0;
|
|
var meta = this.getMeta();
|
|
var initialValue = {
|
|
valuePercent: 0
|
|
}; // animations on will call update(reset) before update()
|
|
|
|
if (reset) {
|
|
meta.previous = null;
|
|
meta.current = initialValue;
|
|
} else {
|
|
dataset.data.sort(function (a, b) {
|
|
return a - b;
|
|
});
|
|
meta.previous = meta.current || initialValue;
|
|
meta.current = {
|
|
valuePercent: this.getValuePercent(dataset, dataset.value)
|
|
};
|
|
}
|
|
|
|
Chart.controllers.doughnut.prototype.update.call(this, reset);
|
|
},
|
|
updateElement: function updateElement(arc, index, reset) {
|
|
// TODO handle reset and options.animation
|
|
Chart.controllers.doughnut.prototype.updateElement.call(this, arc, index, reset);
|
|
var dataset = this.getDataset();
|
|
var data = dataset.data; // const { options } = this.chart.config;
|
|
// scale data
|
|
|
|
var previousValue = index === 0 ? dataset.minValue : data[index - 1];
|
|
var value = data[index];
|
|
var startAngle = this.getAngle({
|
|
chart: this.chart,
|
|
valuePercent: this.getValuePercent(dataset, previousValue)
|
|
});
|
|
var endAngle = this.getAngle({
|
|
chart: this.chart,
|
|
valuePercent: this.getValuePercent(dataset, value)
|
|
});
|
|
var circumference = endAngle - startAngle;
|
|
arc._model = _objectSpread2({}, arc._model, {
|
|
startAngle: startAngle,
|
|
endAngle: endAngle,
|
|
circumference: circumference
|
|
});
|
|
},
|
|
draw: function draw(ease) {
|
|
Chart.controllers.doughnut.prototype.draw.call(this, ease);
|
|
this.drawNeedle(ease);
|
|
this.drawValueLabel(ease);
|
|
}
|
|
});
|
|
|
|
/* eslint-disable max-len, func-names */
|
|
var polyfill = function polyfill() {
|
|
if (CanvasRenderingContext2D.prototype.ellipse === undefined) {
|
|
CanvasRenderingContext2D.prototype.ellipse = function (x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
|
|
this.save();
|
|
this.translate(x, y);
|
|
this.rotate(rotation);
|
|
this.scale(radiusX, radiusY);
|
|
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
|
|
this.restore();
|
|
};
|
|
}
|
|
};
|
|
|
|
polyfill();
|
|
Chart.controllers.gauge = GaugeController;
|
|
|
|
Chart.Gauge = function (context, config) {
|
|
config.type = 'gauge';
|
|
return new Chart(context, config);
|
|
};
|
|
|
|
var index = Chart.Gauge;
|
|
|
|
return index;
|
|
|
|
})));
|