Two chart for the same set of data - Chart.js - javascript

Is it possible to get 2 separate types of chart (in this case bar and line) for the same dataset, but under one label?
As an example, this is my chart:
const ctx = document.querySelector('#chart-container').getContext('2d');
new Chart(ctx, getConfig());
function getConfig() {
return {
type: 'bar',
data: {
labels: [ "Mar-20", "Apr-20" ],
datasets: [{
type: "line",
label: "en-US",
borderColor: "#8c856f",
data: [ 2, 3 ],
borderWidth: 2,
fill: false
}, {
type: "bar",
label: "en-US",
backgroundColor: "#beb391",
data: [ 2, 3 ]
}, {
type: "line",
label: "sv-SE",
borderColor: "#b3cbaa",
data: [ 1, 2 ],
borderWidth: 2,
fill: false
}, {
type: "bar",
label: "sv-SE",
backgroundColor: "#683e3a",
data: [ 1, 2 ]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
min : 0
}
}]
}
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart-container"></canvas>
As you can see, I have 4 datasets: 2 for each language (en & se). It all works perfectly fine except the legend labels are generated for each dataset (2 x "en-US", 2 x "sv-SE"), while I would like to show only the unique labels, essentially using the values from the main "labels" property, instead of the one inside each dataset.
Is it possible, and how can I do it?

You can filter out the labels for the line series via:
options: {
legend: {
labels: {
filter: function(item, chart) {
return chart.datasets[item.datasetIndex].type === 'bar';
}
},
onClick: function(e, legendItem) {
let chart = this.chart;
let index = legendItem.datasetIndex;
let visible = !chart.getDatasetMeta(index).hidden;
chart.data.datasets.forEach((dataset, i) => {
if (dataset.label === legendItem.text) {
chart.getDatasetMeta(i).hidden = visible;
}
});
chart.update();
}
}
}
This has been adapted from: Is it possible in chartjs to hide certain dataset legends?
For toggling the similar datasets, I followed this example.
Demo
const ctx = document.querySelector('#chart-container').getContext('2d');
new Chart(ctx, getConfig());
function getConfig() {
return {
type: 'bar',
data: {
labels: ["Mar-20", "Apr-20"],
datasets: [{
type: "line",
label: "en-US",
borderColor: "#8c856f",
data: [2, 3],
borderWidth: 2,
fill: false
}, {
type: "bar",
label: "en-US",
backgroundColor: "#beb391",
data: [2, 3]
}, {
type: "line",
label: "sv-SE",
borderColor: "#b3cbaa",
data: [1, 2],
borderWidth: 2,
fill: false
}, {
type: "bar",
label: "sv-SE",
backgroundColor: "#683e3a",
data: [1, 2]
}]
},
options: {
legend: {
labels: {
filter: function(item, chart) {
return chart.datasets[item.datasetIndex].type === 'bar';
}
},
// Override hide/show for multiple series with same label.
onClick: function(e, legendItem) {
let chart = this.chart;
let index = legendItem.datasetIndex;
let visible = !chart.getDatasetMeta(index).hidden;
chart.data.datasets.forEach((dataset, i) => {
if (dataset.label === legendItem.text) {
chart.getDatasetMeta(i).hidden = visible;
}
});
chart.update();
}
},
scales: {
yAxes: [{
ticks: {
min: 0
}
}]
}
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.js"></script>
<canvas id="chart-container"></canvas>
Alternatively, you could create a plugin... This makes it so that you can configure each individual series.
var hideLegendItemsPlugin = {
beforeInit: function(chartInstance) {
if (chartInstance.options.hideLegendItemsPlugin) {
Object.assign(chartInstance.options.legend, {
labels : {
filter : (item, chart) => {
return chart.datasets[item.datasetIndex].hideLegendItem !== true;
}
},
onClick: function(e, legendItem) {
let chart = this.chart;
let index = legendItem.datasetIndex;
let visible = !chart.getDatasetMeta(index).hidden;
chart.data.datasets.forEach((dataset, i) => {
if (dataset.label === legendItem.text) {
chart.getDatasetMeta(i).hidden = visible;
}
});
chart.update();
}
});
}
}
};
Chart.pluginService.register(hideLegendItemsPlugin);
const ctx = document.querySelector('#chart-container').getContext('2d');
new Chart(ctx, getConfig());
function getConfig() {
return {
type: 'bar',
data: {
labels: ["Mar-20", "Apr-20"],
datasets: [{
type: "line",
label: "en-US",
borderColor: "#8c856f",
data: [2, 3],
borderWidth: 2,
fill: false,
hideLegendItem: true // Hide it!
}, {
type: "bar",
label: "en-US",
backgroundColor: "#beb391",
data: [2, 3]
}, {
type: "line",
label: "sv-SE",
borderColor: "#b3cbaa",
data: [1, 2],
borderWidth: 2,
fill: false,
hideLegendItem: true // Hide it!
}, {
type: "bar",
label: "sv-SE",
backgroundColor: "#683e3a",
data: [1, 2]
}]
},
options: {
hideLegendItemsPlugin : true, // Enable the plugin
scales: {
yAxes: [{
ticks: {
min: 0
}
}]
}
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart-container"></canvas>
If you want to have the chart figure-out label uniqueness, you can build a set of series names behind the scenes.
I also added an option to specify priority of series type i.e. bar, line, etc... If you do not specify a priority, the last appearance of the label will take priority. This works in your example, because the bar series are after the line series. Obviously this can be tweaked a bit.
var hideLegendItemsPlugin = {
beforeInit: function(chartInstance) {
let pluginOptions = chartInstance.options.hideLegendItemsPlugin;
if (pluginOptions) {
if (pluginOptions.priority) {
const labelMap = chartInstance.data.datasets.reduce((ret, dataset) => {
return Object.assign(ret, {
[dataset.label] : (ret[dataset.label] || []).concat(dataset.type)
});
}, {});
chartInstance.options.legend.labels.filter = (item, chart) => {
let dataset = chart.datasets[item.datasetIndex];
if (dataset.type !== pluginOptions.priority) {
if (labelMap[dataset.label].includes(pluginOptions.priority)) {
return false;
}
}
return true;
};
} else {
// Default prioritization is the last index (appearance) of that label.
const labelMap = chartInstance.data.datasets.reduce((ret, dataset, index) => {
return Object.assign(ret, { [dataset.label] : index });
}, {});
chartInstance.options.legend.labels.filter = (item, chart) => {
let dataset = chart.datasets[item.datasetIndex];
return item.datasetIndex === labelMap[dataset.label];
};
}
chartInstance.options.legend.onClick = function(e, legendItem) {
let chart = this.chart;
let index = legendItem.datasetIndex;
let visible = !chart.getDatasetMeta(index).hidden;
chart.data.datasets.forEach((dataset, i) => {
if (dataset.label === legendItem.text) {
chart.getDatasetMeta(i).hidden = visible;
}
});
chart.update();
};
}
}
};
Chart.pluginService.register(hideLegendItemsPlugin);
const ctx = document.querySelector('#chart-container').getContext('2d');
new Chart(ctx, getConfig());
function getConfig() {
return {
type: 'bar',
data: {
labels: ["Mar-20", "Apr-20"],
datasets: [{
type: "line",
label: "en-US",
borderColor: "#8c856f",
data: [2, 3],
borderWidth: 2,
fill: false
}, {
type: "bar",
label: "en-US",
backgroundColor: "#beb391",
data: [2, 3]
}, {
type: "line",
label: "sv-SE",
borderColor: "#b3cbaa",
data: [1, 2],
borderWidth: 2,
fill: false,
hideLegendItem: true
}, {
type: "bar",
label: "sv-SE",
backgroundColor: "#683e3a",
data: [1, 2]
}]
},
options: {
// This could also be simply `true` instead of an object.
hideLegendItemsPlugin : {
priority : 'bar'
},
scales: {
yAxes: [{
ticks: {
min: 0
}
}]
}
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart-container"></canvas>

Related

Chartjs adding data values on the right legend

considering the following example:
$(document).ready(function() {
var ctx = document.getElementById('mycanvas').getContext('2d');
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["CL", "ML", "Spl.L", "PD", "Other Permissions"],
datasets: [{
label: "My First dataset",
backgroundColor: ['#F0CB8C', '#EE97A1', '#A9D5D4', '#E8A3D7', '#CFA3FD'],
data: [7, 3, 3, 4, 8],
}]
},
options: {
legend: {
position: 'right'
}
}
});
})
example
is there a way to have the data on the right of the single label?
like here:
You can add custom labels as advised by #LeeLenalee's solution
and here is your workin code :
$(document).ready(function() {
var ctx = document.getElementById('mycanvas').getContext('2d');
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["CL", "ML", "Spl.L", "PD", "Other Permissions"],
datasets: [{
label: "My First dataset",
backgroundColor: ['#F0CB8C', '#EE97A1', '#A9D5D4', '#E8A3D7', '#CFA3FD'],
data: [7, 3, 3, 4, 8],
}]
},
options: {
legend: {
labels: {
generateLabels: (chart) => {
const datasets = chart.data.datasets;
return datasets[0].data.map((data, i) => ({
text: `${chart.data.labels[i]} ${data}`,
fillStyle: datasets[0].backgroundColor[i],
}))}
}
}
}
});
})
.chart-container {
width: 280px;
height: 280px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<div class="chart-container">
<canvas id="mycanvas"></canvas>
</div>

How to create logarithmic scale in chart.js with normal numbers?

How to create a chart.js with logarithmic scale on xAxis with normal numbers instead of "1e+0" etc.? Here's my code:
/* eslint-disable */
import { Line, mixins } from 'vue-chartjs';
Chart.plugins.register({
beforeDraw: function (chartInstance) {
const { ctx } = chartInstance.chart;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, chartInstance.chart.width, chartInstance.chart.height);
}
});
const { reactiveProp } = mixins;
export default {
extends: Line,
mixins: [reactiveProp],
data() {
return {
options: {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false,
position: 'bottom',
},
scales: {
yAxes: [
{
ticks: {
suggestedMin: 0,
callback(tick) {
return `${tick}%`;
},
},
},
],
xAxes: [
{
display: true,
type: "logarithmic",
}
]
},
tooltips: {
callbacks: {
label(tooltipItem, data) {
const dataset = data.datasets[tooltipItem.datasetIndex];
const currentValue = dataset.data[tooltipItem.index];
return `${currentValue} %`;
},
},
},
},
};
},
mounted() {
this.renderChart(this.chartData, this.options);
},
};
/* eslint-enable */
And mine chart:
Appearently all the values are on the same x dont konw why also. Version of chart.js: 2.9.3 (it cannot be changed). Thanks for any advice and help!
You can use the tick callback to transfrom it to a normal number again:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'blue'
}]
},
options: {
scales: {
yAxes: [{
type: 'logarithmic',
ticks: {
callback: (val) => (val)
}
}]
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>

Chartjs, Bubble Chart, positioning problem with duplicate value in data labels

With a duplicate value in the data labels - Is there a way to draw the bubble on the first or second duplicate value in the bubble chart?
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var ctx = document.getElementById("myChart");
var options = {responsive: true,
maintainAspectRatio: false,
};
var mixedChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["1", "2", "1", "4"], //Same Value on First and Third Position
datasets: [
//Lines
{
label: "First_Line",
type: "line",
borderColor: "#8e5ea2",
data: [5,10,7,12],
fill: false
}, {
label: "Second_Line",
type: "line",
borderColor: "#3e95cd",
data: [1,4,15,6],
fill: false
},
//Bubbles
{
label: "Bubble_One",
type: "bubble",
backgroundColor: "#8e5ea2",
data: [{ x: "2", y: 10, r: 15}],
},
{
label: "Bubble_Two",
type: "bubble",
backgroundColor: "#3e95cd",
backgroundColorHover: "#3e95cd",
data: [{x: ???, y: 6, r: 15}] //First "1" or Second "1" possible?
}
]
},
options: options
});
</script>
Something like "1"[0] unfortunately does not work.
You can not do it directly afaik but you can put an extra part in the label and filter it out of there with the callbacks for the tooltip and the axis:
<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var ctx = document.getElementById("myChart");
var options = {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
offset: false,
ticks: {
callback: function(val) {
return this.getLabelForValue(val).split('-')[0]
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: (ttItem) => {
if (ttItem.dataset.type === "bubble") {
return `${ttItem.label}: (${ttItem.raw.x.split('-')[0]},${ttItem.raw.y})`
} else if (ttItem.dataset.type === "line") {
return `${ttItem.dataset.label}: ${ttItem.parsed.y}`
}
},
title: (ttItem) => (ttItem[0].label.split('-')[0])
}
}
}
};
var mixedChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["1-1", "2", "1-2", "4"], //Same Value on First and Third Position
datasets: [
//Lines
{
label: "First_Line",
type: "line",
borderColor: "#8e5ea2",
data: [5, 10, 7, 12],
fill: false
}, {
label: "Second_Line",
type: "line",
borderColor: "#3e95cd",
data: [1, 4, 15, 6],
fill: false
},
//Bubbles
{
label: "Bubble_One",
type: "bubble",
backgroundColor: "#8e5ea2",
data: [{
x: "2",
y: 10,
r: 15
}],
},
{
label: "Bubble_Two",
type: "bubble",
backgroundColor: "#3e95cd",
backgroundColorHover: "#3e95cd",
data: [{
x: "1-2",
y: 6,
r: 15
}] //First "1" or Second "1" possible?
}
]
},
options: options
});
</script>

How to add new dataset in line chart of chart.js by iterating over dictionary in JavaScript?

I am trying to display linechart from charts.js where it shows the pass,fail and skipped test case reults in graph. Here i have hardcoded the number of data in a dataset. I want to add the datapoints by iterating through the object.
Object looks like this
var temp = {"2020":[1,2,3],
"2021":[4,5,6]}
And my javascript function for line chart below.
function GetHealthReport(health,id) {
console.log(health);
var Date = Object.keys(health);
var ctxL = document.getElementById(id).getContext('2d');
var myLineChart = new Chart(ctxL, {
type: 'line',
data: {
labels: Date,
datasets: [
{
label:"Pass",
data: [health[Date[0]][0],health[Date[1]][0],health[Date[2]][0],health[Date[3]][0],health[Date[4]][0]],
backgroundColor: [
'rgba(71,193,28,0.71)'
]
},
{
label:"Failed",
data: [health[Date[0]][1],health[Date[1]][1],health[Date[2]][1],health[Date[3]][1],health[Date[4]][1]],
backgroundColor: [
'rgba(212,0,13,0.71)'
]
},
{
label:"Skipped",
data: [health[Date[0]][2],health[Date[1]][2],health[Date[2]][2],health[Date[3]][2],health[Date[4]][2]],
backgroundColor: [
'rgba(228,78,231,0.56)'
]
}
]
},
options: {
responsive: true
}
});
}
Given the data is available in the variable health, you can extract the labels through Object.keys() as follows.
labels: Object.keys(health),
The data of individual datasets can be extracted through Object.values(), followed by Array.map(). The data of the first dataset for example is defined as follows.
data: Object.values(health).map(v => v[0]),
Please have a look at your amended and runnable code below.
const health = {
"2020": [1, 2, 3],
"2021": [4, 5, 6]
}
var myLineChart = new Chart('myChart', {
type: 'line',
data: {
labels: Object.keys(health),
datasets: [{
label: "Pass",
data: Object.values(health).map(v => v[0]),
backgroundColor: 'rgba(71,193,28, 0.71)',
borderColor: 'rgb(71,193,28)',
fill: false
},
{
label: "Failed",
data: Object.values(health).map(v => v[1]),
backgroundColor: 'rgba(212,0,13,0.71)',
borderColor: 'rgb(212,0,13)',
fill: false
},
{
label: "Skipped",
data: Object.values(health).map(v => v[2]),
backgroundColor: 'rgba(228,78,231,0.56)',
borderColor: 'rgb(228,78,231)',
fill: false
}
]
},
options: {
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="80"></canvas>

Async Drill-down not updating of HighCharts

I am working on updating HighCharts stack bar charts dynamically along with their drilldown charts but I am stick to one problem that async drilldown not getting updated.
In my scenario series data is completely dynamic and also
corresponding drilldown columns.
There one more small issue because of color:null of drilldown series, each time series color are different and because it's dynamic so I can't set static colors is there any way to make color same like default color scheme of simple column chart
Here is issue reproducible JSFiddle
I have used following methods (second method is commented in JSFiddle)
First method use chart.update API
Second method use
chart.options.merge API
// Create the chart
var chart = Highcharts.chart('container', {
chart: {
type: 'column',
events: {
drilldown: function(e) {
if (!e.seriesOptions) {
var chart = this,
drilldowns = {
'Animals': {
type: 'column',
name: 'Animals',
data: [2, 3],
color: null
},
'Fruits': {
type: 'column',
name: 'Fruits',
data: [7, 3],
color: null
}
};
const series = [];
series.push(drilldowns['Animals']);
series.push(drilldowns['Fruits']);
series.forEach(serie => {
chart.addSingleSeriesAsDrilldown(e.point, serie);
});
chart.applyDrilldown();
}
},
drillup: function() {
var newOptions = {
legend: {
enabled: true
}
};
this.update(newOptions);
}
}
},
title: {
text: 'Basic drilldown'
},
xAxis: {
type: 'category'
},
legend: {
enabled: false
},
plotOptions: {
column: {
stacking: 'normal'
},
series: {
borderWidth: 0,
dataLabels: {
enabled: true
}
}
},
series: [{
name: 'Things',
colorByPoint: true,
data: [{
name: 'Animals',
y: 5,
drilldown: true
}, {
name: 'Fruits',
y: 2,
drilldown: true
}, {
name: 'Cars',
y: 4,
drilldown: true
}]
}]
});
$('#update').click(function() {
// First Method
chart.update({
drilldown: {
series: [{
name: 'Animals2',
data: [1, 5],
color: null,
type: 'column'
}, {
name: 'Fruits2',
data: [3, 5],
color: null,
type: 'column'
}]
}
});
// Second Method
/* chart.options.drilldown = Highcharts.merge(chart.options.drilldown, {
series: [{
name: 'Animals2',
data: [1, 5],
color: null,
type: 'column'
}, {
name: 'Fruits2',
data: [3, 5],
color: null,
type: 'column'
}]
}); */
});
You can dynamically set color to your drilldown series:
series.forEach(function(serie, i) {
serie.color = chart.options.colors[i];
chart.addSingleSeriesAsDrilldown(e.point, serie);
});
Live demo: https://jsfiddle.net/BlackLabel/mb7dhxc4/
I have found a workaround for above mention problem that async drilldown charts not getting updated I just updated the drilldown event from chart.events with the updated series of drilldown here is updated button code
$('#update').click(function() {
chart.hcEvents.drilldown[0] = function(e) {
if (!e.seriesOptions) {
var chart = this,
drilldowns = {
'Animals': {
type: 'column',
name: 'Animals2',
data: [1, 6],
color: null
},
'Fruits': {
type: 'column',
name: 'Fruits2',
data: [7, 4],
color: null
}
};
const series = [];
series.push(drilldowns['Animals']);
series.push(drilldowns['Fruits']);
series.forEach(serie => {
chart.addSingleSeriesAsDrilldown(e.point, serie);
});
chart.applyDrilldown();
}
};
});

Categories

Resources