how to align fabric objects with rotation? - javascript

I my fabric application, I want to align the selected objects, for example, to the left. Because objects might be rotated (and or scaled), thus, aligning objects actually mean align the bounding boxes of the objects to some edge.
For non-rotated objects, that's quite trivial to implement.
See sample code below:
// find the minimum 'left' value
// function 'min' is used to find the minimum value of a
// given array of objects by comparing the property value
// which is returned by a given callback function
const { minValue } = min(objects, object => {
const left = object.get('left');
const originX = object.get('originX');
if (originX === 'center') {
return left - (object.get('width') * object.get('scaleX')) / 2;
}
return left;
});
objects.forEach(object => {
if (object.get('originX') === 'center') {
object.set('left', minValue + (
object.get('width') * object.get('scaleX')
) / 2);
} else {
object.set('left', minValue);
}
});
canvas.renderAll();
However, it's quite complicated for rotated objects. I have to translate the rotated objects either horizontally or vertically to some calculated offset/distance.
Can anybody give some advise on this? thanks.

After a small research, I found this demo on the official fabricjs website.
Basically you can do:
var bound = obj.getBoundingRect();
Then use bound.top, bound.left, bound.width, bound.height as the bounding rectangle coordinates.

Related

Detect visibility of mesh which could possibly be hidden by object

I have a scene including an Object3D representing a globe and multiple mesh elements representing points on this globe. I use OrbitControls to allow interaction. Additionally I attach HTMLElements to the points on the globe. Since a globe basically is a sphere, points might not be visible for the camera when placed on the back.
How can I detect whether or not such a point is visible for the camera/hidden by the object? Doing so I want to hide the HTMLElement in relation to the mesh's visibility. The HTMLElement's position is updated on render, hence this check should happen on render as well I assume:
private render() {
this.renderer.render(this.scene, this.camera);
this.points.forEach(({ label, mesh }) => {
const screen = this.toScreenPosition(mesh);
label.style.transform = `translate3d(${screen.x - 15}px, ${screen.y}px, 0)`;
});
this.requestId = window.requestAnimationFrame(this.render.bind(this));
}
Working code within render:
this.points.forEach(({ label, mesh }) => {
const screen = this.toScreenPosition(mesh);
label.style.transform = `translate3d(${screen.x - 15}px, ${screen.y}px, 0)`;
const direction = new Vector3();
direction.copy(mesh.position).sub(this.camera.position).normalize();
this.raycaster.set(this.camera.position, direction);
const intersections = this.raycaster.intersectObject(this.scene, true);
const intersected = intersections.length > 0 ? intersections[0].object.uuid === mesh.uuid : false;
if (intersected && label.style.opacity === "0") {
label.style.opacity = "1";
} else if (!intersected && label.style.opacity === "1") {
label.style.opacity = "0";
}
});
I recommend a simple algorithm with two steps:
First, check if the given point is in the view frustum at all. The code for implementing this feature is shared in: three.js - check if object is still in view of the camera.
If the test passes, you have to verify whether the point is occluded by a 3D object or not. A typical way for checking this is a line-of-sight test. Meaning you setup a raycaster from your camera's position and the direction that points from your camera to the given point. You then test if 3D objects in your scene intersect with this ray. If there is no intersection, the point is not occluded. Otherwise it is and you can hide the respective label.

Highcharts stacked bar chart - how to get the stacks values

I have created an highcharts stacked bar chart, but when the data is skewed, the bars are not visible or the numbers overlap, as shown in below image.
I have seen many posts, but there is no out of the box solution for this, so i am making my custom solution.
I am setting a default height of 150 if the y value is less than 150.
This solution works, but the total of the bars now is shown to be 300 instead of the actual original value. How can i change the total stacklabel value on my own? I am unable to find a way to do that.
Here is the code to change the height to default values. I am storing the actual value in realValue variable in the point object.
chartOptions = {
type: CHARTING.CHART_OPTIONS.TYPE.COLUMN,
// On chart load, apply custom logic
events : {
load: function () {
var chart = this,
minColHeightVal = 150;
chart.series.forEach(function (s) {
s.points.forEach(function (p) {
if (p.y < minColHeightVal) {
p.update({
y: minColHeightVal,
realValue: p.y
}, false);
}
});
});
// How to iterate over the bars here and sum the actual value? i.e. point.realValue and set the stacklabel?
chart.redraw();
}
}
}
Did you try to use minPointLength option? It may be a simpler solution in your case: https://api.highcharts.com/highcharts/series.column.minPointLength
However, using your code to get the wanted result, use stackLabels.formatter function:
formatter: function() {
var series = this.axis.series,
x = this.x,
sum = 0;
series.forEach(function(s) {
if (s.points && s.points[x]) {
sum += s.points[x].realValue ? s.points[x].realValue : s.points[x].y
}
});
return sum;
}
Live demo: https://jsfiddle.net/BlackLabel/uocdykbL/
API Reference: https://api.highcharts.com/highcharts/yAxis.stackLabels.formatter

How to filter a featurecollection to an object that can be used with path.bounds()

I’ve been trying to make a map with a zoom to bounding box functionality, based on this example: https://bl.ocks.org/mbostock/9656675.
But for municipalities with islands, the zoom goes to the bounding box of the selected landmass instead of the bounding box of the municipality.
I figured out that in my data, municipalities with several areas separated by water consist of multiple polygons with the same nametag instead of a single multipolygon as in Mike Bostocks example above.
I managed to fix the issue for filling in the areas, so the error becomes even more obvious if you click on one of the small islands, but I cannot figure out how to properly zoom to the bounding box of the municipality instead of the land area.
I tried looking for different ways to filter or subset a featurecollection based on the areanames but my solutions all end up giving me a wrong data type, or a bounding box from -infinity to infinity.
To sum up, the intended behaviour is for the zoom to go to the bounding box of the highlighted area instead of the selected landmass.
Here is my map so far: http://plnkr.co/edit/iywWsM9RLs7UzI40q66M?p=preview
I slowed down the zoom a bit so the it’s easier to spot the error, I hope it’s not too annoying.
And here is the code piece where i suspect things are going wrong.
function clicked(d) {
if (d.properties.KOMNAVN == kommune) return reset();
d3.selectAll("path")
.attr("fill", "teal");
kommune = d.properties.KOMNAVN;
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(zoomExtent, 0.95 / Math.max(dx / w, dy / h))),
translate = [w / 2 - scale * x, h / 2 - scale * y];
svg.transition()
.duration(4000)
.call(zoom.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale));
d3.selectAll("path").filter(function(d) {
return d.properties.KOMNAVN == kommune
})
.attr("fill", "darkred");
}
Thanks in advance!
path.bounds (or projection.fitSize and projection.fitExtent) for that matter, requires a a geojson object, which can be a feature collection. Feeding this function an array will cause issues.
A feature collection looks like:
{
"type":"FeatureCollection",
"features": features
}
where features is an array of feature types.
You have a a feature collection for your dataset, and you can filter the features:
var filteredFeatures = data.features.filter(function(feature) {
return feature.properties.property == criteria
})
Then you can create a new feature collection with these filtered features. In your case this might look like:
var filteredFeatures = json.features.filter(function(feature) {
return feature.properties.KOMNAVN == d.properties.KOMNAVN;
})
var filteredFeatureCollection = {
"type":"FeatureCollection",
"features":filteredFeatures
}
No you can send this new feature collection to path.bounds.
Note that for your example I've moved the click function into the call back function for d3.json so that the scope of the json variable covers the click function.
Here's an updated plunker.

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.

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