@knowvah/dot-engine 1.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -30
- package/dist/api/builder.d.ts +3 -0
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/edge-ops.d.ts +7 -0
- package/dist/api/edge-ops.d.ts.map +1 -1
- package/dist/api/geometry.d.ts +5 -2
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +159 -31
- package/dist/api.js.map +3 -3
- package/dist/async/collect.d.ts +49 -0
- package/dist/async/collect.d.ts.map +1 -0
- package/dist/async/fonts.d.ts +20 -0
- package/dist/async/fonts.d.ts.map +1 -0
- package/dist/async/render-async.d.ts +91 -0
- package/dist/async/render-async.d.ts.map +1 -0
- package/dist/async/render-into.d.ts +38 -0
- package/dist/async/render-into.d.ts.map +1 -0
- package/dist/async/sanitize.d.ts +28 -0
- package/dist/async/sanitize.d.ts.map +1 -0
- package/dist/common/css-font.d.ts +9 -0
- package/dist/common/css-font.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +3 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-shapes.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/textmeasure.d.ts +11 -1
- package/dist/common/textmeasure.d.ts.map +1 -1
- package/dist/common/utils-inputscale.d.ts +19 -0
- package/dist/common/utils-inputscale.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +36 -4
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device.d.ts +5 -2
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/image-resolver.d.ts +16 -16
- package/dist/gvc/image-resolver.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +1 -9
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/usershape.d.ts +2 -12
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7745 -5550
- package/dist/index.js.map +4 -4
- package/dist/label/index.d.ts.map +1 -1
- package/dist/label/node.d.ts +0 -6
- package/dist/label/node.d.ts.map +1 -1
- package/dist/label/rectangle.d.ts +1 -7
- package/dist/label/rectangle.d.ts.map +1 -1
- package/dist/layout/circo/circular.d.ts +8 -5
- package/dist/layout/circo/circular.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +0 -19
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/position.d.ts +7 -2
- package/dist/layout/dot/position.d.ts.map +1 -1
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/init.d.ts.map +1 -1
- package/dist/layout/fdp/layout.d.ts.map +1 -1
- package/dist/layout/fdp/ports.d.ts +0 -10
- package/dist/layout/fdp/ports.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +0 -16
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/adjust-info.d.ts +117 -0
- package/dist/layout/neato/adjust-info.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +40 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
- package/dist/layout/neato/edge-len.d.ts +20 -0
- package/dist/layout/neato/edge-len.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +32 -4
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +9 -6
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +4 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk-paths.d.ts +50 -0
- package/dist/layout/neato/kk-paths.d.ts.map +1 -0
- package/dist/layout/neato/kk-solve.d.ts +14 -0
- package/dist/layout/neato/kk-solve.d.ts.map +1 -0
- package/dist/layout/neato/kk.d.ts +44 -0
- package/dist/layout/neato/kk.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/poly.d.ts +50 -0
- package/dist/layout/neato/poly.d.ts.map +1 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +14 -9
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +51 -0
- package/dist/layout/neato/start.d.ts.map +1 -0
- package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
- package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts +0 -9
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/layout/twopi/circle.d.ts +0 -7
- package/dist/layout/twopi/circle.d.ts.map +1 -1
- package/dist/ortho/ortho-parallel.d.ts.map +1 -1
- package/dist/ortho/trap-query.d.ts.map +1 -1
- package/dist/parser/index.d.ts +9 -5
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +9 -3
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts +7 -2
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +7548 -5588
- package/dist/render.js.map +4 -4
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/vpsc/Solver.d.ts +1 -0
- package/dist/vpsc/Solver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/builder.ts +73 -5
- package/src/api/edge-ops.ts +19 -0
- package/src/api/geometry.ts +22 -5
- package/src/async/collect.ts +204 -0
- package/src/async/fonts.ts +61 -0
- package/src/async/render-async.ts +205 -0
- package/src/async/render-into.ts +115 -0
- package/src/async/sanitize.ts +150 -0
- package/src/common/css-font.ts +109 -0
- package/src/common/htmltable-types.ts +4 -5
- package/src/common/make-label.ts +10 -1
- package/src/common/poly-shapes.ts +4 -1
- package/src/common/textmeasure-factory.ts +11 -0
- package/src/common/textmeasure.ts +41 -8
- package/src/common/utils-inputscale.ts +31 -0
- package/src/errors.ts +188 -5
- package/src/gvc/context.ts +96 -17
- package/src/gvc/device.ts +11 -6
- package/src/gvc/image-resolver.ts +25 -2
- package/src/gvc/job.ts +3 -1
- package/src/gvc/usershape.ts +6 -0
- package/src/index.ts +59 -43
- package/src/label/index.ts +6 -2
- package/src/label/node.ts +2 -1
- package/src/label/rectangle.ts +4 -2
- package/src/layout/circo/circular.ts +9 -6
- package/src/layout/dot/pack-components.ts +6 -2
- package/src/layout/dot/position.ts +21 -3
- package/src/layout/fdp/derive.ts +7 -6
- package/src/layout/fdp/index.ts +45 -5
- package/src/layout/fdp/init.ts +7 -6
- package/src/layout/fdp/layout.ts +2 -1
- package/src/layout/fdp/ports.ts +3 -2
- package/src/layout/fdp/xlayout.ts +3 -1
- package/src/layout/neato/adjust-info.ts +332 -0
- package/src/layout/neato/cdt-surface.ts +50 -30
- package/src/layout/neato/constraint-adjust.ts +466 -0
- package/src/layout/neato/edge-len.ts +36 -0
- package/src/layout/neato/fdp-adjust.ts +117 -12
- package/src/layout/neato/index.ts +36 -54
- package/src/layout/neato/init.ts +21 -36
- package/src/layout/neato/kk-paths.ts +146 -0
- package/src/layout/neato/kk-solve.ts +64 -0
- package/src/layout/neato/kk.ts +322 -0
- package/src/layout/neato/multispline-router.ts +4 -3
- package/src/layout/neato/poly.ts +493 -0
- package/src/layout/neato/sgd-dijkstra.ts +125 -0
- package/src/layout/neato/sgd.ts +63 -40
- package/src/layout/neato/start.ts +222 -0
- package/src/layout/neato/vpsc-adjust.ts +93 -0
- package/src/layout/sfdp/index.ts +4 -5
- package/src/layout/sfdp/init.ts +40 -4
- package/src/layout/sfdp/spring-driver.ts +30 -1
- package/src/layout/twopi/circle.ts +2 -1
- package/src/ortho/ortho-parallel.ts +2 -1
- package/src/ortho/trap-query.ts +2 -1
- package/src/parser/index.ts +11 -11
- package/src/render/index.ts +5 -0
- package/src/render/public.ts +24 -24
- package/src/render/svg.ts +1 -1
- package/src/render/xdot-public.ts +19 -18
- package/src/util/xml.ts +24 -30
- package/src/vpsc/Solver.ts +5 -3
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Info_t machinery of neatogen's adjust.c shared by the Voronoi and
|
|
5
|
+
* oscale (`AM_SCALE`, "old scaling") adjusters: per-node polygons and site
|
|
6
|
+
* coordinates, the clipping bounding box, site sorting, coincident-node
|
|
7
|
+
* separation, the exact overlap count, and `sAdjust`. Voronoi-only functions
|
|
8
|
+
* (`vAdjust`, `newPos`, `addCorners`, `increaseBoundBox`, `nextOne`) are not
|
|
9
|
+
* ported.
|
|
10
|
+
*
|
|
11
|
+
* C keeps `nodeInfo`, `nsites`, `pxmin..pymax`, `xmin..deltax` as file
|
|
12
|
+
* statics and `state_t` for the rest; here they are one `InfoState` created
|
|
13
|
+
* per call (`newInfoState`). Units are inches, as in C. `Verbose` stderr
|
|
14
|
+
* output ("overlap [i] : n", "Number of iterations") is not reproduced: the
|
|
15
|
+
* port has no verbosity switch. `qsort` is unstable in C; `Array.sort` is
|
|
16
|
+
* stable, which only changes the order among sites with identical
|
|
17
|
+
* coordinates (all of which `rmEquality` treats as one group).
|
|
18
|
+
*
|
|
19
|
+
* @see lib/neatogen/adjust.c
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { Graph } from '../../model/graph.js';
|
|
23
|
+
import type { Node } from '../../model/node.js';
|
|
24
|
+
import type { Point } from '../../model/geom.js';
|
|
25
|
+
import { aggetGraph } from '../fdp/fdp-model.js';
|
|
26
|
+
import { makeAddPoly, makePoly, polyOverlap } from './poly.js';
|
|
27
|
+
import type { Poly } from './poly.js';
|
|
28
|
+
import { sepFactor } from './sep-factor.js';
|
|
29
|
+
|
|
30
|
+
/** Default `voro_margin`. @see lib/neatogen/adjust.c:chkBoundBox */
|
|
31
|
+
const DEFAULT_VORO_MARGIN = 0.05;
|
|
32
|
+
/** Growth per oscale iteration. @see lib/neatogen/adjust.c:incr */
|
|
33
|
+
const INCR = 0.05;
|
|
34
|
+
/** @see lib/common/geom.h:POINTS_PER_INCH */
|
|
35
|
+
const POINTS_PER_INCH = 72;
|
|
36
|
+
|
|
37
|
+
/** @see lib/neatogen/site.h:Site */
|
|
38
|
+
export interface Site {
|
|
39
|
+
coord: Point;
|
|
40
|
+
sitenbr: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @see lib/neatogen/info.h:Info_t (Voronoi `verts` fields omitted) */
|
|
44
|
+
export interface InfoT {
|
|
45
|
+
site: Site;
|
|
46
|
+
overlaps: boolean;
|
|
47
|
+
poly: Poly;
|
|
48
|
+
node: Node;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `state_t` plus the adjust.c file statics (`nodeInfo`, `nsites`, `pxmin`…,
|
|
53
|
+
* `xmin`…). `nsites` is `nodeInfo.length`.
|
|
54
|
+
* @see lib/neatogen/adjust.c:state_t
|
|
55
|
+
*/
|
|
56
|
+
export interface InfoState {
|
|
57
|
+
nodeInfo: InfoT[];
|
|
58
|
+
sites: Site[];
|
|
59
|
+
nw: Point;
|
|
60
|
+
ne: Point;
|
|
61
|
+
sw: Point;
|
|
62
|
+
se: Point;
|
|
63
|
+
pxmin: number;
|
|
64
|
+
pxmax: number;
|
|
65
|
+
pymin: number;
|
|
66
|
+
pymax: number;
|
|
67
|
+
xmin: number;
|
|
68
|
+
xmax: number;
|
|
69
|
+
ymin: number;
|
|
70
|
+
ymax: number;
|
|
71
|
+
deltax: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fresh, empty state (`state_t st = {0}` and zeroed statics). */
|
|
75
|
+
export function newInfoState(): InfoState {
|
|
76
|
+
return {
|
|
77
|
+
nodeInfo: [], sites: [],
|
|
78
|
+
nw: { x: 0, y: 0 }, ne: { x: 0, y: 0 }, sw: { x: 0, y: 0 }, se: { x: 0, y: 0 },
|
|
79
|
+
pxmin: 0, pxmax: 0, pymin: 0, pymax: 0,
|
|
80
|
+
xmin: 0, xmax: 0, ymin: 0, ymax: 0, deltax: 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @see lib/neatogen/adjust.c:setBoundBox */
|
|
85
|
+
function setBoundBox(st: InfoState, ll: Point, ur: Point): void {
|
|
86
|
+
st.pxmin = ll.x;
|
|
87
|
+
st.pxmax = ur.x;
|
|
88
|
+
st.pymin = ll.y;
|
|
89
|
+
st.pymax = ur.y;
|
|
90
|
+
st.nw = { x: st.pxmin, y: st.pymax };
|
|
91
|
+
st.sw = { x: st.pxmin, y: st.pymin };
|
|
92
|
+
st.ne = { x: st.pxmax, y: st.pymax };
|
|
93
|
+
st.se = { x: st.pxmax, y: st.pymin };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** C `atof` on the leading numeric prefix (0 when there is none). */
|
|
97
|
+
function atof(s: string): number {
|
|
98
|
+
const m = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
|
|
99
|
+
return m === null ? 0 : parseFloat(m[0]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extremes of the polygons, then the bounding box as a `voro_margin`
|
|
104
|
+
* (default 0.05) expansion of them. Requires at least one site.
|
|
105
|
+
* @see lib/neatogen/adjust.c:chkBoundBox
|
|
106
|
+
*/
|
|
107
|
+
export function chkBoundBox(st: InfoState, graph: Graph): void {
|
|
108
|
+
let xMin = Number.MAX_VALUE;
|
|
109
|
+
let yMin = Number.MAX_VALUE;
|
|
110
|
+
let xMax = -Number.MAX_VALUE;
|
|
111
|
+
let yMax = -Number.MAX_VALUE;
|
|
112
|
+
for (const ip of st.nodeInfo) {
|
|
113
|
+
const pp = ip.poly;
|
|
114
|
+
xMin = Math.min(xMin, pp.origin.x + ip.site.coord.x);
|
|
115
|
+
yMin = Math.min(yMin, pp.origin.y + ip.site.coord.y);
|
|
116
|
+
xMax = Math.max(xMax, pp.corner.x + ip.site.coord.x);
|
|
117
|
+
yMax = Math.max(yMax, pp.corner.y + ip.site.coord.y);
|
|
118
|
+
}
|
|
119
|
+
const marg = aggetGraph(graph, 'voro_margin');
|
|
120
|
+
const margin = marg !== undefined && marg !== '' ? atof(marg) : DEFAULT_VORO_MARGIN;
|
|
121
|
+
const ydelta = margin * (yMax - yMin);
|
|
122
|
+
const xdelta = margin * (xMax - xMin);
|
|
123
|
+
setBoundBox(st, { x: xMin - xdelta, y: yMin - ydelta }, { x: xMax + xdelta, y: yMax + ydelta });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create the Info_t array for every node of `graph` (polygons per
|
|
128
|
+
* `sepFactor`: additive margins use `makeAddPoly` in inches, scale margins
|
|
129
|
+
* use `makePoly`). Returns false (C: 1, `nodeInfo` freed) when a polygon
|
|
130
|
+
* cannot be made.
|
|
131
|
+
* @see lib/neatogen/adjust.c:makeInfo
|
|
132
|
+
*/
|
|
133
|
+
export function makeInfo(st: InfoState, graph: Graph): boolean {
|
|
134
|
+
const pmargin = { ...sepFactor(graph) };
|
|
135
|
+
if (pmargin.doAdd) {
|
|
136
|
+
pmargin.x /= POINTS_PER_INCH;
|
|
137
|
+
pmargin.y /= POINTS_PER_INCH;
|
|
138
|
+
}
|
|
139
|
+
const polyf = pmargin.doAdd ? makeAddPoly : makePoly;
|
|
140
|
+
const info: InfoT[] = [];
|
|
141
|
+
for (const node of graph.nodes.values()) {
|
|
142
|
+
const poly = polyf(node, pmargin.x, pmargin.y);
|
|
143
|
+
if (poly === null) {
|
|
144
|
+
st.nodeInfo = [];
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const pos = node.info.pos ?? [0, 0];
|
|
148
|
+
info.push({
|
|
149
|
+
site: { coord: { x: pos[0], y: pos[1] }, sitenbr: info.length },
|
|
150
|
+
overlaps: false, poly, node,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
st.nodeInfo = info;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Sort sites on y, then x. @see lib/neatogen/adjust.c:scomp */
|
|
158
|
+
function scomp(s1: Site, s2: Site): number {
|
|
159
|
+
if (s1.coord.y < s2.coord.y) return -1;
|
|
160
|
+
if (s1.coord.y > s2.coord.y) return 1;
|
|
161
|
+
if (s1.coord.x < s2.coord.x) return -1;
|
|
162
|
+
if (s1.coord.x > s2.coord.x) return 1;
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Fill the site array from `nodeInfo` and sort it with `scomp`.
|
|
168
|
+
* @see lib/neatogen/adjust.c:sortSites
|
|
169
|
+
*/
|
|
170
|
+
export function sortSites(st: InfoState): void {
|
|
171
|
+
st.sites = st.nodeInfo.map((ip) => ip.site);
|
|
172
|
+
st.sites.sort(scomp);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @see lib/neatogen/adjust.c:geomUpdate */
|
|
176
|
+
export function geomUpdate(st: InfoState, doSort: boolean): void {
|
|
177
|
+
if (doSort) sortSites(st);
|
|
178
|
+
st.xmin = Number.MAX_VALUE;
|
|
179
|
+
st.xmax = -Number.MAX_VALUE;
|
|
180
|
+
for (const s of st.sites) {
|
|
181
|
+
st.xmin = Math.min(st.xmin, s.coord.x);
|
|
182
|
+
st.xmax = Math.max(st.xmax, s.coord.x);
|
|
183
|
+
}
|
|
184
|
+
st.ymin = st.sites[0]!.coord.y;
|
|
185
|
+
st.ymax = st.sites[st.sites.length - 1]!.coord.y;
|
|
186
|
+
st.deltax = st.xmax - st.xmin;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether sites `a` and `b` share both coordinates. */
|
|
190
|
+
function sameCoord(a: Site, b: Site): boolean {
|
|
191
|
+
return a.coord.x === b.coord.x && a.coord.y === b.coord.y;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Spread a coincident run `[ip+1, kp)` evenly toward the site `kp` on its row. */
|
|
195
|
+
function spreadToRight(sites: Site[], ip: number, kp: number, cnt: number): void {
|
|
196
|
+
const xdel = (sites[kp]!.coord.x - sites[ip]!.coord.x) / cnt;
|
|
197
|
+
let i = 1;
|
|
198
|
+
for (let jp = ip + 1; jp < kp; jp++) {
|
|
199
|
+
sites[jp]!.coord.x += i * xdel;
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Chain a coincident run `[ip+1, kp)` rightward by polygon widths. */
|
|
205
|
+
function chainByWidth(st: InfoState, ip: number, kp: number): void {
|
|
206
|
+
let a = ip;
|
|
207
|
+
for (let jp = ip + 1; jp < kp; a++, jp++) {
|
|
208
|
+
let info = st.nodeInfo[st.sites[a]!.sitenbr]!;
|
|
209
|
+
let xdel = info.poly.corner.x - info.poly.origin.x;
|
|
210
|
+
info = st.nodeInfo[st.sites[jp]!.sitenbr]!;
|
|
211
|
+
xdel += info.poly.corner.x - info.poly.origin.x;
|
|
212
|
+
st.sites[jp]!.coord.x = st.sites[a]!.coord.x + xdel / 2;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check for nodes with identical positions and tweak the positions.
|
|
218
|
+
* @see lib/neatogen/adjust.c:rmEquality
|
|
219
|
+
*/
|
|
220
|
+
export function rmEquality(st: InfoState): void {
|
|
221
|
+
sortSites(st);
|
|
222
|
+
const sites = st.sites;
|
|
223
|
+
const end = sites.length;
|
|
224
|
+
let ip = 0;
|
|
225
|
+
while (ip < end) {
|
|
226
|
+
let jp = ip + 1;
|
|
227
|
+
if (jp >= end || !sameCoord(sites[jp]!, sites[ip]!)) {
|
|
228
|
+
ip = jp;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
/* Find first node kp with position different from ip */
|
|
232
|
+
let cnt = 2;
|
|
233
|
+
let kp = jp + 1;
|
|
234
|
+
while (kp < end && sameCoord(sites[kp]!, sites[ip]!)) {
|
|
235
|
+
cnt++;
|
|
236
|
+
jp = kp;
|
|
237
|
+
kp = jp + 1;
|
|
238
|
+
}
|
|
239
|
+
/* If next node exists and is on the same line */
|
|
240
|
+
if (kp < end && sites[kp]!.coord.y === sites[ip]!.coord.y) {
|
|
241
|
+
spreadToRight(sites, ip, kp, cnt);
|
|
242
|
+
} else {
|
|
243
|
+
chainByWidth(st, ip, kp);
|
|
244
|
+
}
|
|
245
|
+
ip = kp;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Count number of node-node overlaps at iteration `iter` (exact polygon
|
|
251
|
+
* test), flagging the nodes involved.
|
|
252
|
+
* @see lib/neatogen/adjust.c:countOverlap
|
|
253
|
+
*/
|
|
254
|
+
export function countOverlap(st: InfoState, _iter: number): number {
|
|
255
|
+
const info = st.nodeInfo;
|
|
256
|
+
let count = 0;
|
|
257
|
+
for (const ip of info) ip.overlaps = false;
|
|
258
|
+
for (let i = 0; i < info.length - 1; i++) {
|
|
259
|
+
const ip = info[i]!;
|
|
260
|
+
for (let j = i + 1; j < info.length; j++) {
|
|
261
|
+
const jp = info[j]!;
|
|
262
|
+
if (polyOverlap(ip.site.coord, ip.poly, jp.site.coord, jp.poly)) {
|
|
263
|
+
count++;
|
|
264
|
+
ip.overlaps = true;
|
|
265
|
+
jp.overlaps = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return count;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @see lib/neatogen/adjust.c:rePos */
|
|
273
|
+
export function rePos(st: InfoState): void {
|
|
274
|
+
const f = 1.0 + INCR;
|
|
275
|
+
for (const ip of st.nodeInfo) {
|
|
276
|
+
ip.site.coord.x *= f;
|
|
277
|
+
ip.site.coord.y *= f;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Scale positions up by 5% until no polygons overlap. Returns 1 when
|
|
283
|
+
* positions changed, 0 when there was nothing to do.
|
|
284
|
+
* @see lib/neatogen/adjust.c:sAdjust
|
|
285
|
+
*/
|
|
286
|
+
export function sAdjust(st: InfoState): number {
|
|
287
|
+
let iterCnt = 0;
|
|
288
|
+
if (countOverlap(st, iterCnt) === 0) return 0;
|
|
289
|
+
rmEquality(st);
|
|
290
|
+
for (;;) {
|
|
291
|
+
rePos(st);
|
|
292
|
+
iterCnt++;
|
|
293
|
+
if (countOverlap(st, iterCnt) === 0) break;
|
|
294
|
+
}
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Enter new node positions into the graph.
|
|
300
|
+
* @see lib/neatogen/adjust.c:updateGraph
|
|
301
|
+
*/
|
|
302
|
+
export function updateGraph(st: InfoState): void {
|
|
303
|
+
for (const ip of st.nodeInfo) {
|
|
304
|
+
ip.node.info.pos = [ip.site.coord.x, ip.site.coord.y];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Exact overlap count of the graph's nodes (`makeInfo` + `countOverlap(0)`);
|
|
310
|
+
* 0 when `makeInfo` fails.
|
|
311
|
+
* @see lib/neatogen/adjust.c:countOverlap
|
|
312
|
+
*/
|
|
313
|
+
export function countOverlapIn(g: Graph): number {
|
|
314
|
+
const st = newInfoState();
|
|
315
|
+
if (!makeInfo(st, g)) return 0;
|
|
316
|
+
return countOverlap(st, 0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The AM_SCALE branch of `removeOverlapWith`: `makeInfo`, `chkBoundBox`,
|
|
321
|
+
* `sAdjust`, and `updateGraph` when sAdjust moved nodes. Returns sAdjust's
|
|
322
|
+
* result (0 when `makeInfo` fails). Requires at least one node.
|
|
323
|
+
* @see lib/neatogen/adjust.c:removeOverlapWith
|
|
324
|
+
*/
|
|
325
|
+
export function sAdjustGraph(g: Graph): number {
|
|
326
|
+
const st = newInfoState();
|
|
327
|
+
if (!makeInfo(st, g)) return 0;
|
|
328
|
+
chkBoundBox(st, g);
|
|
329
|
+
const ret = sAdjust(st);
|
|
330
|
+
if (ret !== 0) updateGraph(st);
|
|
331
|
+
return ret;
|
|
332
|
+
}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { incircle, orient2d } from './delaunay.js';
|
|
38
|
+
import { InternalError } from '../../errors.js';
|
|
38
39
|
|
|
39
40
|
/** Triangulated surface. @see lib/neatogen/delaunay.h:surface_t */
|
|
40
41
|
export interface Surface {
|
|
@@ -164,7 +165,7 @@ class Cdt {
|
|
|
164
165
|
let i = 0;
|
|
165
166
|
if (e !== null) {
|
|
166
167
|
i = f.e.indexOf(e);
|
|
167
|
-
if (i < 0) throw new
|
|
168
|
+
if (i < 0) throw new InternalError('cdt: edge not in face');
|
|
168
169
|
}
|
|
169
170
|
const j = (i + 1) % 3;
|
|
170
171
|
const k = (i + 2) % 3;
|
|
@@ -395,10 +396,12 @@ class Cdt {
|
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
/** gts_delaunay_add_vertex. Returns null on success, the duplicate
|
|
398
|
-
* vertex id for coincident points, or
|
|
399
|
+
* vertex id for coincident points, or `v` itself when `v` lies outside
|
|
400
|
+
* the hull: C does not add it and the caller (tri) carries on.
|
|
401
|
+
* @see gts-0.7.6/src/cdt.c:668-680 gts_delaunay_add_vertex */
|
|
399
402
|
addVertex(v: number): number | null {
|
|
400
403
|
const f = this.pointLocate(this.px[v]!, this.py[v]!);
|
|
401
|
-
if (f === null)
|
|
404
|
+
if (f === null) return v;
|
|
402
405
|
return this.addVertexToFace(v, f);
|
|
403
406
|
}
|
|
404
407
|
|
|
@@ -476,48 +479,59 @@ class Cdt {
|
|
|
476
479
|
}
|
|
477
480
|
}
|
|
478
481
|
|
|
479
|
-
/**
|
|
482
|
+
/**
|
|
483
|
+
* Returns the constraints the walk crossed and removed (C's returned
|
|
484
|
+
* GSList): a crossed constraint edge is collected, kept even when it loses
|
|
485
|
+
* all its triangles, and the walk continues through it.
|
|
486
|
+
* @see cdt.c:remove_intersected_edge (incl. the NEXT_CUT macro)
|
|
487
|
+
*/
|
|
480
488
|
private removeIntersectedEdge(
|
|
481
489
|
sv1: number, sv2: number, e: CEdge, f: CFace,
|
|
482
490
|
left: CEdge[], right: CEdge[],
|
|
483
|
-
):
|
|
484
|
-
|
|
491
|
+
): CEdge[] {
|
|
492
|
+
const constraint: CEdge[] = e.constraint ? [e] : [];
|
|
485
493
|
|
|
486
494
|
const r = this.ringFrom(f, e);
|
|
487
495
|
const v1 = r.v1; const v2 = r.v2; const v3 = r.v3;
|
|
488
496
|
const e1 = r.e2; const e2 = r.e3; // C shadows: e←r.e1, e1←r.e2, e2←r.e3
|
|
489
|
-
const cut = (edge: CEdge, edge1: CEdge, list: CEdge[]):
|
|
497
|
+
const cut = (edge: CEdge, edge1: CEdge, list: CEdge[]): CEdge[] => {
|
|
490
498
|
const next = this.neighbor(f, edge);
|
|
491
499
|
this.removeTriangles(e);
|
|
492
|
-
if (e.tris.length === 0) this.destroyEdge(e);
|
|
493
|
-
if (next === null) throw new
|
|
500
|
+
if (constraint.length === 0 && e.tris.length === 0) this.destroyEdge(e);
|
|
501
|
+
if (next === null) throw new InternalError('cdt: constraint walk left the surface');
|
|
494
502
|
list.push(edge1);
|
|
495
|
-
|
|
503
|
+
return constraint.concat(
|
|
504
|
+
this.removeIntersectedEdge(sv1, sv2, edge, next, left, right));
|
|
496
505
|
};
|
|
497
506
|
|
|
498
507
|
const o1 = this.orient(v2, v3, sv2);
|
|
499
508
|
const o2 = this.orient(v3, v1, sv2);
|
|
500
509
|
if (o1 === 0) {
|
|
501
510
|
// terminal: s.v2 is v3
|
|
511
|
+
if (o2 !== 0) throw new InternalError('cdt: remove_intersected_edge: o2 != 0 at terminal'); // cdt.c:887 g_assert
|
|
502
512
|
this.removeTriangles(e);
|
|
503
|
-
if (e.tris.length === 0) this.destroyEdge(e);
|
|
513
|
+
if (constraint.length === 0 && e.tris.length === 0) this.destroyEdge(e);
|
|
504
514
|
left.push(e2);
|
|
505
515
|
right.push(e1);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const o3 = this.orient(sv1, sv2, v3);
|
|
512
|
-
if (o3 > 0) cut(e1, e2, left);
|
|
513
|
-
else cut(e2, e1, right);
|
|
516
|
+
return constraint;
|
|
517
|
+
}
|
|
518
|
+
if (o1 > 0) {
|
|
519
|
+
if (o2 > 0) throw new InternalError('cdt: remove_intersected_edge: o2 > 0 with o1 > 0'); // cdt.c:895 g_assert
|
|
520
|
+
return cut(e2, e1, right);
|
|
514
521
|
}
|
|
522
|
+
if (o2 >= 0) return cut(e1, e2, left);
|
|
523
|
+
const o3 = this.orient(sv1, sv2, v3);
|
|
524
|
+
return o3 > 0 ? cut(e1, e2, left) : cut(e2, e1, right);
|
|
515
525
|
}
|
|
516
526
|
|
|
517
|
-
/**
|
|
527
|
+
/**
|
|
528
|
+
* Returns the reference face (C's `*ref` out-param) and the constraints the
|
|
529
|
+
* walk crossed and removed (C's returned GSList).
|
|
530
|
+
* @see cdt.c:remove_intersected_vertex
|
|
531
|
+
*/
|
|
518
532
|
private removeIntersectedVertex(
|
|
519
533
|
sv1: number, sv2: number, left: CEdge[], right: CEdge[],
|
|
520
|
-
): CFace {
|
|
534
|
+
): { ref: CFace; constraints: CEdge[] } {
|
|
521
535
|
// triangles around sv1 in the surface
|
|
522
536
|
for (const t of this.surface) {
|
|
523
537
|
if (t.v[0] !== sv1 && t.v[1] !== sv1 && t.v[2] !== sv1) continue;
|
|
@@ -538,25 +552,30 @@ class Cdt {
|
|
|
538
552
|
const e2 = rr.e2; // v3–sv1 side
|
|
539
553
|
const e1 = rr.e3; // sv1–v2 side
|
|
540
554
|
|
|
541
|
-
|
|
555
|
+
// s.v2 inside (or on far edge of) t — nothing to remove
|
|
556
|
+
if (o3 >= 0) return { ref: t, constraints: [] };
|
|
542
557
|
|
|
543
558
|
// remove t but keep it floating as the attribute reference
|
|
544
559
|
this.removeFace(t, true);
|
|
545
560
|
left.push(e2);
|
|
546
561
|
right.push(e1);
|
|
547
|
-
if (next === null) throw new
|
|
548
|
-
this.removeIntersectedEdge(sv1, sv2, e, next, left, right);
|
|
549
|
-
return t;
|
|
562
|
+
if (next === null) throw new InternalError('cdt: constraint walk left the surface');
|
|
563
|
+
const constraints = this.removeIntersectedEdge(sv1, sv2, e, next, left, right);
|
|
564
|
+
return { ref: t, constraints };
|
|
550
565
|
}
|
|
551
|
-
throw new
|
|
566
|
+
throw new InternalError('cdt: no wedge triangle found at constraint endpoint');
|
|
552
567
|
}
|
|
553
568
|
|
|
554
|
-
/**
|
|
555
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Returns the constraints crossing `c` that were removed (C's return
|
|
571
|
+
* value; tri() ignores it).
|
|
572
|
+
* @see cdt.c:gts_delaunay_add_constraint
|
|
573
|
+
*/
|
|
574
|
+
addConstraint(c: CEdge): CEdge[] {
|
|
556
575
|
const left: CEdge[] = [];
|
|
557
576
|
const right: CEdge[] = [];
|
|
558
|
-
const ref = this.removeIntersectedVertex(c.v1, c.v2, left, right);
|
|
559
|
-
if (ref.inSurface) return; //
|
|
577
|
+
const { ref, constraints } = this.removeIntersectedVertex(c.v1, c.v2, left, right);
|
|
578
|
+
if (ref.inSurface) return constraints; // already realized (o3 >= 0 path)
|
|
560
579
|
|
|
561
580
|
// C: triangulate_polygon(prepend(reverse(right), c)) then
|
|
562
581
|
// triangulate_polygon(prepend(left, c)).
|
|
@@ -576,6 +595,7 @@ class Cdt {
|
|
|
576
595
|
this.triangulatePolygon([c, ...[...left].reverse()]);
|
|
577
596
|
// ref face was kept floating; destroy it now (detach from edges)
|
|
578
597
|
this.detachFace(ref);
|
|
598
|
+
return constraints;
|
|
579
599
|
}
|
|
580
600
|
|
|
581
601
|
// -------------------------------------------------------------------
|