i want to add bubble chart using chart.js to my project and i am trying to show image(PNG Format) background of chart.js bubbles like blow image, i saw this topic already, but doesn't work for me ... I tried this code and got no result. Please help. Bubble flag Charts
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Users</h3>
<ul class="box-controls pull-right">
<li><a class="box-btn-close" href="#"></a></li>
<li><a class="box-btn-slide" href="#"></a></li>
</ul>
</div>
<div class="box-body">
<div class="chart">
<canvas id="chart_6" height="212"></canvas>
</div>
</div>
<!-- /.box-body -->
</div>
if ($('#chart_6').length > 0) {
var BehpardakhtIcon = new Image();
BehpardakhtIcon.src = "~/images/Behpardakht.png";
BehpardakhtIcon.width = 22;
BehpardakhtIcon.height = 22;
var BehsazanIcon = new Image();
BehsazanIcon.src = "~/images/Behsazan.png";
BehsazanIcon.width = 22;
BehsazanIcon.height = 22;
const data = {
datasets: [{
data: [
{ x: 30, y: 50, r: 15 }
],
borderWidth: 2,
pointStyle: BehpardakhtIcon,
label: 'Company1',
hoverBorderWidth: 3,
}],
};
const config = {
type: 'bubble',
data: data,
options: {
scales: {
x: {
title: {
display: true,
text: 'x'
}
},
y: {
title: {
display: true,
text: 'y'
}
},
},
plugins: {
tooltip: { intersect: true },
afterUpdate: function (chart, options) {
chart.getDatasetMeta(0).data.forEach((d, i) => {
d._model.pointStyle = BehpardakhtIcon;
})
}
},
},
};
const myBubbleChart =
new Chart(document.getElementById('chart_6'), config);
}
also i need to know where can i download chart.js plugins and how can i use them?
To draw an image for each bubble, the pointStyle
property (which is scriptable) is sufficient, there's no need for a plugin. (Unrelatedly, you misplaced your plugin in the options.plugins
configuration object, where we set the options for each plugin. To create a new inline plugin, you have to place the code in the plugins
array on top of the configuration object, not under options
). Also, the old solution you linked to, wouldn't work with chart.js v4, as the PointElement
instances don't use the _model
entry any longer.
In any case, if you use a pointStyle
function, or if you define a plugin, you have to make sure the icons are loaded before you create the chart, or update
the chart once the images are loaded.
If we use the two image model from the original post, we can set up
an icons
array, having the images added by the dataset index they
are supposed to be used for:
const icons = [BehsazanIcon, BehpardakhtIcon];
// ....................
let nLoaded = 0;
icons.forEach(icon => {
icon.onload = () => {
nLoaded++
if(nLoaded >= icons.length){
myBubbleChart.update();
}
}
});
Then, the pointStyle
function can create a new canvas element with the corresponding size, draw the image on the canvas, and then return the canvas as it is compatible with the pointStyle
s used by chart.js,(see Point Styles in the docs):
const pointStyle = function(...args) {
const {raw, datasetIndex} = args[0]
const r = raw.r;
const canvas = document.createElement("canvas");
canvas.width = 2 * r;
canvas.height = 2 * r;
const ctx = canvas.getContext("2d");
ctx.drawImage(icons[datasetIndex], 0, 0, 2 * r, 2 * r);
return canvas;
}
Here's the whole code in a stack snippet
const BehpardakhtIcon = new Image();
BehpardakhtIcon.src = "https://upload.wikimedia.org/wikipedia/commons/9/97/Wikipedia-logo_thue.png";
BehpardakhtIcon.width = 22;
BehpardakhtIcon.height = 22;
const BehsazanIcon = new Image();
BehsazanIcon.src = "https://upload.wikimedia.org/wikipedia/commons/5/53/Wikimedia-logo.png";
BehsazanIcon.width = 22;
BehsazanIcon.height = 22;
const icons = [BehsazanIcon, BehpardakhtIcon]; // indexed by the dataset index
const pointStyle = function({raw, datasetIndex}) {
const r = raw.r;
const canvas = document.createElement("canvas");
canvas.width = 2 * r;
canvas.height = 2 * r;
const ctx = canvas.getContext("2d");
ctx.drawImage(icons[datasetIndex], 0, 0, 2 * r, 2 * r);
return canvas;
}
const data = {
datasets: [{
data: [
{ x: 30, y: 50, r: 45 },
{ x: 25, y: 20, r: 15 }
],
pointStyle,
label: 'Company1'
}, {
data: [
{ x: 24, y: 50, r: 35 },
{ x: 35, y: 20, r: 25 }
],
pointStyle,
label: 'Company2'
}],
};
const config = {
type: 'bubble',
data: data,
options: {
maintainAspectRatio: false,
scales: {
x: {
min: 20,
max: 40,
title: {
display: true,
text: 'x'
}
},
y: {
min: 10,
max: 60,
title: {
display: true,
text: 'y'
}
},
},
plugins: {
tooltip: { intersect: true },
},
}
};
const myBubbleChart = new Chart(document.getElementById('chart_6'), config);
let nLoaded = 0;
icons.forEach(icon => {
icon.onload = () => {
nLoaded++
if(nLoaded >= icons.length){
myBubbleChart.update();
}
}
});
<div style="min-height:300px">
<canvas id="chart_6"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
or as a jsFiddle.
The following stack snippet contains some non-essential additions to the previous solution, to cover related chart.js visual features:
5px
(raw.r + ($hovered ? 5 : 0)
) -- other visual responses to hover can easily be adapted into itconst BehpardakhtIcon = new Image();
BehpardakhtIcon.src = "https://upload.wikimedia.org/wikipedia/commons/9/97/Wikipedia-logo_thue.png";
BehpardakhtIcon.width = 22;
BehpardakhtIcon.height = 22;
const BehsazanIcon = new Image();
BehsazanIcon.src = "https://upload.wikimedia.org/wikipedia/commons/5/53/Wikimedia-logo.png";
BehsazanIcon.width = 22;
BehsazanIcon.height = 22;
const icons = [BehsazanIcon, BehpardakhtIcon]; // indexed by the dataset index
const pointStyle = function({raw, datasetIndex, element, type, mode}, {tooltip, legend} = {tooltip: false, legend: false}) {
const r = tooltip ? 8 : legend ? 10 : raw ? raw.r + (element?.$hovered ? 5 : 0) : 10;
const canvas = document.createElement("canvas");
canvas.width = 2 * r + (legend || tooltip ? 2 * r : 0);
canvas.height = 2 * r;
const ctx = canvas.getContext("2d");
ctx.drawImage(icons[datasetIndex], legend || tooltip ? r : 0, 0, 2 * r, 2 * r);
return canvas;
}
const data = {
datasets: [{
data: [
{ x: 30, y: 50, r: 45 },
{ x: 25, y: 20, r: 15 }
],
pointStyle,
label: 'Company1'
}, {
data: [
{ x: 24, y: 50, r: 35 },
{ x: 35, y: 20, r: 25 }
],
pointStyle,
label: 'Company2'
}],
};
const config = {
type: 'bubble',
data: data,
options: {
maintainAspectRatio: false,
onHover: function({chart}, hovered) {
const remove_hovered = (chart.$hovered ?? []).filter(element_h => !hovered.some(({element})=>element === element_h));
remove_hovered.forEach(element => {element.$hovered = false;});
chart.$hovered = (chart.$hovered ?? []).filter(element_h => hovered.some(({element})=>element === element_h));
for(const {element} of hovered){
if(!chart.$hovered.includes(element)){
element.$hovered = true;
chart.$hovered.push(element);
}
}
},
scales: {
x: {
min: 20,
max: 40,
title: {
display: true,
text: 'x'
}
},
y: {
min: 15,
max: 60,
title: {
display: true,
text: 'y'
}
},
},
plugins: {
tooltip: {
intersect: true,
usePointStyle: true,
boxPadding: 3,
callbacks: {
labelPointStyle({datasetIndex}){
return {pointStyle: pointStyle({datasetIndex}, {tooltip: true}), rotation: 0};
}
}
},
legend: {
labels: {
generateLabels(chart){
const labels = chart.data.datasets.map((_, datasetIndex) => ({
text: chart.data.datasets[datasetIndex].label,
hidden: chart.getDatasetMeta(datasetIndex).hidden === true,
datasetIndex: datasetIndex,
pointStyle: pointStyle({datasetIndex}, {legend: true}),
}));
return labels;
},
pointStyleWidth: 20,
usePointStyle: true
}
}
},
}
};
document.querySelector('#chart_6').addEventListener('mouseleave', () => {
if(myBubbleChart.$hovered) {
myBubbleChart.$hovered.forEach(element => element.$hovered = false);
myBubbleChart.$hovered = [];
}
})
const myBubbleChart = new Chart(document.getElementById('chart_6'), config)
let nLoaded = 0;
icons.forEach(icon => {
icon.onload = () => {
nLoaded++
if(nLoaded >= icons.length){
myBubbleChart.update();
}
}
});
<div style="min-height: 300px">
<canvas id="chart_6"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>