00001
00002
00003
00004
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
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
00046 self.max = 20
00047
00048
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
00084
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
00120
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
00157
00158
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
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
00185 self.add_node(word, root=True, style="root")
00186
00187
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
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
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
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
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