chartjs / chartjs-plugin-datalabels

Chart.js plugin to display labels on data elements
https://chartjs-plugin-datalabels.netlify.app
MIT License
886 stars 484 forks source link

Color gradient doesn't work as expected #307

Open michael-soorys opened 2 years ago

michael-soorys commented 2 years ago

The problem

I want to use a line chart with datalabels, with a color gradient. I want the datalabel background color to be the same color as the line.

Expected behavior

When given the same CanvasGradient object to both the line color and datalabel color props, they should have the same color at any given point on the line.

Current behavior

When I pass the same CanvasGradient object as the backgroundColor property to the datalabel config, it seems to completely ignore the value of the datalabel and uses the top-most color in the gradient, rather than calculating its color based on the position in the context chart range.

I have isolated the problem in this fork of the demo - https://codesandbox.io/s/plugin-example-forked-wv2uxg?file=/src/App.vue

stockiNail commented 2 years ago

@michael-soorys I think it's similar to https://github.com/chartjs/chartjs-plugin-datalabels/issues/114

michael-soorys commented 2 years ago

@stockiNail Ah, I see, guess I should have looked into your post more thoroughly. This does however not suffice since our charts can have multiple color stops through the data range. chroma.mix() supports only 2 colors, and as far as I can see there's no weighted mixing of more than 2 colors, like color gradient with multiple color stops.

This does give me an idea on how to implement a mixMore() type of method that would do the trick much like mix.

stockiNail commented 2 years ago

@michael-soorys yes, you're right, it supports only 2 colors.

This does give me an idea on how to implement a mixMore() type of method that would do the trick much like mix.

Yes, that's the solution, I guess. I had played a bit (and quickly) on your sample with 3 colors but I think it can be generalized:

backgroundColor: function(ctx) {
  var count = ctx.dataset.data.length;
  var ratio = count ? ctx.dataIndex / count : 0;
  if (ratio <= 0.5) {
    return chroma.mix("#ff00ff", "#ff0000", ratio / 0.5);  
  }
  return chroma.mix("#ff0000", "#000000", (ratio - 0.5) / 0.5);
},
michael-soorys commented 2 years ago

@stockiNail Thank you for your input : )

michael-soorys commented 2 years ago

I managed to solve my problem. What I was looking for is a way for the user to set specific values on the chart to be color thresholds, which would for example change the line to red when bellow 10, be yellow above 40 and be green above 100. I solved this problem for my use case, but some of the code can still be used to mix less complex charts with more than 2 colors.

Here's the example code: https://codesandbox.io/s/plugin-example-forked-i5njjt?file=/src/App.vue

It basically does the same as mixing 2 colors, but it figures out what the 2 colors are based on the datalabel value in question. Luckily color gradient logic is not very sophisticated, just an average of RBG numbers, so it's the same color as the line, even though they're separate calculations.