123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- /*************************************************************************
- * 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
- *************************************************************************/
- #include <assert.h>
- #include <errno.h>
- #include <limits.h>
- #include <math.h>
- #include <stdbool.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #define XLABEL_INT
- #include <label/xlabels.h>
- #include <util/alloc.h>
- #include <util/exit.h>
- static int icompare(void *, void *);
- Dtdisc_t Hdisc = { offsetof(HDict_t, key), sizeof(int), -1, 0, 0,
- icompare
- };
- static int icompare(void *v1, void *v2) {
- int k1 = *((int *) v1), k2 = *((int *) v2);
- if (k1 < k2) {
- return -1;
- }
- if (k1 > k2) {
- return 1;
- }
- return 0;
- }
- static XLabels_t *xlnew(object_t *objs, size_t n_objs, xlabel_t *lbls,
- size_t n_lbls, label_params_t *params) {
- XLabels_t *xlp = gv_alloc(sizeof(XLabels_t));
- /* used to load the rtree in hilbert space filling curve order */
- if (!(xlp->hdx = dtopen(&Hdisc, Dtobag))) {
- fprintf(stderr, "out of memory\n");
- graphviz_exit(EXIT_FAILURE);
- }
- /* for querying intersection candidates */
- if (!(xlp->spdx = RTreeOpen())) {
- fprintf(stderr, "out of memory\n");
- graphviz_exit(EXIT_FAILURE);
- }
- /* save arg pointers in the handle */
- xlp->objs = objs;
- xlp->n_objs = n_objs;
- xlp->lbls = lbls;
- xlp->n_lbls = n_lbls;
- xlp->params = params;
- return xlp;
- }
- static void xlfree(XLabels_t * xlp)
- {
- RTreeClose(xlp->spdx);
- free(xlp);
- }
- /***************************************************************************/
- /*
- * determine the order(depth) of the hilbert sfc so that we satisfy the
- * precondition of hd_hil_s_from_xy()
- */
- static unsigned int xlhorder(XLabels_t * xlp)
- {
- double maxx = xlp->params->bb.UR.x, maxy = xlp->params->bb.UR.y;
- return (unsigned)floor(log2(round(fmax(maxx, maxy)))) + 1;
- }
- /* from http://www.hackersdelight.org/ site for the book by Henry S Warren */
- /*
- * precondition
- * pow(2, n) >= max(p.x, p.y)
- */
- /* adapted from lams1.c
- Given the "order" n of a Hilbert curve and coordinates x and y, this
- program computes the length s of the curve from the origin to (x, y).
- The square that the Hilbert curve traverses is of size 2**n by 2**n.
- The method is that given in [Lam&Shap], described by the following
- table. Here i = n-1 for the most significant bit of x and y, and i = 0
- for the least significant bits.
- x[i] y[i] | s[2i+1:2i] x y
- -----------|-------------------
- 0 0 | 00 y x
- 0 1 | 01 x y
- 1 0 | 11 ~y ~x
- 1 1 | 10 x y
- To use this table, start at the most significant bits of x and y
- (i = n - 1). If they are both 0 (first row), set the most significant
- two bits of s to 00 and interchange x and y. (Actually, it is only
- necessary to interchange the remaining bits of x and y.) If the most
- significant bits of x and y are 10 (third row), output 11, interchange x
- and y, and complement x and y.
- Then, consider the next most significant bits of x and y (which may
- have been changed by this process), and select the appropriate row of
- the table to determine the next two bits of s, and how to change x and
- y. Continue until the least significant bits of x and y have been
- processed. */
- static unsigned int hd_hil_s_from_xy(point p, int n)
- {
- int x = p.x, y = p.y;
- unsigned s = 0; /* Initialize. */
- for (int i = n - 1; i >= 0; i--) {
- int xi = (x >> i) & 1; /* Get bit i of x. */
- int yi = (y >> i) & 1; /* Get bit i of y. */
- s = 4 * s + 2 * (unsigned)xi + ((unsigned)xi ^ (unsigned)yi); // Append two bits to s.
- x = x ^ y; /* These 3 lines swap */
- y = y ^ (x & (yi - 1)); /* x and y if yi = 0. */
- x = x ^ y;
- x = x ^ (-xi & (yi - 1)); /* Complement x and y if */
- y = y ^ (-xi & (yi - 1)); /* xi = 1 and yi = 0. */
- }
- return s;
- }
- /* intersection test from
- * from Real-Time Collision Detection 4.2.1 by Christer Ericson
- * intersection area from
- * http://stackoverflow.com/questions/4549544/total-area-of-intersecting-rectangles
- */
- static double aabbaabb(Rect_t * r, Rect_t * s)
- {
- if (!Overlap(r, s))
- return 0;
- /* if we get here we have an intersection */
- /* rightmost left edge of the 2 rectangles */
- double iminx =
- r->boundary[0] > s->boundary[0] ? r->boundary[0] : s->boundary[0];
- /* upmost bottom edge */
- double iminy =
- r->boundary[1] > s->boundary[1] ? r->boundary[1] : s->boundary[1];
- /* leftmost right edge */
- double imaxx =
- r->boundary[2] < s->boundary[2] ? r->boundary[2] : s->boundary[2];
- /* downmost top edge */
- double imaxy =
- r->boundary[3] < s->boundary[3] ? r->boundary[3] : s->boundary[3];
- return (imaxx - iminx) * (imaxy - iminy);
- }
- /*
- * test if objp1, a size 0 object is enclosed in the xlabel
- * associated with objp
- */
- static bool lblenclosing(object_t *objp, object_t *objp1) {
- xlabel_t * xlp = objp->lbl;;
- assert(objp1->sz.x == 0 && objp1->sz.y == 0);
- if(! xlp) return false;
- return objp1->pos.x > xlp->pos.x &&
- objp1->pos.x < (xlp->pos.x + xlp->sz.x) &&
- objp1->pos.y > xlp->pos.y &&
- objp1->pos.y < (xlp->pos.y + xlp->sz.y);
- }
- /*fill in rectangle from the object */
- static Rect_t objp2rect(const object_t *op) {
- Rect_t r = {0};
- r.boundary[0] = op->pos.x;
- r.boundary[1] = op->pos.y;
- r.boundary[2] = op->pos.x + op->sz.x;
- r.boundary[3] = op->pos.y + op->sz.y;
- return r;
- }
- /*fill in rectangle from the objects xlabel */
- static Rect_t objplp2rect(const object_t *objp) {
- Rect_t r = {0};
- const xlabel_t *lp = objp->lbl;
- r.boundary[0] = lp->pos.x;
- r.boundary[1] = lp->pos.y;
- r.boundary[2] = lp->pos.x + lp->sz.x;
- r.boundary[3] = lp->pos.y + lp->sz.y;
- return r;
- }
- /* compute boundary that encloses all possible label boundaries */
- static Rect_t objplpmks(object_t * objp)
- {
- Rect_t rect;
- pointf p;
- p.x = p.y = 0.0;
- if (objp->lbl)
- p = objp->lbl->sz;
- rect.boundary[0] = (int) floor(objp->pos.x - p.x);
- rect.boundary[1] = (int) floor(objp->pos.y - p.y);
- rect.boundary[2] = (int) ceil(objp->pos.x + objp->sz.x + p.x);
- assert(rect.boundary[2] < INT_MAX);
- rect.boundary[3] = (int) ceil(objp->pos.y + objp->sz.y + p.y);
- assert(rect.boundary[3] < INT_MAX);
- return rect;
- }
- /* determine the position clp will occupy in intrsx[] */
- static int getintrsxi(object_t * op, object_t * cp)
- {
- xlabel_t *lp = op->lbl, *clp = cp->lbl;
- assert(lp != clp);
- if (lp->set == 0 || clp->set == 0)
- return -1;
- if ((op->pos.x == 0.0 && op->pos.y == 0.0) ||
- (cp->pos.x == 0.0 && cp->pos.y == 0.0))
- return -1;
- if (cp->pos.y < op->pos.y) {
- if (cp->pos.x < op->pos.x)
- return XLPXPY;
- if (cp->pos.x > op->pos.x)
- return XLNXPY;
- return XLCXPY;
- }
- if (cp->pos.y > op->pos.y) {
- if (cp->pos.x < op->pos.x)
- return XLPXNY;
- if (cp->pos.x > op->pos.x)
- return XLNXNY;
- return XLCXNY;
- }
- if (cp->pos.x < op->pos.x)
- return XLPXCY;
- if (cp->pos.x > op->pos.x)
- return XLNXCY;
- return -1;
- }
- /* record the intersecting objects label */
- static double
- recordointrsx(object_t * op, object_t * cp, Rect_t * rp,
- double a, object_t * intrsx[XLNBR])
- {
- int i = getintrsxi(op, cp);
- if (i < 0)
- i = 5;
- if (intrsx[i] != NULL) {
- double sa, maxa = 0.0;
- /* keep maximally overlapping object */
- Rect_t srect = objp2rect(intrsx[i]);
- sa = aabbaabb(rp, &srect);
- if (sa > a)
- maxa = sa;
- /*keep maximally overlapping label */
- if (intrsx[i]->lbl) {
- srect = objplp2rect(intrsx[i]);
- sa = aabbaabb(rp, &srect);
- if (sa > a)
- maxa = fmax(sa, maxa);
- }
- if (maxa > 0.0)
- return maxa;
- /*replace overlapping label/object pair */
- intrsx[i] = cp;
- return a;
- }
- intrsx[i] = cp;
- return a;
- }
- /* record the intersecting label */
- static double
- recordlintrsx(object_t * op, object_t * cp, Rect_t * rp,
- double a, object_t * intrsx[XLNBR])
- {
- int i = getintrsxi(op, cp);
- if (i < 0)
- i = 5;
- if (intrsx[i] != NULL) {
- double sa, maxa = 0.0;
- /* keep maximally overlapping object */
- Rect_t srect = objp2rect(intrsx[i]);
- sa = aabbaabb(rp, &srect);
- if (sa > a)
- maxa = sa;
- /*keep maximally overlapping label */
- if (intrsx[i]->lbl) {
- srect = objplp2rect(intrsx[i]);
- sa = aabbaabb(rp, &srect);
- if (sa > a)
- maxa = fmax(sa, maxa);
- }
- if (maxa > 0.0)
- return maxa;
- /*replace overlapping label/object pair */
- intrsx[i] = cp;
- return a;
- }
- intrsx[i] = cp;
- return a;
- }
- /* find the objects and labels intersecting lp */
- static BestPos_t
- xlintersections(XLabels_t * xlp, object_t * objp, object_t * intrsx[XLNBR])
- {
- BestPos_t bp;
- assert(objp->lbl);
- bp.n = 0;
- bp.area = 0.0;
- bp.pos = objp->lbl->pos;
- for (size_t i = 0; i < xlp->n_objs; i++) {
- if(objp == &xlp->objs[i]) continue;
- if(xlp->objs[i].sz.x > 0 && xlp->objs[i].sz.y > 0) continue;
- if(lblenclosing(objp, &xlp->objs[i]) ) {
- bp.n++;
- }
- }
- Rect_t rect = objplp2rect(objp);
- LeafList_t *llp = RTreeSearch(xlp->spdx, xlp->spdx->root, &rect);
- if (!llp)
- return bp;
- for (LeafList_t *ilp = llp; ilp; ilp = ilp->next) {
- double a, ra;
- object_t *cp = ilp->leaf->data;
- if (cp == objp)
- continue;
- /*label-object intersect */
- Rect_t srect = objp2rect(cp);
- a = aabbaabb(&rect, &srect);
- if (a > 0.0) {
- ra = recordointrsx(objp, cp, &rect, a, intrsx);
- bp.n++;
- bp.area += ra;
- }
- /*label-label intersect */
- if (!cp->lbl || !cp->lbl->set)
- continue;
- srect = objplp2rect(cp);
- a = aabbaabb(&rect, &srect);
- if (a > 0.0) {
- ra = recordlintrsx(objp, cp, &rect, a, intrsx);
- bp.n++;
- bp.area += ra;
- }
- }
- RTreeLeafListFree(llp);
- return bp;
- }
- /*
- * xladjust - find a label position
- * the individual tests at the top are intended to place a preference order
- * on the position
- */
- static BestPos_t xladjust(XLabels_t * xlp, object_t * objp)
- {
- xlabel_t *lp = objp->lbl;
- double xincr = (2 * lp->sz.x + objp->sz.x) / XLXDENOM;
- double yincr = (2 * lp->sz.y + objp->sz.y) / XLYDENOM;
- object_t *intrsx[XLNBR] = {0};
- BestPos_t bp, nbp;
- assert(objp->lbl);
- /*x left */
- lp->pos.x = objp->pos.x - lp->sz.x;
- /*top */
- lp->pos.y = objp->pos.y + objp->sz.y;
- bp = xlintersections(xlp, objp, intrsx);
- if (bp.n == 0)
- return bp;
- /*mid */
- lp->pos.y = objp->pos.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*bottom */
- lp->pos.y = objp->pos.y - lp->sz.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*x mid */
- lp->pos.x = objp->pos.x;
- /*top */
- lp->pos.y = objp->pos.y + objp->sz.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*bottom */
- lp->pos.y = objp->pos.y - lp->sz.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*x right */
- lp->pos.x = objp->pos.x + objp->sz.x;
- /*top */
- lp->pos.y = objp->pos.y + objp->sz.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*mid */
- lp->pos.y = objp->pos.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*bottom */
- lp->pos.y = objp->pos.y - lp->sz.y;
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- /*sliding from top left */
- if (intrsx[XLPXNY] || intrsx[XLCXNY] || intrsx[XLNXNY] || intrsx[XLPXCY] || intrsx[XLPXPY]) { /* have to move */
- if (!intrsx[XLCXNY] && !intrsx[XLNXNY]) { /* some room right? */
- /* slide along upper edge */
- for (lp->pos.x = objp->pos.x - lp->sz.x,
- lp->pos.y = objp->pos.y + objp->sz.y;
- lp->pos.x <= (objp->pos.x + objp->sz.x);
- lp->pos.x += xincr) {
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- }
- }
- if (!intrsx[XLPXCY] && !intrsx[XLPXPY]) { /* some room down? */
- /* slide down left edge */
- for (lp->pos.x = objp->pos.x - lp->sz.x,
- lp->pos.y = objp->pos.y + objp->sz.y;
- lp->pos.y >= (objp->pos.y - lp->sz.y);
- lp->pos.y -= yincr) {
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- }
- }
- }
- /*sliding from bottom right */
- lp->pos.x = objp->pos.x + objp->sz.x;
- lp->pos.y = objp->pos.y - lp->sz.y;
- if (intrsx[XLNXPY] || intrsx[XLCXPY] || intrsx[XLPXPY] || intrsx[XLNXCY] || intrsx[XLNXNY]) { /* have to move */
- if (!intrsx[XLCXPY] && !intrsx[XLPXPY]) { /* some room left? */
- /* slide along lower edge */
- for (lp->pos.x = objp->pos.x + objp->sz.x,
- lp->pos.y = objp->pos.y - lp->sz.y;
- lp->pos.x >= (objp->pos.x - lp->sz.x);
- lp->pos.x -= xincr) {
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- }
- }
- if (!intrsx[XLNXCY] && !intrsx[XLNXNY]) { /* some room up? */
- /* slide up right edge */
- for (lp->pos.x = objp->pos.x + objp->sz.x,
- lp->pos.y = objp->pos.y - lp->sz.y;
- lp->pos.y <= (objp->pos.y + objp->sz.y);
- lp->pos.y += yincr) {
- nbp = xlintersections(xlp, objp, intrsx);
- if (nbp.n == 0)
- return nbp;
- if (nbp.area < bp.area)
- bp = nbp;
- }
- }
- }
- return bp;
- }
- /* load the hilbert sfc keyed tree */
- static int xlhdxload(XLabels_t * xlp)
- {
- int order = xlhorder(xlp);
- for (size_t i = 0; i < xlp->n_objs; i++) {
- HDict_t *hp;
- point pi;
- hp = gv_alloc(sizeof(HDict_t));
- hp->d.data = &xlp->objs[i];
- hp->d.rect = objplpmks(&xlp->objs[i]);
- /* center of the labeling area */
- pi.x = hp->d.rect.boundary[0] +
- (hp->d.rect.boundary[2] - hp->d.rect.boundary[0]) / 2;
- pi.y = hp->d.rect.boundary[1] +
- (hp->d.rect.boundary[3] - hp->d.rect.boundary[1]) / 2;
- hp->key = hd_hil_s_from_xy(pi, order);
- if (!dtinsert(xlp->hdx, hp))
- return -1;
- }
- return 0;
- }
- static void xlhdxunload(XLabels_t * xlp)
- {
- int size=dtsize(xlp->hdx), freed=0;
- while(dtsize(xlp->hdx) ) {
- void*vp=dtfinger(xlp->hdx);
- assert(vp);
- if(vp) {
- dtdetach(xlp->hdx, vp);
- free(vp);
- freed++;
- }
- }
- assert(size==freed);
- (void)size;
- }
- static void xlspdxload(XLabels_t *xlp) {
- for (HDict_t *op = dtfirst(xlp->hdx); op; op = dtnext(xlp->hdx, op)) {
- /* tree rectangle data node lvl */
- RTreeInsert(xlp->spdx, &op->d.rect, op->d.data, &xlp->spdx->root, 0);
- }
- }
- static int xlinitialize(XLabels_t * xlp)
- {
- int r=0;
- if ((r = xlhdxload(xlp)) < 0)
- return r;
- xlspdxload(xlp);
- xlhdxunload(xlp);
- return dtclose(xlp->hdx);
- }
- int placeLabels(object_t *objs, size_t n_objs, xlabel_t *lbls, size_t n_lbls,
- label_params_t *params) {
- int r;
- BestPos_t bp;
- XLabels_t *xlp = xlnew(objs, n_objs, lbls, n_lbls, params);
- if ((r = xlinitialize(xlp)) < 0)
- return r;
- /* Place xlabel_t* lp near lp->obj so that the rectangle whose lower-left
- * corner is lp->pos, and size is lp->sz does not intersect any object
- * in objs (by convention, an object consisting of a single point
- * intersects nothing) nor any other label, if possible. On input,
- * lp->set is 0.
- *
- * On output, any label with a position should have this stored in
- * lp->pos and have lp->set non-zero.
- *
- * If params->force is true, all labels must be positioned, even if
- * overlaps are necessary.
- *
- * Return 0 if all labels could be placed without overlap;
- * non-zero otherwise.
- */
- r = 0;
- for (size_t i = 0; i < n_objs; i++) {
- if (objs[i].lbl == 0)
- continue;
- bp = xladjust(xlp, &objs[i]);
- if (bp.n == 0) {
- objs[i].lbl->set = 1;
- } else if(bp.area == 0) {
- objs[i].lbl->pos.x = bp.pos.x;
- objs[i].lbl->pos.y = bp.pos.y;
- objs[i].lbl->set = 1;
- } else if (params->force == 1) {
- objs[i].lbl->pos.x = bp.pos.x;
- objs[i].lbl->pos.y = bp.pos.y;
- objs[i].lbl->set = 1;
- } else {
- r = 1;
- }
- }
- xlfree(xlp);
- return r;
- }
|