Path: blob/master/sage/graphs/planarity/graphDrawPlanar.c
4069 views
/*1Planarity-Related Graph Algorithms Project2Copyright (c) 1997-2010, John M. Boyer3All rights reserved. Includes a reference implementation of the following:45* John M. Boyer. "Simplified O(n) Algorithms for Planar Graph Embedding,6Kuratowski Subgraph Isolation, and Related Problems". Ph.D. Dissertation,7University of Victoria, 2001.89* John M. Boyer and Wendy J. Myrvold. "On the Cutting Edge: Simplified O(n)10Planarity by Edge Addition". Journal of Graph Algorithms and Applications,11Vol. 8, No. 3, pp. 241-273, 2004.1213* John M. Boyer. "A New Method for Efficiently Generating Planar Graph14Visibility Representations". In P. Eades and P. Healy, editors,15Proceedings of the 13th International Conference on Graph Drawing 2005,16Lecture Notes Comput. Sci., Volume 3843, pp. 508-511, Springer-Verlag, 2006.1718Redistribution and use in source and binary forms, with or without modification,19are permitted provided that the following conditions are met:2021* Redistributions of source code must retain the above copyright notice, this22list of conditions and the following disclaimer.2324* Redistributions in binary form must reproduce the above copyright notice, this25list of conditions and the following disclaimer in the documentation and/or26other materials provided with the distribution.2728* Neither the name of the Planarity-Related Graph Algorithms Project nor the names29of its contributors may be used to endorse or promote products derived from this30software without specific prior written permission.3132THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"33AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE34IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE35DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR36ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES37(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;38LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON39ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT40(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS41SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.42*/4344#include "graphDrawPlanar.h"45#include "graphDrawPlanar.private.h"4647extern int DRAWPLANAR_ID;4849#include "graph.h"5051#if 052#include <malloc.h>53#else54#if !defined(_XOPEN_SOURCE) && !defined(_ISOC99_SOURCE)55#define _XOPEN_SOURCE 60056#endif57#include <stdlib.h> /* ISO C99 also defines malloc() to be there. */58#endif59#include <string.h>60#include <stdio.h>6162extern void _FillVisitedFlags(graphP theGraph, int FillValue);6364/* Private functions exported to system */6566void _CollectDrawingData(DrawPlanarContext *context, int RootVertex, int W, int WPrevLink);67int _BreakTie(DrawPlanarContext *context, int BicompRoot, int W, int WPrevLink);6869int _ComputeVisibilityRepresentation(DrawPlanarContext *context);70int _CheckVisibilityRepresentationIntegrity(DrawPlanarContext *context);7172/* Private functions */73int _ComputeVertexPositions(DrawPlanarContext *context);74int _ComputeVertexPositionsInComponent(DrawPlanarContext *context, int root, int *pIndex);75int _ComputeEdgePositions(DrawPlanarContext *context);76int _ComputeVertexRanges(DrawPlanarContext *context);77int _ComputeEdgeRanges(DrawPlanarContext *context);7879/********************************************************************80_ComputeVisibilityRepresentation()8182Compute vertex positions83Compute edge positions84Assign horizontal ranges of vertices85Assign vertical ranges of edges8687********************************************************************/8889int _ComputeVisibilityRepresentation(DrawPlanarContext *context)90{91if (sp_NonEmpty(context->theGraph->edgeHoles))92return NOTOK;9394if (_ComputeVertexPositions(context) != OK)95return NOTOK;9697if (_ComputeEdgePositions(context) != OK)98return NOTOK;99100if (_ComputeVertexRanges(context) != OK)101return NOTOK;102103if (_ComputeEdgeRanges(context) != OK)104return NOTOK;105106return OK;107}108109/********************************************************************110_ComputeVertexPositions()111112Computes the vertex positions in the graph. This method accounts113for disconnected graphs by finding the DFS tree roots and then,114for each, invoking _ComputeVertexPositionsInComponent().115The index variable for the positioning is maintained by this method116so that the vertices in separate components still get distinct117vertex positions.118********************************************************************/119120int _ComputeVertexPositions(DrawPlanarContext *context)121{122graphP theEmbedding = context->theGraph;123int I, index;124125for (I = index = 0; I < theEmbedding->N; I++)126{127// For each DFS tree root in the embedding, we128// compute the vertex positions129if (theEmbedding->V[I].DFSParent == NIL)130{131if (_ComputeVertexPositionsInComponent(context, I, &index) != OK)132return NOTOK;133}134}135136return OK;137}138139/********************************************************************140_ComputeVertexPositionsInComponent()141142The vertical positions of the vertices are computed based in part143on the information compiled during the planar embedding.144145Each vertex is marked as being between its parent and some ancestor146or beyond the parent relative to the ancestor. The localized,147intuitive notion is that the vertex is either below the parent148or above the parent, but the bicomp containing the vertex, its149parent and the ancestor may be turned upside-down as the result150of a global sequence of operations, resulting in a between or beyond151generalization.152153As the core planarity algorithm constructs successively larger154bicomps out of smaller ones, the bicomp root and its DFS child155are marked as 'tied' in vertex position using markers along the156external face. The marking of the DFS child may be indirect.157Since the child may not be on the external face, its descendant158that is next along the external face is marked instead.159160Later (possibly in the same step or possibly many vertices later),161the Walkdown proceeds around the bicomp and returns to each merge162point, and the tie is broken based on the direction of approach.163164As the Walkdown passes a vertex to its successor, the external165face is short-circuited to remove the vertex from it. Immediately166before this occurs, the new drawing method resolves the tie. Since167the vertex is going to the internal face, its vertex position should168be 'between' its successor and the current vertex being processed169by the Walkdown.170171If the vertex is a child of its external face successor, then it172is simply marked as being 'between' that successor and the current173vertex being processed by the planarity method. But if the vertex174is the parent of its external face successor, then the successor175is placed 'beyond' the vertex. Recall that the successor is either176the DFS child of the vertex or a descendant of that DFS child that177was specially marked because it, not the DFS child, was on the178external face.179180This explains the information that has been collected by the181planarity embedder, which will now be turned into a vertex ordering182system. The idea is to proceed with a pre-order traversal of183the DFS tree, determining the relative orders of the ancestors of184a vertex by the time we get to a vertex. This will allow us to185convert between/beyond into above/below based on the known relative186order of the parent and some given ancestor of the vertex. A vertex187would then be added immediately above or below its parent in the188total ordering, and then the algorithm proceeds to the descendants.189190Consider a depth-first pre-order visitation of vertices. If the191full order of all vertices visited so far is dynamically maintained,192then it is easy to decide whether a vertex goes above or below193its parent based on the between/beyond indicator and the relative194positions in the order of the parent and given ancestor of the195vertex. If the ancestor is above the parent, then 'between' means196put the vertex immediately above its parent and 'beyond' means put197the vertex immediately below its parent in the order. And if the198ancestor is below the parent, then the meaning of between and199beyond are simply reversed.200201Once a vertex is known to be above or below its parent, the drawing202flag is changed from between/beyond to above/below, and processing203proceeds to the next vertex in pre-order depth first search.204205The difficulty lies in keeping an up-to-date topological ordering206that can be queried in constant time to find the relative positions207of two vertices. By itself, this is an instance of "online" or208dynamic topological sorting and has been proven not to be achievable209in linear total time. But this is a special case of the problem and210is therefore solvable through the collection and maintenance of some211additional information.212213Recall that the ancestor V of a vertex is recorded when the setting214for between/beyond is made for a vertex. However, the Walkdown is215invoked on the bicomp rooted by edge (V', C), so the child C of V216that roots the subtree containing the vertex being marked is known.217218Note that when a DFS child is placed above its parent, the entire219DFS subtree of vertices is placed above the parent. Hence, to220determine whether the parent P of a vertex W is above the ancestor221V, where W is marked either between or beyond P and V, we need222only determine the relationship between V and C, which has already223been directly determined due to previous steps of the algorithm224(because V and C are ancestors of P and W). If C is above/below V225then so is P.226227As mentioned above, once the position of P is known relative to V,228it is a simple matter to decide whether to put W above or below P229based on the between/beyond indicator stored in W during embedding.230********************************************************************/231232int _ComputeVertexPositionsInComponent(DrawPlanarContext *context, int root, int *pIndex)233{234graphP theEmbedding = context->theGraph;235listCollectionP theOrder = LCNew(theEmbedding->N);236int W, P, C, V, J;237238if (theOrder == NULL)239return NOTOK;240241// Determine the vertex order using a depth first search with242// pre-order visitation.243244sp_ClearStack(theEmbedding->theStack);245sp_Push(theEmbedding->theStack, root);246while (!sp_IsEmpty(theEmbedding->theStack))247{248sp_Pop(theEmbedding->theStack, W);249250P = theEmbedding->V[W].DFSParent;251V = context->V[W].ancestor;252C = context->V[W].ancestorChild;253254// For the special case that we just popped the DFS tree root,255// we simply add the root to its own position.256if (P == NIL)257{258// Put the DFS root in the list by itself259LCAppend(theOrder, NIL, W);260// The children of the DFS root have the root as their261// ancestorChild and 'beyond' as the drawingFlag, so this262// causes the root's children to be placed below the root263context->V[W].drawingFlag = DRAWINGFLAG_BELOW;264}265266// Determine vertex W position relative to P267else268{269// An unresolved tie is an error270if (context->V[W].drawingFlag == DRAWINGFLAG_TIE)271return NOTOK;272273// If C below V, then P below V, so interpret W between274// P and V as W above P, and interpret W beyond P relative275// to V as W below P.276if (context->V[C].drawingFlag == DRAWINGFLAG_BELOW)277{278if (context->V[W].drawingFlag == DRAWINGFLAG_BETWEEN)279context->V[W].drawingFlag = DRAWINGFLAG_ABOVE;280else281context->V[W].drawingFlag = DRAWINGFLAG_BELOW;282}283284// If C above V, then P above V, so interpret W between285// P and V as W below P, and interpret W beyond P relative286// to V as W above P.287else288{289if (context->V[W].drawingFlag == DRAWINGFLAG_BETWEEN)290context->V[W].drawingFlag = DRAWINGFLAG_BELOW;291else292context->V[W].drawingFlag = DRAWINGFLAG_ABOVE;293}294295if (context->V[W].drawingFlag == DRAWINGFLAG_BELOW)296LCInsertAfter(theOrder, P, W);297else298LCInsertBefore(theOrder, P, W);299}300301// Push DFS children302J = gp_GetFirstArc(theEmbedding, W);303while (gp_IsArc(theEmbedding, J))304{305if (theEmbedding->G[J].type == EDGE_DFSCHILD)306sp_Push(theEmbedding->theStack, theEmbedding->G[J].v);307308J = gp_GetNextArc(theEmbedding, J);309}310}311312// Use the order to assign vertical positions313V = root;314while (V != NIL)315{316context->G[V].pos = *pIndex;317(*pIndex)++;318V = LCGetNext(theOrder, root, V);319}320321// Clean up and return322323LCFree(&theOrder);324return OK;325}326327328#ifdef LOGGING329/********************************************************************330_LogEdgeList()331Used to show the progressive calculation of the edge position list.332********************************************************************/333void _LogEdgeList(graphP theEmbedding, listCollectionP edgeList, int edgeListHead)334{335int e = edgeListHead, J, JTwin;336337gp_Log("EdgeList: [ ");338339while (e != NIL)340{341J = theEmbedding->edgeOffset + 2*e;342JTwin = gp_GetTwinArc(theEmbedding, J);343344gp_Log(gp_MakeLogStr2("(%d, %d) ",345theEmbedding->G[theEmbedding->G[J].v].v,346theEmbedding->G[theEmbedding->G[JTwin].v].v));347348e = LCGetNext(edgeList, edgeListHead, e);349}350351gp_LogLine("]");352}353#endif354355/********************************************************************356_ComputeEdgePositions()357358Performs a vertical sweep of the combinatorial planar embedding,359developing the edge order in the horizontal sweep line as it360advances through the vertices according to their assigned361vertical positions.362363For expedience, the 'visited' flag for each vertex shall be used364instead to indicate the location in the edge order list of the365generator edge for the vertex, i.e. the first edge added to the366vertex from a higher vertex (with lower position number).367All edges added from this vertex to the neighbors below it are368added immediately after the generator edge for the vertex.369********************************************************************/370371int _ComputeEdgePositions(DrawPlanarContext *context)372{373graphP theEmbedding = context->theGraph;374int *vertexOrder = NULL;375listCollectionP edgeList = NULL;376int edgeListHead, edgeListInsertPoint;377int I, J, Jcur, e, v, vpos;378int eIndex, JTwin;379380gp_LogLine("\ngraphDrawPlanar.c/_ComputeEdgePositions() start");381382// Sort the vertices by vertical position (in linear time)383384if ((vertexOrder = (int *) malloc(theEmbedding->N * sizeof(int))) == NULL)385return NOTOK;386387for (I = 0; I < theEmbedding->N; I++)388vertexOrder[context->G[I].pos] = I;389390// Allocate the edge list of size M.391// This is an array of (prev, next) pointers.392// An edge at position X corresponds to the edge393// at position X in the graph structure, which is394// represented by a pair of adjacent graph nodes395// starting at index 2N + 2X.396397if (theEmbedding->M > 0 && (edgeList = LCNew(theEmbedding->M)) == NULL)398{399free(vertexOrder);400return NOTOK;401}402403edgeListHead = NIL;404405// Each vertex starts out with a NIL generator edge.406407for (I=0; I < theEmbedding->N; I++)408theEmbedding->G[I].visited = NIL;409410// Perform the vertical sweep of the combinatorial embedding, using411// the vertex ordering to guide the sweep.412// For each vertex, each edge leading to a vertex with a higher number in413// the vertex order is recorded as the "generator edge", or the edge of414// first discovery of that higher numbered vertex, unless the vertex already has415// a recorded generator edge416for (vpos=0; vpos < theEmbedding->N; vpos++)417{418// Get the vertex associated with the position419v = vertexOrder[vpos];420gp_LogLine(gp_MakeLogStr3("Processing vertex %d with DFI=%d at position=%d",421theEmbedding->G[v].v, v, vpos));422423// The DFS tree root of a connected component is always the least424// number vertex in the vertex ordering. We have to give it a425// false generator edge so that it is still "visited" and then426// all of its edges are generators for its neighbor vertices because427// they all have greater numbers in the vertex order.428if (theEmbedding->V[v].DFSParent == NIL)429{430// False generator edge, so the vertex is distinguishable from431// a vertex with no generator edge when its neighbors are visited432// This way, an edge from a neighbor won't get recorded as the433// generator edge of the DFS tree root.434theEmbedding->G[v].visited = 1;435436// Now we traverse the adjacency list of the DFS tree root and437// record each edge as the generator edge of the neighbors438J = gp_GetFirstArc(theEmbedding, v);439while (gp_IsArc(theGraph, J))440{441e = (J - theEmbedding->edgeOffset) / 2;442443edgeListHead = LCAppend(edgeList, edgeListHead, e);444gp_LogLine(gp_MakeLogStr2("Append generator edge (%d, %d) to edgeList",445theEmbedding->G[v].v, theEmbedding->G[theEmbedding->G[J].v].v));446447// Set the generator edge for the root's neighbor448theEmbedding->G[theEmbedding->G[J].v].visited = J;449450// Go to the next node of the root's adj list451J = gp_GetNextArc(theEmbedding, J);452}453}454455// Else, if we are not on a DFS tree root...456else457{458// Get the generator edge of the vertex459if ((JTwin = theEmbedding->G[v].visited) == NIL)460return NOTOK;461J = gp_GetTwinArc(theEmbedding, JTwin);462463// Traverse the edges of the vertex, starting464// from the generator edge and going counterclockwise...465466e = (J - theEmbedding->edgeOffset) / 2;467edgeListInsertPoint = e;468469Jcur = gp_GetNextArcCircular(theEmbedding, J);470471while (Jcur != J)472{473// If the neighboring vertex's position is greater474// than the current vertex (meaning it is lower in the475// diagram), then add that edge to the edge order.476477if (context->G[theEmbedding->G[Jcur].v].pos > vpos)478{479e = (Jcur - theEmbedding->edgeOffset) / 2;480LCInsertAfter(edgeList, edgeListInsertPoint, e);481482gp_LogLine(gp_MakeLogStr4("Insert (%d, %d) after (%d, %d)",483theEmbedding->G[v].v,484theEmbedding->G[theEmbedding->G[Jcur].v].v,485theEmbedding->G[theEmbedding->G[gp_GetTwinArc(theEmbedding, J)].v].v,486theEmbedding->G[theEmbedding->G[J].v].v));487488edgeListInsertPoint = e;489490// If the vertex does not yet have a generator edge, then set it.491if (theEmbedding->G[theEmbedding->G[Jcur].v].visited == NIL)492{493theEmbedding->G[theEmbedding->G[Jcur].v].visited = Jcur;494gp_LogLine(gp_MakeLogStr2("Generator edge (%d, %d)",495theEmbedding->G[theEmbedding->G[gp_GetTwinArc(theEmbedding, J)].v].v,496theEmbedding->G[theEmbedding->G[Jcur].v].v));497}498}499500// Go to the next node in v's adjacency list501Jcur = gp_GetNextArcCircular(theEmbedding, Jcur);502}503}504505#ifdef LOGGING506_LogEdgeList(theEmbedding, edgeList, edgeListHead);507#endif508}509510// Now iterate through the edgeList and assign positions to the edges.511eIndex = 0;512e = edgeListHead;513while (e != NIL)514{515J = theEmbedding->edgeOffset + 2*e;516JTwin = gp_GetTwinArc(theEmbedding, J);517518context->G[J].pos = context->G[JTwin].pos = eIndex;519520eIndex++;521522e = LCGetNext(edgeList, edgeListHead, e);523}524525// Clean up and return526LCFree(&edgeList);527free(vertexOrder);528529gp_LogLine("graphDrawPlanar.c/_ComputeEdgePositions() end\n");530531return OK;532}533534/********************************************************************535_ComputeVertexRanges()536537Assumes edge positions are known (see _ComputeEdgePositions()).538A vertex spans horizontally the positions of the edges incident539to it.540********************************************************************/541542int _ComputeVertexRanges(DrawPlanarContext *context)543{544graphP theEmbedding = context->theGraph;545int I, J, min, max;546547for (I = 0; I < theEmbedding->N; I++)548{549min = theEmbedding->M + 1;550max = -1;551552// Iterate the edges, except in the isolated vertex case we just553// set the min and max to 1 since there no edges controlling where554// it gets drawn.555J = gp_GetFirstArc(theEmbedding, I);556if (!gp_IsArc(theEmbedding, J))557{558min = max = 0;559}560else561{562while (gp_IsArc(theEmbedding, J))563{564if (min > context->G[J].pos)565min = context->G[J].pos;566567if (max < context->G[J].pos)568max = context->G[J].pos;569570J = gp_GetNextArc(theEmbedding, J);571}572}573574context->G[I].start = min;575context->G[I].end = max;576}577578return OK;579}580581/********************************************************************582_ComputeEdgeRanges()583584Assumes vertex positions are known (see _ComputeVertexPositions()).585An edges spans the vertical range of its endpoints.586********************************************************************/587588int _ComputeEdgeRanges(DrawPlanarContext *context)589{590graphP theEmbedding = context->theGraph;591int e, J, JTwin, v1, v2, pos1, pos2;592593for (e = 0; e < theEmbedding->M; e++)594{595J = theEmbedding->edgeOffset + 2*e;596JTwin = gp_GetTwinArc(theEmbedding, J);597598v1 = theEmbedding->G[J].v;599v2 = theEmbedding->G[JTwin].v;600601pos1 = context->G[v1].pos;602pos2 = context->G[v2].pos;603604if (pos1 < pos2)605{606context->G[J].start = pos1;607context->G[J].end = pos2;608}609else610{611context->G[J].start = pos2;612context->G[J].end = pos1;613}614615context->G[JTwin].start = context->G[J].start;616context->G[JTwin].end = context->G[J].end;617}618619return OK;620}621622/********************************************************************623_GetNextExternalFaceVertex()624Uses the extFace links to traverse to the next vertex on the external625face given a current vertex and the link that points to its predecessor.626********************************************************************/627int _GetNextExternalFaceVertex(graphP theGraph, int curVertex, int *pPrevLink)628{629int nextVertex, nextPrevLink;630631nextVertex = theGraph->extFace[curVertex].vertex[1 ^ *pPrevLink];632633// If the two links in the new vertex are not equal, then only one points634// back to the current vertex, and it is the new prev link.635if (theGraph->extFace[nextVertex].vertex[0] != theGraph->extFace[nextVertex].vertex[1])636{637nextPrevLink = theGraph->extFace[nextVertex].vertex[0]==curVertex ? 0 : 1;638}639else640{641int inverted = 0;642643// One of the two vertices is the root of the bicomp. The non-root may644// not have the same orientation as the root because a consistent orientation645// is not imposed until post-processing of the planarity test. But the646// planarity test does special-case tracking of orientation inversions of647// singleton bicomps in the inversionFlag of the non-root vertex.648649if (nextVertex < theGraph->N)650inverted = theGraph->extFace[nextVertex].inversionFlag;651else inverted = theGraph->extFace[curVertex].inversionFlag;652653// If the curVertex and nextVertex are in a singleton and have the same654// orientation, then the prev link from the current vertex is the prev655// link from the next vertex because you'd just be going around in a656// small circle using the same links for prev every time.657// If their orientations are inverted, then we just invert the prev link.658659nextPrevLink = inverted ? (1^*pPrevLink) : (*pPrevLink);660}661662// Return the information663*pPrevLink = nextPrevLink;664return nextVertex;665}666667/********************************************************************668_CollectDrawingData()669To be called by core planarity Walkdown immediately before merging670bicomps and embedding a new back edge.671672Each bicomp is rooted by a DFS tree edge. The parent vertex in673that edge is the bicomp root, and the bicomp contains one DFS child674of the vertex, which is on the child end of the 'root edge'.675676Here we decide whether the DFS child is to be embedded between or677beyond its parent relative to vertex I, the one currently being678processed (and the ancestor endpoint of a back edge being embedded,679where the descendant endpoint is also an endpoint of the bicomp680root being merged).681********************************************************************/682683void _CollectDrawingData(DrawPlanarContext *context, int RootVertex, int W, int WPrevLink)684{685graphP theEmbedding = context->theGraph;686//int ancestorChild = RootVertex - theEmbedding->N;687//int ancestor = theEmbedding->V[ancestorChild].DFSParent;688int K, Parent, BicompRoot, DFSChild, direction, descendant;689690gp_LogLine("\ngraphDrawPlanar.c/_CollectDrawingData() start");691gp_LogLine(gp_MakeLogStr3("_CollectDrawingData(RootVertex=%d, W=%d, W_in=%d)",692RootVertex, W, WPrevLink));693694/* Process all of the merge points to set their drawing flags. */695696for (K = 0; K < sp_GetCurrentSize(theEmbedding->theStack); K += 4)697{698/* Get the parent and child that are about to be merged from699the 4-tuple in the merge stack */700Parent = theEmbedding->theStack->S[K];701BicompRoot = theEmbedding->theStack->S[K+2];702DFSChild = BicompRoot - theEmbedding->N;703704/* We get the active descendant vertex in the child bicomp that705will be adjacent to the parent along the external face.706This vertex is guaranteed to be found in one step707due to external face 'short-circuiting' that was done in708step 'Parent' of the planarity algorithm.709We pass theEmbedding->N for the second parameter because710of this; we use this function to signify need of extFace711links in the other implementation.*/712713direction = theEmbedding->theStack->S[K+3];714descendant = _GetNextExternalFaceVertex(theEmbedding, BicompRoot, &direction);715716/* Now we set the tie flag in the DFS child, and mark the717descendant and parent with non-NIL pointers to the child718whose tie flag is to be resolved as soon as one of the719two is connected to by an edge or child bicomp merge. */720721context->V[DFSChild].drawingFlag = DRAWINGFLAG_TIE;722723context->V[descendant].tie[direction] = DFSChild;724725direction = theEmbedding->theStack->S[K+1];726context->V[Parent].tie[direction] = DFSChild;727728gp_LogLine(gp_MakeLogStr5("V[Parent=%d]=.tie[%d] = V[descendant=%d].tie[%d] = (child=%d)",729Parent, direction, descendant, theEmbedding->theStack->S[K+3], DFSChild));730}731732gp_LogLine("graphDrawPlanar.c/_CollectDrawingData() end\n");733}734735/********************************************************************736_BreakTie()737738The given vertex W has just been arrived at by the core planarity739algorithm. Using WPrevLink, we seek its predecessor WPred on the740external face and test whether the two are involved in a tie that741can be resolved.742743Since the planarity algorithm has just passed by WPred, it is744safe to conclude that WPred can go between W and the current vertex.745746Of course, if W was the parent to some DFS child whose subtree747contains WPred, then the DFS child is marked 'between', placing748the whole subtree including WPred between W and the current vertex.749On the other hand, if WPred was the parent of some DFS child whose750subtree contained W, then we achieve the same effect of putting WPred751'between' W and the curent vertex by marking the DFS child 'beyond'.752Since the DFS child and hence W are beyond W relative to the current753vertex, WPred is also between W and the current vertex.754755Thus the certain positional relationship between W and WPred756relative to a specific ancestor, the current vertex, is used to757indirectly break the positional tie between MIN(W, WPred) and the758DFS child of MIN(W, WPred) whose subtree contains MAX(W, WPred).759760The ancestorChild is the DFS child of the current vertex whose DFS761subtree contains W and WPred, and it is recorded here in order to762optimize the post-processing calculation of vertex positions.763********************************************************************/764765int _BreakTie(DrawPlanarContext *context, int BicompRoot, int W, int WPrevLink)766{767graphP theEmbedding = context->theGraph;768769/* First we get the predecessor of W. */770771int WPredNextLink = 1^WPrevLink,772WPred = _GetNextExternalFaceVertex(theEmbedding, W, &WPredNextLink);773774gp_LogLine("\ngraphDrawPlanar.c/::_BreakTie() start");775gp_LogLine(gp_MakeLogStr3("_BreakTie(BicompRoot=%d, W=%d, W_in=%d)",776BicompRoot, W, WPrevLink));777778/* Ties happen only within a bicomp (i.e. between two non-root vertices) */779if (W > theEmbedding->N || WPred >= theEmbedding->N)780{781gp_LogLine("graphDrawPlanar.c/_BreakTie() end\n");782return OK;783}784785/* The two vertices are either tied or not; having one tied and the other786not is an error */787788if (context->V[W].tie[WPrevLink] != context->V[WPred].tie[WPredNextLink])789return NOTOK;790791/* If there is a tie, it can now be resolved. */792if (context->V[W].tie[WPrevLink] != NIL)793{794int DFSChild = context->V[W].tie[WPrevLink];795796/* Set the two ancestor variables that contextualize putting W 'between'797or 'beyond' its parent relative to what. */798799context->V[DFSChild].ancestorChild = BicompRoot - theEmbedding->N;800context->V[DFSChild].ancestor = theEmbedding->V[BicompRoot - theEmbedding->N].DFSParent;801802gp_LogLine(gp_MakeLogStr4("V[child=%d]=.ancestorChild = %d, V[child=%d]=.ancestor = %d",803DFSChild, context->V[DFSChild].ancestorChild, DFSChild, context->V[DFSChild].ancestor));804805/* If W is the ancestor of WPred, then the DFSChild subtree contains806WPred, and so must go between W and some ancestor. */807if (W < WPred)808{809context->V[DFSChild].drawingFlag = DRAWINGFLAG_BETWEEN;810gp_LogLine(gp_MakeLogStr3("Child=%d is 'between' ancestorChild=%d and ancestor=%d",811DFSChild, context->V[DFSChild].ancestorChild, context->V[DFSChild].ancestor));812}813814/* If W is the descendant, so we achieve the effect of putting WPred815between DFSChild and ancestor by putting the DFSChild 'beyond' WPred. */816else817{818context->V[DFSChild].drawingFlag = DRAWINGFLAG_BEYOND;819gp_LogLine(gp_MakeLogStr3("Child=%d is 'beyond' ancestorChild=%d relative to ancestor=%d",820DFSChild, context->V[DFSChild].ancestorChild, context->V[DFSChild].ancestor));821}822823/* The tie is resolved so clear the flags*/824context->V[W].tie[WPrevLink] = context->V[WPred].tie[WPredNextLink] = NIL;825}826else827return NOTOK;828829gp_LogLine("graphDrawPlanar.c/_BreakTie() end\n");830return OK;831}832833/********************************************************************834_RenderToString()835Draws the previously calculated visibility representation in a836string of size (M+1)*2N + 1 characters, which should be deallocated837with free().838839Returns NULL on failure, or the string containing the visibility840representation otherwise. The string can be printed using %s,841********************************************************************/842843char *_RenderToString(graphP theEmbedding)844{845DrawPlanarContext *context = NULL;846gp_FindExtension(theEmbedding, DRAWPLANAR_ID, (void *) &context);847848if (context != NULL)849{850int N = theEmbedding->N;851int M = theEmbedding->M;852int I, J, e, Mid, Pos;853char *visRep = (char *) malloc(sizeof(char) * ((M+1) * 2*N + 1));854char numBuffer[32];855856if (visRep == NULL)857return NULL;858859if (sp_NonEmpty(context->theGraph->edgeHoles))860{861free(visRep);862return NULL;863}864865// Clear the space866for (I = 0; I < N; I++)867{868for (J=0; J < M; J++)869{870visRep[(2*I) * (M+1) + J] = ' ';871visRep[(2*I+1) * (M+1) + J] = ' ';872}873874visRep[(2*I) * (M+1) + M] = '\n';875visRep[(2*I+1) * (M+1) + M] = '\n';876}877878// Draw the vertices879for (I = 0; I < N; I++)880{881Pos = context->G[I].pos;882for (J=context->G[I].start; J<=context->G[I].end; J++)883visRep[(2*Pos) * (M+1) + J] = '-';884885// Draw vertex label886Mid = (context->G[I].start + context->G[I].end)/2;887sprintf(numBuffer, "%d", I);888if ((unsigned)(context->G[I].end - context->G[I].start + 1) >= strlen(numBuffer))889{890strncpy(visRep + (2*Pos) * (M+1) + Mid, numBuffer, strlen(numBuffer));891}892// If the vertex width is less than the label width, then fail gracefully893else894{895if (strlen(numBuffer)==2)896visRep[(2*Pos) * (M+1) + Mid] = numBuffer[0];897else898visRep[(2*Pos) * (M+1) + Mid] = '*';899900visRep[(2*Pos+1) * (M+1) + Mid] = numBuffer[strlen(numBuffer)-1];901}902}903904// Draw the edges905for (e=0; e<M; e++)906{907J = 2*N + 2*e;908Pos = context->G[J].pos;909for (I=context->G[J].start; I<context->G[J].end; I++)910{911if (I > context->G[J].start)912visRep[(2*I) * (M+1) + Pos] = '|';913visRep[(2*I+1) * (M+1) + Pos] = '|';914}915}916917// Null terminate string and return it918visRep[(M+1) * 2*N] = '\0';919return visRep;920}921922return NULL;923}924925/********************************************************************926gp_DrawPlanar_RenderToFile()927Creates a rendition of the planar graph visibility representation928as a string, then dumps the string to the file.929********************************************************************/930int gp_DrawPlanar_RenderToFile(graphP theEmbedding, char *theFileName)931{932if (sp_IsEmpty(theEmbedding->edgeHoles))933{934FILE *outfile;935char *theRendition;936937if (strcmp(theFileName, "stdout") == 0)938outfile = stdout;939else if (strcmp(theFileName, "stderr") == 0)940outfile = stderr;941else outfile = fopen(theFileName, WRITETEXT);942943if (outfile == NULL)944return NOTOK;945946theRendition = _RenderToString(theEmbedding);947if (theRendition != NULL)948{949fprintf(outfile, "%s", theRendition);950free(theRendition);951}952953if (strcmp(theFileName, "stdout") == 0 || strcmp(theFileName, "stderr") == 0)954fflush(outfile);955956else if (fclose(outfile) != 0)957return NOTOK;958959return theRendition ? OK : NOTOK;960}961962return NOTOK;963}964965/********************************************************************966_CheckVisibilityRepresentationIntegrity()967********************************************************************/968969int _CheckVisibilityRepresentationIntegrity(DrawPlanarContext *context)970{971graphP theEmbedding = context->theGraph;972int I, e, J, JTwin, JPos, JIndex;973974if (sp_NonEmpty(context->theGraph->edgeHoles))975return NOTOK;976977_FillVisitedFlags(theEmbedding, 0);978979/* Test whether the vertex values make sense and980whether the vertex positions are unique. */981982for (I = 0; I < theEmbedding->N; I++)983{984if (theEmbedding->M > 0)985{986if (context->G[I].pos < 0 ||987context->G[I].pos >= theEmbedding->N ||988context->G[I].start < 0 ||989context->G[I].start > context->G[I].end ||990context->G[I].end >= theEmbedding->M)991return NOTOK;992}993994// Has the vertex position (context->G[I].pos) been used by a995// vertex before vertex I?996if (theEmbedding->G[context->G[I].pos].visited)997return NOTOK;998999// Mark the vertex position as used by vertex I.1000// Note that this marking is made on some other vertex unrelated to I1001// We're just reusing the vertex visited array as cheap storage for a1002// detector of reusing vertex position integers.1003theEmbedding->G[context->G[I].pos].visited = 1;1004}10051006/* Test whether the edge values make sense and1007whether the edge positions are unique */10081009for (e = 0; e < theEmbedding->M; e++)1010{1011/* Each edge has an index location J in the graph structure */1012J = theEmbedding->edgeOffset + 2*e;1013JTwin = gp_GetTwinArc(theEmbedding, J);10141015if (context->G[J].pos != context->G[JTwin].pos ||1016context->G[J].start != context->G[JTwin].start ||1017context->G[J].end != context->G[JTwin].end ||1018context->G[J].pos < 0 ||1019context->G[J].pos >= theEmbedding->M ||1020context->G[J].start < 0 ||1021context->G[J].start > context->G[J].end ||1022context->G[J].end >= theEmbedding->N)1023return NOTOK;10241025/* Get the recorded horizontal position of that edge,1026a number between 0 and M-1 */10271028JPos = context->G[J].pos;10291030/* Convert that to an index in the graph structure so we1031can use the visited flags in the graph's edges to1032tell us whether the positions are being reused. */10331034JIndex = theEmbedding->edgeOffset + 2*JPos;1035JTwin = gp_GetTwinArc(theEmbedding, JIndex);10361037if (theEmbedding->G[JIndex].visited || theEmbedding->G[JTwin].visited)1038return NOTOK;10391040theEmbedding->G[JIndex].visited = theEmbedding->G[JTwin].visited = 1;1041}10421043/* Test whether any edge intersects any vertex position1044for a vertex that is not an endpoint of the edge. */10451046for (e = 0; e < theEmbedding->M; e++)1047{1048J = theEmbedding->edgeOffset + 2*e;1049JTwin = gp_GetTwinArc(theEmbedding, J);10501051for (I = 0; I < theEmbedding->N; I++)1052{1053/* If the vertex is an endpoint of the edge, then... */10541055if (theEmbedding->G[J].v == I || theEmbedding->G[JTwin].v == I)1056{1057/* The vertical position of the vertex must be1058at the top or bottom of the edge, */1059if (context->G[J].start != context->G[I].pos &&1060context->G[J].end != context->G[I].pos)1061return NOTOK;10621063/* The horizontal edge position must be in the range of the vertex */1064if (context->G[J].pos < context->G[I].start ||1065context->G[J].pos > context->G[I].end)1066return NOTOK;1067}10681069/* If the vertex is not an endpoint of the edge... */10701071else // if (theEmbedding->G[J].v != I && theEmbedding->G[JTwin].v != I)1072{1073/* If the vertical position of the vertex is in the1074vertical range of the edge ... */10751076if (context->G[J].start <= context->G[I].pos &&1077context->G[J].end >= context->G[I].pos)1078{1079/* And if the horizontal position of the edge is in the1080horizontal range of the vertex, then return an error. */10811082if (context->G[I].start <= context->G[J].pos &&1083context->G[I].end >= context->G[J].pos)1084return NOTOK;1085}1086}1087}1088}108910901091/* All tests passed */10921093return OK;1094}109510961097