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


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