Scaling SVG country map relative to the selected district center - javascript

I have an svg country map and I'm trying to achieve the effect when you click on a district and then see an animated scaling relative to selected district, i.e. scaling with transform-origin point in the center of a clicked district. And I would prefer not to use any libraries.
First of all, why here, when we have svg viewBox attribute setted, polygons drawn with getBoundingClientRect() points don't match their actual elements svg, map, e.target?
If we add some coefficients like here, then what viewBoxMapCoefX1 and viewBoxMapCoefX1 we need so the mapPolygon drawn with getBoundingClientRect() points matches actual map element?
When I try to scale map by adding:
// ANIMATION HERE
map.style.transformOrigin = transformOriginX + "px " + transformOriginY + "px";
map.style.transform = "scale(" + scale + ")";
it looks like transformOrigin value is wrong. If I try to change viewBox value by adding:
// ANIMATION HERE
svg.viewBox.baseVal.x = bounding.left;
svg.viewBox.baseVal.y = bounding.top;
svg.viewBox.baseVal.width = bounding.width;
svg.viewBox.baseVal.height = bounding.height;
then how can I make scaling animation with CSS (no SMIL)?
Any help or advice on this is greatly appreciated.

1) You should be using .getBBox() instead of BoundingClientRect to get the coordinates, as this will be based on the SVG itself and not the screen.
3) You are applying the transforms to the group tag, whereas it makes sense for these to be applied to the SVG element itself instead. You may have tried this already, but the transform origin value will all be based on the co-ordinates you're getting previously? So long as the animation works as intended, there is no issue with the animation front from what I can see.
// ANIMATION HERE
var svg = document.querySelector("svg");
svg.style.transformOrigin = '50% 50%';
svg.style.transform = 'scale(4)';

Thanks to #OwenAyres's answer 1 part of question has been solved.
Instead of scaling map straight relative to the selected district center we can find district's center in % insted of px and then move district's center to map's center and then make scale:
// ANIMATION HERE
var transformOriginXPercent = (50 - transformOriginX * 100 / mapBounding.width) * scale;
var transformOriginYPercent = (50 - transformOriginY * 100 / mapBounding.height) * scale;
var scaleText = "scale(" + scale + ")";
var translateText = "translate(" + transformOriginXPercent + "%," + transformOriginYPercent + "%)";
map.style.transformOrigin = "50% 50%";
map.style.transform = translateText + " " + scaleText;
and get expected result.
But there is still a question what coefficients for transformOriginX and transformOriginY are correct to get the result in the following form:
map.style.transformOrigin = (transformOriginX) + "px " + (transformOriginY) + "px";
map.style.transform = "scale(" + scale + ")";

Related

getBoundingClientRect() rotated object

I have a rectangular SVG object which is rotated via transform="rotate(45,0,0)". Using getBoundingClientRect() on the object gives the width and height of the bounding box which is not correct with the rotation.
I have searched and tried many calculations to get the original width and height taking off the rotation.
var rectangle = object.getBoundingClientRect()
var width = Math.sin(radian) * rectangle.height + Math.cos(radian) * rectangle.width
var height = Math.sin(radian) * rectangle.width + Math.cos(radian) * rectangle.height
And many other variations on this i have found, any help on the correct formula to use would be great. All i have is the values from getBoundingClientRect() and the radian.

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

How do I change the speed of the zoom in D3

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

SVG drag elements on the edges of the path

I have the path as described in the attached image 1, and I want to drag and drop the another element (as rounded red) on the edges of the drawn path, so at the end I want to drag the red marked element on the black line that is edge of the path, not inside the path,and now i can move inside and outside the path, but I want only to be on the border of the path
So here I have the customized the code as below on the mouseMove function and on the select case svgedit.compiled.js
if (path_child == 'path') {
var line_rotate = 90;
if (group_w_d == "drag_drop_D") {
var test = 'rotate(' + line_rotate + ',' + line_pt.x + ',' + line_pt.y + ') translate(' + trans_x + ',' + line_pt.y + ') scale(' + width + ',' + height + ')';
} else {
var test = 'rotate(' + line_rotate + ',' + line_pt.x + ',' + line_pt.y + ') translate(' + line_pt.x + ',' + line_pt.y + ') scale(' + width + ',' + height + ')';
}
selectedElements[0].setAttribute('transform', test);
var cls = selectedElements[0].getAttribute('class');
//alert(cls);
$('.' + cls).each(function() {
$(this).attr('transform', test)
});
}
For example here is the fiddle link example, which I want exactly, here we can move the ellipse on the path wherever we want, but I want it to be on the edges of my path elements
Edited:
For example just imagine that we are creating a room using rectangle and in this room we have to fix a door or window,like marked as rounded red, so we can move the path along all the position of the rectangle but it should only on the edges not inside the rect or outside the rect, there is no problem about it we done it,
But now we want split the wall to extended wall so we converted the rect into path after converting this the doors or windows are not moving along with the edges of the path instead of this the doors are moving anywhere on the working area of the SVG-EDIT
and here my doors and windows are the g not ellipse, so i know ellipse only have the cx,cy and g may have the transform so am not clear how to do it
i hope this will make sense
You need to tweak my SO answer, see the comment section for triangle fiddle (SO wont let me post fiddle link w/o code). Basically extend line from the centroid of triangle and calculate the intersection path.
Edit: In native SVG, you need an intersection library for line with line
Do post the updated code here for future readers.

How can I use .mousemove to fade a background image according to distance from center page?

I have this function set up
if (window.innerWidth && window.innerHeight) {
var winW = window.innerWidth;
}
var xM = winW/180;
var axis = 0;
$(window).bind('mousemove',function(e){
var xCoord = Math.floor(e.pageX/xM);
axis = 0.6 * Math.sin(xCoord);
var pageCoords = "( " + e.pageX + ", " + e.pageY + ", " + xCoord + " )";
$("span#showme").text(pageCoords);
});
setInterval(function() {
$("#welcome-background").fadeTo(0, 0.4 + axis);
}, 100);
(for additional reference and working visual- http://jsfiddle.net/ySjqh/2/ )
The code works in theory to divide the page evenly into segments from 0-180, then calculates which segment the mouse appears in. Then uses the Math.sin() function to derive how much opacity to apply, based on a padded starting point of 0.4 opacity (jQuery style), and should use the mouse position to determine how much of the remaining 0.6 to apply based on its distance from center, where mouse at center-page should yield full opacity.
What I don't get is why the script behaves this way, rolling through an entire sine wave when I've limited the input to the Math.sin(x) function to 1 < x < 180. If you replace xCoord with axis in the place where I build the jQuery text for #showme, you'll see that it throws negative numbers- which shouldn't be happening! ... so I don't get what the problem/behavior results from!!! Frustrating!!!
Just use:
xCoord = (xCoord * Math.PI) / 180; // Convert value to Radians
and it works..
Sample
http://jsfiddle.net/ySjqh/4/
axis = 0.6 * (1 - Math.abs(e.pageX - winW/2)/(winW/2));
Using the X distance from the center instead of sin.

Categories

Resources