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')
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.
Is there any way to shrink my SVG graphic to fit the printing page, while leaving the web version of the graphic as it is? Or am I wasting my time?
I can only use FF at the moment but of course a cross-browser approach is even better...
Is there a CSS version of viewBox that I can put in a media
query?
Is there an onPrint event in js where I could apply
viewBox?
Any other approach - javascript, css, jquery, d3 all
welcome.
Googling suggests "no" to 1 & 2 but many of the posts are old.
Latest approach:
var svg = d3.select("div#matrix").append("svg")
.attr("width", 1000)
.attr("height", 1000)
//.attr("viewBox","0 0 500 500") - I don't want to apply this to the webpage SVG - this is just for testing
//.attr("preserveAspectRatio","xMinYMin meet")
.attr("id","matrixSVG")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Once the text labels are added to the graphic I resize the container elements for the webpage:
var svgWidth = margin.left+margin.right+(cellSize*horizNodes.length);
var svgHeight = margin.top+margin.bottom+(cellSize*vertNodes.length);
d3.select("svg#matrixSVG").attr("width",svgWidth).attr("height",svgHeight);
d3.select("div#matrix").style("width",svgWidth+"px").style("height",svgHeight+"px");
CSS:
#media print {
svg#matrixSVG {
width: 175mm; //this is the max size to fit US letter and A4
height: 245mm;
}
}
Thanks for any help
Example Codepen: http://codepen.io/mattrice/full/peXeqd/
What I want is for the SVG to stay completely contained within the parent's given width (6 columns for this example, but could be any other arbitrary width grid element).
This example functions as expected at widths <768px because the Bootstrap columns are full-page width; however, when the Bootstrap columns flow back to horizontally stacked at widths above 768 the SVG takes up the entire width of the page.
I think the problem stems from the arguments to updateDimensions() in render():
function render() {
updateDimensions(window.innerWidth);
...<snip>...
}
I have also tried getBoundingClientRect() like so
function render() {
updateDimensions(d3.select(options.selector).node().getBoundingClientRect().width);
...<snip>...
}
but that led to some odd results (probably outside of the scope of this question).
How can I fix this?
If you want the SVG to scale to fit its parent element, it needs to have a viewBox attribute.
Instead of setting the width and height of your SVG, use those values for the viewBox instead.
Change
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
to
.attr("viewBox", [0, 0, (width + margin.right + margin.left),
(height + margin.top + margin.bottom)].join(' '))
Updated Codepen
You may want to tweak the width and height components of the viewBox if you want, to remove the gap on the right, so it fits more neatly.
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.
It is quite straight forward to implement responsive SVG elements like below.
<div id="chartId"/>
var svg = d3.select("#chartId")
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 600 400");
svg.append("rect")
.attr("fill","blue")
.attr("x", 10)
.attr("y", 10)
.attr("width", 500)
.attr("height", 500);
JSFIDDLE
The following takes place when the window size is shrank.
Before shrinking
After shrinking
As can be seen the rect angle is horizontally and vertically responsive.
But, how can I implement an SVG element that is only horizontally responsive and the following takes place?
One option would be to redraw the SVG element every time the window size is changed, but I would like know if there is more sophisticated solution available.
The preserveAspectRatio attribute determines the scaling and alignment used to fit the viewBox in the svg. When preserveAspectRatio = "xMinYMin meet", content is scaled uniformly (i.e. horizontal and vertical scaled at same ratio). When preserveAspectRatio = "none", content is scaled non-uniformly. In your code, change...
.attr("preserveAspectRatio", "xMinYMin meet")
to...
.attr("preserveAspectRatio", "none")
Some time ago, I was quite into the responsive D3, I am a bit sluggish now, but here is an answer anyway. And it is not so sophisticated. Just put '%' for the width dimension of your svg, and a fixed one for the height.
Do mind that when using '%', the actual dimensions are in function of those of the parent element. You need to take this into account.
Here is the code i took from your fiddle and adjusted it:
var svg = d3.select("#chartId")
.append("svg")
.attr("width", "100%")
.attr("height", "200");
svg.append("rect")
.attr("fill","blue")
.attr("x", 10)
.attr("y", 10)
.attr("width", '100%')
.attr("height", '100%');
It works, i tried it, but I am just not sure if it really is what you want...