I'm using a Gauge chart based on (Chartjs-tsgauge).
I want to set background colors for chart separate from gauge limits. The problem with how Charts.JS renders background because the plugin I used doesn't have a code about backgrounds.
For example, I have a Gauge with limits [0, 20, 40, 60, 80, 100]
. I want to set [0-30]
to green, [30-70]
to yellow and [70-100]
to red.
Current code: CodePEN
Here is my current options.
var ctx = document.getElementById("canvas").getContext("2d");
new Chart(ctx, {
type: "tsgauge",
data: {
datasets: [{
backgroundColor: ["#0fdc63", "#fd9704", "#ff7143"],
borderWidth: 0,
gaugeData: {
value: 7777,
valueColor: "#ff7143"
},
gaugeLimits: [0, 3000, 7000, 10000]
}]
},
options: {
events: [],
showMarkers: true
}
});
And here is my approach for setting background colors.
new Chart(ctx, {
type: "tsgauge",
data: {
datasets: [{
backgroundColor: ["#0fdc63", "#fd9704", "#ff7143"],
borderWidth: 0,
gaugeData: {
value: 50,
valueColor: "#ff7143"
},
gaugeLimits: [0, 20, 40, 60, 80, 100],
gaugeColors: [{
min: 0,
max: 30,
color: ""
}, {
min: 30,
max: 70,
color: ""
},{
min:70,
max:100,
color: ""
}]
}]
},
options: {
events: [],
showMarkers: true
}
});
Currently Chart.JS
matches colors 0
to i
with limits 0
to i
.
I also thought drawing another dummy chart with desired colors and set it on top of the real chart but it seems dodgy way of doing this.
To display pie chart data values of each slice in Chart. js and JavaScript, we can use the chartjs-plugin-labels plugin. to add the script tags for Chart. js, the plugin, and the canvas for the chart.
You can change your dataset a bit to achieve that:
backgroundColor: ["green", "green", "green", "yellow", "yellow", "yellow", "yellow", "red", "red", "red"],
gaugeLimits: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
Since your gagueLimits
are split into a range of 20
currently, so you can't access the limit of 30,
So, what I did here is elaborated the guageLimits
to a split range of 10
and now 30
is accessible, so is 70
,
Now about the colors, each of your guageLimits
require a color in the backgroundColor
array, I set them accordingly, check the above array. Since none of the corresponding colors can be empty, you might need to use the same color until you reach the next color range.
Your requirements cleared now:
Now, technically if you want to set a color range to a specific point, you need to have that value on the gaugeLimits
array, so your reduced gaugeLimits
array now becomes like this:
gaugeLimits: [0, 20, 30, 40, 60, 70, 80, 100],
Well, no you can set the colors accordingly since you have the points specified in the array. Hence, the backgroundColor
array is:
backgroundColor: ["#0fdc63", "#0fdc63", "#fd9704", "#fd9704", "#fd9704", "#ff7143", "#ff7143"]
You still need to hide the 30, and 70 from the scale, I tried to toggle the showMarkers
in your code, looks like it either enables or disables the visible number scale entirely, I tried to provide an array of strings, but it only accepts boolean
Chartjs-tsgauge Doesn't provide much documentation about available options, so I snuck into the code and found markerFormatFn
What it does is take a function as a parameter and you can provide a function telling what to do with each of your marker items.
So, I came up with an idea to provide a function to only show the numbers which are dividable by 20 without any remainder, otherwise return empty string, here it is:
markerFormatFn: n => n % 20 === 0 ? n.toString() : '',
Check the Snippet:
//Gauge Plugin
(function() {
if (!window.Chart) {
return;
}
function GaugeChartHelper() {}
GaugeChartHelper.prototype.setup = function(chart, config) {
this.chart = chart;
this.ctx = chart.ctx;
this.limits = config.data.datasets[0].gaugeLimits;
this.data = config.data.datasets[0].gaugeData;
var options = chart.options;
this.fontSize = options.defaultFontSize;
this.fontStyle = options.defaultFontFamily;
this.fontColor = options.defaultFontColor;
this.ctx.textBaseline = "alphabetic";
this.arrowAngle = 25 * Math.PI / 180;
this.arrowColor = config.options.indicatorColor || options.arrowColor;
this.showMarkers =
typeof config.options.showMarkers === "undefined" ?
true :
config.options.showMarkers;
if (config.options.markerFormatFn) {
this.markerFormatFn = config.options.markerFormatFn;
} else {
this.markerFormatFn = function(value) {
return value;
};
}
};
GaugeChartHelper.prototype.applyGaugeConfig = function(chartConfig) {
this.calcLimits();
chartConfig.data.datasets[0].data = this.doughnutData;
var ctx = this.ctx;
var labelsWidth = this.limits.map(
function(label) {
var text = this.markerFormatFn(label);
return ctx.measureText(text).width;
}.bind(this)
);
var padding = Math.max.apply(this, labelsWidth) + this.chart.width / 35;
var heightRatio = this.chart.height / 50;
chartConfig.options.layout.padding = {
top: this.fontSize + heightRatio,
left: padding,
right: padding,
bottom: heightRatio * 2
};
};
GaugeChartHelper.prototype.calcLimits = function() {
var limits = this.limits;
var data = [];
var total = 0;
for (var i = 1, ln = limits.length; i < ln; i++) {
var dataValue = Math.abs(limits[i] - limits[i - 1]);
total += dataValue;
data.push(dataValue);
}
this.doughnutData = data;
var minValue = limits[0];
var maxValue = limits[limits.length - 1];
this.isRevers = minValue > maxValue;
this.minValue = this.isRevers ? maxValue : minValue;
this.totalValue = total;
};
GaugeChartHelper.prototype.updateGaugeDimensions = function() {
var chartArea = this.chart.chartArea;
this.gaugeRadius = this.chart.innerRadius;
this.gaugeCenterX = (chartArea.left + chartArea.right) / 2;
this.gaugeCenterY =
(chartArea.top + chartArea.bottom + this.chart.outerRadius) / 2;
this.arrowLength = this.chart.radiusLength * 2;
};
GaugeChartHelper.prototype.getCoordOnCircle = function(r, alpha) {
return {
x: r * Math.cos(alpha),
y: r * Math.sin(alpha)
};
};
GaugeChartHelper.prototype.getAngleOfValue = function(value) {
var result = 0;
var gaugeValue = value - this.minValue;
if (gaugeValue <= 0) {
result = 0;
} else if (gaugeValue >= this.totalValue) {
result = Math.PI;
} else {
result = Math.PI * gaugeValue / this.totalValue;
}
if (this.isRevers) {
return Math.PI - result;
} else {
return result;
}
};
GaugeChartHelper.prototype.renderLimitLabel = function(value) {
var ctx = this.ctx;
var angle = this.getAngleOfValue(value);
var coord = this.getCoordOnCircle(
this.chart.outerRadius + this.chart.radiusLength / 2,
angle
);
var align;
var diff = angle - Math.PI / 2;
if (diff > 0) {
align = "left";
} else if (diff < 0) {
align = "right";
} else {
align = "center";
}
ctx.textAlign = align;
ctx.font = this.fontSize + "px " + this.fontStyle;
ctx.fillStyle = this.fontColor;
var text = this.markerFormatFn(value);
ctx.fillText(
text,
this.gaugeCenterX - coord.x,
this.gaugeCenterY - coord.y
);
};
GaugeChartHelper.prototype.renderLimits = function() {
for (var i = 0, ln = this.limits.length; i < ln; i++) {
this.renderLimitLabel(this.limits[i]);
}
};
GaugeChartHelper.prototype.renderValueLabel = function() {
var label = this.data.value.toString();
var ctx = this.ctx;
ctx.font = "30px " + this.fontStyle;
var stringWidth = ctx.measureText(label).width;
var elementWidth = 0.75 * this.gaugeRadius * 2;
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var fontSizeToUse = Math.min(newFontSize, this.gaugeRadius);
ctx.textAlign = "center";
ctx.font = fontSizeToUse + "px " + this.fontStyle;
ctx.fillStyle = this.data.valueColor || this.fontColor;
ctx.fillText(label, this.gaugeCenterX, this.gaugeCenterY);
};
GaugeChartHelper.prototype.renderValueArrow = function(value) {
var angle = this.getAngleOfValue(
typeof value === "number" ? value : this.data.value
);
this.ctx.globalCompositeOperation = "source-over";
this.renderArrow(
this.gaugeRadius,
angle,
this.arrowLength,
this.arrowAngle,
this.arrowColor
);
};
GaugeChartHelper.prototype.renderSmallValueArrow = function(value) {
var angle = this.getAngleOfValue(value);
this.ctx.globalCompositeOperation = "source-over";
this.renderArrow(
this.gaugeRadius - 1,
angle,
this.arrowLength - 1,
this.arrowAngle,
this.arrowColor
);
};
GaugeChartHelper.prototype.clearValueArrow = function(value) {
var angle = this.getAngleOfValue(value);
this.ctx.lineWidth = 2;
this.ctx.globalCompositeOperation = "destination-out";
this.renderArrow(
this.gaugeRadius - 1,
angle,
this.arrowLength + 1,
this.arrowAngle,
"#FFFFFF"
);
this.ctx.stroke();
};
GaugeChartHelper.prototype.renderArrow = function(
radius,
angle,
arrowLength,
arrowAngle,
arrowColor
) {
var coord = this.getCoordOnCircle(radius, angle);
var arrowPoint = {
x: this.gaugeCenterX - coord.x,
y: this.gaugeCenterY - coord.y
};
var ctx = this.ctx;
ctx.fillStyle = arrowColor;
ctx.beginPath();
ctx.moveTo(arrowPoint.x, arrowPoint.y);
coord = this.getCoordOnCircle(arrowLength, angle + arrowAngle);
ctx.lineTo(arrowPoint.x + coord.x, arrowPoint.y + coord.y);
coord = this.getCoordOnCircle(arrowLength, angle - arrowAngle);
ctx.lineTo(arrowPoint.x + coord.x, arrowPoint.y + coord.y);
ctx.closePath();
ctx.fill();
};
GaugeChartHelper.prototype.animateArrow = function() {
var stepCount = 30;
var animateTimeout = 300;
var gaugeValue = this.data.value - this.minValue;
var step = gaugeValue / stepCount;
var i = 0;
var currentValue = this.minValue;
var interval = setInterval(
function() {
i++;
this.clearValueArrow(currentValue);
if (i > stepCount) {
clearInterval(interval);
this.renderValueArrow();
} else {
currentValue += step;
this.renderSmallValueArrow(currentValue);
}
}.bind(this),
animateTimeout / stepCount
);
};
Chart.defaults.tsgauge = {
animation: {
animateRotate: true,
animateScale: false
},
cutoutPercentage: 95,
rotation: Math.PI,
circumference: Math.PI,
legend: {
display: false
},
scales: {},
arrowColor: "#444"
};
Chart.controllers.tsgauge = Chart.controllers.doughnut.extend({
initialize: function(chart) {
var gaugeHelper = (this.gaugeHelper = new GaugeChartHelper());
gaugeHelper.setup(chart, chart.config);
gaugeHelper.applyGaugeConfig(chart.config);
chart.config.options.animation.onComplete = function(chartElement) {
gaugeHelper.updateGaugeDimensions();
gaugeHelper.animateArrow();
};
Chart.controllers.doughnut.prototype.initialize.apply(this, arguments);
},
draw: function() {
Chart.controllers.doughnut.prototype.draw.apply(this, arguments);
var gaugeHelper = this.gaugeHelper;
gaugeHelper.updateGaugeDimensions();
gaugeHelper.renderValueLabel();
if (gaugeHelper.showMarkers) {
gaugeHelper.renderLimits();
}
gaugeHelper.renderSmallValueArrow(gaugeHelper.minValue);
}
});
})();
//Chart setup
var ctx = document.getElementById("chart3").getContext("2d");
new Chart(ctx, {
type: "tsgauge",
data: {
datasets: [{
backgroundColor: ["#0fdc63", "#0fdc63", "#fd9704", "#fd9704", "#fd9704", "#ff7143", "#ff7143"],
borderWidth: 0,
gaugeData: {
value: 50,
valueColor: "#ff7143"
},
gaugeLimits: [0, 20, 30, 40, 60, 70, 80, 100],
}]
},
options: {
events: [],
showMarkers: true,
markerFormatFn: n => n % 20 === 0 ? n.toString() : '',
}
});
.gauge {
width: 500px;
height: 400px;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/Chart.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Chart.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div class="gauge">
<canvas id="chart3"></canvas>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With