could you please help me with zooming to SVG objects. no idea how to do this.
i need to zoom and center by click on object, i've made a test plunkr, so please take a look: http://plnkr.co/edit/ZQxhQ8VVoIXjMvdFIvQF
here's full code:
$(function(){
svg = d3.select("#svg");
svg_group = d3.select('#outer_group');
zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([.5, 20])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
svg_group.style("stroke-width", 1.5 / d3.event.scale + "px");
svg_group.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
$zoomService.$broadcast('zoom', {
scale: d3.event.scale
});
}
$('.sector').click(function(){
//zoom to somehow??
});
});
You have to use call zoom.event explicitly on the correct element after setting the desired translation and scaling.
var zoomed = false;
$('.sector').click(function(){
var bbox = this.getBBox();
var scale = 4;
// Manually calculating the position to which to transition to
// Will differ on a case by case basis.
svg
.call(zoom
.translate([ (- bbox.x - bbox.width / 2) * scale
, (- bbox.y - bbox.height / 2) * scale
])
.scale(scale).event
);
});
Demo: http://plnkr.co/edit/h1UP87dfQneRCFye9Xtu?p=preview
In the demo, I changed the position of the polygons and the viewBox on the svg to make it easier to calculate the exact coordinates to transition to for the zoom to stay centered. I also added some transitions and zoom-to-zero behavior not shown in the code excerpt above.
Sidenote: You don't have to use jQuery here to bind to click events; D3's selection.on can provide that function.
Related
I am not able to figure out how to re position an svg animation that i add dynamically on my paper during a zoom in or zoom out.
I add the animation when the link is active according to business logic in the following way.
c = joint.V('circle', { r: 8, fill: 'green' });
c.animateAlongPath({ dur: '4s', repeatCount: 'indefinite' }, canvasPaper.findViewByModel(link).$('.connection')[0]);
joint.V(canvasPaper.svg).append(c)
Now i go to the canvas and zoom in or zoom out the elements in the canvas scale appropriately . i use the paper.scale command. But the animation that i added does not move. I was able to get it scaled down to size but not its position. How do i achieve this?.
My zoom in and zoom out code is as below. I also know that i have to use the translate command on the svg object but i do not know how to calculate the translate values based on the zoomLevel
$('#zoom-in').on('click', function(e) {
e.preventDefault();
zoomLevel = Number((Math.min(1, zoomLevel + 0.2)).toFixed(1));
canvasPaper.scale(zoomLevel, zoomLevel,0,0);
_.each(canvasGraph.getLinks(), function(link) {
if(link.attr('linkActiveAnimationSvgId/text')) {document.getElementById(link.attr('linkActiveAnimationSvgId/text')).setAttribute("transform", "scale(" + zoomLevel + "," + zoomLevel + ")")
}
})
})
My zoom out code is as below
$('#zoom-out').on('click', function(e) {
e.preventDefault();
zoomLevel =Number((Math.max(0.2, zoomLevel - 0.2)).toFixed(1));
canvasPaper.scale(zoomLevel, zoomLevel,0,0);
_.each(canvasGraph.getLinks(), function(link) {
if(link.attr('linkActiveAnimationSvgId/text')) {document.getElementById(link.attr('linkActiveAnimationSvgId/text')).setAttribute("transform", "scale(" + zoomLevel + "," + zoomLevel + ")")
}
})
})
ok all i had to do was to find the view of the paper and then
paperView.vel.append(c)
instead of
joint.V(canvasPaper.svg).append(c)
and now i scale the paper and the animation scales/translates appropriately
I have a simple, modified version of the cluster diagram from D3 that I'm trying to get to respond to mouse clicks. It works for the links between nodes but not the nodes themselves. It looks to me like I'm treating lines and nodes (svg circles) the same, and yet nodes do not work... but of course D3 itself is generating those lines...
I have a very simple demo of it on JSFiddle at: http://jsfiddle.net/gaelicmichael1965/c2XWg/8/
What's going on? I would certainly appreciate any help that could be offered.
var nodes = tree.nodes(flareData),
links = tree.links(nodes);
// Create all of the link paths (using diagonal projection)
// Uses D3 functions to create SVG elements
var link = vis.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal)
.on("click", function(d, index) {
console.log("Selected line");
});
// Create all of the g-elements that contain node svg-elements
var node = vis.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 4.5)
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
// In actuality, will need to access property of d
.style( "fill", function(d, index) { return fillColors[index%4] } )
.on("click", function(d, index) {
console.log("Selected node");
});
The issue you have stems from your CSS. In particular, you are turning off pointer events for the nodes, meaning that mouse-triggered events (such as "click") are not processing:
.node {
font-size: 12px;
pointer-events: none; /*Comment out or remove this line*/
}
Comment out or remove the pointer-events:none; line in your CSS to allow the nodes to be the target of your "click" event.
Scratching my head on this one.
We have a list of text on the left side of the page. Each item in the list has a data-id attribute that makes it easy to match up corresponding schools in our SVG map. This SVG map is a map of the US, and has school locations fed in from a CSV excel sheet and stored in "schools" for access.
circles.selectAll("circles")
.data(schools)
.enter().append("svg:a")
.attr("xlink:href", function(d) { return d.url; })
.append("svg:circle")
.attr("school", function(d, i) { return d.name; })
.attr("id", function(d, i) { return d.id; })
.attr("cx", function(d,i) { return d.longitude; })
.attr("cy", function(d,i) { return d.latitude; })
.attr("r", function(d,i) { return 6; })
.attr("i", function(d,i) { return i; })
.attr("class", "icon")
So when a user hovers over this list of text I previously mentioned, I use this function:
mapSearch = function(id) {
d3.selectAll("circle")
.filter(function(d) {
if (d.id == id) {
return show_bubble_2(d);
}
})
}
Which calls:
show_bubble_2 = function(school_data) {
var school_info = school_data,
latitude = school_info.latitude,
longitude = school_info.longitude;
bubble.css({
"left": (longitude - 75)+"px",
"top": (latitude - 67)+"px"
});
bubble.html("<h1>" + school_info.name + "</h1>" + "<p>" + school_info.city + ", " + school_info.state + "</p>")
.attr("class", function(d) { return school_info.letter; });
bubble.addClass("active");
}
This works unless we start resizing the map to fit different screen sizes, or unless we do special zoom functions on the map. Then the bubbles closer to the west coast are where they're supposed to be but the ones on the east coast are way off. In short, it's a complete nightmare and not at all scalable.
My question: How do I just append this DIV to the corresponding circle ID instead of using an absolute positioned DIV so that no matter what size the map is, the bubble will always pop up right on top of that circle.
I have tried appending inside the if (d.id == id) { } but it always returns errors and so far I haven't figured it out. I'll keep trying something along those lines because I feel like that's the way to do it. If you have a better solution or could point me in the right direction, I would really appreciate it.
Thanks, and have a good one!
You can find the position of the circle even if there is a transform applied by using Element.getBoundingClientRect.
You could use your filtered selection, get the .node() and find its bounding rect. Then by adjusting for the scroll position, you can find the values of top and left to give to your bubble.
This means that the position of the bubble is based on the actual position at which the circle appears on the page, rather than being based on its data, which would require you to take the transforms into account. Try something like this:
mapSearch = function(id) {
// get the selection for the single element that matches id
var c = d3.selectAll("circle")
.filter(function(d) {
if (d.id == id) {
return show_bubble_2(d);
}
});
// get the bounding rect for that selection's node (the actual circle element)
var bcr = c.node().getBoundingClientRect();
// calculate the top/left based on bcr and scroll position
var bubbleTop = bcr.top + document.body.scrollTop + 'px',
bubbleLeft = bcr.left + document.body.scrollLeft + 'px';
// set the top and left positions
bubble.css({
'top': bubbleTop,
'left': bubbleLeft
});
}
Of course, if you are zooming or panning and want the bubble to remain on the circle, you will need to recalculate these values inside your zoom and pan functions, but the process would be the same.
HERE is a demo using circles that are randomly placed within a g element that has a translation and scale applied. Click on an item in the list to place the bubble on the corresponding circle.
A <div> is HTML. A <circle> is SVG. You can't (easily) put HTML elements inside SVG. You'd have to use <foreignobject> elements to do that. (See this question for details.) Alternatively, you could use native SVG elements such as <tspan> instead of <div>
I'm having trouble connecting points with a line on a map using d3. I think that I should use d3.svg.line() to create the points - but when I do it, I simply get a very small blob. Please see the link below for a screenshot of what I've been able to accomplish thus far - I want to connect the black dots with a line. Any help would be much appreciated.
Screenshot
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height*3 + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll("g")
.data(data)
.enter()
.append("g")
var projection = d3.geo.mercator().scale(5000).translate([-2000,5900])
var path = d3.geo.path().projection(projection)
var graticule = d3.geo.graticule()
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) { d.geometry.coordinates[0]; })
.y(function(d) { return d.geometry.coordinates[1] ; });
// this returns a parse error
// .x(function(d) { return projection(d.geometry.coordinates[0]); })
// .y(function(d) { return projection(d.geometry.coordinates[1]) ; });
var area = group.append("path")
.attr("d", path)
// .attr("d", line(data))
.attr("class", "area")
})
You have to pass both components of your coordinate to the d3.geo.mercator object, before taking each one separately as your x and y values. Your 'parse error' should go away if you use
.x(function(d) { return projection([d.lon, d.lat])[0]; })
.y(function(d) { return projection([d.lon, d.lat])[1]; });
instead. This post has a more complete example: D3 map Styling tutorial III: Drawing animated paths.
Hopefully once you are drawing the lines in the correct projection, they'll appear as you expect.
I have generated a map of Phoenix from this GeoJson and made it show as I would like it to.
Now I would like to add circles to the map to represent something of interest, but the circles never show up. Here is the code:
<script type="text/javascript">
var h = 1280;
var w = 1280;
var projection = d3.geo.albers().scale(80000).center([0, 33.44]).rotate([112.07, 0]).translate([920, 850]);
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
d3.json("data/phoenix.json", function(json) {
svg.selectAll("path").data(json.features).enter().append("path")
.attr("d", path).style("fill", "grey");
var coordinates = projection([33.46764,112.0785]);
svg.append("circle")
.attr("cx", coordinates[0])
.attr("cy", coordinates[1])
.attr("r", 5)
.style("fill", "red");
});
</script>
I have tried following different tutorial and howto's like from bost.ocks.org and this where it's with a csv file, but no matter what I do it won't draw the circle, what am I missing?
Adam Pearce is correct that the coordinates are [33.46764, -112.0785], however there is another problem: when translating from lat-long to the coords, you need to pass longitude as the first parameter, not latitude!
The tricky thing is that the albers projection, if called with a value not in (lower 48, alaska, hawaii) returns null silently.
Trying to translate [33.46764, -112.0785] in the console:
> proj = d3.geo.albersUsa()
function albersUsa(coordinates) {
var x = coordinates[0], y = coordinates[1];
point = null;
(lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
return point;
} d3.v3.js:3257
> proj( [33.46764, -112.0785] )
null
> proj( [-112.0785, 33.46762] )
[241.08874867733104, 327.6295325563234]
Bingo. In this case, it was useful to take a look at the actual function we are calling by using the console (in this case, in Chrome).
This was done using d3 version 3.3.8.
Schimmy's answer is correct, however I didn't understand at first. Here's how I added a circle on an Albers map:
//var projection = d3.geo.albersUsa();
var coordinates = projection([-112.0785,33.46764]);
svg.append("circle")
.attr("cx", coordinates[0])
.attr("cy", coordinates[1])
.attr("r", 5)
.style("fill", "red");
You may also want to use attr("transform", "translate") rather than attr("cx", coor[0].attr("cy", coor[1]).
If you have a GeoJson fie of the US and you want to plot a circle on each county:
// us = the geoJson file
svg.append("circle")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("r", 5)
.style("fill", "red");
You may find this much more efficient than "cx" and "cy".
From http://bost.ocks.org/mike/bubble-map/