Related
I am trying to migrate this chart made by excel into chartJS:
important features:
having a horizontal bar showing a range
let range be from 'lower' (0%) til 'upper' (100%)
median is shown in by a vertical line (50%)
place one single point somewhere on the range (e.g. at 23%)
the single point is the only dynamic component here, everything else always looks the same
I know thats not a typical chart and a bit special.
Closest charts I found are:
1)
a simple stacked bar chart were instead of the bright obvious star I just another bar-stack
not very pretty the same
a mixed chart
with an line using the annotations plugin (here I draw the line with paint by hand)
in css the whole chart needs to be rotated by 90° (already done in the image shown below)
Another option my be creating it by using plane css stuff. But Id rather make it using chart js since there are some more charts and all my framework is made for it.
Any idea is appreciated.
Thanks.
Charts.js allows you to access the canvas and draw custom stuff, using a simple mechanism.
We could start with just the bar, as a horizontal bar chart with one item and most other stuff disabled:
const data = {
labels: [""],
datasets: [{
label: '100%',
data: [100],
backgroundColor: '#4af',
barThickness: 100
}]
};
const options = {
type: 'bar',
data,
options: {
animation: {duration: 0},
indexAxis: 'y',
layout: {
padding:{
left: 10,
right: 10
}
},
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip:{
enabled: false
}
}
}
};
const chart = new Chart(document.getElementById("myChart"), options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.0/chart.umd.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body>
<canvas id="myChart" style="height:250px; width: 90vw; border: 1px solid #ddd "></canvas>
</body>
Now we can access the canvas through a plugin a draw all the rest:
const plugin = {
id: 'customDraw', // to identify the plugin in the chart options
afterDraw: (chart, args, options) => {
const {ctx} = chart;
// read plugin options
const lineWidth = options.lineWidth || 1,
lineColor = options.lineColor || '#000',
textColor = options.textColor || '#000',
textFont = options.textFont,
starAt = options.starAt,
starColor = options.starColor || '#f44';
// get pixel coordinates for our bar, that is
// positioned at y = 0, from x = 0 to x = 100
const yCenter = chart.scales.y.getPixelForValue(0),
yTop = yCenter-50,
yBottom = yCenter+50,
x0 = chart.scales.x.getPixelForValue(0),
x50 = chart.scales.x.getPixelForValue(50),
x100 = chart.scales.x.getPixelForValue(100),
xStar = chart.scales.x.getPixelForValue(starAt);
ctx.save();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x50, yTop);
ctx.lineTo(x50, yBottom);
ctx.stroke();
ctx.fillStyle = starColor;
drawStar(ctx, xStar, yCenter, 10);
ctx.textBaseline = "top";
ctx.fillStyle = textColor;
if(textFont){
ctx.font = textFont;
}
ctx.textAlign = "start";
ctx.fillText("Lower", x0, yBottom + 2);
ctx.textAlign = "center";
ctx.fillText("Median", x50, yBottom + 2);
ctx.textAlign = "right";
ctx.fillText("Upper", x100, yBottom + 2);
ctx.restore();
}
};
Full code:
function drawStar(ctx, x0, y0, radius){
//https://stackoverflow.com/a/58043598/16466946
const nSpikes = 5;
ctx.beginPath();
for(let i = 0; i < nSpikes*2; i++){
let rotation = Math.PI/2;
let angle = (i/(nSpikes*2))*Math.PI*2+rotation;
let dist = radius*(i%2)+radius;
let x = x0+Math.cos(angle)*dist;
let y = y0+Math.sin(angle)*dist;
if(i === 0) {
ctx.moveTo(x, y);
continue; //skip
}
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}
const plugin = {
id: 'customDraw',
afterDraw: (chart, args, options) => {
const {ctx} = chart;
// read plugin options
const lineWidth = options.lineWidth || 1,
lineColor = options.lineColor || '#000',
textColor = options.textColor || '#000',
textFont = options.textFont,
starAt = options.starAt,
starColor = options.starColor || '#f44';
// get pixel coordinates for our bar, that is
// positioned at y = 0, from x = 0 to x = 100
const yCenter = chart.scales.y.getPixelForValue(0),
yTop = yCenter-50,
yBottom = yCenter+50,
x0 = chart.scales.x.getPixelForValue(0),
x50 = chart.scales.x.getPixelForValue(50),
x100 = chart.scales.x.getPixelForValue(100),
xStar = chart.scales.x.getPixelForValue(starAt);
ctx.save();
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(x50, yTop);
ctx.lineTo(x50, yBottom);
ctx.stroke();
ctx.fillStyle = starColor;
drawStar(ctx, xStar, yCenter, 10);
ctx.textBaseline = "top";
ctx.fillStyle = textColor;
if(textFont){
ctx.font = textFont;
}
ctx.textAlign = "start";
ctx.fillText("Lower", x0, yBottom + 2);
ctx.textAlign = "center";
ctx.fillText("Median", x50, yBottom + 2);
ctx.textAlign = "right";
ctx.fillText("Upper", x100, yBottom + 2);
ctx.restore();
}
};
const data = {
labels: [""],
datasets: [{
label: '100%',
data: [100],
backgroundColor: '#4af',
barThickness: 100
}]
};
const options = {
type: 'bar',
data,
options: {
animation: {duration: 0},
indexAxis: 'y',
layout: {
padding:{
left: 10,
right: 10
}
},
scales: {
x: {
display: false
},
y: {
display: false
}
},
plugins: {
legend: {
display: false
},
tooltip:{
enabled: false
},
customDraw:{
lineWidth: 3,
textFont: '20px serif',
starAt: 23
}
}
},
plugins: [plugin],
};
new Chart(document.getElementById("myChart"), options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.2.0/chart.umd.js" integrity="sha512-B51MzT4ksAo6Y0TcUpmvZnchoPYfIcHadIaFqV5OR5JAh6dneYAeYT1xIlaNHhhFAALd5FLDTWNt/fkxhwE/oQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body>
<canvas id="myChart" style="height:250px; width: 90vw; border: 1px solid #ddd "></canvas>
</body>
Next step would be to add custom interaction - tooltips, click events and everything...
Is there a way to force the tooltip to stay inside the canvas?
right now if the window is too small, the pop up is not visible.
So, is there a way to force the tooltip to stay inside the canvas?
Well, if you are using a custom tooltip, like this one, you can create a offset so the tooltip will stay away from the borders:
var offset = tooltip.caretX + 20;
if (offset < tooltip.width)
offset = tooltip.width;
else if (tooltip.caretX > this._chart.width - tooltip.width)
offset = this._chart.width - tooltip.width;
// Hidden Code
tooltipEl.style.left = positionX + offset + 'px';
An working example, this code have been copied from another one of my answers in this post:
var customTooltips = function(tooltip) {
// Tooltip Element
var tooltipEl = document.getElementById('tooltip');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'tooltip';
tooltipEl.innerHTML = '<table></table>';
this._chart.canvas.parentNode.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
var innerHtml = '<thead>';
titleLines.forEach(function(title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function(body, i) {
var colors = tooltip.labelColors[i];
var style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
var innerContent = '<td>' + span + body + '</td>';
// Every even/odd create a new tr
if (i % 2 == 0)
innerHtml += '<tr>' + innerContent;
else
innerHtml += innerContent + '</tr>';
});
// If is a odd number of itens close the last open tr
if (bodyLines.count % 2 == 1)
innerHtml += '</tr></tbody>';
else
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
var positionY = this._chart.canvas.offsetTop;
var positionX = this._chart.canvas.offsetLeft;
var offset = tooltip.caretX + 20;
if (offset < tooltip.width)
offset = tooltip.width;
else if (tooltip.caretX > this._chart.width - tooltip.width)
offset = this._chart.width - tooltip.width;
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + offset + 'px';
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};
var myChart = new Chart($('#myChart'), {
type: 'line',
data: {
labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4'],
datasets: [{
label: 'Dats asd asda 1',
data: [12, 19, 3, 5],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(255, 0, 0, 0.2)'
}, {
label: 'D 2',
data: [13, 17, 4, 6],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(255, 255, 0, 0.2)'
}, {
label: 'D 3',
data: [14, 19, 3, 9],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(0, 255, 0, 0.2)'
}, {
label: 'Data 4',
data: [15, 20, 2, 8],
pointRadius: 5,
pointHoverRadius: 5,
backgroundColor: 'rgba(0, 0, 255, 0.2)'
}]
},
options: {
responsive: false,
scales: {
yAxes: [{
display: true,
ticks: {
suggestedMax: 50,
}
}]
},
tooltips: {
enabled: false,
mode: 'index',
intersect: false,
custom: customTooltips
}
}
});
#tooltip {
opacity: 1;
position: absolute;
background: rgba(0, 0, 0, .7);
color: white;
border-radius: 3px;
-webkit-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
padding: 4px;
}
#tooltip td {
text-align: left;
}
.chartjs-tooltip-key {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" integrity="sha256-aa0xaJgmK/X74WM224KMQeNQC2xYKwlAt08oZqjeF0E=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" integrity="sha256-Uv9BNBucvCPipKQ2NS9wYpJmi8DTOEfTA/nH2aoJALw=" crossorigin="anonymous"></script>
<canvas id="myChart" width="400" height="200"></canvas>
Take a look at this: https://stackoverflow.com/a/64887282/8411093
Demo: https://codepen.io/themustafaomar/pen/wvWZrod
function customTooltips(tooltipModel) {
// Tooltip Element
var tooltipEl = document.getElementById("chartjs-tooltip");
const yAlign = tooltipModel.yAlign;
const xAlign = tooltipModel.xAlign;
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement("div");
tooltipEl.id = "chartjs-tooltip";
tooltipEl.innerHTML = "<table></table>";
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove("top", "bottom", "center", "left", "right");
if (tooltipModel.yAlign || tooltipModel.xAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
tooltipEl.classList.add(tooltipModel.xAlign);
}
// Set Text
if (tooltipModel.body) {
var titleLines = tooltipModel.title || [];
var bodyLines = tooltipModel.body.map((bodyItem) => {
return bodyItem.lines;
});
var innerHtml = "<thead>";
titleLines.forEach(function (title) {
innerHtml += '<tr><th><div class="mb-1">' + title + "</div></th></tr>";
});
innerHtml += "</thead><tbody>";
bodyLines.forEach((body, i) => {
var colors = tooltipModel.labelColors[i];
// var style = 'background-color:' + colors.borderColor
var style =
"background-color:" + this._chart.data.datasets[i].borderColor;
var value = tooltipModel.dataPoints[i].value;
var label = this._chart.data.datasets[i].label;
style += "; border-color:" + colors.borderColor;
style += "; border-color:" + this._chart.data.datasets[i].borderColor;
style += "; border-width: 2px";
var span =
'<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += `<tr><td> ${span} $${value}K </td></tr>`;
});
innerHtml += "</tbody>";
var tableRoot = tooltipEl.querySelector("table");
tableRoot.innerHTML = innerHtml;
}
// Tooltip height and width
const { height, width } = tooltipEl.getBoundingClientRect();
// Chart canvas positions
const positionY = this._chart.canvas.offsetTop;
const positionX = this._chart.canvas.offsetLeft;
// Carets
const caretY = tooltipModel.caretY;
const caretX = tooltipModel.caretX;
// Final coordinates
let top = positionY + caretY - height;
let left = positionX + caretX - width / 2;
let space = 8; // This for making space between the caret and the element.
// yAlign could be: `top`, `bottom`, `center`
if (yAlign === "top") {
top += height + space;
} else if (yAlign === "center") {
top += height / 2;
} else if (yAlign === "bottom") {
top -= space;
}
// xAlign could be: `left`, `center`, `right`
if (xAlign === "left") {
left = left + width / 2 - tooltipModel.xPadding - space / 2;
if (yAlign === "center") {
left = left + space * 2;
}
} else if (xAlign === "right") {
left -= width / 2;
if (yAlign === "center") {
left = left - space;
} else {
left += space;
}
}
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
// Left and right
tooltipEl.style.top = `${top}px`;
tooltipEl.style.left = `${left}px`;
// Font
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
// Paddings
tooltipEl.style.padding =
tooltipModel.yPadding + "px " + tooltipModel.xPadding + "px";
}
Link to jsFiddle
google.charts.load('current', {
'packages': ['corechart']
});
//Input data
var data = [
['Data', 'CAT1', 'CAT2', 'CAT3', 'CAT4'],
['Provisions', 5, 0, 0, 0],
];
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
var options = {
colors: ['#00699B', '#087EB4', '#CBE7F7', '8A6996'],
isStacked: true,
chartArea: {
width: '40%'
},
bar: {
groupWidth: "40%"
},
// tooltip: { isHtml: true },
trigger: 'both',
vAxis: {
gridlines: {
color: '#0000006b',
minValue: 0,
baseline: 0
},
format: "$ #,###"
},
};
var dataTable = google.visualization.arrayToDataTable(data);
//Formatters
var intergerFormatter = new google.visualization.NumberFormat({
groupingSymbol: ",",
fractionDigits: 0
});
for (var i = 0; i < data[0].length; i++) {
intergerFormatter.format(dataTable, i);
}
var view = new google.visualization.DataView(dataTable);
var cols = [0];
for (var i = 1; i < data[0].length; i++) {
cols.push({
sourceColumn: i,
type: "number",
label: data[0][i]
});
cols.push({
calc: createTooltip(i),
type: "string",
role: "tooltip",
});
}
view.setColumns(cols);
var chart = new google.visualization.ColumnChart(document.getElementById('provision_chart'));
chart.draw(view, options);
function createTooltip(col) {
return function(dataTable, row) {
var html = dataTable.getColumnLabel(col) + ":" + "\n";
html += "4 " + dataTable.getValue(row, 0) + "\n";
html += "$ " + intergerFormatter.formatValue(dataTable.getValue(row, col)) + " total" + "\n";
return html;
};
}
}
The grid lines on a stacked bar type google charts are not rendering properly.
As per the data, $5 is recorded against Category1, but when it's rendered the bar is slightly over $5.
Can someone suggest a fix?
removing the option --> format: "$ #,###" -- reveals the problem
although the tick mark displays --> $ 5 -- the actual number used is 4.5
see following working snippet...
google.charts.load('current', {
'packages': ['corechart']
});
//Input data
var data = [
['Data', 'CAT1', 'CAT2', 'CAT3', 'CAT4'],
['Provisions', 5, 0, 0, 0],
];
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
var options = {
colors: ['#00699B', '#087EB4', '#CBE7F7', '8A6996'],
isStacked: true,
chartArea: {
width: '40%'
},
bar: {
groupWidth: "40%"
},
// tooltip: { isHtml: true },
trigger: 'both',
vAxis: {
gridlines: {
color: '#0000006b',
minValue: 0,
baseline: 0
},
//format: "$ #,###"
},
};
var dataTable = google.visualization.arrayToDataTable(data);
//Formatters
var intergerFormatter = new google.visualization.NumberFormat({
groupingSymbol: ",",
fractionDigits: 0
});
for (var i = 0; i < data[0].length; i++) {
intergerFormatter.format(dataTable, i);
}
var view = new google.visualization.DataView(dataTable);
var cols = [0];
for (var i = 1; i < data[0].length; i++) {
cols.push({
sourceColumn: i,
type: "number",
label: data[0][i]
});
cols.push({
calc: createTooltip(i),
type: "string",
role: "tooltip",
});
}
view.setColumns(cols);
var chart = new google.visualization.ColumnChart(document.getElementById('provision_chart'));
chart.draw(view, options);
function createTooltip(col) {
return function(dataTable, row) {
var html = dataTable.getColumnLabel(col) + ":" + "\n";
html += "4 " + dataTable.getValue(row, 0) + "\n";
html += "$ " + intergerFormatter.formatValue(dataTable.getValue(row, col)) + " total" + "\n";
return html;
};
}
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="provision_chart" style="width: 500px; height: 500px;"></div>
to correct, you can add a decimal place to the format --> $ #,##0.0
or provide your own vAxis.ticks in an array --> [0, 1, 2, 3, 4, 5, 6]
see following working snippet...
google.charts.load('current', {
'packages': ['corechart']
});
//Input data
var data = [
['Data', 'CAT1', 'CAT2', 'CAT3', 'CAT4'],
['Provisions', 5, 0, 0, 0],
];
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
var options = {
colors: ['#00699B', '#087EB4', '#CBE7F7', '8A6996'],
isStacked: true,
chartArea: {
width: '40%'
},
bar: {
groupWidth: "40%"
},
// tooltip: { isHtml: true },
trigger: 'both',
vAxis: {
gridlines: {
color: '#0000006b',
minValue: 0,
baseline: 0
},
format: "$ #,###",
ticks: [0, 1, 2, 3, 4, 5, 6]
},
};
var dataTable = google.visualization.arrayToDataTable(data);
//Formatters
var intergerFormatter = new google.visualization.NumberFormat({
groupingSymbol: ",",
fractionDigits: 0
});
for (var i = 0; i < data[0].length; i++) {
intergerFormatter.format(dataTable, i);
}
var view = new google.visualization.DataView(dataTable);
var cols = [0];
for (var i = 1; i < data[0].length; i++) {
cols.push({
sourceColumn: i,
type: "number",
label: data[0][i]
});
cols.push({
calc: createTooltip(i),
type: "string",
role: "tooltip",
});
}
view.setColumns(cols);
var chart = new google.visualization.ColumnChart(document.getElementById('provision_chart'));
chart.draw(view, options);
function createTooltip(col) {
return function(dataTable, row) {
var html = dataTable.getColumnLabel(col) + ":" + "\n";
html += "4 " + dataTable.getValue(row, 0) + "\n";
html += "$ " + intergerFormatter.formatValue(dataTable.getValue(row, col)) + " total" + "\n";
return html;
};
}
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="provision_chart" style="width: 500px; height: 500px;"></div>
the getColumnRange(colIndex) method can assist in building the ticks dynamically
the method returns an object {} with properties for min and max for the column index provided
see following working snippet for an example...
google.charts.load('current', {
'packages': ['corechart']
});
//Input data
var data = [
['Data', 'CAT1', 'CAT2', 'CAT3', 'CAT4'],
['Provisions', 5, 0, 0, 0],
];
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
var options = {
colors: ['#00699B', '#087EB4', '#CBE7F7', '8A6996'],
isStacked: true,
chartArea: {
width: '40%'
},
bar: {
groupWidth: "40%"
},
// tooltip: { isHtml: true },
trigger: 'both',
vAxis: {
gridlines: {
color: '#0000006b',
minValue: 0,
baseline: 0
},
format: "$ #,###"
},
};
var dataTable = google.visualization.arrayToDataTable(data);
//Formatters
var intergerFormatter = new google.visualization.NumberFormat({
groupingSymbol: ",",
fractionDigits: 0
});
for (var i = 0; i < data[0].length; i++) {
intergerFormatter.format(dataTable, i);
}
var view = new google.visualization.DataView(dataTable);
var cols = [0];
var ticksY = [];
var maxY = null;
var minY = null;
for (var i = 1; i < view.getNumberOfColumns(); i++) {
var range = view.getColumnRange(i);
if (maxY === null) {
maxY = Math.ceil(range.max);
} else {
maxY = Math.max(maxY, Math.ceil(range.max));
}
if (minY === null) {
minY = Math.floor(range.min);
} else {
minY = Math.min(minY, Math.floor(range.min));
}
}
for (var i = minY; i <= maxY + 1; i++) {
ticksY.push(i);
}
options.vAxis.ticks = ticksY;
for (var i = 1; i < data[0].length; i++) {
cols.push({
sourceColumn: i,
type: "number",
label: data[0][i]
});
cols.push({
calc: createTooltip(i),
type: "string",
role: "tooltip",
});
}
view.setColumns(cols);
var chart = new google.visualization.ColumnChart(document.getElementById('provision_chart'));
chart.draw(view, options);
function createTooltip(col) {
return function(dataTable, row) {
var html = dataTable.getColumnLabel(col) + ":" + "\n";
html += "4 " + dataTable.getValue(row, 0) + "\n";
html += "$ " + intergerFormatter.formatValue(dataTable.getValue(row, col)) + " total" + "\n";
return html;
};
}
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="provision_chart" style="width: 500px; height: 500px;"></div>
I'm generating a pie chart with legend that looks like so:
As you can perceive, the pie is pitifully puny. I prefer it to be twice as tall and twice as wide.
Here is the code I am using:
var formatter = new Intl.NumberFormat("en-US");
Chart.pluginService.register({
afterDatasetsDraw: function (chartInstance) {
var ctx = chartInstance.chart.ctx;
ctx.font = Chart.helpers.fontString(14, 'bold', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#666';
chartInstance.config.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius) / 2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle) / 2;
var x = mid_radius * 1.5 * Math.cos(mid_angle);
var y = mid_radius * 1.5 * Math.sin(mid_angle);
ctx.fillStyle = '#fff';
if (i === 0 || i === 3 || i === 7) { // Darker text color for lighter background
ctx.fillStyle = '#666';
}
var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
// this prints the data number
// this prints the percentage
ctx.fillText(percent, model.x + x, model.y + y);
}
});
}
});
var data = {
labels: [
"Bananas (18%)",
"Lettuce, Romaine (14%)",
"Melons, Watermelon (10%)",
"Pineapple (10%)",
"Berries (10%)",
"Lettuce, Spring Mix (9%)",
"Broccoli (8%)",
"Melons, Honeydew (7%)",
"Grapes (7%)",
"Melons, Cantaloupe (7%)"
],
datasets: [
{
data: [2755, 2256, 1637, 1608, 1603, 1433, 1207, 1076, 1056, 1048],
backgroundColor: [
"#FFE135",
"#3B5323",
"#fc6c85",
"#ffec89",
"#021c3d",
"#3B5323",
"#046b00",
"#cef45a",
"#421C52",
"#FEA620"
]
}]
};
var optionsPie = {
responsive: true,
scaleBeginAtZero: true,
legend: {
display: false
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
return data.labels[tooltipItem.index] + ": " +
formatter.format(data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]);
}
}
}
};
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
var top10PieChart = new Chart(ctx,
{
type: 'pie',
data: data,
options: optionsPie,
animation: {
duration: 0,
easing: "easeOutQuart",
onComplete: function () {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
total = dataset._meta[Object.keys(dataset._meta)[0]].total,
mid_radius = model.innerRadius + (model.outerRadius - model.innerRadius) / 2,
start_angle = model.startAngle,
end_angle = model.endAngle,
mid_angle = start_angle + (end_angle - start_angle) / 2;
var x = mid_radius * Math.cos(mid_angle);
var y = mid_radius * Math.sin(mid_angle);
ctx.fillStyle = '#fff';
if (i === 3) { // Darker text color for lighter background
ctx.fillStyle = '#444';
}
var percent = String(Math.round(dataset.data[i] / total * 100)) + "%";
// this prints the data number
ctx.fillText(dataset.data[i], model.x + x, model.y + y);
// this prints the percentage
ctx.fillText(percent, model.x + x, model.y + y + 15);
}
});
}
}
});
$("#top10Legend").html(top10PieChart.generateLegend());
How can I increase the size of the pie?
UPDATE
The "View" as requested by Nkosi is:
<div class="row" id="top10Items">
<div class="col-md-6">
<div class="topleft">
<h2 class="sectiontext">Top 10 Items</h2>
<br />
<div id="piechartlegendleft">
<div id="container">
<canvas id="top10ItemsChart"></canvas>
</div>
<div id="top10Legend" class="pieLegend"></div>
</div>
</div>
</div>
. . .
The classes "row" and "col-md-6" are Bootstrap classes.
The custom classes are "topleft":
.topleft {
margin-top: -4px;
margin-left: 16px;
margin-bottom: 16px;
padding: 16px;
border: 1px solid black;
}
...sectionText:
.sectiontext {
font-size: 1.5em;
font-weight: bold;
font-family: Candara, Calibri, Cambria, serif;
color: green;
margin-top: -4px;
}
...and "pieLegend":
.pieLegend li span {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 5px;
}
You just need to change the canvas size.
When you are creating the chart you can specify it right in the element:
<canvas id="top10ItemsChart" width="1000" height="1000"></canvas>
Or if you prefer to do it in javascript
var ctx = $("#top10ItemsChart").get(0).getContext("2d");
ctx.width = 1000;
ctx.height = 1000;
If the resizing doesn't work as you wish, you can also try setting the maintainAspectRatio option to false:
var optionsPie = {
/** ... */
responsive: true,
maintainAspectRatio: false,
/** ... */
};
Hope it helps.
I have used the chart.js 1.0.2 without knowing that a version 2+ arrived. Now I need fuctionallity only available in 2+
Meanwhile i have written several extensions to the chart.js 1.0.2 that i would like to convert to version 2+.
Chart.types.Doughnut.extend(
{
name: "DoughnutWithText",
draw: function() {
Chart.types.Doughnut.prototype.draw.apply(this, arguments);
width = this.chart.width,
height = this.chart.height;
var fontSize = (height / this.options.textScale).toFixed(2);
this.chart.ctx.font = fontSize + "em Lato";
this.chart.ctx.textBaseline = "middle";
this.chart.ctx.fillStyle="#000";
textX = Math.round((width - this.chart.ctx.measureText(this.options.doughnutText).width) / 2),
textY = height / 2;
this.chart.ctx.fillText(this.options.doughnutText, textX, textY);
}
});
How do I do do this in version 2+?
https://jsfiddle.net/64106xh8/1/
With 2.1.x, you can write a plugin to do this
Preview
Script
Chart.pluginService.register({
afterDraw: function (chart) {
if (chart.config.options.elements.center) {
var helpers = Chart.helpers;
var centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
var centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
var ctx = chart.chart.ctx;
ctx.save();
var fontSize = helpers.getValueOrDefault(chart.config.options.elements.center.fontSize, Chart.defaults.global.defaultFontSize);
var fontStyle = helpers.getValueOrDefault(chart.config.options.elements.center.fontStyle, Chart.defaults.global.defaultFontStyle);
var fontFamily = helpers.getValueOrDefault(chart.config.options.elements.center.fontFamily, Chart.defaults.global.defaultFontFamily);
var font = helpers.fontString(fontSize, fontStyle, fontFamily);
ctx.font = font;
ctx.fillStyle = helpers.getValueOrDefault(chart.config.options.elements.center.fontColor, Chart.defaults.global.defaultFontColor);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(chart.config.options.elements.center.text, centerX, centerY);
ctx.restore();
}
},
})
and then
...
options: {
elements: {
center: {
text: 'Hello',
fontColor: '#000',
fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
fontSize: 24,
fontStyle: 'normal'
}
}
}
};
Fiddle - http://jsfiddle.net/a1r1kszb/