Automatic zoom on object in D3 force layout - javascript

I have a force directed graph and I implemented an autocomplete in order to highlight a node. Basically, once you select a node it is colored in red. I would now like to "zoom" on this node, which is change my window to be 400% the size of the node and the node should be centered in it.
Here are the relevant samples of my code: (or you can directly go to the jsFiddle I setup.)
First the code used to create the svg element:
var w = 4000,
h = 3000;
var vis = d3.select("#mysvg")
.append("svg:svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("id","svg")
.attr("pointer-events", "all")
.attr("viewBox","0 0 "+w+" "+h)
.attr("perserveAspectRatio","xMinYMid")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
Then, as an example, the function used to redraw the directed graph on "normal" zoom.
function redraw() {
trans=d3.event.translate;
scale=d3.event.scale;
vis.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")");
}
Here are the nodes of my graph:
vis.selectAll("g.node")
.data(nodes, function(d) {return d.id;})
.enter().append("g")
.append("circle")
.attr("id", function(d){return "circle-node-"+ d.id})
.attr("fill","white")
.attr("r","50px")
.attr("stroke", "black")
.attr("stroke-width","2px");
And finally here is my autocomplete.
$(function() {
$( "#tags" ).autocomplete({
source: nodes; //...
select: function( event, ui){
// ...
vis.selectAll("#circle-node-"+ui.item.value)
.transition()
.attr("fill", "red")
}
})
});
I tried to put as little code as possible so, sorry if I forgot something.
Update Here is a jsFiddle illustrating where I am for now.

The scaling and translation should be handled in the same function where you color the node red. You haven't really described how exactly you want the zoom to behave, but probably the easiest way is to apply translate and scale to the g element containing the graph.
I've changed your jsfiddle to do this; result here. I've assumed that by "400% the size of the node" you mean that the node should be magnified 400%? I've introduced a variable for the zoom factor if you want to change it.

Related

How to make map draggable in D3v6

I have a Drilldown world map(continent map + country map) where the second map(the country map) is zoomed-in onload by using fitExtent function. Since it is zoomed-in, I wanted to implement a draggable feature where I can drag the map and see other part of the map.
//My svg tag
<svg id="mapSVG" width="560"; height="350"></svg>
let zoomControl = function (event) {
svg.selectAll("path")
.attr("transform", event.transform);
}
function loadCountryMap(path, mapData) {
d3.json(path).then(function (json) {
var projection = d3.geoMercator();
var features = json.features;
//The reason why we have to do this is because d3.js has winding problem
//We need to rewind for the map to display correctly
var fixed = features.map(function (feature) {
return turf.rewind(feature, { reverse: true });
})
//Projections
var geoPath = d3.geoPath().projection(projection);
//Zoom in
projection.fitExtent([[mapData.XOffSet, mapData.YOffSet], [width*2, height*2]], { "type": "FeatureCollection", "features": fixed })
//Draggable
svg.selectAll("path")
.data(fixed)
.enter()
.append("path")
.attr("d", geoPath)
.attr("id", function (d) { return d.properties.FIPS_10_; })
.style("fill", "steelblue")
.style("stroke", "transparent")
.on("mouseover", mouseOver)
.on("mouseleave", mouseLeave)
.on("click", mouthClick)
.call(d3.zoom()
.on("zoom", zoomControl)
.scaleExtent([1, 1])
)
})
}
//How I select the svg
var svg = d3.select("svg")
.style("background-color", "white")
.style("border", "solid 1px black");
var width = +svg.attr("width");
var height = +svg.attr("height");
There are two problems with this:
1: By selecting the "svg" tag, this will drag the entire SVG HTML element, instead of the map content of SVG. I also changed it to "path" and "d", it didn't work either.
2: When the drag event first occurred, the dragged elements are being placed at the bottom right corner of the mouse cursor and follow the mouse cursor after that.
I want the zoomed-in map to be draggable to so I can see other part of the map.
The example desired behavior bin. This is the code from Andrew Reid's answer to a question. When the map is zoomed in, it became draggable. I don't see the drag behavior been defined anywhere in the code. I am assuming it is achieved by using d3.zoom(). However, since my map are zoomed-in by default(onload), and I have a separate mouse click event, I don't think I can use the similar approach.
var svg = d3.select("#mapDiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "white")
.style("border", "solid 1px black")
.call(d3.zoom()
.on("zoom", function (event) {
svg.attr("transform", event.transform)
})
.scaleExtent([1, 1])
)
.append("g");
I have achieved the functionality by grouping my path with .append("g"). Instead of assigning the zoom functionality path by path, I simply assigned it to the entire SVG and now the map is working fine.

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

How to apply drag behavior to a d3.svg.arc?

How do I apply d3.behavior.drag() to the following arc?
var arc = d3.svg.arc()
.innerRadius(50)
.outerRadius(70)
.startAngle(45 * (pi/180)) //converting from degs to radians
.endAngle(3) //just radians
vis.append("path")
.attr("d", arc)
.attr("transform", "translate(200,200)")
I want to be able to drag the arc around. I have not been able to see anything that uses the drag behavior on any SVG path based object (only for basic elements like circle, rectangle, etc.)
The closest thing I can find related to dragging is this:
http://bl.ocks.org/mbostock/1557377
Though it appears that if you try to ".on("drag", dragmove) for the appended path (.append("path")) "d" comes out as undefined. And if you attach ".on("drag", dragmove)" to the arc itself, the event doesn't appear to fire...)
Drag is a behaviour that you create and then apply to the elements you want to execute that behaviour. There should be no issue applying it to an arc.
So with your arc (minor modification to make the translation accessible):
var position = [200,200];
var arc = vis.append("path")
.attr("d", arc)
.attr("transform", "translate(" + position + ")");
Start by creating the behavior you want. Our drag will update the translation of the arc:
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
position[0] += d3.event.dx;
position[1] += d3.event.dy;
d3.select(this)
.attr("transform", function(d,i){
return "translate(" + position + ")"
})
});
Now we attach the behaviour to the arc:
arc.call(drag);
You can try it yourself here.

Using arc.Centroid to put an svg:image in the middle of the arc - but not appearing

I am currently trying to place a svg:image in the centre of my arc:
var arcs = svg.selectAll("path");
arcs.append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("width", "150px")
.attr("height", "200px");
I would appreciate it if someone could give me any advice on why it isn't appearing
thanks : http://jsfiddle.net/xwZjN/17/
Looking at the jsfiddle, you are creating the path elements after you try to append the svg:image elements to the them. It should be the other way around. You should first create the arcs and then append the images.
Second, as far as I know, the svg:path element should not contain any svg:image tags. It doesn't seem to display them if you place some inside. Instead what you should do is create svg:g tags with class arc and then use those to place the svg:images
Slightly modifying your jsfiddle could look something like this:
var colours = ['#909090','#A8A8A8','#B8B8B8','#D0D0D0','#E8E8E8'];
var arcs = svg.selectAll("path");
for (var z=0; z<30; z++){
arcs.data(donut(data1))
.enter()
//append the groups
.append("svg:g")
.attr("class", "arc")
.append("svg:path")
.attr("fill", function(d, i) { return colours[(Math.floor(z/6))]; })
.attr("d", arc[z])
.attr("stroke","black")
}
//here we append images into arc groups
var pics = svg.selectAll(".arc").append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d,i) {
//since you have an array of arc generators I used i to find the arc
return "translate(" + arc[i].centroid(d) + ")"; })
.attr("x",-5)
.attr("y",-10)
.attr("width", "10px")
.attr("height", "20px");
Where I also decreased the size of the images and offset them so that they fit into the arc.

Categories