graph_example4.py
Go to the documentation of this file.
00001 # A graph that browses through the WordNet dictionary.
00002 # You can click on nodes (including "has-parts" and "has-specific").
00003 # You'll need the NodeBox Linguistics library installed.
00004 # Author: Tom De Smedt.
00005 
00006 query = "bird"
00007 
00008 try:
00009     graph = ximport("graph")
00010 except ImportError:
00011     graph = ximport("__init__")
00012     reload(graph)
00013 
00014 import en
00015 from random import shuffle
00016 
00017 #### WORDNET GRAPH ###################################################################################
00018 
00019 class wordnetgraph(graph.graph):
00020     
00021     """ Browse WordNet in a graph.
00022     
00023     The wordnetgraph class is based on the standard graph class.
00024     I've added some functionality to fetch data from WordNet and browse through it.
00025     When you click on a node, the wordnetgraph.click() method is fired.
00026     This will check if the clicked node is a noun in WordNet, and if so,
00027     reload the graph's nodes and connections with that noun at the root.
00028     
00029     The main methods, get_senses() and get_relations(),
00030     are called when the graph reloads.
00031     They retrieve data from WordNet and format it as nodes.
00032     The expand() method is called when you click on "has-specific" or "has-parts".
00033     
00034     A helper class, senses, draws the interactive word sense selection buttons.
00035     
00036     """
00037     
00038     def __init__(self, iterations=2000, distance=1.3):
00039         
00040         graph.graph.__init__(self, iterations, distance)
00041         self.styles = graph.create().styles
00042         self.events.click = self.click
00043         self.events.popup = True
00044         
00045         # Display a maximum of 20 nodes.
00046         self.max = 20
00047         
00048         # A row of buttons to select the current word sense.
00049         self.senses = senses(self, 20, 20)
00050     
00051     def is_expandable(self, id):
00052         
00053         """ Some of the branches are expandable:
00054         if you click on has-parts or has-specific, a detailed view will load.
00055         """
00056         
00057         if id in ["has-parts", "has-specific"]: 
00058             return True
00059         else:
00060             return False
00061     
00062     def is_clickable(self, node):
00063         
00064         """ Every node that is a noun is clickable (except the root).
00065         """
00066         
00067         if en.is_noun(str(node.id.lower())) \
00068         or self.is_expandable(node.id) and node != self.root: 
00069             return True
00070         else:
00071             return False
00072     
00073     def get_senses(self, word, top=6):
00074  
00075         """ The graph displays the different senses of a noun,
00076         e.g. light -> lighter, luminosity, sparkle, ...
00077         """
00078         
00079         word = str(word)
00080         
00081         if self.is_expandable(word): return []
00082         
00083         # If there are 4 word senses and each of it a list of words,
00084         # take the first word from each list, then take the second etc.
00085         words = []
00086         for i in range(2):
00087             for sense in en.noun.senses(word):
00088                 if len(sense) > i \
00089                 and sense[i] != word \
00090                 and sense[i] not in words: 
00091                     words.append(sense[i])
00092                     
00093         return words[:top]
00094     
00095     def get_relations(self, word, previous=None):
00096         
00097         """ The graph displays semantic relations for a noun,
00098         e.g. light -> has-specific -> candlelight.
00099         """
00100         
00101         word = str(word)
00102         
00103         if self.is_expandable(word): 
00104             return self.expand(word, previous)
00105 
00106         words = []
00107         lexname = en.noun.lexname(word)
00108         if lexname != "":
00109             words.append((lexname, "category "))
00110         
00111         relations = [
00112             (6, en.noun.holonym  , "has-parts"),
00113             (2, en.noun.meronym  , "is-part-of"),
00114             (2, en.noun.antonym  , "is-opposite-of"),
00115             (3, en.noun.hypernym , "is-a"),
00116             (2, en.verb.senses   , "is-action"),
00117             (6, en.noun.hyponym  , "has-specific"),
00118         ]
00119         # Get related words from WordNet.
00120         # Exclude long words and take the top of the list.
00121         for top, f, relation in relations:
00122             r = []
00123             try: rng = f(word, sense=self.senses.current)
00124             except:
00125                 try: rng = f(word)
00126                 except:
00127                     continue
00128             for w in rng:
00129                 if  w[0] != word \
00130                 and w[0] not in r \
00131                 and len(w[0]) < 20:
00132                     r.append((w[0], relation))
00133                     
00134             words.extend(r[:top])
00135             
00136         return words
00137     
00138     def expand(self, relation, previous=None):
00139         
00140         """ Zoom in to the hyponym or holonym branch.
00141         """
00142         
00143         if relation == "has-specific" : f = en.noun.hyponym
00144         if relation == "has-parts"    : f = en.noun.holonym
00145         
00146         root = str(self.root.id.lower())
00147         unique = []
00148         if previous: previous = str(previous)
00149         for w in f(previous, sense=self.senses.current):
00150             if w[0] not in unique: unique.append(w[0])
00151         shuffle(unique)
00152         
00153         words = []
00154         i = 0
00155         for w in unique:
00156             # Organise connected nodes in branches of 4 nodes each.
00157             # Nodes that have the root id in their own id,
00158             # form a branch on their own.
00159             label = " "
00160             if w.find(root) < 0:
00161                 label = (i+4)/4*"  "
00162                 i += 1
00163             words.append((w, label))
00164             
00165         return words
00166     
00167     def click(self, node):
00168         
00169         """ If the node is indeed clickable, load it.
00170         """
00171         
00172         if self.is_clickable(node):
00173             p = self.root.id
00174             # Use the previous back node instead of "has specific".
00175             if self.is_expandable(p): p = self.nodes[-1].id
00176             self.load(node.id, previous=p)
00177     
00178     def load(self, word, previous=None):
00179         
00180         self.clear()
00181         
00182         word = str(word)
00183         
00184         # Add the root (the clicked node) with the ROOT style.
00185         self.add_node(word, root=True, style="root")
00186         
00187         # Add the word senses to the root in the LIGHT style.
00188         for w in self.get_senses(word):
00189             self.add_node(w, style=self.styles.light.name)
00190             self.add_edge(word, w, 0.5)
00191             if len(self) > self.max: break
00192 
00193         # Add relation branches to the root in the DARK style.
00194         for w, r in self.get_relations(word, previous):
00195             self.add_node(r, style="dark")
00196             self.add_edge(w, r, 1.0)
00197             self.add_edge(word, r)
00198             if len(self) > self.max: break    
00199 
00200         # Provide a back link to the previous word.
00201         if previous and previous != self.root.id:
00202             n = self.add_node(previous, 10)
00203             if len(n.links) == 0: self.add_edge(word, n.id)
00204             n.style = "back"
00205         
00206         # Indicate the word corresponding to the current sense.
00207         if self.senses.count() > 0:
00208             for w in en.noun.senses(word)[self.senses.current]:
00209                 n = self.node(w)
00210                 if n and n != self.root: 
00211                     n.style = "marked"
00212 
00213     def draw(self, *args, **kwargs):
00214         
00215         """ Additional drawing for sense selection buttons.
00216         """
00217         
00218         graph.graph.draw(self, *args, **kwargs)
00219         self.senses.draw()
00220 
00221 ### WORD SENSE SELECTION #############################################################################
00222 
00223 class senses:
00224     
00225     """ A row of word sense selection buttons.
00226     """
00227     
00228     def __init__(self, graph, x, y):
00229         
00230         self.graph = graph
00231         self.word = ""
00232         self.x = x
00233         self.y = y
00234         
00235         self.current = 0
00236         self.pressed = None
00237     
00238     def count(self):
00239         
00240         """ The number of senses for the current word.
00241         The current word is synched to the graph's root node.
00242         """
00243         
00244         if self.word != self.graph.root.id:
00245             self.word = str(self.graph.root.id)
00246             self.current = 0
00247             self._count = 0
00248             try: self._count = len(en.noun.senses(self.word))
00249             except:
00250                 pass
00251                 
00252         return self._count
00253     
00254     def draw(self):
00255         
00256         s = self.graph.styles.default
00257         x, y, f = self.x, self.y, s.fontsize
00258         
00259         _ctx.reset()
00260         _ctx.nostroke()
00261         _ctx.fontsize(f)
00262         
00263         for i in range(self.count()):
00264             
00265             clr = s.fill
00266             if i == self.current:
00267                 clr = self.graph.styles.default.background
00268             _ctx.fill(clr)
00269             p = _ctx.rect(x, y, f*2, f*2)
00270             _ctx.fill(s.text)
00271             _ctx.align(CENTER)
00272             _ctx.text(str(i+1), x-f, y+f*1.5, width=f*4)
00273             x += f * 2.2
00274             
00275             self.log_pressed(p, i)
00276             self.log_clicked(p, i)
00277     
00278     def log_pressed(self, path, i):
00279         
00280         """ Update senses.pressed to the last button pressed.
00281         """
00282         
00283         if mousedown \
00284         and self.graph.events.dragged == None \
00285         and path.contains(MOUSEX, MOUSEY):
00286             self.pressed = i
00287 
00288     def log_clicked(self, path, i):
00289         
00290         """ Update senses.current to the last button clicked.
00291         """
00292         
00293         if not mousedown and self.pressed == i:
00294             self.pressed = None
00295             if path.contains(MOUSEX, MOUSEY):
00296                 self.current = i
00297                 self.graph.load(self.graph.root.id)      
00298         
00299 ######################################################################################################
00300 
00301 g = wordnetgraph(distance=1.2)
00302 g.load(query)
00303 
00304 size(550, 550)
00305 speed(30) 
00306 def draw():
00307     g.styles.textwidth = 120
00308     g.draw(
00309         directed=True, 
00310         weighted=True,
00311         traffic=True
00312     )
00313     


rcommander
Author(s): Hai Nguyen (haidai@gmail.com)
autogenerated on Thu Nov 28 2013 11:46:34