Chartjs-ChartJS Searching chart Type for positioning a Point onto a bar (range)

1👍

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…

Leave a comment