javascriptchartschart.jsbubble-chart

Show image background of chart.js Bubbles


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?


Solution

  • 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 pointStyles 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:

    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, 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>

    or as a jsFiddle