javascriptcolorschart.jscontrast

Chart.js Pie with best random background color


I have some data that will be visualized in Pie Chart form.

However, I want 2 adjacent colors not to be identical. For example, if I have 3 data then I don't want them to have the background colors #90EE90, #A1FFA1, and #FF0000 because the colors #90EE90 and #A1FFA1 have a bad contrast score according to coolors.co.

let randomColor = () => {
  let characters='0123456789ABCDEF';
  let randomString='';
  for (let i=0; i<6; i++) {
    let randomIndex=Math.floor(Math.random()*characters.length);
    randomString+=characters.charAt(randomIndex);
  }
  return '#'+randomString;
}
let round = (num) => Math.round(num*100)/100;

let chart = null;
$('#generate').click(()=>{
  let data = [];
  let labels = [];
  let pieces = $('#pieces').val();
  for(let i=0; i<pieces; i++) {
    labels.push(randomColor());
    data.push(round(1/pieces));
  }
  if(chart) chart.destroy();
  chart = new Chart($('#chart'), {
    type: 'pie',
    data: {
      labels: labels,
      datasets: [{
        data: data,
        backgroundColor: labels,
        borderWidth: 0
      }]
    },
    options: {
      plugins: {
        tooltip: {
          callbacks: {
            label: (context) => {
              return context.parsed;
            }
          }
        }
      }
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div>
  <input id="pieces" type="number" value="20" />
  <button id="generate">Generate Chart</button>
</div>
<canvas id="chart"></canvas>

Can you help me generate some random background colors but adjacent colors have a good contrast score or don't hurt the eyes?


Solution

  • A possible solution is to use a color-contrast checker library, like this one: https://www.npmjs.com/package/wcag-contrast

    It's based on the WCAG contrast ratio standard: https://www.w3.org/TR/WCAG20/#contrast-ratiodef

    Using the hex() function, you can check the contrast ratio between two hex values, which is a value between 1 (low contrast) and 21 (high contrast).

    Your code can be improved in many different ways, anyway i only added the "core" improvements to add the discussed functionality (so you can better understand what i've done)

    const CONTRAST_THRESHOLD = 6;
    
    let randomColor = (prevColor = '#fff') => {
        const characters='0123456789ABCDEF';
        let currentColor;
        do {
            currentColor = '';
            for (let i = 0; i < 6; i++) {
                let randomIndex=Math.floor(Math.random()*characters.length);
                currentColor+=characters.charAt(randomIndex);
            }
        } while (wcagContrast.hex(prevColor, currentColor) < CONTRAST_THRESHOLD)
    
        return '#'+currentColor;
    }
    
    let checkFirstAndLastColor = (colorsArray) => {
        while (wcagContrast.hex(colorsArray[0], colorsArray[colorsArray.length -1]) < CONTRAST_THRESHOLD || wcagContrast.hex(colorsArray[0], colorsArray[1]) < CONTRAST_THRESHOLD) {
            colorsArray[0] = randomColor(colorsArray[1]);
        }
    }
    
    let round = (num) => Math.round(num*100)/100;
    
    let chart = null;
    $('#generate').click(()=>{
        let data = [];
        let labels = [];
        let pieces = $('#pieces').val();
        for(let i=0; i<pieces; i++) {
            labels.push(randomColor(labels[i - 1]));
            data.push(round(1/pieces));
        }
        if (labels.length > 2) {
            checkFirstAndLastColor(labels);
        }
        if(chart) chart.destroy();
        chart = new Chart($('#chart'), {
            type: 'pie',
            data: {
                labels: labels,
                datasets: [{
                    data: data,
                    backgroundColor: labels,
                    borderWidth: 0
                }]
            },
            options: {
                plugins: {
                    tooltip: {
                        callbacks: {
                            label: (context) => {
                                return context.parsed;
                            }
                        }
                    }
                }
            }
        });
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="
    https://cdn.jsdelivr.net/npm/wcag-contrast@3.0.0/dist/index.umd.min.js
    "></script>
    <div>
      <input id="pieces" type="number" value="20" />
      <button id="generate">Generate Chart</button>
    </div>
    <canvas id="chart"></canvas>

    The main idea is:

    1. Upgrade the randomColor() function to check if currentColor has a high contrast with the previous color (prevColor). Otherwise, generate another color until contrast ratio is >= CONTRAST_THRESHOLD (set a number between 1 and 21).

    2. Run checkFirstAndLastColor when all colors are set to check if the last color and the first one have an high contrast. If not, change the first color until it reach an high contrast with both the second and the last colors.

    As already said, code could be improved in many ways, i hope my answer gives you the right hint to reach your result.