Chapter 04Legends

D3 doesn’t have built-in support for legends, however, Susie Lu has developed a module named d3-legend that can be used to easily create legends.  Here user documentation can be found at d3-legend.susielu.com.

The latest version of the module can be imported into a script using the CDN as shown below:

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.6/d3-legend.min.js"></script>

D3-legend provides 3 types of legends: color legend, size legend, and symbol legend.  We can construct legend generators using the following methods:

Color Legend

The example below construct a plot and legend in a svg element.

<svg id="demo1" width="580" height="120"></svg>

The plot consists of 8 circle elements with data joined to them and their fill properties set using a color scale created using scaleLinear.

var colorScale = d3.scaleLinear()
    .domain([0,2500])
    .range(["red", "blue"]);

The color scale is also used to create a legend.  Below we create a color legend using the legendColor generator and the scale functions to set the scale.

var legend = d3.legendColor()
    .scale(colorScale);

We then create a group element, shift it down and to the right, and draw the legend within the group element.

svg.append("g")
    .attr("transform", "translate(500,10)")
    .call(legend);
<style>
    svg { background-color: pink; }
</style>

<svg id="demo1" width="580" height="120"></svg>

<script>
  var svg = d3.select("#demo1");

  /*** Draw X-Axis ***/
  var xScale = d3.scaleLinear()
    .domain([0,9])
    .range([25, 475]);

  var xAxis = d3.axisBottom(xScale);

  svg.append("g")
    .attr("transform", "translate(0,80)")
    .call(xAxis);

  /*** Create color scale ***/
  var colorScale = d3.scaleLinear()
    .domain([0,2500])
    .range(["red", "blue"]);

  /*** Draw data points using color scale ***/

  svg.selectAll("circle")
    .data([300, 580, 900, 1200, 1500, 1800, 2100, 2400])
    .enter()
    .append("circle")
    .attr("cx", (d,i) => xScale(i+1))
    .attr("cy", 50)
    .attr("r", 13)
    .attr("fill", (d) => colorScale(d));

  var legend = d3.legendColor()
    .scale(colorScale);

  svg.append("g")
    .attr("transform", "translate(500,10)")
    .call(legend);

</script>

There are various methods that can be called on a legend function object returned by the d3.legendColor generator.  You can find information on each of these below and at http://d3-legend.susielu.com.

Setting the Number of Symbol/Label Pairs

Below we set the number of symbol/label pairs to 3.

var legend = d3.legendColor()
    .scale(colorScale)
    .cells(3);
<svg id="demo2" width="580" height="120"></svg>

<script>
  var svg = d3.select("#demo2");

  /*** Draw X-Axis ***/
  var xScale = d3.scaleLinear()
    .domain([0,9])
    .range([25, 475]);

  var xAxis = d3.axisBottom(xScale);

  svg.append("g")
    .attr("transform", "translate(0,80)")
    .call(xAxis);

  /*** Create color scale ***/
  var colorScale = d3.scaleLinear()
    .domain([0,2500])
    .range(["red", "blue"]);

  /*** Draw data points using color scale ***/

  svg.selectAll("circle")
    .data([300, 580, 900, 1200, 1500, 1800, 2100, 2400])
    .enter()
    .append("circle")
    .attr("cx", (d,i) => xScale(i+1))
    .attr("cy", 50)
    .attr("r", 13)
    .attr("fill", (d) => colorScale(d));

  /*** Create and draw the legend ***/
  var legend = d3.legendColor()
    .scale(colorScale)
    .cells(3);

  svg.append("g")
    .attr("transform", "translate(500,10)")
    .call(legend);

</script>

Setting Label Text

We can also format the label text and provide a title for the legend using the lableFormat and title methods respectively.  Below we remove the mantissa digits (digits after the decimal point) and add “Legend” as the title of the legend.

var legend = d3.legendColor()
    .scale(colorScale)
    .labelFormat(d3.format(".0f"))
    .title("Legend");
<svg id="demo4" width="580" height="120"></svg>

<script>
  var svg = d3.select("#demo4");

  /*** Draw X-Axis ***/
  var xScale = d3.scaleLinear()
    .domain([0,9])
    .range([25, 475]);

  var xAxis = d3.axisBottom(xScale);

  svg.append("g")
    .attr("transform", "translate(0,80)")
    .call(xAxis);

  /*** Create color scale ***/
  var colorScale = d3.scaleLinear()
    .domain([0,2500])
    .range(["red", "blue"]);

  /*** Draw data points using color scale ***/

  svg.selectAll("circle")
    .data([300, 580, 900, 1200, 1500, 1800, 2100, 2400])
    .enter()
    .append("circle")
    .attr("cx", (d,i) => xScale(i+1))
    .attr("cy", 50)
    .attr("r", 13)
    .attr("fill", (d) => colorScale(d));

  /*** Create and draw the legend ***/
  var legend = d3.legendColor()
    .scale(colorScale)
    .cells(3)
    .labelFormat(d3.format(".0f"))
    .title("Legend");

  svg.append("g")
    .attr("transform", "translate(500,20)")
    .call(legend);

</script>

Using Classes

In the example below we have defined 4 CSS classes in a CSS stylesheet.

.one { fill: red;}
.two { fill: orange;}
.three { fill: green;}
.four { fill: blue;}

We also have a quantize scale that uniformly segments a continuous domain into 4 segments and maps each segment to one of 4 strings (the names of the above classes).

var colorScale = d3.scaleQuantize()
    .domain([0,2500])
    .range(["one", "two", "three", "four"]);

We’re going to use the color scale in two ways.  First to assign classes to circle elements so that their fill properties will be set to an appropriate color.  Second, to determine the number of symbol/label pairs in the legend and to define the fill of each of the symbols.

We can set the class name for each circle drawn by using the each method on the selection of circles after the circles are created.

svg.selectAll("circle")
    .each((d, i, nodes) => {
        nodes[i].classList.add(colorScale(d));
    });

By default the legend creates rect elements and fills them with a color returned by the scale.  In our case, the scale doesn’t return a color, but rather the name of a class.  By calling useClass, each symbol in the legend is assigned one of the class names returned by the scale - thus setting the fill color.

var legend = d3.legendColor()
    .scale(colorScale)
    .useClass(true);
<style>
.one { fill: red;}
.two { fill: orange;}
.three { fill: green;}
.four { fill: blue;}
</style>

<svg id="demo3" width="700" height="120"></svg>

<script>
  var svg = d3.select("#demo3");

  /*** Draw X-Axis ***/
  var xScale = d3.scaleLinear()
    .domain([0,9])
    .range([25, 475]);

  var xAxis = d3.axisBottom(xScale);

  svg.append("g")
    .attr("transform", "translate(0,80)")
    .call(xAxis);

  /*** Create color scale ***/
  var colorScale = d3.scaleQuantize()
    .domain([0,2500])
    .range(["one", "two", "three", "four"]);

  /*** Draw data points using color scale ***/

  svg.selectAll("circle")
    .data([300, 580, 900, 1200, 1500, 1800, 2100, 2400])
    .enter()
    .append("circle")
    .attr("cx", (d,i) => xScale(i+1))
    .attr("cy", 50)
    .attr("r", 13);

  svg.selectAll("circle")
    .each((d, i, nodes) => {
        nodes[i].classList.add(colorScale(d));
  });

  /*** Create and draw the legend ***/
  var legend = d3.legendColor()
    .scale(colorScale)
    .useClass(true);

  svg.append("g")
    .attr("transform", "translate(500,10)")
    .call(legend);

</script>