Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart.js label position incorrect for horizontal stacked bar chart #416

Open
nikkorn opened this issue May 15, 2024 · 0 comments
Open

Chart.js label position incorrect for horizontal stacked bar chart #416

nikkorn opened this issue May 15, 2024 · 0 comments

Comments

@nikkorn
Copy link

nikkorn commented May 15, 2024

Hello,

I'm currently tying to use this plugin to render labels over bars on a horizontal stacked bar chart. My aim is to have something that matches a Gantt chart.

We already have this working with this plugin as part of a server-side chart renderer which we use to generate images of client-side charts. This is done using node and the napi-rs/canvas package and the result looks a little something like this, which is exactly what we want:
right_labeks
Some of the labels are missing for some of the smaller bars, which is intentional.

The problem is that we use the exact same chart configuration on the client but get a chart where the labels are way out of position, which looks like this (i've made the label colour black on the client so its easier to see where the labels are):
wrong_labels

Both the client and the chart rendered are using Chart.js v4.4.1 and chartjs-plugin-datalabels v2.2.0, and this is the chart definition we are using for both examples:

{
    "data": {
        "datasets": [
            {
                "backgroundColor": "#1976D2cc",
                "borderColor": "#1976D2",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    [
                        947203200000,
                        947233800000
                    ],
                    null
                ],
                "datalabels": {
                	"formatter": () => "Sleeping"
                },
                "group": "Home",
                "label": "Sleeping",
                "skipNull": true,
                "stack": "Home_Stack0"
            },
            {
                "backgroundColor": "#FFA000cc",
                "borderColor": "#FFA000",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    null,
                    [
                        947235600000,
                        947246400000
                    ]
                ],
                "datalabels": {
                	"formatter": () => "Coding"
                },
                "group": "Work",
                "label": "Coding",
                "skipNull": true,
                "stack": "Work_Stack0"
            },
            {
                "backgroundColor": "#388E3Ccc",
                "borderColor": "#388E3C",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    null,
                    [
                        0,
                        3600000
                    ]
                ],
                "datalabels": {
                	"formatter": () => "Eating"
                },
                "group": "Work",
                "label": "Eating",
                "skipNull": true,
                "stack": "Work_Stack0"
            },
            {
                "backgroundColor": "#FFA000cc",
                "borderColor": "#FFA000",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    null,
                    [
                        0,
                        16200000
                    ]
                ],
                "datalabels": {
                	"formatter": () => "Coding"
                },
                "group": "Work",
                "label": "Coding",
                "skipNull": true,
                "stack": "Work_Stack0"
            },
            {
                "backgroundColor": "#388E3Ccc",
                "borderColor": "#388E3C",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    [
                        34200000,
                        37800000
                    ],
                    null
                ],
                "datalabels": {
                	"formatter": () => "Eating"
                },
                "group": "Home",
                "label": "Eating",
                "skipNull": true,
                "stack": "Home_Stack0"
            },
            {
                "backgroundColor": "#D32F2Fcc",
                "borderColor": "#D32F2F",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    [
                        3600000,
                        10800000
                    ],
                    null
                ],
                "datalabels": {
                	"formatter": () => "Solving Crimes"
                },
                "group": "Home",
                "label": "Solving Crimes",
                "skipNull": true,
                "stack": "Home_Stack0"
            },
            {
                "backgroundColor": "#1976D2cc",
                "borderColor": "#1976D2",
                "borderSkipped": false,
                "borderWidth": 1,
                "data": [
                    [
                        3600000,
                        7200000
                    ],
                    null
                ],
                "datalabels": {
                	"formatter": () => "Sleeping"
                },
                "group": "Home",
                "label": "Sleeping",
                "skipNull": true,
                "stack": "Home_Stack0"
            }
        ],
        "labels": [
            "Home",
            "Work"
        ]
    },
 
    "options": {
        "animation": false,
        "barThickness": 28,
        "indexAxis": "y",
        "maintainAspectRatio": false,
        "plugins": {
            "datalabels": {
                "align": "right",
                "anchor": "start",
                "clamp": true,
                "clip": true,
                "color": "black",
                "display": (context) => {
                    // We don't want to display a label for a dataset which doesn't have any data for the current index.
                    if (!context.dataset.data[context.dataIndex]) {
                      return false;
                    }

                    // Get the metadata associated with this dataset.
                    const dataset = context.chart.getDatasetMeta(context.datasetIndex);

                    // Get the metadata of the rendered bar element.
                    const bar = dataset.data[context.dataIndex];

                    // We only want to render the label if it isn't wider than the bar that we are rendering it onto.
                    return context.chart.canvas.context.measureText(dataset.label).width < bar.width;
                }
            },
            "legend": {
                "display": false
            },
            "title": {
                "display": true,
                "text": "Activity/Location For Sean"
            }
        },
        "responsive": true,
        "scales": {
            "x": {
                "max": 947289600000,
                "min": 947203200000,
                "stacked": true,
                "ticks": {
                    "autoSkip": true,
                    "maxTicksLimit": 6
                },
                "type": "time"
            },
            "y": {
                "stacked": true
            }
        }
    },
    "type": "bar"
}

Something to mention is that on the client we are using the react-chartjs-2 package to render the chart (as our front end is all React), but we still have the same issue if don't use this package and instead just pass a reference to our blank canvas in to the Chart constructor.

Another thing to mention that might not be relevant, but on the client the following line in the plugins.datalabels.display callback fails as the canvas has no context (and I have to replace .context with .getContext("2d")) but the context is defined in the server-side renderer when the callback is invoked:

return context.chart.canvas.context.measureText(dataset.label).width < bar.width;

Do you happen to know why we are having this issue and what we might be able to do to resolve it? Thanks very much

Nik Howard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant