Related
I'm trying to create a chart with a time axis in Vue using Chart.js. When I set the data right in the Data object like so, everything works correctly:
chartData: {
datasets: [
{
backgroundColor: '#ED645A',
borderColor: '#ED645A',
fill: false,
data: [
{
t: new Date("1998-1-1"),
y: 7.1
},
{
t: new Date("1998-2-1"),
y: 8.4
},
{
t: new Date("1998-3-1"),
y: 18.5
},
{
t: new Date("1998-4-1"),
y: 16.2
},
{
t: new Date("1998-5-1"),
y: 18.4
}
]
}
]
},
But when I'm trying to load the data from JSON and form the datasets object in computed property, like so, it doesn't work:
import pureSecondDatasetJson from "../assets/pureSecondDataset.json"
...
export default {
...
data () {
return {
pureSecondDataset: pureSecondDatasetJson,
charts: [
chartData: {
datasets: this.foo
},
...
computed: {
foo() {
var plotData = []
for (var i=0; i<this.pureSecondDataset.length; i++) {
plotData.push({t: new Date(this.pureSecondDataset[i].t), y: this.pureSecondDataset[i].y})
}
var chartData = [
{
backgroundColor: '#ED645A',
borderColor: '#ED645A',
fill: false,
data: plotData
}
];
return chartData
}
}
The object, created in computed seems the same as the one, which put directly, so why it doesn;t work?
You shouldn't try access a computed property from your data as explained on the Vue Forum.
the data is evaluated before the computed array
I would advise changing your computed property to something like below and then use that as your chart data:
foo() {
var plotData = []
for (var i=0; i<this.pureSecondDataset.length; i++)
plotData.push({t: new Date(this.pureSecondDataset[i].t), y: this.pureSecondDataset[i].y})
return {
chartData: {
datasets:[{
backgroundColor: '#ED645A',
borderColor: '#ED645A',
fill: false,
data: plotData
}]
}
}
}
I've added a fiddle to show how you can do this by not using the computed property in data. While I was making the fiddle I found that for a proper line graph you need to have a labels property for the x-values otherwise you can use type: scatter and drawLine:true to specify the x and y values together. Chart.js - Plot line graph with X , Y coordinates
new Vue({
el: "#app",
data: () => {
return {
plotData: [{
x: 1,
y: 1
}, {
x: 2,
y: 2
}, {
x: 3,
y: 3
}]
}
},
computed: {
foo() {
return {
type: 'scatter',
data: {
datasets: [{
showLine: true,
label: 'My First dataset',
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: this.plotData
}]
}
}
}
},
mounted() {
var ctx = document.getElementById('chart').getContext('2d');
new Chart(ctx, this.foo);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<div id="app">
<canvas id="chart" width="400" height="400"></canvas>
</div>
Is there a way to have multiple colors?
Example:
Thank you!
New answer:
My original answer bothered me and I thought there must be a better way to achieve this style. So here's a much better solution that uses a radial gradient.
Note that this implementation is quite naïve in that it only supports a single dataset!
const colours = [
{ primary: '#fec1c6', shadow: '#e8b0b5' },
{ primary: '#bdeeed', shadow: '#aad2d0' },
{ primary: '#e4da84', shadow: '#d3ca76' }
];
new Chart(document.getElementById('chart'), {
type: 'doughnut',
data: {
datasets: [{
data: [3, 2, 2]
}]
},
options: {
cutoutPercentage: 65
},
plugins: [{
beforeDatasetsUpdate: c => {
const x = (c.chartArea.right + c.chartArea.left) / 2,
y = (c.chartArea.bottom + c.chartArea.top) / 2,
bgc = [];
for (let i = 0; i < colours.length; i++) {
const gradient = c.ctx.createRadialGradient(x, y, c.innerRadius, x, y, c.outerRadius);
gradient.addColorStop(0, colours[i].shadow);
gradient.addColorStop(.4, colours[i].shadow);
gradient.addColorStop(.45, colours[i].primary);
gradient.addColorStop(1, colours[i].primary);
bgc.push(gradient);
}
c.config.data.datasets[0].backgroundColor = bgc;
}
}]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="75"></canvas>
Original answer:
If you don't mind a 'dirty' solution you can achieve a similar visual result by duplicating your dataset, e.g.:
const values = [3, 2, 2],
primaryColours = ['#fec1c6', '#bdeeed', '#e4da84'],
secondaryColours = ['#e8b0b5', '#aad2d0', '#d3ca76'];
new Chart(document.getElementById('chart'), {
type: 'doughnut',
data: {
datasets: [{
data: values,
weight: 2,
backgroundColor: primaryColours,
borderColor: primaryColours
}, {
data: values,
weight: 1,
backgroundColor: secondaryColours,
borderColor: secondaryColours
}]
},
options: {
cutoutPercentage: 65
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="75"></canvas>
I'm trying to put a simple chart on my website. I have this code:
<script src="http://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script>
var ctx = canvas.getContext("2d");
var parent = document.getElementById('parent');
canvas.width = parent.offsetWidth;
canvas.height = parent.offsetHeight;
var chart = new Chart(ctx, {
type: 'line',
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false
},
data: {
labels: [3,4],
datasets: [{
label: 'Test 01',
data: [1,2],
}
]
}
});
</script>
<div id="parent">
<canvas id="myChart"></canvas>
</div>
However it returns me the error:
Uncaught ReferenceError: canvas is not defined
at mytest3.html:47
I've tried moving the canvas above and below the script but it doesn't make a difference. What am I doing wrong here?
You also need to get the canvas like
var canvas = document.getElementById('myChart')
You need to set the canvas variable before you use it. Add the following:
<script>
//Add the following line
var canvas = document.getElementById("myChart");
var ctx = canvas.getContext("2d");
You need to put the script AFTER the element exists. You need to define canvas....
<script src="http://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<!-- Need to put canvas before the script -->
<div id="parent">
<canvas id="myChart"></canvas>
</div>
<script>
var canvas = document.getElementById("myChart"); // <-- need to define canvas
var ctx = canvas.getContext("2d");
var parent = document.getElementById('parent');
canvas.width = parent.offsetWidth;
canvas.height = parent.offsetHeight;
var chart = new Chart(ctx, {
type: 'line',
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false
},
data: {
labels: [3,4],
datasets: [{
label: 'Test 01',
data: [1,2],
}
]
}
});
</script>
I'm quite new with JS, and have a problem with Chart.js and multiple charts.
I load all libraries like jquery and chart.js from CDNs
Then I have one plugins.js for localy loaded plugins
And one js called main.js with all the custom JS.
I get this error:
Uncaught TypeError: Cannot read property 'style' of undefined
My question is:
How do I keep all my Chart.js charts in one .js file, that get loaded on all website pages, but not all pages contains all charts.
(I have a site that consists on multiple pages with different charts, and I'm thinking it's smarter to load all at once in one JS-file)
Appreciate any other pointers also. :)
-
About the code
I've posted the code below and also created a JSFiddle: https://jsfiddle.net/NoLooseEnds/1pt3m41a/
In the main.js I have all my Chart.js data like so (among other things):
// *************************************
// Chart.js
// *************************************
// Global Settings
Chart.defaults.global.defaultFontColor = "rgba(43,43,43,1)";
Chart.defaults.global.defaultFontFamily = "'ApexNew-Medium', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
Chart.defaults.global.defaultFontSize = 12;
Chart.defaults.global.defaultFontStyle = "normal";
Chart.defaults.global.maintainAspectRatio = false;
// Disable click on legend
Chart.defaults.global.legend.onClick = (e) => e.stopPropagation();
// Bar Charts settings
var barChart__options = {
};
// Doughnut settings
var dnutChart__options = {
legend: {
position: "bottom",
// Disable click on legend
onClick: (e) => e.stopPropagation()
}
};
// *************************************
// Datasets
// *************************************
// Bar Chart X
var barChartX__data = {
labels: ["Alpha", "Bravo", "Charlie"],
datasets: [
{
label: "2015",
borderWidth: 0,
backgroundColor: "rgba(43,43,43,1)",
data: [410,430,110]
},
{
label: "2016",
borderWidth: 0,
backgroundColor: "rgba(233,131,0,1.0)",
data: [405,360,150]
}
]
};
const barChartX = $("#barChartX");
let ChartX = new Chart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});
// Doughnut Chart Y
var dnutChartY__data = {
labels: ["Alpha", "Bravo"],
datasets: [
{
label: "Points",
borderWidth: 0,
backgroundColor: ["rgba(43,43,43,1)", "rgba(233,131,0,1.0)"],
data: [90,270]
}
]
};
const dnutChartY = $("#dnutChartY");
let ChartY = new Chart(dnutChartY, {
type: "doughnut",
data: dnutChartY__data,
options: dnutChart__options
});
// Doughnut Chart Z
var dnutChartZ__data = {
labels: ["Alpha", "Bravo", "Charlie"],
datasets: [
{
label: "Points",
borderWidth: 0,
backgroundColor: ["rgba(233,131,0,1.0)","rgba(239,162,64,1.0)","rgba(244,193,127,1.0)"],
data: [405,360,150]
}
]
};
const dnutChartZ = $("#dnutChartZ");
let ChartZ = new Chart(dnutChartZ, {
type: "doughnut",
data: dnutChartZ__data,
options: dnutChart__options
});
And my HTML like so:
<div class="example__chart">
<div>
<canvas id="barChartX" height="400" width="400"></canvas>
</div>
<!--
<div>
<canvas id="dnutChartY" height="400" width="400"></canvas>
</div>
-->
<div>
<canvas id="dnutChartZ" height="400" width="400"></canvas>
</div>
</div>
As you can see I have commented out a part to trigger the error.
You can create a helper function and check whenever the DOM element exists for the barChartX, dnutChartY, dnutChartZ or any other DOM element like this:
var doChart = function(o, d) {
if (typeof(o) != 'undefined' && o.length > 0) {
return new Chart(o, d);
} else {
return null;
}
}
Then your Chart.js code will be like this
// *************************************
// Chart.js
// *************************************
// Global Settings
Chart.defaults.global.defaultFontColor = "rgba(43,43,43,1)";
Chart.defaults.global.defaultFontFamily = "'ApexNew-Medium', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
Chart.defaults.global.defaultFontSize = 12;
Chart.defaults.global.defaultFontStyle = "normal";
Chart.defaults.global.maintainAspectRatio = false;
// Disable click on legend
Chart.defaults.global.legend.onClick = (e) => e.stopPropagation();
var doChart = function(o, d) {
if (typeof(o) != 'undefined' && o.length > 0) {
return new Chart(o, d);
} else {
return null;
}
}
// Bar Charts settings
var barChart__options = {};
// Doughnut settings
var dnutChart__options = {
legend: {
position: "bottom",
// Disable click on legend
onClick: (e) => e.stopPropagation()
}
};
// *************************************
// Datasets
// *************************************
// Bar Chart X
var barChartX__data = {
labels: ["Alpha", "Bravo", "Charlie"],
datasets: [{
label: "2015",
borderWidth: 0,
backgroundColor: "rgba(43,43,43,1)",
data: [410, 430, 110]
},
{
label: "2016",
borderWidth: 0,
backgroundColor: "rgba(233,131,0,1.0)",
data: [405, 360, 150]
}
]
};
const barChartX = $("#barChartX");
/*let ChartX = new Chart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});*/
let ChartX = doChart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});
// Doughnut Chart Y
var dnutChartY__data = {
labels: ["Alpha", "Bravo"],
datasets: [{
label: "Points",
borderWidth: 0,
backgroundColor: ["rgba(43,43,43,1)", "rgba(233,131,0,1.0)"],
data: [90, 270]
}]
};
const dnutChartY = $("#dnutChartY");
let ChartY = doChart(dnutChartY, {
type: "doughnut",
data: dnutChartY__data,
options: dnutChart__options
});
// Doughnut Chart Z
var dnutChartZ__data = {
labels: ["Alpha", "Bravo", "Charlie"],
datasets: [{
label: "Points",
borderWidth: 0,
backgroundColor: ["rgba(233,131,0,1.0)", "rgba(239,162,64,1.0)", "rgba(244,193,127,1.0)"],
data: [405, 360, 150]
}]
};
const dnutChartZ = $("#dnutChartZ");
let ChartZ = doChart(dnutChartZ, {
type: "doughnut",
data: dnutChartZ__data,
options: dnutChart__options
});
Please check my working example with no javascript errors here: http://zikro.gr/dbg/html/chartsjs/
UPDATE
Please have a look at the JSFiddle example here: https://jsfiddle.net/h1dafqjx/2/
UPDATE 2
Problem analysis and solution
The problem you are facing, is that you want to have multiple declarations and chart setups in one javascript file that you are going to use on many location which may happen some of these locations do not have some of the declared elements, thus you want your code to handle those cases.
You declare a chart like this:
const barChartX = $("#barChartX");
let ChartX = new Chart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});
In order to handle a missing DOM element, like when the $("#barChartX") does not exists, you need to create a condition to check those cases.
For excample:
const barChartX = $("#barChartX");
if(typeof(barChartX) != 'undefined' && barChartX.length > 0) {
let ChartX = new Chart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});
}
With the obove code, you check if the barChartX is defined and if it actually contains any elements with the barChartX.length > 0 condition.
Now, in order to do not repeat things, we keep it simple and do all that checking in a function. Thus we create the function doChart like this:
var doChart = function(o, d) {
if (typeof(o) != 'undefined' && o.length > 0) {
return new Chart(o, d);
} else {
return null;
}
}
The first parameter o is the DOM element we want to check and the second parameter d is the chart object properties.
For example:
let ChartX = doChart(barChartX, {
type: "bar",
data: barChartX__data,
options: barChart__options
});
Here we pass to the doChart function the barChartX jQuery object of the element in order for the function to check if exists. Then we pass the chart object parameters by the second parameter named d, which is the chart object properties for creating the new chart:
{
type: "bar",
data: barChartX__data,
options: barChart__options
}
I'm using chart.js (V2) to try to build a bar chart that has more information available to user without having to hover over or click anywhere. I've provided two examples of how I hope to edit my chart.
Two edited versions of what I hope to achieve
As can be seen, I hope to place (somewhere), some extra information outside of the labels. I had hope that by adding '\n' to the labels I might have been able to get what I was looking for similar to option A.
Some edited code is provided blow:
var barChartData = {
labels: playerNames,
datasets: [{
label: 'Actual Score/Hour',
backgroundColor: "rgba(0, 128, 0,0.5)",
data: playerScores
}, {
label: 'Expected Score/Hour',
backgroundColor: "rgba(255,0,0,0.5)",
data: playerExpected
}]
};
function open_win(linktosite) {
window.open(linktosite)
}
canvas.onclick = function(evt){
var activePoints = myBar.getElementsAtEvent(evt);
console.log(activePoints);
linktosite = 'https://www.mytestsite.com/' + activePoints[1]['_model']['label'];
open_win(linktosite);
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
title:{
display:true,
text:"Player Expected and Actual Score per Hour"
},
tooltips: {
mode: 'label'
},
responsive: true,
scales: {
xAxes: [{
stacked: false,
}],
yAxes: [{
stacked: false
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx;
ctx.textAlign = "center";
Chart.helpers.each(this.data.datasets.forEach(function (dataset) {
Chart.helpers.each(dataset.metaData.forEach(function (bar, index) {
// console.log("printing bar" + bar);
ctx.fillText(dataset.data[index], bar._model.x, bar._model.y - 10);
}),this)
}),this);
}
}
}
});
// Chart.helpers.each(myBar.getDatasetMeta(0).data, function(rectangle, index) {
// rectangle.draw = function() {
// myBar.chart.ctx.setLineDash([5, 5]);
// Chart.elements.Rectangle.prototype.draw.apply(this, arguments);
// }
// }, null);
};
At this point I'd be satisfied with having the extradata anywhere on the bar. Any help would be appreciated. Thanks~
Chart.js v2.1.5 allows for multi-line labels using nested arrays (v2.5.0 fixes it for radar graphs):
...
data: {
labels: [["Jake", "Active: 2 hrs", "Score: 1", "Expected: 127", "Attempts: 4"],
["Matt", "Active: 2 hrs", "Score: 4", "Expected: 36", "Attempts: 4"]],
...
However, this does mean that you will have to pre-calculate the label values.
var config = {
type: 'line',
data: {
labels: [["January","First Month","Jellyfish","30 of them"], ["February","Second Month","Foxes","20 of them"], ["March","Third Month","Mosquitoes","None of them"], "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
data: [65, 40, 80, 81, 56, 85, 45],
backgroundColor: "rgba(255,99,132,0.2)",
}, {
label: "My Second dataset",
data: [40, 80, 21, 56, 85, 45, 65],
backgroundColor: "rgba(99,255,132,0.2)",
}]
},
scales : {
xAxes : [{
gridLines : {
display : false,
lineWidth: 1,
zeroLineWidth: 1,
zeroLineColor: '#666666',
drawTicks: false
},
ticks: {
display:true,
stepSize: 0,
min: 0,
autoSkip: false,
fontSize: 11,
padding: 12
}
}],
yAxes: [{
ticks: {
padding: 5
},
gridLines : {
display : true,
lineWidth: 1,
zeroLineWidth: 2,
zeroLineColor: '#666666'
}
}]
},
spanGaps: true,
responsive: true,
maintainAspectRatio: true
};
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx, config);
<div class="myChart">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
</div>
If a label is an array as opposed to a string i.e. [["June","2015"], "July"] then each element is treated as a separate line. The appropriate calculations are made to determine the correct height and width, and rotation is still supported.
charJS version 2.7.2 used
this also works in https://github.com/jtblin/angular-chart.js
If you are using Chart.js v2.7.1, the above solution might not work.
The solution that actually worked for us was adding a small plugin right in the data and options level:
const config = {
type: 'bar',
data: {
// ...
},
options: {
// ...
},
plugins: [{
beforeInit: function (chart) {
chart.data.labels.forEach(function (label, index, labelsArr) {
if (/\n/.test(label)) {
labelsArr[index] = label.split(/\n/)
}
})
}
}]
};
A full description of how to fix this issue can be found here.
With Chart.js v2.1, you can write a chart plugin to do this
Preview
Script
Chart.pluginService.register({
beforeInit: function (chart) {
var hasWrappedTicks = chart.config.data.labels.some(function (label) {
return label.indexOf('\n') !== -1;
});
if (hasWrappedTicks) {
// figure out how many lines we need - use fontsize as the height of one line
var tickFontSize = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontSize, Chart.defaults.global.defaultFontSize);
var maxLines = chart.config.data.labels.reduce(function (maxLines, label) {
return Math.max(maxLines, label.split('\n').length);
}, 0);
var height = (tickFontSize + 2) * maxLines + (chart.options.scales.xAxes[0].ticks.padding || 0);
// insert a dummy box at the bottom - to reserve space for the labels
Chart.layoutService.addBox(chart, {
draw: Chart.helpers.noop,
isHorizontal: function () {
return true;
},
update: function () {
return {
height: this.height
};
},
height: height,
options: {
position: 'bottom',
fullWidth: 1,
}
});
// turn off x axis ticks since we are managing it ourselves
chart.options = Chart.helpers.configMerge(chart.options, {
scales: {
xAxes: [{
ticks: {
display: false,
// set the fontSize to 0 so that extra labels are not forced on the right side
fontSize: 0
}
}]
}
});
chart.hasWrappedTicks = {
tickFontSize: tickFontSize
};
}
},
afterDraw: function (chart) {
if (chart.hasWrappedTicks) {
// draw the labels and we are done!
chart.chart.ctx.save();
var tickFontSize = chart.hasWrappedTicks.tickFontSize;
var tickFontStyle = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
var tickFontFamily = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
var tickLabelFont = Chart.helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
chart.chart.ctx.font = tickLabelFont;
chart.chart.ctx.textAlign = 'center';
var tickFontColor = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].fontColor, Chart.defaults.global.defaultFontColor);
chart.chart.ctx.fillStyle = tickFontColor;
var meta = chart.getDatasetMeta(0);
var xScale = chart.scales[meta.xAxisID];
var yScale = chart.scales[meta.yAxisID];
chart.config.data.labels.forEach(function (label, i) {
label.split('\n').forEach(function (line, j) {
chart.chart.ctx.fillText(line, xScale.getPixelForTick(i + 0.5), (chart.options.scales.xAxes[0].ticks.padding || 0) + yScale.getPixelForValue(yScale.min) +
// move j lines down
j * (chart.hasWrappedTicks.tickFontSize + 2));
});
});
chart.chart.ctx.restore();
}
}
});
and then
...
data: {
labels: ["January\nFirst Month\nJellyfish\n30 of them", "February\nSecond Month\nFoxes\n20 of them", "March\nThird Month\nMosquitoes\nNone of them", "April", "May", "June", "July"],
...
Note - we assume that the maximum content of one line will fit between the ticks (i.e. that no rotation logic is needed. I'm sure it's possible to incorporate rotation logic too, but it would be a tad more complicated)
You should format the tooltips to not show the x axis label, or format it to show a shorter version of the label.
Fiddle - http://jsfiddle.net/m0q03wpy/