3

I am trying to create a chart with line/border color with different color based on value, like this image, so color change above and below a certain point.

enter image description here

I have already went through the segment sample on chartjs docs (https://www.chartjs.org/docs/latest/samples/line/segments.html), but this is using skip and points, whereas I am trying to do this with value, which I can't figure out.

Here is my code, it's in ejs file (only diff from js is variable passed in <%- %> over ${}) :

<canvas id="LineChart" height="100"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.min.js"
    integrity="sha512-asxKqQghC1oBShyhiBwA+YgotaSYKxGP1rcSYTDrB0U6DxwlJjU59B67U8+5/++uFjcuVM8Hh5cokLjZlhm3Vg=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>


<script>
var ctx=document.getElementById( 'LineChart' ).getContext( "2d" );

var myChart=new Chart( ctx, {
                type: 'line',
                data: {
                    labels: <%- JSON.stringify( labels )%>,
                    datasets: [ {
                        label: ['a','b','c','d'],
                        data: ['1','2','3','4'],
                        backgroundColor:'rgba(54, 162, 235, 0.5)',
                        borderColor: rgb( 255, 255, 255 ), 
                        segment: {
                           borderColor: function black() {
                               if ( <%=maxValue%> >99) {
                                     return 'black';
                                 }
                             if ( <%=minValue%> >99) {
                                 return 'red';
                             }
                        }
                    },
}],
}
});
</script>

Here is the related sample code on chartjs website, (for quick reference) :

const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;

const down = (ctx, value) => ctx.p0.parsed.y > ctx.p1.parsed.y ? value : undefined;

borderColor: ctx => skipped(ctx, 'rgb(0,0,0,0.2)') || down(ctx, 'rgb(192,75,75)')

I will be really grateful for any help! Thanks a lot.

Update: I was able to figure this out mostly (I can make a linear gradient with color stops on certain value), but the problem I am facing is in selecting x/y-axis to fetch pixel for value, as I can't select chart properly, I made this fiddle with comment where I am facing the issue, it would be great help if someone can take a look and suggest me something, here is the fiddle : jsfiddle.net/baozkrw1/1

Also, here is the fiddle code for quick view :

HTML :

<h1>Chart One</h1>
<canvas id="LineChart" height="100"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.min.js"
    integrity="sha512-asxKqQghC1oBShyhiBwA+YgotaSYKxGP1rcSYTDrB0U6DxwlJjU59B67U8+5/++uFjcuVM8Hh5cokLjZlhm3Vg=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>


JS :

var ctx = document.getElementById('LineChart').getContext("2d");
let width, height, gradient;

function getGradient(ctx, chartArea) {
  const chartWidth = chartArea.right - chartArea.left;
  const chartHeight = chartArea.bottom - chartArea.top;

  if (gradient === null || width !== chartWidth || height !== chartHeight) {
    width = chartWidth;
    height = chartHeight;
    let y = 27; // I want this to be the pixel for value I pass, instead of hard value

    // but I can't select y-axis like this:
    // var yaxis=y.scales[ 'y-axis-0' ];
    // as it says undefined, and if I do like this : 
    //  const { ctx, canvas, scales: { x, y } }=chart;
    // var xaxis=x.scales[ 'x-axis-0' ];
    // It says chart not defined, which I think is because I am not making this a function and passing chart, and I can't figure out how to do it right.


    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    gradient.addColorStop(y / chartHeight, 'black');
    gradient.addColorStop(0.6, 'yellow');

    gradient.addColorStop(0.9, 'red');

    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    gradient.addColorStop(y / chartHeight, 'black');
    gradient.addColorStop(0.6, 'yellow');

    gradient.addColorStop(0.9, 'red');
  }
  return gradient;
}
var myChart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['10', '20', '30', '40', '50'],
    datasets: [{
      label: 'Hello',
      data: ['1', '2', '3', '4', '5'],
      backgroundColor: 'rgba(255, 255, 78, 1)',
      borderColor: function(context, options) {
        const chart = context.chart;
        const {
          ctx,
          chartArea
        } = chart;
        if (!chartArea) {
          return null;
        }
        return getGradient(ctx, chartArea);
      },
    }]
  },
});

LeeLenalee
  • 27,463
  • 6
  • 45
  • 69
Abhishek Soni
  • 261
  • 1
  • 3
  • 12

1 Answers1

3

You will get the both points on the start and end of the line segment the function is evaluating for as p0 and p1 in here you can access the y value like this: val = ctx.p0.parsed.y;

Live example:

var options = {
  type: 'line',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
      label: '# of Votes',
      data: [12, 19, 3, 5, 2, 3],
      segment: {
        borderColor: (ctx) => {
          val = ctx.p0.parsed.y;
          return val >= 15 ? 'green' : val >= 10 ? 'blue' : val >= 5 ? 'pink' : 'purple'
        }
      }
    }]
  },
  options: {}
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>

EDIT:

As per my comment you were pretty close with your pixelForValue, you just had to pass the context also to your gradiant function and dont use the v2 default ID's for the scales:

borderColor: function(context, options) {
  const chart = context.chart;
  const {
    ctx,
    chartArea
  } = chart;
  if (!chartArea) {
    return null;
  }
  return getGradient(ctx, chartArea, chart);
}

function getGradient(ctx, chartArea, chart) {
  const chartWidth = chartArea.right - chartArea.left;
  const chartHeight = chartArea.bottom - chartArea.top;

  if (gradient === null || width !== chartWidth || height !== chartHeight) {
    width = chartWidth;
    height = chartHeight;
    let y2 = 27; // I want this to be the pixel for value I pass, instead of hard value
    
    const { scales: { x, y } } = chart;

    console.log(x.getPixelForValue(20), x.getPixelForValue(4))
    


    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    gradient.addColorStop(y2 / chartHeight, 'black');
    gradient.addColorStop(0.6, 'yellow');

    gradient.addColorStop(0.9, 'red');

    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
    gradient.addColorStop(y2 / chartHeight, 'black');
    gradient.addColorStop(0.6, 'yellow');

    gradient.addColorStop(0.9, 'red');
  }
  return gradient;
}

But you can also choose to make an array of objects with the value and color and use that to make your gradiant:

var ctx = document.getElementById('LineChart').getContext("2d");
let width, height, gradient;

const getcolorStop = (val, max) => (val / max);
const colors = [{
    val: 2,
    color: 'pink',
  },
  {
    val: 12,
    color: 'purple',
  },
  {
    val: 20,
    color: 'green',
  }
]

function getGradient(ctx, chartArea, chart) {
  const chartWidth = chartArea.right - chartArea.left;
  const chartHeight = chartArea.bottom - chartArea.top;

  if (gradient === null || width !== chartWidth || height !== chartHeight) {
    width = chartWidth;
    height = chartHeight;

    const {
      scales: {
        y: {
          max
        }
      }
    } = chart;

    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);

    colors.forEach((color) => {
      if (color.val > max) {
        color.val = max;
      }

      gradient.addColorStop(getcolorStop(color.val, max), color.color);
    });
  }
  return gradient;
}
var myChart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: ['10', '20', '30', '40', '50'],
    datasets: [{
      label: 'Hello',
      data: [12, 19, 3, 5, 2, 3],
      backgroundColor: 'rgba(255, 255, 78, 1)',
      borderColor: function(context, options) {
        const chart = context.chart;
        const {
          ctx,
          chartArea
        } = chart;
        if (!chartArea) {
          return null;
        }
        return getGradient(ctx, chartArea, chart);
      },
    }]
  },
});
<h1>Chart One</h1>
<canvas id="LineChart" height="100"></canvas>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
LeeLenalee
  • 27,463
  • 6
  • 45
  • 69
  • Hi LeeLenalee, thanks a lot for responding, but this has separate color above same value, like I want all the lines above 12 to be green and those between 4 and 12 to be violet, and below 4 to be pink, but currently it's like different color above same value, can you please look into it – Abhishek Soni Aug 13 '21 at 21:47
  • Here is a image of what I am talking about: https://imgur.com/bi0YPM9 – Abhishek Soni Aug 13 '21 at 21:53
  • Only ways to achieve that is by either adding points in between so you can use segments but thats ugly or you will need to draw the line yourself on the canvas because chart.js does not support the behaviour you want afaik – LeeLenalee Aug 13 '21 at 21:56
  • Okay, I will try to draw it then, also if something comes up in your mind, please let me know. – Abhishek Soni Aug 13 '21 at 22:00
  • Hi Lee, I was able to figure this out mostly (I can make a linear gradient with color stops on certain value), but the problem I am facing is in selecting x/y-axis to fetch pixel for value, as I can't select chart properly, I made this fiddle with comment where I am facing the issue, it would be great help if you can take a look and suggest me something, here is the fiddle : https://jsfiddle.net/baozkrw1/1/ – Abhishek Soni Aug 15 '21 at 19:10
  • You were close, you just need to pass the chart instance to your function ass well and dont try to use the v2 id's: https://jsfiddle.net/Leelenaleee/043c1gk6/4/ – LeeLenalee Aug 15 '21 at 19:50
  • Thanks a lot! Also, if you want to add your code as answer, I'd be happy to accept the answer, otherwise I will add it for others help. – Abhishek Soni Aug 16 '21 at 08:23
  • Updated answe, also added a slightly different answer wich doesnt use `pixelForValue` but which might also work nice – LeeLenalee Aug 16 '21 at 15:36