# autolayoutforcefast.py - Fast Diagram Layout Plugin for Dia # # Diagram Automatic Layout Plugin for Dia. # # Copyright (c) 2008 Frederic-Gerald Morcos # Copyright (c) 2008 Hans Breuer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import dia, gtk, math class LayoutUI(gtk.Window): def __init__(self): gtk.Window.__init__(self) self.set_border_width(5) self.set_skip_pager_hint(True) self.set_skip_taskbar_hint(True) # FIXME - self.set_transient_for() on Dia's main window box = gtk.VBox(False, 5) connBox = gtk.HBox(False, 5) connLabel = gtk.Label('Connection Length:') self.connSpin = gtk.SpinButton(gtk.Adjustment(1, 1, 500, 1, 10)) connBox.pack_start(connLabel, False, False, 2) connBox.pack_start(self.connSpin, True, True, 2) buttBox = gtk.HButtonBox() buttBox.set_spacing(5) buttBox.set_layout(gtk.BUTTONBOX_END) self.buttApply = gtk.Button(None, gtk.STOCK_APPLY) buttClose = gtk.Button(None, gtk.STOCK_CLOSE) buttClose.connect('clicked', self.buttClose_clicked, None) buttBox.pack_start(self.buttApply, False, False, 2) buttBox.pack_start(buttClose, False, False, 2) box.pack_start(connBox, False, False, 2) box.pack_start(gtk.HSeparator(), False, False, 2) box.pack_start(buttBox, False, False, 2) self.add(box) self.show_all() def buttClose_clicked(self, widget, data): self.destroy() def fast_layout_force_cb (data, flags): nodes = [] connections = [] for i in data.selected: if isConnection(i): connections.append(i) else: nodes.append(i) win = LayoutUI() win.buttApply.connect( 'clicked', buttApply_clicked, nodes, connections, win.connSpin, data) def fast_layout_force(nodes, connections, connLen, rconst, aconst, timestep, damping): energy = [0.0, 0.0] i = 0 netforces = [] while i < len(nodes): netforces.append([0.0, 0.0]) i += 1 for i, n in enumerate(nodes): for j, nn in enumerate(nodes[i + 1 : len(nodes)]): res = repulsion (n, nn, rconst) netforces[i][0] += res[0] netforces[i][1] += res[1] netforces[j][0] -= res[0] netforces[j][1] -= res[1] # for c in connections: # attraction (c, connLen, aconst, energy) for i, n in enumerate(nodes): update(timestep, damping, n, netforces[i], energy) return energy def repulsion(node1, node2, const): pos1 = node1.properties['obj_pos'].value pos2 = node2.properties['obj_pos'].value dx = pos1.x - pos2.x dy = pos1.y - pos2.y numer = area(node1) * area(node2) * const denom = math.pow(dx * dx + dy * dy, 1.5) try: resx = numer * dx / denom resy = numer * dy / denom except ZeroDivisionError: print 'Repulsion: Zero division error.' return 0, 0 return resx, resy def update(timestep, damping, object, netforce, energy): velocity = [0.0, 0.0] velocity[0] = timestep * netforce[0] * damping velocity[1] = timestep * netforce[1] * damping p = object.properties['obj_pos'].value px = p.x + timestep * velocity[0] py = p.y + timestep * velocity[1] object.move(px, py) mass = area(object) energy[0] += mass * math.pow(velocity[0], 2) / 2 energy[1] += mass * math.pow(velocity[1], 2) / 2 def area(object): rect = object.bounding_box return (rect.bottom - rect.top) * (rect.right - rect.left) def buttApply_clicked(widget, nodes, connections, connSpin, data): connLen = connSpin.get_value_as_int() diagram = dia.active_display().diagram loops = 0 energy = fast_layout_force( nodes, connections, connLen, 2.0, 3.0, 2e-1, 5e-1) while (energy[0] > 1 or energy[1] > 1) and loops < 100: energy = fast_layout_force( nodes, connections, connLen, 2.0, 3.0, 2e-1, 5e-1) loops += 1 for n in nodes: diagram.update_connections(n) data.active_layer.update_extents() diagram.update_extents() diagram.flush() print loops, 'iterations.' def isConnection (o): for connPoint in o.connections: if len (connPoint.connected) > 0: return False return True dia.register_action ( 'FastLayoutForcePy', 'Fast Layout (force)', '/DisplayMenu/Test/TestExtensionStart', fast_layout_force_cb)