From 1ba8167396fef9be41d63c8817c485b1e5cc4b81 Mon Sep 17 00:00:00 2001 From: Josef Vonasek Date: Mon, 13 May 2019 14:41:15 +0200 Subject: [PATCH] Added styles and layout compaction. --- src/ILP.py | 37 ++++++++---- src/graph.coffee | 70 ++++++++++++---------- src/layoutdata.coffee | 132 ------------------------------------------ src/main.coffee | 76 ++++++++++++++---------- 4 files changed, 110 insertions(+), 205 deletions(-) delete mode 100644 src/layoutdata.coffee diff --git a/src/ILP.py b/src/ILP.py index 355a9dc..011e76b 100644 --- a/src/ILP.py +++ b/src/ILP.py @@ -5,24 +5,39 @@ class GraphLayout: def __init__(self): - self.node = {} # id: (y,x) + self.styl = [] # label: style + self.node = {} # id: (y,x,rank,label) self.edge = {} # id: (s,t,offset) @classmethod - def loadJson(cls, file): - data = json.loads(open(file, 'r').read()) + def loadCoffee(cls, file): + data = (line for line in open(file, 'r')) grph = cls() - grph.node = {int(k): tuple(v) for k,v in data['nodes'].items()} - grph.edge = {int(k): tuple(v) for k,v in data['edges'].items()} + next(data) + next(data) + for line in data: + if 'nodes:' in line: break + grph.styl.append(line) + for line in data: + if 'edges:' in line: break + line = line.split(':') + [y,x,r,l] = [l.split(',')[0].split('}')[0] for l in line[2:]] + grph.node[int(line[0])] = int(y), int(x), int(r), l + for line in data: + line = line.split(':') + [s,t] = [l.split(',')[0].split('}')[0] for l in line[2:]] + grph.edge[int(line[0])] = int(s), int(t), 0 return grph def saveCoffee(self, file): f = open(file, 'w') print('export graph =', file=f) + print(' styles:', file=f) + for s in self.styl: print(s, file=f, end='') print(' nodes:', file=f) - for k, v in self.node.items(): print(f' {k}: {list(v)}', file=f) + for k, (y,x,r,l) in self.node.items(): print(f' {k}: {{y:{y}, x:{x}, rank:{r}, label:{l}}}', file=f) print(' edges:', file=f) - for k, v in self.edge.items(): print(f' {k}: {list(v)}', file=f) + for k, (s,t,o) in self.edge.items(): print(f' {k}: {{s:{s}, t:{t}}}', file=f) # exact, but slow grid layout algorithm def main(graph): @@ -78,7 +93,7 @@ def imp(name, cond, vals): # c[i] >= 0 ===> v[i] >= 0 # - set objective : change in node position + total edge length m.setObjective( - sum(abs(node[n] - x) for n, (_, x) in graph.node.items() if x is not None)+ + # sum(abs(node[n] - x) for n, (_, x, _, _) in graph.node.items() if x is not None)+ sum(abs(node[s] - node[t]) for s, t, _ in graph.edge.values()), g.GRB.MINIMIZE, ) @@ -88,13 +103,13 @@ def imp(name, cond, vals): # c[i] >= 0 ===> v[i] >= 0 m.optimize() - for n in graph.node: graph.node[n] = graph.node[n][y], int(round(node[n].X)) + for n in graph.node: graph.node[n] = graph.node[n][y], int(round(node[n].X)), graph.node[n][2], graph.node[n][3] for e in graph.edge: graph.edge[e] = graph.edge[e][0], graph.edge[e][1], int(round(edge[e].X)) print(graph.node) print({e: edge[e].X for e in graph.edge}) print({var.VarName: int(round(var.X)) for var in m.getVars()}) if __name__ == '__main__': - grph = GraphLayout.loadJson('../data/graphlayout.json') + grph = GraphLayout.loadCoffee('./layoutdata.coffee') main(grph) - grph.saveCoffee('./layoutData.coffee') \ No newline at end of file + grph.saveCoffee('./layoutdata.coffee') \ No newline at end of file diff --git a/src/graph.coffee b/src/graph.coffee index 095d713..06ad7a0 100644 --- a/src/graph.coffee +++ b/src/graph.coffee @@ -16,7 +16,7 @@ class Stack constructor: (@stack = []) -> - empty: ( ) => @stack.length == 0 + empty : ( ) => @stack.length == 0 insert: (x) => @stack.push x remove: ( ) => @stack.pop() @@ -27,10 +27,8 @@ class Queue constructor: (@frnt = [], @back = []) -> - empty: ( ) => - @frnt.length == 0 and @back.length == 0 - insert: (x) => - @back.push x + empty : ( ) => @frnt.length == 0 and @back.length == 0 + insert: (x) => @back.push x remove: ( ) => if @frnt.length == 0 @frnt = @back.reverse() @@ -95,28 +93,26 @@ class Graph class GraphLayout + label: {} sourc: [] ranks: {} nodes: {} edges: {} - reach: {} graph: null constructor: (@sourc = [0], @ranks = {0: 0}, @nodes = {}, @edges = {}) -> @graph = new Graph() step: (newnodes, newedges, layout = true) => - sourc = [] graph = new Graph() @addEdges graph, newnodes, newedges - @getRanks graph, newedges + @rankNodes graph, newedges @orientEdges graph, newedges - console.log ([n, @ranks[n]] for n of newnodes) - - for i, _ of newnodes + for i, [label] of newnodes @nodes[i] = [null, null] + @label[i] = label @dominanceLayout() if layout @@ -130,14 +126,13 @@ class GraphLayout graph.addEdge s, t @edges[i] = [s, t, o] - getRanks: (graph, newedges) => + rankNodes: (graph, newedges) => queue = new UniqueQueue Queue, @ranks, [] nodes = [] for _, [s,t] of newedges nodes.push(s) if s of @ranks nodes.push(t) if t of @ranks nodes = uniq(sort nodes, (n) => -@ranks[n]) - console.log "HERE", nodes for n in nodes queue.reinsert n, @ranks[n] for [n, r, _] from queue.iter() @@ -172,30 +167,43 @@ class GraphLayout remains[n] = @graph.rdges[n].length for [n, _, _] from queue.iter() @nodes[n][0] = y++ +# @nodes[n][0] = y++ if @trans[n] ys.push n for t from @graph.edges[n].reverse() queue.insert t, 1 if --remains[t] == 0 # COMPACTION + [x, ns] = [0, sort (n for n, _ of @nodes), (n) => @nodes[n][1]] + for i in [0..ns.length-2] + @nodes[ns[i+1]][1] = if int(ns[i+1]) == @graph.edges[ns[i]][0] then x else ++x + + for n, _ of @nodes when @graph.edges[n].length == 0 + @nodes[n][0] = 1 + Math.max.apply null, (@nodes[s][0] for s in @graph.rdges[n]) + + ns = sort (n for n, _ of @nodes), (n) => @nodes[n][0] + ys = (-1 for n, _ of @nodes) + for n in sort (n for n, _ of @nodes), (n) => @nodes[n][0] + parentY = Math.max.apply null, (@nodes[s][0] for s in @graph.rdges[n]) + ys[@nodes[n][1]] = @nodes[n][0] = 1 + Math.max parentY, ys[@nodes[n][1]] +# # for n, pos of @nodes -## pos[1] = pos[1] - pos[0] -# pos[0] = @ranks[n] #pos[1] + pos[0] * 2 -## [x, xs] = [0, sort (n for n,[_,x] of @nodes when x <= 0), (n) => -@nodes[n][1]] -## for i in [0..xs.length-2] -## @nodes[xs[i+1]][1] = if int(xs[i+1]) in @graph.edges[xs[i]] then x else --x -# ns = sort (n for n,[_,x] of @nodes when x > 0), (n) => @nodes[n][1] -# xs = (-1 for _ of @nodes) -# for n in ns -# @nodes[n][1] = xs[-@ranks[n]] = Math.max xs[-@ranks[n]-1], xs[-@ranks[n]]+1 +# pos[1] = pos[1] - pos[0] +# pos[0] = pos[1] + 2*pos[0] write: () => file = fs.openSync('./layoutdata.coffee', 'w') - fs.writeSync(file, "export graph =\n") - fs.writeSync(file, " nodes:\n") - fs.writeSync(file, " " + n + " : [" + d + "]\n") for n, d of @nodes - fs.writeSync(file, " edges:\n") - fs.writeSync(file, " " + e + " : [" + d + "]\n") for e, d of @edges - fs.closeSync(file) + fs.writeSync file, "export graph =\n" + fs.writeSync file, " styles:\n" + labels = uniq (l for _, l of @label).sort() + for label, i in labels + fs.writeSync file, " #{label}: {hsl: [#{i/(labels.length-1)},1,.5]}\n" + fs.writeSync file, " nodes:\n" + for n, [y, x] of @nodes + fs.writeSync file, " #{n} : {y: #{y}, x: #{x}, rank: #{@ranks[n]}, label: '#{@label[n]}'}\n" + fs.writeSync file, " edges:\n" + for e, [s, t] of @edges + fs.writeSync file, " #{e} : {s: #{s}, t: #{t}}\n" + fs.closeSync file class State @@ -234,7 +242,7 @@ readCSV = (file) -> while nodes[n][0] == 'remove' t.remove.nodes.push int(nodes[n++][1]) while nodes[n][0] == 'insert' - t.insert.nodes[int(nodes[n][1])] = nodes[n][2] + t.insert.nodes[int(nodes[n][1])] = [nodes[n][2]] n++ while edges[e][0] == 'remove' t.remove.edges.push int(edges[e++][1]) @@ -243,11 +251,11 @@ readCSV = (file) -> e++ yield t -state = new State readCSV '../data/27f' +state = new State readCSV '../data/q' i=0 for graph from state.iter() - if i++ == 1 + if i++ == 0 graph.write() break diff --git a/src/layoutdata.coffee b/src/layoutdata.coffee deleted file mode 100644 index 36199f7..0000000 --- a/src/layoutdata.coffee +++ /dev/null @@ -1,132 +0,0 @@ -export graph = - nodes: - 0 : [0,0] - 1 : [0,0] - 3 : [12,-12] - 5 : [12,-12] - 6 : [21,-3] - 7 : [21,-3] - 8 : [27,-5] - 9 : [26,-8] - 10 : [9,9] - 11 : [26,-6] - 12 : [23,-1] - 13 : [24,0] - 14 : [28,0] - 15 : [27,-3] - 16 : [23,-1] - 17 : [27,-1] - 18 : [26,2] - 19 : [27,3] - 20 : [17,17] - 21 : [28,2] - 22 : [26,2] - 23 : [28,4] - 24 : [23,15] - 25 : [22,12] - 26 : [1,1] - 27 : [22,14] - 28 : [19,19] - 29 : [20,20] - 30 : [24,20] - 31 : [23,17] - 32 : [19,19] - 33 : [23,19] - 34 : [22,22] - 35 : [23,23] - 36 : [7,-5] - 37 : [24,22] - 38 : [22,22] - 39 : [24,24] - 40 : [13,-7] - 41 : [12,-10] - 43 : [12,-8] - 44 : [9,-3] - 45 : [10,-2] - 46 : [14,-2] - 47 : [13,-5] - 48 : [9,-3] - 49 : [13,-3] - 50 : [12,0] - 51 : [13,1] - 53 : [14,0] - 54 : [12,0] - 55 : [14,2] - edges: - 0 : [0,1,0] - 4 : [1,26,0] - 5 : [1,3,0] - 10 : [3,5,0] - 11 : [3,5,0] - 15 : [5,6,0] - 18 : [6,7,0] - 19 : [10,6,0] - 22 : [7,16,0] - 23 : [7,9,0] - 24 : [12,8,0] - 25 : [9,8,0] - 26 : [11,8,0] - 29 : [9,11,0] - 30 : [10,20,0] - 31 : [26,10,0] - 33 : [16,11,0] - 36 : [12,13,0] - 37 : [16,12,0] - 40 : [13,22,0] - 41 : [13,15,0] - 42 : [18,14,0] - 43 : [15,14,0] - 44 : [17,14,0] - 47 : [15,17,0] - 51 : [22,17,0] - 54 : [18,19,0] - 55 : [22,18,0] - 58 : [19,23,0] - 59 : [19,21,0] - 61 : [20,32,0] - 62 : [20,25,0] - 64 : [23,21,0] - 65 : [23,21,0] - 72 : [28,24,0] - 73 : [25,24,0] - 74 : [27,24,0] - 77 : [25,27,0] - 78 : [26,36,0] - 81 : [32,27,0] - 84 : [28,29,0] - 85 : [32,28,0] - 88 : [29,38,0] - 89 : [29,31,0] - 90 : [34,30,0] - 91 : [31,30,0] - 92 : [33,30,0] - 95 : [31,33,0] - 99 : [38,33,0] - 102 : [34,35,0] - 103 : [38,34,0] - 106 : [35,39,0] - 107 : [35,37,0] - 109 : [36,48,0] - 110 : [36,41,0] - 112 : [39,37,0] - 113 : [39,37,0] - 120 : [44,40,0] - 121 : [41,40,0] - 122 : [43,40,0] - 125 : [41,43,0] - 129 : [48,43,0] - 132 : [44,45,0] - 133 : [48,44,0] - 136 : [45,54,0] - 137 : [45,47,0] - 138 : [50,46,0] - 139 : [47,46,0] - 140 : [49,46,0] - 143 : [47,49,0] - 147 : [54,49,0] - 150 : [50,51,0] - 151 : [54,50,0] - 154 : [51,55,0] - 155 : [51,53,0] - 160 : [55,53,0] - 161 : [55,53,0] diff --git a/src/main.coffee b/src/main.coffee index 14c4895..9d990a0 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -5,39 +5,40 @@ import {group} from 'basegl/display/Symbol' import {KeyboardMouseReactor} from './EventReactor' import {graph} from 'layoutdata' -G = 15 +G = 30 -addNode = (n, [y, x]) -> +addNode = (n, {y, x, rank, label}, hsl) -> r = G/6 -# name = basegl.text {str: "HELLO", scene: scene, fontFamily: 'SourceCodePro', size: 16} - node = scene.add basegl.symbol basegl.expr -> circle(r).move(r, r) +# name = basegl.tet.x {str: "HELLO", scene: scene, fontFamily: 'SourceCodePro', size: 16} + src = addLine {y: y, x: x}, {y: y+.3, x: x} + trg = addLine {y: y, x: x}, {y: y-.3, x: x} + node = scene.add basegl.symbol basegl.expr -> circle(r).move(r, r).fill(Color.hsl hsl) node.bbox.xy = [2*r, 2*r] node.position.xy = [G*x, G*y] - node.addEventListener "mouseover", (e) => alert "Node: " + n - group [node] + node.addEventListener "mouseover", (e) => alert "Node: id:#{n}, label: #{label}" -addEdge = ([ys, xs], [yt, xt], offset) -> - line1 = addLine [ys,xs], [ys,xs+offset] - line2 = addLine [yt+.3,xs+offset], [yt+.3,xt] - line3 = addLine [ys,xs+offset], [yt+.3,xs+offset] - line4 = addLine [yt+.3, xt], [yt, xt] - group [line1, line2, line3, line4] + group [node, src, trg] -addLine = ([ys, xs], [yt, xt]) -> - pi = if xs > xt then Math.PI else 0 +addEdge = (s, t, offset) -> + line1 = addLine {y: s.y-.3, x: s.x} , {y: s.y-.3, x: s.x+offset} + line2 = addLine {y: s.y-.3, x: s.x+offset}, {y: t.y+.3, x: s.x+offset} + line3 = addLine {y: t.y+.3, x: s.x+offset}, {y: t.y+.3, x: t.x} + group [line1, line2, line3] - size = Math.sqrt (Math.pow G*xs-G*xt, 2) + (Math.pow G*ys-G*yt, 2) - line = scene.add basegl.symbol basegl.expr -> rect(1.5*size, G/10) - line.bbox.xy = [1.2*size, G/10] +addLine = (s, t) -> + pi = if s.x > t.x then Math.PI else 0 - srce = scene.add basegl.symbol basegl.expr -> rect(0.8*size, G/10).fill(Color.rgb [0.8,0.3,0,.5]) - srce.bbox.xy = [.8*size, G/10] - srce.position.xy = [0.6*size, 0] + size = 2 + 2* Math.sqrt (Math.pow G*s.x-G*t.x, 2) + (Math.pow G*s.y-G*t.y, 2) + src = scene.add basegl.symbol basegl.expr -> rect(size, G/10) + src.bbox.xy = [size, G/10] - line = group [line, srce] - line.rotation.z = pi + Math.atan (yt-ys) / (xt-xs) + line = group [src] + + src.position.x = Math.ceil src.position.x - G*.035 + src.position.y = Math.ceil src.position.y - G*.035 + line.rotation.z = pi + Math.atan (t.y-s.y) / (t.x-s.x) line = group [line] - line.position.xy = [G*xs+G/6, G*ys+G/6] + line.position.xy = [G*s.x+G/6, G*s.y+G/6] line @@ -45,15 +46,26 @@ main = () -> basegl.fontManager.register 'SourceCodePro', 'fonts/SourceCodePro.ttf' await basegl.fontManager.load 'SourceCodePro' - eventReactor = new KeyboardMouseReactor scene + new KeyboardMouseReactor scene - for _, pos of graph.nodes - pos[1] += 50 - pos[0] += 60 - for _, [s, t, offset] of graph.edges - addLine graph.nodes[s], graph.nodes[t] - for n, pos of graph.nodes - addNode n, pos + nodes = {} + origin = {x: graph.nodes[0].x, y: graph.nodes[0].y} + for _, node of graph.nodes + nodes[n] = [0,0] for n, _ of graph.nodes + node.x += 30 - origin.x + node.y = 20 - origin.y - node.y + for _, {s, t} of graph.edges + if nodes[t][0] == 0 + offset = graph.nodes[t].x - graph.nodes[s].x + else if nodes[s][1] == 0 + offset = 0 + else + offset = 0 + addEdge graph.nodes[s], graph.nodes[t], offset + nodes[s][1] += 1 + nodes[t][0] += 1 + for n, node of graph.nodes + addNode n, node, graph.styles[node.label].hsl scene = basegl.scene @@ -61,4 +73,6 @@ scene = basegl.scene width: 2048 height: 2048 +new KeyboardMouseReactor scene +console.log Color.hsl([1,1,.5]).toRGB() main() \ No newline at end of file