Julian
Julian

Reputation: 393

Chart.js stacked bar chart text on top of the stacked bars

I have Chart.js chart inside a div element. The way that I got so far is good looking and it is what I need, but it is missing one big part, a dynamic text that has to aggregate the data of the chart and show a summary. Not bar by bar , but a summary of the two bars. This is where I got so far:

var chart;
var opts = {
  layout: {
    padding: {
      left: 0,
      right: 0,
      top: 0,
      bottom: 0
    }
  },
  responsive: true,
  maintainAspectRatio: false,
  legend: {
    display: false,
  },
  tooltips: {
    enabled: false,
  },
  scales: {
    xAxes: [{
      stacked: true,
      display: false,
      ticks: {
        fontColor: "#fff",
      }
    }, ],
    yAxes: [{
      stacked: true,
      display: false,
      ticks: {
        fontColor: "#fcafe4",
        mirror: true
      }
    }]
  }
};

var datasets = [{
    label: "My Label",
    backgroundColor: ["#FF3A2F"],
    data: [],
  },
  {
    label: "Your Label",
    backgroundColor: ["#0CB04A"],
    data: [],
  }
];

var labels = ['Label 1'];

var ctx = document.getElementById('chart-canvas');

chart = new Chart(ctx, {
  type: "horizontalBar",
  data: {
    labels: labels,
    datasets: datasets
  },
  options: opts
});

function refresh() {

  chart.data.datasets[0].data[0] = Math.floor(Math.random() * 1000) + 1;
  chart.data.datasets[1].data[0] = Math.floor(Math.random() * 1000) + 1;
  chart.config.options.scales.xAxes[0].ticks.max = chart.data.datasets[0].data[0] + chart.data.datasets[1].data[0];
  chart.update();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<div class="row">
  <div class="col-lg-5 small">My Stacked Chart</div>
  <div class="col-lg-6">
    <div class="chart-container">
      <canvas id="chart-canvas"></canvas>
    </div>
  </div>
  <div class="col-lg-1 my-auto">
    <div class="btn-group">
      <button type="button" class="btn btn-primary btn-md btn-sm" onClick="refresh()">
                            Refresh
                        </button>
    </div>
  </div>
</div>

I'm wondering if there is a way that I can add a text that will be in the middle of the canvas and will show data that is in the chart. For example XXXX of Total and will be updated every time the Refresh button is clicked. Something like that: enter image description here

Thanks in advance! Julian

Upvotes: 0

Views: 1318

Answers (1)

uminder
uminder

Reputation: 26150

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 text directly on the canvas using CanvasRenderingContext2D.fillText().

chart = new Chart(ctx, {
  type: "horizontalBar",
  plugins: [{
    afterDraw: chart => {      
      var ctx = chart.chart.ctx; 
      var value =  chart.config.data.datasets[0].data[0];
      var total = chart.config.data.datasets.reduce((t, ds) => t + ds.data[0], 0);
      var percent = Math.round(1000 / total * value) / 10;
      var xAxis = chart.scales['x-axis-0'];   
      var yAxis = chart.scales['y-axis-0'];   
      ctx.save();
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.font = "24px Arial";
      ctx.fillText(value + ' (' + percent + '%) of ' + total, xAxis.right / 2, (yAxis.bottom + yAxis.top) / 2);
      ctx.restore();
    }
  }],
  ... 

Please have a look at your amended code below.

var chart;
var opts = {
  layout: {
    padding: {
      left: 0,
      right: 0,
      top: 0,
      bottom: 0
    }
  },
  responsive: true,
  maintainAspectRatio: false,
  legend: {
    display: false,
  },
  tooltips: {
    enabled: false,
  },
  scales: {
    xAxes: [{
      stacked: true,
      display: false,
      ticks: {
        fontColor: "#fff",
      }
    }, ],
    yAxes: [{
      stacked: true,
      display: false,
      ticks: {
        fontColor: "#fcafe4",
        mirror: true
      }
    }]
  }
};

var datasets = [{
    label: "My Label",
    backgroundColor: ["#FF3A2F"],
    data: [],
  },
  {
    label: "Your Label",
    backgroundColor: ["#0CB04A"],
    data: [],
  }
];

var labels = ['Label 1'];

var ctx = document.getElementById('chart-canvas');

chart = new Chart(ctx, {
  type: "horizontalBar",
  plugins: [{
    afterDraw: chart => {      
      var ctx = chart.chart.ctx; 
      var value =  chart.config.data.datasets[0].data[0];
      var total = chart.config.data.datasets.reduce((t, ds) => t + ds.data[0], 0);
      var percent = Math.round(1000 / total * value) / 10;
      var xAxis = chart.scales['x-axis-0'];   
      var yAxis = chart.scales['y-axis-0'];   
      ctx.save();
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.font = "24px Arial";
      ctx.fillText(value + ' (' + percent + '%) of ' + total, xAxis.right / 2, (yAxis.bottom + yAxis.top) / 2);
      ctx.restore();
    }
  }],
  data: {
    labels: labels,
    datasets: datasets
  },
  options: opts
});
refresh();

function refresh() {
  chart.data.datasets[0].data[0] = Math.floor(Math.random() * 1000) + 1;
  chart.data.datasets[1].data[0] = Math.floor(Math.random() * 1000) + 1;
  chart.config.options.scales.xAxes[0].ticks.max = chart.data.datasets[0].data[0] + chart.data.datasets[1].data[0];
  chart.update();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<div class="row">
  <div class="col-lg-5 small">My Stacked Chart</div>
  <div class="col-lg-6">
    <div class="chart-container">
      <canvas id="chart-canvas"></canvas>
    </div>
  </div>
  <div class="col-lg-1 my-auto">
    <div class="btn-group">
      <button type="button" class="btn btn-primary btn-md btn-sm" onClick="refresh()">
          Refresh
      </button>
    </div>
  </div>
</div>

Upvotes: 1

Related Questions