I want to triggle an event on a path with D3.js. Here a working example: http://jsfiddle.net/kwoxer/kpL1uyy2/
So how can I say that a specific path is triggered by the zoomIntoArea function. I mean at the end I have some pathes and I want to load one specific at startup without clicking on it. I already tried:
zoomIntoArea(d3.select("lines"))
and some others but for sure that does not give me back the correct element.
You don't need the zoom behavior to explicitly zoom to a path, e.g.:
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [
width / 2 - scale * x,
height / 2 - scale * y];
svg.transition()
.duration(750)
.attr("transform", "translate(" +
translate + ")scale(" +
scale + ")");
Related
I use this as reference: https://bl.ocks.org/iamkevinv/0a24e9126cd2fa6b283c6f2d774b69a2
Adjusted some syntax to fit for version 5
Scale works, Translate looks like it works too because if I change the value, it zooms on different place..
But the problem is it doesn't zoom on the correct place I clicked.
I think this doesn't get to the place correctly because I use d3.geoMercator().fitSize([width, height], geoJSONFeatures) instead:
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [width / 2 - scale * x, height / 2 - scale * y];
Already tried to change the values to fit mine but failed, I can't get it.
Here is my projection:
var width = 500;
var height = 600;
d3.json("/regions50mtopo.json")
.then((geoJSON) => {
var geoJSONFeatures = topojson.feature(geoJSON, geoJSON.objects["Regions.50m"]);
// My Projection
var projection = d3.geoMercator().fitSize([width, height], geoJSONFeatures);
...
Any help, guide or reference?
Note: I'm mapping different country and fitSize(...) solves the
problem easily to fit on my svg that's why I can't use the same as in
the reference link I provided.
Found an answer: https://bl.ocks.org/veltman/77679636739ea2fc6f0be1b4473cf03a
centered = centered !== d && d;
var paths = svg.selectAll("path")
.classed("active", d => d === centered);
// Starting translate/scale
var t0 = projection.translate(),
s0 = projection.scale();
// Re-fit to destination
projection.fitSize([960, 500], centered || states);
// Create interpolators
var interpolateTranslate = d3.interpolate(t0, projection.translate()),
interpolateScale = d3.interpolate(s0, projection.scale());
var interpolator = function(t) {
projection.scale(interpolateScale(t))
.translate(interpolateTranslate(t));
paths.attr("d", path);
};
d3.transition()
.duration(750)
.tween("projection", function() {
return interpolator;
});
Exactly what I'm looking for. It works now as expected.
But maybe somebody also have suggestions on how to optimise it, because as the author said too, it feels slow and "laggy" when zooming in/out.
I am trying to zoom in bbox depending on the option of drop down menu, I tryed the code D3.js - Zooming to bbox with a dropdown menu but it is not working,and here is a js fiiddle of my work
<div id="LayerCover"style="display: inline-block;">
</div> //this is the div where drop down menu must place
function mapZoomgDorow(file){
d3.queue()
.defer(d3.json, "Data/Updated_map.json")
.await(menuChanged);
}
function menuChanged(error, jordan) {
if (error) throw error;
var select = d3.select('#LayerCover')
.append('select')
select.selectAll("option")
.data(jordan.features)
.enter().append("option")
.filter(function(d) { return d.properties.Level == '1' })
.text(function(d) { return d.properties.Name_1; console.log(d.properties.Name_1); })
.on("click",clicked)
this give me the drop down menu but when I click nothing happened ,note that my function clicked is just like https://bl.ocks.org/mbostock/4699541
For the answer I've removed some of your code to make it more specific to the problem you are having (and thus hopefully easier to use).
When appending your features, you could append a select menu and its options:
// append a menu:
var select = d3.select('form')
.append('select')
.on('change',function() { zoom(this.value); });
var options = select.selectAll('option')
.data(jordan.features)
.enter()
.append('option')
.html(function(d) { return d.properties.name_2; })
.attr('value',function(d,i) { return i; });
I'm using an old version of your jordan.json (I think you've updated it, but your fiddle wanted me to create a profile for drop box so it was easier to use the old, and I don't have your csv). You'll want to make sure that this is working before implementing the zoom functionality. Also, you'll need to place an on change event for the select menu.
Also, it might be easiest if your click (on the map) to zoom functionality and your select an option zoom functionality used the same function - if we do this they'll both need to take the same value. The increment works fine for this (unless you are modifying the number of elements in the geojson). You'll need to apply the same filter to each though - the data for the paths and the options must be the same if using the increment.
Your zoom funcion appears to work great, I've modified it slightly with an if statement: If you click or select the same feature twice, the map zooms out:
var last = -1; // the last feature zoomed to
function zoom(i) {
// if clicking on the same feature that was zoomed to last zoom out:
if (i == last) {
var bounds = path.bounds(jordan),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .8 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
last = -1;
}
// otherwise, zoom in:
else {
var bounds = path.bounds(jordan.features[i]),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .8 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
last = i;
}
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
I've put together a block here.
I have a function which gets the mouse position in world space, then checks to see if the mouse is over or near to the circle's line.
The added complication how ever is the circle is transformed at an angle so it's more of an ellipse. I can't see to get the code to detect that the mouse is near the border of circle and am unsure where I am going wrong.
This is my code:
function check(evt){
var x = (evt.offsetX - element.width/2) + camera.x; // world space
var y = (evt.offsetY - element.height/2) + camera.y; // world space
var threshold = 20/scale; //margin to edge of circle
for(var i = 0; i < obj.length;i++){
// var mainAngle is related to the transform
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
var y1 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius + threshold) * mainAngle,2);
var x0 = Math.pow((x - obj[i].originX),2) / Math.pow((obj[i].radius - threshold) * 1, 2);
var y0 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius - threshold) * mainAngle, 2);
if(x1 + y1 <= 1 && x0 + y0 >= 1){
output.innerHTML += '<br/>Over';
return false;
}
}
output.innerHTML += '<br/>out';
}
To understand it better, I have a fiddle here: http://jsfiddle.net/nczbmbxm/ you can move the mouse over the circle, it should say "Over" when you are within the threshold of being near the circle's perimeter. Currently it does not seem to work. And I can't work out what the maths needs to be check for this.
There is a typo on line 34 with orignX
var x1 = Math.pow((x - obj[i].orignX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
should be
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
now you're good to go!
EDIT: In regards to the scaling of the image and further rotation of the circle, I would set up variables for rotation about the x-axis and y-axis, such as
var xAngle;
var yAngle;
then as an ellipse can be written in the form
x^2 / a^2 + y^2 / b^2 = 1
such as in Euclidean Geometry,
then the semi-major and semi-minor axes would be determined by the rotation angles. If radius is the circles actual radius. then
var semiMajor = radius * cos( xAngle );
var semiMinor = radius;
or
var semiMajor = radius;
var semiMinor = radius * cos( yAngle );
you would still need to do some more transformations if you wanted an x and y angle.
so if (xMouseC, yMouseC) are the mouse coordinates relative to the circles centre, all you must do is check if that point satisfies the equation of the ellipse to within a certain tolerance, i.e. plug in
a = semiMajor;
b = semiMinor;
x = xMouseC;
y = yMouseC;
and see if it is sufficiently close to 1.
Hope that helps!
I am currently using the mouse-wheel to zoom in and out on my D3 force directed graph.
Is there a way of zooming in slower or quicker
For example, say the scale currently moves like this when I move the mouse wheel :
1,2,3,4,5.
I wish to move it :
1,1.5,2,2.5,3 and so on.
Here is my zooming function :
var minZoom = 0.2,
maxZoom = 2.5;
var zoom = d3.behavior.zoom()
.on("zoom", redraw)
.scaleExtent([minZoom, maxZoom])//-call zoom but put scale extent on to limit zoom in/out
;
My redraw :
function redraw() //-redraw network
{
var trans=d3.event.translate;
var scale=d3.event.scale;
svg.attr("transform","translate(" + trans + ")" + " scale(" + scale + ")"); //-translates and scales
}
You can intercept and adjust the scale inside the redraw function:
function redraw() //-redraw network
{
var trans=d3.event.translate;
var scale=d3.event.scale;
var newScale = scale / 2;
zoom.scale(newScale);
svg.attr("transform","translate(" + trans + ")" + " scale(" + newScale + ")"); //-translates and scales
}
Or if you want more specific interpolation, you can add an interpolator like d3.linear.scale() into the redraw function:
function redraw() //-redraw network
{
scaleInterpolator = d3.linear.scale().domain([1,10,100,1000]).range9([1,3,9,99]);
var trans=d3.event.translate;
var scale=d3.event.scale;
svg.attr("transform","translate(" + trans + ")" + " scale(" + scaleInterpolator(scale) + ")"); //-translates and scales
}
I mark as duplicate and the original reply is in here:
How to change speed of translate and scale when zooming in and out in D3 (using zoom.on & d3.event.translate, d3.event.zoom)?
You can adapt the solution with you height and your width.
You can get this info as well from this.getBBox().height. This height have the value after apply the scale, this mind that you are always an iteration behind. Better if you add the real height and width.
function redraw() {
var scale = Math.pow(d3.event.scale,.1);
var translateY = (height - (height * scale))/2;
var translateX = (width - (width * scale))/2;
svg.attr("transform", "translate(" + [translateX,translateY] + ")" + " scale(" +scale+ ")");
};
In this line: var scale = Math.pow(d3.event.scale,.1); .1 is the factor of velocity, slowly when smaller
I want to create the following:
Make a dynamic graph
It is Zoomable (Zooms at the center of currently seen display) (Zoom when certain buttons are clicked, mouse wheel are disabled for zoom)
Elements are draggable (When dragged it is not affected by force graph arrangement) (When elements are dragged outside of the svg the svg grows in size)
It has Scrollbar used as pan
So far I am already successful with
Creating a force graph
Creating zoom
Elements are already draggable and not included in force after dragged
Scrollbar also
I have two problems with these combination items:
Having dragged elements, it is not included in force graph anymore. Which would lead to possible overlap of other elements if new ones.
Scrollbar with zoom is not working wonders, when you zoom->scroll->zoom it zooms at the old location where the first zoom happened.
I would really need help for these two problems. I have not seen any example for zoom and scrollbar combination.
Here is the code.
function drawGraph(Data){
setDefault();
svg = d3.select("#graphingArea").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.on("dblclick.zoom", false)
.on("mousewheel.zoom", false)
.on("DOMMouseScroll.zoom", false) // disables older versions of Firefox
.on("wheel.zoom", false); // disables newer versions of Firefox;
//Needed for canvas to be dragged
rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
//Holds all that is to be dragged by the canvas
container = svg.append("g");
//Call zoom before drawing
svg.call(zoomUpdate);
//FOR DRAG
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
//Creating data that is drawn
populateD3Data(container, drag);
// Set data to be Force Arranged
force = self.force = d3.layout.force()
.nodes(nodes)
.links(links)
.distance(150)
.charge(-1000)
.size([width,height])
.start();
//Event to call arrange
force.on("tick", tick);
}
Zooming js:
var zoom = d3.behavior.zoom()
.scaleExtent([zoom_min_scale, zoom_max_scale])
.on("zoom", zoomed);
function zoomed() {
if(container != null && container != undefined) {
var translate = zoom.translate(),
scale = zoom.scale();
tx = Math.min(0, Math.max(width * (1 - scale), translate[0]));
ty = Math.min(0, Math.max(height * (1 - scale), translate[1]));
zoom.translate([tx, ty]);
container.attr("transform", "translate(" + [0,0] + ")scale(" + zoom.scale() + ")");
svg.attr("width", AreaWidth_ * zoom.scale());
svg.attr("height", AreaHeight_ * zoom.scale());
$("#graphingArea").scrollLeft(Math.abs(zoom.translate()[0]));
$("#graphingArea").scrollTop(Math.abs(zoom.translate()[1]));
}
}
//Button event for zoom in
d3.select("#zoom_in")
.on("click", zoomInOrOut);
//Button event for zoom out
d3.select("#zoom_out")
.on("click", zoomInOrOut);
//Gets the center of currently seen display
function interpolateZoom (translate, scale) {
return d3.transition().duration(1).tween("zoom", function () {
var iTranslate = d3.interpolate(zoom.translate(), translate),
iScale = d3.interpolate(zoom.scale(), scale);
return function (t) {
zoom
//Round number to nearest int because expected scale for now is whole number
.scale(Math.floor(iScale(t)))
.translate(iTranslate(t));
zoomed();
};
});
}
function zoomInOrOut() {
var direction = 1,
target_zoom = 1,
center = [graph_area_width / 2, graph_area_height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
d3.event.preventDefault();
direction = (this.id === 'zoom_in') ? 1 : -1;
target_zoom = zoom.scale() + (direction * zoom_scale);
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
function zoomUpdate() {
var target_zoom = 1,
center = [graph_area_width / 2, graph_area_height / 2],
extent = zoom.scaleExtent(),
translate = zoom.translate(),
translate0 = [],
l = [],
view = {x: translate[0], y: translate[1], k: zoom.scale()};
target_zoom = zoom.scale();
if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }
translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
view.k = target_zoom;
l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
view.x += center[0] - l[0];
view.y += center[1] - l[1];
interpolateZoom([view.x, view.y], view.k);
}
Here is my take on combining d3-zoom with scrollbars:
https://stackblitz.com/edit/d3-pan-and-zoom
Apart from handling d3's zoom to update scrollbar positions, you also need to handle scrolling with scrollbars to update d3's internal zoom representation by calling translateTo().