I'd like to do a smooth transition between alber/orthographic in a mini app I am building, much like this example:
http://mbostock.github.io/d3/talk/20111018/#27
It seems like this smooth transition is broken in v3 however, with a rather choppy transition of the map paths:
https://www.evernote.com/shard/s236/sh/46b002bd-9c5b-4e9b-87ef-270c303eb677/2eaeebb267a3fc59df5a8447bbbcc58b/res/37917835-5aad-4509-b534-31a3e3034762/Worst_Tornado_Outbreaks_of_All_Time-20130611-074050.jpg.jpg?resizeSmall&width=832
Code is pretty straight forward, I initialize the map as albers, then run ortho() to update it.
function ortho() {
var self = this,
h = 1000,
w = document.width;
this.projection = d3.geo.orthographic()
.scale(500)
.translate([ (w - 300) / 2, h / 2])
.clipAngle(90)
.rotate([90, 0, 0])
.precision(.1);
this.path = d3.geo.path()
.projection(this.projection);
//update path WITH transition
d3.selectAll('path')
.transition()
.duration(900)
.attr('d', app.path);
}
The map changes from albers to orthographic, but the transition is not smooth. Any thoughts would be great.
If you interpolate the path using D3’s naïve string interpolator (d3.interpolateString), then the number of coordinates in the starting path and the number of the coordinates in the ending path must match exactly, including in the same order. But this is almost never the case due to clipping, cutting and resampling. Shape interpolation is possible (using multiple strategies), but it’s a hard problem to solve in the general case. See this explanation (part of the Path Transitions) for why naïve interpolation is insufficient.
Instead of interpolating the path, you want to interpolate the projection. Interpolating the projection does not require an exact correspondence between coordinates and therefore avoids interpolation artifacts. See these examples for a demonstration:
http://bl.ocks.org/mbostock/5731632
http://bl.ocks.org/mbostock/3711652
http://www.jasondavies.com/maps/transition/
As shown in the first example, here is an implementation you can use:
function interpolatedProjection(a, b) {
var projection = d3.geo.projection(raw).scale(1),
center = projection.center,
translate = projection.translate,
α;
function raw(λ, φ) {
var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]);
return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]];
}
projection.alpha = function(_) {
if (!arguments.length) return α;
α = +_;
var ca = a.center(), cb = b.center(),
ta = a.translate(), tb = b.translate();
center([(1 - α) * ca[0] + α * cb[0], (1 - α) * ca[1] + α * cb[1]]);
translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]);
return projection;
};
delete projection.scale;
delete projection.translate;
delete projection.center;
return projection.alpha(0);
}
Create the interpolated projection using two projections a and b, and then set the interpolated alpha to a value between 0 (for a) and 1 (for b).
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 have found a workaround here (see code below), but I wonder if Fabric has built-in support to set the rotation point of an object to a specific place.
function rotateObject(fabObj, angleRadian, pivotX, pivotY) {
ty = pivotY - fabObj.height / 2.0;
tx = pivotX - fabObj.width / 2.0;
if (angleRadian >= Math.PI * 2) {
angleRadian -= Math.PI * 2;
}
angle2 = Math.atan2(ty, tx);
angle3 = (2 * angle2 + angleRadian - Math.PI) / 2.0;
pdist_sq = tx * tx + ty * ty;
disp = Math.sqrt(2 * pdist_sq * (1 - Math.cos(angleRadian)));
fabObj.set({transformMatrix:[
Math.cos(angleRadian),
Math.sin(angleRadian),
-Math.sin(angleRadian),
Math.cos(angleRadian),
disp * Math.cos(angle3),
disp * Math.sin(angle3)
]});
}
There is no built in method.
By default mouse rotation on fabricjs is around the center point.
If you deactivate centeredRotation on objects, setting it to false:
fabric.Object.prototype.centeredRotation = false
the object will start to rotate around the originX and originY position.
Then you can set for each object a specific originX and originY, that can be numeric and represent any point inside the object ( with values ranging from 0 to 1 ) i m not even sure if you can do it with points outside the object.
At that point the object rotates around that point, just setting the angle property to a desired angle.
Consider that now also position is relative to that origin.
As a side not, do not set transformMatrix of an object. is unsupported and will give you weird controls in case of interactivity with controls.
In your specific example, once found the matrix:
var matrix = [
Math.cos(angleRadian),
Math.sin(angleRadian),
-Math.sin(angleRadian),
Math.cos(angleRadian),
disp * Math.cos(angle3),
disp * Math.sin(angle3)
];
var options = fabric.util.qrDecompose(matrix);
object.set(options);
object.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center');
this should give you same effect but being supported by fabricjs better.
Rotation on fabricjs is around the center point.
This fuction is on developmenting...
but I have implemented to line rotation.
Redrawing line from custom center point to mouse position.
it works well for me.
There is a spiral rise effect as showed bellow: A demo can be found here: http://openlayers.org/en/latest/examples/dynamic-data.html
In the bottom of the demo page, an algorithm is used to implement this effect. But I can not figure out how does it work.
var t = theta + 2 * Math.PI * i / n;
var x = (R + r) * Math.cos(t) + p * Math.cos((R + r) * t / r);
var y = (R + r) * Math.sin(t) + p * Math.sin((R + r) * t / r);
What does R, r and p means? And how to understand the formula above? Can someone explain detail for me? Any help is appreciated.
I can recognize that code draws epicycloid curve (+ it's phase theta permanently changes to provide moving effect).
You can refer to R as radius of inner (here bigger) circle, p and r (equal values here) as radius of outer (here smaller) circle. Here r looks like radius of coil of toroidal spring.
The first summands correspond to the center of outer circle, the second ones - to the second-order rotation about outer circle center.
Play with these values and observe effects
Note that if you change p to make it not equal to r, you'll get epitrochoid curve (more general kind of epicycloid)
I'm interested in tweaking the radius of the circles on the circle pack layout. For that I need to know how the original radius is calculated.
By reading the d3.js source code for pack layout it seems the default radius function is simply Math.sqrt of value for each node. But that is not really the case because I modified the D3.js original circle pack example adding a .radius(function(d){return Math.sqrt(d);}) and as you can see at bl.ocks.org/ecerulm/f0a36710e3 the radius of the circles are not the same.
The d3.layout.pack() uses Math.sqrt as radius function. But pack.nodes will apply a scale transform d3_layout_packTransform(node, x, y, k) to make the whole circle pack chart to fit if radius wasn't explicitly set. That is why if you apply you own function (even if its radius(Math.sqrt)) you will need to apply your own scaling after if you want to get the same result as with implicit radius.
In the example below I explicitly set Math.sqrt as the radius function and then scale afterward to fit [diameter,diameter] with my own function pack_transform since d3_layout_packTranform is not accesible:
var pack = d3.layout.pack()
.value(function(d) { return d.size; })
.radius(Math.sqrt)
.size([diameter - 4, diameter - 4]);
var packnodes = pack.nodes(root);
var packroot = packnodes[0];
var w = diameter, h = diameter;
function pack_transform(node, k) {
function inner_transform(node,cx,cy,k) {
var children = node.children;
node.x = cx + k * (node.x-cx);
node.y = cy + k * ( node.y-cy);
node.r *= k;
if (children) {
var i = -1, n = children.length;
while (++i < n) inner_transform(children[i],cx,cy, k);
}
}
return inner_transform(node,node.x,node.y,k);
}
pack_transform(packroot, 1 / Math.max(2 * packroot.r / w, 2 * packroot.r / h));
I'm trying to write a small 'perspective' javascript app that allows me to fly through a set of x,y,z points that inhabit a 3d space.
I have the concept of a camera which changes its rotation and xyz position, while each point maintains a constant xyz point.
I then have a set of equations that works out how the camera's x,y,z coordinates should be adjusted for flying directly forwards. The x,y,z adjustments obviously depend upon the rotation of the camera.
It almost works, but at certain 'attitudes' the camera position adjustment goes wrong and the flightpath doesn't go straight ahead but goes off at an angle, or even reverses. The equations for working out the projection are as follows:
var directionFactor = 1;
if (direction == 'backward') directionFactor = -1;
sx = Math.sin(cameraView.rotX);
cx = Math.cos(cameraView.rotX);
sy = Math.sin(cameraView.rotY);
cy = Math.cos(cameraView.rotY);
sz = Math.sin(cameraView.rotZ);
cz = Math.cos(cameraView.rotZ);
// Z-Axis
ztrig = Math.sqrt((cx * cx) + (cy * cy)) * (cx * cy);
cameraView.z = cameraView.z + directionFactor *
(Math.abs(airspeed / 15) * ztrig);
// Y-Axis
ytrig = Math.sqrt((sx * sx) + (cz * cz)) * (sx * cz);
cameraView.y = cameraView.y + directionFactor *
(Math.abs(airspeed / 15) *ytrig);
// X-Axis
xtrig = Math.sqrt((cz * cz) + (sy * sy)) * (cz * sy);
cameraView.x = cameraView.x - directionFactor *
(Math.abs(airspeed / 15) * xtrig);
Obviously my equations aren't quite right. Can anyone tell me where I'm going wrong? Much appreciated and thanks.
You have some errors in your equations. (They are valid in the 2d case but not in 3d)
when you calculate
sx = Math.sin(cameraView.rotX);
It does make sense in 2d since :
sx = Math.sin(cameraView.rotX) = x/SQRT(y*y + x*x)
where (x, y) is the position of the camera.
But in 3d it's more complicated :
In 3d :
Thus to obtain the cartesian coordinate :
You may also use 3d matrix to perform rotation.