I recently took up D3 for a project, and i am facing an issue with the tree layout. When i initially load the tree layout it stays nicely within the SVG, as seen on the screenshot below.
However, once i start opening the nodes, the more nodes i open, the more start going up and thus become invisible, as seen on the images below.
When i start opening nodes:
When all nodes are opened:
As you can see i have made the svg scrollable vertically so that i can see bigger trees.
Here is how i create the tree layout:
var tree = d3.layout.tree()
.nodeSize([55,30]);
Right now I found a solution by increasing the height of the 'g' element which holds all the nodes every time i click on a node that has children, but it is not a good solution because this causes the whole tree to "jump" every time this happens.
//creates the <g> element
var gWidth = 90;
var gHeight = 250;
var vis = svg.append("g")
.attr('id', 'group')
.attr("transform", "translate(" + gWidth + "," + gHeight + ")");
function updateSvg(node){
//moves the g element 50px down on each click that has a child
if(node.children){
gHeight +=50;
vis.attr("transform", "translate(" + gWidth + "," + gHeight + ")");
}
else{
gHeight-=50;
vis.attr("transform", "translate(" + gWidth + "," + gHeight + ")");
// }
}
I am also increasing the SVG height if there are more than a certain amount of nodes, but i think this is out of scope for my current issue.
Is there something I am missing?
I can see two ways of trying to solve this:
1. Scroll your container automatically to avoid the jump, in your solution with the gHeight variable:
document.getElementById("container").scrollTop += 50;
I am really not sure of this, though, so I think you'll do better with:
2. Use d3 zoom & pan methods The zoom behavior works pretty well, see
https://github.com/mbostock/d3/wiki/Zoom-Behavior
It boils down to
var zoom = d3.behavior.zoom();
svg.call(zoom);
and so you can get rid of the scrollbars and gHeights, and basically don't have to worry anymore about the window boundaries.
Related
I am working on a d3.js chart - This current model creates a legend segment as a g element. I'd like to ensure the legend stacks below if there is not enough room in the container.
//desktop - ample space
//mobile - not enough space
I've cleaned up the legend part -- you able to clean up the code base and add some more comments here. One of the old features I had - is if there wasn't enough room in the chart the legend stacks underneath - like responsive design - I used to create 2 svg parts but apparently with d3 it should only be 1 svg - http://jsfiddle.net/h066yn5u/13/
see if the chart can be more dynamic with different sizes - I think I had to add a padding of 10 to the radius to give it a little gap between the edges.. maybe though its a case of adding a transform on the svg itself to add that padding
var arcchart = chart.append("g")
.attr("class", "starchart")
.attr("transform", "translate("+(r+10)+"," + h / 2 + ")");
var legend = chart.append("g")
.attr("class", "legend")
.attr("transform", "translate(" + ((r + 10) * 2) + "," + (h / 4) + ")");
a version where it splits the chart into two svgs
http://jsfiddle.net/h066yn5u/14/
There are multiple ways to solve this issue.
Split into 2 svg containers: d3.js is not bound to just one svg container. You can split up the legend and the chart into 2 seperate svg containers and let the HTML handle the flow of the page
Use foreignObject: If you don't want to do that. You can try to use tag. Remember, that this is not supported by ie11 (and edge either afaik)
Calculate everything by hand: calculate the width of your legend (and including text), the width of the chart and get the available width for your whole container. If the whole container width is too small, push the legend below and of course adjust the svg height and width accordingly.
I have a graph visualisation.
I've added zooming by scaling a <g> which holds everything.
That also resizes the nodes (circles) and their labels.
From what I've seen, keeping the size and only repositioning is done like this:
function zoomed() {
var t = d3.event.transform;
circle.attr("transform", function(d) {
return "translate(" + t.applyX(d[0]) + "," + t.applyY(d[1]) + ")";
});
}
However this won't work for me, because I already use translate for positioning the nodes by d3.forceSimulation(). I could apply the zoom like it's done above, but that would all fall back when the simulation gets started again - e.g. when dragging a node, which is done using:
function dragstarted() {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
...
How could I combine the simulation and the zooming?
One way I am thinking is to scale down all the nodes by inverse scale to what is used for zooming.
Other way could be styling down the things - smaller font, smaller circles, etc.
Other way could be tampering with the forces so the nodes go further from each other on zoom in.
For now I went the way of reversed scale for each node. Works quite fine, although it's a little bit of overhead I think.
Here's the code:
// Zooming
var zoom = d3.zoom();
zoom.scaleExtent([0.4, 3]);
zoom.on("zoom", function onZoomed() {
console.log("Zooming", d3.event);
var t = d3.event.transform;
zoomingGroup.attr("transform", t); // Using transform.toString()
svg.selectAll(".myGroup circle").attr("transform", "scale(" + 1/t.k + ")");
svg.selectAll(".myGroup .labelBox").attr("transform", "scale(" + 1/t.k + ")");
svg.selectAll(".myGroup .labelText").attr("transform", "scale(" + 1/t.k + ")");
});
I wanted to apply it to the whole <g class=".myGroup">, but changing it's transform to scale would interfere with forceSimulation()'s translate. Can you give me a tip to handle that? I could add another <g> and apply translate(...) to the parent <g> and scale(...) to the other.
I'm leaving this unaccepted in case someone has better (simpler) solution.
I have been fiddling with this donut chart for a bit, but it won't render just right using the aspect ratio / viewbox method.
I got this working with a window resize function, but it has a bug -- since the container it can be can be collapsed, it can resize incorrectly. I figure I can work around this, but any tips on getting this to work with the commented out code?
What tends to happen is that based on the original window size the chart has dimensions based on that... which can skew the look if the window is at the wrong size when it starts.
https://jsfiddle.net/7rgf09x1/9/
// WORK IN PROGRESS: Responsive using only d3.
// var svg = d3.select('#revenue-chart').append('svg')
// .attr('id', 'revenue-chart-render')
// .attr("width", '100%')
// .attr("height", '100%')
// .attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height))
// .attr('preserveAspectRatio','xMinYMin')
// .attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
The goal of viewBox is to separate the page coordinates from the graphics coordinates. So
.attr('viewBox','0 0 '+width +' '+height)
gives you graphics coordinates in [0,width]x[0,height]. This is independent of the size of the svg in the page. You can change the '0 0' in order to have the origin of the graphics coordinates in the center instead of the top-left corner (although your solution with a translated g was valid too). Finally, preserveAspectRatio makes sure that your image isn't stretched by adding padding to the sides as necessary.
So overall this should give you
var svg = d3.select('#revenue-chart').append('svg')
.attr('id', 'revenue-chart-render')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox',(-width / 2 ) + ' ' + (-height/2) + ' '+width +' '+height)
.attr('preserveAspectRatio','xMinYMin')
How can I change the position of the legend, where the legend being on the right most side, I can make it move to the left most side? I tried to do some changes in the nvd3.js code, but that hasn't worked for me at all!. I'm sure how and where can i add attributes to change the position?
Just for info, here is the legend (Key Usage', 'block Usage','other usage') whose position im trying to change:
Any ideasss? Thanks!
Absolutely right about the rightAlign Property and good news is we can set it in the addGraph function as follows:
chart.legend.rightAlign(false);
From the source code comment line 5005 in nv.d3.js:
//position legend as far right as possible within the total width
if (rightAlign) {
g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
}
else {
g.attr('transform', 'translate(0' + ',' + margin.top + ')');
}
So I guess you can not positioning the legend to left side:)
The code is designed to put the legend as far right as possible, it also contains some wrapping logic, once the length of legends in one line reach some max limit(like reach the chart's width), it will separate into two lines in well format.
Hope it helps!
My question concerns this example:
http://bl.ocks.org/mbostock/4699541
And this bit of code:
var b = path.bounds(d);
g.transition().duration(750).attr("transform",
"translate(" + projection.translate() + ")"
+ "scale(" + .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height) + ")"
+ "translate(" + -(b[1][0] + b[0][0]) / 2 + "," + -(b[1][1] + b[0][1]) / 2 + ")");
What if there were text labels on the states? How would I handle text when zooming in and out of the states? I'd want to do it in a way that would keep the label the same size and still centered on the state. Here's what I've tried:
Tansforming/scaling the text along with the shapes. The text basically stays in its original position; it doesn't seem to scale and tansform the same way the shapes do.
Putting the text and states into a group together, then applying the transform/scale on the whole group. This just makes the text scale with the state -- so on zoom in, the text becomes huge. Etc.
Reprojecting. It works fine but it is way too expensive.
Removing the text and then reapplying it after the transition. This seems to just reapply the text at the same size, because it's applying with the same projection.
Any ideas? Any notes on things I might be missing? Thanks!