root/branch/joao/umitMapper/RadialNet.py @ 622

Revision 622, 21.8 kB (checked in by ignotus21, 6 years ago)

Start of animation of graph.

Line 
1# Copyright (C) 2007 Adriano Monteiro Marques <py.adriano@gmail.com>
2#
3# Author: Joao Paulo de Souza Medeiros <ignotus21@gmail.com>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19import gtk
20import math
21import time
22import pango
23import drawing
24import geometry
25import gobject
26from gtk import gdk
27from Coordinate import PolarCoordinate
28from Interpolation import Linear2DInterpolator
29from Graph import Graph, Node
30
31
32class RadialNet(gtk.DrawingArea):
33    """
34    Radial network visualization widget
35    """
36    def __init__(self, numberOfRings=3):
37        """
38        Constructor method of RadialNet widget class
39        @type  numberOfRings: number
40        @param numberOfRings: Number of rings in radial layout
41        """
42        self.__numberOfRings = numberOfRings
43        """Number of rings in radial layout"""
44        self.__ringGap = 50
45        """Gap between rings"""
46        self.__graph = None
47        """Graph displayed in radial layout"""
48        self.__centerOfWidget = (0, 0)
49        """Store the center of widget"""
50        self.__isInAnimation = False
51        """"""
52        self.exposedNodes = set([])
53        """"""
54        self.__animationRate = 10
55        """"""
56
57        super(RadialNet, self).__init__()
58        self.connect("expose_event", self.expose)
59        self.connect("button_press_event", self.button_press)
60        self.connect("button_release_event", self.button_release)
61        self.connect("motion_notify_event", self.motion_notify)
62        self.connect("enter_notify_event", self.enter_notify)
63        self.connect("leave_notify_event", self.leave_notify)
64
65        self.add_events(gdk.BUTTON_PRESS_MASK |
66                        gdk.BUTTON_RELEASE_MASK |
67                        gdk.ENTER_NOTIFY |
68                        gdk.LEAVE_NOTIFY |
69                        gdk.POINTER_MOTION_MASK)
70
71
72    def notIsInAnimation(function):
73        """
74        """
75        def checkAnimationStatus(*args):
76            if args[0].__isInAnimation == True:
77                return True
78            return function(*args)
79
80        return checkAnimationStatus
81
82
83    def __getNodeByCoordinate(self, point):
84        """
85        """
86        xc, yc = self.__centerOfWidget
87
88        for id in range(self.__graph.getNumberOfNodes()):
89
90            node = self.__graph.getNodeByID(id)
91
92            xn, yn = node.getCartesianCoordinate()
93            center = (xc + xn, yc - yn)
94            radius = node.getDrawInfo('radius')
95
96            if ( geometry.pointIsInCircle(point, radius, center) == True ):
97                return node, center
98
99        return None
100
101
102    @notIsInAnimation
103    def enter_notify(self, widget, event):
104        """
105        """
106
107
108    @notIsInAnimation
109    def leave_notify(self, widget, event):
110        """
111        """
112        for id in range(self.__graph.getNumberOfNodes()):
113            node = self.__graph.getNodeByID(id)
114            node.setDrawInfo('over', False)
115
116        self.queue_draw()
117       
118
119    @notIsInAnimation
120    def button_press(self, widget, event):
121        """
122        Drawing callback
123        @type  widget: GtkWidget
124        @param widget: Gtk widget superclass
125        @type  event: GtkEvent
126        @param event: Gtk event of widget
127        @rtype: boolean
128        @return: Indicator of the event propagation
129        """
130        event.window.set_cursor(gdk.Cursor(gtk.gdk.X_CURSOR))
131
132        if event.button == 1:
133            result = self.__getNodeByCoordinate(self.get_pointer())
134
135            if ( result != None ):
136
137                node, point = result
138                self.anim(node.getID())
139
140        if event.button == 3:
141
142            result = self.__getNodeByCoordinate(self.get_pointer())
143
144            if ( result != None ):
145
146                xw, yw = self.window.get_origin()
147                node, point = result
148                x, y = point
149
150                if node.getID() not in self.exposedNodes:
151
152                    nodeView = NodeView(node, (int(xw + x), int(yw + y)), self)
153                    nodeView.show_all()
154                    self.exposedNodes.add(node.getID())
155       
156        return True
157
158
159    @notIsInAnimation
160    def button_release(self, widget, event):
161        """
162        Drawing callback
163        @type  widget: GtkWidget
164        @param widget: Gtk widget superclass
165        @type  event: GtkEvent
166        @param event: Gtk event of widget
167        @rtype: boolean
168        @return: Indicator of the event propagation
169        """
170        event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
171
172        return True
173
174
175    @notIsInAnimation
176    def motion_notify(self, widget, event):
177        """
178        Drawing callback
179        @type  widget: GtkWidget
180        @param widget: Gtk widget superclass
181        @type  event: GtkEvent
182        @param event: Gtk event of widget
183        @rtype: boolean
184        @return: Indicator of the event propagation
185        """
186        xc, yc = self.__centerOfWidget
187        pointer = self.get_pointer()
188
189        for id in range(self.__graph.getNumberOfNodes()):
190            node = self.__graph.getNodeByID(id)
191            node.setDrawInfo('over', False)
192
193        result = self.__getNodeByCoordinate(self.get_pointer())
194
195        if result != None:
196            result[0].setDrawInfo('over', True)
197
198        self.queue_draw()
199       
200        return True
201
202
203    def expose(self, widget, event):
204        """
205        Drawing callback
206        @type  widget: GtkWidget
207        @param widget: Gtk widget superclass
208        @type  event: GtkEvent
209        @param event: Gtk event of widget
210        @rtype: boolean
211        @return: Indicator of the event propagation
212        """
213        self.context = widget.window.cairo_create()
214
215        self.context.rectangle(*event.area)
216        self.context.set_source_rgb(1.0, 1.0, 1.0)
217        self.context.fill_preserve()
218        self.context.clip()
219
220        self.__draw()
221
222        return True
223
224
225    def __draw(self):
226        """
227        Drawing method
228        """
229        # Getting allocation reference
230        allocation = self.get_allocation()
231        self.__centerOfWidget = (allocation.x + allocation.width / 2,
232                                 allocation.y + allocation.height / 2)
233
234        (xc, yc) = self.__centerOfWidget
235
236        # Drawing network rings
237        for i in range(self.__numberOfRings,0,-1):
238
239            self.context.arc(xc, yc, self.__ringGap * (i + 1), 0, 2 * math.pi)
240            self.context.set_source_rgb(0.2, 0.2, 0.2)
241            self.context.set_dash([4, 6])
242            self.context.set_line_width(0.2)
243            self.context.stroke()
244
245        # Drawing nodes and your connections
246
247        connectionsList = set()
248
249        for i in range(self.__graph.getNumberOfNodes()):
250
251            node = self.__graph.getNodeByID(i)
252
253            for j in self.__graph.getNodeConnections(node):
254
255                if (i, j) not in connectionsList:
256                    self.__drawConnection(node, self.__graph.getNodeByID(j))
257                    connectionsList.add((i, j))
258                    connectionsList.add((j, i))
259
260        for i in range(self.__graph.getNumberOfNodes()):
261
262            self.__drawNode(self.__graph.getNodeByID(i))
263
264
265    def __drawConnection(self, nodeA, nodeB):
266        """
267        Draw the connection between two nodes
268        @type  : NetNode
269        @param : The first node that will be connected
270        @type  : NetNode
271        @param : The second node that will be connected
272        """
273        # Draw connections
274        self.context.save()
275        (xA, yA) = nodeA.getCartesianCoordinate()
276        (xB, yB) = nodeB.getCartesianCoordinate()
277
278        (xc, yc) = self.__centerOfWidget
279
280        self.context.set_source_rgb(0.5, 0.5, 1.0)
281        self.context.set_dash([1,0])
282        self.context.move_to(xc + xA, yc - yA)
283        self.context.line_to(xc + xB, yc - yB)
284        self.context.set_line_width(0.5)
285        self.context.stroke()
286        self.context.restore()
287
288
289    def __drawNode(self, node):
290        """
291        Draw nodes and your informations
292        @type  : NetNode
293        @param : The node will be draw
294        """
295        x, y = node.getCartesianCoordinate()
296        r, g, b = node.getDrawInfo('color')
297        xc, yc = self.__centerOfWidget
298
299        radius = node.getDrawInfo('radius')
300
301        if node.getDrawInfo('over') == True:
302            self.context.arc(xc + x, yc - y, radius + 2, 0, 2 * math.pi)
303            self.context.set_source_rgba(0.0, 0.0, 1.0, 0.3)
304            self.context.fill_preserve()
305            self.context.stroke()
306
307        self.context.arc(xc + x, yc - y, radius, 0, 2 * math.pi)
308        self.context.set_source_rgb(r, g, b)
309        self.context.fill_preserve()
310        self.context.set_source_rgb(0.0, 0.0, 0.0)
311        self.context.set_font_size(9)
312        self.context.show_text(node.getInformation('ip'))
313        self.context.set_dash([1,0])
314        self.context.set_line_width(0.5)
315        self.context.stroke()
316
317
318    def __calculateNodePositions(self):
319        """
320        Make some operations to determine the initial node positions
321        """
322        newNodes = [self.__graph.getMainNodeID()]
323        oldNodes = []
324
325        angleRange = range(self.__graph.getNumberOfNodes())
326        angleRange[self.__graph.getMainNodeID()] = (0,360)
327
328        self.__graph.getMainNode().setPolarCoordinate(0, 0)
329
330        for i in range(self.__numberOfRings):
331
332            locNodes = []
333
334            for nodeID in newNodes:
335
336                oldNodes.append(nodeID)
337
338                node = self.__graph.getNodeByID(nodeID)
339                connections = self.__graph.getNodeConnections(node, oldNodes)
340                (min, max) = angleRange[node.getID()]
341                alpha = 0
342
343                radius = self.__ringGap * (i + 2)
344
345                for j in connections:
346
347                    child = self.__graph.getNodeByID(j)
348                    alpha += 1
349
350                    factor = (max - min) / len(connections)
351
352                    if factor > 180:
353                        factor = 180
354
355                    a = min + (alpha - 1) * factor
356                    b = min + alpha * factor
357                    angleRange[j] = (a, b)
358
359                    theta = ((b - a) / 2) + a
360                    child.setPolarCoordinate(radius, theta)
361
362                    locNodes.append(j)
363
364            for j in locNodes:
365                newNodes.append(j)
366
367            for j in oldNodes:
368                if j in newNodes:
369                    newNodes.remove(j)
370
371            if len(newNodes) == 0: break
372
373
374    def anim(self, node_id):
375        """
376        """
377        self.__isInAnimation = True # Lock main draw functions and events
378
379        initialCoordinates = range(self.__graph.getNumberOfNodes())
380        finalCoordinates = range(self.__graph.getNumberOfNodes())
381        interpolatedCoordinates = range(self.__graph.getNumberOfNodes())
382
383        for id in range(self.__graph.getNumberOfNodes()):
384
385            node = self.__graph.getNodeByID(id)
386            initialCoordinates[id] = node.getPolarCoordinate()
387
388        old_main_node_id = self.__graph.getMainNodeID()
389
390        self.__graph.setMainNodeByID(node_id)
391        self.__calculateNodePositions()
392
393        for id in range(self.__graph.getNumberOfNodes()):
394
395            node = self.__graph.getNodeByID(id)
396            finalCoordinates[id] = node.getPolarCoordinate()
397
398            ri, ti = initialCoordinates[id]
399            rf, tf = finalCoordinates[id]
400
401            interpolator = Linear2DInterpolator()
402            interpolator.setInitialPoint(ri, ti)
403            interpolator.setFinalPoint(rf, tf)
404            points = interpolator.getInterpolatedPoints(self.__animationRate)
405            interpolatedCoordinates[id] = points
406
407        self.__isInAnimation = False # Unlock main draw functions and events
408
409        for i in range(self.__animationRate):
410
411            for id in range(self.__graph.getNumberOfNodes()):
412
413                r, t = interpolatedCoordinates[id][i]
414                node = self.__graph.getNodeByID(id)
415                node.setPolarCoordinate(r, t)
416
417            self.queue_draw()
418
419
420
421    def setGraph(self, graph):
422        """
423        Set graph to be displayed in layout
424        @type  : Graph
425        @param : Set the graph used in visualization
426        """
427        self.__graph = graph
428        self.__calculateNodePositions()
429
430
431
432class NodeView(gtk.Window):
433    """
434    """
435
436    __COMPACTED = 1
437    __COLLAPSED = 0
438
439    def __init__(self, node, position, parent):
440        """
441        """
442        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
443        self.move(position[0], position[1])
444        self.__button_press_position = self.get_pointer()
445        self.resize(240, 120)
446
447        self.__node = node
448        self.__pressed = False
449        self.__parent = parent
450
451        self.connect("button_press_event", self.button_press)
452        self.connect("button_release_event", self.button_release)
453        self.connect("enter_notify_event", self.enter_notify)
454        self.connect("leave_notify_event", self.leave_notify)
455        self.connect("motion_notify_event", self.motion_notify)
456
457        self.add_events(gdk.BUTTON_PRESS_MASK |
458                        gdk.BUTTON_RELEASE_MASK |
459                        gdk.POINTER_MOTION_MASK |
460                        gdk.ENTER_NOTIFY |
461                        gdk.LEAVE_NOTIFY |
462                        gdk.POINTER_MOTION_HINT_MASK)
463
464        self.__draw_compact_layout()
465
466
467    def __draw_compact_layout(self):
468        """
469        """
470        self.__content = gtk.VBox()
471        self.__content.set_spacing(2)
472        self.__content.set_border_width(3)
473        self.__body = gtk.VBox()
474        self.__head = gtk.HBox()
475        self.__head.set_spacing(2)
476
477        bold_font = pango.FontDescription('bold')
478        normal_font = pango.FontDescription('monospace 8')
479
480        color_event = gtk.EventBox()
481        color = gtk.Image()
482        color.set_from_file('image/border.png')
483        color_event.add(color)
484        color_event.set_size_request(15, 15)
485        r, g, b = drawing.cairoToGdkColor(self.__node.getDrawInfo('color'))
486        color_event.modify_bg(gtk.STATE_NORMAL, gdk.Color(r, g, b))
487
488        title_event = gtk.EventBox()
489        title = gtk.Label()
490        title.set_markup(self.__node.getInformation('ip'))
491        title.modify_font(bold_font)
492
493        close_event = gtk.EventBox()
494        close = gtk.Image()
495        close.set_from_file('image/close.png')
496        close_event.add(close)
497        close_event.connect('button_press_event', self.close_window)
498        close_event.add_events(gdk.BUTTON_PRESS_MASK)
499
500        expand_event = gtk.EventBox()
501        expand = gtk.Image()
502        expand.set_from_file('image/expand.png')
503        expand_event.add(expand)
504        expand_event.connect('button_press_event', self.expand_window)
505        expand_event.add_events(gdk.BUTTON_PRESS_MASK)
506
507        collapse_event = gtk.EventBox()
508        collapse = gtk.Image()
509        collapse.set_from_file('image/collapse.png')
510        collapse_event.add(collapse)
511        collapse_event.connect('button_press_event', self.collapse_window)
512        collapse_event.add_events(gdk.BUTTON_PRESS_MASK)
513
514        text_scroll = gtk.ScrolledWindow()
515        text_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
516        text = gtk.TextView()
517        text_scroll.add(text)
518
519        self.__head.pack_start(color_event, False, False, 0)
520        self.__head.pack_start(title, False, False, 0)
521        self.__head.pack_end(close_event, False, False, 0)
522        self.__head.pack_end(expand_event, False, False, 0)
523        self.__head.pack_end(collapse_event, False, False, 0)
524        self.__body.pack_start(text_scroll, True, True, 0)
525
526        self.__content.pack_start(self.__head, False, False, 0)
527        self.__content.pack_start(self.__body, True, True, 0)
528
529        self.add(self.__content)
530
531
532    def close_window(self, widget, event):
533        """
534        """
535        self.__node.setDrawInfo('over', False)
536        self.destroy()
537        self.__parent.exposedNodes.remove(self.__node.getID())
538        self.__parent.queue_draw()
539
540        return True
541
542
543    def expand_window(self, widget, event):
544        """
545        """
546        self.present()
547        self.__body.show()
548
549        if self.__COMPACTED == 1:
550            self.resize(480, 240)
551            self.__COMPACTED = 0
552        else:
553            self.resize(240, 120)
554            self.__COMPACTED = 1
555
556        return True
557
558
559    def collapse_window(self, widget, event):
560        """
561        """
562        self.present()
563
564        if self.__COLLAPSED == 0:
565            self.resize(240, 20)
566            self.__body.hide()
567            self.__COLLAPSED = 1
568        else:
569            if self.__COMPACTED == 0:
570                self.resize(480, 240)
571            else:
572                self.resize(240, 120)
573            self.__body.show()
574            self.__COLLAPSED = 0
575
576        return True
577
578
579    def button_press(self, widget, event):
580        """
581        """
582        self.present()
583        self.__pressed = True
584        self.__button_press_position = self.get_pointer()
585
586        return True
587
588
589    def button_release(self, widget, event):
590        """
591        """
592        self.__pressed = False
593
594        return True
595
596
597    def enter_notify(self, widget, event):
598        """
599        """
600        self.__node.setDrawInfo('over', True)
601        self.__parent.queue_draw()
602
603
604    def leave_notify(self, widget, event):
605        """
606        """
607        self.__node.setDrawInfo('over', False)
608
609
610    def motion_notify(self, widget, event):
611        """
612        """
613        self.__node.setDrawInfo('over', True)
614
615        x, y, button_state = event.window.get_pointer()
616
617        if button_state & gtk.gdk.BUTTON1_MASK and self.__pressed:
618
619            xw, yw = event.window.get_root_origin()
620            xd, yd = self.__button_press_position
621            self.move(x + xw - xd, y + yw - yd)
622
623        return True
624
625
626
627class NetNode(Node):
628    """
629    Node class for radial network widget
630    """
631    def __init__(self, id=Node):
632        """
633        """
634        self.__drawInfo = None 
635        """Hash with draw information"""
636        self.__coordinate = PolarCoordinate()
637
638        super(NetNode, self).__init__(id)
639
640
641    def setPolarCoordinate(self, r, t):
642        """
643        Set polar coordinate
644        @type  r: number
645        @param r: The radius of coordinate
646        @type  t: number
647        @param t: The angle (theta) of coordinate in radians
648        """
649        self.__coordinate.setCoordinate(r, t)
650
651
652    def getPolarCoordinate(self):
653        """
654        Get cartesian coordinate
655        @rtype: tuple
656        @return: Cartesian coordinates (x, y)
657        """
658        return self.__coordinate.getCoordinate()
659
660
661    def getCartesianCoordinate(self):
662        """
663        Get cartesian coordinate
664        @rtype: tuple
665        @return: Cartesian coordinates (x, y)
666        """
667        return self.__coordinate.toCartesian()
668
669
670    def getDrawInfo(self, info):
671        """
672        Get draw information about node
673        @type  : string
674        @param : Information name
675        @rtype: mixed
676        @return: The requested information
677        """
678        try:
679            return self.__drawInfo[ info ]
680        except:
681            return None
682
683
684    def setDrawInfo(self, info, value=None):
685        """
686        Set draw information
687        @type  : dict
688        @param : Draw information dictionary
689        """
690        if value == None:
691            self.__drawInfo = info
692        else:
693            self.__drawInfo[info] = value
694
695
696
697
698if __name__ == "__main__":
699
700    # Testing application
701
702    window = gtk.Window()
703    radialnet = RadialNet(5)
704
705    # Creating nodes
706    nodes = range(10)
707
708    nodes[0] = NetNode(0)
709    nodes[0].setDrawInfo({"color":(0,1,0), "radius":10})
710    nodes[0].setInformation({"ip":"192.168.1.1", "host":"joao.test.net"})
711
712    nodes[1] = NetNode(1)
713    nodes[1].setDrawInfo({"color":(1,1,0), "radius":8})
714    nodes[1].setInformation({"ip":"192.168.1.2", "host":"luis.test.net"})
715
716    nodes[2] = NetNode(2)
717    nodes[2].setDrawInfo({"color":(1,0,0), "radius":6})
718    nodes[2].setInformation({"ip":"192.168.1.3", "host":"polo.test.net"})
719
720    nodes[3] = NetNode(3)
721    nodes[3].setDrawInfo({"color":(0,1,0), "radius":10})
722    nodes[3].setInformation({"ip":"192.168.2.5", "host":"mara.test.net"})
723
724    nodes[4] = NetNode(4)
725    nodes[4].setDrawInfo({"color":(1,0,0), "radius":12})
726    nodes[4].setInformation({"ip":"192.168.3.2", "host":"nara.test.net"})
727
728    nodes[5] = NetNode(5)
729    nodes[5].setDrawInfo({"color":(0,1,0), "radius":8})
730    nodes[5].setInformation({"ip":"192.168.3.3", "host":"luca.test.net"})
731
732    nodes[6] = NetNode(6)
733    nodes[6].setDrawInfo({"color":(1,1,0), "radius":8})
734    nodes[6].setInformation({"ip":"192.168.3.1", "host":"jose.test.net"})
735
736    nodes[7] = NetNode(7)
737    nodes[7].setDrawInfo({"color":(1,1,0), "radius":10})
738    nodes[7].setInformation({"ip":"192.168.1.8", "host":"mark.test.net"})
739
740    nodes[8] = NetNode(8)
741    nodes[8].setDrawInfo({"color":(1,1,0), "radius":8})
742    nodes[8].setInformation({"ip":"192.168.1.7", "host":"mama.test.net"})
743
744    nodes[9] = NetNode(9)
745    nodes[9].setDrawInfo({"color":(1,0,0), "radius":6})
746    nodes[9].setInformation({"ip":"192.168.4.7", "host":"papa.test.net"})
747
748    graph = Graph(nodes)
749    graph.setMainNodeByID(0)
750    graph.setConnections(0, 1)
751    graph.setConnections(0, 2)
752    graph.setConnections(0, 7)
753    graph.setConnections(0, 8)
754    graph.setConnections(1, 3)
755    graph.setConnections(2, 4)
756    graph.setConnections(2, 5)
757    graph.setConnections(2, 6)
758    graph.setConnections(5, 9)
759
760    radialnet.setGraph(graph)
761    radialnet.set_size_request(640, 480)
762
763    window.add(radialnet)
764    window.connect("destroy", gtk.main_quit)
765    window.show_all()
766
767    gtk.main()
768
Note: See TracBrowser for help on using the browser.