dropdown menu for Zooming to bbox (d3.js) - javascript

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.

Related

D3js v5 Zooming to Bounding box on geoMercator().fitSize()

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.

d3 Force: Calculating position of text on links where links between nodes are arcs

One way to apply links to text is to use the force.links() array for the text elements and centre the text on the midpoint of the link.
I have some nodes with bidirectional links, which I've rendered as paths that bend at their midpoint to ensure it's clear that there's two links between the two nodes.
For these bidirectional links, I want to move the text so that it sits correctly over the bending path.
To do this, I've attempted to calculate the intersection(s) of a circle centred on the centre point of the link and a line running perpendicular to the link that also passes through its centre. I think in principal this makes sense, and it seems to be half working, but I'm not sure how to define which coordinate returned through calculating the intersections to apply to which label, and how to stop them jumping between the curved links when I move the nodes around (see jsfiddle - https://jsfiddle.net/sL3au5fz/6/).
The function for calculating coordinates of text on arcing paths is as follows:
function calcLinkTextCoords(d,coord, calling) {
var x_coord, y_coord;
//find centre point of coords
var cp = [(d.target.x + d.source.x)/2, (d.target.y + d.source.y)/2];
// find perpendicular gradient of line running through coords
var pg = -1 / ((d.target.y - d.source.y)/(d.target.x - d.source.x));
// define radius of circle (distance from centre point text will appear)
var radius = Math.sqrt(Math.pow(d.target.x - d.source.x,2) + Math.pow(d.target.y - d.source.y,2)) / 5 ;
// find x coord where circle with radius 20 centred on d midpoint meets perpendicular line to d.
if (d.target.y < d.source.y) {
x_coord = cp[0] + (radius / Math.sqrt(1 + Math.pow(pg,2)));
} else {
x_coord = cp[0] - (radius / Math.sqrt(1 + Math.pow(pg,2)));
};
// find y coord where x coord is x_text and y coord falls on perpendicular line to d running through midpoint of d
var y_coord = pg * (x_coord - cp[0]) + cp[1];
return (coord == "x" ? x_coord : y_coord);
};
Any help either to fix the above or propose another way to achieve this would be appreciated.
Incidentally I've tried using textPath to line my text up with my links but I don't find that method to be performant when displaying upward of 30-40 nodes and links.
Update: Amended above function and now works as intended. Updated fiddle here:https://jsfiddle.net/o82c2s4x/6/
You can calculate the projection of the chord to x and y axis and add it to the source node coordinates:
function calcLinkTextCoords(d,coord) {
//find chord length
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var chord = Math.sqrt(dx*dx + dy*dy);
//Saggita
// since radius is equal to chord
var sag = chord - Math.sqrt(chord*chord - Math.pow(chord/2,2));
//Find the angles
var t1 = Math.atan2(sag, chord/2);
var t2 = Math.atan2(dy,dx);
var teta = t1+t2;
var h = Math.sqrt(sag*sag + Math.pow(chord/2,2));
return ({x: d.source.x + h*Math.cos(teta),y: d.source.y + h*Math.sin(teta)});
};
Here is the updated JsFiddle

Show D3 link text right-side up

I have built a D3 force directed visualization with text labels along the links. The one problem I'm running into is these labels appearing upside down when the links are to the left of their source node. Example here:
The code where I position the path and text looks like so:
var nodes = flatten(data);
var links = d3.layout.tree().links(nodes);
var path = vis.selectAll('path.link')
.data(links, function(d) {
return d.target.id;
});
path.enter().insert('svg:path')
.attr({
class: 'link',
id: function(d) {
return 'text-path-' + d.target.id;
},
'marker-end': 'url(#end)'
})
.style('stroke', '#ccc');
var linkText = vis.selectAll('g.link-text').data(links);
linkText.enter()
.append('text')
.append('textPath')
.attr('xlink:href', function(d) {
return '#text-path-' + d.target.id;
})
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.text(function(d) {return d.target.customerId});
I know I will need to somehow determine the current angle of each path and then set the text position accordingly, but I am not sure how to.
Here is a link to a block based on this issue: http://blockbuilder.org/MattDionis/5f966a5230079d9eb9f4
The answer below has got me about 90% of the way there. Here is what my original visualization looks like with text longer than a couple digit number:
...and here is what it looks like utilizing the tips in the below answer:
So while the text is now "right-side up", it no longer follows the arc.
The arcs you draw are such that their tangent in the middle is exactly the direction of the baseline of the text, AND it is also colinear with the vector that separates the two tree nodes.
We can use that to solve the problem.
A bit of math is needed. First, let's define a function that returns the angle of a vector v with respect to the horizontal axis:
function xAngle(v) {
return Math.atan(v.y/v.x) + (v.x < 0 ? Math.PI : 0);
}
Then, at each tick, let's rotate the text in place by minus the angle of its baseline. First, a few utility functions:
function isFiniteNumber(x) {
return typeof x === 'number' && (Math.abs(x) < Infinity);
}
function isVector(v) {
return isFiniteNumber(v.x) && isFiniteNumber(v.y);
}
and then, in your tick function, add
linkText.attr('transform', function (d) {
// Checks just in case, especially useful at the start of the sim
if (!(isVector(d.source) && isVector(d.target))) {
return '';
}
// Get the geometric center of the text element
var box = this.getBBox();
var center = {
x: box.x + box.width/2,
y: box.y + box.height/2
};
// Get the tangent vector
var delta = {
x: d.target.x - d.source.x,
y: d.target.y - d.source.y
};
// Rotate about the center
return 'rotate('
+ (-180/Math.PI*xAngle(delta))
+ ' ' + center.x
+ ' ' + center.y
+ ')';
});
});
edit: added pic:
edit 2 With straight lines instead of curved arcs (simply <text> instead of <textPath> inside of <text>), you can replace the part of the tick function that concerns linkText with this:
linkText.attr('transform', function(d) {
if (!(isVector(d.source) && isVector(d.target))) {
return '';
}
// Get the geometric center of this element
var box = this.getBBox();
var center = {
x: box.x + box.width / 2,
y: box.y + box.height / 2
};
// Get the direction of the link along the X axis
var dx = d.target.x - d.source.x;
// Flip the text if the link goes towards the left
return dx < 0
? ('rotate(180 '
+ center.x
+ ' ' + center.y
+ ')')
: '';
});
and this is what you get:
Notice how the text gets flipped as the link goes from pointing more to the right to pointing more to the left.
The problem with this is that the text ends up below the link. That can be fixed as follows:
linkText.attr('transform', function(d) {
if (!(isVector(d.source) && isVector(d.target))) {
return '';
}
// Get the geometric center of this element
var box = this.getBBox();
var center = {
x: box.x + box.width / 2,
y: box.y + box.height / 2
};
// Get the vector of the link
var delta = {
x: d.target.x - d.source.x,
y: d.target.y - d.source.y
};
// Get a unitary vector orthogonal to delta
var norm = Math.sqrt(delta.x * delta.x + delta.y * delta.y);
var orth = {
x: delta.y/norm,
y: -delta.x/norm
};
// Replace this with your ACTUAL font size
var fontSize = 14;
// Flip the text and translate it beyond the link line
// if the link goes towards the left
return delta.x < 0
? ('rotate(180 '
+ center.x
+ ' ' + center.y
+ ') translate('
+ (orth.x * fontSize) + ' '
+ (orth.y * fontSize) + ')')
: '';
});
and now the result looks like this:
As you can see, the text sits nicely on top of the line, even when the link points towards the left.
Finally, in order to solve the problem while keeping the arcs AND having the text right side up curved along the arc, I reckon you would need to build two <textPath> elements. One for going from source to target, and one for going the opposite way. You would use the first one when the link goes towards the right (delta.x >= 0) and the second one when the link goes towards the left (delta.x < 0) and I think the result would look nicer and the code would not be necessarily more complicated than the original, just with a bit more logic added.

D3.js zooming on specific path after loading finished

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 + ")");

Adjusting scale of a group to ensure shapes inside are as big as possible in d3 js

I'm using d3 tree layout similar to this example: http://bl.ocks.org/mbostock/4339083
I implemented a search box that when typing, centers your screen on a virtual "average" position of all the appropriate nodes.
I want to adjust the scale, so that selected nodes will be
All Visible
As zoomed in as possible.
If the search match is exactly 1, simulate the clicking on the node, else center to this virtual position.
if (matches[0].length === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var position = GetAveragePosition(matches);
centerToPosition(position.x, position.y, 1);
}
This is what the centerToPosition function looks like:
function centerToPosition(x0, y0, newScale) {
if (typeof newScale == "undefined") {
scale = zoomListener.scale();
}
else {
scale = newScale;
}
var x = y0 * -1; //not sure why this is.. but it is
var y = x0 * -1;
x = x * scale + viewerWidth / 2;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);
}
So how can I calculate the new scale? I tried different variations by taking the extents of the data points
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
Also tried looking at the transform properties of the group before centering the screen.
var components = d3.transform(svgGroup.attr("transform"));
I'll try to add a js fiddle soon!
EDIT: Here it is: http://jsfiddle.net/7SJqC/
Interesting project.
The method of determining the appropriate scale to fit a collection of points is fairly straightforward, although it took me quite a while to figure out why it wasn't working for me -- I hadn't clued in to the fact that (since you were drawing the tree horizontally) "x" from the tree layout represented vertical position, and "y" represented horizontal position, so I was getting apparently arbitrary results.
With that cleared up, to figure out the zoom you simply need to find the height and width (in data-coordinates) of the area you want to display, and compare that with the height and width of the viewport (or whatever your original max and min dimensions are).
ScaleFactor = oldDomain / newDomain
Generally, you don't want to distort the image with different horizontal and vertical scales, so you figure out the scale factor separately for width and height and take the minimum (so the entire area will fit in the viewport).
You can use the d3 array functions to figure out the extent of positions in each direction, and then find the middle of the extent adding max and min and dividing by two.
var matches = d3.selectAll(".selected");
/*...*/
if ( matches.empty() ) {
centerToPosition(0, 0, 1); //reset
}
else if (matches.size() === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
//note: the "x" values are used to set VERTICAL position,
//while the "y" values are setting the HORIZONTAL position
var potentialXZoom = viewerHeight/(xExtent[1] - xExtent[0] + 20);
var potentialYZoom = viewerWidth/(yExtent[1] - yExtent[0] + 150);
//The "20" and "150" are for height and width of the labels
//You could (should) replace with calculated values
//or values stored in variables
centerToPosition( (xExtent[0] + xExtent[1])/2,
(yExtent[0] + yExtent[1])/2,
Math.min(potentialXZoom, potentialYZoom)
);
}
http://jsfiddle.net/7SJqC/2/

Categories

Resources