Stacked areachart with percents values on the y axis using Charts.js and Angular 8

👍:1

Updated answer:

I don’t know anything about typescript or angular. That’s why I wrote my program in plain Javascript.

Took me quite some time, updating the legend is not that easy in chart.js

I know it’s quite complexe and may not be an optimal solution. Please feel free to ask me about the code or tell me possible improvements.

Complete code with live-preview: https://jsbin.com/detiqucime/6/edit?js,output

Complete code:

let canvas = document.getElementById("chart");
let ctx = canvas.getContext("2d");

const dataLabels = [
  'First', 
  'Second', 
  'Third'
]

const colors = {
  'First': '#bad96b',
  'Second': '#8ad0f9',
  'Third': '#ffda78'
}

let data = [
  [65, 59, 80, 81, 56, 55, 40],
  [28, 48, 40, 19, 86, 27, 90],
  [22, 44, 23, 69, 82, 47, 50]
]

// hiddenArray (array with 'true' or 'null' (hidden or not) to know which datasets are hidden, those get not calculated)
let hiddenArray = []
// Initiate hiddenArray: nothing hidden at start
for (let i = 0; i < data.length; i++) {
  hiddenArray.push(null)
}

// New data array we use for the chart
let percentageData = []

calcPercentages(hiddenArray)

function calcPercentages(hiddenArray) {
  percentageData = []

  // Fill percentageData with empty arrays for same structure like data
  for (let i = 0; i < data.length; i++) {
    percentageData.push([])
  }

  for (let innerKey in data[0]) {
    // Get the sum of each data
    let sum = 0
    for (let outerKey in data) {
      if (hiddenArray[outerKey]) {
        continue;
      }
      sum = sum + data[outerKey][innerKey]
    }

    // Calculate the new percentageData
    for (let outerKey in data) {
      if (hiddenArray[outerKey]) {
        percentageData[outerKey][innerKey] = 0
      } else {
        percentageData[outerKey][innerKey] = (data[outerKey][innerKey]/sum)*100
      }
    }
  }
}

let chartData = {
  labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],
  datasets: []
}

for (let i = 0; i < data.length; i++) {
  let newDataset = {}
  newDataset.label = dataLabels[i]
  newDataset.backgroundColor = colors[dataLabels[i]]
  newDataset.data = percentageData[i]
  chartData.datasets.push(newDataset)
}

let options = {
  responsive: true,
  legend: {
    //reverse: true,
    onClick: function(e, legendItem) {
      let index = legendItem.datasetIndex;
      let ci = this.chart;      
      let meta = ci.getDatasetMeta(index);

      // Change 'hidden'-property
      meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;      

      // Get hiddenArray
      let hiddenArray = []

      ci.data.datasets.forEach(function(e, i) {
        let meta = ci.getDatasetMeta(i);
        hiddenArray.push(meta.hidden)
      });

      // Update percentageData
      calcPercentages(hiddenArray)

      for (let i = 0; i < percentageData.length; i++) {
        chartData.datasets[i].data = percentageData[i]
      }

      // Rerender the chart
      ci.update();
    }
  },
  scales: {
    yAxes: [{
      stacked: true,
      ticks: {
        min: 0,
        max: 100,
        callback: function(value) {
          return value + '%'
        }
      }
    }],
  },
  tooltips: {
    callbacks: {
      label: function(tooltipItem){
        return data[tooltipItem.datasetIndex][tooltipItem.index]
      }
    }
  }
}

let myChart = new Chart(ctx, {
  type: 'line',
  data: chartData,
  options: options,
});

👍:0

Please find the updated code snippet of the component below. Updated the answer accordingly in the stackblitz here

import { Component, OnInit } from '@angular/core';
import { ChartDataSets, ChartOptions } from 'chartjs';
import { Color, Label } from 'ng2-charts';
import 'chartjs-plugin-zoom';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  lineChartData: ChartDataSets[] = [];
  lineChartLabels: Label[] = [];

  lineChartOptions: ChartOptions = {
    legend: {
      reverse: true
    },
    responsive: true,
    scales: {
      yAxes: [{}],
      xAxes:[{}]
    },
    pan: {
      enabled: true,
      mode: 'x',
    },
    zoom: {
      enabled: true,
      mode: 'x',
      speed: 1.5
    },
  };

  lineChartColors: Color[] = [];

  lineChartLegend = true;
  lineChartPlugins = [];
  lineChartType = 'line';

  ngOnInit() {
    this.lineChartData = [
    { data: [65, 59, 80, 81, 56, 55, 40], label: 'First'},
    { data: [28, 48, 40, 19, 110, 27, 90], label: 'Second'},
    { data: [22, 44, 23, 69, 82, 47, 50], label: 'Third'}
    ];

    this.lineChartLabels = ['2006', '2007', '2008', '2009', '2010', '2011', '2012'];
  }
}

👍:0

What you could do is to find maximum y value of your graph and then calculate each other value as a percentage of this found maximum one.

For example like this:

let sumOfData = [];
this.lineChartData.forEach((elem) => elem.data.forEach((value, index) => {
    if(!sumOfData[index]) {
      sumOfData[index] = value;
    } else {
      sumOfData[index] += value;
    }
}));
this.maxChartValue = Math.max(...sumOfData);

Working example on stackblitz

Of course any other arbitrary number to calculate percents for the graph may be used. If it is greater, than a maximum one, then it will look more similar to the example from anychart.com that you linked to.

In any case you need some number in order to determine percent of it.
Just some values in array won’t do.

Leave a comment