chart.js

how can I make a custom point style in ChartJS


In ChartJS one can change the point style to some predefined forms. But I need the point style to be an arrow so I tried to customize the point style but didn't succeed. I tried the following:

customTrianglePointStyle: function (ctx) {
const path = new Path2D();
const radius = 10;
path.moveTo(0, -radius);
path.lineTo(radius, radius);
path.lineTo(-radius, radius);
path.closePath();
return path;

},

and add it to the dataset

let dataset = {
data: JSON.parse(jsonData),
yAxisID: "y",
showLine: false,
pointRadius: 5,
pointStyle: this.customTrianglePointStyle,

}

But it falls back to the default point style.

In the documentation I found:

When a string is provided, the following values are supported: 'circle' 'cross' 'crossRot' 'dash' 'line' 'rect' 'rectRounded' 'rectRot' 'star' 'triangle' false If the value is an image or a canvas element, that image or canvas element is drawn on the canvas using drawImage

But in my case I don't want to draw an image but a shape on the canvas at each plot point.

Has someone an idea how I could achieve my goal.


Solution

  • As C3roe explained in the comments, and as is also stated in the documentation page you quoted from, you may provide the pointStyle as an HTMLCanvasElement (or an ImageElement). So, to set a custom style drawn using a canvas, you have to create a new canvas element, draw on that canvas using its RenderingContext and then 3) return the canvas (not the path).

    function pointStyle(){
        const canvas = document.createElement('canvas');
        canvas.height = 20;
        canvas.width = 20;
        const ctx = canvas.getContext('2d');
        const path = new Path2D();
        const radius = 10;
        path.moveTo(radius, 0);
        path.lineTo(2*radius, 2*radius);
        path.lineTo(0, 2*radius);
        path.closePath();
        ctx.strokeStyle = '#000';
        ctx.fillStyle = 'red';
        ctx.stroke(path);
        ctx.fill(path);
        return canvas;
    }
    

    The point style may be customised for each point, through the first argument received by the pointStyle function, that contains all information regarding the data point (explore it with console.log). You may use that information to rotate the points of the Path2D around the center of the canvas.

    However, we have a built-in point property for rotation, pointRotation, which is also scriptable receiving the same arguments as pointStyle (note that you may also use it to rotate a standard point style, like a triangle).

    Assuming the rotation information in the data is given as a rotate entry for each data point (e.g., {x: 0, y: 25, rotate: 20}) that contains the rotation of the point in degrees, the pointRotation function can be:

    function pointRotation(dataContext){
        return dataContext.raw?.rotate ?? 0;
    }
    

    which is much simpler than implementing rotation at the pointStyle level.

    Demo stack snippet:

    const dataXY = [
        {x: 0, y: 25, rotate: 20},
        {x: 100, y: 42, rotate: 80},
        {x: 200, y: 35, rotate: 120}
    ];
    
    function pointStyle(dataContext){
        // dataContext may be used to customize the image
        const canvas = document.createElement('canvas');
        canvas.height = 20;
        canvas.width = 20;
        const ctx = canvas.getContext('2d');
        const path = new Path2D();
        const radius = 10;
        path.moveTo(radius, 0);
        path.lineTo(2*radius, 2*radius);
        path.lineTo(0, 2*radius);
        path.closePath();
        ctx.strokeStyle = '#000';
        ctx.fillStyle = 'red';
        ctx.stroke(path);
        ctx.fill(path);
        return canvas;
    }
    
    function pointRotation(dataContext){
        return dataContext.raw?.rotate ?? 0;
    }
    
    new Chart('myChart', {
        type: 'line',
        data: {
            datasets: [{
                label: 'Dataset 1',
                data: dataXY
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            pointStyle: pointStyle,
            pointRotation: pointRotation,
            scales: {
                x: {
                    type: 'linear',
                    min: -5,
                    max: 205,
                    ticks: {
                        includeBounds: false,
                    },
                },
                y:{
                    grace: 2
                }
            }
        }
    });
    <div style="min-height: 250px">
        <canvas id="myChart"></canvas>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

    Update: if the dataset has a large number of points, creating a canvas-custom point style for each point might make the system slow, as Bert reports in comments. Of course, if the image itself does not change, but only other properties, like the rotation angle, there is no need for the pointStyle to be scriptable, it can be just one canvas, which should improve performance.

    In the example above, the point style doesn't actually change, so the options could be written as pointStyle: pointStyle() (instead of pointStyle: pointStyle) which will result in the function pointStyle being called only once, when the options object is created, and the pointStyle property being assigned its result, the unique point style-canvas. This jsFiddle contains the code of the above snippet with this small change.