Skip to content

Commit 2efb18c

Browse files
Merge pull request #42 from Abse2001/main
Add First-Class Polygon Regions to Topology and JumperGraph Visualization
2 parents e786ad0 + 778b054 commit 2efb18c

File tree

9 files changed

+158
-11
lines changed

9 files changed

+158
-11
lines changed

lib/JumperGraphSolver/jumper-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface JRegion extends Region {
55
d: {
66
bounds: Bounds
77
center: { x: number; y: number }
8+
polygon?: { x: number; y: number }[]
89
isPad: boolean
910
isThroughJumper?: boolean
1011
isConnectionRegion?: boolean

lib/JumperGraphSolver/visualizeJumperGraph.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ export const visualizeJumperGraph = (
2121
points: [],
2222
rects: [],
2323
texts: [],
24+
polygons: [],
2425
coordinateSystem: "cartesian",
2526
} as Required<GraphicsObject>
2627

27-
// Draw regions as rectangles
28+
// Draw regions as rectangles or polygons
2829
for (const region of graph.regions) {
29-
const { bounds, isPad, isThroughJumper, isConnectionRegion } = region.d
30+
const { bounds, isPad, isThroughJumper, isConnectionRegion, polygon } =
31+
region.d
3032
const centerX = (bounds.minX + bounds.maxX) / 2
3133
const centerY = (bounds.minY + bounds.maxY) / 2
3234
const width = bounds.maxX - bounds.minX
@@ -43,12 +45,22 @@ export const visualizeJumperGraph = (
4345
fill = "rgba(200, 200, 255, 0.1)" // blue for other regions
4446
}
4547

46-
graphics.rects.push({
47-
center: { x: centerX, y: centerY },
48-
width: width - 0.1,
49-
height: height - 0.1,
50-
fill,
51-
})
48+
if (polygon && polygon.length >= 3) {
49+
const points = polygon
50+
graphics.polygons.push({
51+
points,
52+
fill,
53+
stroke: "rgba(128, 128, 128, 0.5)",
54+
strokeWidth: 0.03,
55+
})
56+
} else {
57+
graphics.rects.push({
58+
center: { x: centerX, y: centerY },
59+
width: width - 0.1,
60+
height: height - 0.1,
61+
fill,
62+
})
63+
}
5264
}
5365

5466
// Draw ports as small circles with labels

lib/topology/RegionBuilder.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export class RegionBuilder implements RegionRef {
88
this.data = {
99
id,
1010
bounds: null,
11+
polygon: null,
1112
center: null,
1213
width: null,
1314
height: null,
@@ -27,17 +28,50 @@ export class RegionBuilder implements RegionRef {
2728

2829
rect(b: Bounds): this {
2930
this.data.bounds = { ...b }
31+
this.data.polygon = null
3032
// Clear center/size if rect is used
3133
this.data.center = null
3234
this.data.width = null
3335
this.data.height = null
3436
return this
3537
}
3638

39+
polygon(points: { x: number; y: number }[]): this {
40+
if (points.length < 3) {
41+
throw new TopologyError(
42+
`Region "${this.data.id}" has invalid polygon: at least 3 points required`,
43+
{
44+
regionIds: [this.data.id],
45+
suggestion: "Provide at least three polygon vertices",
46+
},
47+
)
48+
}
49+
50+
for (const point of points) {
51+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
52+
throw new TopologyError(
53+
`Region "${this.data.id}" has invalid polygon point`,
54+
{
55+
regionIds: [this.data.id],
56+
suggestion: "Use finite numeric x/y values",
57+
},
58+
)
59+
}
60+
}
61+
62+
this.data.polygon = points.map((p) => ({ x: p.x, y: p.y }))
63+
this.data.bounds = null
64+
this.data.center = null
65+
this.data.width = null
66+
this.data.height = null
67+
return this
68+
}
69+
3770
center(x: number, y: number): this {
3871
this.data.center = { x, y }
3972
// Clear bounds if center/size approach is used
4073
this.data.bounds = null
74+
this.data.polygon = null
4175
return this
4276
}
4377

@@ -56,6 +90,7 @@ export class RegionBuilder implements RegionRef {
5690
this.data.anchor = anchor
5791
// Clear bounds if center/size approach is used
5892
this.data.bounds = null
93+
this.data.polygon = null
5994
return this
6095
}
6196

lib/topology/Topology.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ export class Topology {
270270
bounds,
271271
center,
272272
isPad: data.isPad,
273+
...(data.polygon && { polygon: data.polygon }),
273274
...(data.isThroughJumper && { isThroughJumper: true }),
274275
...(data.isConnectionRegion && { isConnectionRegion: true }),
275276
...data.meta,

lib/topology/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import type { Bounds } from "../JumperGraphSolver/Bounds"
12
import type {
2-
JRegion,
33
JPort,
4+
JRegion,
45
JumperGraph,
56
} from "../JumperGraphSolver/jumper-types"
6-
import type { Bounds } from "../JumperGraphSolver/Bounds"
77

88
export type { Bounds, JRegion, JPort, JumperGraph }
99

@@ -39,6 +39,7 @@ export type SharedBoundary = {
3939
export type RegionData = {
4040
id: string
4141
bounds: Bounds | null
42+
polygon: { x: number; y: number }[] | null
4243
center: { x: number; y: number } | null
4344
width: number | null
4445
height: number | null

lib/topology/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ export function computeBoundsFromRegionData(data: RegionData): Bounds {
88
return data.bounds
99
}
1010

11+
if (data.polygon && data.polygon.length > 0) {
12+
let minX = data.polygon[0].x
13+
let maxX = data.polygon[0].x
14+
let minY = data.polygon[0].y
15+
let maxY = data.polygon[0].y
16+
17+
for (let i = 1; i < data.polygon.length; i++) {
18+
const point = data.polygon[i]
19+
minX = Math.min(minX, point.x)
20+
maxX = Math.max(maxX, point.x)
21+
minY = Math.min(minY, point.y)
22+
maxY = Math.max(maxY, point.y)
23+
}
24+
25+
return { minX, maxX, minY, maxY }
26+
}
27+
1128
if (data.center && data.width !== null && data.height !== null) {
1229
const halfW = data.width / 2
1330
const halfH = data.height / 2

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@tscircuit/math-utils": "^0.0.29",
2020
"@types/bun": "latest",
2121
"bun-match-svg": "^0.0.15",
22-
"graphics-debug": "^0.0.76",
22+
"graphics-debug": "^0.0.83",
2323
"react-cosmos": "^7.1.0",
2424
"react-cosmos-plugin-vite": "^7.1.0",
2525
"transformation-matrix": "^3.1.0",
Lines changed: 44 additions & 0 deletions
Loading

tests/topology/topology19.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect, test } from "bun:test"
2+
import { getSvgFromGraphicsObject } from "graphics-debug"
3+
import { visualizeJumperGraph } from "lib/JumperGraphSolver/visualizeJumperGraph"
4+
import { Topology } from "lib/topology"
5+
6+
test("topology19 - connect polygon regions", () => {
7+
const topo = new Topology()
8+
9+
const A = topo.region("A").polygon([
10+
{ x: 1, y: 2 },
11+
{ x: 1.5, y: 0.866 },
12+
{ x: 2.5, y: 0.866 },
13+
{ x: 3, y: 2 },
14+
{ x: 2.5, y: 3.134 },
15+
{ x: 1.5, y: 3.134 },
16+
])
17+
const B = topo.region("B").polygon([
18+
{ x: 3, y: 2 },
19+
{ x: 3.5, y: 0.866 },
20+
{ x: 4.5, y: 0.866 },
21+
{ x: 5, y: 2 },
22+
{ x: 4.5, y: 3.134 },
23+
{ x: 3.5, y: 3.134 },
24+
])
25+
26+
topo.connect(A, B)
27+
28+
const graph = topo.toJumperGraph()
29+
30+
expect(graph.regions.map((r) => r.regionId).sort()).toEqual(["A", "B"])
31+
expect(graph.ports[0].portId).toBe("A-B")
32+
33+
expect(
34+
getSvgFromGraphicsObject(visualizeJumperGraph(graph)),
35+
).toMatchSvgSnapshot(import.meta.path)
36+
})

0 commit comments

Comments
 (0)