12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- /* Module for packing disconnected graphs together.
- * Based on "Disconnected Graph Layout and the Polyomino Packing Approach",
- * K. Freivalds et al., GD0'01, LNCS 2265, pp. 378-391.
- */
- #include <assert.h>
- #include <common/pointset.h>
- #include <common/render.h>
- #include <math.h>
- #include <pack/pack.h>
- #include <stdbool.h>
- #include <stddef.h>
- #include <util/alloc.h>
- #include <util/prisize_t.h>
- #include <util/sort.h>
- #include <util/startswith.h>
- #include <util/streq.h>
- #define C 100 /* Max. avg. polyomino size */
- #define MOVEPT(p) ((p).x += dx, (p).y += dy)
- /// given cell size `s`, how many cells are required by size x?
- static int GRID(double x, int s) {
- const double required = ceil(x / s);
- return (int)required;
- }
- /* Given grid cell size s, CVAL(v:int,s:int) returns index of cell containing
- * point v */
- #define CVAL(v, s) ((v) >= 0 ? (v) / (s) : (((v) + 1) / (s)) - 1)
- /* Given grid cell size s, CELL(p:point,s:int) sets p to cell containing point p
- */
- #define CELL(p, s) ((p).x = CVAL((p).x, s), (p).y = CVAL((p).y, (s)))
- typedef struct {
- int perim; /* half size of bounding rectangle perimeter */
- pointf *cells; ///< cells in covering polyomino
- int nc; /* no. of cells */
- size_t index; ///< index in original array
- } ginfo;
- typedef struct {
- double width, height;
- size_t index; ///< index in original array
- } ainfo;
- /* Compute grid step size. This is a root of the
- * quadratic equation a×l² + b×l + c, where a, b and
- * c are defined below.
- */
- static int computeStep(size_t ng, boxf *bbs, unsigned int margin) {
- double l1, l2;
- double a, b, c, d, r;
- double W, H; /* width and height of graph, with margin */
- int root;
- a = C * (double)ng - 1;
- c = 0;
- b = 0;
- for (size_t i = 0; i < ng; i++) {
- boxf bb = bbs[i];
- W = bb.UR.x - bb.LL.x + 2 * margin;
- H = bb.UR.y - bb.LL.y + 2 * margin;
- b -= W + H;
- c -= W * H;
- }
- d = b * b - 4.0 * a * c;
- assert(d >= 0);
- r = sqrt(d);
- l1 = (-b + r) / (2 * a);
- l2 = (-b - r) / (2 * a);
- root = (int)l1;
- if (root == 0)
- root = 1;
- if (Verbose > 2) {
- fprintf(stderr, "Packing: compute grid size\n");
- fprintf(stderr, "a %f b %f c %f d %f r %f\n", a, b, c, d, r);
- fprintf(stderr, "root %d (%f) %d (%f)\n", root, l1, (int)l2, l2);
- fprintf(stderr, " r1 %f r2 %f\n", a * l1 * l1 + b * l1 + c,
- a * l2 * l2 + b * l2 + c);
- }
- return root;
- }
- /* Comparison function for polyominoes.
- * Size is determined by perimeter.
- */
- static int cmpf(const void *X, const void *Y) {
- const ginfo *x = *(ginfo *const *)X;
- const ginfo *y = *(ginfo *const *)Y;
- /* flip order to get descending array */
- if (y->perim < x->perim) {
- return -1;
- }
- if (y->perim > x->perim) {
- return 1;
- }
- return 0;
- }
- /// `sgn`, as defined in Graphics Gems I, §11.8, pp. 99
- static int sgn(int x) { return x > 0 ? 1 : -1; }
- /* Mark cells crossed by line from cell p to cell q.
- * Bresenham's algorithm, from Graphics Gems I, pp. 99-100.
- */
- static void fillLine(pointf p, pointf q, PointSet *ps) {
- int x1 = ROUND(p.x);
- int y1 = ROUND(p.y);
- int x2 = ROUND(q.x);
- int y2 = ROUND(q.y);
- int d, x, y, ax, ay, sx, sy, dx, dy;
- dx = x2 - x1;
- ax = abs(dx) << 1;
- sx = sgn(dx);
- dy = y2 - y1;
- ay = abs(dy) << 1;
- sy = sgn(dy);
- x = x1;
- y = y1;
- if (ax > ay) { /* x dominant */
- d = ay - (ax >> 1);
- for (;;) {
- addPS(ps, x, y);
- if (x == x2)
- return;
- if (d >= 0) {
- y += sy;
- d -= ax;
- }
- x += sx;
- d += ay;
- }
- } else { /* y dominant */
- d = ax - (ay >> 1);
- for (;;) {
- addPS(ps, x, y);
- if (y == y2)
- return;
- if (d >= 0) {
- x += sx;
- d -= ay;
- }
- y += sy;
- d += ax;
- }
- }
- }
- /* It appears that spline_edges always have the start point at the
- * beginning and the end point at the end.
- */
- static void fillEdge(Agedge_t *e, pointf p, PointSet *ps, double dx, double dy,
- int ssize, bool doS) {
- size_t k;
- bezier bz;
- pointf pt, hpt;
- Agnode_t *h;
- pt = p;
- /* If doS is false or the edge has not splines, use line segment */
- if (!doS || !ED_spl(e)) {
- h = aghead(e);
- hpt = coord(h);
- MOVEPT(hpt);
- CELL(hpt, ssize);
- fillLine(pt, hpt, ps);
- return;
- }
- for (size_t j = 0; j < ED_spl(e)->size; j++) {
- bz = ED_spl(e)->list[j];
- if (bz.sflag) {
- pt = bz.sp;
- hpt = bz.list[0];
- k = 1;
- } else {
- pt = bz.list[0];
- hpt = bz.list[1];
- k = 2;
- }
- MOVEPT(pt);
- CELL(pt, ssize);
- MOVEPT(hpt);
- CELL(hpt, ssize);
- fillLine(pt, hpt, ps);
- for (; k < bz.size; k++) {
- pt = hpt;
- hpt = bz.list[k];
- MOVEPT(hpt);
- CELL(hpt, ssize);
- fillLine(pt, hpt, ps);
- }
- if (bz.eflag) {
- pt = hpt;
- hpt = bz.ep;
- MOVEPT(hpt);
- CELL(hpt, ssize);
- fillLine(pt, hpt, ps);
- }
- }
- }
- /* Generate polyomino info from graph using the bounding box of
- * the graph.
- */
- static void genBox(boxf bb0, ginfo *info, int ssize, unsigned int margin,
- pointf center, char *s) {
- PointSet *ps;
- int W, H;
- pointf UR, LL;
- double x, y;
- const boxf bb = {.LL = {.x = round(bb0.LL.x), .y = round(bb0.LL.y)},
- .UR = {.x = round(bb0.UR.x), .y = round(bb0.UR.y)}};
- ps = newPS();
- LL.x = center.x - margin;
- LL.y = center.y - margin;
- UR.x = center.x + bb.UR.x - bb.LL.x + margin;
- UR.y = center.y + bb.UR.y - bb.LL.y + margin;
- CELL(LL, ssize);
- LL = (pointf){.x = round(LL.x), .y = round(LL.y)};
- CELL(UR, ssize);
- UR = (pointf){.x = round(UR.x), .y = round(UR.y)};
- for (x = LL.x; x <= UR.x; x++)
- for (y = LL.y; y <= UR.y; y++)
- addPS(ps, x, y);
- info->cells = pointsOf(ps);
- info->nc = sizeOf(ps);
- W = GRID(bb0.UR.x - bb0.LL.x + 2 * margin, ssize);
- H = GRID(bb0.UR.y - bb0.LL.y + 2 * margin, ssize);
- info->perim = W + H;
- if (Verbose > 2) {
- int i;
- fprintf(stderr, "%s no. cells %d W %d H %d\n", s, info->nc, W, H);
- for (i = 0; i < info->nc; i++)
- fprintf(stderr, " %.0f %.0f cell\n", info->cells[i].x, info->cells[i].y);
- }
- freePS(ps);
- }
- /* Generate polyomino info from graph.
- * We add all cells covered partially by the bounding box of the
- * node. If doSplines is true and an edge has a spline, we use the
- * polyline determined by the control point. Otherwise,
- * we use each cell crossed by a straight edge between the head and tail.
- * If mode = l_clust, we use the graph's GD_clust array to treat the
- * top level clusters like large nodes.
- * Returns 0 if okay.
- */
- static int genPoly(Agraph_t *root, Agraph_t *g, ginfo *info, int ssize,
- pack_info *pinfo, pointf center) {
- PointSet *ps;
- int W, H;
- Agraph_t *eg; /* graph containing edges */
- Agnode_t *n;
- Agedge_t *e;
- graph_t *subg;
- unsigned int margin = pinfo->margin;
- bool doSplines = pinfo->doSplines;
- if (root)
- eg = root;
- else
- eg = g;
- ps = newPS();
- const double dx = center.x - round(GD_bb(g).LL.x);
- const double dy = center.y - round(GD_bb(g).LL.y);
- if (pinfo->mode == l_clust) {
- int i;
- /* backup the alg data */
- void **alg = gv_calloc(agnnodes(g), sizeof(void *));
- for (i = 0, n = agfstnode(g); n; n = agnxtnode(g, n)) {
- alg[i++] = ND_alg(n);
- ND_alg(n) = 0;
- }
- /* do bbox of top clusters */
- for (i = 1; i <= GD_n_cluster(g); i++) {
- subg = GD_clust(g)[i];
- boxf bb = {
- .LL = {.x = round(GD_bb(subg).LL.x), .y = round(GD_bb(subg).LL.y)},
- .UR = {.x = round(GD_bb(subg).UR.x), .y = round(GD_bb(subg).UR.y)}};
- if (bb.UR.x > bb.LL.x && bb.UR.y > bb.LL.y) {
- MOVEPT(bb.LL);
- MOVEPT(bb.UR);
- bb.LL.x -= margin;
- bb.LL.y -= margin;
- bb.UR.x += margin;
- bb.UR.y += margin;
- CELL(bb.LL, ssize);
- bb.LL = (pointf){.x = round(bb.LL.x), .y = round(bb.LL.y)};
- CELL(bb.UR, ssize);
- bb.UR = (pointf){.x = round(bb.UR.x), .y = round(bb.UR.y)};
- for (double x = bb.LL.x; x <= bb.UR.x; x++)
- for (double y = bb.LL.y; y <= bb.UR.y; y++)
- addPS(ps, x, y);
- /* note which nodes are in clusters */
- for (n = agfstnode(subg); n; n = agnxtnode(subg, n))
- ND_clust(n) = subg;
- }
- }
- /* now do remaining nodes and edges */
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- const pointf ptf = coord(n);
- pointf pt = {.x = round(ptf.x), .y = round(ptf.y)};
- MOVEPT(pt);
- if (!ND_clust(n)) { /* n is not in a top-level cluster */
- const pointf s2 = {.x = round(margin + ND_xsize(n) / 2),
- .y = round(margin + ND_ysize(n) / 2)};
- pointf LL = sub_pointf(pt, s2);
- pointf UR = add_pointf(pt, s2);
- CELL(LL, ssize);
- LL = (pointf){.x = round(LL.x), .y = round(LL.y)};
- CELL(UR, ssize);
- UR = (pointf){.x = round(UR.x), .y = round(UR.y)};
- for (double x = LL.x; x <= UR.x; x++)
- for (double y = LL.y; y <= UR.y; y++)
- addPS(ps, x, y);
- CELL(pt, ssize);
- pt = (pointf){.x = round(pt.x), .y = round(pt.y)};
- for (e = agfstout(eg, n); e; e = agnxtout(eg, e)) {
- fillEdge(e, pt, ps, dx, dy, ssize, doSplines);
- }
- } else {
- CELL(pt, ssize);
- pt = (pointf){.x = round(pt.x), .y = round(pt.y)};
- for (e = agfstout(eg, n); e; e = agnxtout(eg, e)) {
- if (ND_clust(n) == ND_clust(aghead(e)))
- continue;
- fillEdge(e, pt, ps, dx, dy, ssize, doSplines);
- }
- }
- }
- /* restore the alg data */
- for (i = 0, n = agfstnode(g); n; n = agnxtnode(g, n)) {
- ND_alg(n) = alg[i++];
- }
- free(alg);
- } else
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- const pointf ptf = coord(n);
- pointf pt = {.x = round(ptf.x), .y = round(ptf.y)};
- MOVEPT(pt);
- pointf s2 = {.x = round(margin + ND_xsize(n) / 2),
- .y = round(margin + ND_ysize(n) / 2)};
- pointf LL = sub_pointf(pt, s2);
- pointf UR = add_pointf(pt, s2);
- CELL(LL, ssize);
- LL = (pointf){.x = round(LL.x), .y = round(LL.y)};
- CELL(UR, ssize);
- UR = (pointf){.x = round(UR.x), .y = round(UR.y)};
- for (double x = LL.x; x <= UR.x; x++)
- for (double y = LL.y; y <= UR.y; y++)
- addPS(ps, x, y);
- CELL(pt, ssize);
- pt = (pointf){.x = round(pt.x), .y = round(pt.y)};
- for (e = agfstout(eg, n); e; e = agnxtout(eg, e)) {
- fillEdge(e, pt, ps, dx, dy, ssize, doSplines);
- }
- }
- info->cells = pointsOf(ps);
- info->nc = sizeOf(ps);
- W = GRID(GD_bb(g).UR.x - GD_bb(g).LL.x + 2 * margin, ssize);
- H = GRID(GD_bb(g).UR.y - GD_bb(g).LL.y + 2 * margin, ssize);
- info->perim = W + H;
- if (Verbose > 2) {
- int i;
- fprintf(stderr, "%s no. cells %d W %d H %d\n", agnameof(g), info->nc, W, H);
- for (i = 0; i < info->nc; i++)
- fprintf(stderr, " %.0f %.0f cell\n", info->cells[i].x, info->cells[i].y);
- }
- freePS(ps);
- return 0;
- }
- /* Check if polyomino fits at given point.
- * If so, add cells to pointset, store point in place and return true.
- */
- static int fits(int x, int y, ginfo *info, PointSet *ps, pointf *place,
- int step, boxf *bbs) {
- pointf *cells = info->cells;
- int n = info->nc;
- int i;
- for (i = 0; i < n; i++) {
- pointf cell = *cells;
- cell.x += x;
- cell.y += y;
- if (inPS(ps, cell))
- return 0;
- cells++;
- }
- const pointf LL = {.x = round(bbs[info->index].LL.x),
- .y = round(bbs[info->index].LL.y)};
- place->x = step * x - LL.x;
- place->y = step * y - LL.y;
- cells = info->cells;
- for (i = 0; i < n; i++) {
- pointf cell = *cells;
- cell.x += x;
- cell.y += y;
- insertPS(ps, cell);
- cells++;
- }
- if (Verbose >= 2)
- fprintf(stderr, "cc (%d cells) at (%d,%d) (%.0f,%.0f)\n", n, x, y, place->x,
- place->y);
- return 1;
- }
- /* Position fixed graph. Store final translation and
- * fill polyomino set. Note that polyomino set for the
- * graph is constructed where it will be.
- */
- static void placeFixed(ginfo *info, PointSet *ps, pointf *place,
- pointf center) {
- pointf *cells = info->cells;
- int n = info->nc;
- int i;
- place->x = -center.x;
- place->y = -center.y;
- for (i = 0; i < n; i++) {
- insertPS(ps, *cells++);
- }
- if (Verbose >= 2)
- fprintf(stderr, "cc (%d cells) at (%.0f,%.0f)\n", n, place->x, place->y);
- }
- /* Search for points on concentric "circles" out
- * from the origin. Check if polyomino can be placed
- * with bounding box origin at point.
- * First graph (i == 0) is centered on the origin if possible.
- */
- static void placeGraph(size_t i, ginfo *info, PointSet *ps, pointf *place,
- int step, unsigned int margin, boxf *bbs) {
- int x, y;
- int bnd;
- boxf bb = bbs[info->index];
- if (i == 0) {
- const int W = GRID(bb.UR.x - bb.LL.x + 2 * margin, step);
- const int H = GRID(bb.UR.y - bb.LL.y + 2 * margin, step);
- if (fits(-W / 2, -H / 2, info, ps, place, step, bbs))
- return;
- }
- if (fits(0, 0, info, ps, place, step, bbs))
- return;
- const double W = ceil(bb.UR.x - bb.LL.x);
- const double H = ceil(bb.UR.y - bb.LL.y);
- if (W >= H) {
- for (bnd = 1;; bnd++) {
- x = 0;
- y = -bnd;
- for (; x < bnd; x++)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; y < bnd; y++)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; x > -bnd; x--)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; y > -bnd; y--)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; x < 0; x++)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- }
- } else {
- for (bnd = 1;; bnd++) {
- y = 0;
- x = -bnd;
- for (; y > -bnd; y--)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; x < bnd; x++)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; y < bnd; y++)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; x > -bnd; x--)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- for (; y > 0; y--)
- if (fits(x, y, info, ps, place, step, bbs))
- return;
- }
- }
- }
- #ifdef DEBUG
- void dumpp(ginfo *info, char *pfx) {
- pointf *cells = info->cells;
- int i, c_cnt = info->nc;
- fprintf(stderr, "%s\n", pfx);
- for (i = 0; i < c_cnt; i++) {
- fprintf(stderr, "%.0f %.0f box\n", cells[i].x, cells[i].y);
- }
- }
- #endif
- /// Sort by user values.
- static int ucmpf(const void *X, const void *Y, void *user_values) {
- const ainfo *x = *(ainfo *const *)X;
- const ainfo *y = *(ainfo *const *)Y;
- const packval_t *userVals = user_values;
- const unsigned int dX = userVals[x->index];
- const unsigned int dY = userVals[y->index];
- if (dX > dY)
- return 1;
- if (dX < dY)
- return -1;
- return 0;
- }
- /// Sort by height + width
- static int acmpf(const void *X, const void *Y) {
- const ainfo *x = *(ainfo *const *)X;
- const ainfo *y = *(ainfo *const *)Y;
- double dX = x->height + x->width;
- double dY = y->height + y->width;
- if (dX < dY)
- return 1;
- if (dX > dY)
- return -1;
- return 0;
- }
- /// step to the next iteration of a matrix cell loop
- ///
- /// @param m Is the matrix in row-major order?
- /// @param c [inout] Column index
- /// @param r [inout] Row index
- /// @param nc Total column count
- /// @param nr Total row count
- static void INC(bool m, size_t *c, size_t *r, size_t nc, size_t nr) {
- if (m) {
- (*c)++;
- if (*c == nc) {
- *c = 0;
- (*r)++;
- }
- } else {
- (*r)++;
- if (*r == nr) {
- *r = 0;
- (*c)++;
- }
- }
- }
- static pointf *arrayRects(size_t ng, boxf *gs, pack_info *pinfo) {
- size_t nr = 0, nc;
- size_t r, c;
- ainfo *info;
- double v, wd, ht;
- pointf *places = gv_calloc(ng, sizeof(pointf));
- boxf bb;
- int sz;
- bool rowMajor;
- /* set up no. of rows and columns */
- sz = pinfo->sz;
- if (pinfo->flags & PK_COL_MAJOR) {
- rowMajor = false;
- if (sz > 0) {
- nr = (size_t)sz;
- nc = (ng + (nr - 1)) / nr;
- } else {
- nr = ceil(sqrt(ng));
- nc = (ng + (nr - 1)) / nr;
- }
- } else {
- rowMajor = true;
- if (sz > 0) {
- nc = (size_t)sz;
- nr = (ng + (nc - 1)) / nc;
- } else {
- nc = ceil(sqrt(ng));
- nr = (ng + (nc - 1)) / nc;
- }
- }
- if (Verbose)
- fprintf(stderr,
- "array packing: %s %" PRISIZE_T " rows %" PRISIZE_T " columns\n",
- rowMajor ? "row major" : "column major", nr, nc);
- double *widths = gv_calloc(nc + 1, sizeof(double));
- double *heights = gv_calloc(nr + 1, sizeof(double));
- ainfo *ip = info = gv_calloc(ng, sizeof(ainfo));
- for (size_t i = 0; i < ng; i++, ip++) {
- bb = gs[i];
- ip->width = bb.UR.x - bb.LL.x + pinfo->margin;
- ip->height = bb.UR.y - bb.LL.y + pinfo->margin;
- ip->index = i;
- }
- ainfo **sinfo = gv_calloc(ng, sizeof(ainfo *));
- for (size_t i = 0; i < ng; i++) {
- sinfo[i] = info + i;
- }
- if (pinfo->vals) {
- gv_sort(sinfo, ng, sizeof(ainfo *), ucmpf, pinfo->vals);
- } else if (!(pinfo->flags & PK_INPUT_ORDER)) {
- qsort(sinfo, ng, sizeof(ainfo *), acmpf);
- }
- /* compute column widths and row heights */
- r = c = 0;
- for (size_t i = 0; i < ng; i++, ip++) {
- ip = sinfo[i];
- widths[c] = fmax(widths[c], ip->width);
- heights[r] = fmax(heights[r], ip->height);
- INC(rowMajor, &c, &r, nc, nr);
- }
- /* convert widths and heights to positions */
- wd = 0;
- for (size_t i = 0; i <= nc; i++) {
- v = widths[i];
- widths[i] = wd;
- wd += v;
- }
- ht = 0;
- for (size_t i = nr; 0 < i; i--) {
- v = heights[i - 1];
- heights[i] = ht;
- ht += v;
- }
- heights[0] = ht;
- /* position rects */
- r = c = 0;
- for (size_t i = 0; i < ng; i++, ip++) {
- ip = sinfo[i];
- const size_t idx = ip->index;
- bb = gs[idx];
- if (pinfo->flags & PK_LEFT_ALIGN)
- places[idx].x = round(widths[c]);
- else if (pinfo->flags & PK_RIGHT_ALIGN)
- places[idx].x = round(widths[c + 1] - (bb.UR.x - bb.LL.x));
- else
- places[idx].x =
- round((widths[c] + widths[c + 1] - bb.UR.x - bb.LL.x) / 2.0);
- if (pinfo->flags & PK_TOP_ALIGN)
- places[idx].y = round(heights[r] - (bb.UR.y - bb.LL.y));
- else if (pinfo->flags & PK_BOT_ALIGN)
- places[idx].y = round(heights[r + 1]);
- else
- places[idx].y =
- round((heights[r] + heights[r + 1] - bb.UR.y - bb.LL.y) / 2.0);
- INC(rowMajor, &c, &r, nc, nr);
- }
- free(info);
- free(sinfo);
- free(widths);
- free(heights);
- return places;
- }
- static pointf *polyRects(size_t ng, boxf *gs, pack_info *pinfo) {
- int stepSize;
- Dict_t *ps;
- /* calculate grid size */
- stepSize = computeStep(ng, gs, pinfo->margin);
- if (Verbose)
- fprintf(stderr, "step size = %d\n", stepSize);
- if (stepSize <= 0)
- return 0;
- /* generate polyomino cover for the rectangles */
- ginfo *info = gv_calloc(ng, sizeof(ginfo));
- for (size_t i = 0; i < ng; i++) {
- info[i].index = i;
- genBox(gs[i], info + i, stepSize, pinfo->margin, (pointf){0}, "");
- }
- /* sort */
- ginfo **sinfo = gv_calloc(ng, sizeof(ginfo *));
- for (size_t i = 0; i < ng; i++) {
- sinfo[i] = info + i;
- }
- qsort(sinfo, ng, sizeof(ginfo *), cmpf);
- ps = newPS();
- pointf *places = gv_calloc(ng, sizeof(pointf));
- for (size_t i = 0; i < ng; i++)
- placeGraph(i, sinfo[i], ps, places + sinfo[i]->index, stepSize,
- pinfo->margin, gs);
- free(sinfo);
- for (size_t i = 0; i < ng; i++)
- free(info[i].cells);
- free(info);
- freePS(ps);
- if (Verbose > 1)
- for (size_t i = 0; i < ng; i++)
- fprintf(stderr, "pos[%" PRISIZE_T "] %.0f %.0f\n", i, places[i].x,
- places[i].y);
- return places;
- }
- /* Given a collection of graphs, reposition them in the plane
- * to not overlap but pack "nicely".
- * ng is the number of graphs
- * gs is a pointer to an array of graph pointers
- * root gives the graph containing the edges; if null, the function
- * looks in each graph in gs for its edges
- * pinfo->margin gives the amount of extra space left around nodes in points
- * If pinfo->doSplines is true, use edge splines, if computed,
- * in calculating polyomino.
- * pinfo->mode specifies the packing granularity and technique:
- * l_node : pack at the node/cluster level
- * l_graph : pack at the bounding box level
- * Returns array of points to which graphs should be translated;
- * the array needs to be freed;
- * Returns NULL if problem occur or if ng == 0.
- *
- * Depends on graph fields GD_bb, node fields ND_pos(inches), ND_xsize and
- * ND_ysize, and edge field ED_spl.
- *
- * FIX: fixed mode does not always work. The fixed ones get translated
- * back to be centered on the origin.
- * FIX: Check CELL and GRID macros for negative coordinates
- * FIX: Check width and height computation
- */
- static pointf *polyGraphs(size_t ng, Agraph_t **gs, Agraph_t *root,
- pack_info *pinfo) {
- int stepSize;
- ginfo *info;
- Dict_t *ps;
- bool *fixed = pinfo->fixed;
- int fixed_cnt = 0;
- boxf fixed_bb = {{0, 0}, {0, 0}};
- if (ng == 0)
- return 0;
- /* update bounding box info for each graph */
- /* If fixed, compute bbox of fixed graphs */
- for (size_t i = 0; i < ng; i++) {
- Agraph_t *g = gs[i];
- compute_bb(g);
- if (fixed && fixed[i]) {
- const boxf bb = {
- .LL = {.x = round(GD_bb(g).LL.x), .y = round(GD_bb(g).LL.y)},
- .UR = {.x = round(GD_bb(g).UR.x), .y = round(GD_bb(g).UR.y)}};
- if (fixed_cnt) {
- fixed_bb.LL.x = fmin(bb.LL.x, fixed_bb.LL.x);
- fixed_bb.LL.y = fmin(bb.LL.y, fixed_bb.LL.y);
- fixed_bb.UR.x = fmax(bb.UR.x, fixed_bb.UR.x);
- fixed_bb.UR.y = fmax(bb.UR.y, fixed_bb.UR.y);
- } else
- fixed_bb = bb;
- fixed_cnt++;
- }
- if (Verbose > 2) {
- fprintf(stderr, "bb[%s] %.5g %.5g %.5g %.5g\n", agnameof(g),
- GD_bb(g).LL.x, GD_bb(g).LL.y, GD_bb(g).UR.x, GD_bb(g).UR.y);
- }
- }
- /* calculate grid size */
- boxf *bbs = gv_calloc(ng, sizeof(boxf));
- for (size_t i = 0; i < ng; i++)
- bbs[i] = GD_bb(gs[i]);
- stepSize = computeStep(ng, bbs, pinfo->margin);
- if (Verbose)
- fprintf(stderr, "step size = %d\n", stepSize);
- if (stepSize <= 0) {
- free(bbs);
- return 0;
- }
- /* generate polyomino cover for the graphs */
- pointf center = {0};
- if (fixed) {
- center.x = round((fixed_bb.LL.x + fixed_bb.UR.x) / 2);
- center.y = round((fixed_bb.LL.y + fixed_bb.UR.y) / 2);
- }
- info = gv_calloc(ng, sizeof(ginfo));
- for (size_t i = 0; i < ng; i++) {
- Agraph_t *g = gs[i];
- info[i].index = i;
- if (pinfo->mode == l_graph)
- genBox(GD_bb(g), info + i, stepSize, pinfo->margin, center, agnameof(g));
- else if (genPoly(root, gs[i], info + i, stepSize, pinfo, center)) {
- free(bbs);
- return 0;
- }
- }
- /* sort */
- ginfo **sinfo = gv_calloc(ng, sizeof(ginfo *));
- for (size_t i = 0; i < ng; i++) {
- sinfo[i] = info + i;
- }
- qsort(sinfo, ng, sizeof(ginfo *), cmpf);
- ps = newPS();
- pointf *places = gv_calloc(ng, sizeof(pointf));
- if (fixed) {
- for (size_t i = 0; i < ng; i++) {
- if (fixed[i])
- placeFixed(sinfo[i], ps, places + sinfo[i]->index, center);
- }
- for (size_t i = 0; i < ng; i++) {
- if (!fixed[i])
- placeGraph(i, sinfo[i], ps, places + sinfo[i]->index, stepSize,
- pinfo->margin, bbs);
- }
- } else {
- for (size_t i = 0; i < ng; i++)
- placeGraph(i, sinfo[i], ps, places + sinfo[i]->index, stepSize,
- pinfo->margin, bbs);
- }
- free(sinfo);
- for (size_t i = 0; i < ng; i++)
- free(info[i].cells);
- free(info);
- freePS(ps);
- free(bbs);
- if (Verbose > 1)
- for (size_t i = 0; i < ng; i++)
- fprintf(stderr, "pos[%" PRISIZE_T "] %.0f %.0f\n", i, places[i].x,
- places[i].y);
- return places;
- }
- pointf *putGraphs(size_t ng, Agraph_t **gs, Agraph_t *root, pack_info *pinfo) {
- int v;
- Agraph_t *g;
- pointf *pts = NULL;
- char *s;
- if (ng == 0)
- return NULL;
- if (pinfo->mode <= l_graph)
- return polyGraphs(ng, gs, root, pinfo);
- boxf *bbs = gv_calloc(ng, sizeof(boxf));
- for (size_t i = 0; i < ng; i++) {
- g = gs[i];
- compute_bb(g);
- bbs[i] = GD_bb(g);
- }
- if (pinfo->mode == l_array) {
- if (pinfo->flags & PK_USER_VALS) {
- pinfo->vals = gv_calloc(ng, sizeof(packval_t));
- for (size_t i = 0; i < ng; i++) {
- s = agget(gs[i], "sortv");
- if (s && sscanf(s, "%d", &v) > 0 && v >= 0)
- pinfo->vals[i] = v;
- }
- }
- pts = arrayRects(ng, bbs, pinfo);
- if (pinfo->flags & PK_USER_VALS)
- free(pinfo->vals);
- }
- free(bbs);
- return pts;
- }
- pointf *putRects(size_t ng, boxf *bbs, pack_info *pinfo) {
- if (ng == 0)
- return NULL;
- if (pinfo->mode == l_node || pinfo->mode == l_clust)
- return NULL;
- if (pinfo->mode == l_graph)
- return polyRects(ng, bbs, pinfo);
- if (pinfo->mode == l_array)
- return arrayRects(ng, bbs, pinfo);
- return NULL;
- }
- /* Packs rectangles.
- * ng - number of rectangles
- * bbs - array of rectangles
- * info - parameters used in packing
- * This decides where to layout the rectangles and repositions
- * the bounding boxes.
- *
- * Returns 0 on success.
- */
- int packRects(size_t ng, boxf *bbs, pack_info *pinfo) {
- boxf bb;
- if (ng <= 1)
- return 0;
- pointf *pp = putRects(ng, bbs, pinfo);
- if (!pp)
- return 1;
- for (size_t i = 0; i < ng; i++) {
- bb = bbs[i];
- const pointf p = pp[i];
- bb.LL = add_pointf(bb.LL, p);
- bb.UR = add_pointf(bb.UR, p);
- bbs[i] = bb;
- }
- free(pp);
- return 0;
- }
- /// Translate all of the edge components by the given offset.
- static void shiftEdge(Agedge_t *e, double dx, double dy) {
- if (ED_label(e))
- MOVEPT(ED_label(e)->pos);
- if (ED_xlabel(e))
- MOVEPT(ED_xlabel(e)->pos);
- if (ED_head_label(e))
- MOVEPT(ED_head_label(e)->pos);
- if (ED_tail_label(e))
- MOVEPT(ED_tail_label(e)->pos);
- if (ED_spl(e) == NULL)
- return;
- for (size_t j = 0; j < ED_spl(e)->size; j++) {
- bezier bz = ED_spl(e)->list[j];
- for (size_t k = 0; k < bz.size; k++)
- MOVEPT(bz.list[k]);
- if (bz.sflag)
- MOVEPT(ED_spl(e)->list[j].sp);
- if (bz.eflag)
- MOVEPT(ED_spl(e)->list[j].ep);
- }
- }
- static void shiftGraph(Agraph_t *g, double dx, double dy) {
- graph_t *subg;
- boxf bb = GD_bb(g);
- int i;
- bb.LL.x += dx;
- bb.UR.x += dx;
- bb.LL.y += dy;
- bb.UR.y += dy;
- GD_bb(g) = bb;
- if (GD_label(g) && GD_label(g)->set)
- MOVEPT(GD_label(g)->pos);
- for (i = 1; i <= GD_n_cluster(g); i++) {
- subg = GD_clust(g)[i];
- shiftGraph(subg, dx, dy);
- }
- }
- /* The function takes ng graphs gs and a similar
- * number of points pp and translates each graph so
- * that the lower left corner of the bounding box of graph gs[i] is at
- * point ps[i]. To do this, it assumes the bb field in
- * Agraphinfo_t accurately reflects the current graph layout.
- * The graph is repositioned by translating the pos and coord fields of
- * each node appropriately.
- *
- * If doSplines is non-zero, the function also translates splines coordinates
- * of each edge, if they have been calculated. In addition, edge labels are
- * repositioned.
- *
- * If root is non-NULL, it is taken as the root graph of
- * the graphs in gs and is used to find the edges. Otherwise, the function
- * uses the edges found in each graph gs[i].
- *
- * It returns 0 on success.
- *
- * This function uses the bb field in Agraphinfo_t,
- * the pos and coord fields in nodehinfo_t and
- * the spl field in Aedgeinfo_t.
- */
- int shiftGraphs(size_t ng, Agraph_t **gs, pointf *pp, Agraph_t *root,
- bool doSplines) {
- double fx, fy;
- Agraph_t *g;
- Agraph_t *eg;
- Agnode_t *n;
- Agedge_t *e;
- if (ng == 0)
- return 0;
- for (size_t i = 0; i < ng; i++) {
- g = gs[i];
- if (root)
- eg = root;
- else
- eg = g;
- const pointf p = pp[i];
- const double dx = p.x;
- const double dy = p.y;
- fx = PS2INCH(dx);
- fy = PS2INCH(dy);
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- ND_pos(n)[0] += fx;
- ND_pos(n)[1] += fy;
- MOVEPT(ND_coord(n));
- if (ND_xlabel(n)) {
- MOVEPT(ND_xlabel(n)->pos);
- }
- if (doSplines) {
- for (e = agfstout(eg, n); e; e = agnxtout(eg, e))
- shiftEdge(e, dx, dy);
- }
- }
- shiftGraph(g, dx, dy);
- }
- return 0;
- }
- /* Packs graphs.
- * ng - number of graphs
- * gs - pointer to array of graphs
- * root - graph used to find edges
- * info - parameters used in packing
- * info->doSplines - if true, use already computed spline control points
- * This decides where to layout the graphs and repositions the graph's
- * position info.
- *
- * Returns 0 on success.
- */
- int packGraphs(size_t ng, Agraph_t **gs, Agraph_t *root, pack_info *info) {
- int ret;
- pointf *pp = putGraphs(ng, gs, root, info);
- if (!pp)
- return 1;
- ret = shiftGraphs(ng, gs, pp, root, info->doSplines);
- free(pp);
- return ret;
- }
- /* Packs subgraphs of given root graph, then recalculates root's bounding box.
- * Note that it does not recompute subgraph bounding boxes.
- * Cluster bounding boxes are recomputed in shiftGraphs.
- */
- int packSubgraphs(size_t ng, Agraph_t **gs, Agraph_t *root, pack_info *info) {
- int ret;
- ret = packGraphs(ng, gs, root, info);
- if (ret == 0) {
- int j;
- boxf bb;
- graph_t *g;
- compute_bb(root);
- bb = GD_bb(root);
- for (size_t i = 0; i < ng; i++) {
- g = gs[i];
- for (j = 1; j <= GD_n_cluster(g); j++) {
- EXPANDBB(bb, GD_bb(GD_clust(g)[j]));
- }
- }
- GD_bb(root) = bb;
- }
- return ret;
- }
- /// Pack subgraphs followed by postprocessing.
- int pack_graph(size_t ng, Agraph_t **gs, Agraph_t *root, bool *fixed) {
- int ret;
- pack_info info;
- getPackInfo(root, l_graph, CL_OFFSET, &info);
- info.doSplines = true;
- info.fixed = fixed;
- ret = packSubgraphs(ng, gs, root, &info);
- if (ret == 0)
- dotneato_postprocess(root);
- return ret;
- }
- static const char *chkFlags(const char *p, pack_info *pinfo) {
- int c, more;
- if (*p != '_')
- return p;
- p++;
- more = 1;
- while (more && (c = *p)) {
- switch (c) {
- case 'c':
- pinfo->flags |= PK_COL_MAJOR;
- p++;
- break;
- case 'i':
- pinfo->flags |= PK_INPUT_ORDER;
- p++;
- break;
- case 'u':
- pinfo->flags |= PK_USER_VALS;
- p++;
- break;
- case 't':
- pinfo->flags |= PK_TOP_ALIGN;
- p++;
- break;
- case 'b':
- pinfo->flags |= PK_BOT_ALIGN;
- p++;
- break;
- case 'l':
- pinfo->flags |= PK_LEFT_ALIGN;
- p++;
- break;
- case 'r':
- pinfo->flags |= PK_RIGHT_ALIGN;
- p++;
- break;
- default:
- more = 0;
- break;
- }
- }
- return p;
- }
- static const char *mode2Str(pack_mode m) {
- switch (m) {
- case l_clust:
- return "cluster";
- case l_node:
- return "node";
- case l_graph:
- return "graph";
- case l_array:
- return "array";
- case l_aspect:
- return "aspect";
- case l_undef:
- default:
- break;
- }
- return "undefined";
- }
- /* Return pack_mode of graph using "packmode" attribute.
- * If not defined, return dflt
- */
- pack_mode parsePackModeInfo(const char *p, pack_mode dflt, pack_info *pinfo) {
- float v;
- int i;
- assert(pinfo);
- pinfo->flags = 0;
- pinfo->mode = dflt;
- pinfo->sz = 0;
- pinfo->vals = NULL;
- if (p) {
- if (startswith(p, "array")) {
- pinfo->mode = l_array;
- p += strlen("array");
- p = chkFlags(p, pinfo);
- if (sscanf(p, "%d", &i) > 0 && i > 0)
- pinfo->sz = i;
- } else if (startswith(p, "aspect")) {
- pinfo->mode = l_aspect;
- if (sscanf(p + strlen("aspect"), "%f", &v) > 0 && v > 0)
- pinfo->aspect = v;
- else
- pinfo->aspect = 1;
- } else if (streq(p, "cluster")) {
- pinfo->mode = l_clust;
- } else if (streq(p, "graph")) {
- pinfo->mode = l_graph;
- } else if (streq(p, "node")) {
- pinfo->mode = l_node;
- }
- }
- if (Verbose) {
- fprintf(stderr, "pack info:\n");
- fprintf(stderr, " mode %s\n", mode2Str(pinfo->mode));
- if (pinfo->mode == l_aspect)
- fprintf(stderr, " aspect %f\n", pinfo->aspect);
- fprintf(stderr, " size %d\n", pinfo->sz);
- fprintf(stderr, " flags %d\n", pinfo->flags);
- }
- return pinfo->mode;
- }
- /* Return pack_mode of graph using "packmode" attribute.
- * If not defined, return dflt
- */
- pack_mode getPackModeInfo(Agraph_t *g, pack_mode dflt, pack_info *pinfo) {
- return parsePackModeInfo(agget(g, "packmode"), dflt, pinfo);
- }
- pack_mode getPackMode(Agraph_t *g, pack_mode dflt) {
- pack_info info;
- return getPackModeInfo(g, dflt, &info);
- }
- /* Return "pack" attribute of g.
- * If not defined or negative, return not_def.
- * If defined but not specified, return dflt.
- */
- int getPack(Agraph_t *g, int not_def, int dflt) {
- char *p;
- int i;
- int v = not_def;
- if ((p = agget(g, "pack"))) {
- if (sscanf(p, "%d", &i) == 1 && i >= 0)
- v = i;
- else if (*p == 't' || *p == 'T')
- v = dflt;
- }
- return v;
- }
- pack_mode getPackInfo(Agraph_t *g, pack_mode dflt, int dfltMargin,
- pack_info *pinfo) {
- assert(pinfo);
- pinfo->margin = getPack(g, dfltMargin, dfltMargin);
- if (Verbose) {
- fprintf(stderr, " margin %u\n", pinfo->margin);
- }
- pinfo->doSplines = false;
- pinfo->fixed = NULL;
- getPackModeInfo(g, dflt, pinfo);
- return pinfo->mode;
- }
- /**
- * @dir lib/pack
- * @brief support for connected components, API pack.h
- *
- * [man 3 libpack](https://graphviz.org/pdf/pack.3.pdf)
- *
- */
|