D3js diagonal and Bezier curves - javascript

I would like to use d3.svg.diagonal() to create a Bezier curve. Someone could explain me how it works. I mean I don't understand the link between the mathematical definition https://en.wikipedia.org/wiki/B%C3%A9zier_curve and the diagonal function with source, target and projection. Where are the control points?

I mean I don't understand the link between the mathematical definition https://en.wikipedia.org/wiki/B%C3%A9zier_curve and the diagonal function with source, target and projection. Where are the control points?
the projection converts the inputs (source and target) to coords (x,y) and output as an Array
default is a 1:1 projection, no change of the values.
//pseudocode:
var src = projection(source),
trg = projection(target);
var points = [
src,
[src[0], (src[1] + trg[1]) / 2],
[trg[0], (src[1] + trg[1]) / 2],
trg
]

Related

Using D3-Geo to plot a shape

I'm using d3-geo package to plot points and shapes in latitude and longitude space. I would like to plot a simple polygon, triangle, square, star, etc centered on a point location. I would also, if possible, like to plot text at a given [lat,lon].
I currently have the below code working and plotting a ring at the given coords [-40.5, 65.5]. I would, however, like to be able to define different shapes at this location, is there an easy way of doing this without manually defining the shape myself? There is an empty 'properties' field that I'm unable to find any documentation on that could be used? D3-geo documentation and google searches have yielded zilch so far.
let geoGenerator = geoPath()
.projection(projection)
.pointRadius(4)
.context(context); //2d Canvas contect
context.beginPath();
geoGenerator({
type: "Feature",
geometry: {
type: "Point",
coordinates: [-40.5, 65.5]
},
properties: {}
});
context.stroke();
Geojson does not have any property in its specification that specifies the type of symbol (symbol shape, color, size, etc) that should be drawn. Geojson only specifies the geometry (point, line, polygon, etc) of the drawn object in geographic coordinates.
Of course you can use the properties property of a geojson feature to hold symbol data, it just has no effect on the rendering the feature unless you build that functionality yourself.
While geojson doesn't have any specifications for symbology, the geoPath generator in D3 let's you specify one part of a drawn symbol: the radius of a point (as points are dimensionless otherwise). However, other than this, d3-geo doesn't offer any support for drawing specific symbols, it can only project geometry.
To draw a symbol at a specific geographic coordinate, you'll want project the coordinate (projection([longitude,latitude])). Now you have a coordinate in pixel values, you can use that coordinate to draw your symbol. You don't want to try and draw the symbol in geographic coordinates as this isn't scalable and it is dependent on projection.
Here's a simple implementation with d3-symbol (I haven't drawn the rest of the world, just two points, but they are projected properly):
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = d3.symbol()
.type(d3.symbolWye)
.context(context)
.size(200);
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
context.save();
// position symbol:
context.translate(...xy);
// Draw symbol:
context.beginPath();
shape();
context.fill();
// Remove translation:
context.restore();
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could specify your own shapes too, here's a simple square implementation:
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = function(xy) {
var offset = 10;
var x = xy[0];
var y = xy[1];
context.beginPath();
// draw a sqaure:
context.moveTo(x-offset,y-offset);
context.lineTo(x-offset,y+offset);
context.lineTo(x+offset,y+offset);
context.lineTo(x+offset,y-offset);
context.lineTo(x-offset,y-offset);
context.fill();
context.strokeStyle = "steelblue";
context.lineWidth = 5;
context.lineCap = "square"
context.stroke();
}
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
shape(xy);
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could get a lot fancier in these basic functions, but for the purposes of demonstration they should be sufficient.

PaperJS - How to move along path and rotate along the path

The examples shown in here show how to move an object along the path in Paperjs but how do I rotate them correctly along the path?
In the examples shown on the link above, people suggested by using a circle as an example. But once changed to a rectangle new Path.Rectangle(new Point(20,20), new Size(20,20)); you can see that it moves along the path but does not actually rotate in the direction of the path.
How do I calculate the rotation and set it to my object?
In order to calculate the rotation, you need to know the tangent vector to the path at the position of your rectangle.
This can be retrieved with path.getTangentAt(offset) method.
Then, an easy way to animate the rotation of an item is to set item.applyMatrix to false and then animate the item.rotation property on each frame.
Here is a sketch demonstrating the solution.
// Create the rectangle to animate along the path.
// Note that matrix is not applied, this will allow us to easily animate its
// rotation.
var rectangle = new Path.Rectangle({
point: view.center,
size: new Size(100, 200),
strokeColor: 'orange',
applyMatrix: false
});
// Create the path along which the rectangle will be animated.
var path = new Path.Circle({
center: view.center,
radius: 250,
strokeColor: 'blue'
});
// On each frame...
function onFrame(event) {
// ...calculate the time of the animation between 0 and 1...
var slowness = 400;
var time = event.count % slowness / slowness;
// ...and move the rectangle.
updateRectangle(time);
}
function updateRectangle(time) {
// Calculate the offset relatively to the path length.
var offset = time * path.length;
// Get point to position the rectangle.
var point = path.getPointAt(offset);
// Get tangent vector at this point.
var tangent = path.getTangentAt(offset);
// Move rectangle.
rectangle.position = point;
// Rotate rectangle.
rectangle.rotation = tangent.angle;
}

How to determine the width/height/depth of an Object3D (as if it wasn't rotated)?

I am computing the height of an Object3D like so:
let obj = ... ; // An Object3D instance. Could be a Mesh, Group, etc.
let boundingBox = new THREE.Box3().setFromObject(obj);
let height = Math.abs(boundingBox.min.y - boundingBox.max.y);
When obj is rotated (on the X and/or Z axis), the difference between boundingBox.min.y and boundingBox.max.y increases/decreases, resulting in a height that is different to when it isn't rotated.
But I want to calculate the height of obj as if it wasn't rotated at all. How can I do this?
I'm guessing I need to transform boundingBox's dimensions based on the angle(s) of rotation, but I'm not sure how to do that.
Before rotation:
After rotation:
(red = obj, blue = boundingBox)
THREE.Box3().setFromObject(obj) will give you the "world-axis-aligned bounding box" of the object. It will explicitly compute the world-coordinates (read: including rotation, position and scale of the object and all of its parents) for all of your vertices.
If you just want the bounding-box of the geometry (without position, rotation, scale of the object), you can just use obj.geometry.boundingBox after calling computeBoundingBox():
obj.geometry.computeBoundingBox();
let boundingBox = obj.geometry.boundingBox;
For object-hierarchies, you can do something like this to get an aggregated bounding-box:
function getCombinedBoundingBox(object) {
const result = new THREE.Box();
object.traverse(child => {
// skip everything that doesn't have a geometry
if (!child.geometry) { return; }
child.geometry.computeBoundingBox();
result.union(child.geometry.boundingBox);
});
return result;
}
Note that this will only work if the child-objects are not transformed in any way.

D3 albersUsa projection funtion return null

I'm pretty new to D3 and I'm trying to set point on a map.
I'm confused as I created a projection with this code:
var projection = d3.geo
.albersUsa()
.scale(500)
.translate([el.clientWidth / 2, el.clientHeight / 2]);
I use this projection to draw a map and it works fine.
But then whenever I call projection([10, 20]) it returns null whichever values I'm passing in.
What is my error?
From the documentation
# projection(location)
[…]
May return null if the specified location has no defined projected position, such as when the location is outside the clipping bounds of the projection.
The Albers USA projection is defined only within its borders and will yield null for coordinates outside the well-defined area.
See this comparison of calls to projection(location) using [10,20], which is not valid, and [-110,40], which is a valid point:
var projection = d3.geo
.albersUsa()
.scale(500)
.translate([960, 500]);
d3.selectAll("p")
.data([[10,20],[-110,40]])
.enter().append("p")
.text(function(d) { return d + ": " + projection(d); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

What is the most efficient way to arrange images radially using javascript?

I have been racking my brain on how to make this work. I can find no examples of this and actually no previous questions. Basically I have a 121 thumbnail images (with the exact same dimensions), arrange them in a grid with gutters and I want to take the first image and place it in the center. (this allows for an 11x11 image grid) Then I would like to take each next image and begin to arrange them around the center image using the next closest available vacant location to the center image until all used up. It is assumed the list of images will be gotten from an array object. What is the most efficient way of doing this?
Most likely not the most efficient way of solving this, but I wanted to play with it:
You could iterate over all the points in your grid, calculate their distances to the center point and then sort the points by this distance. The advantage over the algorithmic solutions is that you can use all sorts of distance functions:
// Setup constants
var arraySize = 11;
var centerPoint = {x:5, y:5};
// Calculate the Euclidean Distance between two points
function distance(point1, point2) {
return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
}
// Create array containing points with distance values
var pointsWithDistances = [];
for (var i=0; i<arraySize; i++) {
for (var j=0; j<arraySize; j++) {
var point = {x:i, y:j};
point.distance = distance(centerPoint, point);
pointsWithDistances.push(point);
}
}
// Sort points by distance value
pointsWithDistances.sort(function(point1, point2) {
return point1.distance == point2.distance ? 0 : point1.distance < point2.distance ? -1 : 1;
});
The resulting pointsWithDistances array will look like this:
[
{x:5, y:5, distance:0},
{x:4, y:5, distance:1},
{x:5, y:4, distance:1},
...
{x:4, y:4, distance:1.4142135623730951},
{x:4, y:6, distance:1.4142135623730951},
...
{x:3, y:5, distance:2},
...
]
By iterating over the array in this order you are effectively filling the grid from the center outwards.
(Thanks for Andreas Carlbom's idea how to display this structure.)
Check out the difference to using Rectilinear Distances:
// Rectilinear Distance between two points
function distance(point1, point2) {
return Math.abs(point1.x - point2.x) + Math.abs(point1.y - point2.y);
}
For the shell-like structure of the algorithmic approaches you can use the Maximum Metric:
// 'Maximum Metric' Distance between two points
function distance(point1, point2) {
return Math.max(Math.abs(point1.x - point2.x), Math.abs(point1.y - point2.y));
}
You can play with the code here: http://jsfiddle.net/green/B3cF8/

Categories