10

I am generating a chart.js canvas bar chart. What I am trying to do is, inside of the labels array, add images that go with each label, as opposed to just the text label itself. Here is the code for the chart: The json object that I am getting data from has an image url that I want to use to display the picture:

$.ajax({
method: "get",
url: "http://localhost:3000/admin/stats/show",
dataType: "json",
error: function() {
  console.log("Sorry, something went wrong");
},
success: function(response) {
  console.log(response)
  var objectToUse = response.top_dogs
  var updateLabels = [];
  var updateData = [];
  for (var i = 0; i < objectToUse.length; i+=1) {
    updateData.push(objectToUse[i].win_percentage * 100);
    updateLabels.push(objectToUse[i].title);
  }
  var data = {
    labels: updateLabels,
    datasets: [
      {
        label: "Top Winners Overall",
        fillColor: get_random_color(),
        strokeColor: "rgba(220,220,220,0.8)",
        highlightFill: get_random_color(),
        highlightStroke: "rgba(220,220,220,1)",
        data: updateData
      }
    ]
  };

  var options = {
    //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
    scaleBeginAtZero : true,

    //Boolean - Whether grid lines are shown across the chart
    scaleShowGridLines : true,

    //String - Colour of the grid lines
    scaleGridLineColor : "rgba(0,0,0,.05)",

    //Number - Width of the grid lines
    scaleGridLineWidth : 1,

    //Boolean - Whether to show horizontal lines (except X axis)
    scaleShowHorizontalLines: true,

    //Boolean - Whether to show vertical lines (except Y axis)
    scaleShowVerticalLines: true,

    //Boolean - If there is a stroke on each bar
    barShowStroke : true,

    //Number - Pixel width of the bar stroke
    barStrokeWidth : 2,

    //Number - Spacing between each of the X value sets
    barValueSpacing : 5,

    //Number - Spacing between data sets within X values
    barDatasetSpacing : 2,
  };

  var loadNewChart = new Chart(barChart).Bar(data, options);
  }    

});

If anyone has a solution it would be greatly appreciated!

uminder
  • 23,831
  • 5
  • 37
  • 72
jared k
  • 111
  • 1
  • 1
  • 7
  • Adding an image to the labels is not an easy adjustment. The label size and position is determined in 3-4 places in the source code and is very dependent on elements of the chart itself. I would say don't attempt to add your images to the labels themselves but rather put them below the chart (like a footnote or bottom legend) and refer to the images in the labels. – markE May 14 '15 at 23:10
  • @jared k - did you decide to go with the footnote for this? – potatopeelings May 30 '15 at 06:43
  • yes I did. Thank you for the tip! – jared k Jun 17 '15 at 18:46
  • I wish this was possible. It would be great to be able to have profile pictures under a bar chart instead of text. Does anyone know of another solution? – Barrett Kuethen Mar 10 '16 at 22:56

2 Answers2

14

I'm aware that this is an old post but since it has been viewed so many times, I'll describe a solution that works with the current Chart.js version 2.9.3.

The Plugin Core API offers a range of hooks that may be used for performing custom code. You can use the afterDraw hook to draw images (icons) directly on the canvas using CanvasRenderingContext2D.

plugins: [{
  afterDraw: chart => {
    var ctx = chart.chart.ctx; 
    var xAxis = chart.scales['x-axis-0'];
    var yAxis = chart.scales['y-axis-0'];
    xAxis.ticks.forEach((value, index) => {  
      var x = xAxis.getPixelForTick(index);   
      ctx.drawImage(images[index], x - 12, yAxis.bottom + 10);
    });      
  }
}],

The position of the labels will have to be defined through the xAxes.ticks.padding as follows:

xAxes: [{
  ticks: {
    padding: 30
  }   
}],

Please have a look at the following runnable code snippet.

const labels = ['Red Vans', 'Blue Vans', 'Green Vans', 'Gray Vans'];
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png']
  .map(png => {
    const image = new Image();
    image.src = png;
    return image;
  });
const values = [48, 56, 33, 44];

new Chart(document.getElementById("myChart"), {
  type: "bar",
  plugins: [{
    afterDraw: chart => {      
      var ctx = chart.chart.ctx; 
      var xAxis = chart.scales['x-axis-0'];
      var yAxis = chart.scales['y-axis-0'];
      xAxis.ticks.forEach((value, index) => {  
        var x = xAxis.getPixelForTick(index);   
        ctx.drawImage(images[index], x - 12, yAxis.bottom + 10);
      });      
    }
  }],
  data: {
    labels: labels,
    datasets: [{
      label: 'My Dataset',
      data: values,
      backgroundColor: ['red', 'blue', 'green', 'lightgray']
    }]
  },
  options: {
    responsive: true,
    legend: {
      display: false
    },    
    scales: {
      yAxes: [{ 
        ticks: {
          beginAtZero: true
        }
      }],
      xAxes: [{
        ticks: {
          padding: 30
        }   
      }],
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>
uminder
  • 23,831
  • 5
  • 37
  • 72
  • 1
    This is a really good approach. I had a bug resizing the element but it can be easily fixed by not defining the image every time. Instead I created an array with images and not urls and then it worked like a charm +1 for that answer :) – BlackNetworkBit Dec 19 '20 at 11:00
  • 1
    I had to add a onload function for the images to show up. `image.onload = () => { ctx.drawImage(image, x - 12, yAxis.bottom + 10);};` – Mahin Khan Sep 08 '21 at 17:14
  • You can add the image to the document and `v-show=false` it. Then use that image instead of creating a new elem. That way you wont have to use onload. And this is better because every chart redraw (on hover, hide/show data etc) will not try to reload the image. It reduced a lot of flickering for me. – myusuf Nov 29 '21 at 14:49
0

Chart.js v3+ solution to pie, doughnut and polar charts

With version 3 of Chart.js and the updated version of chart.js-plugin-labels, this is now incredbly simple.

in options.plugins.labels, add render: image and the nested array images with objects containing the properties src, width and height.

const data = {
  labels: ['Label 1', 'Label 2', 'Label 3', 'Label 4', 'Label 5', 'Label 6', 'Label 7', 'Label 8'],
  datasets: [{
    label: 'Image labels',

    // Making each element take up full width, equally divided
    data: [100, 100, 100, 100, 100, 100, 100, 100],
    backgroundColor: [
      'rgba(255, 26, 104, 0.2)',
      'rgba(54, 162, 235, 0.2)',
      'rgba(255, 206, 86, 0.2)',
      'rgba(75, 192, 192, 0.2)',
      'rgba(153, 102, 255, 0.2)',
      'rgba(255, 159, 64, 0.2)',
      'rgba(0, 0, 0, 0.2)',
      'rgba(20, 43, 152, 0.2)'
    ]
  }]
};

const config = {
  type: 'doughnut',
  data,
  options: {
    plugins: {
      // Accessing labels and making them images
      labels: {
        render: 'image',
        images: [{
            src: 'https://cdn0.iconfinder.com/data/icons/google-material-design-3-0/48/ic_book_48px-256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn3.iconfinder.com/data/icons/glypho-free/64/pen-checkbox-256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn1.iconfinder.com/data/icons/jumpicon-basic-ui-glyph-1/32/-_Home-House--256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn1.iconfinder.com/data/icons/social-media-vol-3/24/_google_chrome-256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn0.iconfinder.com/data/icons/google-material-design-3-0/48/ic_book_48px-256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn3.iconfinder.com/data/icons/glypho-free/64/pen-checkbox-256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn1.iconfinder.com/data/icons/jumpicon-basic-ui-glyph-1/32/-_Home-House--256.png',
            height: 25,
            width: 25
          },
          {
            src: 'https://cdn1.iconfinder.com/data/icons/social-media-vol-3/24/_google_chrome-256.png',
            height: 25,
            width: 25
          },
        ]
      }
    }
  }
};

// render init block
const myChart = new Chart(
  document.getElementById('myChart').getContext('2d'),
  config
);
.chartCard {
  width: 100vw;
  height: 500px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chartBox {
  width: 600px;
  padding: 20px;
}
<div class="chartCard">
  <div class="chartBox">
    <canvas id="myChart"></canvas>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/chart.js-plugin-labels-dv@3.0.5/dist/chartjs-plugin-labels.min.js"></script>
Sigurd Mazanti
  • 2,098
  • 1
  • 8
  • 24