D3: How to implement horizontally responsive SVG elements? - javascript

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

Related

D3 v4 force graph with images as nodes

Having problems using images as nodes in a force-directed graph. Everything I've looked at so far seems to be v3 code and I haven't been able to get any images at all so far, whether using xlink:href or svg:image or both.
What's the right way to use an img as a node with v4?
Here's what I'm trying, and a jsfiddle.
As you can see in the CSS, I'm trying to get images from a spritesheet for each node.
var defs = d3.append("svg:defs");
defs.append("svg:pattern")
.attr("width", 48)
.attr("height", 48)
//.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", "http://placekitten.com/g/48/48")
.attr("width", 48)
.attr("height", 48)
.attr("x", 0)
.attr("y", 0);
var nodesDrawn = svg
.selectAll("node")
.data(nodesData)
.enter()
.append("g")
//.append("xhtml:i")
.append('circle')
.attr('r', 10);
The problems so far:
There is no d3.append in D3. You probably meant svg.append.
Set the width and the height of the <pattern> to 1.
Set an ID to the <pattern> and use that to fill the circles.
Here are the code with that changes (I'm increasing the circles' radii, so you can better see the image): https://jsfiddle.net/ppk23hnz/
By the way, none of those changes are related to D3 version: they are the same in v3.x and v4.x.
PS: Don't mix jQuery and D3. you don't need that (and it's quite disturbing).

D3 V4 setting initial zoom level

I've just started using D3 V4 and I'm having trouble getting a pan and zoom function working. The problem is that I want the initial scale value to be something over than 1 so I set this using
zoom.scaleTo(svg, 2);
However as soon as I pan or zoom the scale resets back to 1.
Here's an example of this behavior on JSFiddle, what am I doing wrong?
JSFiddle
Here's the problem:
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 600)
.call(zoom)
.append("g");
You are assigning the group element instead of the svg element to the variable svg.
The zoom is correctly applied to the svg element, but then when you call zoom.scaleTo(svg, 2) you are zooming the group element, not the svg element.
This can be fixed by first creating and assigning the svg element to the svg variable and only then creating and assigning the group element.
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 600)
.call(zoom);
var mainContainer = svg.append("g");
Here's the fixed JSFiddle: https://jsfiddle.net/skgktrcu/2/

Responsive Donut Chart d3

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 to set the exact size of an inline SVG element?

I am creating a visualization with d3.js:
http://bl.ocks.org/EE2dev/raw/cd904f10097b9921f1cc/
In that code I create an SVG element and set the size with this line:
var chart = d3.select("body")
.append("div").attr("class", "chart")
.append("svg")
.attr("width", outerWidth) // outerWidth = 960
.attr("height", outerHeight) // outerHeight = 500
.append("g")
.attr("class", "margin")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Testing the code in Chrome, everything works fine, the SVG has the desired size of (960, 500). When I open this site in Firefox, however, the SVG element is created with a different size, appearantly depending on the actual browser window size, e.g. (634, 856) in the case below.
How do I fix this behaviour to set the SVG to the desired fixed size for Firefox?
I tried several things, including wrapping a div around and/or following ideas I found elsewhere
SVG only shown partially in Firefox
http://tympanus.net/codrops/2014/08/19/making-svgs-responsive-with-css/
But I didn't find a way to fix this problem for me that worked:(
Any help would be greatly appreciated!
outerWidth and outerHeight are browsers Window properties. It is curious problem that these properties could be overwritten in Chrome but not in Firefox (FF API). So when you set
outerWidth = 960;
then outerWidth is changed to 960 in Chrome. In the case of Firefox it is current window width and it can not be changed by the client script.
So rename outerWidth and outerHeight and it should be working.
svgWidth = 960;
svgHeight = 500;
...
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)

Why is there whitespace / "overlap" between rect elements where there shouldn't be?

I'm using d3.js to generate some rects which are directly above one another, in this fashion:
var greenRed = d3.select(".green-red").append("svg")
.attr("height", 120);
greenRed.append("rect")
.attr("fill", "green")
.attr("x", 0)
.attr("y", 0)
.attr("height", 50)
.attr("width", 300);
greenRed.append("rect")
.attr("fill", "red")
.attr("x", 0)
.attr("y", 50)
.attr("height", 50)
.attr("width", 300);
I've noticed that depending on which colours are stacked on top of one another, there is either a very thin whitespace present between the rectangles, or a sort of "overlap" of the two colours.
You can see what I mean in this fiddle: http://jsfiddle.net/ysim/PrC7X/
You can see that for .green-green and .green-grey there's no issue (to the naked eye, anyway); but for .green-blue and .red-blue, there is an overlap, and for .green-red, there is an extra whitespace.
I've tried adding .attr("stroke-rendering", "crispEdges") (suggested here) and .attr("stroke", "none") to the rect elements, as well as wrapping both the rect elements in a g element within the svg and adding .attr("stroke-rendering", "crispEdges") to that (suggested here), but neither of those solutions work.
What's causing this extra whitespace/overlap, and how do I go about fixing it so that the colours are neatly aligned, like in the first two cases?
try setting the stroke-width property to 0
That's antialiasing. Add style="shape-rendering: crispEdges" to the <div> elements and it will go away. You could add it to the shapes themselves instead if you want either as an attribute or a style.
The other thing to do is to add 0.5 to the y co-ordinates of your shapes There's more information about why that works here

Categories