-1

I'am using Angular 8 and ng2-charts package to create a stacked areachart. I want to create a chart like this. I tried to use min and max values to create Y axis from 0% to 100%, but it works incorrect with different data (if the sum of data for each label is greater than 100, then the graph is out of bounds). I also want to display them stacked on top of one another, but don't know how to achieve this.

I also attach my code on stackblitz, where I tried to wrote this.

Selaka Nanayakkara
  • 3,296
  • 1
  • 22
  • 42
Syncmaxim
  • 47
  • 9

3 Answers3

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,
});
HeadhunterKev
  • 1,728
  • 5
  • 13
  • This is almost what I wanted, but I will still need to make the display of the point value equal to the non-percentage value. Also the problem is that if you remove the display of any label, the chart on the level below is not redrawn with 100% maximum value. I assume this requires a callback to redraw the chart with new values. – Syncmaxim Dec 09 '19 at 07:48
  • @Syncmaxim I updated my post. Feel free to ask me about it. – HeadhunterKev Dec 09 '19 at 13:34
  • In my solution it's easy to add an additional dataset. Note that the animation is not 100% correct because of the wrong animation order. You can find an solution for that [on GitHub](https://github.com/chartjs/Chart.js/issues/5181#issuecomment-360328471) I didn't add that in my code. – HeadhunterKev Dec 09 '19 at 13:37
  • 1
    _Took me quite some time, updating the legend is not that easy in chart.js..._ I'm fully agree with you. And this is what I expected to get. I will try to implement it using angular and then I will tell you about the results. I also will update my question with working angular code. Thanks! – Syncmaxim Dec 12 '19 at 12:51
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'];
  }
}
Selaka Nanayakkara
  • 3,296
  • 1
  • 22
  • 42
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.

matvs
  • 1,763
  • 16
  • 26
  • Unfortunately, this is not a solution, but just a way to find the maximum value. In any case, the resulting value must still be displayed correctly. – Syncmaxim Dec 09 '19 at 07:52