alexbol99 / flatten-js Goto Github PK
View Code? Open in Web Editor NEWJavascript library for 2d geometry
License: MIT License
Javascript library for 2d geometry
License: MIT License
Hello,
I am looking at:
https://observablehq.com/@alexbol99/flattenjs-tutorials-polygons
and would like to know if there is any way to add a name attribute to the polygon that can pop up when you move your mouse over the polygon in D3?
I'm using a planerSet with lots of polygons, I managed to get it showing on d3, but now I would like to do actions when the mouse hovers over the polygon.
How would you recommend to do this? It would be helpful if there was a tutorial on how to do that as well.
Thank you,
in point.js line 93 :"point1.x < point2.y ".
It seems it should be "point1.x < point2.x ".
Got some more for you :)
https://codesandbox.io/s/wild-snow-0801g?file=/src/index.ts
EDIT: updated the link for one that includes multiple failing pairs of polygons.
our platform has switched from node v6.11.5 -> v6.14.2
We can't use flatten-js anymore
// require package
let Flatten = require('flatten-js');
generates an error:
./workspace/Electron/idscan-electron/node_modules/flatten-js/classes/point.js:232
let {r, stroke, strokeWidth, fill, ...rest} = attrs;
any clue?
Rgds
I'm sure I'm just doing something stupid, but can anyone shed any light on the below?
import { Vector, Point, Ray, Segment } from '@flatten-js/core';
const segment = new Segment(new Point(0, 0), new Point(1, 0));
const origin = new Point(0.5, 0.5);
const vector = new Vector(origin, new Point(0.5, 0.4));
const ray = new Ray(origin, vector);
const intersections = ray.intersect(segment);
I would expect the ray (starting at { x: 0.5, y: 0.5 }
, with a vector heading negative y) would intersect with the segment ({ x: 0, y: 0 }
to { x: 1, y: 0 }
) at a point on that segment: { x: 0.5, y: 0 }
.
But intersections.length === 0
. What am I doing wrong?
Am I able to have two shapes, where there is some overlap between the two, and somehow combine them into a single custom polygon that combines the two shapes together?
In the picture above, if I have the left side, how can I get the right side? Is there some sort of algorithm or simple function to call?
I need this because I'd like to calculate the overall surface area that multiple shapes may take up, in a given box. I know if I just added up surface areas, that there would be errors if there is overlap.
Change default anchor point for rotation method.
Set a center of bounding box as a default anchor point instead current (0,0) point.
I need to rotate a rectangle (I'm using a polygon to represent it) by using it's center point as pivot. Is there a way to determine the center point of a shape (or at least a polygon)? I didn't find anything in the documentation (maybe it's named differently).
On a side note: I'm not sure that using 0,0 for the rotation center is a good default. What do you think about using the center of the shape that is being rotated by default?
It would be very useful to specify a whole in a polygon using either a new method, such as addHole, or the addFace method. The area method states: "Returns area of the polygon. Area of an island will be added, area of a hole will be subtracted"
So it looks as if holes were contemplated at one point in time.
In the tutorial, the demo for '3. Check inclusion' breaks in Safari (11.1). Lines outside the circle are colored red.
let shapes = [
point(0, 0), point(200, 0),
point(200, 0), point(200, 200),
point(200, 200), point(0, 200),
point(0, 200), point(0, 0),
];
let polygon = new Polygon();
polygon.addFace(shapes);
let offsetPolygon = offset(polygon, 2);
I get an error:
Error: Zero division
at Function.get ZERO_DIVISION [as ZERO_DIVISION]
at Vector.normalize
at offsetSegment
at offset
What am I doing wrong? And How do I create a simple square by points?
The following code causes a Zero division exception. Because Chrome is struggling with the source map and doesn't point to the right lines as I try to detect where this is happening, I'm lazily submitting it here:
const Flatten = window.flatten;
const points = [
new Flatten.Point(-0.0774582, 51.4791865),
new Flatten.Point(-0.0784252, 51.4792941),
new Flatten.Point(-0.0774582, 51.4791865)
]
const poly = new Flatten.Polygon();
poly.addFace(points);
const pp = new Flatten.Point(-0.07776044568759738, 51.47918678917519);
console.log(poly.contains(pp));
EDIT: reduced number of points causing error
What about add ability to create vector from start point and direction(angle)? It's could be handy.
Hello,
If I do the planarSet.search function and the box I pass in is in the hole of a polygon, or between two parts of a multipolygon, it returns true.
Here's the code:
const plane = new PlanarSet()
const poly = new Polygon()
poly.addFace([
new Point(1,1),
new Point(5,1),
new Point(5,5),
new Point(1,5)
])
poly.addFace([
new Point(8,1),
new Point(8, 4),
new Point(11,4),
new Point(11,1)
])
//Hole face
poly.addFace([
new Point(10,2),
new Point(10,3),
new Point(9,3),
new Point(9,2)
])
plane.add(poly)
//The lines that we pass to the search function
const intersectLine = new Segment(new Point(3,5), new Point(3,6))
const parallelLine = new Segment(new Point(6,1), new Point(6, 2)) // Should be empty, but has poly
const perpLine = new Segment(new Point(6,2), new Point(7,2)) // should be empty, but has poly
const insideLine = new Segment(new Point(2,2), new Point(2,3))
const inHoleLine = new Segment(new Point(9.5,2.5), new Point(9.9, 2.5)) // should be empty, but it has poly
//Function to test the search function
function t(l){
return plane.search(l)
}
//print the results of the search function to the console
console.log(t(parallelLine)) // should be empty
console.log(t(perpLine)) // should be empty
console.log(t(intersectLine)) // should have the polygon
console.log(t(insideLine)) // should have the polygon
console.log(t(inHoleLine )) // should be empty
I found two polygons for which a union operation causes an infinite loop. I did some digging and tracked it down to an apparently infinite CircularLinkedList. I put a limit on the number of iterations in CircularLinkedList and made it throw an exception when it exceeds 1,000,000 iterations, which then gives me this stack trace when I run the unify function in a test:
Error: CircularLinkedList iteration exceeded limit.
at Object.next (src/data_structures/circular_linked_list.js:29:25)
at Face.setArcLength (src/classes/face.js:281:9)
at new Face (src/classes/face.js:130:18)
at Polygon.addFace (src/classes/polygon.js:146:17)
at restoreFaces (src/algorithms/boolean_op.js:755:28)
at swapLinksAndRestore (src/algorithms/boolean_op.js:174:5)
at booleanOpBinary (src/algorithms/boolean_op.js:204:9)
at unify (src/algorithms/boolean_op.js:30:32)
at Context.<anonymous> (test/algorithms/boolean_op.js:636:33)
at processImmediate (internal/timers.js:456:21)
The changed iterator function is:
[Symbol.iterator]() {
let element = undefined;
let count = 0;
return {
next: () => {
let value = element ? element : this.first;
let done = this.first ? (element ? element === this.first : false) : true;
element = value ? value.next : undefined;
count++;
if (count > 1000000) {
throw Error("CircularLinkedList iteration exceeded limit.")
}
return {value: value, done: done};
}
};
};
The test I ran is:
import { infiniteLoopPolygons} from './boolean_op_data';
const polygons = infiniteLoopPolygons.map(p => new Polygon(p));
const result = unify(...polygons);
The polygons are attached.
boolean_op_data.zip
Hello, can you please explain what is the difference between these two classes? For me it looks like Segment is same as Line only with more methods.
Also, can you please help me to figure out easy way to change length of segment?
And last one, if there any question can I post them on stackoverflow with tag flatten-js
(do you watch on them) or it will be easier for you if post questions here?
Thank you!
The setup:
The problem:
Because of floating point calculations, the rectangles are sometimes slightly apart and it is not possible to directly calculate the overlap.
Do you have any idea how to make this more robust?
One of my ideas was to use the flatten/offset library to make the rectangles or the side-segments larger and then calculate the intersection on those. This should make the calculation more stable.
I get an error when passing an empty polygon as one of the arguments to the intersect or subtract boolean operations. Eg see the console output at https://codesandbox.io/s/late-https-8sjsf?file=/src/index.js
I expected output for intersect to be empty polygon.
I expected output for subtract to be same as the first polygon passed.
If the current behaviour is an error I'm happy to submit a PR.
In my browser console the error looks like:
Uncaught (in promise) TypeError: element is undefined
toArray modules.js:160363
get edges modules.js:164770
get shapes modules.js:164778
clone modules.js:165370
subtract modules.js:158791
I have a suggestion for this project.
I've recently tried working with an SVG graphics library, only to realize it can't really manipulate vectors very well on its own. I've actually noticed this is a theme among many graphics libraries.
The best in terms of 2d geometry is (by far) paper.js
, but not everyone can use that library or wants to (it's a canvas library, first of all, and sometimes you might want to specifically use an SVG library). Also, even that library doesn't really have a complete toolset.
So I came up with this concept of a 2d geometry library that you can "drop-in" and use with other graphics libraries that actually do the rendering. The idea is to have lots of conversions (sometimes implicit ones) to and from different formats. This includes for lines, matrices, vector/point/complex numbers, etc.
I did a bit of work on it, and you can see the API I came up with here, including some implementations for things: https://github.com/GregRos/dropin-geometry
But I figured that there are lots of 2d geometry libraries for JS, it's a lot of work to make one, and there's no point if you can just add that functionality to other libraries.
Do you think this kind of thing is a good fit for this library?
const { polygon } = require('@flatten-js/core');
const { intersect } = require('@flatten-js/boolean-op');
const item1 = polygon([
[0, 30],
[30, 30],
[30, 0],
[0, 0],
[0, 30],
]);
const item2 = polygon([
[10, 20],
[20, 20],
[20, 10],
[10, 10],
[10, 20],
]);
const intersection = intersect(item1, item2);
console.log('item1', item1.svg());
console.log('item2', item2.svg());
console.log('intersection', intersection.svg());
Prints:
<path stroke="black" stroke-width="1" fill="lightcyan" fill-rule="evenodd" fill-opacity="1" d="
M0,30 L30,30 L30,0 L0,0 L0,30 L0,30 z" >
</path>
item2
<path stroke="black" stroke-width="1" fill="lightcyan" fill-rule="evenodd" fill-opacity="1" d="
M10,20 L20,20 L20,10 L10,10 L10,20 L10,20 z" >
</path>
intersection
<path stroke="black" stroke-width="1" fill="lightcyan" fill-rule="evenodd" fill-opacity="1" d="" >
</path>
I also tried using the intersect method on the polygon object:
const { polygon } = require('@flatten-js/core');
const item1 = polygon([
[0, 30],
[30, 30],
[30, 0],
[0, 0],
[0, 30],
]);
const item2 = polygon([
[10, 20],
[20, 20],
[20, 10],
[10, 10],
[10, 20],
]);
console.log('item1', item1.svg());
console.log('item2', item2.svg());
console.log('intersection', item1.intersect(item2));
Which prints:
item1
<path stroke="black" stroke-width="1" fill="lightcyan" fill-rule="evenodd" fill-opacity="1" d="
M0,30 L30,30 L30,0 L0,0 L0,30 L0,30 z" >
</path>
item2
<path stroke="black" stroke-width="1" fill="lightcyan" fill-rule="evenodd" fill-opacity="1" d="
M10,20 L20,20 L20,10 L10,10 L10,20 L10,20 z" >
</path>
intersection []
import { Polygon, Relations } from "@flatten-js/core";
let polygonA = new Polygon(JSON.parse('[[{"pc":{"x":361.86046511627904,"y":358.1395348837209,"name":"point"},"r":3.7013112186046513,"startAngle":0.8060492302297078,"endAngle":4.549840858948246,"counterClockwise":false,"name":"arc"},{"ps":{"x":361.26146984929693,"y":354.4870139177165,"name":"point"},"pe":{"x":355.3805768669687,"y":355.45145110913296,"name":"point"},"name":"segment"},{"pc":{"x":356.27906976744185,"y":360.93023255813955,"name":"point"},"r":5.551966827906977,"startAngle":4.549840858948247,"endAngle":0.8060492302297152,"counterClockwise":false,"name":"arc"},{"ps":{"x":360.12299918636324,"y":364.936295747922,"name":"point"},"pe":{"x":364.42308472889334,"y":360.8102436769092,"name":"point"},"name":"segment"}]]'));
let polygonB = new Polygon(JSON.parse('[[{"pc":{"x":356.27906976744185,"y":360.93023255813955,"name":"point"},"r":5.551966827906977,"startAngle":4.569083935001064,"endAngle":1.9978954813868353,"counterClockwise":false,"name":"arc"},{"ps":{"x":353.979265872936,"y":365.9834728754998,"name":"point"},"pe":{"x":359.7242924817441,"y":368.59811887275936,"name":"point"},"name":"segment"},{"pc":{"x":362.7906976744186,"y":361.86046511627904,"name":"point"},"r":7.402622437209303,"startAngle":1.9978954813868424,"endAngle":4.56908393500106,"counterClockwise":false,"name":"arc"},{"ps":{"x":361.7334917412655,"y":354.5337240561091,"name":"point"},"pe":{"x":355.48616531757705,"y":355.4351767630121,"name":"point"},"name":"segment"}]]'));
try {
let val = Relations.relate(polygonA, polygonB);
} catch (err) {
console.error(err);
}
Error: Infinite loop
at Function.get (flatten-js.esm.js:159)
at Function.testInfiniteLoop (flatten-js.esm.js:215)
at restoreFaces (flatten-js.esm.js:1322)
at swapLinksAndRestore (flatten-js.esm.js:526)
at booleanOpBinary (flatten-js.esm.js:555)
at intersect (flatten-js.esm.js:405)
at relatePolygon2Polygon (flatten-js.esm.js:2786)
at Object.relate (flatten-js.esm.js:2613)
I have a number of arrays, such as [[1,1], [1,2], [2,2], [2,1]], and [[[1,1],[1,3],[2,3],[2,1]], [[2,2], [2,3], [4,3], [4,2]]].
That describe a single polygon and multipolygon respectively. The first function I need to add for any project I have is the code to convert from the nested points, to the Flatten polygons.
If there could be a function that would do this for me, it would make the library much easier to work with when implementing into a new project.
If I have a circle and a line passing through I would expect intersect
to return an array on points where the line passes through the diamater of the circle. As far as I can tell what intersect
acceally returns is an array of points where the line passes through the bounding box of the circle.
let center = new Flatten.Point(width / 2, width / 2)
let circle = new Flatten.Circle(center, width / 3)
let eighth = new Flatten.Line(center, new Flatten.Vector(-1, 1))
let points = circle.intersect(eighth)
See this observable
In the index.d.ts you are declaring the method Vector.multiple (line 252) although in the documentation and the implementation it is named multiply.
Hello, I'm trying to figure out why Flatten.Line
has norm
vector which is 90 CCW from original vector? It was surprise for me that to build vertical line code should be NOT
new Line(new Point(10, 10), new Vector(0, 1))
it should be
new Line(new Point(10, 10), new Vector(-1, 0))
instead. In docs it says just norm - normal vector to a line
, nothing about 90 CCW.
Thanks great lib.
I wanted to use FlattenJS for just it's polygon boolean operations (since I already have implemented other functionality otherwise) and for that want to convert my polygon format into FlattenJS format.
For that I use the following function: return Flatten.polygon(this.points.map(p => Flatten.point(p.x, p.y)));
this.points is an array of points Point: {x, y}
. For a hexagon I would have 6 points at the corners for example.This actually works fine.
But when I want to convert the result of a boolean intersection back the polygon seems to be intersecting itself (even though .isValid
returns true). This is the function I use to convert them back:
var points = intersection.vertices.map(p => new Point(p.x, p.y))
new Point is the same type I used above.
It seems like the vertices of flattenJs seem to be a bit more complicated. How would I go about converting them to just an array of points, each point being connected to the next one?
When in arc constructor the last parameter counterClockwise is omitted, like this:
let arc = new Flatten.Arc(point(0,1000),980, 0, 2*Math.PI)
property counterClockwise is set to undefined, while its value should be set to the default value Flatten.CCW, which is true.
It will be fixed in the ongoing release 0.6.4
Here is an observable notebook to play: https://observablehq.com/d/adead0595ff39aa7
let {point, BooleanOperations, Polygon} = Flatten;
// Create new polygon
let a = new Polygon([
point(10,10),
point(10, 100),
point(100, 100),
point(100, 10),
]);
let b = new Polygon([
point(90,50),
point(120, 10),
point(120, 100)
]);
let stage = d3.select(DOM.svg(400, 400));
stage.html(a.svg() + b.svg() + BooleanOperations.intersect(a, b).svg({fill: 'red'}));
I expect the intersection to be there, but seems like return path is empty:
is there a good way to get the center of a polygon (or a face)?
React cannot minify this repository during build, because it hasn't been compiled to ES5. The React team recommends compiling packages to ES5 before publishing to ES5.
Of course, you may have your own needs and reasons for not having it compiled to ES5. I just wanted to inform you that compiling it will make this repository more relevant for React developers. The alternative for React developers is forking it or copying it to project code (both not ideal).
Maybe it is because I am inexperienced with Typescript, but if you import something in Typescript it tries to load the default property of the exported object, which gives me the error:
new flatten_js_1.default.Point(x, y);
TypeError: Cannot read property 'Point' of undefined
because in the index.js is only module.exports = f (if I change it to
module.exports.default = f;
it fixes the problem for me)
I'm using Flatten to model real world geometry. For my use anything less that 0.1 mm is essentially zero. I would like to be able to set the DP_TOL value a custom value instead of having it hard coded as a constant.
See an example on Runkit
hi alexbol99!
I use flatten-js in my project to draw window and door. it works very well right now.
but I need to draw ellipse window. could you please support ellipse
Is it intentional that Face#svg
is not documented, perhaps because it's intended to be private?
Version: 1.2.10
According to documentation this should be the case. An example to show the problem:
import Flatten from "@flatten-js/core";
import Box = Flatten.Box;
import Polygon = Flatten.Polygon;
function buildPoly(x: number, y: number) {
let width = 200;
let height = 50;
let rec = new Box(
x,
y,
x + width,
y + height
);
return new Polygon(rec);
}
const p1 = buildPoly(0, 0);
const p2 = buildPoly(0, 500);
const [distance, segment] = p1.distanceTo(p2);
console.log("Polygons", p1.vertices, p2.vertices)
console.log("Segment Start: ", segment.start, p1.contains(segment.start));
console.log("Segment End: ", segment.end, p2.contains(segment.end));
Which results in segment.start being part of p2, and not p1 as expected.
Since few days the links inside documentation are broken
https://alexbol99.github.io/flatten-js/Flatten.html#.Box
https://alexbol99.github.io/flatten-js/Flatten.html#.Arc
The link https://alexbol99.github.io/flatten-js/Flatten.html give me 404 error
Box isn't a shape inside the intersect method for either segment or line. So passing a box to their intersect method fails.
I didn't see anywhere the intersectLine2Box(line, box) function was exported. Though its used internally.
I took the three function: intersectLine2Box, intersectSegment2Line and intersectLine2Line: They work for what I'm doing..
Great library! thanks for all your work!
If you look closely, some definitions are off.
For example - in the definitions, it's allowed to pass an instance of Box to the constructor of a Polygon. The documentation doesn't mention it and it doesn't work.
Another instance are the Box toSegments
and toPoints
methods. They aren't present in the type definitions at all.
Support creation of face using Face()
constructor and append(shape)
method
Can you please provide a @types / flatten-js package?
If you are interested in adding it as a project asset I can contribute to it.
Hello,
I am trying to make a walking algorithm where I have old_x, old_y, new_x, and new_y, and I need to check if there are items between those points.
Plane has a great check point function, but being able to only pass in a bounding box means I need to not use Plane for checking a line, and instead iterate through all the objects and use Line.intersect, which defeats the reason for having a plane in the first place.
So I think it would be very nice to have just one search function that could take a point, bounding box, polygon, line, circle, or any shape, and run the respective intersect functions on all the polygons.
You could make a point that shapes other than point and box are slower, but in my case, it doesn't matter.
Thanks,
The various distanceTo()
methods have been of great use to me. Now I wanted to use the library for some basic operations, like adding and subtracting vectors, but I don't seem to find them in documentation or code. The closest thing I can find is Point.translate()
, but that's tied to Point
and does not include other vector operations.
Am I missing something, or are these vector operations currently not supported?
Currently, when using the Relations.intersect
function, an error is thrown when both params are Circle
because the the relate
function doesn't have a relateCircle2Circle
Would be useful when you need to find whether two circles intersect, without using Circle
's intersect
method and testing for a non-empty array.
Add a method that enables to remove vertex from polygon and join two edges
(I tried to create a branch commit with pull request with fix, but I don't have permissions)
Method segment2line
is missing variable declaration and instantiation for dist_and_segment
.
If you try to intersect a line and a polygon you get:
Cannot read property 'not_intersect' of undefined (line: 322)
because the function intersectShape2Polygon wants the line to have a box which it does not have.
Multiple times the documentation mentions 'json structure' or 'json object'. None of those are valid terminology and can be confusing. JSON is a string representation of a JS object, not an object. The documentation would be more accurate if terms like 'object' and 'JSON string' were used where appropriate.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.