Skip to content

Commit 4abeb46

Browse files
authored
g.Rect: add methods to find a union of rectangles or points (#1446)
1 parent 42bdf47 commit 4abeb46

9 files changed

Lines changed: 214 additions & 9 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
<pre class="docs-method-signature"><code>point.update(x, y)</code></pre>
2+
<pre class="docs-method-signature"><code>point.update(p)</code></pre>
23
<p>Update the point's <code>x</code> and <code>y</code> coordinates with new values and return the point itself. Useful for chaining.</p>
4+
<p>Also accepts a single argument in the form of an object <code>{ x: [number], y: [number] }</code>.</p>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<pre class="docs-method-signature"><code>g.Rect(x, y, width, height)</code></pre>
22
<p>Return a new rectangle object with top left corner at point with coordinates <code>x</code>, <code>y</code> and dimensions <code>width</code> and <code>height</code>.</p>
33

4-
<p>Also accepts as a single argument an object in the form <code>{ x: [number], y: [number], width: [number], height: [number] }</code>.</p>
4+
<p>Also accepts a single argument in the form of an object <code>{ x: [number], y: [number], width: [number], height: [number] }</code>.</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<pre class="docs-method-signature"><code>g.Rect.fromPointUnion(...points)</code></pre>
2+
<p>Returns a new rectangle object that is large enough to contain given <code>points</code>, where each point is an object with <code>x</code> and <code>y</code> properties. Examples:</p>
3+
<pre><code>const rect = g.Rect.fromPointUnion(new g.Point(0, 0), new g.Point(20, 20));
4+
const rect2 = g.Rect.fromPointUnion({ x: 0, y: 0 }, { x: 20, y: 20 });
5+
// { x: 0, y: 0, width: 20, height: 20 }</code></pre>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<pre class="docs-method-signature"><code>g.Rect.fromRectUnion(...rects)</code></pre>
2+
<p>Returns a new rectangle object that is large enough to contain given <code>rects</code>, where each rect is an object with <code>x</code>, <code>y</code>, <code>width</code> and <code>height</code> properties. Examples:</p>
3+
<pre><code>const rect = g.Rect.fromRectUnion(new g.Rect(0, 0, 10, 10), new g.Rect(10, 10, 10, 10));
4+
const rect2 = g.Rect.fromRectUnion({ x: 0, y: 0, height: 10, width: 10 }, { x: 10, y: 10, height: 10, width: 10 });
5+
// { x: 0, y: 0, width: 20, height: 20 }</code></pre>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<pre class="docs-method-signature"><code>rect.update(x, y, width, height)</code></pre>
2+
<pre class="docs-method-signature"><code>rect.update(r)</code></pre>
3+
<p>Update the rect's <code>x</code>, <code>y</code>, <code>width</code> and <code>height</code> properties with new values and return the rect itself. Useful for chaining.</p>
4+
<p>Also accepts a single argument in the form of an object <code>{ x: [number], y: [number], width: [number], height: [number] }</code>.</p>

src/g/index.mjs

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3346,6 +3346,11 @@ Point.prototype = {
33463346

33473347
update: function(x, y) {
33483348

3349+
if ((Object(x) === x)) {
3350+
y = x.y;
3351+
x = x.x;
3352+
}
3353+
33493354
this.x = x || 0;
33503355
this.y = y || 0;
33513356
return this;
@@ -4104,6 +4109,54 @@ Rect.fromEllipse = function(e) {
41044109
return new Rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b);
41054110
};
41064111

4112+
Rect.fromPointUnion = function(...points) {
4113+
4114+
if (points.length === 0) return null;
4115+
4116+
const p = new Point();
4117+
let minX, minY, maxX, maxY;
4118+
minX = minY = Infinity;
4119+
maxX = maxY = -Infinity;
4120+
4121+
for (let i = 0; i < points.length; i++) {
4122+
p.update(points[i]);
4123+
const x = p.x;
4124+
const y = p.y;
4125+
4126+
if (x < minX) minX = x;
4127+
if (x > maxX) maxX = x;
4128+
if (y < minY) minY = y;
4129+
if (y > maxY) maxY = y;
4130+
}
4131+
4132+
return new Rect(minX, minY, maxX - minX, maxY - minY);
4133+
};
4134+
4135+
Rect.fromRectUnion = function(...rects) {
4136+
4137+
if (rects.length === 0) return null;
4138+
4139+
const r = new Rect();
4140+
let minX, minY, maxX, maxY;
4141+
minX = minY = Infinity;
4142+
maxX = maxY = -Infinity;
4143+
4144+
for (let i = 0; i < rects.length; i++) {
4145+
r.update(rects[i]);
4146+
const x = r.x;
4147+
const y = r.y;
4148+
const mX = x + r.width;
4149+
const mY = y + r.height;
4150+
4151+
if (x < minX) minX = x;
4152+
if (mX > maxX) maxX = mX;
4153+
if (y < minY) minY = y;
4154+
if (mY > maxY) maxY = mY;
4155+
}
4156+
4157+
return new Rect(minX, minY, maxX - minX, maxY - minY);
4158+
};
4159+
41074160
Rect.prototype = {
41084161

41094162
// Find my bounding box when I'm rotated with the center of rotation in the center of me.
@@ -4532,14 +4585,23 @@ Rect.prototype = {
45324585
// @return {rect} representing the union of both rectangles.
45334586
union: function(rect) {
45344587

4535-
const u = new Rect(rect);
4536-
const { x, y, width, height } = this;
4537-
const { x: rx, y: ry, width: rw, height: rh } = u;
4538-
const ux = u.x = min(x, rx);
4539-
const uy = u.y = min(y, ry);
4540-
u.width = max(x + width, rx + rw) - ux;
4541-
u.height = max(y + height, ry + rh) - uy;
4542-
return u;
4588+
return Rect.fromRectUnion(this, rect);
4589+
},
4590+
4591+
update: function(x, y, w, h) {
4592+
4593+
if ((Object(x) === x)) {
4594+
y = x.y;
4595+
w = x.width;
4596+
h = x.height;
4597+
x = x.x;
4598+
}
4599+
4600+
this.x = x || 0;
4601+
this.y = y || 0;
4602+
this.width = w || 0;
4603+
this.height = h || 0;
4604+
return this;
45434605
}
45444606
};
45454607

test/geometry/point.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,19 @@ QUnit.module('point', function() {
345345
point.update(16, 24);
346346
assert.equal(point.toString(), '16@24');
347347
});
348+
349+
QUnit.test('changes the values of x and y with object arg', function(assert) {
350+
351+
var point = new g.Point(2, 15);
352+
point.update({x: 10, y: 20});
353+
assert.equal(point.toString(), '10@20');
354+
355+
point.update({x: 5});
356+
assert.equal(point.toString(), '5@0');
357+
358+
point.update({});
359+
assert.equal(point.toString(), '0@0');
360+
});
348361
});
349362

350363
QUnit.module('dot(p)', function() {

test/geometry/rect.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,83 @@ QUnit.module('rect', function() {
2929
});
3030
});
3131

32+
QUnit.module('fromPointUnion(points)', function() {
33+
34+
QUnit.test('returns null if no arguments are passed', function(assert) {
35+
assert.ok(g.Rect.fromPointUnion() === null);
36+
});
37+
38+
QUnit.test('creates a new Rect object from points', function(assert) {
39+
var r0 = new g.Rect(10, 20, 5, 5);
40+
var topRightR0 = r0.topRight();
41+
var bottomLeftR0 = r0.bottomLeft();
42+
var originR0 = r0.origin();
43+
var cornerR0 = r0.corner();
44+
45+
var r1 = new g.Rect(-10, -20, 40, 60);
46+
var originR1 = r1.origin();
47+
var cornerR1 = r1.corner();
48+
49+
var r2 = new g.Rect();
50+
51+
var unionR0R1 = new g.Rect(-10, -20, 40, 60);
52+
var unionR0R2 = new g.Rect(0, 0, 15, 25);
53+
54+
assert.ok(g.Rect.fromPointUnion(originR0, cornerR0).equals(r0), 'rect from g.Points');
55+
assert.ok(g.Rect.fromPointUnion(topRightR0, bottomLeftR0).equals(r0), 'rect from g.Points 2');
56+
assert.ok(g.Rect.fromPointUnion({ x: r0.x, y: r0.y }, { x: r0.x + r0.width, y: r0.y + r0.height }).equals(r0), 'rect from PlainPoints');
57+
assert.ok(g.Rect.fromPointUnion(cornerR1, { x: r0.x, y: r0.y}, {x: r0.x + r0.width, y: r0.y + r0.height }, originR1).equals(unionR0R1), 'rect from g.Points and PlainPoints');
58+
assert.ok(g.Rect.fromPointUnion(originR0).equals(new g.Rect(r0.x, r0.y, 0, 0)), 'rect from single g.Point has width and height equal to 0');
59+
assert.ok(g.Rect.fromPointUnion({ x: r0.x, y: r0.y }).equals(new g.Rect(r0.x, r0.y, 0, 0)), 'rect from single PlainPoint has width and height equal to 0');
60+
assert.ok(g.Rect.fromPointUnion(originR0, cornerR1, cornerR0, originR1).equals(unionR0R1), 'rect from multiple g.Points');
61+
assert.ok(g.Rect.fromPointUnion({ x: r1.x + r1.width, y: r1.y + r1.height }, originR0, { x: r0.x, y: r0.y }, originR1).equals(unionR0R1), 'rect from multiple g.Points and PlainPoints');
62+
assert.ok(g.Rect.fromPointUnion({}, {}).equals(r2), 'creates default rect if cannot read x and y from arguments');
63+
assert.ok(g.Rect.fromPointUnion({}, topRightR0, bottomLeftR0).equals(unionR0R2), 'creates default rect if cannot read x and y from argument');
64+
});
65+
});
66+
67+
QUnit.module('fromRectUnion(rects)', function() {
68+
69+
QUnit.test('returns null if no arguments are passed', function(assert) {
70+
assert.ok(g.Rect.fromRectUnion() === null);
71+
});
72+
73+
QUnit.test('creates a new Rect object from rects', function(assert) {
74+
var r0 = new g.Rect();
75+
assert.ok(g.Rect.fromRectUnion(r0).equals(r0), 'rect from g.Rect');
76+
assert.ok(g.Rect.fromRectUnion({ x: r0.x, y: r0.y, width: r0.width, height: r0.height }).equals(r0), 'rect from PlainRect');
77+
78+
var r1 = new g.Rect(-10, -20, 40, 60);
79+
var r2 = new g.Rect(10, 20, 70, 90);
80+
var r3 = new g.Rect(80, 90, 110, 130);
81+
var r2Plain = { x: r2.x, y: r2.y, width: r2.width, height: r2.height };
82+
var r3Plain = { x: r3.x, y: r3.y, width: r3.width, height: r3.height };
83+
84+
var unionR1R2R3 = new g.Rect(-10, -20, 200, 240);
85+
var unionR0R3 = new g.Rect(0, 0, 190, 220);
86+
87+
assert.ok(g.Rect.fromRectUnion(r1, r2, r3).equals(unionR1R2R3), 'rect from multiple g.Rects');
88+
assert.ok(g.Rect.fromRectUnion(r3, r2, r1).equals(unionR1R2R3), 'rect from multiple g.Rects 2');
89+
assert.ok(g.Rect.fromRectUnion(r3, r1, r2).equals(unionR1R2R3), 'rect from multiple g.Rects 3');
90+
assert.ok(g.Rect.fromRectUnion(r3Plain, r1, r2Plain).equals(unionR1R2R3), 'rect from multiple g.Rects and PlainRects');
91+
assert.ok(g.Rect.fromRectUnion({}, {}).equals(r0), 'creates default Rect if cannot read x, y, width and height from arguments');
92+
assert.ok(g.Rect.fromRectUnion(r3, {}).equals(unionR0R3), 'creates default Rect if cannot read x, y, width and height from argument');
93+
94+
var r4 = new g.Rect(10, 10, 50, 50);
95+
var r5 = new g.Rect(100, 100, 50, 50);
96+
var r6 = new g.Rect(20, 20, 10, 10);
97+
var r7 = new g.Rect(20, 20, 50, 50);
98+
99+
var unionR4R5 = new g.Rect(10, 10, 140, 140);
100+
var unionR4R7 = new g.Rect(10, 10, 60, 60);
101+
102+
assert.ok(g.Rect.fromRectUnion(r4, r5).equals(unionR4R5), 'rect of distant rectangles');
103+
assert.ok(g.Rect.fromRectUnion(r4, r6).equals(r4), 'rect of embedded rectangles');
104+
assert.ok(g.Rect.fromRectUnion(r4, r4).equals(r4), 'rect of embedded rectangles 2');
105+
assert.ok(g.Rect.fromRectUnion(r4, r7).equals(unionR4R7), 'rect of intersecting rectangles');
106+
});
107+
});
108+
32109
QUnit.module('prototype', function() {
33110

34111
QUnit.module('bbox()', function() {
@@ -296,5 +373,34 @@ QUnit.module('rect', function() {
296373
assert.equal((new g.Rect(20, 20, 150, 150)).union(new g.Rect(50, 50, 200, 200)).toString(), (new g.Rect(20, 20, 230, 230)).toString(), 'union of intersecting rectangles');
297374
});
298375
});
376+
377+
QUnit.module('update(x, y, width, height)', function() {
378+
379+
QUnit.test('changes the values of x, y, width and height', function(assert) {
380+
381+
var rect = new g.Rect(12, 25);
382+
rect.update(1, 2, 3, 4);
383+
assert.equal(rect.toString(), '1@2 4@6');
384+
385+
rect.update(5, 6);
386+
assert.equal(rect.toString(), '5@6 5@6');
387+
388+
rect.update();
389+
assert.equal(rect.toString(), '0@0 0@0');
390+
});
391+
392+
QUnit.test('changes the values of x, y, width and height with object arg', function(assert) {
393+
394+
var rect = new g.Rect(12, 25);
395+
rect.update({ x: 1, y: 2, width: 3, height: 4 });
396+
assert.equal(rect.toString(), '1@2 4@6');
397+
398+
rect.update({ x: 5, y: 6 });
399+
assert.equal(rect.toString(), '5@6 5@6');
400+
401+
rect.update({});
402+
assert.equal(rect.toString(), '0@0 0@0');
403+
});
404+
});
299405
});
300406
});

types/geometry.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ export namespace g {
509509
translate(tx: PlainPoint): this;
510510

511511
update(x?: number, y?: number): this;
512+
update(p: PlainPoint): this;
512513

513514
vectorAngle(p: PlainPoint) : number;
514515

@@ -664,7 +665,14 @@ export namespace g {
664665

665666
union(rect: PlainRect): Rect;
666667

668+
update(x?: number, y?: number, width?: number, height?: number): this;
669+
update(rect: PlainRect): this;
670+
667671
static fromEllipse(e: Ellipse): Rect;
672+
673+
static fromPointUnion(...points: PlainPoint[]): Rect | null;
674+
675+
static fromRectUnion(...rects: PlainRect[]): Rect | null;
668676
}
669677

670678
namespace bezier {

0 commit comments

Comments
 (0)