�ɲɾ�����ӯ�����һ��ˣ��������С���˴��ͣ�������P���ҹ��ñ˽��ά�Բ��������˸߸ԣ�������ơ��ҹ��ñ�����ά�Բ���ˡ���˳^�ӣ������ӡ� ���ͯj�ӣ��ƺ���ӣ� ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!PK1]Xv00cyclenu[/* Detect directed cycle and print one if found */ BEG_G{ node_t tp, hp; node_t stk[node_t]; $tvtype = TV_prepostfwd; $tvroot = fstnode($); } N { if (stk[$]) { stk[$] = NULL; } else if ($tvedge == NULL) { /* current root */ stk[$] = $; } else { stk[$] = $tvedge.tail; } } E { if (stk[$.head]) { tp = $.tail; hp = $.head; while (tp != $.head) { printf ("%s -> %s\n", tp.name, hp.name); hp = tp; tp = stk[tp]; } printf ("%s -> %s\n", tp.name, hp.name); exit(0); } } PK1]Xoybinducenu[/* Given a bipartite graph, induce a non-bipartite graph. * argv[0]="name=value" This is used to identify the nodes used * to induce edges. If aget(n,name) == value, * if deg(n) == 1, delete * if deg(n) == 2, delete and connect to neighbor with edge * if deg(n) > 2, delete and add edge between all pairs of neighbors * Add weights to edge. */ BEGIN{ int i, cnt; int wt[edge_t]; string values[int]; node_t nbrs[int]; edge_t e; tokens(ARGV[0],values,"="); string aname = values[0]; string value = values[1]; printf(2, "%s=%s\n", aname, value); } N[aget($,aname)==value] { if ($.degree > 1) { cnt = 0; for (e = fstedge($); e; e = nxtedge(e, $)) nbrs[cnt++] = opp(e,$); for (i = 0; i < cnt-1; i++) { if ((e = isEdge(nbrs[i],nbrs[i+1],"")) != NULL) { wt[e] += 1; } else if ($G.directed && (e = isEdge(nbrs[i+1],nbrs[i],""))) { wt[e] += 1; } else if (nbrs[i] != nbrs[i+1]) { // avoid loops e = edge(nbrs[i],nbrs[i+1],""); wt[e] = 1; } } unset(nbrs); } delete($G,$); } END_G{ for (wt[e]) { e.multiplicity = sprintf ("%d", wt[e]); } } PK1]2wrotatenu[/* Given node name and angle, rotate a layout using the given * node as origin. */ BEGIN { double x,y; double x0,y0; double x1,y1; double angle, cosa, sina; int cnt, sz; void rotate (double a, double b) { a -= x0; b -= y0; x1 = a*cosa - b*sina; y1 = a*sina + b*cosa; } char* rotateE (char* p) { char* newpos = ""; cnt = sscanf (p, "e,%lf,%lf%n", &x, &y, &sz); if (cnt == 2) { rotate (x,y); newpos = newpos + sprintf ("e%lf,%lf ", x1, y1); p = substr(p, sz); } cnt = sscanf (p, "s,%lf,%lf%n", &x, &y, &sz); if (cnt == 2) { rotate (x,y); newpos = newpos + sprintf ("s%lf,%lf ", x1, y1); p = substr(p, sz); } while (sscanf (p, "%lf,%lf%n", &x, &y, &sz) == 2) { rotate (x,y); newpos = newpos + sprintf ("%lf,%lf ", x1, y1); p = substr(p, sz); } return newpos; } } BEG_G { node_t ctr = node ($, ARGV[0]); sscanf (ARGV[1], "%f", &angle); cosa = cos(angle); sina = sin(angle); sscanf (ctr.pos, "%f,%f", &x0, &y0); $.bb =""; } N { sscanf ($.pos, "%f,%f", &x, &y); rotate (x,y); $.pos = sprintf ("%f,%f", x1, y1); } E { $.pos = rotateE($.pos); } PK1]*TUdelmultinu[/* create a copy of the input graph with no multiedges */ BEG_G { graph_t g = graph ("merge", "S"); } E { int wt; node_t h = node(g,$.head.name); node_t t = node(g,$.tail.name); edge_t e = isEdge(t,h,""); wt = $.weight; if (wt <= 0) wt = 1; if (e) { e.weight = e.weight + wt; } else if (h != t) { e = edge(t,h,""); e.weight = wt; } } END_G { fwriteG(g,1); } PK1]attrnu[/* List known node attributes */ BEG_G { char* attr; for (attr = fstAttr($,"N"); attr != ""; attr = nxtAttr($,"N",attr)) { print (attr); } } PK1]-fget-layers-listnu[BEG_G { char* larr[int]; int i; if (!isAttr($,"G","layers")) return; if (isAttr($,"G","layersep")) tokens($.layers,larr,$.layersep); else tokens($.layers,larr," :\t"); for (larr[i]) { if (i>0) printf(" %s",larr[i]); else printf("%s",larr[i]); } } PK1]߀N##depathnu[/* Replace paths a -> b -> ... -> c with a -> c */ BEGIN { edge_t e; node_t n, prv, nxt; } N [(indegree == 1) && (outdegree == 1)] { e = fstin ($); prv = e.tail; e = fstout ($); nxt = e.head; delete ($G,$); while ((prv.indegree == 1) && (prv.outdegree == 0)) { e = fstin (prv); n = e.tail; delete ($G,prv); prv = n; } while ((nxt.indegree == 0) && (nxt.outdegree == 1)) { e = fstout (nxt); n = e.head; delete ($G,nxt); nxt = n; } if (!isEdge (prv,nxt,"")) edge (prv,nxt,""); } PK1]_zm8collapsenu[/* Collapse all edges with same group attribute into a single edge */ BEG_G { int seen[string]; $O = $; // Use the input graph as output. } E { if (collapse == "") return; // If no collapse, ignore. if (seen[collapse]) delete ($G, $); // If already seen an edge with this collapse value, // delete the edge. else seen[collapse] = 1; // Else mark collapse value as seen and keep edge. } PK1]vpathnu[/* Report the distance from src = ARGV[0] to dst = ARGV[1] */ BEG_G { int dist[node_t]; node_t n, curn; node_t src = node($G, ARGV[0]); node_t dst = node($G, ARGV[1]); $tvroot = src; $tvtype = TV_bfs; } N { curn = $; if ($ == dst) { printf ("dist from %s to %s is %d\n", src.name, dst.name, dist[dst]); exit(0); } } E { if ($.head == curn) n = $.tail; else n = $.head; if (dist[n] == 0) dist[n] = dist[curn]+1; } PK1]#0 >>addranksnu[/* Assuming nodes have been positioned by dot, this adds a rank * attribute by placing all nodes with the same y value on a specific * integer rank. * If the graph has rankdir=LR, x and y are flipped. */ BEG_G { double x,y; int lv[double]; int r, rk[double]; int flip; if (isAttr($,"G","rankdir") && $.rankdir=="LR") flip = 1; } N { sscanf($.pos,"%f,%f",&x,&y); if (flip) lv[x] = 1; else lv[y] = 1; } BEG_G { r = 0; if (flip) forr (lv[x]) { rk[x] = r++; /* printf (2, "rk[%f] = %d\n", y, rk[y]); */ } else forr (lv[y]) { rk[y] = r++; /* printf (2, "rk[%f] = %d\n", y, rk[y]); */ } } N { sscanf($.pos,"%f,%f",&x,&y); /* printf(2, "node %s y %f rk %d\n", $.name, y, rk[y]); */ if (flip) $.rank = sprintf("%d", rk[x]); else $.rank = sprintf("%d", rk[y]); } PK1]`{{scalenu[/* finds node n with root attribute * finds distance minr of closest node * the layout is then scaled out from n so that * a node is put on the smallest circle of radius x*minr * containing n */ BEG_G { node_t ctr; int cx, cy; int x, y; double delx, dely; int newx, newy; node_t n; edge_t e; int i, sc, d, mind = -1; double fact, newr, ang, minr; ctr = node($,aget($,"root")); sscanf (ctr.pos, "%d,%d", &cx, &cy); for (e = fstedge(ctr); e; e = nxtedge(e, ctr)) { if (e.head == ctr) n = e.tail; else n = e.head; sscanf (n.pos, "%d,%d", &x, &y); d = (x-cx)*(x-cx) + (y-cy)*(y-cy); if ((mind == -1) || (d < mind)) mind = d; } minr = (int)sqrt((double)mind); } N [$ != ctr] { sscanf ($.pos, "%d,%d", &x, &y); dely = y - cy; delx = x - cx; d = delx*delx + dely*dely; sc = (int)sqrt((double)(d/mind)); if (sc > 1) { fact = 2.0; for (i=1; i xmax) xmax = x; if (y > ymax) ymax = y; } } END { printf ("(%f,%f) (%f,%f)\n", xmin, ymin, xmax, ymax); if (ARGC) printf ("area = %f aspect = %f\n", ((xmax-xmin)*(ymax-ymin))/1000000., (xmax-xmin)/(ymax-ymin)); } PK1].<33maxdegnu[/* find nodes of max and min degree */ BEG_G {node_t mxn, mnn; int maxd = -1; int mind = 1000000;} N { if (degree > maxd) {maxd = degree; mxn = $;} if (degree < mind) {mind = degree; mnn = $;} } END_G {printf ("max degree = %d, node %s, min degree = %d, node %s\n", maxd, mxn.name, mind, mnn.name)} PK1]Mctoponnu[/* Generate copy of topology of input graph * Replace names with numbers */ BEGIN { int id = 0; char* names[char*]; char* mapn (char* inname) { char* s = names[inname]; if (s == "") { s = id++; names[inname] = s; } return s; } } BEG_G { graph_t g = graph ($.name, "U"); } N { node (g, mapn($.name)); } E { edge (node (g, mapn($.tail.name)), node (g, mapn($.head.name)), ""); } END_G { write (g); } PK1]D''addringsnu[/* Takes a graph laid out by twopi and adds rings. * Assumes ARGV[] = "root" "=" , as output by twopi -v. * Usage: * twopi -v foo.dot > out 2> log * gvpr -f addrings.g -a"`grep root log`" out | neato -n2 ... */ BEG_G { graph_t og; edge_t e; node_t ctr = node($, ARGV[0]); double rs = 1.0; /* min. slack between the squares of two consecutive radii */ int cx, cy; int x, y; node_t n; int i, n_r; int d; int rads[int]; char* ctr_s = ctr.pos; sscanf (ctr_s, "%d,%d", &cx, &cy); if (hasAttr($, "ranksep")) { sscanf ($.ranksep, "%f", &rs); if (rs == 0.0) rs = 1.0; } rs *= 72; rs = 1.5*rs*rs; } N [$ != ctr] { sscanf ($.pos, "%d,%d", &x, &y); d = (x-cx)*(x-cx) + (y-cy)*(y-cy); for (rads[i]) { if ((rads[i]-rs <= d) && (d <= rads[i]+rs)) return; } n_r++; rads[n_r] = d; } END_G { og = copy (NULL, $); og.outputorder = "nodesfirst"; setDflt (og, "N", "label", "\\N"); for (rads[i]) { n = node(og, "ring_"+((string)i)); n.shape = "circle"; n.pos = ctr_s; n.style = ""; n.label = ""; d = rads[i]; n.width = sprintf("%f", sqrt(d)/36.0); } for (n=fstnode($);n;n = nxtnode(n)) clone (og, n); for (n=fstnode($);n;n = nxtnode(n)) for (e=fstedge(n);e;e = nxtedge(e,n)) clone (og, e); write(og); } PK1]#K chkclustersnu[/* Report if graph has non-hierarchical clusters */ BEGIN { graph_t c[node_t]; node_t n; void proc (graph_t h, graph_t p, graph_t g) { if (h.name == "cluster*") { for (n = fstnode(h); n; n = nxtnode_sg(h,n)) { g = c[n]; if (g) { if (g != p) { printf(2,"node %s in %s and %s\n", n.name, h.name, g.name); exit(1); } } c[n] = h; } p = h; } for (g = fstsubg(h); g; g = nxtsubg(g)) { proc(g,p,NULL); } } } BEG_G { proc($,$,NULL); } PK1]wk`YYcolnu[# test /* color nodes using output of dijkstra */ BEG_G { double h, hn, hf, d, sat, md = maxdist; if (hue_near =="") hue_near = 0.8; if (hue_far =="") hue_far = 1.2; hn = hue_near; hf = hue_far; } # test N { d = dist; sat = (md - d + 1.0) /( md + 1.0); h = hn + ((hf - hn) * d)/md; while (h < 0.0) h = h + 1.0; while (h > 1.0) h = h - 1.0; color = sprintf("%lf %lf %lf",hue,sat,1.0); /* make sure the shape is filled */ if (!match(style,"filled")>=0) { if (style != "") style=sprintf("%s,filled",style); else style="filled"; } } PK1]/?%colornu[/* color edges based on normalized length alpha. * If we have lim[i-1] <= alpha <= lim[i], the color * is linearly interpolated between color[i-1] and color[i]. * Edges already having a color attribute are left unchanged. */ BEGIN { double lens [edge_t]; double maxlen, len, minlen=1.7976931348623157e+308; double alpha, x0, y0, x1, y1; double h0, s0, v0; int i; int ncolors; /* number of colors: ncolors >= 2 */ /* start of color i. * lim[0]=0 < lim[1] < ... < lim[ncolors-1]=1 */ double lim[int]; /* HSV values for color i */ double h[int]; double s[int]; double v[int]; /* simple default */ ncolors = 2; lim[0] = 0; lim[1] = 1; h[0] = 0; h[1] = 0.67; s[0] = 1; s[1] = 1; v[0] = 1; v[1] = 1; } E{ sscanf ($.tail.pos, "%f,%f", &x0, &y0); sscanf ($.head.pos, "%f,%f", &x1, &y1); len = sqrt ((x0-x1)*(x0-x1) + (y0-y1)*(y0-y1)); lens[$] = len; if (len > maxlen) maxlen = len; if (len < minlen) minlen = len; } BEG_G { if (!isAttr($,"E","color")) setDflt($,"E","color",""); } E{ if ($.color != "") return; alpha = (lens[$]-minlen)/(maxlen-minlen); for (i = 1; i < ncolors; i++) { if (alpha < lim[i]) break; } alpha = (alpha - lim[i-1])/(lim[i] - lim[i-1]); h0 = (1-alpha)*h[i-1] + alpha*h[i]; s0 = (1-alpha)*s[i-1] + alpha*s[i]; v0 = (1-alpha)*v[i-1] + alpha*v[i]; $.color = sprintf ("%.02f %.02f %.02f", h0, s0, v0); } PK1]\rccscalexynu[/* finds root node of graph. * scales the x and y position of all other nodes using, first, * ARGV[0], or $G.scale. * * The expected syntax is "x,y" where at least one of x or y must * be given. If only one is given, the other is taken as 1. */ BEGIN { double scalex, scaley; int r, done; int setScale (char* s) { if ((sscanf (s, ",%f",&scaley))) { scalex = 1; return 1; } else { r = sscanf (s, "%f,%f",&scalex,&scaley); if (r) { if (r == 1) scaley = 1; return 1; } } return 0; } } BEG_G { node_t ctr = node($,aget($,"root")); double cx, cy, x, y, delx; /* get scale argument */ done = 0; if (ARGC == 1) done = setScale (ARGV[0]); if (!done && isAttr($,"G","scale")) done = setScale ($.scale); if (!done) scalex = scaley = 1.0; if ((scalex == 1.0) && (scaley == 1.0)) exit (0); $.bb = ""; sscanf (ctr.pos, "%f,%f", &cx, &cy); } N [$ != ctr] { sscanf ($.pos, "%f,%f", &x, &y); delx = scalex*(x - cx) + cx; dely = scaley*(y - cy) + cy; $.pos = sprintf ("%f,%f", delx, dely); } PK1]hbbnu[/* computes the bounding box of a graph based on its nodes taking into account clusters and node sizes. */ BEGIN { double x, y, w2, h2; double llx, lly, urx, ury; double llx0, lly0, urx0, ury0; graph_t clustBB (graph_t G) { graph_t sg; for (sg = fstsubg(G); sg; sg = nxtsubg(sg)) { sg = clustBB(sg); } if (G.name == "cluster*") { sscanf (G.bb, "%lf,%lf,%lf,%lf", &llx0, &lly0, &urx0, &ury0); if (llx0 < llx) llx = llx0; if (lly0 < lly) lly = lly0; if (urx0 > urx) urx = urx0; if (ury0 > ury) ury = ury0; } return G; } } BEG_G { llx = 1000000; lly = 1000000; urx = -1000000; ury = -1000000; } N { sscanf ($.pos, "%lf,%lf", &x, &y); w2 = (36.0*(double)$.width); h2 = (36.0*(double)$.height); if ((x - w2) < llx) llx = x - w2; if ((x + w2) > urx) urx = x + w2; if ((y - h2) < lly) lly = y - h2; if ((y + h2) > ury) ury = y + h2; } END_G { clustBB ($); $.bb = sprintf ("%lf,%lf,%lf,%lf", llx, lly, urx, ury); } PK1]{N?? histogramnu[/* print histogram of integer attribute */ BEGIN { int count[]; int maxd = 0; int i, d, v; char* attrname = ARGV[0]; } N{ v = (int)(aget($,attrname)); count[v]++; if (v > maxd) { maxd = v; } } END { for (i = 1; i <= maxd; i++) { d = count[i]; if (d > 0) printf ("[%d] %d\n", i, d); } } PK1]kQzzindentnu[/* Print the depth-first traversal of nodes * as an indented list */ BEGIN { int i, indent; int seen[string]; void prInd () { for (i = 0; i < indent; i++) printf (" "); } } BEG_G { $tvtype = TV_prepostfwd; $tvroot = node($,ARGV[0]); } N { if (seen[$.name]) indent--; else { prInd(); print ($.name); seen[$.name] = 1; indent++; } } PK1]E dijkstranu[/* Given graph processed by dijkstra, and node, * color shortest path * Assumes path has been computed by dijkstra */ BEG_G { node_t n = isNode($,ARGV[0]); node_t nxt; edge_t e; double d, totd = 0; if (n == NULL) { printf(2, "no node named \"%s\"\n", ARGV[0]); exit(1); } while (n.prev != "") { nxt = isNode($,n.prev); /* printf(2, "nxt \"%s\"\n", nxt.name); */ e = isEdge (n, nxt, ""); if (e == NULL) { printf(2, "no edge between %s and %s\n", n.name, nxt.name); } e.color = "blue"; /* printf(2, "len %s\n", e.len); */ /* sscanf (e.len, "%f", &d); */ /* totd += d; */ n = nxt; } } PK1](delnodesnu[/* Delete nodes whose names are given in ARGV */ BEG_G { int names[char*]; int nodes[node_t]; node_t n; int i; for (i = 0; i < ARGC; i++) names[ARGV[i]] = 1; } N[names[name]]{nodes[$] = 1} END_G { for (nodes[n]) delete ($,n); } PK1]u__deghistnu[/* print histogram of node degrees */ BEGIN { int degrees[]; int maxd = 0; int i, d; char* maxn; } N{ degrees[degree]++; if (degree > maxd) { maxn = $.name; maxd = degree; } } END { printf ("max node %s (%d)\n", maxn, maxd); for (i = 1; i <= maxd; i++) { d = degrees[i]; if (d > 0) printf ("[%d] %d\n", i, d); } } PK1]knbhdnu[/* knbhd - Return the k-neighborhood of a node, i.e., allnodes * whose path length from the given node is <= k. * ARGV[] = k node_name */ BEG_G { node_t ctr; int maxlen; graph_t comp = subg($, "kcomp"); int sid = 0, eid = 0; int curlen; node_t curnode; int nlen[node_t]; node_t stk[int]; node_t other; edge_t e; if (ARGC != 2) { printf (2, "Two arguments required\n"); exit(1); } if (!sscanf(ARGV[0],"%d",&maxlen)) { printf (2, "Improper length parameter \"%s\"\n", ARGV[0]); exit(1); } maxlen++; /* length of 0 means unset */ ctr = isNode ($, ARGV[1]); if (!ctr) { printf (2, "node %s not found\n", ARGV[1]); exit(1); } subnode (comp,ctr); nlen[ctr] = 1; curnode = ctr; while (curnode) { curlen = nlen[curnode]; if (curlen == maxlen) break; for (e = fstedge(curnode); e; e = nxtedge(e,curnode)) { other = e.head; if (other == curnode) other = e.tail; if (nlen[other]) continue; /* already seen */ subnode(comp,other); nlen[other] = curlen+1; stk[eid++] = other; } if (sid < eid) curnode = stk[sid++]; else curnode = NULL; } induce(comp); write(comp); } PK1] ##addedgesnu[/* Add edges from input graph to argument graph * Does not add nodes. */ BEGIN{ graph_t g = readG(ARGV[0]); node_t h, t; edge_t e; } E { if ((h = isNode(g,head.name)) && (t = isNode(g,tail.name))) { if (!isEdge(t,h,"")) { e = copy(g,$); } } } END { write(g); } PK1]Wdechainnu[/* Remove peninsulas - chains hanging off the main graph */ BEGIN { edge_t e; node_t v, n; } N [degree == 1] { n = $; while (n.degree == 1) { e = fstedge (n); if (e.head == n) v = e.tail; else v = e.head; delete($G,n); n = v; } } PK1]v Q treetoclustnu[/* Convert a rooted tree to a hierarchy of clusters for patchwork. * ARGV[0] is desired root */ BEG_G { node_t rt; node_t n; graph_t cg; graph_t sg; int depth; int mark[node_t]; graph_t stk[int]; if (! $.directed) { printf(2,"Input graph is not directed\n"); exit (1); } rt = isNode($,ARGV[0]); if (rt == NULL) { printf(2,"Root node \"%s\" not found\n", ARGV[0]); exit (1); } $tvroot = rt; $tvtype = TV_prepostfwd; cg = graph(rt.name,"U"); } N { if (mark[$]) { depth--; } else { mark[$] = 1; if (depth > 0) { if (fstout($)) { sg = subg(stk[depth-1], "cluster_" + $.name); if (($.style == "filled") && ($.fillcolor != "")) sg.bgcolor = $.fillcolor; } else { sg = NULL; n = node(stk[depth-1], $.name); n.style = "filled"; n.fillcolor = $.fillcolor; } } else sg = cg; stk[depth] = sg; depth++; } } END_G { write(cg); } PK1]W/cliptreenu[/* Construct subgraph reachable from node ARGV[0] by forward edges */ BEG_G { node_t r = node($,ARGV[0]); $tvroot = r; $tvtype = TV_fwd; } N{$tvroot=NULL; subnode($T,$);} PK1]|chkedgesnu[/* Looks for multiedges and loops, and output * those found along with counts. If the -d flag * is given, edge direction is taken into account. */ BEGIN{ char* ename; char* n; int doDir, cnt[]; int loopcnt[]; int nloops, nmulti; if ((ARGC > 0) && (ARGV[0] == "-d")) doDir = 1; else doDir = 0; } BEG_G{unset(cnt); unset(loopcnt); nloops = nmulti = 0;} E{ if (doDir || (tail.name <= head.name)) ename=tail.name+"_"+head.name; else ename = head.name+"_"+tail.name; if (tail == head) { loopcnt[ename] += 1; if (loopcnt[ename] == 1) nloops += 1; } else { cnt[ename] += 1; if (cnt[ename] == 2) nmulti += 1; } } END_G{ printf ("graph %s: %d loops %d multiedges\n", $.name, nloops, nmulti); for (cnt[n]) { if (cnt[n] > 1) printf ("%s : %d\n", n, cnt[n]); } for (loopcnt[n]) { if (loopcnt[n] > 0) printf ("%s : %d\n", n, loopcnt[n]); } } PK1]attdeledgesnu[/* delete all edges */ BEGIN { int map[edge_t]; edge_t e; } E {map[$]=1} END_G { for (map[e]) delete ($,e); } PK1][bipartnu[/* Determine if a graph is bipartite or not. */ BEG_G{ int vc, c, color[node_t]; node_t v; edge_t e; $tvtype = TV_dfs; $tvroot = fstnode($); } N{ if ($tvedge == NULL) color[$] = 1; if (color[$] == 1) c = 2; else c = 1; for (e = fstedge($); e; e = nxtedge(e,$)) { v = opp(e,$); vc = color[v]; if (vc == 0) color[v] = c; else if (vc != c) { printf(2, "Not bipartite\n"); exit(1); } } } PK1]Xv00cyclenu[PK1]Xoyebinducenu[PK1]2w*rotatenu[PK1]*TU delmultinu[PK1] attrnu[PK1]-fget-layers-listnu[PK1]߀N##depathnu[PK1]_zm8Gcollapsenu[PK1]vTpathnu[PK1]#0 >>Iaddranksnu[PK1]`{{scalenu[PK1]XXoflattennu[PK1]+{groupnu[PK1]V$ J"spannu[PK1]Ϧ #anonnu[PK1]$bboxnu[PK1].<33+'maxdegnu[PK1]Mc(toponnu[PK1]D''v*addringsnu[PK1]#K /chkclustersnu[PK1]wk`YY$2colnu[PK1]/?%4colornu[PK1]\rcc:scalexynu[PK1]h?bbnu[PK1]{N?? 6Chistogramnu[PK1]kQzzDindentnu[PK1]E ^Fdijkstranu[PK1](Idelnodesnu[PK1]u__NJdeghistnu[PK1]Kknbhdnu[PK1] ##Paddedgesnu[PK1]W>Rdechainnu[PK1]v Q }Streetoclustnu[PK1]W/Wcliptreenu[PK1]|~Xchkedgesnu[PK1]attJ\deledgesnu[PK1][\bipartnu[PK%%6 ^