Chart Legend in an External Container

This tutorial will show you how you can easily move your chart legend outside main container into a totally separate one.

NOTE We're going to be using Pie chart as an example in this tutorial. However, this applies to any other chart type that uses Legend.

Base chart

Let's start with a basic Pie chart with a Legend:

See the Pen amCharts 4: Legend in external div (1) by amCharts (@amcharts) on CodePen.24419

We've done little else, just created a Pie chart with a single series, then added a Legend.

As you can see the legend takes up the majority of chart area, making our Pie very small.

Creating a separate container for Legend

We could increase the height of the chart container. That's one way to go about it.

Another is to move the legend to its own container, that is completely independent from the main chart. As a consequence, it can also be styled and sized, independently.

Let's go ahead and create a separate div for the legend, next to the one we have for the chart:

<style>
#chartdiv, #legenddiv {
  width: 100%;
  height: 200px;
  border: 1px dotted #c99;
  margin: 1em 0;
}

#legenddiv {
  height: 150px;
}
</style>

Chart:
<div id="chartdiv"></div>

Legend:
<div id="legenddiv"></div>

That will create a separate <div>. Now, all we have to do is to create a new Container instance in it.

We create a new container just like we create a chart, using am4core.create() function. (or am4core.createFromConfig() if we are using JSON-based config)

let legendContainer = am4core.create("legenddiv", am4core.Container);
legendContainer.width = am4core.percent(100);
legendContainer.height = am4core.percent(100);
var legendContainer = am4core.create("legenddiv", am4core.Container);
legendContainer.width = am4core.percent(100);
legendContainer.height = am4core.percent(100);
var legendContainer = am4core.createFromConfig({
  "width": "100%",
  "height": "100%"
}, "legenddiv", am4core.Container);

IMPORTANT Plain Container, unlike charts, does not set its own dimensions automatically. We need to set them manually. In the above example with set them to take up the whole area of our "legenddiv".

Moving the Legend

Now, all we have left to do is to move our Legend to the new container.

This is super easy, just set Legend's parent to a reference to our Legend container:

chart.legend.parent = legendContainer;
chart.legend.parent = legendContainer;
var legendContainer = am4core.createFromConfig({
  "width": "100%",
  "height": "100%"
}, "legenddiv", am4core.Container);

var chart = am4core.createFromConfig({
  // ...
  "legend": {
    "parent": legendContainer
  }
}, "chartdiv", am4charts.PieChart);

Let's see if it worked:

See the Pen amCharts 4: Legend in external div (2) by amCharts (@amcharts) on CodePen.24419

Yup, it did!

Sizing the legend

So far we had the <div> that houses legend set at a fixed height. This means that, depending on a number of legend items, and available width, some items might not fit, or there might be some white space left.

Let's look at some ways to fix that.

Dynamically resizing legend div

Or task here is simple - once chart initializes, we want to size the legend <div> to fit actual height of the legend perfectly.

For that we will be using two events:

  • datavalidated - for when chart initially loads data, or for events if data was changed dynamically;
  • maxsizechanged - for when the chart resizes, e.g. when window is resized and our legend arrangement might have changed.

Those events will trigger a function, that will check legend's special property contentHeight which returns actual height of all the items in legend, including those items that do not fit, and will dynamically update legend's <div>'s style.height.

Since data can be updated in a number of ways, and legend is in different <div>, we're going to be adding those events to both chart and the legend itself.

Let's whip it all up together:

chart.events.on("datavalidated", resizeLegend);
chart.events.on("maxsizechanged", resizeLegend);

chart.legend.events.on("datavalidated", resizeLegend);
chart.legend.events.on("maxsizechanged", resizeLegend);

function resizeLegend(ev) {
  document.getElementById("legenddiv").style.height = chart.legend.contentHeight + "px";
}
chart.events.on("datavalidated", resizeLegend);
chart.events.on("maxsizechanged", resizeLegend);

chart.legend.events.on("datavalidated", resizeLegend);
chart.legend.events.on("maxsizechanged", resizeLegend);

function resizeLegend(ev) {
  document.getElementById("legenddiv").style.height = chart.legend.contentHeight + "px";
}
var chart = am4core.createFromConfig({
  // ...
  "legend": {
    "events": {
      "datavalidated": resizeLegend,
      "maxsizechanged": resizeLegend
    }
  },
  "events": {
    "datavalidated": resizeLegend,
    "maxsizechanged": resizeLegend
  }
}, "chartdiv", am4charts.PieChart);

function resizeLegend(ev) {
  document.getElementById("legenddiv").style.height = chart.legend.contentHeight + "px";
}

See the Pen amCharts 4: Legend in external div (3) by amCharts (@amcharts) on CodePen.24419

Now, you should see the legend resize to fit its contents perfectly, even if you resize the demo window.

Making legend scrollable

Resizing the legend is fine. However, we might not want to grow past some point if we have a lot of items.

Let's try and make our legend scrollable instead.

The natural instinct would be to plop something like this CSS on legend's <div> and call it a day:

#legenddiv {
  max-height: 150px;
  overflow: auto;
}

That, unfortunately, won't work.

There are two ways to make our legend scrollable: one which involves some HTML and CSS trickery; the other using built-in amCharts features.

Scrollable legend via HTML and CSS

The Container element, that we "outsource" our Legend to, will always try to size itself based on the parent <div> which will result in legend always being capped at 150 pixels height, and thus not overflow directive not kicking in.

What we need to do is to wrap our legend <div> into another div (let's give it an id of "legendwrapper"), that has the above CSS.

The inner element will always size itself as we set it up to in previous section, while outer element (wrapper) will keep it constrained to certain height.

Let's try it.

<style>
#chartdiv, #legendwrapper {
  width: 100%;
  height: 200px;
  border: 1px dotted #c99;
  margin: 1em 0;
}

#legenddiv {
  height: 150px;
}

#legendwrapper {
  max-height: 120px;
  overflow-x: none;
  overflow-y: auto;
}
</style>

Chart:
<div id="chartdiv"></div>

Legend:
<div id="legendwrapper">
  <div id="legenddiv"></div>
</div>

Let's see if that worked:

See the Pen amCharts 4: Legend in external div (4) by amCharts (@amcharts) on CodePen.24419

Scrollable legend via settings

Another, probably easier, option is to use Legend's scrollable option.

Basically, setting this option to true will fix the legend height at certain number of pixels. If it fits it fits. If it doesn't a built-in scrollbar will appear.

chart.legend.scrollable = true;
chart.legend.scrollable = true;
{
  // ...
  "legend": {
    // ...
    "scrollable": true
  }
}

Using this method we don't have to care about sizing the legend container dynamically.

See the Pen amCharts 4: Legend in external div (scrollable) by amCharts team (@amcharts) on CodePen.24419

NOTE scrollable setting supports only vertical scrolling.

Including external legend in export

Since 4.2.0 it's possible to include extra elements when exporting chart to images. That means we can include our external legend as well:

chart.exporting.menu = new am4core.ExportMenu();
chart.exporting.extraSprites.push({
  "sprite": legendContainer,
  "position": "bottom",
  "marginTop": 20
});
chart.exporting.menu = new am4core.ExportMenu();
chart.exporting.extraSprites.push({
  "sprite": legendContainer,
  "position": "bottom",
  "marginTop": 20
});
var chart = am4core.createFromConfig({
  // ...
  "exporting": {
    "menu": "ExportMenu"
  }
}, "chartdiv", am4charts.PieChart);

// ...

chart.exporting.extraSprites.push({
  "sprite": legendContainer,
  "position": "bottom",  
  "marginTop": 20
});

Try it out live:

See the Pen amCharts 4: Legend in external div (5) by amCharts team (@amcharts) on CodePen.0

Disposing external legend

IMPORTANT External legend is a separate instance that needs to be disposed as separately, as it won't get disposed together with the chart.

That means calling dispose() on both chart and the Container instance that holds your legend.

chart.legend.dispose();
chart.legend.dispose();