xdot.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Copyright 2008 Jose Fonseca
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #
18 
19 '''Visualize dot graphs via the xdot format.'''
20 
21 __author__ = "Jose Fonseca"
22 
23 __version__ = "0.4"
24 
25 
26 import os
27 import sys
28 import subprocess
29 import math
30 import colorsys
31 import time
32 import re
33 
34 import gobject
35 import gtk
36 import gtk.gdk
37 import gtk.keysyms
38 import cairo
39 import pango
40 import pangocairo
41 
42 
43 # See http://www.graphviz.org/pub/scm/graphviz-cairo/plugin/cairo/gvrender_cairo.c
44 
45 # For pygtk inspiration and guidance see:
46 # - http://mirageiv.berlios.de/
47 # - http://comix.sourceforge.net/
48 
49 
50 class Pen:
51  """Store pen attributes."""
52 
53  def __init__(self):
54  # set default attributes
55  self.color = (0.0, 0.0, 0.0, 1.0)
56  self.fillcolor = (0.0, 0.0, 0.0, 1.0)
57  self.linewidth = 1.0
58  self.fontsize = 14.0
59  self.fontname = "Times-Roman"
60  self.dash = ()
61 
62  def copy(self):
63  """Create a copy of this pen."""
64  pen = Pen()
65  pen.__dict__ = self.__dict__.copy()
66  return pen
67 
68  def highlighted(self):
69  pen = self.copy()
70  pen.color = (1, 0, 0, 1)
71  pen.fillcolor = (1, .8, .8, 1)
72  return pen
73 
74 
75 class Shape:
76  """Abstract base class for all the drawing shapes."""
77 
78  def __init__(self):
79  pass
80 
81  def draw(self, cr, highlight=False):
82  """Draw this shape with the given cairo context"""
83  raise NotImplementedError
84 
85  def select_pen(self, highlight):
86  if highlight:
87  if not hasattr(self, 'highlight_pen'):
88  self.highlight_pen = self.pen.highlighted()
89  return self.highlight_pen
90  else:
91  return self.pen
92 
93 
95 
96  #fontmap = pangocairo.CairoFontMap()
97  #fontmap.set_resolution(72)
98  #context = fontmap.create_context()
99 
100  LEFT, CENTER, RIGHT = -1, 0, 1
101 
102  def __init__(self, pen, x, y, j, w, t):
103  Shape.__init__(self)
104  self.pen = pen.copy()
105  self.x = x
106  self.y = y
107  self.j = j
108  self.w = w
109  self.t = t
110 
111  def draw(self, cr, highlight=False):
112 
113  try:
114  layout = self.layout
115  except AttributeError:
116  layout = cr.create_layout()
117 
118  # set font options
119  # see http://lists.freedesktop.org/archives/cairo/2007-February/009688.html
120  context = layout.get_context()
121  fo = cairo.FontOptions()
122  fo.set_antialias(cairo.ANTIALIAS_DEFAULT)
123  fo.set_hint_style(cairo.HINT_STYLE_NONE)
124  fo.set_hint_metrics(cairo.HINT_METRICS_OFF)
125  try:
126  pangocairo.context_set_font_options(context, fo)
127  except TypeError:
128  # XXX: Some broken pangocairo bindings show the error
129  # 'TypeError: font_options must be a cairo.FontOptions or None'
130  pass
131 
132  # set font
133  font = pango.FontDescription()
134  font.set_family(self.pen.fontname)
135  font.set_absolute_size(self.pen.fontsize*pango.SCALE)
136  layout.set_font_description(font)
137 
138  # set text
139  layout.set_text(self.t)
140 
141  # cache it
142  self.layout = layout
143  else:
144  cr.update_layout(layout)
145 
146  descent = 2 # XXX get descender from font metrics
147 
148  width, height = layout.get_size()
149  width = float(width)/pango.SCALE
150  height = float(height)/pango.SCALE
151  # we know the width that dot thinks this text should have
152  # we do not necessarily have a font with the same metrics
153  # scale it so that the text fits inside its box
154  if width > self.w:
155  f = self.w / width
156  width = self.w # equivalent to width *= f
157  height *= f
158  descent *= f
159  else:
160  f = 1.0
161 
162  if self.j == self.LEFT:
163  x = self.x
164  elif self.j == self.CENTER:
165  x = self.x - 0.5*width
166  elif self.j == self.RIGHT:
167  x = self.x - width
168  else:
169  assert 0
170 
171  y = self.y - height + descent
172 
173  cr.move_to(x, y)
174 
175  cr.save()
176  cr.scale(f, f)
177  cr.set_source_rgba(*self.select_pen(highlight).color)
178  cr.show_layout(layout)
179  cr.restore()
180 
181  if 0: # DEBUG
182  # show where dot thinks the text should appear
183  cr.set_source_rgba(1, 0, 0, .9)
184  if self.j == self.LEFT:
185  x = self.x
186  elif self.j == self.CENTER:
187  x = self.x - 0.5*self.w
188  elif self.j == self.RIGHT:
189  x = self.x - self.w
190  cr.move_to(x, self.y)
191  cr.line_to(x+self.w, self.y)
192  cr.stroke()
193 
194 
196 
197  def __init__(self, pen, x0, y0, w, h, filled=False):
198  Shape.__init__(self)
199  self.pen = pen.copy()
200  self.x0 = x0
201  self.y0 = y0
202  self.w = w
203  self.h = h
204  self.filled = filled
205 
206  def draw(self, cr, highlight=False):
207  cr.save()
208  cr.translate(self.x0, self.y0)
209  cr.scale(self.w, self.h)
210  cr.move_to(1.0, 0.0)
211  cr.arc(0.0, 0.0, 1.0, 0, 2.0*math.pi)
212  cr.restore()
213  pen = self.select_pen(highlight)
214  if self.filled:
215  cr.set_source_rgba(*pen.fillcolor)
216  cr.fill()
217  else:
218  cr.set_dash(pen.dash)
219  cr.set_line_width(pen.linewidth)
220  cr.set_source_rgba(*pen.color)
221  cr.stroke()
222 
223 
225 
226  def __init__(self, pen, points, filled=False):
227  Shape.__init__(self)
228  self.pen = pen.copy()
229  self.points = points
230  self.filled = filled
231 
232  def draw(self, cr, highlight=False):
233  x0, y0 = self.points[-1]
234  cr.move_to(x0, y0)
235  for x, y in self.points:
236  cr.line_to(x, y)
237  cr.close_path()
238  pen = self.select_pen(highlight)
239  if self.filled:
240  cr.set_source_rgba(*pen.fillcolor)
241  cr.fill_preserve()
242  cr.fill()
243  else:
244  cr.set_dash(pen.dash)
245  cr.set_line_width(pen.linewidth)
246  cr.set_source_rgba(*pen.color)
247  cr.stroke()
248 
249 
251 
252  def __init__(self, pen, points):
253  Shape.__init__(self)
254  self.pen = pen.copy()
255  self.points = points
256 
257  def draw(self, cr, highlight=False):
258  x0, y0 = self.points[0]
259  cr.move_to(x0, y0)
260  for x1, y1 in self.points[1:]:
261  cr.line_to(x1, y1)
262  pen = self.select_pen(highlight)
263  cr.set_dash(pen.dash)
264  cr.set_line_width(pen.linewidth)
265  cr.set_source_rgba(*pen.color)
266  cr.stroke()
267 
268 
270 
271  def __init__(self, pen, points, filled=False):
272  Shape.__init__(self)
273  self.pen = pen.copy()
274  self.points = points
275  self.filled = filled
276 
277  def draw(self, cr, highlight=False):
278  x0, y0 = self.points[0]
279  cr.move_to(x0, y0)
280  for i in xrange(1, len(self.points), 3):
281  x1, y1 = self.points[i]
282  x2, y2 = self.points[i + 1]
283  x3, y3 = self.points[i + 2]
284  cr.curve_to(x1, y1, x2, y2, x3, y3)
285  pen = self.select_pen(highlight)
286  if self.filled:
287  cr.set_source_rgba(*pen.fillcolor)
288  cr.fill_preserve()
289  cr.fill()
290  else:
291  cr.set_dash(pen.dash)
292  cr.set_line_width(pen.linewidth)
293  cr.set_source_rgba(*pen.color)
294  cr.stroke()
295 
296 
298 
299  def __init__(self, shapes):
300  Shape.__init__(self)
301  self.shapes = shapes
302 
303  def draw(self, cr, highlight=False):
304  for shape in self.shapes:
305  shape.draw(cr, highlight=highlight)
306 
307 
308 class Url(object):
309 
310  def __init__(self, item, url, highlight=None):
311  self.item = item
312  self.url = url
313  if highlight is None:
314  highlight = set([item])
315  self.highlight = highlight
316 
317 
318 class Jump(object):
319 
320  def __init__(self, item, x, y, highlight=None, url=None):
321  self.item = item
322  self.x = x
323  self.y = y
324  if highlight is None:
325  highlight = set([item])
326  self.highlight = highlight
327  self.url = url
328 
329 
331  """Base class for graph nodes and edges."""
332 
333  def __init__(self, shapes):
334  CompoundShape.__init__(self, shapes)
335 
336  def get_url(self, x, y):
337  return None
338 
339  def get_jump(self, x, y):
340  return None
341 
342 
343 class Node(Element):
344 
345  def __init__(self, x, y, w, h, shapes, url):
346  Element.__init__(self, shapes)
347 
348  self.x = x
349  self.y = y
350 
351  self.x1 = x - 0.5*w
352  self.y1 = y - 0.5*h
353  self.x2 = x + 0.5*w
354  self.y2 = y + 0.5*h
355 
356  self.url = url
357 
358  def is_inside(self, x, y):
359  return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2
360 
361  def get_url(self, x, y):
362  if self.url is None:
363  return None
364  #print (x, y), (self.x1, self.y1), "-", (self.x2, self.y2)
365  if self.is_inside(x, y):
366  return Url(self, self.url)
367  return None
368 
369  def get_jump(self, x, y):
370  if self.is_inside(x, y):
371  return Jump(self, self.x, self.y)
372  return None
373 
374 
375 def square_distance(x1, y1, x2, y2):
376  deltax = x2 - x1
377  deltay = y2 - y1
378  return deltax*deltax + deltay*deltay
379 
380 
381 class Edge(Element):
382 
383  def __init__(self, src, dst, points, shapes, url):
384  Element.__init__(self, shapes)
385  self.src = src
386  self.dst = dst
387  self.points = points
388  self.url = url
389 
390  RADIUS = 10
391 
392  def get_jump(self, x, y):
393  if square_distance(x, y, *self.points[0]) <= self.RADIUS*self.RADIUS:
394  return Jump(self, self.dst.x, self.dst.y, highlight=set([self, self.dst]),url=self.url)
395  if square_distance(x, y, *self.points[-1]) <= self.RADIUS*self.RADIUS:
396  return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src]),url=self.url)
397  return None
398 
399 
400 class Graph(Shape):
401 
402  def __init__(self, width=1, height=1, shapes=(), nodes=(), edges=(), subgraph_shapes={}):
403  Shape.__init__(self)
404 
405  self.width = width
406  self.height = height
407  self.shapes = shapes
408  self.nodes = nodes
409  self.edges = edges
410  self.subgraph_shapes = subgraph_shapes
411 
412  def get_size(self):
413  return self.width, self.height
414 
415  def draw(self, cr, highlight_items=None):
416  if highlight_items is None:
417  highlight_items = ()
418  cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
419 
420  cr.set_line_cap(cairo.LINE_CAP_BUTT)
421  cr.set_line_join(cairo.LINE_JOIN_MITER)
422 
423  for shape in self.shapes:
424  shape.draw(cr)
425  for edge in self.edges:
426  edge.draw(cr, highlight=(edge in highlight_items))
427  for node in self.nodes:
428  node.draw(cr, highlight=(node in highlight_items))
429 
430  def get_url(self, x, y):
431  for node in self.nodes:
432  url = node.get_url(x, y)
433  if url is not None:
434  return url
435  return None
436 
437  def get_jump(self, x, y):
438  for edge in self.edges:
439  jump = edge.get_jump(x, y)
440  if jump is not None:
441  return jump
442  for node in self.nodes:
443  jump = node.get_jump(x, y)
444  if jump is not None:
445  return jump
446  return None
447 
448 
450  """Parser for xdot drawing attributes.
451  See also:
452  - http://www.graphviz.org/doc/info/output.html#d:xdot
453  """
454 
455  def __init__(self, parser, buf):
456  self.parser = parser
457  self.buf = self.unescape(buf)
458  self.pos = 0
459 
460  self.pen = Pen()
461  self.shapes = []
462 
463  def __nonzero__(self):
464  return self.pos < len(self.buf)
465 
466  def unescape(self, buf):
467  buf = buf.replace('\\"', '"')
468  buf = buf.replace('\\n', '\n')
469  return buf
470 
471  def read_code(self):
472  pos = self.buf.find(" ", self.pos)
473  res = self.buf[self.pos:pos]
474  self.pos = pos + 1
475  while self.pos < len(self.buf) and self.buf[self.pos].isspace():
476  self.pos += 1
477  return res
478 
479  def read_number(self):
480  return int(float(self.read_code()))
481 
482  def read_float(self):
483  return float(self.read_code())
484 
485  def read_point(self):
486  x = self.read_number()
487  y = self.read_number()
488  return self.transform(x, y)
489 
490  def read_text(self):
491  num = self.read_number()
492  pos = self.buf.find("-", self.pos) + 1
493  self.pos = pos + num
494  res = self.buf[pos:self.pos]
495  while self.pos < len(self.buf) and self.buf[self.pos].isspace():
496  self.pos += 1
497  return res
498 
499  def read_polygon(self):
500  n = self.read_number()
501  p = []
502  for i in range(n):
503  x, y = self.read_point()
504  p.append((x, y))
505  return p
506 
507  def read_color(self):
508  # See http://www.graphviz.org/doc/info/attrs.html#k:color
509  c = self.read_text()
510  c1 = c[:1]
511  if c1 == '#':
512  hex2float = lambda h: float(int(h, 16)/255.0)
513  r = hex2float(c[1:3])
514  g = hex2float(c[3:5])
515  b = hex2float(c[5:7])
516  try:
517  a = hex2float(c[7:9])
518  except (IndexError, ValueError):
519  a = 1.0
520  return r, g, b, a
521  elif c1.isdigit() or c1 == ".":
522  # "H,S,V" or "H S V" or "H, S, V" or any other variation
523  h, s, v = map(float, c.replace(",", " ").split())
524  r, g, b = colorsys.hsv_to_rgb(h, s, v)
525  a = 1.0
526  return r, g, b, a
527  else:
528  return self.lookup_color(c)
529 
530  def lookup_color(self, c):
531  try:
532  color = gtk.gdk.color_parse(c)
533  except ValueError:
534  pass
535  else:
536  s = 1.0/65535.0
537  r = color.red*s
538  g = color.green*s
539  b = color.blue*s
540  a = 1.0
541  return r, g, b, a
542 
543  try:
544  dummy, scheme, index = c.split('/')
545  r, g, b = brewer_colors[scheme][int(index)]
546  except (ValueError, KeyError):
547  pass
548  else:
549  s = 1.0/255.0
550  r = r*s
551  g = g*s
552  b = b*s
553  a = 1.0
554  return r, g, b, a
555 
556  sys.stderr.write("unknown color '%s'\n" % c)
557  return None
558 
559  def parse(self):
560  s = self
561 
562  while s:
563  op = s.read_code()
564  if op == "c":
565  color = s.read_color()
566  if color is not None:
567  self.handle_color(color, filled=False)
568  elif op == "C":
569  color = s.read_color()
570  if color is not None:
571  self.handle_color(color, filled=True)
572  elif op == "S":
573  # http://www.graphviz.org/doc/info/attrs.html#k:style
574  style = s.read_text()
575  if style.startswith("setlinewidth("):
576  lw = style.split("(")[1].split(")")[0]
577  lw = float(lw)
578  self.handle_linewidth(lw)
579  elif style in ("solid", "dashed"):
580  self.handle_linestyle(style)
581  elif op == "F":
582  size = s.read_float()
583  name = s.read_text()
584  self.handle_font(size, name)
585  elif op == "T":
586  x, y = s.read_point()
587  j = s.read_number()
588  w = s.read_number()
589  t = s.read_text()
590  self.handle_text(x, y, j, w, t)
591  elif op == "E":
592  x0, y0 = s.read_point()
593  w = s.read_number()
594  h = s.read_number()
595  self.handle_ellipse(x0, y0, w, h, filled=True)
596  elif op == "e":
597  x0, y0 = s.read_point()
598  w = s.read_number()
599  h = s.read_number()
600  self.handle_ellipse(x0, y0, w, h, filled=False)
601  elif op == "L":
602  points = self.read_polygon()
603  self.handle_line(points)
604  elif op == "B":
605  points = self.read_polygon()
606  self.handle_bezier(points, filled=False)
607  elif op == "b":
608  points = self.read_polygon()
609  self.handle_bezier(points, filled=True)
610  elif op == "P":
611  points = self.read_polygon()
612  self.handle_polygon(points, filled=True)
613  elif op == "p":
614  points = self.read_polygon()
615  self.handle_polygon(points, filled=False)
616  else:
617  sys.stderr.write("unknown xdot opcode '%s'\n" % op)
618  break
619 
620  return self.shapes
621 
622  def transform(self, x, y):
623  return self.parser.transform(x, y)
624 
625  def handle_color(self, color, filled=False):
626  if filled:
627  self.pen.fillcolor = color
628  else:
629  self.pen.color = color
630 
631  def handle_linewidth(self, linewidth):
632  self.pen.linewidth = linewidth
633 
634  def handle_linestyle(self, style):
635  if style == "solid":
636  self.pen.dash = ()
637  elif style == "dashed":
638  self.pen.dash = (6, ) # 6pt on, 6pt off
639 
640  def handle_font(self, size, name):
641  self.pen.fontsize = size
642  self.pen.fontname = name
643 
644  def handle_text(self, x, y, j, w, t):
645  self.shapes.append(TextShape(self.pen, x, y, j, w, t))
646 
647  def handle_ellipse(self, x0, y0, w, h, filled=False):
648  if filled:
649  # xdot uses this to mean "draw a filled shape with an outline"
650  self.shapes.append(EllipseShape(self.pen, x0, y0, w, h, filled=True))
651  self.shapes.append(EllipseShape(self.pen, x0, y0, w, h))
652 
653  def handle_line(self, points):
654  self.shapes.append(LineShape(self.pen, points))
655 
656  def handle_bezier(self, points, filled=False):
657  if filled:
658  # xdot uses this to mean "draw a filled shape with an outline"
659  self.shapes.append(BezierShape(self.pen, points, filled=True))
660  self.shapes.append(BezierShape(self.pen, points))
661 
662  def handle_polygon(self, points, filled=False):
663  if filled:
664  # xdot uses this to mean "draw a filled shape with an outline"
665  self.shapes.append(PolygonShape(self.pen, points, filled=True))
666  self.shapes.append(PolygonShape(self.pen, points))
667 
668 
669 EOF = -1
670 SKIP = -2
671 
672 
673 class ParseError(Exception):
674 
675  def __init__(self, msg=None, filename=None, line=None, col=None):
676  self.msg = msg
677  self.filename = filename
678  self.line = line
679  self.col = col
680 
681  def __str__(self):
682  return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
683 
684 
685 class Scanner:
686  """Stateless scanner."""
687 
688  # should be overriden by derived classes
689  tokens = []
690  symbols = {}
691  literals = {}
692  ignorecase = False
693 
694  def __init__(self):
695  flags = re.DOTALL
696  if self.ignorecase:
697  flags |= re.IGNORECASE
698  self.tokens_re = re.compile(
699  '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
700  flags
701  )
702 
703  def next(self, buf, pos):
704  if pos >= len(buf):
705  return EOF, '', pos
706  mo = self.tokens_re.match(buf, pos)
707  if mo:
708  text = mo.group()
709  type, regexp, test_lit = self.tokens[mo.lastindex - 1]
710  pos = mo.end()
711  if test_lit:
712  type = self.literals.get(text, type)
713  return type, text, pos
714  else:
715  c = buf[pos]
716  return self.symbols.get(c, None), c, pos + 1
717 
718 
719 class Token:
720 
721  def __init__(self, type, text, line, col):
722  self.type = type
723  self.text = text
724  self.line = line
725  self.col = col
726 
727 
728 class Lexer:
729 
730  # should be overriden by derived classes
731  scanner = None
732  tabsize = 8
733 
734  newline_re = re.compile(r'\r\n?|\n')
735 
736  def __init__(self, buf = None, pos = 0, filename = None, fp = None):
737  if fp is not None:
738  try:
739  fileno = fp.fileno()
740  length = os.path.getsize(fp.name)
741  import mmap
742  except:
743  # read whole file into memory
744  buf = fp.read()
745  pos = 0
746  else:
747  # map the whole file into memory
748  if length:
749  # length must not be zero
750  buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
751  pos = os.lseek(fileno, 0, 1)
752  else:
753  buf = ''
754  pos = 0
755 
756  if filename is None:
757  try:
758  filename = fp.name
759  except AttributeError:
760  filename = None
761 
762  self.buf = buf
763  self.pos = pos
764  self.line = 1
765  self.col = 1
766  self.filename = filename
767 
768  def next(self):
769  while True:
770  # save state
771  pos = self.pos
772  line = self.line
773  col = self.col
774 
775  type, text, endpos = self.scanner.next(self.buf, pos)
776  assert pos + len(text) == endpos
777  self.consume(text)
778  type, text = self.filter(type, text)
779  self.pos = endpos
780 
781  if type == SKIP:
782  continue
783  elif type is None:
784  msg = 'unexpected char '
785  if text >= ' ' and text <= '~':
786  msg += "'%s'" % text
787  else:
788  msg += "0x%X" % ord(text)
789  raise ParseError(msg, self.filename, line, col)
790  else:
791  break
792  return Token(type = type, text = text, line = line, col = col)
793 
794  def consume(self, text):
795  # update line number
796  pos = 0
797  for mo in self.newline_re.finditer(text, pos):
798  self.line += 1
799  self.col = 1
800  pos = mo.end()
801 
802  # update column number
803  while True:
804  tabpos = text.find('\t', pos)
805  if tabpos == -1:
806  break
807  self.col += tabpos - pos
808  self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
809  pos = tabpos + 1
810  self.col += len(text) - pos
811 
812 
813 class Parser:
814 
815  def __init__(self, lexer):
816  self.lexer = lexer
817  self.lookahead = self.lexer.next()
818 
819  def match(self, type):
820  if self.lookahead.type != type:
821  raise ParseError(
822  msg = 'unexpected token %r' % self.lookahead.text,
823  filename = self.lexer.filename,
824  line = self.lookahead.line,
825  col = self.lookahead.col)
826 
827  def skip(self, type):
828  while self.lookahead.type != type:
829  self.consume()
830 
831  def consume(self):
832  token = self.lookahead
833  self.lookahead = self.lexer.next()
834  return token
835 
836 
837 ID = 0
838 STR_ID = 1
839 HTML_ID = 2
840 EDGE_OP = 3
841 
842 LSQUARE = 4
843 RSQUARE = 5
844 LCURLY = 6
845 RCURLY = 7
846 COMMA = 8
847 COLON = 9
848 SEMI = 10
849 EQUAL = 11
850 PLUS = 12
851 
852 STRICT = 13
853 GRAPH = 14
854 DIGRAPH = 15
855 NODE = 16
856 EDGE = 17
857 SUBGRAPH = 18
858 
859 
861 
862  # token regular expression table
863  tokens = [
864  # whitespace and comments
865  (SKIP,
866  r'[ \t\f\r\n\v]+|'
867  r'//[^\r\n]*|'
868  r'/\*.*?\*/|'
869  r'#[^\r\n]*',
870  False),
871 
872  # Alphanumeric IDs
873  (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
874 
875  # Numeric IDs
876  (ID, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
877 
878  # String IDs
879  (STR_ID, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
880 
881  # HTML IDs
882  (HTML_ID, r'<[^<>]*(?:<[^<>]*>[^<>]*)*>', False),
883 
884  # Edge operators
885  (EDGE_OP, r'-[>-]', False),
886  ]
887 
888  # symbol table
889  symbols = {
890  '[': LSQUARE,
891  ']': RSQUARE,
892  '{': LCURLY,
893  '}': RCURLY,
894  ',': COMMA,
895  ':': COLON,
896  ';': SEMI,
897  '=': EQUAL,
898  '+': PLUS,
899  }
900 
901  # literal table
902  literals = {
903  'strict': STRICT,
904  'graph': GRAPH,
905  'digraph': DIGRAPH,
906  'node': NODE,
907  'edge': EDGE,
908  'subgraph': SUBGRAPH,
909  }
910 
911  ignorecase = True
912 
913 
915 
916  scanner = DotScanner()
917 
918  def filter(self, type, text):
919  # TODO: handle charset
920  if type == STR_ID:
921  text = text[1:-1]
922 
923  # line continuations
924  text = text.replace('\\\r\n', '')
925  text = text.replace('\\\r', '')
926  text = text.replace('\\\n', '')
927 
928  text = text.replace('\\r', '\r')
929  text = text.replace('\\n', '\n')
930  text = text.replace('\\t', '\t')
931  text = text.replace('\\', '')
932 
933  type = ID
934 
935  elif type == HTML_ID:
936  text = text[1:-1]
937  type = ID
938 
939  return type, text
940 
941 
943 
944  def __init__(self, lexer):
945  Parser.__init__(self, lexer)
946  self.graph_attrs = {}
947  self.node_attrs = {}
948  self.edge_attrs = {}
949 
950  def parse(self):
951  self.parse_graph()
952  self.match(EOF)
953 
954  def parse_graph(self):
955  if self.lookahead.type == STRICT:
956  self.consume()
957  self.skip(LCURLY)
958  self.consume()
959  while self.lookahead.type != RCURLY:
960  self.parse_stmt()
961  self.consume()
962 
963  def parse_subgraph(self):
964  id = None
965  shapes_before = set(self.shapes)
966  if self.lookahead.type == SUBGRAPH:
967  self.consume()
968  if self.lookahead.type == ID:
969  id = self.lookahead.text
970  self.consume()
971  if self.lookahead.type == LCURLY:
972  self.consume()
973  while self.lookahead.type != RCURLY:
974  self.parse_stmt()
975  self.consume()
976  new_shapes = set(self.shapes) - shapes_before
977  self.subgraph_shapes[id] = [s for s in new_shapes if not any([s in ss for ss in self.subgraph_shapes.values()])]
978  return id
979 
980  def parse_stmt(self):
981  if self.lookahead.type == GRAPH:
982  self.consume()
983  attrs = self.parse_attrs()
984  self.graph_attrs.update(attrs)
985  self.handle_graph(attrs)
986  elif self.lookahead.type == NODE:
987  self.consume()
988  self.node_attrs.update(self.parse_attrs())
989  elif self.lookahead.type == EDGE:
990  self.consume()
991  self.edge_attrs.update(self.parse_attrs())
992  elif self.lookahead.type in (SUBGRAPH, LCURLY):
993  self.parse_subgraph()
994  else:
995  id = self.parse_node_id()
996  if self.lookahead.type == EDGE_OP:
997  self.consume()
998  node_ids = [id, self.parse_node_id()]
999  while self.lookahead.type == EDGE_OP:
1000  node_ids.append(self.parse_node_id())
1001  attrs = self.parse_attrs()
1002  for i in range(0, len(node_ids) - 1):
1003  self.handle_edge(node_ids[i], node_ids[i + 1], attrs)
1004  elif self.lookahead.type == EQUAL:
1005  self.consume()
1006  self.parse_id()
1007  else:
1008  attrs = self.parse_attrs()
1009  self.handle_node(id, attrs)
1010  if self.lookahead.type == SEMI:
1011  self.consume()
1012 
1013  def parse_attrs(self):
1014  attrs = {}
1015  while self.lookahead.type == LSQUARE:
1016  self.consume()
1017  while self.lookahead.type != RSQUARE:
1018  name, value = self.parse_attr()
1019  attrs[name] = value
1020  if self.lookahead.type == COMMA:
1021  self.consume()
1022  self.consume()
1023  return attrs
1024 
1025  def parse_attr(self):
1026  name = self.parse_id()
1027  if self.lookahead.type == EQUAL:
1028  self.consume()
1029  value = self.parse_id()
1030  else:
1031  value = 'true'
1032  return name, value
1033 
1034  def parse_node_id(self):
1035  node_id = self.parse_id()
1036  if self.lookahead.type == COLON:
1037  self.consume()
1038  port = self.parse_id()
1039  if self.lookahead.type == COLON:
1040  self.consume()
1041  compass_pt = self.parse_id()
1042  else:
1043  compass_pt = None
1044  else:
1045  port = None
1046  compass_pt = None
1047  # XXX: we don't really care about port and compass point values when parsing xdot
1048  return node_id
1049 
1050  def parse_id(self):
1051  self.match(ID)
1052  id = self.lookahead.text
1053  self.consume()
1054  return id
1055 
1056  def handle_graph(self, attrs):
1057  pass
1058 
1059  def handle_node(self, id, attrs):
1060  pass
1061 
1062  def handle_edge(self, src_id, dst_id, attrs):
1063  pass
1064 
1065 
1066 class XDotParser(DotParser):
1067 
1068  def __init__(self, xdotcode):
1069  lexer = DotLexer(buf = xdotcode)
1070  DotParser.__init__(self, lexer)
1071 
1072  self.nodes = []
1073  self.edges = []
1074  self.shapes = []
1075  self.node_by_name = {}
1076  self.top_graph = True
1078 
1079  def handle_graph(self, attrs):
1080  if self.top_graph:
1081  try:
1082  bb = attrs['bb']
1083  except KeyError:
1084  return
1085 
1086  if not bb:
1087  return
1088 
1089  xmin, ymin, xmax, ymax = map(float, bb.split(","))
1090 
1091  self.xoffset = -xmin
1092  self.yoffset = -ymax
1093  self.xscale = 1.0
1094  self.yscale = -1.0
1095  # FIXME: scale from points to pixels
1096 
1097  self.width = xmax - xmin
1098  self.height = ymax - ymin
1099 
1100  self.top_graph = False
1101 
1102  for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
1103  if attr in attrs:
1104  parser = XDotAttrParser(self, attrs[attr])
1105  self.shapes.extend(parser.parse())
1106 
1107  def handle_node(self, id, attrs):
1108  try:
1109  pos = attrs['pos']
1110  except KeyError:
1111  return
1112 
1113  x, y = self.parse_node_pos(pos)
1114  w = float(attrs['width'])*72
1115  h = float(attrs['height'])*72
1116  shapes = []
1117  for attr in ("_draw_", "_ldraw_"):
1118  if attr in attrs:
1119  parser = XDotAttrParser(self, attrs[attr])
1120  shapes.extend(parser.parse())
1121  url = attrs.get('URL', None)
1122  node = Node(x, y, w, h, shapes, url)
1123  self.node_by_name[id] = node
1124  if shapes:
1125  self.nodes.append(node)
1126 
1127  def handle_edge(self, src_id, dst_id, attrs):
1128  try:
1129  pos = attrs['pos']
1130  except KeyError:
1131  return
1132 
1133  points = self.parse_edge_pos(pos)
1134  shapes = []
1135  for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
1136  if attr in attrs:
1137  parser = XDotAttrParser(self, attrs[attr])
1138  shapes.extend(parser.parse())
1139  url = attrs.get('URL', None)
1140  if shapes:
1141  src = self.node_by_name[src_id]
1142  dst = self.node_by_name[dst_id]
1143  self.edges.append(Edge(src, dst, points, shapes, url))
1144 
1145  def parse(self):
1146  DotParser.parse(self)
1147 
1148  """
1149  for k,shapes in self.subgraph_shapes.iteritems():
1150  self.shapes += shapes
1151  """
1152 
1153  return Graph(self.width, self.height, self.shapes, self.nodes, self.edges, self.subgraph_shapes)
1154 
1155  def parse_node_pos(self, pos):
1156  x, y = pos.split(",")
1157  return self.transform(float(x), float(y))
1158 
1159  def parse_edge_pos(self, pos):
1160  points = []
1161  for entry in pos.split(' '):
1162  fields = entry.split(',')
1163  try:
1164  x, y = fields
1165  except ValueError:
1166  # TODO: handle start/end points
1167  continue
1168  else:
1169  points.append(self.transform(float(x), float(y)))
1170  return points
1171 
1172  def transform(self, x, y):
1173  # XXX: this is not the right place for this code
1174  x = (x + self.xoffset)*self.xscale
1175  y = (y + self.yoffset)*self.yscale
1176  return x, y
1177 
1178 
1179 class Animation(object):
1180 
1181  step = 0.03 # seconds
1182 
1183  def __init__(self, dot_widget):
1184  self.dot_widget = dot_widget
1185  self.timeout_id = None
1186 
1187  def start(self):
1188  self.timeout_id = gobject.timeout_add(int(self.step * 1000), self.tick)
1189 
1190  def stop(self):
1191  self.dot_widget.animation = NoAnimation(self.dot_widget)
1192  if self.timeout_id is not None:
1193  gobject.source_remove(self.timeout_id)
1194  self.timeout_id = None
1195 
1196  def tick(self):
1197  self.stop()
1198 
1199 
1201 
1202  def start(self):
1203  pass
1204 
1205  def stop(self):
1206  pass
1207 
1208 
1209 class LinearAnimation(Animation):
1210 
1211  duration = 0.6
1212 
1213  def start(self):
1214  self.started = time.time()
1215  Animation.start(self)
1216 
1217  def tick(self):
1218  t = (time.time() - self.started) / self.duration
1219  self.animate(max(0, min(t, 1)))
1220  return (t < 1)
1221 
1222  def animate(self, t):
1223  pass
1224 
1225 
1226 class MoveToAnimation(LinearAnimation):
1227 
1228  def __init__(self, dot_widget, target_x, target_y):
1229  Animation.__init__(self, dot_widget)
1230  self.source_x = dot_widget.x
1231  self.source_y = dot_widget.y
1232  self.target_x = target_x
1233  self.target_y = target_y
1234 
1235  def animate(self, t):
1236  sx, sy = self.source_x, self.source_y
1237  tx, ty = self.target_x, self.target_y
1238  self.dot_widget.x = tx * t + sx * (1-t)
1239  self.dot_widget.y = ty * t + sy * (1-t)
1240  self.dot_widget.queue_draw()
1241 
1242 
1244 
1245  def __init__(self, dot_widget, target_x, target_y):
1246  MoveToAnimation.__init__(self, dot_widget, target_x, target_y)
1247  self.source_zoom = dot_widget.zoom_ratio
1249  self.extra_zoom = 0
1250 
1251  middle_zoom = 0.5 * (self.source_zoom + self.target_zoom)
1252 
1253  distance = math.hypot(self.source_x - self.target_x,
1254  self.source_y - self.target_y)
1255  rect = self.dot_widget.get_allocation()
1256  visible = min(rect.width, rect.height) / self.dot_widget.zoom_ratio
1257  visible *= 0.9
1258  if distance > 0:
1259  desired_middle_zoom = visible / distance
1260  self.extra_zoom = min(0, 4 * (desired_middle_zoom - middle_zoom))
1261 
1262  def animate(self, t):
1263  a, b, c = self.source_zoom, self.extra_zoom, self.target_zoom
1264  self.dot_widget.zoom_ratio = c*t + b*t*(1-t) + a*(1-t)
1265  self.dot_widget.zoom_to_fit_on_resize = False
1266  MoveToAnimation.animate(self, t)
1267 
1268 
1269 class DragAction(object):
1270 
1271  def __init__(self, dot_widget):
1272  self.dot_widget = dot_widget
1273 
1274  def on_button_press(self, event):
1275  self.startmousex = self.prevmousex = event.x
1276  self.startmousey = self.prevmousey = event.y
1277  self.start()
1278 
1279  def on_motion_notify(self, event):
1280  if event.is_hint:
1281  x, y, state = event.window.get_pointer()
1282  else:
1283  x, y, state = event.x, event.y, event.state
1284  deltax = self.prevmousex - x
1285  deltay = self.prevmousey - y
1286  self.drag(deltax, deltay)
1287  self.prevmousex = x
1288  self.prevmousey = y
1289 
1290  def on_button_release(self, event):
1291  self.stopmousex = event.x
1292  self.stopmousey = event.y
1293  self.stop()
1294 
1295  def draw(self, cr):
1296  pass
1297 
1298  def start(self):
1299  pass
1300 
1301  def drag(self, deltax, deltay):
1302  pass
1303 
1304  def stop(self):
1305  pass
1306 
1307  def abort(self):
1308  pass
1309 
1310 
1311 class NullAction(DragAction):
1312 
1313  def on_motion_notify(self, event):
1314  if event.is_hint:
1315  x, y, state = event.window.get_pointer()
1316  else:
1317  x, y, state = event.x, event.y, event.state
1318  dot_widget = self.dot_widget
1319  item = dot_widget.get_url(x, y)
1320  if item is None:
1321  item = dot_widget.get_jump(x, y)
1322  if item is not None:
1323  dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
1324  dot_widget.set_highlight(item.highlight)
1325  else:
1326  dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
1327  dot_widget.set_highlight(None)
1328 
1329 
1331 
1332  def start(self):
1333  self.dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
1334 
1335  def drag(self, deltax, deltay):
1336  self.dot_widget.x += deltax / self.dot_widget.zoom_ratio
1337  self.dot_widget.y += deltay / self.dot_widget.zoom_ratio
1338  self.dot_widget.queue_draw()
1339 
1340  def stop(self):
1341  self.dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
1342 
1343  abort = stop
1344 
1345 
1347 
1348  def drag(self, deltax, deltay):
1349  self.dot_widget.zoom_ratio *= 1.005 ** (deltax + deltay)
1350  self.dot_widget.zoom_to_fit_on_resize = False
1351  self.dot_widget.queue_draw()
1352 
1353  def stop(self):
1354  self.dot_widget.queue_draw()
1355 
1356 
1358 
1359  def drag(self, deltax, deltay):
1360  self.dot_widget.queue_draw()
1361 
1362  def draw(self, cr):
1363  cr.save()
1364  cr.set_source_rgba(.5, .5, 1.0, 0.25)
1365  cr.rectangle(self.startmousex, self.startmousey,
1366  self.prevmousex - self.startmousex,
1367  self.prevmousey - self.startmousey)
1368  cr.fill()
1369  cr.set_source_rgba(.5, .5, 1.0, 1.0)
1370  cr.set_line_width(1)
1371  cr.rectangle(self.startmousex - .5, self.startmousey - .5,
1372  self.prevmousex - self.startmousex + 1,
1373  self.prevmousey - self.startmousey + 1)
1374  cr.stroke()
1375  cr.restore()
1376 
1377  def stop(self):
1378  x1, y1 = self.dot_widget.window2graph(self.startmousex,
1379  self.startmousey)
1380  x2, y2 = self.dot_widget.window2graph(self.stopmousex,
1381  self.stopmousey)
1382  self.dot_widget.zoom_to_area(x1, y1, x2, y2)
1383 
1384  def abort(self):
1385  self.dot_widget.queue_draw()
1386 
1387 
1388 class DotWidget(gtk.DrawingArea):
1389  """PyGTK widget that draws dot graphs."""
1390 
1391  __gsignals__ = {
1392  'expose-event': 'override',
1393  'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event))
1394  }
1395 
1396  filter = 'dot'
1397 
1398  def __init__(self):
1399  gtk.DrawingArea.__init__(self)
1400 
1401  self.graph = Graph()
1402  self.openfilename = None
1403 
1404  self.set_flags(gtk.CAN_FOCUS)
1405 
1406  self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
1407  self.connect("button-press-event", self.on_area_button_press)
1408  self.connect("button-release-event", self.on_area_button_release)
1409  self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
1410  self.connect("motion-notify-event", self.on_area_motion_notify)
1411  self.connect("scroll-event", self.on_area_scroll_event)
1412  self.connect("size-allocate", self.on_area_size_allocate)
1413 
1414  self.connect('key-press-event', self.on_key_press_event)
1415 
1416  self.x, self.y = 0.0, 0.0
1417  self.zoom_ratio = 1.0
1419  self.animation = NoAnimation(self)
1421  self.presstime = None
1422  self.highlight = None
1423 
1424  def set_filter(self, filter):
1425  self.filter = filter
1426 
1427  def set_dotcode(self, dotcode, filename='<stdin>'):
1428  if isinstance(dotcode, unicode):
1429  dotcode = dotcode.encode('utf8')
1430  p = subprocess.Popen(
1431  [self.filter, '-Txdot'],
1432  stdin=subprocess.PIPE,
1433  stdout=subprocess.PIPE,
1434  stderr=subprocess.PIPE,
1435  shell=False,
1436  universal_newlines=True
1437  )
1438  xdotcode, error = p.communicate(dotcode)
1439  if p.returncode != 0:
1440  print "UNABLE TO SHELL TO DOT", error
1441  dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
1442  message_format=error,
1443  buttons=gtk.BUTTONS_OK)
1444  dialog.set_title('Dot Viewer')
1445  dialog.run()
1446  dialog.destroy()
1447  return False
1448  try:
1449  self.set_xdotcode(xdotcode)
1450  except ParseError, ex:
1451  dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
1452  message_format=str(ex),
1453  buttons=gtk.BUTTONS_OK)
1454  dialog.set_title('Dot Viewer')
1455  dialog.run()
1456  dialog.destroy()
1457  return False
1458  else:
1459  self.openfilename = filename
1460  return True
1461 
1462  def set_xdotcode(self, xdotcode):
1463  #print xdotcode
1464  parser = XDotParser(xdotcode)
1465  self.graph = parser.parse()
1466  self.zoom_image(self.zoom_ratio, center=False)
1467 
1468  def reload(self):
1469  if self.openfilename is not None:
1470  try:
1471  fp = file(self.openfilename, 'rt')
1472  self.set_dotcode(fp.read(), self.openfilename)
1473  fp.close()
1474  except IOError:
1475  pass
1476 
1477  def do_expose_event(self, event):
1478  cr = self.window.cairo_create()
1479 
1480  # set a clip region for the expose event
1481  cr.rectangle(
1482  event.area.x, event.area.y,
1483  event.area.width, event.area.height
1484  )
1485  cr.clip()
1486 
1487  cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
1488  cr.paint()
1489 
1490  cr.save()
1491  rect = self.get_allocation()
1492  cr.translate(0.5*rect.width, 0.5*rect.height)
1493  cr.scale(self.zoom_ratio, self.zoom_ratio)
1494  cr.translate(-self.x, -self.y)
1495 
1496  self.graph.draw(cr, highlight_items=self.highlight)
1497  cr.restore()
1498 
1499  self.drag_action.draw(cr)
1500 
1501  return False
1502 
1503  def get_current_pos(self):
1504  return self.x, self.y
1505 
1506  def set_current_pos(self, x, y):
1507  self.x = x
1508  self.y = y
1509  self.queue_draw()
1510 
1511  def set_highlight(self, items):
1512  if self.highlight != items:
1513  self.highlight = items
1514  self.queue_draw()
1515 
1516  def zoom_image(self, zoom_ratio, center=False, pos=None):
1517  if center:
1518  self.x = self.graph.width/2
1519  self.y = self.graph.height/2
1520  elif pos is not None:
1521  rect = self.get_allocation()
1522  x, y = pos
1523  x -= 0.5*rect.width
1524  y -= 0.5*rect.height
1525  self.x += x / self.zoom_ratio - x / zoom_ratio
1526  self.y += y / self.zoom_ratio - y / zoom_ratio
1527  self.zoom_ratio = zoom_ratio
1528  self.zoom_to_fit_on_resize = False
1529  self.queue_draw()
1530 
1531  def zoom_to_area(self, x1, y1, x2, y2):
1532  rect = self.get_allocation()
1533  width = abs(x1 - x2)
1534  height = abs(y1 - y2)
1535  self.zoom_ratio = min(
1536  float(rect.width)/float(width),
1537  float(rect.height)/float(height)
1538  )
1539  self.zoom_to_fit_on_resize = False
1540  self.x = (x1 + x2) / 2
1541  self.y = (y1 + y2) / 2
1542  self.queue_draw()
1543 
1544  def zoom_to_fit(self):
1545  rect = self.get_allocation()
1546  rect.x += self.ZOOM_TO_FIT_MARGIN
1547  rect.y += self.ZOOM_TO_FIT_MARGIN
1548  rect.width -= 2 * self.ZOOM_TO_FIT_MARGIN
1549  rect.height -= 2 * self.ZOOM_TO_FIT_MARGIN
1550  zoom_ratio = min(
1551  float(rect.width)/float(self.graph.width),
1552  float(rect.height)/float(self.graph.height)
1553  )
1554  self.zoom_image(zoom_ratio, center=True)
1555  self.zoom_to_fit_on_resize = True
1556 
1557  ZOOM_INCREMENT = 1.25
1558  ZOOM_TO_FIT_MARGIN = 12
1559 
1560  def on_zoom_in(self, action):
1561  self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
1562 
1563  def on_zoom_out(self, action):
1564  self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
1565 
1566  def on_zoom_fit(self, action):
1567  self.zoom_to_fit()
1568 
1569  def on_zoom_100(self, action):
1570  self.zoom_image(1.0)
1571 
1572  POS_INCREMENT = 100
1573 
1574  def on_key_press_event(self, widget, event):
1575  if event.keyval == gtk.keysyms.Left:
1576  self.x -= self.POS_INCREMENT/self.zoom_ratio
1577  self.queue_draw()
1578  return True
1579  if event.keyval == gtk.keysyms.Right:
1580  self.x += self.POS_INCREMENT/self.zoom_ratio
1581  self.queue_draw()
1582  return True
1583  if event.keyval == gtk.keysyms.Up:
1584  self.y -= self.POS_INCREMENT/self.zoom_ratio
1585  self.queue_draw()
1586  return True
1587  if event.keyval == gtk.keysyms.Down:
1588  self.y += self.POS_INCREMENT/self.zoom_ratio
1589  self.queue_draw()
1590  return True
1591  if event.keyval == gtk.keysyms.Page_Up:
1592  self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
1593  self.queue_draw()
1594  return True
1595  if event.keyval == gtk.keysyms.Page_Down:
1596  self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
1597  self.queue_draw()
1598  return True
1599  if event.keyval == gtk.keysyms.Escape:
1600  self.drag_action.abort()
1601  self.drag_action = NullAction(self)
1602  return True
1603  if event.keyval == gtk.keysyms.r:
1604  self.reload()
1605  return True
1606  if event.keyval == gtk.keysyms.q:
1607  gtk.main_quit()
1608  return True
1609  return False
1610 
1611  def get_drag_action(self, event):
1612  state = event.state
1613  if event.button in (1, 2): # left or middle button
1614  if state & gtk.gdk.CONTROL_MASK:
1615  return ZoomAction
1616  elif state & gtk.gdk.SHIFT_MASK:
1617  return ZoomAreaAction
1618  else:
1619  return PanAction
1620  return NullAction
1621 
1622  def on_area_button_press(self, area, event):
1623  self.animation.stop()
1624  self.drag_action.abort()
1625  action_type = self.get_drag_action(event)
1626  self.drag_action = action_type(self)
1627  self.drag_action.on_button_press(event)
1628  self.presstime = time.time()
1629  self.pressx = event.x
1630  self.pressy = event.y
1631  return False
1632 
1633  def is_click(self, event, click_fuzz=4, click_timeout=1.0):
1634  assert event.type == gtk.gdk.BUTTON_RELEASE
1635  if self.presstime is None:
1636  # got a button release without seeing the press?
1637  return False
1638  # XXX instead of doing this complicated logic, shouldn't we listen
1639  # for gtk's clicked event instead?
1640  deltax = self.pressx - event.x
1641  deltay = self.pressy - event.y
1642  return (time.time() < self.presstime + click_timeout
1643  and math.hypot(deltax, deltay) < click_fuzz)
1644 
1645  def on_area_button_release(self, area, event):
1646  self.drag_action.on_button_release(event)
1647  self.drag_action = NullAction(self)
1648  if event.button == 1 and self.is_click(event):
1649  x, y = int(event.x), int(event.y)
1650  url = self.get_url(x, y)
1651  if url is not None:
1652  self.emit('clicked', unicode(url.url), event)
1653  else:
1654  jump = self.get_jump(x, y)
1655  if jump is not None:
1656  self.animate_to(jump.x, jump.y)
1657 
1658  return True
1659  if event.button == 1 or event.button == 2:
1660  return True
1661  return False
1662 
1663  def on_area_scroll_event(self, area, event):
1664  if event.direction == gtk.gdk.SCROLL_UP:
1665  self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT,
1666  pos=(event.x, event.y))
1667  return True
1668  if event.direction == gtk.gdk.SCROLL_DOWN:
1669  self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT,
1670  pos=(event.x, event.y))
1671  return True
1672  return False
1673 
1674  def on_area_motion_notify(self, area, event):
1675  self.drag_action.on_motion_notify(event)
1676  return True
1677 
1678  def on_area_size_allocate(self, area, allocation):
1679  if self.zoom_to_fit_on_resize:
1680  self.zoom_to_fit()
1681 
1682  def animate_to(self, x, y):
1683  self.animation = ZoomToAnimation(self, x, y)
1684  self.animation.start()
1685 
1686  def window2graph(self, x, y):
1687  rect = self.get_allocation()
1688  x -= 0.5*rect.width
1689  y -= 0.5*rect.height
1690  x /= self.zoom_ratio
1691  y /= self.zoom_ratio
1692  x += self.x
1693  y += self.y
1694  return x, y
1695 
1696  def get_url(self, x, y):
1697  x, y = self.window2graph(x, y)
1698  return self.graph.get_url(x, y)
1699 
1700  def get_jump(self, x, y):
1701  x, y = self.window2graph(x, y)
1702  return self.graph.get_jump(x, y)
1703 
1704 
1705 class DotWindow(gtk.Window):
1706 
1707  ui = '''
1708  <ui>
1709  <toolbar name="ToolBar">
1710  <toolitem action="Open"/>
1711  <toolitem action="Reload"/>
1712  <separator/>
1713  <toolitem action="ZoomIn"/>
1714  <toolitem action="ZoomOut"/>
1715  <toolitem action="ZoomFit"/>
1716  <toolitem action="Zoom100"/>
1717  </toolbar>
1718  </ui>
1719  '''
1720 
1721  def __init__(self):
1722  gtk.Window.__init__(self)
1723 
1724  self.graph = Graph()
1725 
1726  window = self
1727 
1728  window.set_title('Dot Viewer')
1729  window.set_default_size(512, 512)
1730  vbox = gtk.VBox()
1731  window.add(vbox)
1732 
1734 
1735  # Create a UIManager instance
1736  uimanager = self.uimanager = gtk.UIManager()
1737 
1738  # Add the accelerator group to the toplevel window
1739  accelgroup = uimanager.get_accel_group()
1740  window.add_accel_group(accelgroup)
1741 
1742  # Create an ActionGroup
1743  actiongroup = gtk.ActionGroup('Actions')
1744  self.actiongroup = actiongroup
1745 
1746  # Create actions
1747  actiongroup.add_actions((
1748  ('Open', gtk.STOCK_OPEN, None, None, None, self.on_open),
1749  ('Reload', gtk.STOCK_REFRESH, None, None, None, self.on_reload),
1750  ('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget.on_zoom_in),
1751  ('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget.on_zoom_out),
1752  ('ZoomFit', gtk.STOCK_ZOOM_FIT, None, None, None, self.widget.on_zoom_fit),
1753  ('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget.on_zoom_100),
1754  ))
1755 
1756  # Add the actiongroup to the uimanager
1757  uimanager.insert_action_group(actiongroup, 0)
1758 
1759  # Add a UI descrption
1760  uimanager.add_ui_from_string(self.ui)
1761 
1762  # Create a Toolbar
1763  toolbar = uimanager.get_widget('/ToolBar')
1764  vbox.pack_start(toolbar, False)
1765 
1766  vbox.pack_start(self.widget)
1767 
1768  self.set_focus(self.widget)
1769 
1770  self.show_all()
1771 
1772  def update(self, filename):
1773  import os
1774  if not hasattr(self, "last_mtime"):
1775  self.last_mtime = None
1776 
1777  current_mtime = os.stat(filename).st_mtime
1778  if current_mtime != self.last_mtime:
1779  self.last_mtime = current_mtime
1780  self.open_file(filename,True)
1781 
1782  return True
1783 
1784  def set_filter(self, filter):
1785  self.widget.set_filter(filter)
1786 
1787  def set_dotcode(self, dotcode, filename='<stdin>',refresh=False):
1788  if self.widget.set_dotcode(dotcode, filename):
1789  self.set_title(os.path.basename(filename) + ' - Dot Viewer')
1790  if not refresh:
1791  self.widget.zoom_to_fit()
1792 
1793  def set_xdotcode(self, xdotcode, filename='<stdin>'):
1794  if self.widget.set_xdotcode(xdotcode):
1795  self.set_title(os.path.basename(filename) + ' - Dot Viewer')
1796  self.widget.zoom_to_fit()
1797 
1798  def open_file(self, filename, refresh=False):
1799  try:
1800  fp = file(filename, 'rt')
1801  self.set_dotcode(fp.read(), filename ,refresh)
1802  fp.close()
1803  except IOError, ex:
1804  dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
1805  message_format=str(ex),
1806  buttons=gtk.BUTTONS_OK)
1807  dlg.set_title('Dot Viewer')
1808  dlg.run()
1809  dlg.destroy()
1810 
1811  def on_open(self, action):
1812  chooser = gtk.FileChooserDialog(title="Open dot File",
1813  action=gtk.FILE_CHOOSER_ACTION_OPEN,
1814  buttons=(gtk.STOCK_CANCEL,
1815  gtk.RESPONSE_CANCEL,
1816  gtk.STOCK_OPEN,
1817  gtk.RESPONSE_OK))
1818  chooser.set_default_response(gtk.RESPONSE_OK)
1819  filter = gtk.FileFilter()
1820  filter.set_name("Graphviz dot files")
1821  filter.add_pattern("*.dot")
1822  chooser.add_filter(filter)
1823  filter = gtk.FileFilter()
1824  filter.set_name("All files")
1825  filter.add_pattern("*")
1826  chooser.add_filter(filter)
1827  if chooser.run() == gtk.RESPONSE_OK:
1828  filename = chooser.get_filename()
1829  chooser.destroy()
1830  self.open_file(filename)
1831  else:
1832  chooser.destroy()
1833 
1834  def on_reload(self, action):
1835  self.widget.reload()
1836 
1837 
1838 def main():
1839  import optparse
1840 
1841  parser = optparse.OptionParser(
1842  usage='\n\t%prog [file]',
1843  version='%%prog %s' % __version__)
1844  parser.add_option(
1845  '-f', '--filter',
1846  type='choice', choices=('dot', 'neato', 'twopi', 'circo', 'fdp'),
1847  dest='filter', default='dot',
1848  help='graphviz filter: dot, neato, twopi, circo, or fdp [default: %default]')
1849 
1850  (options, args) = parser.parse_args(sys.argv[1:])
1851  if len(args) > 1:
1852  parser.error('incorrect number of arguments')
1853 
1854  win = DotWindow()
1855  win.connect('destroy', gtk.main_quit)
1856  win.set_filter(options.filter)
1857  if len(args) >= 1:
1858  if args[0] == '-':
1859  win.set_dotcode(sys.stdin.read())
1860  else:
1861  win.open_file(args[0])
1862  gobject.timeout_add(1000, win.update, args[0])
1863  gtk.main()
1864 
1865 
1866 # Apache-Style Software License for ColorBrewer software and ColorBrewer Color
1867 # Schemes, Version 1.1
1868 #
1869 # Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State
1870 # University. All rights reserved.
1871 #
1872 # Redistribution and use in source and binary forms, with or without
1873 # modification, are permitted provided that the following conditions are met:
1874 #
1875 # 1. Redistributions as source code must retain the above copyright notice,
1876 # this list of conditions and the following disclaimer.
1877 #
1878 # 2. The end-user documentation included with the redistribution, if any,
1879 # must include the following acknowledgment:
1880 #
1881 # This product includes color specifications and designs developed by
1882 # Cynthia Brewer (http://colorbrewer.org/).
1883 #
1884 # Alternately, this acknowledgment may appear in the software itself, if and
1885 # wherever such third-party acknowledgments normally appear.
1886 #
1887 # 3. The name "ColorBrewer" must not be used to endorse or promote products
1888 # derived from this software without prior written permission. For written
1889 # permission, please contact Cynthia Brewer at cbrewer@psu.edu.
1890 #
1891 # 4. Products derived from this software may not be called "ColorBrewer",
1892 # nor may "ColorBrewer" appear in their name, without prior written
1893 # permission of Cynthia Brewer.
1894 #
1895 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
1896 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
1897 # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CYNTHIA
1898 # BREWER, MARK HARROWER, OR THE PENNSYLVANIA STATE UNIVERSITY BE LIABLE FOR ANY
1899 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1900 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
1901 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1902 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1903 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
1904 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1905 brewer_colors = {
1906  'accent3': [(127, 201, 127), (190, 174, 212), (253, 192, 134)],
1907  'accent4': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153)],
1908  'accent5': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176)],
1909  'accent6': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127)],
1910  'accent7': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127), (191, 91, 23)],
1911  'accent8': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127), (191, 91, 23), (102, 102, 102)],
1912  'blues3': [(222, 235, 247), (158, 202, 225), (49, 130, 189)],
1913  'blues4': [(239, 243, 255), (189, 215, 231), (107, 174, 214), (33, 113, 181)],
1914  'blues5': [(239, 243, 255), (189, 215, 231), (107, 174, 214), (49, 130, 189), (8, 81, 156)],
1915  'blues6': [(239, 243, 255), (198, 219, 239), (158, 202, 225), (107, 174, 214), (49, 130, 189), (8, 81, 156)],
1916  'blues7': [(239, 243, 255), (198, 219, 239), (158, 202, 225), (107, 174, 214), (66, 146, 198), (33, 113, 181), (8, 69, 148)],
1917  'blues8': [(247, 251, 255), (222, 235, 247), (198, 219, 239), (158, 202, 225), (107, 174, 214), (66, 146, 198), (33, 113, 181), (8, 69, 148)],
1918  'blues9': [(247, 251, 255), (222, 235, 247), (198, 219, 239), (158, 202, 225), (107, 174, 214), (66, 146, 198), (33, 113, 181), (8, 81, 156), (8, 48, 107)],
1919  'brbg10': [(84, 48, 5), (0, 60, 48), (140, 81, 10), (191, 129, 45), (223, 194, 125), (246, 232, 195), (199, 234, 229), (128, 205, 193), (53, 151, 143), (1, 102, 94)],
1920  'brbg11': [(84, 48, 5), (1, 102, 94), (0, 60, 48), (140, 81, 10), (191, 129, 45), (223, 194, 125), (246, 232, 195), (245, 245, 245), (199, 234, 229), (128, 205, 193), (53, 151, 143)],
1921  'brbg3': [(216, 179, 101), (245, 245, 245), (90, 180, 172)],
1922  'brbg4': [(166, 97, 26), (223, 194, 125), (128, 205, 193), (1, 133, 113)],
1923  'brbg5': [(166, 97, 26), (223, 194, 125), (245, 245, 245), (128, 205, 193), (1, 133, 113)],
1924  'brbg6': [(140, 81, 10), (216, 179, 101), (246, 232, 195), (199, 234, 229), (90, 180, 172), (1, 102, 94)],
1925  'brbg7': [(140, 81, 10), (216, 179, 101), (246, 232, 195), (245, 245, 245), (199, 234, 229), (90, 180, 172), (1, 102, 94)],
1926  'brbg8': [(140, 81, 10), (191, 129, 45), (223, 194, 125), (246, 232, 195), (199, 234, 229), (128, 205, 193), (53, 151, 143), (1, 102, 94)],
1927  'brbg9': [(140, 81, 10), (191, 129, 45), (223, 194, 125), (246, 232, 195), (245, 245, 245), (199, 234, 229), (128, 205, 193), (53, 151, 143), (1, 102, 94)],
1928  'bugn3': [(229, 245, 249), (153, 216, 201), (44, 162, 95)],
1929  'bugn4': [(237, 248, 251), (178, 226, 226), (102, 194, 164), (35, 139, 69)],
1930  'bugn5': [(237, 248, 251), (178, 226, 226), (102, 194, 164), (44, 162, 95), (0, 109, 44)],
1931  'bugn6': [(237, 248, 251), (204, 236, 230), (153, 216, 201), (102, 194, 164), (44, 162, 95), (0, 109, 44)],
1932  'bugn7': [(237, 248, 251), (204, 236, 230), (153, 216, 201), (102, 194, 164), (65, 174, 118), (35, 139, 69), (0, 88, 36)],
1933  'bugn8': [(247, 252, 253), (229, 245, 249), (204, 236, 230), (153, 216, 201), (102, 194, 164), (65, 174, 118), (35, 139, 69), (0, 88, 36)],
1934  'bugn9': [(247, 252, 253), (229, 245, 249), (204, 236, 230), (153, 216, 201), (102, 194, 164), (65, 174, 118), (35, 139, 69), (0, 109, 44), (0, 68, 27)],
1935  'bupu3': [(224, 236, 244), (158, 188, 218), (136, 86, 167)],
1936  'bupu4': [(237, 248, 251), (179, 205, 227), (140, 150, 198), (136, 65, 157)],
1937  'bupu5': [(237, 248, 251), (179, 205, 227), (140, 150, 198), (136, 86, 167), (129, 15, 124)],
1938  'bupu6': [(237, 248, 251), (191, 211, 230), (158, 188, 218), (140, 150, 198), (136, 86, 167), (129, 15, 124)],
1939  'bupu7': [(237, 248, 251), (191, 211, 230), (158, 188, 218), (140, 150, 198), (140, 107, 177), (136, 65, 157), (110, 1, 107)],
1940  'bupu8': [(247, 252, 253), (224, 236, 244), (191, 211, 230), (158, 188, 218), (140, 150, 198), (140, 107, 177), (136, 65, 157), (110, 1, 107)],
1941  'bupu9': [(247, 252, 253), (224, 236, 244), (191, 211, 230), (158, 188, 218), (140, 150, 198), (140, 107, 177), (136, 65, 157), (129, 15, 124), (77, 0, 75)],
1942  'dark23': [(27, 158, 119), (217, 95, 2), (117, 112, 179)],
1943  'dark24': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138)],
1944  'dark25': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30)],
1945  'dark26': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30), (230, 171, 2)],
1946  'dark27': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30), (230, 171, 2), (166, 118, 29)],
1947  'dark28': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30), (230, 171, 2), (166, 118, 29), (102, 102, 102)],
1948  'gnbu3': [(224, 243, 219), (168, 221, 181), (67, 162, 202)],
1949  'gnbu4': [(240, 249, 232), (186, 228, 188), (123, 204, 196), (43, 140, 190)],
1950  'gnbu5': [(240, 249, 232), (186, 228, 188), (123, 204, 196), (67, 162, 202), (8, 104, 172)],
1951  'gnbu6': [(240, 249, 232), (204, 235, 197), (168, 221, 181), (123, 204, 196), (67, 162, 202), (8, 104, 172)],
1952  'gnbu7': [(240, 249, 232), (204, 235, 197), (168, 221, 181), (123, 204, 196), (78, 179, 211), (43, 140, 190), (8, 88, 158)],
1953  'gnbu8': [(247, 252, 240), (224, 243, 219), (204, 235, 197), (168, 221, 181), (123, 204, 196), (78, 179, 211), (43, 140, 190), (8, 88, 158)],
1954  'gnbu9': [(247, 252, 240), (224, 243, 219), (204, 235, 197), (168, 221, 181), (123, 204, 196), (78, 179, 211), (43, 140, 190), (8, 104, 172), (8, 64, 129)],
1955  'greens3': [(229, 245, 224), (161, 217, 155), (49, 163, 84)],
1956  'greens4': [(237, 248, 233), (186, 228, 179), (116, 196, 118), (35, 139, 69)],
1957  'greens5': [(237, 248, 233), (186, 228, 179), (116, 196, 118), (49, 163, 84), (0, 109, 44)],
1958  'greens6': [(237, 248, 233), (199, 233, 192), (161, 217, 155), (116, 196, 118), (49, 163, 84), (0, 109, 44)],
1959  'greens7': [(237, 248, 233), (199, 233, 192), (161, 217, 155), (116, 196, 118), (65, 171, 93), (35, 139, 69), (0, 90, 50)],
1960  'greens8': [(247, 252, 245), (229, 245, 224), (199, 233, 192), (161, 217, 155), (116, 196, 118), (65, 171, 93), (35, 139, 69), (0, 90, 50)],
1961  'greens9': [(247, 252, 245), (229, 245, 224), (199, 233, 192), (161, 217, 155), (116, 196, 118), (65, 171, 93), (35, 139, 69), (0, 109, 44), (0, 68, 27)],
1962  'greys3': [(240, 240, 240), (189, 189, 189), (99, 99, 99)],
1963  'greys4': [(247, 247, 247), (204, 204, 204), (150, 150, 150), (82, 82, 82)],
1964  'greys5': [(247, 247, 247), (204, 204, 204), (150, 150, 150), (99, 99, 99), (37, 37, 37)],
1965  'greys6': [(247, 247, 247), (217, 217, 217), (189, 189, 189), (150, 150, 150), (99, 99, 99), (37, 37, 37)],
1966  'greys7': [(247, 247, 247), (217, 217, 217), (189, 189, 189), (150, 150, 150), (115, 115, 115), (82, 82, 82), (37, 37, 37)],
1967  'greys8': [(255, 255, 255), (240, 240, 240), (217, 217, 217), (189, 189, 189), (150, 150, 150), (115, 115, 115), (82, 82, 82), (37, 37, 37)],
1968  'greys9': [(255, 255, 255), (240, 240, 240), (217, 217, 217), (189, 189, 189), (150, 150, 150), (115, 115, 115), (82, 82, 82), (37, 37, 37), (0, 0, 0)],
1969  'oranges3': [(254, 230, 206), (253, 174, 107), (230, 85, 13)],
1970  'oranges4': [(254, 237, 222), (253, 190, 133), (253, 141, 60), (217, 71, 1)],
1971  'oranges5': [(254, 237, 222), (253, 190, 133), (253, 141, 60), (230, 85, 13), (166, 54, 3)],
1972  'oranges6': [(254, 237, 222), (253, 208, 162), (253, 174, 107), (253, 141, 60), (230, 85, 13), (166, 54, 3)],
1973  'oranges7': [(254, 237, 222), (253, 208, 162), (253, 174, 107), (253, 141, 60), (241, 105, 19), (217, 72, 1), (140, 45, 4)],
1974  'oranges8': [(255, 245, 235), (254, 230, 206), (253, 208, 162), (253, 174, 107), (253, 141, 60), (241, 105, 19), (217, 72, 1), (140, 45, 4)],
1975  'oranges9': [(255, 245, 235), (254, 230, 206), (253, 208, 162), (253, 174, 107), (253, 141, 60), (241, 105, 19), (217, 72, 1), (166, 54, 3), (127, 39, 4)],
1976  'orrd3': [(254, 232, 200), (253, 187, 132), (227, 74, 51)],
1977  'orrd4': [(254, 240, 217), (253, 204, 138), (252, 141, 89), (215, 48, 31)],
1978  'orrd5': [(254, 240, 217), (253, 204, 138), (252, 141, 89), (227, 74, 51), (179, 0, 0)],
1979  'orrd6': [(254, 240, 217), (253, 212, 158), (253, 187, 132), (252, 141, 89), (227, 74, 51), (179, 0, 0)],
1980  'orrd7': [(254, 240, 217), (253, 212, 158), (253, 187, 132), (252, 141, 89), (239, 101, 72), (215, 48, 31), (153, 0, 0)],
1981  'orrd8': [(255, 247, 236), (254, 232, 200), (253, 212, 158), (253, 187, 132), (252, 141, 89), (239, 101, 72), (215, 48, 31), (153, 0, 0)],
1982  'orrd9': [(255, 247, 236), (254, 232, 200), (253, 212, 158), (253, 187, 132), (252, 141, 89), (239, 101, 72), (215, 48, 31), (179, 0, 0), (127, 0, 0)],
1983  'paired10': [(166, 206, 227), (106, 61, 154), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111), (255, 127, 0), (202, 178, 214)],
1984  'paired11': [(166, 206, 227), (106, 61, 154), (255, 255, 153), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111), (255, 127, 0), (202, 178, 214)],
1985  'paired12': [(166, 206, 227), (106, 61, 154), (255, 255, 153), (177, 89, 40), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111), (255, 127, 0), (202, 178, 214)],
1986  'paired3': [(166, 206, 227), (31, 120, 180), (178, 223, 138)],
1987  'paired4': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44)],
1988  'paired5': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153)],
1989  'paired6': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28)],
1990  'paired7': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111)],
1991  'paired8': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111), (255, 127, 0)],
1992  'paired9': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111), (255, 127, 0), (202, 178, 214)],
1993  'pastel13': [(251, 180, 174), (179, 205, 227), (204, 235, 197)],
1994  'pastel14': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228)],
1995  'pastel15': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166)],
1996  'pastel16': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204)],
1997  'pastel17': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204), (229, 216, 189)],
1998  'pastel18': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204), (229, 216, 189), (253, 218, 236)],
1999  'pastel19': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204), (229, 216, 189), (253, 218, 236), (242, 242, 242)],
2000  'pastel23': [(179, 226, 205), (253, 205, 172), (203, 213, 232)],
2001  'pastel24': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228)],
2002  'pastel25': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201)],
2003  'pastel26': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201), (255, 242, 174)],
2004  'pastel27': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201), (255, 242, 174), (241, 226, 204)],
2005  'pastel28': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201), (255, 242, 174), (241, 226, 204), (204, 204, 204)],
2006  'piyg10': [(142, 1, 82), (39, 100, 25), (197, 27, 125), (222, 119, 174), (241, 182, 218), (253, 224, 239), (230, 245, 208), (184, 225, 134), (127, 188, 65), (77, 146, 33)],
2007  'piyg11': [(142, 1, 82), (77, 146, 33), (39, 100, 25), (197, 27, 125), (222, 119, 174), (241, 182, 218), (253, 224, 239), (247, 247, 247), (230, 245, 208), (184, 225, 134), (127, 188, 65)],
2008  'piyg3': [(233, 163, 201), (247, 247, 247), (161, 215, 106)],
2009  'piyg4': [(208, 28, 139), (241, 182, 218), (184, 225, 134), (77, 172, 38)],
2010  'piyg5': [(208, 28, 139), (241, 182, 218), (247, 247, 247), (184, 225, 134), (77, 172, 38)],
2011  'piyg6': [(197, 27, 125), (233, 163, 201), (253, 224, 239), (230, 245, 208), (161, 215, 106), (77, 146, 33)],
2012  'piyg7': [(197, 27, 125), (233, 163, 201), (253, 224, 239), (247, 247, 247), (230, 245, 208), (161, 215, 106), (77, 146, 33)],
2013  'piyg8': [(197, 27, 125), (222, 119, 174), (241, 182, 218), (253, 224, 239), (230, 245, 208), (184, 225, 134), (127, 188, 65), (77, 146, 33)],
2014  'piyg9': [(197, 27, 125), (222, 119, 174), (241, 182, 218), (253, 224, 239), (247, 247, 247), (230, 245, 208), (184, 225, 134), (127, 188, 65), (77, 146, 33)],
2015  'prgn10': [(64, 0, 75), (0, 68, 27), (118, 42, 131), (153, 112, 171), (194, 165, 207), (231, 212, 232), (217, 240, 211), (166, 219, 160), (90, 174, 97), (27, 120, 55)],
2016  'prgn11': [(64, 0, 75), (27, 120, 55), (0, 68, 27), (118, 42, 131), (153, 112, 171), (194, 165, 207), (231, 212, 232), (247, 247, 247), (217, 240, 211), (166, 219, 160), (90, 174, 97)],
2017  'prgn3': [(175, 141, 195), (247, 247, 247), (127, 191, 123)],
2018  'prgn4': [(123, 50, 148), (194, 165, 207), (166, 219, 160), (0, 136, 55)],
2019  'prgn5': [(123, 50, 148), (194, 165, 207), (247, 247, 247), (166, 219, 160), (0, 136, 55)],
2020  'prgn6': [(118, 42, 131), (175, 141, 195), (231, 212, 232), (217, 240, 211), (127, 191, 123), (27, 120, 55)],
2021  'prgn7': [(118, 42, 131), (175, 141, 195), (231, 212, 232), (247, 247, 247), (217, 240, 211), (127, 191, 123), (27, 120, 55)],
2022  'prgn8': [(118, 42, 131), (153, 112, 171), (194, 165, 207), (231, 212, 232), (217, 240, 211), (166, 219, 160), (90, 174, 97), (27, 120, 55)],
2023  'prgn9': [(118, 42, 131), (153, 112, 171), (194, 165, 207), (231, 212, 232), (247, 247, 247), (217, 240, 211), (166, 219, 160), (90, 174, 97), (27, 120, 55)],
2024  'pubu3': [(236, 231, 242), (166, 189, 219), (43, 140, 190)],
2025  'pubu4': [(241, 238, 246), (189, 201, 225), (116, 169, 207), (5, 112, 176)],
2026  'pubu5': [(241, 238, 246), (189, 201, 225), (116, 169, 207), (43, 140, 190), (4, 90, 141)],
2027  'pubu6': [(241, 238, 246), (208, 209, 230), (166, 189, 219), (116, 169, 207), (43, 140, 190), (4, 90, 141)],
2028  'pubu7': [(241, 238, 246), (208, 209, 230), (166, 189, 219), (116, 169, 207), (54, 144, 192), (5, 112, 176), (3, 78, 123)],
2029  'pubu8': [(255, 247, 251), (236, 231, 242), (208, 209, 230), (166, 189, 219), (116, 169, 207), (54, 144, 192), (5, 112, 176), (3, 78, 123)],
2030  'pubu9': [(255, 247, 251), (236, 231, 242), (208, 209, 230), (166, 189, 219), (116, 169, 207), (54, 144, 192), (5, 112, 176), (4, 90, 141), (2, 56, 88)],
2031  'pubugn3': [(236, 226, 240), (166, 189, 219), (28, 144, 153)],
2032  'pubugn4': [(246, 239, 247), (189, 201, 225), (103, 169, 207), (2, 129, 138)],
2033  'pubugn5': [(246, 239, 247), (189, 201, 225), (103, 169, 207), (28, 144, 153), (1, 108, 89)],
2034  'pubugn6': [(246, 239, 247), (208, 209, 230), (166, 189, 219), (103, 169, 207), (28, 144, 153), (1, 108, 89)],
2035  'pubugn7': [(246, 239, 247), (208, 209, 230), (166, 189, 219), (103, 169, 207), (54, 144, 192), (2, 129, 138), (1, 100, 80)],
2036  'pubugn8': [(255, 247, 251), (236, 226, 240), (208, 209, 230), (166, 189, 219), (103, 169, 207), (54, 144, 192), (2, 129, 138), (1, 100, 80)],
2037  'pubugn9': [(255, 247, 251), (236, 226, 240), (208, 209, 230), (166, 189, 219), (103, 169, 207), (54, 144, 192), (2, 129, 138), (1, 108, 89), (1, 70, 54)],
2038  'puor10': [(127, 59, 8), (45, 0, 75), (179, 88, 6), (224, 130, 20), (253, 184, 99), (254, 224, 182), (216, 218, 235), (178, 171, 210), (128, 115, 172), (84, 39, 136)],
2039  'puor11': [(127, 59, 8), (84, 39, 136), (45, 0, 75), (179, 88, 6), (224, 130, 20), (253, 184, 99), (254, 224, 182), (247, 247, 247), (216, 218, 235), (178, 171, 210), (128, 115, 172)],
2040  'puor3': [(241, 163, 64), (247, 247, 247), (153, 142, 195)],
2041  'puor4': [(230, 97, 1), (253, 184, 99), (178, 171, 210), (94, 60, 153)],
2042  'puor5': [(230, 97, 1), (253, 184, 99), (247, 247, 247), (178, 171, 210), (94, 60, 153)],
2043  'puor6': [(179, 88, 6), (241, 163, 64), (254, 224, 182), (216, 218, 235), (153, 142, 195), (84, 39, 136)],
2044  'puor7': [(179, 88, 6), (241, 163, 64), (254, 224, 182), (247, 247, 247), (216, 218, 235), (153, 142, 195), (84, 39, 136)],
2045  'puor8': [(179, 88, 6), (224, 130, 20), (253, 184, 99), (254, 224, 182), (216, 218, 235), (178, 171, 210), (128, 115, 172), (84, 39, 136)],
2046  'puor9': [(179, 88, 6), (224, 130, 20), (253, 184, 99), (254, 224, 182), (247, 247, 247), (216, 218, 235), (178, 171, 210), (128, 115, 172), (84, 39, 136)],
2047  'purd3': [(231, 225, 239), (201, 148, 199), (221, 28, 119)],
2048  'purd4': [(241, 238, 246), (215, 181, 216), (223, 101, 176), (206, 18, 86)],
2049  'purd5': [(241, 238, 246), (215, 181, 216), (223, 101, 176), (221, 28, 119), (152, 0, 67)],
2050  'purd6': [(241, 238, 246), (212, 185, 218), (201, 148, 199), (223, 101, 176), (221, 28, 119), (152, 0, 67)],
2051  'purd7': [(241, 238, 246), (212, 185, 218), (201, 148, 199), (223, 101, 176), (231, 41, 138), (206, 18, 86), (145, 0, 63)],
2052  'purd8': [(247, 244, 249), (231, 225, 239), (212, 185, 218), (201, 148, 199), (223, 101, 176), (231, 41, 138), (206, 18, 86), (145, 0, 63)],
2053  'purd9': [(247, 244, 249), (231, 225, 239), (212, 185, 218), (201, 148, 199), (223, 101, 176), (231, 41, 138), (206, 18, 86), (152, 0, 67), (103, 0, 31)],
2054  'purples3': [(239, 237, 245), (188, 189, 220), (117, 107, 177)],
2055  'purples4': [(242, 240, 247), (203, 201, 226), (158, 154, 200), (106, 81, 163)],
2056  'purples5': [(242, 240, 247), (203, 201, 226), (158, 154, 200), (117, 107, 177), (84, 39, 143)],
2057  'purples6': [(242, 240, 247), (218, 218, 235), (188, 189, 220), (158, 154, 200), (117, 107, 177), (84, 39, 143)],
2058  'purples7': [(242, 240, 247), (218, 218, 235), (188, 189, 220), (158, 154, 200), (128, 125, 186), (106, 81, 163), (74, 20, 134)],
2059  'purples8': [(252, 251, 253), (239, 237, 245), (218, 218, 235), (188, 189, 220), (158, 154, 200), (128, 125, 186), (106, 81, 163), (74, 20, 134)],
2060  'purples9': [(252, 251, 253), (239, 237, 245), (218, 218, 235), (188, 189, 220), (158, 154, 200), (128, 125, 186), (106, 81, 163), (84, 39, 143), (63, 0, 125)],
2061  'rdbu10': [(103, 0, 31), (5, 48, 97), (178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (209, 229, 240), (146, 197, 222), (67, 147, 195), (33, 102, 172)],
2062  'rdbu11': [(103, 0, 31), (33, 102, 172), (5, 48, 97), (178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (247, 247, 247), (209, 229, 240), (146, 197, 222), (67, 147, 195)],
2063  'rdbu3': [(239, 138, 98), (247, 247, 247), (103, 169, 207)],
2064  'rdbu4': [(202, 0, 32), (244, 165, 130), (146, 197, 222), (5, 113, 176)],
2065  'rdbu5': [(202, 0, 32), (244, 165, 130), (247, 247, 247), (146, 197, 222), (5, 113, 176)],
2066  'rdbu6': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (209, 229, 240), (103, 169, 207), (33, 102, 172)],
2067  'rdbu7': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (247, 247, 247), (209, 229, 240), (103, 169, 207), (33, 102, 172)],
2068  'rdbu8': [(178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (209, 229, 240), (146, 197, 222), (67, 147, 195), (33, 102, 172)],
2069  'rdbu9': [(178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (247, 247, 247), (209, 229, 240), (146, 197, 222), (67, 147, 195), (33, 102, 172)],
2070  'rdgy10': [(103, 0, 31), (26, 26, 26), (178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (224, 224, 224), (186, 186, 186), (135, 135, 135), (77, 77, 77)],
2071  'rdgy11': [(103, 0, 31), (77, 77, 77), (26, 26, 26), (178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (255, 255, 255), (224, 224, 224), (186, 186, 186), (135, 135, 135)],
2072  'rdgy3': [(239, 138, 98), (255, 255, 255), (153, 153, 153)],
2073  'rdgy4': [(202, 0, 32), (244, 165, 130), (186, 186, 186), (64, 64, 64)],
2074  'rdgy5': [(202, 0, 32), (244, 165, 130), (255, 255, 255), (186, 186, 186), (64, 64, 64)],
2075  'rdgy6': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (224, 224, 224), (153, 153, 153), (77, 77, 77)],
2076  'rdgy7': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (255, 255, 255), (224, 224, 224), (153, 153, 153), (77, 77, 77)],
2077  'rdgy8': [(178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (224, 224, 224), (186, 186, 186), (135, 135, 135), (77, 77, 77)],
2078  'rdgy9': [(178, 24, 43), (214, 96, 77), (244, 165, 130), (253, 219, 199), (255, 255, 255), (224, 224, 224), (186, 186, 186), (135, 135, 135), (77, 77, 77)],
2079  'rdpu3': [(253, 224, 221), (250, 159, 181), (197, 27, 138)],
2080  'rdpu4': [(254, 235, 226), (251, 180, 185), (247, 104, 161), (174, 1, 126)],
2081  'rdpu5': [(254, 235, 226), (251, 180, 185), (247, 104, 161), (197, 27, 138), (122, 1, 119)],
2082  'rdpu6': [(254, 235, 226), (252, 197, 192), (250, 159, 181), (247, 104, 161), (197, 27, 138), (122, 1, 119)],
2083  'rdpu7': [(254, 235, 226), (252, 197, 192), (250, 159, 181), (247, 104, 161), (221, 52, 151), (174, 1, 126), (122, 1, 119)],
2084  'rdpu8': [(255, 247, 243), (253, 224, 221), (252, 197, 192), (250, 159, 181), (247, 104, 161), (221, 52, 151), (174, 1, 126), (122, 1, 119)],
2085  'rdpu9': [(255, 247, 243), (253, 224, 221), (252, 197, 192), (250, 159, 181), (247, 104, 161), (221, 52, 151), (174, 1, 126), (122, 1, 119), (73, 0, 106)],
2086  'rdylbu10': [(165, 0, 38), (49, 54, 149), (215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 144), (224, 243, 248), (171, 217, 233), (116, 173, 209), (69, 117, 180)],
2087  'rdylbu11': [(165, 0, 38), (69, 117, 180), (49, 54, 149), (215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 144), (255, 255, 191), (224, 243, 248), (171, 217, 233), (116, 173, 209)],
2088  'rdylbu3': [(252, 141, 89), (255, 255, 191), (145, 191, 219)],
2089  'rdylbu4': [(215, 25, 28), (253, 174, 97), (171, 217, 233), (44, 123, 182)],
2090  'rdylbu5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (171, 217, 233), (44, 123, 182)],
2091  'rdylbu6': [(215, 48, 39), (252, 141, 89), (254, 224, 144), (224, 243, 248), (145, 191, 219), (69, 117, 180)],
2092  'rdylbu7': [(215, 48, 39), (252, 141, 89), (254, 224, 144), (255, 255, 191), (224, 243, 248), (145, 191, 219), (69, 117, 180)],
2093  'rdylbu8': [(215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 144), (224, 243, 248), (171, 217, 233), (116, 173, 209), (69, 117, 180)],
2094  'rdylbu9': [(215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 144), (255, 255, 191), (224, 243, 248), (171, 217, 233), (116, 173, 209), (69, 117, 180)],
2095  'rdylgn10': [(165, 0, 38), (0, 104, 55), (215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 139), (217, 239, 139), (166, 217, 106), (102, 189, 99), (26, 152, 80)],
2096  'rdylgn11': [(165, 0, 38), (26, 152, 80), (0, 104, 55), (215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 139), (255, 255, 191), (217, 239, 139), (166, 217, 106), (102, 189, 99)],
2097  'rdylgn3': [(252, 141, 89), (255, 255, 191), (145, 207, 96)],
2098  'rdylgn4': [(215, 25, 28), (253, 174, 97), (166, 217, 106), (26, 150, 65)],
2099  'rdylgn5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (166, 217, 106), (26, 150, 65)],
2100  'rdylgn6': [(215, 48, 39), (252, 141, 89), (254, 224, 139), (217, 239, 139), (145, 207, 96), (26, 152, 80)],
2101  'rdylgn7': [(215, 48, 39), (252, 141, 89), (254, 224, 139), (255, 255, 191), (217, 239, 139), (145, 207, 96), (26, 152, 80)],
2102  'rdylgn8': [(215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 139), (217, 239, 139), (166, 217, 106), (102, 189, 99), (26, 152, 80)],
2103  'rdylgn9': [(215, 48, 39), (244, 109, 67), (253, 174, 97), (254, 224, 139), (255, 255, 191), (217, 239, 139), (166, 217, 106), (102, 189, 99), (26, 152, 80)],
2104  'reds3': [(254, 224, 210), (252, 146, 114), (222, 45, 38)],
2105  'reds4': [(254, 229, 217), (252, 174, 145), (251, 106, 74), (203, 24, 29)],
2106  'reds5': [(254, 229, 217), (252, 174, 145), (251, 106, 74), (222, 45, 38), (165, 15, 21)],
2107  'reds6': [(254, 229, 217), (252, 187, 161), (252, 146, 114), (251, 106, 74), (222, 45, 38), (165, 15, 21)],
2108  'reds7': [(254, 229, 217), (252, 187, 161), (252, 146, 114), (251, 106, 74), (239, 59, 44), (203, 24, 29), (153, 0, 13)],
2109  'reds8': [(255, 245, 240), (254, 224, 210), (252, 187, 161), (252, 146, 114), (251, 106, 74), (239, 59, 44), (203, 24, 29), (153, 0, 13)],
2110  'reds9': [(255, 245, 240), (254, 224, 210), (252, 187, 161), (252, 146, 114), (251, 106, 74), (239, 59, 44), (203, 24, 29), (165, 15, 21), (103, 0, 13)],
2111  'set13': [(228, 26, 28), (55, 126, 184), (77, 175, 74)],
2112  'set14': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163)],
2113  'set15': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0)],
2114  'set16': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51)],
2115  'set17': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51), (166, 86, 40)],
2116  'set18': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51), (166, 86, 40), (247, 129, 191)],
2117  'set19': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51), (166, 86, 40), (247, 129, 191), (153, 153, 153)],
2118  'set23': [(102, 194, 165), (252, 141, 98), (141, 160, 203)],
2119  'set24': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195)],
2120  'set25': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84)],
2121  'set26': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84), (255, 217, 47)],
2122  'set27': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84), (255, 217, 47), (229, 196, 148)],
2123  'set28': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84), (255, 217, 47), (229, 196, 148), (179, 179, 179)],
2124  'set310': [(141, 211, 199), (188, 128, 189), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105), (252, 205, 229), (217, 217, 217)],
2125  'set311': [(141, 211, 199), (188, 128, 189), (204, 235, 197), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105), (252, 205, 229), (217, 217, 217)],
2126  'set312': [(141, 211, 199), (188, 128, 189), (204, 235, 197), (255, 237, 111), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105), (252, 205, 229), (217, 217, 217)],
2127  'set33': [(141, 211, 199), (255, 255, 179), (190, 186, 218)],
2128  'set34': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114)],
2129  'set35': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211)],
2130  'set36': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98)],
2131  'set37': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105)],
2132  'set38': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105), (252, 205, 229)],
2133  'set39': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105), (252, 205, 229), (217, 217, 217)],
2134  'spectral10': [(158, 1, 66), (94, 79, 162), (213, 62, 79), (244, 109, 67), (253, 174, 97), (254, 224, 139), (230, 245, 152), (171, 221, 164), (102, 194, 165), (50, 136, 189)],
2135  'spectral11': [(158, 1, 66), (50, 136, 189), (94, 79, 162), (213, 62, 79), (244, 109, 67), (253, 174, 97), (254, 224, 139), (255, 255, 191), (230, 245, 152), (171, 221, 164), (102, 194, 165)],
2136  'spectral3': [(252, 141, 89), (255, 255, 191), (153, 213, 148)],
2137  'spectral4': [(215, 25, 28), (253, 174, 97), (171, 221, 164), (43, 131, 186)],
2138  'spectral5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (171, 221, 164), (43, 131, 186)],
2139  'spectral6': [(213, 62, 79), (252, 141, 89), (254, 224, 139), (230, 245, 152), (153, 213, 148), (50, 136, 189)],
2140  'spectral7': [(213, 62, 79), (252, 141, 89), (254, 224, 139), (255, 255, 191), (230, 245, 152), (153, 213, 148), (50, 136, 189)],
2141  'spectral8': [(213, 62, 79), (244, 109, 67), (253, 174, 97), (254, 224, 139), (230, 245, 152), (171, 221, 164), (102, 194, 165), (50, 136, 189)],
2142  'spectral9': [(213, 62, 79), (244, 109, 67), (253, 174, 97), (254, 224, 139), (255, 255, 191), (230, 245, 152), (171, 221, 164), (102, 194, 165), (50, 136, 189)],
2143  'ylgn3': [(247, 252, 185), (173, 221, 142), (49, 163, 84)],
2144  'ylgn4': [(255, 255, 204), (194, 230, 153), (120, 198, 121), (35, 132, 67)],
2145  'ylgn5': [(255, 255, 204), (194, 230, 153), (120, 198, 121), (49, 163, 84), (0, 104, 55)],
2146  'ylgn6': [(255, 255, 204), (217, 240, 163), (173, 221, 142), (120, 198, 121), (49, 163, 84), (0, 104, 55)],
2147  'ylgn7': [(255, 255, 204), (217, 240, 163), (173, 221, 142), (120, 198, 121), (65, 171, 93), (35, 132, 67), (0, 90, 50)],
2148  'ylgn8': [(255, 255, 229), (247, 252, 185), (217, 240, 163), (173, 221, 142), (120, 198, 121), (65, 171, 93), (35, 132, 67), (0, 90, 50)],
2149  'ylgn9': [(255, 255, 229), (247, 252, 185), (217, 240, 163), (173, 221, 142), (120, 198, 121), (65, 171, 93), (35, 132, 67), (0, 104, 55), (0, 69, 41)],
2150  'ylgnbu3': [(237, 248, 177), (127, 205, 187), (44, 127, 184)],
2151  'ylgnbu4': [(255, 255, 204), (161, 218, 180), (65, 182, 196), (34, 94, 168)],
2152  'ylgnbu5': [(255, 255, 204), (161, 218, 180), (65, 182, 196), (44, 127, 184), (37, 52, 148)],
2153  'ylgnbu6': [(255, 255, 204), (199, 233, 180), (127, 205, 187), (65, 182, 196), (44, 127, 184), (37, 52, 148)],
2154  'ylgnbu7': [(255, 255, 204), (199, 233, 180), (127, 205, 187), (65, 182, 196), (29, 145, 192), (34, 94, 168), (12, 44, 132)],
2155  'ylgnbu8': [(255, 255, 217), (237, 248, 177), (199, 233, 180), (127, 205, 187), (65, 182, 196), (29, 145, 192), (34, 94, 168), (12, 44, 132)],
2156  'ylgnbu9': [(255, 255, 217), (237, 248, 177), (199, 233, 180), (127, 205, 187), (65, 182, 196), (29, 145, 192), (34, 94, 168), (37, 52, 148), (8, 29, 88)],
2157  'ylorbr3': [(255, 247, 188), (254, 196, 79), (217, 95, 14)],
2158  'ylorbr4': [(255, 255, 212), (254, 217, 142), (254, 153, 41), (204, 76, 2)],
2159  'ylorbr5': [(255, 255, 212), (254, 217, 142), (254, 153, 41), (217, 95, 14), (153, 52, 4)],
2160  'ylorbr6': [(255, 255, 212), (254, 227, 145), (254, 196, 79), (254, 153, 41), (217, 95, 14), (153, 52, 4)],
2161  'ylorbr7': [(255, 255, 212), (254, 227, 145), (254, 196, 79), (254, 153, 41), (236, 112, 20), (204, 76, 2), (140, 45, 4)],
2162  'ylorbr8': [(255, 255, 229), (255, 247, 188), (254, 227, 145), (254, 196, 79), (254, 153, 41), (236, 112, 20), (204, 76, 2), (140, 45, 4)],
2163  'ylorbr9': [(255, 255, 229), (255, 247, 188), (254, 227, 145), (254, 196, 79), (254, 153, 41), (236, 112, 20), (204, 76, 2), (153, 52, 4), (102, 37, 6)],
2164  'ylorrd3': [(255, 237, 160), (254, 178, 76), (240, 59, 32)],
2165  'ylorrd4': [(255, 255, 178), (254, 204, 92), (253, 141, 60), (227, 26, 28)],
2166  'ylorrd5': [(255, 255, 178), (254, 204, 92), (253, 141, 60), (240, 59, 32), (189, 0, 38)],
2167  'ylorrd6': [(255, 255, 178), (254, 217, 118), (254, 178, 76), (253, 141, 60), (240, 59, 32), (189, 0, 38)],
2168  'ylorrd7': [(255, 255, 178), (254, 217, 118), (254, 178, 76), (253, 141, 60), (252, 78, 42), (227, 26, 28), (177, 0, 38)],
2169  'ylorrd8': [(255, 255, 204), (255, 237, 160), (254, 217, 118), (254, 178, 76), (253, 141, 60), (252, 78, 42), (227, 26, 28), (177, 0, 38)],
2170 }
2171 
2172 
2173 if __name__ == '__main__':
2174  main()
def handle_text(self, x, y, j, w, t)
Definition: xdot.py:644
def draw(self, cr, highlight=False)
Definition: xdot.py:111
def set_dotcode(self, dotcode, filename='< stdin >', refresh=False)
Definition: xdot.py:1787
def __init__(self, item, url, highlight=None)
Definition: xdot.py:310
def consume(self, text)
Definition: xdot.py:794
def on_open(self, action)
Definition: xdot.py:1811
def on_key_press_event(self, widget, event)
Definition: xdot.py:1574
def draw(self, cr, highlight=False)
Definition: xdot.py:303
def __init__(self, type, text, line, col)
Definition: xdot.py:721
def on_motion_notify(self, event)
Definition: xdot.py:1279
def draw(self, cr, highlight=False)
Definition: xdot.py:277
def handle_font(self, size, name)
Definition: xdot.py:640
def set_filter(self, filter)
Definition: xdot.py:1784
def __init__(self, pen, points, filled=False)
Definition: xdot.py:226
def __init__(self)
Definition: xdot.py:53
def on_zoom_fit(self, action)
Definition: xdot.py:1566
def update(self, filename)
Definition: xdot.py:1772
def handle_polygon(self, points, filled=False)
Definition: xdot.py:662
def __init__(self, width=1, height=1, shapes=(), nodes=(), edges=(), subgraph_shapes={})
Definition: xdot.py:402
def animate_to(self, x, y)
Definition: xdot.py:1682
def match(self, type)
Definition: xdot.py:819
def __init__(self, dot_widget, target_x, target_y)
Definition: xdot.py:1228
def parse_node_pos(self, pos)
Definition: xdot.py:1155
def __init__(self, dot_widget, target_x, target_y)
Definition: xdot.py:1245
def on_zoom_in(self, action)
Definition: xdot.py:1560
def get_jump(self, x, y)
Definition: xdot.py:1700
def square_distance(x1, y1, x2, y2)
Definition: xdot.py:375
def __init__(self, xdotcode)
Definition: xdot.py:1068
def transform(self, x, y)
Definition: xdot.py:1172
def on_area_motion_notify(self, area, event)
Definition: xdot.py:1674
def drag(self, deltax, deltay)
Definition: xdot.py:1359
def get_url(self, x, y)
Definition: xdot.py:336
def draw(self, cr, highlight=False)
Definition: xdot.py:206
def handle_ellipse(self, x0, y0, w, h, filled=False)
Definition: xdot.py:647
def __init__(self, pen, x, y, j, w, t)
Definition: xdot.py:102
def filter(self, type, text)
Definition: xdot.py:918
def highlighted(self)
Definition: xdot.py:68
def handle_color(self, color, filled=False)
Definition: xdot.py:625
def on_button_release(self, event)
Definition: xdot.py:1290
def get_jump(self, x, y)
Definition: xdot.py:339
def zoom_image(self, zoom_ratio, center=False, pos=None)
Definition: xdot.py:1516
def next(self, buf, pos)
Definition: xdot.py:703
def __init__(self, pen, x0, y0, w, h, filled=False)
Definition: xdot.py:197
def handle_line(self, points)
Definition: xdot.py:653
def do_expose_event(self, event)
Definition: xdot.py:1477
def __init__(self, src, dst, points, shapes, url)
Definition: xdot.py:383
def draw(self, cr, highlight=False)
Definition: xdot.py:232
def handle_graph(self, attrs)
Definition: xdot.py:1056
def handle_edge(self, src_id, dst_id, attrs)
Definition: xdot.py:1062
def get_drag_action(self, event)
Definition: xdot.py:1611
def skip(self, type)
Definition: xdot.py:827
def drag(self, deltax, deltay)
Definition: xdot.py:1301
def set_current_pos(self, x, y)
Definition: xdot.py:1506
def __init__(self, buf=None, pos=0, filename=None, fp=None)
Definition: xdot.py:736
def select_pen(self, highlight)
Definition: xdot.py:85
def on_zoom_out(self, action)
Definition: xdot.py:1563
def draw(self, cr, highlight=False)
Definition: xdot.py:81
def __init__(self, item, x, y, highlight=None, url=None)
Definition: xdot.py:320
def zoom_to_area(self, x1, y1, x2, y2)
Definition: xdot.py:1531
def __init__(self, dot_widget)
Definition: xdot.py:1183
def parse_edge_pos(self, pos)
Definition: xdot.py:1159
def set_filter(self, filter)
Definition: xdot.py:1424
def get_url(self, x, y)
Definition: xdot.py:430
def on_reload(self, action)
Definition: xdot.py:1834
def handle_graph(self, attrs)
Definition: xdot.py:1079
def __init__(self, shapes)
Definition: xdot.py:299
def __init__(self, pen, points, filled=False)
Definition: xdot.py:271
def on_area_button_release(self, area, event)
Definition: xdot.py:1645
def handle_bezier(self, points, filled=False)
Definition: xdot.py:656
def get_jump(self, x, y)
Definition: xdot.py:392
def draw(self, cr, highlight_items=None)
Definition: xdot.py:415
def handle_linewidth(self, linewidth)
Definition: xdot.py:631
def draw(self, cr, highlight=False)
Definition: xdot.py:257
def __init__(self, pen, points)
Definition: xdot.py:252
def __init__(self, msg=None, filename=None, line=None, col=None)
Definition: xdot.py:675
def __init__(self, lexer)
Definition: xdot.py:815
def is_inside(self, x, y)
Definition: xdot.py:358
def drag(self, deltax, deltay)
Definition: xdot.py:1348
def get_url(self, x, y)
Definition: xdot.py:1696
def handle_linestyle(self, style)
Definition: xdot.py:634
def handle_edge(self, src_id, dst_id, attrs)
Definition: xdot.py:1127
def get_jump(self, x, y)
Definition: xdot.py:437
def __init__(self, lexer)
Definition: xdot.py:944
def set_dotcode(self, dotcode, filename='< stdin >')
Definition: xdot.py:1427
def set_xdotcode(self, xdotcode)
Definition: xdot.py:1462
def handle_node(self, id, attrs)
Definition: xdot.py:1059
def get_url(self, x, y)
Definition: xdot.py:361
def on_area_size_allocate(self, area, allocation)
Definition: xdot.py:1678
def set_xdotcode(self, xdotcode, filename='< stdin >')
Definition: xdot.py:1793
def window2graph(self, x, y)
Definition: xdot.py:1686
def drag(self, deltax, deltay)
Definition: xdot.py:1335
def on_button_press(self, event)
Definition: xdot.py:1274
def on_area_scroll_event(self, area, event)
Definition: xdot.py:1663
def __init__(self, x, y, w, h, shapes, url)
Definition: xdot.py:345
def set_highlight(self, items)
Definition: xdot.py:1511
def __init__(self, dot_widget)
Definition: xdot.py:1271
def get_jump(self, x, y)
Definition: xdot.py:369
def handle_node(self, id, attrs)
Definition: xdot.py:1107
def open_file(self, filename, refresh=False)
Definition: xdot.py:1798
def __init__(self, shapes)
Definition: xdot.py:333
def on_motion_notify(self, event)
Definition: xdot.py:1313
def is_click(self, event, click_fuzz=4, click_timeout=1.0)
Definition: xdot.py:1633
def __init__(self, parser, buf)
Definition: xdot.py:455
def on_area_button_press(self, area, event)
Definition: xdot.py:1622
def on_zoom_100(self, action)
Definition: xdot.py:1569


smach_viewer
Author(s): Jonathan Bohren
autogenerated on Mon Jun 10 2019 13:13:42