I have a web page that is using Chart.js. In this page, I am rendering three donut charts. In the middle of each chart, I want to show the percentage of the donut that is filled. Currently, I have the following code, which can be seen in this Fiddle.
function setupChart(chartId, progress) {
var canvas = document.getElementById(chartId);
var context = canvas.getContext('2d');
var remaining = 100 - progress;
var data = {
labels: [ 'Progress', '', ],
datasets: [{
data: [progress, remaining],
backgroundColor: [
'#8FF400',
'#FFFFFF'
],
borderColor: [
'#8FF400',
'#408A00'
],
hoverBackgroundColor: [
'#8FF400',
'#FFFFFF'
]
}]
};
var options = {
responsive: true,
maintainAspectRatio: false,
scaleShowVerticalLines: false,
cutoutPercentage: 80,
legend: {
display: false
},
animation: {
onComplete: function () {
var xCenter = (canvas.width / 2) - 20;
var yCenter = canvas.height / 2 - 40;
context.textAlign = 'center';
context.textBaseline = 'middle';
var progressLabel = data.datasets[0].data[0] + '%';
context.font = '20px Helvetica';
context.fillStyle = 'black';
context.fillText(progressLabel, xCenter, yCenter);
}
}
};
Chart.defaults.global.tooltips.enabled = false;
var chart = new Chart(context, {
type: 'doughnut',
data: data,
options: options
});
}
The chart "runs". But the problem is with the rendering of the label. The label is not vertically and horizontally centered within the middle of the donut. The position of the label changes based on the screen resolution too. You can see the label change position by resizing the frame that the charts are rendered in within the Fiddle.
My question is, how do I consistently position the percentage in the middle of the donut? My page is a responsive page so, having consistent positioning is important Yet, I'm not sure what else to do. I feel like the textAlign and textBaseline properties aren't working, or I'm misunderstanding their usage.
Thanks!
According to this answer, you could do it with a plugin in a further version. I don't know if you noticed, but if you increase the width and reduce it over and over again on your fiddle, the height of your canvas gets bigger and bigger (the chart goes further and further down).
On the version you are using, you could extend the doughnut controller like this:
http://jsfiddle.net/ivanchaer/cutkqkuz/1/
Chart.defaults.DoughnutTextInside = Chart.helpers.clone(Chart.defaults.doughnut);
Chart.controllers.DoughnutTextInside = Chart.controllers.doughnut.extend({
draw: function(ease) {
var ctx = this.chart.chart.ctx;
var data = this.getDataset();
var easingDecimal = ease || 1;
Chart.helpers.each(this.getDataset().metaData, function(arc, index) {
arc.transition(easingDecimal).draw();
var vm = arc._view;
var radius = (vm.outerRadius + vm.innerRadius) / 2;
var thickness = (vm.outerRadius - vm.innerRadius) / 2;
var angle = Math.PI - vm.endAngle - Math.PI / 2;
ctx.save();
ctx.fillStyle = vm.backgroundColor;
ctx.font = "20px Verdana";
ctx.textAlign = 'center';
ctx.textBaseline = "middle";
var text = data.data[0] + '%';
ctx.fillStyle = '#000';
ctx.fillText(text, $(ctx.canvas).width()/2, $(ctx.canvas).height()/2);
});
ctx.fillStyle = '#fff';
ctx.fill();
ctx.restore();
}
});
function setupChart(chartId, progress) {
var canvas = document.getElementById(chartId);
var context = canvas.getContext('2d');
var remaining = 100 - progress;
var deliveredData = {
labels: [
"Value"
],
datasets: [{
data: [progress, remaining],
backgroundColor: [
'#8FF400',
'#FFFFFF'
],
borderColor: [
'#8FF400',
'#408A00'
],
hoverBackgroundColor: [
'#8FF400',
'#FFFFFF'
]
}]
};
var deliveredOpt = {
cutoutPercentage: 88,
animation: false,
legend: {
display: false
},
tooltips: {
enabled: false
}
};
var chart = new Chart(canvas, {
type: 'DoughnutTextInside',
data: deliveredData,
options: deliveredOpt
});
}
setupChart('standChart', 33);
setupChart('moveChart', 66);
setupChart('exerciseChart', 99);
See this http://jsfiddle.net/uha5tseb/2/
It can be handled with getting width/height of each chart by this reference
onComplete: function (event) {
console.log(this.chart.height);
var xCenter = this.chart.width/2;
var yCenter = this.chart.height/2;
context.textAlign = 'center';
context.textBaseline = 'middle';
var progressLabel = data.datasets[0].data[0] + '%';
context.font = '20px Helvetica';
context.fillStyle = 'black';
context.fillText(progressLabel, xCenter, yCenter);
}
Hope this solves your problem!
You set your variables incorrectly when you get xCenter and yCenter. Currently they don't get the middle of the canvas.
You have:
var xCenter = (canvas.width / 2) - 20;
var yCenter = canvas.height / 2 - 40;
It should be:
var xCenter = (canvas.width / 2);
var yCenter = (canvas.height / 2);
Im not familiar with chart.js, but as far as I understood from the documentation, they provide generateLegend method for your purposes. It returns a data from legendCallback that you can display on the page outside of canvas.
Container of the legend text can be aligned relatively to the canvas's wrapper.
HTML:
<div class="chart" data-legend="">
<canvas id="standChart"></canvas>
</div>
JS:
var container = canvas.parentElement;
...
container.setAttribute('data-legend', chart.generateLegend());
Fiddle
One way to resolve this is by adding an absolutely positioned element on top of the canvas.
You can add a div element after each canvas element and then set its style to:
.precentage {
font-family: tahoma;
font-size: 28px;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
Here is a complete JSFiddle.
Related
I'm trying to create a zoomable canvas with rectangles arranged in a grid using pixi.js. Everything works smoothly except that the grid creates heavy moire effects. My knowledge about pixijs and webgl is only very superficial but I'm suspecting that something with the antialiasing is not working as I expect it to. I'm drawing the rectangles using a 2048x2048px texture I create beforehand in separate canvas. I make it that big so I do this so I can zoom in all the way while still having a sharp rectangle. I also tried using app.renderer.generateTexture(graphics) but got a similar result.
The black rectangles are drawn using pixi.js and the red ones are drawn using SVG as a reference. There is still moire occurring in the SVG as well but it is much less. Any ideas how I can get closer to what the SVG version looks like? You can find a a working version here.
Here's the relevant code I use to setup the pixi.js application:
// PIXI SETUP
const app = new Application({
view: canvasRef,
width,
height,
transparent: true,
antialias: false,
autoDensity: true,
resolution: devicePixelRatio,
resizeTo: window
});
const particleContainer = new ParticleContainer(2500, {
scale: true,
position: true,
rotation: true,
uvs: true,
alpha: true
});
app.stage.addChild(particleContainer);
// TEXTURE
const size = 2048;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, size, size);
ctx.fill();
const texture = PIXI.Texture.from(canvas);
// RECTANGLE GRID
const size = 10;
for(let i=0; i<2500; i++) {
const particle = Sprite.from(texture);
particle.x = i % 50 * size * 1.5;
particle.y = Math.floor(i / 50) * size * 1.5;
particle.anchor.set(0);
particle.width = size;
particle.height = size;
parent.addChild(particle);
}
Don't render sub pixel detail.
The best way to maintain a grid while avoiding artifacts is to not render grid steps below the resolution of the canvas. Eg if you have zoomed out by 100 then do not draw grids less than 100 pixels.
As this can result in grid steps popping in and out you can fade grids in and out depending on the zoom level.
The example shows one way to do this. It still has some artifacts, these are hard to avoid, but this eliminates the horrid moire patterns you get when you render all the detail at every zoom level.
The grid is defined as 2D repeating patterns to reduce rendering overhead.
Also I find grid lines more problematic than grid squares (Demo has both)
This is very basic and can be adapted to any type of grid layout.
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d");
const size = 138;
const grids = createPatterns(size, 4, "#222", "#EEE", "#69B", "#246");
var zoom = 1;
var zoomTarget = 16;
var zoomC = 0;
var gridType = 0;
var origin = {x: ctx.canvas.width / 2, y: ctx.canvas.height / 2};
const scales = [0,0,0];
function createPatterns(size, lineWidth, color1, color2, color3, color4){
function grid(col1, col2) {
ctx.fillStyle = col1;
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = col2;
ctx.fillRect(0, 0, size, lineWidth);
ctx.fillRect(0, 0, lineWidth, size);
}
function grid2(col1, col2) {
ctx.fillStyle = col1;
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = col2;
ctx.fillRect(0, 0, size / 2, size / 2);
ctx.fillRect( size / 2, size / 2, size / 2, size / 2);
}
const patterns = [];
const ctx = Object.assign(document.createElement("canvas"), {width: size, height: size}).getContext("2d");
grid(color1, color2)
patterns[0] = ctx.createPattern(ctx.canvas, "repeat");
grid2(color3, color4)
patterns[1] = ctx.createPattern(ctx.canvas, "repeat");
return patterns;
}
function drawGrid(ctx, grid, zoom, origin, smooth = true) {
function zoomAlpha(logScale) {
const t = logScale % 3;
return t < 1 ? t % 1 : t > 2 ? 1 - (t - 2) % 1 : 1;
}
function fillScale(scale) {
ctx.setTransform(scale / 8, 0, 0, scale / 8, origin.x, origin.y);
ctx.globalAlpha = zoomAlpha(Math.log2(scale));
ctx.fill();
}
ctx.fillStyle = grid;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.imageSmoothingEnabled = smooth;
ctx.beginPath();
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalAlpha = 1;
const l2 = Math.log2(zoom);
scales[0] = 2 ** ((l2 + 122) % 3); // zoom limit 1 / (2 ** 122) (well beyond number precision)
scales[1] = 2 ** ((l2 + 123) % 3);
scales[2] = 2 ** ((l2 + 124) % 3);
scales.sort((a,b) => a - b);
fillScale(scales[0]);
fillScale(scales[1]);
fillScale(scales[2]);
ctx.globalAlpha = 1;
}
function mainLoop() {
if (innerWidth !== ctx.canvas.width || innerHeight !== ctx.canvas.height) {
origin.x = (ctx.canvas.width = innerWidth) / 2;
origin.y = (ctx.canvas.height = innerHeight) / 2;
zoomTarget = 16;
zoom = 1;
}
zoomC += (zoomTarget - zoom) * 0.3;
zoomC *= 0.02;
zoom += zoomC;
if (gridType === 0) {
drawGrid(ctx, grids[0], zoom, origin);
} else {
drawGrid(ctx, grids[1], zoom, origin, false);
}
requestAnimationFrame(mainLoop);
}
zoomIn.addEventListener("click", () => zoomTarget *= 2);
zoomOut.addEventListener("click", () => zoomTarget *= 1/2);
toggle.addEventListener("click", () => gridType = (gridType + 1) % 2);
* { margin: 0px;}
canvas { position: absolute; top: 0px;left: 0px; }
.UI { position: absolute; top: 14px; left: 14px; }
<canvas id="canvas"></canvas>
<div class="UI">
<button id="zoomIn">Zoom In</button><button id="zoomOut">Zoom Out</button><button id="toggle">Toggle grid type</button>
</div>
I am new to writing JS/jquery and looking to build a doughnut charts.js chart with two lines of text: a larger size percentage on top and a smaller text only 'category' on the bottom as in the image below.
Sample Chart
I have had limited success with the code below taken from a fiddle link found on another similar stackoverflow post
https://jsfiddle.net/cmyker/ooxdL2vj/
Struggling to understand how to have two lines of responsive text. Ex. getting the "prepaid" to scale without affecting the size of the percentage text above. Any help is greatly appreciated.
HTML:
<canvas id="myChart"> </canvas>
Code:
Chart.pluginService.register({
beforeDraw: function (chart) {
if (chart.config.options.elements.center) {
//Get ctx from string
var ctx = chart.chart.ctx;
//Get options from the center object in options
var centerConfig = chart.config.options.elements.center;
var fontStyle = centerConfig.fontStyle || 'Arial';
var txt = centerConfig.text;
var txtB = centerConfig.textB;
var color = centerConfig.color || '#000';
var sidePadding = centerConfig.sidePadding || 20;
var sidePaddingCalculated = (sidePadding/100) * (chart.innerRadius * 2)
//Start with a base font of 30px
ctx.font = "30px " + fontStyle;
//Get the width of the string and also the width of the element minus 10 to give it 5px side padding
var stringWidth = ctx.measureText(txt).width;
var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
// Find out how much the font can grow in width.
var widthRatio = elementWidth / stringWidth;
var newFontSize = Math.floor(30 * widthRatio);
var elementHeight = (chart.innerRadius * 2);
// Pick a new font size so it will not be larger than the height of label.
var fontSizeToUse = Math.min(newFontSize, elementHeight);
//Set font settings to draw it correctly.
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
ctx.font = fontSizeToUse+"px " + fontStyle;
ctx.fillStyle = color;
//Draw text in center
ctx.fillText(txt, centerX, centerY);
}
}
});
var config = {
type: 'doughnut',
data: {
labels: [
"Red",
"Green",
"Yellow"
],
datasets: [{
data: [300, 50, 100],
backgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56"
]
}]
},
options: {
legend: {
display: false},
cutoutPercentage: 70,
aspectRatio: 1.5,
elements: {
center: {
text: '80%' ,
color: 'black', // Default is #000000
fontStyle: 'Arial', // Default is Arial
sidePadding: 20 // Defualt is 20 (as a percentage)
}
}
}
};
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, config);
If I use your sample chart as a template, this can be done with the following code.
Chart.pluginService.register({
beforeDraw: (chart) => {
let ctx = chart.chart.ctx;
ctx.save();
let fontSize = (chart.chart.height / 75).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
let text = chart.data.datasets[0].data[0] + '%';
let textX = Math.round((chart.chart.width - ctx.measureText(text).width) / 2);
let textY = chart.chart.height / 2.25;
ctx.fillText(text, textX, textY);
fontSize = (chart.chart.height / 124).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
text = chart.data.labels[0];
textX = Math.round((chart.chart.width - ctx.measureText(text).width) / 2);
textY = chart.chart.height / 1.6;
ctx.fillText(text, textX, textY);
ctx.restore();
}
});
var config = {
type: 'doughnut',
data: {
labels: ["Prepaid", "Others"],
datasets: [{
data: [90.8, 9.2],
backgroundColor: ["yellow", "white" ]
}]
},
options: {
legend: {
display: false
},
tooltips: {
enabled: false
},
cutoutPercentage: 80,
aspectRatio: 5,
rotation: 70
}
};
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart"> </canvas>
I created a Donut chart, which works correctly but now I need to show the number 45 in the center of this, for example.
Where should I indicate the text to be displayed and the coordinates? In the options of the chart?
I'm using react component
class DoughnutChart extends React.Component {
render() {
const data = {
datasets: [{
data: [],
backgroundColor: [
'#999',
'#eee'
]
}],
text: '45'
};
return (
<Doughnut data={data} />
);
}
};
export default DoughnutChart;
EDIT
I found this plugin, but I cann't find how it applies to the react component
//Plugin for center text
Chart.pluginService.register({
beforeDraw: function(chart) {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 160).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "top";
var text = "Foo-bar",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
});
Thanks for your help.
I tried this and it worked
import { Doughnut } from "react-chartjs-2";
function DoughnutChart() {
const data = {...}
const options = {...}
const plugins = [{
beforeDraw: function(chart) {
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 160).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "top";
var text = "Foo-bar",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}]
return (
<Doughnut
type="doughnut"
data={data}
options{options}
plugins={plugins}
/>
);
}
export default DoughnutChart;
I don't believe React Chart JS has this option available. However, there are custom plugins out there that allow you to insert custom data labels inside of your doughnut charts. Please refer to this documentation on how to accomplish that.
ChartJS Plugin Datalabels
From what I've seen from react-chartjs-2 it only has these properties:
data: (PropTypes.object | PropTypes.func).isRequired,
width: PropTypes.number,
height: PropTypes.number,
id: PropTypes.string,
legend: PropTypes.object,
options: PropTypes.object,
redraw: PropTypes.bool,
getDatasetAtEvent: PropTypes.func,
getElementAtEvent: PropTypes.func,
getElementsAtEvent: PropTypes.func
onElementsClick: PropTypes.func, // alias for getElementsAtEvent (backward compatibility)
Non of them seem to be text you can pass in to show your number in the middle. You can maybe use the legend prop and manipulate the object created for the legend to move it with css or have another div inside your render that displays the number and then style that to be inside the chart.
Another way you may be able to do it is by wrapping the Doughnut component in a div with another child inside that div that contains the text, like so:
return(
<div className='wrapper'>
<div className='chart-number'>{this.state.text}</div>
<Doughnut data={data} />
</div>)
in your css then you will have to figure out how to position the chart-number div in the middle of the wrapper div, which will contain the chart.
If you are just starting to use this library, maybe look for a different one that suits your needs so you don't have to code around it!
Hope this helps :)
EDIT 1:
From what I see, you probably need to connect the Chart.pluginService.register code to your graph object somehow which is the <Doughnut /> component, then it will do the calculation before the redraw.
Here is a simple solution:
Step 01: add this options in your options
options: {
centerText: {
display: true,
text: `90%`
}
.....
.....
Step 02: Create a new method drawInnerText
drawInnerText = (chart) => {
var width = chart.chart.width,
height = chart.chart.height,
ctx = chart.chart.ctx;
ctx.restore();
var fontSize = (height / 114).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "middle";
var text = chart.config.options.centerText.text,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
Step 03: Invoke drawInnerText
componentWillMount() {
Chart.Chart.pluginService.register({
beforeDraw: function (chart) {
if (chart.config.options.centerText.display !== null &&
typeof chart.config.options.centerText.display !== 'undefined' &&
chart.config.options.centerText.display) {
this.drawInnerText(chart);
}
},
});
}
function DoughnutChart({ data = {} }) {
return (
<Doughnut
data={format(dataObj)}
plugins={[
{
beforeDraw(chart) {
const { width } = chart;
const { height } = chart;
const { ctx } = chart;
ctx.restore();
const fontSize = (height / 160).toFixed(2);
ctx.font = `${fontSize}em sans-serif`;
ctx.textBaseline = 'top';
const { text } = "23";
const textX = Math.round((width - ctx.measureText(text).width) / 2);
const textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
},
},
]}
/>);
}
I tried Jays Answer before but nowadays it doesnt work anymore.
You need to specify an id inside the plugins array.
const plugins = [{
id : 'here comes your id for the specific plugin',
beforeDraw: function(chart) {
var width = chart.width,
height = chart.height,
ctx = chart.ctx;
ctx.restore();
var fontSize = (height / 160).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.textBaseline = "top";
var text = "Foo-bar",
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
}
}]
I have tried Jay answer and at first it did not work so I had to make some updates to it and now it is working.
const plugins = {[
{
id:"Id here",
beforeDraw:function(chart:any) {
let ctx = chart.ctx;
let xAxis = chart.scales.x;
let yAxis = chart.scales.y;
let maxValue = Math.max(...chart.data.datasets[0].data);
let minValue = Math.min(...chart.data.datasets[0].data);
ctx.save();
ctx.textAlign = 'center';
ctx.font = '14px Roboto';
ctx.fillStyle = 'blue';
ctx.textAlign = 'left';
ctx.fillText('Text1 = ', xAxis.left , yAxis.top + 15);
ctx.fillText('Text2 = ', xAxis.left , yAxis.top + 40);
ctx.fillText(maxValue + '°C', xAxis.left + 200, yAxis.top + 15);
ctx.fillText(minValue + '°C', xAxis.left + 200, yAxis.top + 40);
ctx.restore();
},
},
]}
I am plotting a bar chart with some series values in a javascript loop. Even though the code drawing the series values lies below the code that draws the bars, in the next iteration of the loop the text that has just been drawn gets overwritten.
You can see this effect taking place in the code below as the alert boxes appear.
Why do drawing operations on another part of the canvas overwrite something drawn previously in a completely different place?
Update: Someone has helpfully pointed out that using fillRect() hides this issue, but my question is rather: why is it happening in the first place?
var exampleData = {
"Series 1": 10,
"Series 2": 14,
"Series 3": 2,
"Series 4": 12
};
var BarChart = function(options) {
this.options = options;
this.canvas = options.canvas;
this.ctx = this.canvas.getContext("2d");
this.colors = options.colors;
this.plot = function() {
var maxValue = 0;
for (var categ in this.options.data) {
maxValue = Math.max(maxValue, this.options.data[categ]);
}
var noBars = Object.keys(this.options.data).length;
var barWidth = (this.canvas.height) / noBars;
var barIdx = 0;
for (categ in this.options.data) {
var barLength = Math.round(this.canvas.width * this.options.data[categ] / maxValue);
this.ctx.save();
alert("plotting series line " + categ);
this.ctx.fillStyle = this.colors[barIdx % this.colors.length];
this.ctx.rect(30, barIdx * barWidth, barLength, barWidth);
this.ctx.fill();
alert("plotting series value " + categ);
this.ctx.fillStyle = "#000000";
this.ctx.font = "24px Georgia";
this.ctx.textBaseline = "middle";
this.ctx.fillText(this.options.data[categ], 25, barIdx * barWidth + barWidth / 2); //will be covered in the loop's next iteration. Why?
this.ctx.restore();
barIdx++;
}
}
}
function init() {
var myCanvas = document.getElementById("myCanvas");
myCanvas.width = 800;
myCanvas.height = 300;
var ctx = myCanvas.getContext("2d");
var myBarChart = new BarChart({
canvas: myCanvas,
seriesName: "Example Series",
padding: 40,
data: exampleData,
colors: ["#D1E3F3", "#D1E3F3", "#D1E3F3", "#D1E3F3"]
});
myBarChart.plot();
}
document.addEventListener("DOMContentLoaded", init, false);
<canvas id="myCanvas"></canvas>
Changing :
this.ctx.rect(30, barIdx * barWidth, barLength, barWidth);
this.ctx.fill();
To instead use fillRect() fixes the problem:
this.ctx.fillRect(30, barIdx * barWidth, barLength, barWidth);
Working example here (compared to the original example here)
i'm, using charts.js librarie and would like to know how could I add some mark to the hole of a doughnut chart (sth like a percentage)-
My js
jQuery(document).ready(function(){
var data = [
{
value: 5,
color:"#A1638C",
highlight: "#BF7AAF",
label: "Días Completados 1/21"
},
{
value: 95,
color: "#07659A",
highlight: "#4190BA",
label: "Días pendientes 20/21"
},
]
var ctx = jQuery("#myChart").get(0).getContext("2d");
var myDoughnutChart = new Chart(ctx).Doughnut(data);
});
My canvas:
<canvas id="myChart" width="264px"></canvas>
The donut chart is centered in the canvas, so you can calculate the center of the donut like this:
// get the canvas element and its context
var canvas = document.getElementById("myChart");
var ctx = canvas.getContext("2d");
// calculate the center of the canvas (cx,cy)
var cx=canvas.width/2;
var cy=canvas.height/2;
Then you can tell canvas to draw text vertically & horizontally centered around cx,cy like this:
// horizontally align text around the specified point (cx)
ctx.textAlign='center';
// vertically align text around the specified point (cy)
ctx.textBaseline='middle';
// draw the text
ctx.font='14px verdana';
ctx.fillStyle='black';
ctx.fillText("Text Here",cx,cy);
But you must wait for any animations to complete before drawing your centered text.
To do that you must tell ChartJS that you want a callback function triggered when it completes its animation. You can set the callback by setting the onAnimationComplete property of the chart options:
var myDoughnutChart = new Chart(ctx).Doughnut(data, {
responsive : true,
// when ChartJS has completed all animations then call the addText function
onAnimationComplete: addText
});
Here's a demo putting it all together:
var doughnutData = [{
value: 300,
color: "#F7464A",
highlight: "#FF5A5E",
label: "Red"
}, {
value: 50,
color: "#46BFBD",
highlight: "#5AD3D1",
label: "Green"
}, {
value: 100,
color: "#FDB45C",
highlight: "#FFC870",
label: "Yellow"
}, {
value: 40,
color: "#949FB1",
highlight: "#A8B3C5",
label: "Grey"
}, {
value: 120,
color: "#4D5360",
highlight: "#616774",
label: "Dark Grey"
}
];
window.onload = function() {
var canvas = document.getElementById("chart-area");
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = new Chart(ctx).Doughnut(doughnutData, {
responsive: true,
onAnimationComplete: addText
});
};
var myDoughnutChart = new Chart(ctx).Doughnut(data);
var myDoughnutChart = new Chart(ctx).Doughnut(doughnutData, {
responsive: true,
onAnimationComplete: addText
});
function addText() {
var canvas = document.getElementById("chart-area");
var ctx = document.getElementById("chart-area").getContext("2d");
var cx = canvas.width / 2;
var cy = canvas.height / 2;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '14px verdana';
ctx.fillStyle = 'black';
ctx.fillText("Text Here", cx, cy);
}
body {
padding: 10px;
margin: 0;
}
#canvas-holder {
width: 30%;
}
canvas {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.1/Chart.min.js"></script>
<div id="canvas-holder">
<canvas id="chart-area" width="500" height="500" />
</div>
Draw method creates the canvas for chart. On hover draw method is called to re-create the chart and show the tool-tip. Text disappears because there is no code to show text inside draw method as we are adding text manually outside of API.
You can achieve this by extending the chart. Follow Docs here.
Following is the example of adding total records at the centre of the
doughnut chart.
Chart.defaults.derivedDoughnut = Chart.defaults.doughnut;
var customDoughnut = Chart.controllers.doughnut.extend({
draw: function(ease) {
Chart.controllers.doughnut.prototype.draw.apply(this, [ease]);
var width = this.chart.chart.width,
height = this.chart.chart.height;
var total = this.getMeta().total;
var fontSize = (height / 114).toFixed(2);
var ctx = this.chart.chart.ctx;
ctx.font = fontSize + "em Verdana";
ctx.textBaseline = "middle";
var text = total,
textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
ctx.fillText(text, textX, textY);
}
});
Chart.controllers.derivedDoughnut = customDoughnut;
Example: jsfiddle
I think the accepted answer does not work, at least for me. Here is my solution:
let canvas = document.getElementById('chart-area');
let ctx = canvas.getContext('2d');
let perc = 25;
const config = {
type: 'doughnut',
data: {
datasets: [{
data: [
perc,
100-perc
],
backgroundColor: [
window.chartColors.green,
window.chartColors.gray
]
}]
},
options: {
responsive: true,
animation: {
animateScale: true,
animateRotate: true,
onComplete : function() {
var cx = canvas.width / 2;
var cy = canvas.height / 2;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '16px verdana';
ctx.fillStyle = 'black';
ctx.fillText(perc+"%", cx, cy);
}
},
}
};
new Chart(ctx, config);
A pure css solution
.overview-total {
position: relative;
.overview-num{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
Html
<div class="overview-total">
<span class="overview-num">140</span>
<canvas baseChart class="chart-size" width="300" height="180"
[plugins]="doughnutChartPluginsSo"
[colors]="doughnutChartColorsSo"
[data]="doughnutChartDataSo"
[labels]="doughnutChartLabelsSo"
[chartType]="doughnutChartTypeSo"
[options]="doughnutChartOptionsSo"></canvas>
</div>