Skip to content

Commit

Permalink
feat: Add unflatten method
Browse files Browse the repository at this point in the history
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
  • Loading branch information
GordonSmith committed Jan 14, 2023
1 parent af11237 commit ce2d6ee
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 0 deletions.
130 changes: 130 additions & 0 deletions src-cpp/graphviz/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// #include <globals.h>
#include <gvplugin.h>
#include <graphviz_version.h>
#include <fstream>
// #include <cgraph++/AGraph.h>
// #include <gvc++/GVContext.h>
// #include <gvc++/GVLayout.h>
Expand Down Expand Up @@ -136,5 +137,134 @@ const char *Graphviz::layout(const char *src, const char *format, const char *en
return m_result.c_str();
}

static int myindegree(Agnode_t *n)
{
return agdegree(n->root, n, TRUE, FALSE);
}

/* need outdegree without selfarcs */
static int myoutdegree(Agnode_t *n)
{
Agedge_t *e;
int rv = 0;

for (e = agfstout(n->root, n); e; e = agnxtout(n->root, e))
{
if (agtail(e) != aghead(e))
rv++;
}
return rv;
}

static bool isleaf(Agnode_t *n)
{
return myindegree(n) + myoutdegree(n) == 1;
}

static bool ischainnode(Agnode_t *n)
{
return myindegree(n) == 1 && myoutdegree(n) == 1;
}

static void adjustlen(Agedge_t *e, Agsym_t *sym, int newlen)
{
char buf[12];

snprintf(buf, sizeof(buf), "%d", newlen);
agxset(e, sym, buf);
}

static Agsym_t *bindedgeattr(Agraph_t *g, const char *str)
{
return agattr(g, AGEDGE, const_cast<char *>(str), "");
}

#include <iostream>

const char *Graphviz::unflatten(const char *src, unsigned int MaxMinlen, bool Do_fans, unsigned int ChainLimit)
{
lastErrorStr[0] = '\0';
m_result = "";
Agraph_t *g = agmemread(src);
if (g)
{

Agnode_t *ChainNode;
unsigned int ChainSize = 0;

Agnode_t *n;
Agedge_t *e;
char *str;
Agsym_t *m_ix, *s_ix;
int cnt, d;

m_ix = bindedgeattr(g, "minlen");
s_ix = bindedgeattr(g, "style");

for (n = agfstnode(g); n; n = agnxtnode(g, n))
{
d = myindegree(n) + myoutdegree(n);
if (d == 0)
{
if (ChainLimit < 1)
continue;
if (ChainNode)
{
e = agedge(g, ChainNode, n, const_cast<char *>(""), TRUE);
agxset(e, s_ix, "invis");
ChainSize++;
if (ChainSize < ChainLimit)
ChainNode = n;
else
{
ChainNode = NULL;
ChainSize = 0;
}
}
else
ChainNode = n;
}
else if (d > 1)
{
if (MaxMinlen < 1)
continue;
cnt = 0;
for (e = agfstin(g, n); e; e = agnxtin(g, e))
{
if (isleaf(agtail(e)))
{
str = agxget(e, m_ix);
if (str[0] == 0)
{
adjustlen(e, m_ix, cnt % MaxMinlen + 1);
cnt++;
}
}
}

cnt = 0;
for (e = agfstout(g, n); e; e = agnxtout(g, e))
{
if (isleaf(e->node) || (Do_fans && ischainnode(e->node)))
{
str = agxget(e, m_ix);
if (str[0] == 0)
adjustlen(e, m_ix, cnt % MaxMinlen + 1);
cnt++;
}
}
}
}
FILE *fp = fopen("tmp.dot", "w");
agwrite(g, fp);
fclose(fp);
std::ifstream file("tmp.dot");
std::string graph_str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
remove("tmp.dot");
m_result = graph_str;
}
return m_result.c_str();
}

// Include JS Glue ---
#include "main_glue.cpp"
1 change: 1 addition & 0 deletions src-cpp/graphviz/main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ class Graphviz
void createFile(const char *path, const char *data);
const char *lastResult();
const char *layout(const char *dot, const char *format, const char *engine);
const char *unflatten(const char *dot, unsigned int MaxMinlen = 0, bool Do_fans = false, unsigned int ChainLimit = 0);
};
1 change: 1 addition & 0 deletions src-cpp/graphviz/main.idl
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ interface Graphviz
void createFile([Const] DOMString file, [Const] DOMString data);
[Const] DOMString lastResult();
[Const] DOMString layout([Const] DOMString dot, [Const] DOMString format, [Const] DOMString engine);
[Const] DOMString unflatten([Const] DOMString dot, long MaxMinlen, boolean Do_fans, long ChainLimit);
};
38 changes: 38 additions & 0 deletions src-ts/__tests__/graphviz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,41 @@ describe("options", async function () {
expect(plain1).to.not.equal(plain5);
});
});

const stripWhitespaces = str => str.replace(/[\r\n\t\s]+/g, "");

describe("unflatten", async function () {
it.only("simple", async function () {
const graphviz = await Graphviz.load();
const dot = `\
graph {
a -- 1;
a -- 2;
a -- 3;
a -- 4;
b;
c;
d;
e;
}`;
const after = graphviz.unflatten(dot, 2, false, 1);
expect(stripWhitespaces(after)).to.equal(stripWhitespaces(`\
graph {
a -- 1 [minlen=1];
a -- 2 [minlen=2];
a -- 3 [minlen=1];
a -- 4 [minlen=2];
b -- c [style=invis];
d -- e [style=invis];
}`));
expect(stripWhitespaces(after)).to.not.equal(stripWhitespaces(`\
graph {
a -- 1 [minlen=1];
a -- 2 [minlen=2];
a -- 3 [minlen=1];
a -- 4;
b -- c [style=invis];
d -- e [style=invis];
}`));
});
});
32 changes: 32 additions & 0 deletions src-ts/graphviz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,38 @@ export class Graphviz {
} finally {
this._module.destroy(graphViz);
}
if (!retVal && errorMsg) {
Graphviz.unload();
throw new Error(errorMsg);
}
return retVal;
}

/**
* unflatten is a preprocessor to dot that is used to improve the aspect ratio of graphs having many leaves or disconnected nodes. The usual layout for such a graph is generally very wide or tall. unflatten inserts invisible edges or adjusts the minlen on edges to improve layout compaction.
*
* @param dotSource {string}
* @param MaxMinlen {number}
* @param Do_fans {boolean}
* @param ChainLimit {number}
* @returns {string}
*/

unflatten(dotSource: string, MaxMinlen: number = 0, Do_fans: boolean = false, ChainLimit: number = 0): string {
if (!dotSource) return "";
const graphViz = new this._module.Graphviz();
let retVal = "";
let errorMsg = "";
try {
try {
retVal = graphViz.unflatten(dotSource, MaxMinlen, Do_fans, ChainLimit);
} catch (e: any) {
errorMsg = e.message;
};
errorMsg = graphViz.lastError() || errorMsg;
} finally {
this._module.destroy(graphViz);
}
if (!retVal && errorMsg) {
throw new Error(errorMsg);
}
Expand Down

0 comments on commit ce2d6ee

Please sign in to comment.