Skip to content

Instantly share code, notes, and snippets.

@ZJONSSON
Forked from mbostock/.block
Created October 19, 2012 13:59
Show Gist options
  • Save ZJONSSON/3918369 to your computer and use it in GitHub Desktop.
Save ZJONSSON/3918369 to your computer and use it in GitHub Desktop.
d3.legend example

d3.legend

d3.legend is a quick hack to add a legend to a d3 chart. Simply add a g and .call(d3.legend). Any elements that have a title set in the "data-legend" attribute will be included when d3.legend is called. Each title will appear only once (even when multiple items define the same data-legend) as the process uses a set based on a existing names, not an array of all items.

Color

By default the color in the legend will try to match the fill attribute or the stroke attribute of the relevant items. Color can be explicitly defined by attribute "data-legend-color"

Order

The order of items in the legend will be sorted using the top of the bounding box for each included item. The order can be explicitly defined by attribute "data-legend-pos"

Padding

Padding will be determined by attribute "data-style-padding" on the legend element. Defaults to 5.

Size

Size of the box is determined by font size, as items are placed using "em" and the frame around the items is based on the bounding box.

This Example

This example takes an existing Gist and adds a legend by defining data-legend for each series and calling d3.legend on a "g" element. To show the font-adjustment I change font-size after one second and call d3.legend again.

d3.legend

d3.legend is a quick hack to add a legend to a d3.graph. Simply add a g and .call(d3.legend). Any elements that has a title set in the "data-legend" attribute set will be included, when d3.legend is called. Each title will appear only once as the process uses a set based on a existing names, not an array of all items.

Color

By default the color in the legend will try to match the fill attribute or the stroke attribute of the relevant items. Color can be explicitly defined by attribute "data-legend-color"

Order

The order of items in the legend will be sorted using the top of the bounding box for each included item. The order can be explicitly defined by attribute "data-legend-pos"

Padding

Padding will be determined by attribute "data-style-padding" on the legend element. Defaults to 5.

Size

Size of the box is determined by font size, as items are placed using "em" and the frame around the items is based on the bounding box.

This Example

This example takes an existing Gist and adds a legend by defining data-legend for each series:

 .attr("data-legend",function(d) { return d.name})

Adding the legend is as simple as:

legend = svg.append("g")
  .attr("class","legend")
  .attr("transform","translate(50,30)")
  .style("font-size","12px")
  .call(d3.legend)

To show the font-adjustment I change font-size after one second and call d3.legend again.

##Comments on the original gist This line chart is constructed from a TSV file storing the daily average temperatures of New York, San Francisco and Austin over the last year. The chart employs conventional margins and a number of D3 features:

// d3.legend.js
// (C) 2012 [email protected]
// MIT licence
(function() {
d3.legend = function(g) {
g.each(function() {
var g= d3.select(this),
items = {},
svg = d3.select(g.property("nearestViewportElement")),
legendPadding = g.attr("data-style-padding") || 5,
lb = g.selectAll(".legend-box").data([true]),
li = g.selectAll(".legend-items").data([true])
lb.enter().append("rect").classed("legend-box",true)
li.enter().append("g").classed("legend-items",true)
svg.selectAll("[data-legend]").each(function() {
var self = d3.select(this)
items[self.attr("data-legend")] = {
pos : self.attr("data-legend-pos") || this.getBBox().y,
color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke")
}
})
items = d3.entries(items).sort(function(a,b) { return a.value.pos-b.value.pos})
li.selectAll("text")
.data(items,function(d) { return d.key})
.call(function(d) { d.enter().append("text")})
.call(function(d) { d.exit().remove()})
.attr("y",function(d,i) { return i+"em"})
.attr("x","1em")
.text(function(d) { ;return d.key})
li.selectAll("circle")
.data(items,function(d) { return d.key})
.call(function(d) { d.enter().append("circle")})
.call(function(d) { d.exit().remove()})
.attr("cy",function(d,i) { return i-0.25+"em"})
.attr("cx",0)
.attr("r","0.4em")
.style("fill",function(d) { console.log(d.value.color);return d.value.color})
// Reposition and resize the box
var lbbox = li[0][0].getBBox()
lb.attr("x",(lbbox.x-legendPadding))
.attr("y",(lbbox.y-legendPadding))
.attr("height",(lbbox.height+2*legendPadding))
.attr("width",(lbbox.width+2*legendPadding))
})
return g
}
})()
date New York San Francisco Austin
20111001 63.4 62.7 72.2
20111002 58.0 59.9 67.7
20111003 53.3 59.1 69.4
20111004 55.7 58.8 68.0
20111005 64.2 58.7 72.4
20111006 58.8 57.0 77.0
20111007 57.9 56.7 82.3
20111008 61.8 56.8 78.9
20111009 69.3 56.7 68.8
20111010 71.2 60.1 68.7
20111011 68.7 61.1 70.3
20111012 61.8 61.5 75.3
20111013 63.0 64.3 76.6
20111014 66.9 67.1 66.6
20111015 61.7 64.6 68.0
20111016 61.8 61.6 70.6
20111017 62.8 61.1 71.1
20111018 60.8 59.2 70.0
20111019 62.1 58.9 61.6
20111020 65.1 57.2 57.4
20111021 55.6 56.4 64.3
20111022 54.4 60.7 72.4
20111023 54.4 65.1 72.4
20111024 54.8 60.9 72.5
20111025 57.9 56.1 72.7
20111026 54.6 54.6 73.4
20111027 54.4 56.1 70.7
20111028 42.5 58.1 56.8
20111029 40.9 57.5 51.0
20111030 38.6 57.7 54.9
20111031 44.2 55.1 58.8
20111101 49.6 57.9 62.6
20111102 47.2 64.6 71.0
20111103 50.1 56.2 58.4
20111104 50.1 50.5 45.1
20111105 43.5 51.3 52.2
20111106 43.8 52.6 73.0
20111107 48.9 51.4 75.4
20111108 55.5 50.6 72.1
20111109 53.7 54.6 56.6
20111110 57.7 55.6 55.4
20111111 48.5 53.9 46.7
20111112 46.8 54.0 62.0
20111113 51.1 53.8 71.6
20111114 56.8 53.5 75.5
20111115 59.7 53.4 72.1
20111116 56.5 52.2 65.7
20111117 49.6 52.7 56.8
20111118 41.5 53.1 49.9
20111119 44.3 49.0 71.7
20111120 54.0 50.4 77.7
20111121 54.1 51.1 76.4
20111122 49.4 52.3 68.8
20111123 50.0 54.6 57.0
20111124 44.0 55.1 55.5
20111125 50.3 51.5 61.6
20111126 52.1 53.6 64.1
20111127 49.6 52.3 51.1
20111128 57.2 51.0 43.0
20111129 59.1 49.5 46.4
20111130 50.6 49.8 48.0
20111201 44.3 60.4 48.1
20111202 43.9 62.2 60.6
20111203 42.1 58.3 62.6
20111204 43.9 52.7 57.1
20111205 50.2 51.5 44.2
20111206 54.2 49.9 37.4
20111207 54.6 48.6 35.0
20111208 43.4 46.4 37.0
20111209 42.2 49.8 45.4
20111210 45.0 52.1 50.7
20111211 33.8 48.8 48.6
20111212 36.8 47.4 52.2
20111213 38.6 47.2 60.8
20111214 41.9 46.1 70.0
20111215 49.6 48.8 64.2
20111216 50.2 47.9 50.9
20111217 40.6 49.8 51.6
20111218 29.1 49.1 55.2
20111219 33.7 48.3 62.1
20111220 45.8 49.3 56.3
20111221 47.4 48.4 47.2
20111222 54.4 53.3 52.3
20111223 47.8 47.5 45.2
20111224 34.9 47.9 43.6
20111225 35.9 48.9 42.9
20111226 43.6 45.9 48.2
20111227 42.9 47.2 45.4
20111228 46.2 48.9 44.2
20111229 30.8 50.9 50.4
20111230 40.8 52.9 52.4
20111231 49.8 50.1 53.5
20120101 46.3 53.9 55.9
20120102 43.2 53.1 48.2
20120103 30.3 49.7 41.0
20120104 19.2 52.7 48.9
20120105 32.1 52.6 54.8
20120106 41.2 49.0 61.2
20120107 47.0 51.0 59.7
20120108 46.0 56.8 52.5
20120109 34.7 52.3 54.0
20120110 39.4 51.6 47.7
20120111 40.4 49.8 49.2
20120112 45.4 51.9 48.4
20120113 40.7 53.7 40.2
20120114 30.4 52.9 43.9
20120115 23.9 49.7 45.2
20120116 22.6 45.3 65.0
20120117 39.8 43.6 68.2
20120118 43.2 45.0 47.5
20120119 26.3 47.3 57.1
20120120 32.8 51.4 61.9
20120121 27.4 53.7 54.6
20120122 25.0 48.3 56.7
20120123 39.4 52.9 54.4
20120124 48.7 49.1 52.7
20120125 43.0 52.1 61.8
20120126 37.1 53.6 55.0
20120127 48.2 50.4 50.7
20120128 43.7 50.3 52.9
20120129 40.1 53.8 44.4
20120130 38.0 51.9 49.1
20120131 43.5 50.0 62.8
20120201 50.4 50.0 64.6
20120202 45.8 51.3 61.1
20120203 37.5 51.5 70.0
20120204 40.8 52.0 61.3
20120205 36.5 53.8 48.2
20120206 39.1 54.6 44.2
20120207 43.2 54.3 51.3
20120208 36.5 51.9 49.2
20120209 36.5 53.8 45.7
20120210 38.3 53.9 54.1
20120211 36.9 52.3 44.9
20120212 29.7 50.1 36.5
20120213 33.1 49.5 44.8
20120214 39.6 48.6 52.3
20120215 42.3 49.9 68.0
20120216 39.7 52.4 54.6
20120217 46.0 49.9 53.8
20120218 41.2 51.6 56.2
20120219 39.8 47.8 50.8
20120220 38.1 48.7 53.0
20120221 37.1 49.7 61.0
20120222 45.5 53.4 68.8
20120223 50.6 54.1 69.4
20120224 42.7 55.9 59.3
20120225 42.6 51.7 47.2
20120226 36.9 47.7 47.7
20120227 40.9 45.4 61.9
20120228 45.9 47.0 67.2
20120229 40.7 49.8 70.1
20120301 41.3 48.9 62.1
20120302 36.8 48.1 72.7
20120303 47.6 50.7 59.0
20120304 44.2 55.0 51.8
20120305 38.5 48.8 55.0
20120306 32.9 48.4 61.8
20120307 43.3 49.9 67.1
20120308 51.2 49.2 72.0
20120309 47.8 51.7 46.4
20120310 37.2 49.3 46.7
20120311 42.9 50.0 56.9
20120312 48.8 48.6 61.9
20120313 52.6 53.9 68.8
20120314 60.5 55.2 71.9
20120315 47.2 55.9 72.0
20120316 44.7 54.6 72.5
20120317 48.2 48.2 71.7
20120318 48.2 47.1 71.1
20120319 53.1 45.8 73.0
20120320 57.8 49.7 63.8
20120321 57.5 51.4 60.0
20120322 57.3 51.4 62.3
20120323 61.7 48.4 61.1
20120324 55.8 49.0 62.0
20120325 48.4 46.4 64.6
20120326 49.8 49.7 66.0
20120327 39.6 54.1 65.8
20120328 49.7 54.6 69.2
20120329 56.8 52.3 69.5
20120330 46.5 54.5 73.5
20120331 42.2 56.2 73.9
20120401 45.3 51.1 75.3
20120402 48.1 50.5 75.4
20120403 51.2 52.2 77.3
20120404 61.0 50.6 67.0
20120405 50.7 47.9 71.1
20120406 48.0 47.4 70.4
20120407 51.1 49.4 73.6
20120408 55.7 50.0 71.1
20120409 58.3 51.3 70.0
20120410 55.0 53.8 69.0
20120411 49.0 52.9 69.2
20120412 51.7 53.9 74.5
20120413 53.1 50.2 73.4
20120414 55.2 50.9 76.0
20120415 62.3 51.5 74.5
20120416 62.9 51.9 63.6
20120417 69.3 53.2 67.3
20120418 59.0 53.0 65.1
20120419 54.1 55.1 67.9
20120420 56.5 55.8 68.9
20120421 58.2 58.0 65.1
20120422 52.4 52.8 65.4
20120423 51.6 55.1 70.1
20120424 49.3 57.9 67.0
20120425 52.5 57.5 75.4
20120426 50.5 55.3 77.5
20120427 51.9 53.5 77.0
20120428 47.4 54.7 77.7
20120429 54.1 54.0 77.7
20120430 51.9 53.4 77.7
20120501 57.4 52.7 77.0
20120502 53.7 50.7 77.9
20120503 53.1 52.6 79.1
20120504 57.2 53.4 80.1
20120505 57.0 53.1 82.1
20120506 56.6 56.5 79.0
20120507 54.6 55.3 79.8
20120508 57.9 52.0 70.0
20120509 59.2 52.4 69.8
20120510 61.1 53.4 71.3
20120511 59.7 53.1 69.4
20120512 64.1 49.9 72.0
20120513 65.3 52.0 72.4
20120514 64.2 56.0 72.5
20120515 62.0 53.0 67.6
20120516 63.8 51.0 69.0
20120517 64.5 51.4 72.7
20120518 61.0 52.2 73.7
20120519 62.6 52.4 77.5
20120520 66.2 54.5 75.8
20120521 62.7 52.8 76.9
20120522 63.7 53.9 78.8
20120523 66.4 56.5 77.7
20120524 64.5 54.7 80.6
20120525 65.4 52.5 81.4
20120526 69.4 52.1 82.3
20120527 71.9 52.2 80.3
20120528 74.4 52.9 80.3
20120529 75.9 52.1 82.2
20120530 72.9 52.1 81.9
20120531 72.5 53.3 82.4
20120601 67.2 54.8 77.9
20120602 68.3 54.0 81.1
20120603 67.7 52.3 82.2
20120604 61.9 55.3 81.2
20120605 58.3 53.5 83.0
20120606 61.7 54.1 83.2
20120607 66.7 53.9 82.1
20120608 68.7 54.4 77.5
20120609 72.2 55.0 77.9
20120610 72.6 60.0 82.9
20120611 69.2 57.2 86.8
20120612 66.9 55.1 85.3
20120613 66.7 53.3 76.9
20120614 67.7 53.4 84.5
20120615 68.5 54.6 84.4
20120616 67.5 57.0 83.8
20120617 64.2 55.6 82.5
20120618 61.7 52.5 82.9
20120619 66.4 53.9 82.5
20120620 77.9 55.3 81.3
20120621 88.3 53.3 80.8
20120622 82.2 54.1 81.7
20120623 77.0 55.2 83.9
20120624 75.4 55.8 85.5
20120625 70.9 56.8 87.2
20120626 65.9 57.5 88.0
20120627 73.5 57.7 89.6
20120628 77.4 56.6 86.7
20120629 79.6 56.4 85.3
20120630 84.2 58.4 81.7
20120701 81.8 58.8 78.5
20120702 82.5 56.4 83.1
20120703 80.2 56.5 83.1
20120704 77.8 55.8 84.5
20120705 86.1 54.8 84.6
20120706 79.9 54.9 84.2
20120707 83.5 54.7 86.7
20120708 81.5 52.8 84.3
20120709 77.8 53.7 83.7
20120710 76.1 53.1 77.1
20120711 76.3 52.7 77.4
20120712 75.8 52.0 80.6
20120713 77.2 53.4 81.4
20120714 79.3 54.0 80.2
20120715 78.9 54.0 81.8
20120716 79.6 54.5 77.3
20120717 83.3 56.7 80.8
20120718 84.3 57.5 81.6
20120719 75.1 57.1 80.9
20120720 68.4 58.1 83.9
20120721 68.4 57.6 85.6
20120722 72.2 56.0 83.6
20120723 75.6 56.6 84.0
20120724 82.6 57.8 83.0
20120725 78.4 57.5 84.8
20120726 77.0 56.4 84.4
20120727 79.4 55.3 84.3
20120728 77.4 55.0 83.9
20120729 72.5 55.6 85.0
20120730 72.9 55.6 84.9
20120731 73.6 55.9 86.3
20120801 75.0 55.4 86.5
20120802 77.7 54.4 85.8
20120803 79.7 53.7 85.3
20120804 79.6 54.1 86.0
20120805 81.5 57.8 84.2
20120806 80.0 58.2 81.9
20120807 75.7 58.0 86.5
20120808 77.8 57.0 86.1
20120809 78.6 55.0 86.8
20120810 77.8 54.8 88.0
20120811 78.5 53.0 85.1
20120812 78.8 52.5 87.4
20120813 78.6 53.3 88.0
20120814 76.8 53.9 88.0
20120815 76.7 56.2 87.2
20120816 75.9 57.1 86.1
20120817 77.6 55.3 86.8
20120818 72.6 56.2 84.9
20120819 70.4 54.3 76.8
20120820 71.8 53.1 80.6
20120821 73.6 53.4 80.0
20120822 74.7 54.5 78.2
20120823 74.6 55.7 79.1
20120824 76.0 54.8 81.9
20120825 76.2 53.8 84.7
20120826 73.4 56.5 83.5
20120827 74.6 58.3 82.1
20120828 79.4 58.7 84.0
20120829 74.7 57.5 85.7
20120830 73.5 55.9 87.2
20120831 77.9 55.4 82.9
20120901 80.7 55.7 84.8
20120902 75.1 53.1 83.9
20120903 73.5 53.5 85.5
20120904 73.5 52.5 86.4
20120905 77.7 54.5 85.8
20120906 74.2 56.3 85.4
20120907 76.0 56.4 85.3
20120908 77.1 56.5 81.9
20120909 69.7 56.4 74.8
20120910 67.8 55.4 71.6
20120911 64.0 56.2 75.9
20120912 68.1 55.7 82.1
20120913 69.3 54.3 80.5
20120914 70.0 55.2 70.0
20120915 69.3 54.3 71.2
20120916 66.3 52.9 70.3
20120917 67.0 54.8 72.1
20120918 72.8 54.8 73.7
20120919 67.2 56.8 72.7
20120920 62.1 55.4 71.7
20120921 64.0 55.8 72.9
20120922 65.5 55.9 73.1
20120923 65.7 52.8 75.6
20120924 60.4 54.5 78.3
20120925 63.2 53.3 78.3
20120926 68.5 53.6 79.6
20120927 69.2 52.1 76.4
20120928 68.7 52.6 77.2
20120929 62.5 53.9 75.2
20120930 62.3 55.1 71.9
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.legend rect {
fill:white;
stroke:black;
opacity:0.8;}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="d3.legend.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.temperature); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, temperature: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.attr("data-legend",function(d) { return d.name})
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
legend = svg.append("g")
.attr("class","legend")
.attr("transform","translate(50,30)")
.style("font-size","12px")
.call(d3.legend)
setTimeout(function() {
legend
.style("font-size","20px")
.attr("data-style-padding",10)
.call(d3.legend)
},1000)
});
</script>
@gisvold
Copy link

gisvold commented Jun 27, 2013

Say that I would like to add a legend to an indented tree. Do you have any example as to how?

I am using D3 Indented tree example as a starting point.

@amcox
Copy link

amcox commented Aug 10, 2013

Very helpful, thanks.

@roygurijala
Copy link

Very helpful and neat way of doing legend. Loved it. Noticed just one draw back. Can you implement the functionality to show different shapes in legend than just the circles? I have a map which displays different shapes using d3.svg.symbol().type. How can I get the legend to display what each type means. I appreciate your help.

@kielni
Copy link

kielni commented Sep 19, 2014

I added an option to draw a line icon instead of a circle by setting the data-legend-icon attribute to line

See it here: http://bl.ocks.org/kielni/a4347fe16864903fb9be

@wjaspers
Copy link

Please use the index of the series selected to determine the each title. On rare occasions, multiple series may have the same name, and only one series may be rendered in the legend. Pretty simple fix.

svg.selectAll('[data-legend']).each(function (d, i) {
...
// items[self.attr('data-legend')] // overwrites matching series names
// use the index of the element found so all series' are accounted for
items[i] = { name: self.attr('data-legend'), ... }
...
});
...
li.selectAll('text')...text(function(d) {return d.value.name});
...

See: https://gist.github.com/wjaspers/0f85f8b2cf23c3f78129

@louking
Copy link

louking commented Aug 4, 2016

I tried using d3.legend.js with d3 v4 but ran into a problem. So I ported index.html to use d3 v4 to see if the problem was in my code or in d3.legend.js. See https://gist.github.com/louking/6491d18c9cef56011971be76a85cde8e (http://bl.ocks.org/louking/6491d18c9cef56011971be76a85cde8e for running version) - the problem is at line 47 of d3.legend.js Uncaught TypeError: Cannot read property '0' of undefined.

I'm not that familiar with d3 (or the changes from v3) to understand this error -- any ideas?

Also posted at http://stackoverflow.com/questions/38774855/d3-legend-js-with-d3-v4 because it's not clear author is responding to comments here.

thanks

@juliohm
Copy link

juliohm commented Apr 1, 2017

@louking, can you please share the latest version that works with D3 v4?

I believe this gist should become an actual project on GitHub, it is very useful from the screenshots.

@lubodrinka
Copy link

Why you MIT guys are not able without germans write usable manuals? I follow all instructions and nothing happened.
var legenObj =[{name:"drugs"},{name:"no-drugs"}];

svg
.data(legenObj)
.enter()
.append("g")
.attr( "id","legend")
.attr("transform", "translate(20,20)")
.attr("data-legend",
(d=> d.name));

   legend = svg.append("g")
    .attr("class","legend")
    .attr("transform","translate(50,30)")
    .style("font-size","12px")
         .call(d3.legend)   ;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment