Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js Get Legend Outside of Chart

I am trying to get the legend of my chart outside of the charting area.

Here are the margins:

var margin = {top: 50, right: 200, bottom: 50, left: 40};
        var width = 960 - margin.left - margin.right,
            height = 500 - margin.top - margin.bottom;

First I create the svg:

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 + ")");

So, from my understanding, I now have the svg canvas element and a g inside of it which holds the chart. I am trying to add more to the right margin, so I can get some space between the svg canvas and the g appended to it, which holds the chart. Then I want to put my legend in that empty space.

Here is where I add my legend:

//add legend
            var legend = svg.append("g")
                .attr("class", "legend")
                .attr("height", 300)
                .attr("width", 200)
                .attr("transform", "translate(-1000,50");

Even though I am appending to the SVG element, it is appending to the g within the svg element. So, no matter how much I translate it or try to get it to go more right on the screen, it never goes past the width of the inner g.

When troubleshooting, I see that the outer SVG element has a height of 960 and width of 500. The g appended to that has a transform/translate of 40,50. The width ends up being 839px by 433.223px (not sure I understand this). The outer svg has a bunch of space to the right now because of the margin built in.

So I'm trying to either increase the width of the g appended to the svg so I can put my legend as a child of the g and move it to the empty space created by the margin. Or, I'm trying to create another g that is a sibling to the first g and then I can use the empty space created by the margin.

I can't get either to work and don't know which way is best.

like image 771
strahanstoothgap Avatar asked Feb 26 '16 20:02

strahanstoothgap


1 Answers

Notice that the var svg is being assigned to a <g> nested inside the <svg>

svg = d3.select("body").append("svg")
        .attr("width",  width  + margin.left + margin.right)
        .attr("height", height + margin.top  + margin.bottom)
        .append("g") // <-- This is what svg is currently being assigned to
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

And so, when you later execute var legend = svg.append("g"), you're actually appending the legend as a child of the aforementioned <g>. And that's what you described seeing in the dev tools.

One implication is that the translate() transform you applied to the outer <g> affects the inner <g> (i.e. the translation of the innter <g> of legend is added to that of the outer <g>).

Likely, you want split things up like so:

var svg = d3.select("body").append("svg")
            .attr("width",  width  + margin.left + margin.right)
            .attr("height", height + margin.top  + margin.bottom);

var inner = svg.append("g")
              .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Then change your code to draw the existing chart into inner rather than svg.

As a result, var legend = svg.append("g") will append legend as a sibling of inner, and any translation you apply to legend would be relative to the svg's top left (as opposed to inner's top left, which is translated by margin)

And likely you want to translate legend like so:

var legend = svg.append("g")
               .attr("transform", "translate(" + width - margin.right + "," + margin.top + ")");

That moves the legend to the right end of the chart, MINUS margin.right. That way, you can tweak margin.right to create enough room for legend.

Finally, note that calling

legend
  .attr("height", 300)
  .attr("width", 200)

doesn't do anything, because for svg <g>, there isn't a way to explicitly set the width and height. Those wouldn't mean much anyway, because svg doesn't have a the "flow" behavior of html layouts. The width and height shown in dev tools are the implicit bounds resulting from the bounds of the children of the <g>. (If needed, there's a way to get those computed bounds in javascript, using the getBBox() function).

like image 113
meetamit Avatar answered Sep 24 '22 23:09

meetamit