Search code examples
javascripttypescriptamchartsamcharts4

Amcharts: stacked columns with multiple series


I have multiple series in one chart, such as seen in this codepen:

let chart = am4core.create("chartdiv", am4charts.XYChart);
      chart.leftAxesContainer.layout = "vertical";
      chart.numberFormatter.numberFormat = '# €';

      chart.data = [
        {name: "2011\nLorient", transport: 56, stay: 200, costByNight: 29, costByKm: 14},
        {name: "2015\nPoitiers\nLa Rochelle", transport: 96, stay: 54, costByNight: 9, costByKm: 23},
        {name: "2016\nRoyaume-Uni", transport: 160, stay: 332, costByNight: 47, costByKm: 62},
        {name: "2016\nBiarritz", transport: 185, stay: 516, costByNight: 74, costByKm: 27},
        {name: "2017\nRoyaume-Uni", transport: 258, stay: 355, costByNight: 36, costByKm: 24},
        {name: "2018\nSingapour\nVietnam\nTaïwan", transport: 1020, stay: 622, costByNight: 41, costByKm: 8},
        {name: "2018\nVietnam", transport: 753, stay: 294, costByNight: 49, costByKm: 8},
        {name: "2019\nCanada", transport: 1074, stay: 342, costByNight: 38, costByKm: 13},
        {name: "2019\nLorient\nGroix", transport: 77, stay: 190, costByNight: 27, costByKm: 20}
      ];

      chart.padding(20, 5, 2, 5);

      // Cities/countries names
      let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
      categoryAxis.renderer.grid.template.location = 0;
      categoryAxis.dataFields.category = "name";
      categoryAxis.renderer.ticks.template.disabled = false;
      categoryAxis.renderer.minGridDistance = 1;
      categoryAxis.renderer.labels.template.wrap = true;
      categoryAxis.renderer.labels.template.maxWidth = 100;
      categoryAxis.renderer.labels.template.fontSize = ".75em";
      categoryAxis.renderer.labels.template.textAlign = "middle";

      /* FIRST CHART */

      // First Y axis
      let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
      valueAxis.tooltip.disabled = true;
      valueAxis.zIndex = 1;
      valueAxis.renderer.baseGrid.disabled = true;
      valueAxis.renderer.fontSize = "0.8em";

      // Transport
      let series = chart.series.push(new am4charts.ColumnSeries());
      series.name = "Transport";
      series.dataFields.valueY = "transport";
      series.dataFields.categoryX = "name";
      series.yAxis = valueAxis;
      // Configure columns
      series.columns.template.width = am4core.percent(100);
      series.columns.template.tooltipText = "[font-size:13px]Transport : {valueY}";
      series.columns.template.fillOpacity = .8;

      // Logement
      series = chart.series.push(new am4charts.ColumnSeries());
      series.name = "Logement";
      series.dataFields.valueY = "stay";
      series.dataFields.categoryX = "name";
      series.yAxis = valueAxis;
      // Make it stacked
      series.stacked = true;
      // Configure columns
      series.columns.template.width = am4core.percent(100);
      series.columns.template.tooltipText = "[font-size:13px]Logement : {valueY}";
      series.columns.template.fillOpacity = .8;

      /* SECOND CHART */

      let valueAxis2 = chart.yAxes.push(new am4charts.ValueAxis());
      valueAxis2.marginTop = 50;
      valueAxis2.tooltip.disabled = true;
      valueAxis2.renderer.baseGrid.disabled = true;
      valueAxis2.zIndex = 3;
      valueAxis2.renderer.fontSize = "0.8em";

      series = chart.series.push(new am4charts.ColumnSeries());
      series.name = "Prix par 200km";
      series.dataFields.valueY = "costByKm";
      series.dataFields.categoryX = "name";
      series.yAxis = valueAxis2;
      series.stacked = true;
      // Configure columns
      series.columns.template.width = am4core.percent(40);
      series.columns.template.tooltipText = "[font-size:13px]Prix pour 200km : {valueY}";
      series.columns.template.fillOpacity = .8;

      series = chart.series.push(new am4charts.ColumnSeries());
      series.name = "Prix par nuitée";
      series.dataFields.valueY = "costByNight";
      series.dataFields.categoryX = "name";
      series.yAxis = valueAxis2;
      // Configure columns
      series.columns.template.width = am4core.percent(40);
      series.columns.template.tooltipText = "[font-size:13px]Prix par nuit : {valueY}";
      series.columns.template.fillOpacity = .8;
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

#chartdiv {
  width: 100%;
  height: 350px;
}
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>
<script src="//www.amcharts.com/lib/4/themes/animated.js"></script>
<div id="chartdiv"></div>

What I would like to do and can't find out how is: - Make the top columns getting the width of the two corresponding bottom columns (like some kind of colspan) - Make the bottom series opposite, to have some kind of symetry (I tried opposite = true, but it made everything break) - Have the common x Axis between the two charts

The expected result is this: Expected result

Could you help to achieve at least one of those goals? Thank you!


Solution

  • You can get two out of the three with the stacked value axis approach you're using.

    Column series will always reserve space for other columns, regardless of whether there is a value present or not, so you can't force the top chart's column to fully expand. A workaround for this is to create a second, invisible category axis (disabling labels and grids) and assign the bottom series' xAxis to the second category axis:

    let categoryAxis2 = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis2.renderer.grid.template.location = 0;
    categoryAxis2.dataFields.category = "name";
    categoryAxis2.renderer.ticks.template.disabled = true;
    categoryAxis2.renderer.minGridDistance = 1;
    categoryAxis2.renderer.labels.template.disabled = true;
    
    // ...
    series.xAxis = categoryAxis2; //repeat for both bottom chart's series
    // ...
    

    This will make the top chart's column expand to the full width as the other columns are associated with a different axis entirely.

    To reverse the bottom chart to stop from the top, set inversed to true in the value axis' renderer object:

    valueAxis2.renderer.inversed = true;
    

    The category axis cannot be placed in the middle, however.

    Demo below:

    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.leftAxesContainer.layout = "vertical";
    chart.numberFormatter.numberFormat = '# €';
    
    chart.data = [
      {name: "2011\nLorient", transport: 56, stay: 200, costByNight: 29, costByKm: 14},
      {name: "2015\nPoitiers\nLa Rochelle", transport: 96, stay: 54, costByNight: 9, costByKm: 23},
      {name: "2016\nRoyaume-Uni", transport: 160, stay: 332, costByNight: 47, costByKm: 62},
      {name: "2016\nBiarritz", transport: 185, stay: 516, costByNight: 74, costByKm: 27},
      {name: "2017\nRoyaume-Uni", transport: 258, stay: 355, costByNight: 36, costByKm: 24},
      {name: "2018\nSingapour\nVietnam\nTaïwan", transport: 1020, stay: 622, costByNight: 41, costByKm: 8},
      {name: "2018\nVietnam", transport: 753, stay: 294, costByNight: 49, costByKm: 8},
      {name: "2019\nCanada", transport: 1074, stay: 342, costByNight: 38, costByKm: 13},
      {name: "2019\nLorient\nGroix", transport: 77, stay: 190, costByNight: 27, costByKm: 20}
    ];
    
    chart.padding(20, 5, 2, 5);
    
    // Cities/countries names
    let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.dataFields.category = "name";
    categoryAxis.renderer.ticks.template.disabled = false;
    categoryAxis.renderer.minGridDistance = 1;
    categoryAxis.renderer.labels.template.wrap = true;
    categoryAxis.renderer.labels.template.maxWidth = 100;
    categoryAxis.renderer.labels.template.fontSize = ".75em";
    categoryAxis.renderer.labels.template.textAlign = "middle";
    
    /* FIRST CHART */
    
    // First Y axis
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.tooltip.disabled = true;
    valueAxis.zIndex = 1;
    valueAxis.renderer.baseGrid.disabled = true;
    valueAxis.renderer.fontSize = "0.8em";
    
    // Transport
    let series = chart.series.push(new am4charts.ColumnSeries());
    series.name = "Transport";
    series.dataFields.valueY = "transport";
    series.dataFields.categoryX = "name";
    series.yAxis = valueAxis;
    // Configure columns
    series.columns.template.width = am4core.percent(100);
    series.columns.template.tooltipText = "[font-size:13px]Transport : {valueY}";
    series.columns.template.fillOpacity = .8;
    
    // Logement
    series = chart.series.push(new am4charts.ColumnSeries());
    series.name = "Logement";
    series.dataFields.valueY = "stay";
    series.dataFields.categoryX = "name";
    series.yAxis = valueAxis;
    // Make it stacked
    series.stacked = true;
    // Configure columns
    series.columns.template.width = am4core.percent(100);
    series.columns.template.tooltipText = "[font-size:13px]Logement : {valueY}";
    series.columns.template.fillOpacity = .8;
    
    /* SECOND CHART */
    
    let valueAxis2 = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis2.marginTop = 50;
    valueAxis2.renderer.inversed = true;
    valueAxis2.tooltip.disabled = true;
    valueAxis2.renderer.baseGrid.disabled = true;
    valueAxis2.zIndex = 3;
    valueAxis2.renderer.fontSize = "0.8em";
    
    let categoryAxis2 = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis2.renderer.grid.template.location = 0;
    categoryAxis2.dataFields.category = "name";
    categoryAxis2.renderer.ticks.template.disabled = true;
    categoryAxis2.renderer.minGridDistance = 1;
    categoryAxis2.renderer.labels.template.disabled = true;
    
    
    series = chart.series.push(new am4charts.ColumnSeries());
    series.name = "Prix par 200km";
    series.dataFields.valueY = "costByKm";
    series.dataFields.categoryX = "name";
    series.yAxis = valueAxis2;
    series.stacked = true;
    // Configure columns
    series.columns.template.width = am4core.percent(40);
    series.columns.template.tooltipText = "[font-size:13px]Prix pour 200km : {valueY}";
    series.columns.template.fillOpacity = .8;
    series.xAxis = categoryAxis2;
    
    series = chart.series.push(new am4charts.ColumnSeries());
    series.name = "Prix par nuitée";
    series.dataFields.valueY = "costByNight";
    series.dataFields.categoryX = "name";
    series.yAxis = valueAxis2;
    series.xAxis = categoryAxis2;
    // Configure columns
    series.columns.template.width = am4core.percent(40);
    series.columns.template.tooltipText = "[font-size:13px]Prix par nuit : {valueY}";
    series.columns.template.fillOpacity = .8;
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    }
    
    #chartdiv {
      width: 100%;
      height: 350px;
    }
    <script src="//www.amcharts.com/lib/4/core.js"></script>
    <script src="//www.amcharts.com/lib/4/charts.js"></script>
    <script src="//www.amcharts.com/lib/4/themes/animated.js"></script>
    <div id="chartdiv"></div>