Appending SVG Elements in D3 - javascript

Going through D3.js tutorials, I see two different conventions, and I am not sure what the difference is because they both product the same code:
1)
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
2)
var chart = d3.select("body")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
Can someone explain the difference between append("svg") and append("svg:svg") and why I would use one or the other?

The svg: part specifies the name space for the element that comes after it, i.e. an svg element in the svg namespace. It was advisable to specify this in older versions of d3 as it might be interpreted incorrectly otherwise, but it is not necessary to do so in recent versions.

Related

How to add background shading to D3 line chart

I would like to add shading to the background of a D3 line graph. There would be different shades for different parts of the line. Here is an example
My approach is the add rectangle svg to the chart, but that doesn't seem to be working because I don't know how to make the width correspond with the data.
here is a jsfiddle
Here is an example of the rectangle creation:
svg.append("rect")
.attr("class", "shading")
.attr("x", d[1].date)
.attr("y", 80)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "blue");
Am I on the right track? How do I find the width so that it corresponds with the data?
UPDATE: There will be multiple square of different widths, so I can't just grab the width of the entire svg.
You can do it like this:
//get all the ticks in x axis
//make a pair of it refer: d3.pair
var data = d3.pairs(svg.selectAll(".x .tick").data());
//make a color category
var c10 = d3.scale.category10();
//to svg append rectangles
svg.selectAll("rect")
.data(data)//for the tick pair
.enter()
.append("rect")
.attr("class", "shading")
.attr("x", function(d){return x(d[0])})//x will be the 1st tick
.attr("y", 0)
.attr("width", function(d){return (x(d[1]) - x(d[0]));})//width will be the diff of 1st and 2nd tick
.attr("height", height)
.attr("opacity", 0.2)
.attr("fill", function(d,i){return c10(i)});//use color category to color the rects.
working code here
fill the svg first with the data, then after that get the width property, it should automatically calculate it

D3.js pattern not working with arc or donut graph

I'm trying to have an image fill my donut chart, then rotate the image 60 degrees from its center.
I've had success filling a simple shape as a pattern with this method, but the pattern image gets all screwy and repeats itself when applied to a donut chart. The image is 300px x 300px - same size as the svg. The final result should look like this.
Here's my fiddle.
imgPath = "http://www.mikeespo.com/statDonkey/inner.png";
w = 300;
h = 300;
passingPercent = 60;
rotateStartPosition = 50;
var myScale = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
// MAKES SVG
var svg = d3.select("body")
.append("svg")
.attr("id", "svg_donut")
.attr("width", w)
.attr("height", h);
// MAKE DEFS
var defs = d3.select("#svg_donut")
.append("defs");
// MAKES PATTERN
defs.append('pattern')
.attr('id', 'pic1')
.attr('width', 300)
.attr('height', 300)
.attr('patternUnits', 'userSpaceOnUse')
.append('svg:image')
.attr('xlink:href', imgPath)
.attr("width", 300)
.attr("height", 300)
.attr("transform", "rotate(60 150 150)")
.attr("x", 0)
.attr("y", 0);
// CREATES VARIABLE *VIS* TO SVG
var vis = d3.select("#svg_donut");
// DEFINES DONUT GRAPH
var arc = d3.svg.arc()
.innerRadius(95)
.outerRadius(140)
.startAngle((myScale(0 + rotateStartPosition)))
.endAngle((myScale(passingPercent + rotateStartPosition)));
// APPENDS *VIS* TO SVG
vis.append("path")
.attr("id", "passing")
.attr("d", arc)
.attr("fill", "white")
.attr("transform", "translate(150,150)")
.attr("fill", "url(#pic1)");
I'm not exactly sure why this works to be honest but when I changed the width and height of the pattern element and removed the patternUnits attribute, I was able to achieve the desired look:
defs.append('pattern')
.attr('id', 'pic1')
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', imgPath)
.attr("width", 300)
.attr("height", 300)
.attr("transform", "rotate(00 150 150)")
.attr("x", 0)
.attr("y", 0);
I don't understand it completely but it has something to do with the coordinate system and the way in which the pattern scales to the object you're applying it to. The width and height aren't defining the size of the image as you might initially think, but the way in which the pattern will map to the new coordinate system of the donut. A width and height of 1 indicates that the pattern will just scale to the width and height of the donut.
Getting my info from here and admittedly not fully grasping it all yet but hopefully this will help: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns

d3.js: Horizontally center a <g> element with an unknown size within it's parent <svg> element

I'm creating a decision tree in d3.js and I'm having trouble centering it's g element within the parent svg element. The g element's width is unknown.
How would I center the g element horizontally?
If you need code samples, let me know and I'll post. Thanks!
Edit:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(0,40)")
.attr("id", "decision-tree");
var box = d3.select("#decision-tree").getBBox();
This results in an error in the console:
Uncaught TypeError: d3.select(...).getBBox is not a function
I can't seem to find any examples of using getBBox with d3.js through google. Can you link some examples if you know of any?
You can append a center element before the svg element to bring the whole svg horizontally in the center. But inserting a center element after svg and before g won't work. You'll have to use this workaround or use variables to translate the g element. See the following code for an example:
var svg = d3.select("body")
.append("center")
.append("svg")
.attr("width", width)
.attr("height", height)
svg.append("g")
.append("text")
.text("Blah Blah Blah")
Note that the following won't position the text in the center of g:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
svg.append("center")
.append("g")
.append("text")
.text("Blah Blah Blah")

Drawing states separately with d3 js and country topojson file

I have a topojson which contains state's paths. I want the user to be able to hover over a state and the state to appear in a different svg. So far, I've tried to extract the geometry out of the topojson (d.geometry , d.geometry.coordinates etc) But I'm not able to do it.
Maybe I need to draw a polygon out of that, but some states are of type "Polygon" and some of them are of type "MultiPolgyon".
Any ideas/suggestions?
Edit : Here's my code
var svg = d3.select("#india-map")
.append("svg")
.attr("width",width).attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("height", height)
var stateSvg = d3.select("#state-map")
.append("svg")
.append("g")
.attr("height", height)
.attr("width", width);
var g = svg.append("g");
var projection = d3.geo.mercator()
.center([86, 27])
.scale(1200);
var path = d3.geo.path().projection(projection);
var pc_geojson = topojson.feature(pc, pc.objects.india_pc_2014);
var st_geojson = topojson.feature(state_json, state_json.objects.india_state_2014);
g.selectAll(".pc")
.data(pc_geojson.features)
.enter().append("path")
.attr("class", "pc")
.attr("d", path)
.attr("id", function(d){ return d.properties.Constituency;})
.attr("fill", "orange")
.on("click", function(d){
drawCons(d);
});
function drawCons(d){
stateSvg.selectAll(".pc2")
.data(d)
.enter().append("path")
.attr("class","pc2")
.attr("d", path)
}
.data() expects to be given an array of objects to be matched against the selection. You're passing a single object, so it doesn't work. You can either use .datum(d) or .data([d]) to make it work.
Quick and dirty demo here.

D3 geometric zoom when there is no SVG element under the cursor

I am implementing a geometric zoom behaviour as seen in this example
The problem is that if the cursor is on a white spot outside the green overlay rect or any other SVG element (line, circle etc.) the mousewheel event gets intercepted by the browser and scrolls down the page.
I would like to be able to freely zoom independently of where I am on the visualisation.
Here is a simplified jsFiddle recreating the problem.
var width = 300,
height = 300;
var randomX = d3.random.normal(width / 2, 80),
randomY = d3.random.normal(height / 2, 80);
var data = d3.range(2000).map(function() {
return [
randomX(),
randomY()
];
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.call(d3.behavior.zoom().scaleExtent([-8, 8]).on("zoom", zoom))
.append("g");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height);
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("r", 2.5)
.attr("transform", function(d) { return "translate(" + d + ")"; });
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Hope this isn't too late, I missed this question the first time around.
The reason it isn't working under Chrome is because Chrome hasn't yet implemented the standard CSS transform on html elements -- and as strange as it is to understand, the outermost <svg> tag on an SVG element embedded in a webpage is treated as an HTML element for layout purposes.
You have two options:
Use Chrome's custom transform syntax, -webkit-transform in addition to the regular transform syntax:
http://jsfiddle.net/aW9xC/5/
Kind of jumpy, since you are transforming the entire SVG and readjusting the page layout accordingly. For reasons I don't understand neither the CSS/webkit transform nor the SVG attribute transform work when applied to the "innerSVG" element.
Replace the nested SVG structure with an SVG <g> group element, which Chrome has no problem transforming:
http://jsfiddle.net/aW9xC/4/
Stick a transparent rect in front of everything so the mouse event has something to latch on to. In SVG events are only sent to rendered elements such as rects and not to the general unrendered background.
svg.append("rect")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("width", "100%")
.attr("height", "100%");
In order to make this work properly the SVG would have to cover the whole area so to get the same look as your original fiddle you'd want to clip to the original area which can be done either by setting a clipPath or (as I've done in the fiddle) by creating an innser <svg> element which will clip.
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.behavior.zoom().scaleExtent([-8, 8]).on("zoom", zoom));
svg = svg.append("svg")
.attr("width", width)
.attr("height", height)
So altogether it looks like this...

Categories