d3.js Get Legend Outside of Chart - javascript

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.

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).

by looking at the code you provided, you are actually attaching the legend var to your group "g", instead of "svg", var legend = svg.append("g")
in this line you are telling d3 to get your legend variable to "g" which is append to svg, if i understand correctly you should try something like this:
var legend = svg.selectAll(".legend")
.enter().append("g")
to create another group "g" for your legends.
i apologize for my bad english.

Related

Update d3 graph with new data

I am trying to update a d3 graph when I click on a County on a map. Instead of updating the existing chart, a new graph is created each time. Any ideas of how to rectify this? Code is here: https://github.com/careyshan/d3-graphUpgade
Thanks
This is the part of the code that I am having difficulty with:
on("click", function(d,i){
if(document.getElementById('linechart').childNodes===null){
console.log("Working")
dataNew = data.filter(function( obj ) {
return obj.County == d.properties.NAME_TAG;
console.log(data);
});
dataNew.sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return b.date - a.date;
});
for (var i=1; i<dataNew.length;i++){
dataSel.push(dataNew[i]);
}
}else if(document.getElementById('linechart').childNodes!=null){
//console.log("check")
dataNew = data.filter(function( obj ) {
return obj.County == d.properties.NAME_TAG;
});
dataNew.sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return b.date - a.date;
});
for (var i=1; i<dataNew.length;i++){
dataSel.push(dataNew[i]);
}
}
linechart(dataNew);
console.log(dataNew);
});
A new graph is created each time because on every call to the function linechart() you are appending a new graph (an svg element) to the body of the page.
This is the snippet from your code which appends a new graph each time.
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id","linechart")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
If you want to update the same chart, just modify your linechart() function to refer to the same svg element. You can achieve this by declaring a global variable for the svg element and use it in your linechart() method like below:
// If graph is not there, create it
if(!svg) {
// append the svg object to the body of the page
//appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
svg= d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id","linechart")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
}
Note: Declare the svg as a global variable (may be at the starting of the script tag):
var svg;

Set backgroud image from the javascript not working

In my javascript I have my div svg as following:
var svg = d3.select("#"+settings.id).append("svg")
.attr("id", "svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
And then I added
document.getElementById('svg').style.backgroundImage = 'url(myimageUrl)';
But it didnt work
you are not passing a variable to, it looks like myimageurl should contain a string with the image name like "picture.jpg" if so, you may not pass the variable in quotes, because its a string then.
document.getElementById('svg').style.backgroundImage = 'url('+myimageUrl+')';
another interpretion of the question may be :
it depends on where or better when you execute the line that changes the background color.
when you ecexute it before the svg-variable is inserted to the dom it wont find the id of the element, so be sure to run it after that.

Set background for d3.js collabsible tree

I create a collabsible tree like in this example (http://bl.ocks.org/mbostock/4339083). I tried to change the background color of the SVG. Therefor I use a "rect" element before inserting the "g" element:
svg = d3.select("#"+targetDIVName).append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svg_graph")
.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "green") //for example
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
With this method the graph is hidden. Is there any solution to set the "rect" element as background and get the graph visible?
Thank You
SVG elements are drawn in the order in which they are added. So if you want the rect to be in the background, add it as the first element after creating the SVG.
To get a solid background colour, you could alternatively use the viewport-fill attribute, which is not supported by all browsers though.

d3 ignore SVG on selectAll

Still learning d3.js.
I would like to ignore the selection of an SVG panel when using .selectAll("svg").
I am building a visualization comprising four SVG panels. The top SVG panel is used to display header/title information for the visualization.
var svgHeader = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
The next two SVG panels are dynamically created using a range of two numbers representing two years.
var svg = d3.select("body")
.selectAll("svg")
.data(d3.range(2012, 2013))
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", 200)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
The final SVG contains detail information as the user interacts with the visualization.
Problem: I want to exclude the first SVG panel from the .selectAll("svg") which is used to create the two middle panels. I would like to dynamically build SVG panels and have them locate underneath the previously created header SVG.
Is there any way to exclude the header SVG when dynamically creating the middle panels?
I think the best way you should be going about this is taking advantage of classes and adding an appropriate class to the different svgs and then selecting based on the class rather than the svg. This way you know what each of the svgs represent and you can easily reference them.'
var svgHeader = d3.select("body")
.append("svg")
.attr("class", "svgHeader")
.attr("width", width + margin.left + margin.right)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
And then the other two you add a different class name
var svg = d3.select("body")
.selectAll("svg")
.data(d3.range(2012, 2013))
.enter().append("svg")
.attr("class", "data")
.attr("width", width + margin.left + margin.right)
.attr("height", 200)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
Now you can do d3.selectAll("svg.data") and select only svg elements with the class data
Alternatively, you can embed your svg elements in different divs. Assuming you have a div whose id is 'center-div' the following snippet returns you only the svgs contained in it.
d3.selectAll("#center-div svg")
Please also consider that you can append whatever DOM element via d3, so divs can be dinamically generated.

SVG path element .transition() - where to add?

Basically, I want my graph to start at the x-axis and grow over two seconds to the actual data values. This is probably a simple thing, but I can't seem to get it to work.
I'm appending an area element, in which the d="" attribute is built by a function (area) and I'm not sure where to add a transition.
First I thought to do this in the area function, but this fails. I've also tried to do this when the area element is added without success.
Here is my code:
// Create the area element for each object - a function that will be passed to "path"
var area = d3.svg.area()
.x(function(d) { return x(d.year); })
.y0(height)
//.y1(height)
//.transition()
//.duration(2000)
.y1(function(d) { return y(d.average); });
// build the container SVG element
var svg = d3.select("#co2").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 + ")")
svg.append("path")
// pull in data to the path
.datum(data)
.attr("class", "area")
// passing in the area function, for each object
.attr("d", area)
// starts the graph opacity at 0 and fades to 1 over 2.5 seconds
.style("fill-opacity", "0")
.transition()
.duration(2500)
.style("fill-opacity", "1");
Rather than try to use transition on the shape of the area graph, you could apply a scale(x,y) transform against the whole svg element that you want to animate. The advantage of this approach is that it is not limited to a particular rendering implementation (eg: not path/d3.area specific).
There are a couple of gotchas to note though:
To avoid the transition() behaviour, working on the margin adjustments, make sure you have a separate 'g' element for the transition() transforms to act on
SVG has its origin (0,0) in the top-left, so in addition to scaling the SVG area, you need to set the base of the graph
This is put together below:
'g' element:
var svg = d3.select("#co2").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 + ")")
// define a new 'g' to scope the transition, and keep separate from the margin adjusting transform above
.append("g");
transition() including base adjustment:
svg
.attr("transform", "translate(0," + height + ") scale(1, 0)")
.transition().duration(2000)
.attr("transform", "translate(0,0) scale(1, 1)");
As ever, this is best illustrated with the complete example: http://bl.ocks.org/4239516

Categories