xdot_qt.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2008 Jose Fonseca
00004 #
00005 # This program is free software: you can redistribute it and/or modify it
00006 # under the terms of the GNU Lesser General Public License as published
00007 # by the Free Software Foundation, either version 3 of the License, or
00008 # (at your option) any later version.
00009 #
00010 # This program is distributed in the hope that it will be useful,
00011 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 # GNU Lesser General Public License for more details.
00014 #
00015 # You should have received a copy of the GNU Lesser General Public License
00016 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017 #
00018 # QT Version adapted by Alex Bravo
00019 
00020 '''Visualize dot graphs via the xdot format.'''
00021 
00022 __author__ = "Jose Fonseca"
00023 
00024 __version__ = "0.4"
00025 
00026 import os
00027 import sys
00028 import subprocess
00029 import math
00030 import colorsys
00031 import time
00032 import re
00033 
00034 #from PySide.QtCore import *
00035 #from PySide.QtGui import *
00036 try:
00037     from PyQt4 import *
00038     from PyQt4.QtCore import *
00039     from PyQt4.QtGui import *
00040 except:
00041     from PyQt5 import *
00042     from PyQt5.QtCore import *
00043     from PyQt5.QtGui import *
00044     from python_qt_binding.QtWidgets import QWidget, QMainWindow
00045 
00046 #from python_qt_binding import  *
00047 #from python_qt_binding.QtCore import  *
00048 #from python_qt_binding.QtGui import  *
00049 
00050 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00051 #-- Drawing Classes --#
00052 
00053 class Pen:
00054     """Store pen attributes."""
00055 
00056     def __init__(self):
00057         # set default attributes
00058         self.color = (0.0, 0.0, 0.0, 1.0)
00059         self.fillcolor = (0.0, 0.0, 0.0, 1.0)
00060         self.linewidth = 1.0
00061         self.fontsize = 14.0
00062         self.fontname = "Times-Roman"
00063         self.dash = Qt.SolidLine
00064 
00065     def copy(self):
00066         """Create a copy of this pen."""
00067         pen = Pen()
00068         pen.__dict__ = self.__dict__.copy()
00069         return pen
00070 
00071     def highlighted(self):
00072         pen = self.copy()
00073         pen.color = (1, 0, 0, 1)
00074         pen.fillcolor = (1, .8, .8, 1)
00075         return pen
00076 
00077 class Shape:
00078     """Abstract base class for all the drawing shapes."""
00079 
00080     def __init__(self):
00081         pass
00082 
00083     def draw(self, cr, highlight=False):
00084         """Draw this shape with the given cairo context"""
00085         raise NotImplementedError
00086 
00087     def select_pen(self, highlight):
00088         if highlight:
00089             if not hasattr(self, 'highlight_pen'):
00090                 self.highlight_pen = self.pen.highlighted()
00091             return self.highlight_pen
00092         else:
00093             return self.pen
00094 
00095 class TextShape(Shape):
00096     """Used to draw a text shape with a QPainter"""
00097 
00098     LEFT, CENTER, RIGHT = -1, 0, 1
00099 
00100     def __init__(self, pen, x, y, j, w, t):
00101         Shape.__init__(self)
00102         self.pen = pen.copy()
00103         self.x = x
00104         self.y = y
00105         self.j = j  #AB allignment? JRB: height.
00106         self.w = w
00107         self.t = t
00108 
00109     def draw(self, painter, highlight=False):
00110         pen = self.select_pen(highlight)
00111         painter.setPen(QColor.fromRgbF(*pen.color))
00112         font = QFont(self.pen.fontname)
00113 
00114         fontMetrics = QFontMetrics(QFont(self.pen.fontname))
00115         scale = float(fontMetrics.width(self.t)) / float(self.w)
00116 
00117         if scale < 1.0 or scale > 1.0:
00118             font.setPointSizeF(font.pointSizeF()/scale);
00119 
00120         painter.setFont(font)#, self.pen.fontsize)) #AB simply setting font size doesn't work
00121         painter.drawText(
00122                 self.x - self.w / 2.0,
00123                 self.y,
00124                 self.t)
00125         pass
00126 
00127 class EllipseShape(Shape):
00128     """Used to draw an ellipse shape with a QPainter"""
00129     def __init__(self, pen, x0, y0, w, h, filled=False):
00130         Shape.__init__(self)
00131         self.pen = pen.copy()
00132         self.x0 = x0
00133         self.y0 = y0
00134         self.w = w
00135         self.h = h
00136         self.filled = filled
00137 
00138     def draw(self, painter, highlight=False):
00139         painter.save()
00140         pen = self.select_pen(highlight)
00141         if self.filled:
00142             painter.setPen(QColor.fromRgbF(*pen.fillcolor))
00143             painter.setBrush(QColor.fromRgbF(*pen.fillcolor))
00144         else:
00145             painter.setPen(QPen(QBrush(QColor.fromRgbF(*pen.color)), pen.linewidth, pen.dash))
00146             painter.setBrush(Qt.NoBrush)
00147         painter.drawEllipse(self.x0 - self.w, self.y0 - self.h,  self.w * 2,  self.h * 2)
00148         painter.restore()
00149 
00150 class PolygonShape(Shape):
00151     """Used to draw a polygon with QPainter."""
00152 
00153     def __init__(self, pen, points, filled=False):
00154         Shape.__init__(self)
00155         self.pen = pen.copy()
00156         self.points = points
00157         self.filled = filled
00158 
00159     def draw(self, painter, highlight=False):
00160 
00161         polygon_points = QPolygonF()
00162         for x, y in self.points:
00163             polygon_points.append (QPointF(x, y))
00164 
00165         pen = self.select_pen(highlight)
00166         painter.save()
00167         if self.filled:
00168             painter.setPen(QColor.fromRgbF(*pen.fillcolor))
00169             painter.setBrush(QColor.fromRgbF(*pen.fillcolor))
00170         else:
00171             painter.setPen(QPen(QBrush(QColor.fromRgbF(*pen.color)), pen.linewidth,
00172                                             pen.dash, Qt.SquareCap, Qt.MiterJoin))
00173             painter.setBrush(Qt.NoBrush)
00174 
00175         painter.drawPolygon(polygon_points)
00176         painter.restore()
00177 
00178 class LineShape(Shape):
00179     """Used to draw a line with QPainter."""
00180 
00181     def __init__(self, pen, points):
00182         Shape.__init__(self)
00183         self.pen = pen.copy()
00184         self.points = points
00185 
00186     def draw(self, painter, highlight=False):
00187         pen = self.select_pen(highlight)
00188         painter.setPen(QPen(QBrush(QColor.fromRgbF(*pen.color)), pen.linewidth,
00189                                             pen.dash, Qt.SquareCap, Qt.MiterJoin))
00190 
00191         x0, y0 = self.points[0]
00192         for x1, y1 in self.points[1:]:
00193             painter.drawLine(QPointF(x0, y0),  QPointF(x1, y1))
00194             x0 = x1
00195             y0 = y1
00196 
00197 class BezierShape(Shape):
00198     """Used to draw a bezier curve with QPainter."""
00199 
00200     def __init__(self, pen, points, filled=False):
00201         Shape.__init__(self)
00202         self.pen = pen.copy()
00203         self.points = points
00204         self.filled = filled
00205 
00206     def draw(self, painter, highlight=False):
00207         painter_path = QPainterPath()
00208         painter_path.moveTo(QPointF(*self.points[0]))
00209         for i in xrange(1, len(self.points), 3):
00210             painter_path.cubicTo(
00211                 QPointF(*self.points[i]),
00212                 QPointF(*self.points[i + 1]),
00213                 QPointF(*self.points[i + 2]))
00214         pen = self.select_pen(highlight)
00215         qpen = QPen()
00216         if self.filled:
00217             brush = QBrush()
00218             brush.setColor(QColor.fromRgbF(*pen.fillcolor))
00219             brush.setStyle(Qt.SolidPattern)
00220             painter.setBrush(brush)
00221             #qpen.setCapStyle(Qt.RoundCap)
00222             #qpen.setJoinStyle(Qt.RoundJoin)
00223             #painter_path.setFillRule(Qt.OddEvenFill)
00224         else:
00225             painter.setBrush(Qt.NoBrush)
00226 
00227         qpen.setStyle(pen.dash)
00228         qpen.setWidth(pen.linewidth)
00229         qpen.setColor(QColor.fromRgbF(*pen.color))
00230         painter.setPen(qpen)
00231         painter.drawPath(painter_path)
00232 
00233 class CompoundShape(Shape):
00234     """Used to draw a set of shapes with QPainter."""
00235 
00236     def __init__(self, shapes):
00237         Shape.__init__(self)
00238         self.shapes = shapes
00239 
00240     def draw(self, cr, highlight=False):
00241         for shape in self.shapes:
00242             shape.draw(cr, highlight=highlight)
00243 
00244 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00245 #-- Metadata Classes --#
00246 
00247 class Url(object):
00248     """Represents a graphviz URL."""
00249 
00250     def __init__(self, item, url, highlight=None):
00251         self.item = item
00252         self.url = url
00253         if highlight is None:
00254             highlight = set([item])
00255         self.highlight = highlight
00256 
00257 class Jump(object):
00258     """Represents a jump to another node's position on the canvas."""
00259 
00260     def __init__(self, item, x, y, highlight=None, url=None):
00261         self.item = item
00262         self.x = x
00263         self.y = y
00264         if highlight is None:
00265             highlight = set([item])
00266         self.highlight = highlight
00267         self.url = url
00268 
00269 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00270 #-- Graph Representation Classes --#
00271 
00272 class Element(CompoundShape):
00273     """Base class for graph nodes and edges."""
00274 
00275     def __init__(self, shapes):
00276         CompoundShape.__init__(self, shapes)
00277 
00278     def get_url(self, x, y):
00279         return None
00280 
00281     def get_jump(self, x, y):
00282         return None
00283 
00284 
00285 class Node(Element):
00286     """An abstract node in the graph, it's spatial location, and it's visual representation."""
00287 
00288     def __init__(self, x, y, w, h, shapes, url):
00289         Element.__init__(self, shapes)
00290 
00291         self.x = x
00292         self.y = y
00293 
00294         self.x1 = x - 0.5*w
00295         self.y1 = y - 0.5*h
00296         self.x2 = x + 0.5*w
00297         self.y2 = y + 0.5*h
00298 
00299         self.url = url
00300 
00301     def is_inside(self, x, y):
00302         """Used to check for 2D-picking via the mouse.
00303         param x: The x position on the canvas
00304         param y: The y position on the canvas
00305         """
00306         return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2
00307 
00308     def get_url(self, x, y):
00309         """Get the elemnt's metadata."""
00310         if self.url is None:
00311             return None
00312         if self.is_inside(x, y):
00313             return Url(self, self.url)
00314         return None
00315 
00316     def get_jump(self, x, y):
00317         if self.is_inside(x, y):
00318             return Jump(self, self.x, self.y)
00319         return None
00320 
00321 
00322 def square_distance(x1, y1, x2, y2):
00323     deltax = x2 - x1
00324     deltay = y2 - y1
00325     return deltax*deltax + deltay*deltay
00326 
00327 
00328 class Edge(Element):
00329 
00330     def __init__(self, src, dst, points, shapes, url):
00331         Element.__init__(self, shapes)
00332         self.src = src
00333         self.dst = dst
00334         self.points = points
00335         self.url = url
00336 
00337     RADIUS = 10
00338 
00339     def get_jump(self, x, y):
00340         if square_distance(x, y, *self.points[0]) <= self.RADIUS*self.RADIUS:
00341             return Jump(self, self.dst.x, self.dst.y, highlight=set([self, self.dst]),url=self.url)
00342         if square_distance(x, y, *self.points[-1]) <= self.RADIUS*self.RADIUS:
00343             return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src]),url=self.url)
00344         return None
00345 
00346 
00347 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00348 class Graph(Shape):
00349 
00350     def __init__(self, width=1, height=1, shapes=(), nodes=(), edges=(), subgraph_shapes={}):
00351         Shape.__init__(self)
00352 
00353         self.width = width
00354         self.height = height
00355         self.shapes = shapes
00356         self.nodes = nodes
00357         self.edges = edges
00358         self.subgraph_shapes = subgraph_shapes
00359 
00360     def get_size(self):
00361         return self.width, self.height
00362 
00363     def draw(self, cr, highlight_items=None):
00364         if highlight_items is None:
00365             highlight_items = ()
00366         for shape in self.shapes:
00367             shape.draw(cr)
00368         for edge in self.edges:
00369             edge.draw(cr, highlight=(edge in highlight_items))
00370         for node in self.nodes:
00371             node.draw(cr, highlight=(node in highlight_items))
00372 
00373     def get_url(self, x, y):
00374         for node in self.nodes:
00375             url = node.get_url(x, y)
00376             if url is not None:
00377                 return url
00378         return None
00379 
00380     def get_jump(self, x, y):
00381         for edge in self.edges:
00382             jump = edge.get_jump(x, y)
00383             if jump is not None:
00384                 return jump
00385         for node in self.nodes:
00386             jump = node.get_jump(x, y)
00387             if jump is not None:
00388                 return jump
00389         return None
00390 
00391 
00392 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00393 class XDotAttrParser:
00394     """Parser for xdot drawing attributes.
00395     See also:
00396     - http://www.graphviz.org/doc/info/output.html#d:xdot
00397     """
00398 
00399     def __init__(self, parser, buf):
00400         self.parser = parser
00401         self.buf = self.unescape(buf)
00402         self.pos = 0
00403 
00404         self.pen = Pen()
00405         self.shapes = []
00406 
00407     def __nonzero__(self):
00408         return self.pos < len(self.buf)
00409 
00410     def unescape(self, buf):
00411         buf = buf.replace('\\"', '"')
00412         buf = buf.replace('\\n', '\n')
00413         return buf
00414 
00415     def read_code(self):
00416         pos = self.buf.find(" ", self.pos)
00417         res = self.buf[self.pos:pos]
00418         self.pos = pos + 1
00419         while self.pos < len(self.buf) and self.buf[self.pos].isspace():
00420             self.pos += 1
00421         return res
00422 
00423     def read_number(self):
00424         return int(float(self.read_code()))
00425 
00426     def read_float(self):
00427         return float(self.read_code())
00428 
00429     def read_point(self):
00430         x = self.read_number()
00431         y = self.read_number()
00432         return self.transform(x, y)
00433 
00434     def read_text(self):
00435         num = self.read_number()
00436         pos = self.buf.find("-", self.pos) + 1
00437         self.pos = pos + num
00438         res = self.buf[pos:self.pos]
00439         while self.pos < len(self.buf) and self.buf[self.pos].isspace():
00440             self.pos += 1
00441         return res
00442 
00443     def read_polygon(self):
00444         n = self.read_number()
00445         p = []
00446 #        p = QPointF[]
00447         for i in range(n):
00448             x, y = self.read_point()
00449             p.append((x, y))
00450 #            p.append (QPointF(x, y))
00451         return p
00452 
00453     def read_color(self):
00454         # See http://www.graphviz.org/doc/info/attrs.html#k:color
00455         c = self.read_text()
00456         c1 = c[:1]
00457         if c1 == '#':
00458             hex2float = lambda h: float(int(h, 16)/255.0)
00459             r = hex2float(c[1:3])
00460             g = hex2float(c[3:5])
00461             b = hex2float(c[5:7])
00462             try:
00463                 a = hex2float(c[7:9])
00464             except (IndexError, ValueError):
00465                 a = 1.0
00466             return r, g, b, a
00467         elif c1.isdigit() or c1 == ".":
00468             # "H,S,V" or "H S V" or "H, S, V" or any other variation
00469             h, s, v = map(float, c.replace(",", " ").split())
00470             r, g, b = colorsys.hsv_to_rgb(h, s, v)
00471             a = 1.0
00472             return r, g, b, a
00473         else:
00474             return self.lookup_color(c)
00475 
00476     def lookup_color(self, c):
00477         try:
00478             color = gtk.gdk.color_parse(c)
00479         except ValueError:
00480             pass
00481         else:
00482             s = 1.0/65535.0
00483             r = color.red*s
00484             g = color.green*s
00485             b = color.blue*s
00486             a = 1.0
00487             return r, g, b, a
00488 
00489         try:
00490             dummy, scheme, index = c.split('/')
00491             r, g, b = brewer_colors[scheme][int(index)]
00492         except (ValueError, KeyError):
00493             pass
00494         else:
00495             s = 1.0/255.0
00496             r = r*s
00497             g = g*s
00498             b = b*s
00499             a = 1.0
00500             return r, g, b, a
00501 
00502         sys.stderr.write("unknown color '%s'\n" % c)
00503         return None
00504 
00505     def parse(self):
00506         s = self
00507 
00508         while s:
00509             op = s.read_code()
00510             if op == "c":
00511                 color = s.read_color()
00512                 if color is not None:
00513                     self.handle_color(color, filled=False)
00514             elif op == "C":
00515                 color = s.read_color()
00516                 if color is not None:
00517                     self.handle_color(color, filled=True)
00518             elif op == "S":
00519                 # http://www.graphviz.org/doc/info/attrs.html#k:style
00520                 style = s.read_text()
00521                 if style.startswith("setlinewidth("):
00522                     lw = style.split("(")[1].split(")")[0]
00523                     lw = float(lw)
00524                     self.handle_linewidth(lw)
00525                 elif style in ("solid", "dashed"):
00526                     self.handle_linestyle(style)
00527             elif op == "F":
00528                 size = s.read_float()
00529                 name = s.read_text()
00530                 self.handle_font(size, name)
00531             elif op == "T":
00532                 x, y = s.read_point()
00533                 j = s.read_number()
00534                 w = s.read_number()
00535                 t = s.read_text()
00536                 self.handle_text(x, y, j, w, t)
00537             elif op == "E":
00538                 x0, y0 = s.read_point()
00539                 w = s.read_number()
00540                 h = s.read_number()
00541                 self.handle_ellipse(x0, y0, w, h, filled=True)
00542             elif op == "e":
00543                 x0, y0 = s.read_point()
00544                 w = s.read_number()
00545                 h = s.read_number()
00546                 self.handle_ellipse(x0, y0, w, h, filled=False)
00547             elif op == "L":
00548                 points = self.read_polygon()
00549                 self.handle_line(points)
00550             elif op == "B":
00551                 points = self.read_polygon()
00552                 self.handle_bezier(points, filled=False)
00553             elif op == "b":
00554                 points = self.read_polygon()
00555                 self.handle_bezier(points, filled=True)
00556             elif op == "P":
00557                 points = self.read_polygon()
00558                 self.handle_polygon(points, filled=True)
00559             elif op == "p":
00560                 points = self.read_polygon()
00561                 self.handle_polygon(points, filled=False)
00562             else:
00563                 sys.stderr.write("unknown xdot opcode '%s'\n" % op)
00564                 break
00565 
00566         return self.shapes
00567 
00568     def transform(self, x, y):
00569         return self.parser.transform(x, y)
00570 
00571     def handle_color(self, color, filled=False):
00572         if filled:
00573             self.pen.fillcolor = color
00574         else:
00575             self.pen.color = color
00576 
00577     def handle_linewidth(self, linewidth):
00578         self.pen.linewidth = linewidth
00579 
00580     def handle_linestyle(self, style):
00581         if style == "solid":
00582 #            self.pen.dash = ()
00583             self.pen.dash = Qt.SolidLine
00584         elif style == "dashed":
00585 #            self.pen.dash = (6, )       # 6pt on, 6pt off
00586             self.pen.dash = Qt.DashLine
00587 
00588     def handle_font(self, size, name):
00589         self.pen.fontsize = size
00590         self.pen.fontname = name
00591 
00592     def handle_text(self, x, y, j, w, t):
00593         self.shapes.append(TextShape(self.pen, x, y, j, w, t))
00594 
00595     def handle_ellipse(self, x0, y0, w, h, filled=False):
00596         if filled:
00597             # xdot uses this to mean "draw a filled shape with an outline"
00598             self.shapes.append(EllipseShape(self.pen, x0, y0, w, h, filled=True))
00599         self.shapes.append(EllipseShape(self.pen, x0, y0, w, h))
00600 
00601     def handle_line(self, points):
00602         self.shapes.append(LineShape(self.pen, points))
00603 
00604     def handle_bezier(self, points, filled=False):
00605         if filled:
00606             # xdot uses this to mean "draw a filled shape with an outline"
00607             self.shapes.append(BezierShape(self.pen, points, filled=True))
00608         self.shapes.append(BezierShape(self.pen, points))
00609 
00610     def handle_polygon(self, points, filled=False):
00611         #AB Should filled be handled inside of PolygonShape?
00612         if filled:
00613             # xdot uses this to mean "draw a filled shape with an outline"
00614             self.shapes.append(PolygonShape(self.pen, points, filled=True))
00615         self.shapes.append(PolygonShape(self.pen, points))
00616 
00617 
00618 EOF = -1
00619 SKIP = -2
00620 
00621 
00622 class ParseError(Exception):
00623 
00624     def __init__(self, msg=None, filename=None, line=None, col=None):
00625         self.msg = msg
00626         self.filename = filename
00627         self.line = line
00628         self.col = col
00629 
00630     def __str__(self):
00631         return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
00632 
00633 
00634 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
00635 class Scanner:
00636     """Stateless scanner."""
00637 
00638     # should be overriden by derived classes
00639     tokens = []
00640     symbols = {}
00641     literals = {}
00642     ignorecase = False
00643 
00644     def __init__(self):
00645         flags = re.DOTALL
00646         if self.ignorecase:
00647             flags |= re.IGNORECASE
00648         self.tokens_re = re.compile(
00649             '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
00650              flags
00651         )
00652 
00653     def next(self, buf, pos):
00654         if pos >= len(buf):
00655             return EOF, '', pos
00656         mo = self.tokens_re.match(buf, pos)
00657         if mo:
00658             text = mo.group()
00659             type, regexp, test_lit = self.tokens[mo.lastindex - 1]
00660             pos = mo.end()
00661             if test_lit:
00662                 type = self.literals.get(text, type)
00663             return type, text, pos
00664         else:
00665             c = buf[pos]
00666             return self.symbols.get(c, None), c, pos + 1
00667 
00668 
00669 class Token:
00670 
00671     def __init__(self, type, text, line, col):
00672         self.type = type
00673         self.text = text
00674         self.line = line
00675         self.col = col
00676 
00677 
00678 class Lexer:
00679 
00680     # should be overriden by derived classes
00681     scanner = None
00682     tabsize = 8
00683 
00684     newline_re = re.compile(r'\r\n?|\n')
00685 
00686     def __init__(self, buf = None, pos = 0, filename = None, fp = None):
00687         if fp is not None:
00688             try:
00689                 fileno = fp.fileno()
00690                 length = os.path.getsize(fp.name)
00691                 import mmap
00692             except:
00693                 # read whole file into memory
00694                 buf = fp.read()
00695                 pos = 0
00696             else:
00697                 # map the whole file into memory
00698                 if length:
00699                     # length must not be zero
00700                     buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
00701                     pos = os.lseek(fileno, 0, 1)
00702                 else:
00703                     buf = ''
00704                     pos = 0
00705 
00706             if filename is None:
00707                 try:
00708                     filename = fp.name
00709                 except AttributeError:
00710                     filename = None
00711 
00712         self.buf = buf
00713         self.pos = pos
00714         self.line = 1
00715         self.col = 1
00716         self.filename = filename
00717 
00718     def next(self):
00719         while True:
00720             # save state
00721             pos = self.pos
00722             line = self.line
00723             col = self.col
00724 
00725             type, text, endpos = self.scanner.next(self.buf, pos)
00726             assert pos + len(text) == endpos
00727             self.consume(text)
00728             type, text = self.filter(type, text)
00729             self.pos = endpos
00730 
00731             if type == SKIP:
00732                 continue
00733             elif type is None:
00734                 msg = 'unexpected char '
00735                 if text >= ' ' and text <= '~':
00736                     msg += "'%s'" % text
00737                 else:
00738                     msg += "0x%X" % ord(text)
00739                 raise ParseError(msg, self.filename, line, col)
00740             else:
00741                 break
00742         return Token(type = type, text = text, line = line, col = col)
00743 
00744     def consume(self, text):
00745         # update line number
00746         pos = 0
00747         for mo in self.newline_re.finditer(text, pos):
00748             self.line += 1
00749             self.col = 1
00750             pos = mo.end()
00751 
00752         # update column number
00753         while True:
00754             tabpos = text.find('\t', pos)
00755             if tabpos == -1:
00756                 break
00757             self.col += tabpos - pos
00758             self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
00759             pos = tabpos + 1
00760         self.col += len(text) - pos
00761 
00762 
00763 class Parser:
00764 
00765     def __init__(self, lexer):
00766         self.lexer = lexer
00767         self.lookahead = self.lexer.next()
00768 
00769     def match(self, type):
00770         if self.lookahead.type != type:
00771             raise ParseError(
00772                 msg = 'unexpected token %r' % self.lookahead.text,
00773                 filename = self.lexer.filename,
00774                 line = self.lookahead.line,
00775                 col = self.lookahead.col)
00776 
00777     def skip(self, type):
00778         while self.lookahead.type != type:
00779             self.consume()
00780 
00781     def consume(self):
00782         token = self.lookahead
00783         self.lookahead = self.lexer.next()
00784         return token
00785 
00786 
00787 ID = 0
00788 STR_ID = 1
00789 HTML_ID = 2
00790 EDGE_OP = 3
00791 
00792 LSQUARE = 4
00793 RSQUARE = 5
00794 LCURLY = 6
00795 RCURLY = 7
00796 COMMA = 8
00797 COLON = 9
00798 SEMI = 10
00799 EQUAL = 11
00800 PLUS = 12
00801 
00802 STRICT = 13
00803 GRAPH = 14
00804 DIGRAPH = 15
00805 NODE = 16
00806 EDGE = 17
00807 SUBGRAPH = 18
00808 
00809 
00810 class DotScanner(Scanner):
00811 
00812     # token regular expression table
00813     tokens = [
00814         # whitespace and comments
00815         (SKIP,
00816             r'[ \t\f\r\n\v]+|'
00817             r'//[^\r\n]*|'
00818             r'/\*.*?\*/|'
00819             r'#[^\r\n]*',
00820         False),
00821 
00822         # Alphanumeric IDs
00823         (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
00824 
00825         # Numeric IDs
00826         (ID, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
00827 
00828         # String IDs
00829         (STR_ID, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
00830 
00831         # HTML IDs
00832         (HTML_ID, r'<[^<>]*(?:<[^<>]*>[^<>]*)*>', False),
00833 
00834         # Edge operators
00835         (EDGE_OP, r'-[>-]', False),
00836     ]
00837 
00838     # symbol table
00839     symbols = {
00840         '[': LSQUARE,
00841         ']': RSQUARE,
00842         '{': LCURLY,
00843         '}': RCURLY,
00844         ',': COMMA,
00845         ':': COLON,
00846         ';': SEMI,
00847         '=': EQUAL,
00848         '+': PLUS,
00849     }
00850 
00851     # literal table
00852     literals = {
00853         'strict': STRICT,
00854         'graph': GRAPH,
00855         'digraph': DIGRAPH,
00856         'node': NODE,
00857         'edge': EDGE,
00858         'subgraph': SUBGRAPH,
00859     }
00860 
00861     ignorecase = True
00862 
00863 
00864 class DotLexer(Lexer):
00865 
00866     scanner = DotScanner()
00867 
00868     def filter(self, type, text):
00869         # TODO: handle charset
00870         if type == STR_ID:
00871             text = text[1:-1]
00872 
00873             # line continuations
00874             text = text.replace('\\\r\n', '')
00875             text = text.replace('\\\r', '')
00876             text = text.replace('\\\n', '')
00877 
00878             text = text.replace('\\r', '\r')
00879             text = text.replace('\\n', '\n')
00880             text = text.replace('\\t', '\t')
00881             text = text.replace('\\', '')
00882 
00883             type = ID
00884 
00885         elif type == HTML_ID:
00886             text = text[1:-1]
00887             type = ID
00888 
00889         return type, text
00890 
00891 
00892 class DotParser(Parser):
00893 
00894     def __init__(self, lexer):
00895         Parser.__init__(self, lexer)
00896         self.graph_attrs = {}
00897         self.node_attrs = {}
00898         self.edge_attrs = {}
00899 
00900     def parse(self):
00901         self.parse_graph()
00902         self.match(EOF)
00903 
00904     def parse_graph(self):
00905         if self.lookahead.type == STRICT:
00906             self.consume()
00907         self.skip(LCURLY)
00908         self.consume()
00909         while self.lookahead.type != RCURLY:
00910             self.parse_stmt()
00911         self.consume()
00912 
00913     def parse_subgraph(self):
00914         id = None
00915         shapes_before = set(self.shapes)
00916         if self.lookahead.type == SUBGRAPH:
00917             self.consume()
00918             if self.lookahead.type == ID:
00919                 id = self.lookahead.text
00920                 self.consume()
00921         if self.lookahead.type == LCURLY:
00922             self.consume()
00923             while self.lookahead.type != RCURLY:
00924                 self.parse_stmt()
00925             self.consume()
00926         new_shapes = set(self.shapes) - shapes_before
00927         self.subgraph_shapes[id] = [s for s in new_shapes if not any([s in ss for ss in self.subgraph_shapes.values()])]
00928         return id
00929 
00930     def parse_stmt(self):
00931         if self.lookahead.type == GRAPH:
00932             self.consume()
00933             attrs = self.parse_attrs()
00934             self.graph_attrs.update(attrs)
00935             self.handle_graph(attrs)
00936         elif self.lookahead.type == NODE:
00937             self.consume()
00938             self.node_attrs.update(self.parse_attrs())
00939         elif self.lookahead.type == EDGE:
00940             self.consume()
00941             self.edge_attrs.update(self.parse_attrs())
00942         elif self.lookahead.type in (SUBGRAPH, LCURLY):
00943             self.parse_subgraph()
00944         else:
00945             id = self.parse_node_id()
00946             if self.lookahead.type == EDGE_OP:
00947                 self.consume()
00948                 node_ids = [id, self.parse_node_id()]
00949                 while self.lookahead.type == EDGE_OP:
00950                     node_ids.append(self.parse_node_id())
00951                 attrs = self.parse_attrs()
00952                 for i in range(0, len(node_ids) - 1):
00953                     self.handle_edge(node_ids[i], node_ids[i + 1], attrs)
00954             elif self.lookahead.type == EQUAL:
00955                 self.consume()
00956                 self.parse_id()
00957             else:
00958                 attrs = self.parse_attrs()
00959                 self.handle_node(id, attrs)
00960         if self.lookahead.type == SEMI:
00961             self.consume()
00962 
00963     def parse_attrs(self):
00964         attrs = {}
00965         while self.lookahead.type == LSQUARE:
00966             self.consume()
00967             while self.lookahead.type != RSQUARE:
00968                 name, value = self.parse_attr()
00969                 attrs[name] = value
00970                 if self.lookahead.type == COMMA:
00971                     self.consume()
00972             self.consume()
00973         return attrs
00974 
00975     def parse_attr(self):
00976         name = self.parse_id()
00977         if self.lookahead.type == EQUAL:
00978             self.consume()
00979             value = self.parse_id()
00980         else:
00981             value = 'true'
00982         return name, value
00983 
00984     def parse_node_id(self):
00985         node_id = self.parse_id()
00986         if self.lookahead.type == COLON:
00987             self.consume()
00988             port = self.parse_id()
00989             if self.lookahead.type == COLON:
00990                 self.consume()
00991                 compass_pt = self.parse_id()
00992             else:
00993                 compass_pt = None
00994         else:
00995             port = None
00996             compass_pt = None
00997         # XXX: we don't really care about port and compass point values when parsing xdot
00998         return node_id
00999 
01000     def parse_id(self):
01001         self.match(ID)
01002         id = self.lookahead.text
01003         self.consume()
01004         return id
01005 
01006     def handle_graph(self, attrs):
01007         pass
01008 
01009     def handle_node(self, id, attrs):
01010         pass
01011 
01012     def handle_edge(self, src_id, dst_id, attrs):
01013         pass
01014 
01015 
01016 class XDotParser(DotParser):
01017 
01018     def __init__(self, xdotcode):
01019         lexer = DotLexer(buf = xdotcode)
01020         DotParser.__init__(self, lexer)
01021 
01022         self.nodes = []
01023         self.edges = []
01024         self.shapes = []
01025         self.node_by_name = {}
01026         self.top_graph = True
01027         self.subgraph_shapes = {}
01028 
01029     def handle_graph(self, attrs):
01030         if self.top_graph:
01031             try:
01032                 bb = attrs['bb']
01033             except KeyError:
01034                 return
01035 
01036             if not bb:
01037                 return
01038 
01039             xmin, ymin, xmax, ymax = map(float, bb.split(","))
01040 
01041             self.xoffset = -xmin
01042             self.yoffset = -ymax
01043             self.xscale = 1.0
01044             self.yscale = -1.0
01045             # FIXME: scale from points to pixels
01046 
01047             self.width = xmax - xmin
01048             self.height = ymax - ymin
01049 
01050             self.top_graph = False
01051 
01052         for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
01053             if attr in attrs:
01054                 parser = XDotAttrParser(self, attrs[attr])
01055                 self.shapes.extend(parser.parse())
01056 
01057     def handle_node(self, id, attrs):
01058         try:
01059             pos = attrs['pos']
01060         except KeyError:
01061             return
01062 
01063         x, y = self.parse_node_pos(pos)
01064         w = float(attrs['width'])*72
01065         h = float(attrs['height'])*72
01066         shapes = []
01067         for attr in ("_draw_", "_ldraw_"):
01068             if attr in attrs:
01069                 parser = XDotAttrParser(self, attrs[attr])
01070                 shapes.extend(parser.parse())
01071         url = attrs.get('URL', None)
01072         node = Node(x, y, w, h, shapes, url)
01073         self.node_by_name[id] = node
01074         if shapes:
01075             self.nodes.append(node)
01076 
01077     def handle_edge(self, src_id, dst_id, attrs):
01078         try:
01079             pos = attrs['pos']
01080         except KeyError:
01081             return
01082 
01083         points = self.parse_edge_pos(pos)
01084         shapes = []
01085         for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
01086             if attr in attrs:
01087                 parser = XDotAttrParser(self, attrs[attr])
01088                 shapes.extend(parser.parse())
01089         url = attrs.get('URL', None)
01090         if shapes:
01091             src = self.node_by_name[src_id]
01092             dst = self.node_by_name[dst_id]
01093             self.edges.append(Edge(src, dst, points, shapes, url))
01094 
01095     def parse(self):
01096         DotParser.parse(self)
01097 
01098         """
01099         for k,shapes in self.subgraph_shapes.iteritems():
01100           self.shapes += shapes
01101         """
01102 
01103         return Graph(self.width, self.height, self.shapes, self.nodes, self.edges, self.subgraph_shapes)
01104 
01105     def parse_node_pos(self, pos):
01106         x, y = pos.split(",")
01107         return self.transform(float(x), float(y))
01108 
01109     def parse_edge_pos(self, pos):
01110         points = []
01111         for entry in pos.split(' '):
01112             fields = entry.split(',')
01113             try:
01114                 x, y = fields
01115             except ValueError:
01116                 # TODO: handle start/end points
01117                 #AB Handle data like like e,40,50 here
01118                 continue
01119             else:
01120                 points.append(self.transform(float(x), float(y)))
01121         return points
01122 
01123     def transform(self, x, y):
01124         # XXX: this is not the right place for this code
01125         x = (x + self.xoffset)*self.xscale
01126         y = (y + self.yoffset)*self.yscale
01127         return x, y
01128 
01129 
01130 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
01131 class Animation(object):
01132 
01133     step = 0.03 # seconds
01134 
01135     def __init__(self, dot_widget):
01136         self.dot_widget = dot_widget
01137         self.timeout_id = None
01138 
01139     def start(self):
01140         self.timeout_id = QTimer();
01141         self.dot_widget.connect(self.timeout_id, SIGNAL('timeout()'), self.tick)
01142         self.timeout_id.start(int(self.step * 1000))
01143 
01144     def stop(self):
01145         self.dot_widget.animation = NoAnimation(self.dot_widget)
01146         if self.timeout_id is not None:
01147             self.timeout_id.stop()
01148             self.timeout_id = None
01149 
01150     def tick(self):
01151         self.stop()
01152 
01153 
01154 class NoAnimation(Animation):
01155 
01156     def start(self):
01157         pass
01158 
01159     def stop(self):
01160         pass
01161 
01162 
01163 class LinearAnimation(Animation):
01164 
01165     duration = 0.6
01166 
01167     def start(self):
01168         self.started = time.time()
01169         Animation.start(self)
01170 
01171     def tick(self):
01172         t = (time.time() - self.started) / self.duration
01173         self.animate(max(0, min(t, 1)))
01174 #        return (t < 1) #AB returning False stops the timer
01175         if t >= 1: #AB
01176             self.timeout_id.stop() #AB
01177 
01178     def animate(self, t):
01179         pass
01180 
01181 
01182 class MoveToAnimation(LinearAnimation):
01183 
01184     def __init__(self, dot_widget, target_x, target_y):
01185         Animation.__init__(self, dot_widget)
01186         self.source_x = dot_widget.x
01187         self.source_y = dot_widget.y
01188         self.target_x = target_x
01189         self.target_y = target_y
01190 
01191     def animate(self, t):
01192         sx, sy = self.source_x, self.source_y
01193         tx, ty = self.target_x, self.target_y
01194         self.dot_widget.x = tx * t + sx * (1-t)
01195         self.dot_widget.y = ty * t + sy * (1-t)
01196         self.dot_widget.update()
01197 
01198 class ZoomToAnimation(MoveToAnimation):
01199 
01200     def __init__(self, dot_widget, target_x, target_y):
01201         MoveToAnimation.__init__(self, dot_widget, target_x, target_y)
01202         self.source_zoom = dot_widget.zoom_ratio
01203         self.target_zoom = self.source_zoom
01204         self.extra_zoom = 0
01205 
01206         middle_zoom = 0.5 * (self.source_zoom + self.target_zoom)
01207 
01208         distance = math.hypot(self.source_x - self.target_x,
01209                               self.source_y - self.target_y)
01210 #        rect = self.dot_widget.get_allocation()
01211         rect = self.dot_widget.rect()
01212 #        visible = min(rect.width, rect.height) / self.dot_widget.zoom_ratio
01213         visible = min(rect.width(), rect.height()) / self.dot_widget.zoom_ratio
01214         visible *= 0.9
01215         if distance > 0:
01216             desired_middle_zoom = visible / distance
01217             self.extra_zoom = min(0, 4 * (desired_middle_zoom - middle_zoom))
01218 
01219     def animate(self, t):
01220         a, b, c = self.source_zoom, self.extra_zoom, self.target_zoom
01221         self.dot_widget.zoom_ratio = c*t + b*t*(1-t) + a*(1-t)
01222         self.dot_widget.zoom_to_fit_on_resize = False
01223         MoveToAnimation.animate(self, t)
01224 
01225 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
01226 class DragAction(object):
01227 
01228     def __init__(self, dot_widget):
01229         self.dot_widget = dot_widget
01230 
01231     def on_button_press(self, event):
01232 #        self.startmousex = self.prevmousex = event.x
01233         self.startmousex = self.prevmousex = event.x()
01234         self.startmousey = self.prevmousey = event.y()
01235         self.start()
01236 
01237     def on_motion_notify(self, event):
01238         deltax = self.prevmousex - event.x()
01239         deltay = self.prevmousey - event.y()
01240         self.drag(deltax, deltay)
01241         self.prevmousex = event.x()
01242         self.prevmousey = event.y()
01243 
01244     def on_button_release(self, event):
01245         self.stopmousex = event.x()
01246         self.stopmousey = event.y()
01247         self.stop()
01248 
01249     def draw(self, cr):
01250         pass
01251 
01252     def start(self):
01253         pass
01254 
01255     def drag(self, deltax, deltay):
01256         pass
01257 
01258     def stop(self):
01259         pass
01260 
01261     def abort(self):
01262         pass
01263 
01264 
01265 class NullAction(DragAction):
01266 
01267     def on_motion_notify(self, event):
01268         x, y = event.x(), event.y()
01269 
01270         dot_widget = self.dot_widget
01271         item = dot_widget.get_url(x, y)
01272         if item is None:
01273             item = dot_widget.get_jump(x, y)
01274         if item is not None:
01275             dot_widget.setCursor(Qt.PointingHandCursor)
01276             dot_widget.set_highlight(item.highlight)
01277         else:
01278             dot_widget.setCursor(Qt.ArrowCursor)
01279             dot_widget.set_highlight(None)
01280 
01281 
01282 class PanAction(DragAction):
01283 
01284     def start(self):
01285         self.dot_widget.setCursor(Qt.ClosedHandCursor)
01286 
01287     def drag(self, deltax, deltay):
01288         self.dot_widget.x += deltax / self.dot_widget.zoom_ratio
01289         self.dot_widget.y += deltay / self.dot_widget.zoom_ratio
01290         self.dot_widget.update()
01291 
01292     def stop(self):
01293         self.dot_widget.cursor().setShape(Qt.ArrowCursor)
01294 
01295     abort = stop
01296 
01297 
01298 class ZoomAction(DragAction):
01299 
01300     def drag(self, deltax, deltay):
01301         self.dot_widget.zoom_ratio *= 1.005 ** (deltax + deltay)
01302         self.dot_widget.zoom_to_fit_on_resize = False
01303         self.dot_widget.update()
01304 
01305 def stop(self):
01306         self.dot_widget.update()
01307 
01308 
01309 class ZoomAreaAction(DragAction):
01310 
01311     def drag(self, deltax, deltay):
01312         self.dot_widget.update()
01313 
01314     def draw(self, painter):
01315         #TODO: implement this for qt
01316         print "ERROR: UNIMPLEMENTED ZoomAreaAction.draw"
01317         return
01318         painter.save()
01319         painter.set_source_rgba(.5, .5, 1.0, 0.25)
01320         painter.rectangle(self.startmousex, self.startmousey,
01321                      self.prevmousex - self.startmousex,
01322                      self.prevmousey - self.startmousey)
01323         painter.fill()
01324         painter.set_source_rgba(.5, .5, 1.0, 1.0)
01325         painter.set_line_width(1)
01326         painter.rectangle(self.startmousex - .5, self.startmousey - .5,
01327                      self.prevmousex - self.startmousex + 1,
01328                      self.prevmousey - self.startmousey + 1)
01329         painter.stroke()
01330         painter.restore()
01331 
01332     def stop(self):
01333         x1, y1 = self.dot_widget.window_to_graph(self.startmousex,
01334                                               self.startmousey)
01335         x2, y2 = self.dot_widget.window_to_graph(self.stopmousex,
01336                                               self.stopmousey)
01337         self.dot_widget.zoom_to_area(x1, y1, x2, y2)
01338 
01339     def abort(self):
01340         self.dot_widget.update()
01341 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
01342 class DotWidget(QWidget):
01343 #class DotWidget(gtk.DrawingArea):
01344     """Qt widget that draws dot graphs."""
01345 
01346     filter = 'dot'
01347 
01348     def __init__(self,  parent=None):
01349         super(DotWidget,  self).__init__(parent)
01350         self.graph = Graph()
01351         self.openfilename = None
01352 
01353 #        self.set_flags(gtk.CAN_FOCUS)
01354 #        self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
01355 #        self.connect("button-press-event", self.on_area_button_press)
01356 #        self.connect("button-release-event", self.on_area_button_release)
01357 #        self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
01358 #        self.connect("motion-notify-event", self.on_area_motion_notify)
01359 #        self.connect("scroll-event", self.on_area_scroll_event)
01360 #        self.connect("size-allocate", self.on_area_size_allocate)
01361 #        self.connect('key-press-event', self.on_key_press_event)
01362 
01363         self.x, self.y = 0.0, 0.0
01364         self.zoom_ratio = 1.0
01365         self.zoom_to_fit_on_resize = False
01366         self.animation = NoAnimation(self)
01367         self.drag_action = NullAction(self)
01368         self.presstime = None
01369         self.highlight = None
01370 
01371         # Callback register
01372         self.select_cbs = []
01373         self.dc = None
01374         self.ctx = None
01375         self.items_by_url = {}
01376 
01377         self.setMouseTracking (True) # track all mouse events
01378 
01379     ZOOM_INCREMENT = 1.25
01380     ZOOM_TO_FIT_MARGIN = 12
01381     POS_INCREMENT = 100
01382 
01383     ### User callbacks
01384     def register_select_callback(self, cb):
01385         self.select_cbs.append(cb)
01386 
01387     def set_filter(self, filter):
01388         self.filter = filter
01389 
01390     def set_dotcode(self, dotcode, filename='<stdin>',center=True):
01391         if isinstance(dotcode, unicode):
01392             dotcode = dotcode.encode('utf8')
01393         p = subprocess.Popen(
01394             [self.filter, '-Txdot'],
01395             stdin=subprocess.PIPE,
01396             stdout=subprocess.PIPE,
01397             stderr=subprocess.PIPE,
01398             shell=False,
01399             universal_newlines=True
01400         )
01401         xdotcode, error = p.communicate(dotcode)
01402         if p.returncode != 0:
01403             print "UNABLE TO SHELL TO DOT", error
01404 #            dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
01405 #                                       message_format=error,
01406 #                                       buttons=gtk.BUTTONS_OK)
01407 #            dialog.set_title('Dot Viewer')
01408 #            dialog.run()
01409 #            dialog.destroy()
01410             return False
01411         try:
01412             self.set_xdotcode(xdotcode,center)
01413 
01414             # Store references to all the items
01415             self.items_by_url = {}
01416             for item in self.graph.nodes + self.graph.edges:
01417                 if item.url is not None:
01418                     self.items_by_url[item.url] = item
01419 
01420             # Store references to subgraph states
01421             self.subgraph_shapes = self.graph.subgraph_shapes
01422 
01423         except ParseError, ex:
01424 #            dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
01425 #                                       message_format=str(ex),
01426 #                                       buttons=gtk.BUTTONS_OK)
01427 #            dialog.set_title('Dot Viewer')
01428 #            dialog.run()
01429 #            dialog.destroy()
01430             return False
01431         else:
01432             self.openfilename = filename
01433             return True
01434 
01435     def set_xdotcode(self, xdotcode, center=True):
01436         #print xdotcode
01437         parser = XDotParser(xdotcode)
01438         self.graph = parser.parse()
01439         self.zoom_image(self.zoom_ratio, center=center)
01440 
01441     def reload(self):
01442         if self.openfilename is not None:
01443             try:
01444                 fp = open(str(self.openfilename), "rb")
01445                 self.set_dotcode(fp.read(), self.openfilename)
01446                 fp.close()
01447             except IOError:
01448                 pass
01449 
01450     def paintEvent (self,  event=None):
01451 #    def do_expose_event(self, event):
01452 #        cr = self.window.cairo_create()
01453         painter = QPainter (self)
01454         painter.setRenderHint(QPainter.Antialiasing)
01455         painter.setRenderHint(QPainter.TextAntialiasing)
01456         painter.setRenderHint(QPainter.HighQualityAntialiasing)
01457 
01458         # set a clip region for the expose event
01459 #        cr.rectangle(
01460 #            event.area.x, event.area.y,
01461 #            event.area.width, event.area.height
01462 #        )
01463 #        cr.clip()
01464 #
01465 #        cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
01466 #        cr.paint()
01467 
01468         painter.setClipping(True)
01469         painter.setClipRect(self.rect())
01470         painter.setBackground(QBrush(Qt.blue,Qt.SolidPattern))
01471         painter.save()
01472 
01473 #        rect = self.get_allocation()
01474         rect = self.rect() # JRB was self.rect()
01475 #        cr.translate(0.5*rect.width, 0.5*rect.height)
01476         painter.translate(0.5*rect.width(), 0.5*rect.height())
01477         painter.scale(self.zoom_ratio, self.zoom_ratio)
01478         painter.translate(-self.x, -self.y)
01479 
01480         self.graph.draw(painter, highlight_items=self.highlight)
01481         painter.restore()
01482 
01483         self.drag_action.draw(painter)
01484 
01485 
01486     def get_current_pos(self):
01487         return self.x, self.y
01488 
01489 #AB This function is not used
01490     def set_current_pos(self, x, y):
01491         self.x = x
01492         self.y = y
01493         self.update()
01494 
01495     def set_highlight(self, items):
01496         if self.highlight != items:
01497             self.highlight = items
01498             self.update()
01499 
01500     def zoom_image(self, zoom_ratio, center=False, pos=None):
01501         if center:
01502             self.x = self.graph.width/2
01503             self.y = self.graph.height/2
01504         elif pos is not None:
01505 #            rect = self.get_allocation()
01506             rect = self.rect()
01507             x, y = pos
01508 #            x -= 0.5*rect.width
01509             x -= 0.5*rect.width()
01510 #            y -= 0.5*rect.height
01511             y -= 0.5*rect.height()
01512             self.x += x / self.zoom_ratio - x / zoom_ratio
01513             self.y += y / self.zoom_ratio - y / zoom_ratio
01514         self.zoom_ratio = zoom_ratio
01515         self.zoom_to_fit_on_resize = False
01516         self.update()
01517 
01518     def zoom_to_area(self, x1, y1, x2, y2):
01519 #        rect = self.get_allocation()
01520         rect = self.rect()
01521         width = abs(x1 - x2)
01522         height = abs(y1 - y2)
01523         self.zoom_ratio = min(
01524             float(rect.width())/float(width),
01525             float(rect.height())/float(height)
01526         )
01527         self.zoom_to_fit_on_resize = False
01528         self.x = (x1 + x2) / 2
01529         self.y = (y1 + y2) / 2
01530         self.update()
01531 
01532     def zoom_to_fit(self):
01533 #        rect = self.get_allocation()
01534         rect = self.rect()
01535 #        rect.x += self.ZOOM_TO_FIT_MARGIN
01536         rect.setX (rect.x() + self.ZOOM_TO_FIT_MARGIN)
01537 #        rect.y += self.ZOOM_TO_FIT_MARGIN
01538         rect.setY (rect.y() + self.ZOOM_TO_FIT_MARGIN)
01539 #        rect.width -= 2 * self.ZOOM_TO_FIT_MARGIN
01540         rect.setWidth(rect.width() - 2 * self.ZOOM_TO_FIT_MARGIN)
01541 #        rect.height -= 2 * self.ZOOM_TO_FIT_MARGIN
01542         rect.setHeight(rect.height() - 2 * self.ZOOM_TO_FIT_MARGIN)
01543         zoom_ratio = min(
01544 #            float(rect.width)/float(self.graph.width),
01545             float(rect.width())/float(self.graph.width),
01546 #            float(rect.height)/float(self.graph.height)
01547             float(rect.height())/float(self.graph.height)
01548         )
01549         self.zoom_image(zoom_ratio, center=True)
01550         self.zoom_to_fit_on_resize = True
01551 
01552     def on_zoom_in(self):
01553 #    def on_zoom_in(self, action):
01554         self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
01555 
01556     def on_zoom_out(self):
01557 #    def on_zoom_out(self, action):
01558         self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
01559 
01560     def on_zoom_fit(self):
01561 #    def on_zoom_fit(self, action):
01562         self.zoom_to_fit()
01563 
01564     def on_zoom_100(self):
01565 #    def on_zoom_100(self, action):
01566         self.zoom_image(1.0)
01567 
01568     def keyPressEvent(self, event):
01569         self.animation.stop()
01570         self.drag_action.abort()
01571         if event.key() == Qt.Key_Left:
01572             self.x -= self.POS_INCREMENT/self.zoom_ratio
01573             self.update()
01574         elif event.key() == Qt.Key_Right:
01575             self.x += self.POS_INCREMENT/self.zoom_ratio
01576             self.update()
01577         elif event.key() == Qt.Key_Up:
01578             self.y -= self.POS_INCREMENT/self.zoom_ratio
01579             self.update()
01580         elif event.key() == Qt.Key_Down:
01581             self.y += self.POS_INCREMENT/self.zoom_ratio
01582             self.update()
01583         elif event.key() == Qt.Key_PageUp:
01584             self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
01585             self.update()
01586         elif event.key() == Qt.Key_PageDown:
01587             self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
01588             self.update()
01589         elif event.key() == Qt.Key_PageUp:
01590             self.drag_action.abort()
01591             self.drag_action = NullAction(self)
01592         elif event.key() == Qt.Key_R:
01593             self.reload()
01594         elif event.key() == Qt.Key_F:
01595             self.zoom_to_fit()
01596         event.accept()
01597 
01598     def get_drag_action(self, event):
01599         modifiers = event.modifiers()
01600         if event.button() in (Qt.LeftButton, Qt.MidButton):
01601             if modifiers & Qt.ControlModifier:
01602                 return ZoomAction
01603             elif modifiers & Qt.ShiftModifier:
01604                 return ZoomAreaAction
01605             else:
01606                 return PanAction
01607         return NullAction
01608 
01609     def mousePressEvent(self, event):
01610         self.animation.stop()
01611         self.drag_action.abort()
01612 
01613         for cb in self.select_cbs:
01614             cb(event)
01615 
01616         action_type = self.get_drag_action(event)
01617         self.drag_action = action_type(self)
01618         self.drag_action.on_button_press(event)
01619 
01620         self.presstime = time.time()
01621         self.pressx = event.x()
01622         self.pressy = event.y()
01623         event.accept()
01624 
01625     def is_click(self, event, click_fuzz=4, click_timeout=1.0):
01626         if self.presstime is None:
01627             # got a button release without seeing the press?
01628             return False
01629         # XXX instead of doing this complicated logic, shouldn't we listen
01630         # for gtk's clicked event instead?
01631         deltax = self.pressx - event.x()
01632         deltay = self.pressy - event.y()
01633         return (time.time() < self.presstime + click_timeout
01634                 and math.hypot(deltax, deltay) < click_fuzz)
01635 
01636     def mouseReleaseEvent(self, event):
01637         self.drag_action.on_button_release(event)
01638         self.drag_action = NullAction(self)
01639         if event.button() == Qt.LeftButton and self.is_click(event):
01640             x, y = event.x(), event.y()
01641             url = self.get_url(x, y)
01642             if url is not None:
01643                 self.emit(SIGNAL("clicked"), unicode(url.url), event)
01644             else:
01645                 self.emit(SIGNAL("clicked"), 'none', event)
01646                 jump = self.get_jump(x, y)
01647                 if jump is not None:
01648                     self.animate_to(jump.x, jump.y)
01649 
01650             event.accept()
01651             return
01652         if event.button() == Qt.RightButton and self.is_click(event):
01653             x, y = event.x(), event.y()
01654             url = self.get_url(x, y)
01655             if url is not None:
01656                 self.emit(SIGNAL("right_clicked"), unicode(url.url), event)
01657             else:
01658                 self.emit(SIGNAL("right_clicked"), 'none', event)
01659                 jump = self.get_jump(x, y)
01660                 if jump is not None:
01661                     self.animate_to(jump.x, jump.y)
01662 
01663         if event.button() in (Qt.LeftButton, Qt.MidButton):
01664             event.accept()
01665         return
01666 
01667     def on_area_scroll_event(self, area, event):
01668         return False
01669 
01670     def wheelEvent(self, event):
01671         if event.delta() > 0:
01672             self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT,
01673                             pos=(event.x(), event.y()))
01674         if event.delta() < 0:
01675             self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT,
01676                             pos=(event.x(), event.y()))
01677 
01678     def mouseMoveEvent(self, event):
01679         self.drag_action.on_motion_notify(event)
01680         self.setFocus()
01681         for cb in self.select_cbs:
01682             cb(event)
01683 
01684     def on_area_size_allocate(self, area, allocation):
01685         if self.zoom_to_fit_on_resize:
01686             self.zoom_to_fit()
01687 
01688     def animate_to(self, x, y):
01689         self.animation = ZoomToAnimation(self, x, y)
01690         self.animation.start()
01691 
01692     def window_to_graph(self, x, y):
01693 #        rect = self.get_allocation()
01694         rect = self.rect()
01695         x -= 0.5*rect.width()
01696         y -= 0.5*rect.height()
01697         x /= self.zoom_ratio
01698         y /= self.zoom_ratio
01699         x += self.x
01700         y += self.y
01701         return x, y
01702 
01703     def get_url(self, x, y):
01704         x, y = self.window_to_graph(x, y)
01705         return self.graph.get_url(x, y)
01706 
01707     def get_jump(self, x, y):
01708         x, y = self.window_to_graph(x, y)
01709         return self.graph.get_jump(x, y)
01710 
01711 
01712 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
01713 #class DotWindow(gtk.Window):
01714 class DotWindow(QMainWindow):
01715 
01716     def __init__(self):
01717         super(DotWindow,  self).__init__(None)
01718         self.graph = Graph()
01719         self.setWindowTitle(QApplication.applicationName())
01720         self.widget = DotWidget()
01721         self.widget.setContextMenuPolicy(Qt.ActionsContextMenu)
01722         self.setCentralWidget(self.widget)
01723 
01724         palette = QPalette ()
01725         palette.setColor(QPalette.Background, Qt.white)
01726         self.setPalette(palette)
01727 
01728         self.filename = None
01729 
01730         file_open_action = self.create_action("&Open...", self.on_open,
01731                 QKeySequence.Open, "fileopen", "Open an existing dot file")
01732         file_reload_action = self.create_action("&Refresh", self.on_reload,
01733                 QKeySequence.Refresh, "view-refresh", "Reload opened dot file")
01734         zoom_in_action = self.create_action("Zoom In", self.widget.on_zoom_in,
01735                 QKeySequence.ZoomIn, "zoom-in", "Zoom in")
01736         zoom_out_action = self.create_action("Zoom Out", self.widget.on_zoom_out,
01737                 QKeySequence.ZoomIn, "zoom-out", "Zoom Out")
01738         zoom_fit_action = self.create_action("Zoom Fit", self.widget.on_zoom_fit,
01739                 None, "zoom-fit-best", "Zoom Fit")
01740         zoom_100_action = self.create_action("Zoom 100%", self.widget.on_zoom_100,
01741                 None, "zoom-original", "Zoom 100%")
01742 
01743         self.file_menu = self.menuBar().addMenu("&File")
01744         self.file_menu_actions = (file_open_action, file_reload_action)
01745         self.connect(self.file_menu, SIGNAL("aboutToShow()"), self.update_file_menu)
01746 
01747         file_toolbar = self.addToolBar("File")
01748         file_toolbar.setObjectName("FileToolBar")
01749         self.add_actions(file_toolbar, (file_open_action, file_reload_action))
01750 
01751         fileToolbar = self.addToolBar("Zoom")
01752         fileToolbar.setObjectName("ZoomToolBar")
01753         self.add_actions(fileToolbar, (zoom_in_action, zoom_out_action,  zoom_fit_action,  zoom_100_action))
01754 
01755         settings = QSettings()
01756         self.recent_files = settings.value("RecentFiles").toStringList()
01757         size = settings.value("MainWindow/Size", QVariant(QSize(512, 512))).toSize()
01758         self.resize(size)
01759         position = settings.value("MainWindow/Position", QVariant(QPoint(0, 0))).toPoint()
01760         self.move(position)
01761 
01762         self.restoreState(settings.value("MainWindow/State").toByteArray())
01763         self.update_file_menu()
01764 
01765         self.show()
01766 
01767     def create_action(self, text, slot=None, shortcut=None, icon=None,
01768                      tip=None, checkable=False, signal="triggered()"):
01769         action = QAction(text, self)
01770         if icon is not None:
01771             action.setIcon(QIcon.fromTheme(icon))
01772         if shortcut is not None:
01773             action.setShortcut(shortcut)
01774         if tip is not None:
01775             action.setToolTip(tip)
01776             action.setStatusTip(tip)
01777         if slot is not None:
01778             self.connect(action, SIGNAL(signal), slot)
01779         if checkable:
01780             action.setCheckable(True)
01781         return action
01782 
01783     def add_actions(self, target, actions):
01784         for action in actions:
01785             if action is None:
01786                 target.addSeparator()
01787             else:
01788                 target.addAction(action)
01789 
01790     def update_file(self, filename):
01791         import os
01792         if not hasattr(self, "last_mtime"):
01793             self.last_mtime = None
01794 
01795         current_mtime = os.stat(filename).st_mtime
01796         if current_mtime != self.last_mtime:
01797             self.last_mtime = current_mtime
01798             self.open_file(filename)
01799 
01800         return True
01801 
01802     def set_filter(self, filter):
01803         self.widget.set_filter(filter)
01804 
01805     def set_dotcode(self, dotcode, filename='<stdin>'):
01806         if self.widget.set_dotcode(dotcode, filename):
01807             self.setWindowTitle(os.path.basename(filename) + ' - ' + QApplication.applicationName())
01808             self.widget.zoom_to_fit()
01809 
01810     def set_xdotcode(self, xdotcode, filename='<stdin>'):
01811         if self.widget.set_xdotcode(xdotcode):
01812             self.setWindowTitle(os.path.basename(filename) + ' - ' + QApplication.applicationName())
01813             self.widget.zoom_to_fit()
01814 
01815     def open_file(self, filename=None):
01816         if filename is None:
01817             action = self.sender()
01818             if isinstance(action, QAction):
01819                 filename = unicode(action.data().toString())
01820             else:
01821                 return
01822         try:
01823             fp = file(filename, 'rt')
01824             self.set_dotcode(fp.read(), filename)
01825             fp.close()
01826             self.add_recent_file(filename)
01827         except IOError, ex:
01828             pass
01829 
01830     def on_open(self):
01831         dir = os.path.dirname(self.filename) \
01832                 if self.filename is not None else "."
01833         formats = ["*.dot"]
01834         filename = unicode(QFileDialog.getOpenFileName(self,
01835                             "Open dot File", dir,
01836                             "Dot files (%s)" % " ".join(formats)))
01837         if filename:
01838             self.open_file(filename)
01839 
01840     def on_reload(self):
01841         self.widget.reload()
01842 
01843     def update_file_menu(self):
01844         self.file_menu.clear()
01845         self.add_actions(self.file_menu, self.file_menu_actions[:-1])
01846         current = QString(self.filename) \
01847                 if self.filename is not None else None
01848         recent_files = []
01849         for fname in self.recent_files:
01850             if fname != current and QFile.exists(fname):
01851                 recent_files.append(fname)
01852         if recent_files:
01853             self.file_menu.addSeparator()
01854             for i, fname in enumerate(recent_files):
01855                 action = QAction(QIcon(":/icon.png"), "&%d %s" % (i + 1, QFileInfo(fname).fileName()), self)
01856                 action.setData(QVariant(fname))
01857                 self.connect(action, SIGNAL("triggered()"), self.open_file)
01858                 self.file_menu.addAction(action)
01859         self.file_menu.addSeparator()
01860         self.file_menu.addAction(self.file_menu_actions[-1])
01861 
01862     def add_recent_file(self, filename):
01863         if filename is None:
01864             return
01865         if not self.recent_files.contains(filename):
01866             self.recent_files.prepend(QString(filename))
01867             while self.recent_files.count() > 14:
01868                 self.recent_files.takeLast()
01869 
01870 def closeEvent(self, event):
01871         settings = QSettings()
01872         filename = QVariant(QString(self.filename)) if self.filename is not None else QVariant()
01873         settings.setValue("LastFile", filename)
01874         recent_files = QVariant(self.recent_files) if self.recent_files else QVariant()
01875         settings.setValue("RecentFiles", recent_files)
01876         settings.setValue("MainWindow/Size", QVariant(self.size()))
01877 
01878         #AB It looks like PyQt 4.7.4 has a bug that results in both self.pos() and self.geometry() returning the same values
01879         # see http://doc.qt.nokia.com/latest/application-windows.html#window-geometry for explanation of why they should be different
01880         # The results is a small (5,24) shift of window on consequent start from previous position
01881         #print (self.pos())
01882         #print (self.geometry())
01883         settings.setValue("MainWindow/Position", QVariant(self.pos()))
01884         #settings.setValue("MainWindow/Geometry", QVariant(self.saveGeometry()))
01885         settings.setValue("MainWindow/State", QVariant(self.saveState()))
01886 
01887 #--------------------------------------------------------------------------------------------------------------------------------------------------------------------
01888 def main():
01889     import optparse
01890 
01891     parser = optparse.OptionParser(usage='\n\t%prog [file]', version='%%prog %s' % __version__)
01892     # This program can shell to different Graphviz filter processes specified by -f option
01893     parser.add_option(
01894         '-f', '--filter',
01895         type='choice', choices=('dot', 'neato', 'twopi', 'circo', 'fdp'),
01896         dest='filter', default='dot',
01897         help='graphviz filter: dot, neato, twopi, circo, or fdp [default: %default]')
01898 
01899     (options, args) = parser.parse_args(sys.argv[1:])
01900     if len(args) > 1:
01901         parser.error('incorrect number of arguments')
01902 
01903     app = QApplication(sys.argv)
01904     app.setOrganizationName("RobotNV")
01905     app.setOrganizationDomain("robotNV.com")
01906     app.setApplicationName("Dot Viewer")
01907     app.setWindowIcon(QIcon(":/icon.png"))
01908 
01909     win = DotWindow()
01910     win.show()
01911 #    win.connect('destroy', gtk.main_quit)
01912 #    win.set_filter(options.filter)
01913     if len(args) >= 1:
01914         if args[0] == '-':
01915             win.set_dotcode(sys.stdin.read())
01916         else:
01917             win.open_file(args[0])
01918 #            gobject.timeout_add(1000, win.update_file, args[0])
01919 #    gtk.main()
01920     sys.exit(app.exec_())
01921 
01922 
01923 
01924 
01925 # Apache-Style Software License for ColorBrewer software and ColorBrewer Color
01926 # Schemes, Version 1.1
01927 #
01928 # Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State
01929 # University. All rights reserved.
01930 #
01931 # Redistribution and use in source and binary forms, with or without
01932 # modification, are permitted provided that the following conditions are met:
01933 #
01934 #    1. Redistributions as source code must retain the above copyright notice,
01935 #    this list of conditions and the following disclaimer.
01936 #
01937 #    2. The end-user documentation included with the redistribution, if any,
01938 #    must include the following acknowledgment:
01939 #
01940 #       This product includes color specifications and designs developed by
01941 #       Cynthia Brewer (http://colorbrewer.org/).
01942 #
01943 #    Alternately, this acknowledgment may appear in the software itself, if and
01944 #    wherever such third-party acknowledgments normally appear.
01945 #
01946 #    3. The name "ColorBrewer" must not be used to endorse or promote products
01947 #    derived from this software without prior written permission. For written
01948 #    permission, please contact Cynthia Brewer at cbrewer@psu.edu.
01949 #
01950 #    4. Products derived from this software may not be called "ColorBrewer",
01951 #    nor may "ColorBrewer" appear in their name, without prior written
01952 #    permission of Cynthia Brewer.
01953 #
01954 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
01955 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
01956 # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CYNTHIA
01957 # BREWER, MARK HARROWER, OR THE PENNSYLVANIA STATE UNIVERSITY BE LIABLE FOR ANY
01958 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
01959 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
01960 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
01961 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
01962 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
01963 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
01964 brewer_colors = {
01965     'accent3': [(127, 201, 127), (190, 174, 212), (253, 192, 134)],
01966     'accent4': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153)],
01967     'accent5': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176)],
01968     'accent6': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127)],
01969     'accent7': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127), (191, 91, 23)],
01970     '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)],
01971     'blues3': [(222, 235, 247), (158, 202, 225), (49, 130, 189)],
01972     'blues4': [(239, 243, 255), (189, 215, 231), (107, 174, 214), (33, 113, 181)],
01973     'blues5': [(239, 243, 255), (189, 215, 231), (107, 174, 214), (49, 130, 189), (8, 81, 156)],
01974     'blues6': [(239, 243, 255), (198, 219, 239), (158, 202, 225), (107, 174, 214), (49, 130, 189), (8, 81, 156)],
01975     'blues7': [(239, 243, 255), (198, 219, 239), (158, 202, 225), (107, 174, 214), (66, 146, 198), (33, 113, 181), (8, 69, 148)],
01976     '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)],
01977     '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)],
01978     '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)],
01979     '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)],
01980     'brbg3': [(216, 179, 101), (245, 245, 245), (90, 180, 172)],
01981     'brbg4': [(166, 97, 26), (223, 194, 125), (128, 205, 193), (1, 133, 113)],
01982     'brbg5': [(166, 97, 26), (223, 194, 125), (245, 245, 245), (128, 205, 193), (1, 133, 113)],
01983     'brbg6': [(140, 81, 10), (216, 179, 101), (246, 232, 195), (199, 234, 229), (90, 180, 172), (1, 102, 94)],
01984     'brbg7': [(140, 81, 10), (216, 179, 101), (246, 232, 195), (245, 245, 245), (199, 234, 229), (90, 180, 172), (1, 102, 94)],
01985     '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)],
01986     '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)],
01987     'bugn3': [(229, 245, 249), (153, 216, 201), (44, 162, 95)],
01988     'bugn4': [(237, 248, 251), (178, 226, 226), (102, 194, 164), (35, 139, 69)],
01989     'bugn5': [(237, 248, 251), (178, 226, 226), (102, 194, 164), (44, 162, 95), (0, 109, 44)],
01990     'bugn6': [(237, 248, 251), (204, 236, 230), (153, 216, 201), (102, 194, 164), (44, 162, 95), (0, 109, 44)],
01991     'bugn7': [(237, 248, 251), (204, 236, 230), (153, 216, 201), (102, 194, 164), (65, 174, 118), (35, 139, 69), (0, 88, 36)],
01992     '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)],
01993     '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)],
01994     'bupu3': [(224, 236, 244), (158, 188, 218), (136, 86, 167)],
01995     'bupu4': [(237, 248, 251), (179, 205, 227), (140, 150, 198), (136, 65, 157)],
01996     'bupu5': [(237, 248, 251), (179, 205, 227), (140, 150, 198), (136, 86, 167), (129, 15, 124)],
01997     'bupu6': [(237, 248, 251), (191, 211, 230), (158, 188, 218), (140, 150, 198), (136, 86, 167), (129, 15, 124)],
01998     'bupu7': [(237, 248, 251), (191, 211, 230), (158, 188, 218), (140, 150, 198), (140, 107, 177), (136, 65, 157), (110, 1, 107)],
01999     '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)],
02000     '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)],
02001     'dark23': [(27, 158, 119), (217, 95, 2), (117, 112, 179)],
02002     'dark24': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138)],
02003     'dark25': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30)],
02004     'dark26': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30), (230, 171, 2)],
02005     'dark27': [(27, 158, 119), (217, 95, 2), (117, 112, 179), (231, 41, 138), (102, 166, 30), (230, 171, 2), (166, 118, 29)],
02006     '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)],
02007     'gnbu3': [(224, 243, 219), (168, 221, 181), (67, 162, 202)],
02008     'gnbu4': [(240, 249, 232), (186, 228, 188), (123, 204, 196), (43, 140, 190)],
02009     'gnbu5': [(240, 249, 232), (186, 228, 188), (123, 204, 196), (67, 162, 202), (8, 104, 172)],
02010     'gnbu6': [(240, 249, 232), (204, 235, 197), (168, 221, 181), (123, 204, 196), (67, 162, 202), (8, 104, 172)],
02011     'gnbu7': [(240, 249, 232), (204, 235, 197), (168, 221, 181), (123, 204, 196), (78, 179, 211), (43, 140, 190), (8, 88, 158)],
02012     '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)],
02013     '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)],
02014     'greens3': [(229, 245, 224), (161, 217, 155), (49, 163, 84)],
02015     'greens4': [(237, 248, 233), (186, 228, 179), (116, 196, 118), (35, 139, 69)],
02016     'greens5': [(237, 248, 233), (186, 228, 179), (116, 196, 118), (49, 163, 84), (0, 109, 44)],
02017     'greens6': [(237, 248, 233), (199, 233, 192), (161, 217, 155), (116, 196, 118), (49, 163, 84), (0, 109, 44)],
02018     'greens7': [(237, 248, 233), (199, 233, 192), (161, 217, 155), (116, 196, 118), (65, 171, 93), (35, 139, 69), (0, 90, 50)],
02019     '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)],
02020     '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)],
02021     'greys3': [(240, 240, 240), (189, 189, 189), (99, 99, 99)],
02022     'greys4': [(247, 247, 247), (204, 204, 204), (150, 150, 150), (82, 82, 82)],
02023     'greys5': [(247, 247, 247), (204, 204, 204), (150, 150, 150), (99, 99, 99), (37, 37, 37)],
02024     'greys6': [(247, 247, 247), (217, 217, 217), (189, 189, 189), (150, 150, 150), (99, 99, 99), (37, 37, 37)],
02025     'greys7': [(247, 247, 247), (217, 217, 217), (189, 189, 189), (150, 150, 150), (115, 115, 115), (82, 82, 82), (37, 37, 37)],
02026     '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)],
02027     '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)],
02028     'oranges3': [(254, 230, 206), (253, 174, 107), (230, 85, 13)],
02029     'oranges4': [(254, 237, 222), (253, 190, 133), (253, 141, 60), (217, 71, 1)],
02030     'oranges5': [(254, 237, 222), (253, 190, 133), (253, 141, 60), (230, 85, 13), (166, 54, 3)],
02031     'oranges6': [(254, 237, 222), (253, 208, 162), (253, 174, 107), (253, 141, 60), (230, 85, 13), (166, 54, 3)],
02032     'oranges7': [(254, 237, 222), (253, 208, 162), (253, 174, 107), (253, 141, 60), (241, 105, 19), (217, 72, 1), (140, 45, 4)],
02033     '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)],
02034     '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)],
02035     'orrd3': [(254, 232, 200), (253, 187, 132), (227, 74, 51)],
02036     'orrd4': [(254, 240, 217), (253, 204, 138), (252, 141, 89), (215, 48, 31)],
02037     'orrd5': [(254, 240, 217), (253, 204, 138), (252, 141, 89), (227, 74, 51), (179, 0, 0)],
02038     'orrd6': [(254, 240, 217), (253, 212, 158), (253, 187, 132), (252, 141, 89), (227, 74, 51), (179, 0, 0)],
02039     'orrd7': [(254, 240, 217), (253, 212, 158), (253, 187, 132), (252, 141, 89), (239, 101, 72), (215, 48, 31), (153, 0, 0)],
02040     '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)],
02041     '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)],
02042     '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)],
02043     '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)],
02044     '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)],
02045     'paired3': [(166, 206, 227), (31, 120, 180), (178, 223, 138)],
02046     'paired4': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44)],
02047     'paired5': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153)],
02048     'paired6': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28)],
02049     'paired7': [(166, 206, 227), (31, 120, 180), (178, 223, 138), (51, 160, 44), (251, 154, 153), (227, 26, 28), (253, 191, 111)],
02050     '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)],
02051     '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)],
02052     'pastel13': [(251, 180, 174), (179, 205, 227), (204, 235, 197)],
02053     'pastel14': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228)],
02054     'pastel15': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166)],
02055     'pastel16': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204)],
02056     'pastel17': [(251, 180, 174), (179, 205, 227), (204, 235, 197), (222, 203, 228), (254, 217, 166), (255, 255, 204), (229, 216, 189)],
02057     '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)],
02058     '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)],
02059     'pastel23': [(179, 226, 205), (253, 205, 172), (203, 213, 232)],
02060     'pastel24': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228)],
02061     'pastel25': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201)],
02062     'pastel26': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201), (255, 242, 174)],
02063     'pastel27': [(179, 226, 205), (253, 205, 172), (203, 213, 232), (244, 202, 228), (230, 245, 201), (255, 242, 174), (241, 226, 204)],
02064     '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)],
02065     '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)],
02066     '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)],
02067     'piyg3': [(233, 163, 201), (247, 247, 247), (161, 215, 106)],
02068     'piyg4': [(208, 28, 139), (241, 182, 218), (184, 225, 134), (77, 172, 38)],
02069     'piyg5': [(208, 28, 139), (241, 182, 218), (247, 247, 247), (184, 225, 134), (77, 172, 38)],
02070     'piyg6': [(197, 27, 125), (233, 163, 201), (253, 224, 239), (230, 245, 208), (161, 215, 106), (77, 146, 33)],
02071     'piyg7': [(197, 27, 125), (233, 163, 201), (253, 224, 239), (247, 247, 247), (230, 245, 208), (161, 215, 106), (77, 146, 33)],
02072     '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)],
02073     '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)],
02074     '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)],
02075     '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)],
02076     'prgn3': [(175, 141, 195), (247, 247, 247), (127, 191, 123)],
02077     'prgn4': [(123, 50, 148), (194, 165, 207), (166, 219, 160), (0, 136, 55)],
02078     'prgn5': [(123, 50, 148), (194, 165, 207), (247, 247, 247), (166, 219, 160), (0, 136, 55)],
02079     'prgn6': [(118, 42, 131), (175, 141, 195), (231, 212, 232), (217, 240, 211), (127, 191, 123), (27, 120, 55)],
02080     'prgn7': [(118, 42, 131), (175, 141, 195), (231, 212, 232), (247, 247, 247), (217, 240, 211), (127, 191, 123), (27, 120, 55)],
02081     '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)],
02082     '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)],
02083     'pubu3': [(236, 231, 242), (166, 189, 219), (43, 140, 190)],
02084     'pubu4': [(241, 238, 246), (189, 201, 225), (116, 169, 207), (5, 112, 176)],
02085     'pubu5': [(241, 238, 246), (189, 201, 225), (116, 169, 207), (43, 140, 190), (4, 90, 141)],
02086     'pubu6': [(241, 238, 246), (208, 209, 230), (166, 189, 219), (116, 169, 207), (43, 140, 190), (4, 90, 141)],
02087     'pubu7': [(241, 238, 246), (208, 209, 230), (166, 189, 219), (116, 169, 207), (54, 144, 192), (5, 112, 176), (3, 78, 123)],
02088     '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)],
02089     '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)],
02090     'pubugn3': [(236, 226, 240), (166, 189, 219), (28, 144, 153)],
02091     'pubugn4': [(246, 239, 247), (189, 201, 225), (103, 169, 207), (2, 129, 138)],
02092     'pubugn5': [(246, 239, 247), (189, 201, 225), (103, 169, 207), (28, 144, 153), (1, 108, 89)],
02093     'pubugn6': [(246, 239, 247), (208, 209, 230), (166, 189, 219), (103, 169, 207), (28, 144, 153), (1, 108, 89)],
02094     'pubugn7': [(246, 239, 247), (208, 209, 230), (166, 189, 219), (103, 169, 207), (54, 144, 192), (2, 129, 138), (1, 100, 80)],
02095     '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)],
02096     '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)],
02097     '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)],
02098     '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)],
02099     'puor3': [(241, 163, 64), (247, 247, 247), (153, 142, 195)],
02100     'puor4': [(230, 97, 1), (253, 184, 99), (178, 171, 210), (94, 60, 153)],
02101     'puor5': [(230, 97, 1), (253, 184, 99), (247, 247, 247), (178, 171, 210), (94, 60, 153)],
02102     'puor6': [(179, 88, 6), (241, 163, 64), (254, 224, 182), (216, 218, 235), (153, 142, 195), (84, 39, 136)],
02103     'puor7': [(179, 88, 6), (241, 163, 64), (254, 224, 182), (247, 247, 247), (216, 218, 235), (153, 142, 195), (84, 39, 136)],
02104     '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)],
02105     '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)],
02106     'purd3': [(231, 225, 239), (201, 148, 199), (221, 28, 119)],
02107     'purd4': [(241, 238, 246), (215, 181, 216), (223, 101, 176), (206, 18, 86)],
02108     'purd5': [(241, 238, 246), (215, 181, 216), (223, 101, 176), (221, 28, 119), (152, 0, 67)],
02109     'purd6': [(241, 238, 246), (212, 185, 218), (201, 148, 199), (223, 101, 176), (221, 28, 119), (152, 0, 67)],
02110     'purd7': [(241, 238, 246), (212, 185, 218), (201, 148, 199), (223, 101, 176), (231, 41, 138), (206, 18, 86), (145, 0, 63)],
02111     '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)],
02112     '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)],
02113     'purples3': [(239, 237, 245), (188, 189, 220), (117, 107, 177)],
02114     'purples4': [(242, 240, 247), (203, 201, 226), (158, 154, 200), (106, 81, 163)],
02115     'purples5': [(242, 240, 247), (203, 201, 226), (158, 154, 200), (117, 107, 177), (84, 39, 143)],
02116     'purples6': [(242, 240, 247), (218, 218, 235), (188, 189, 220), (158, 154, 200), (117, 107, 177), (84, 39, 143)],
02117     'purples7': [(242, 240, 247), (218, 218, 235), (188, 189, 220), (158, 154, 200), (128, 125, 186), (106, 81, 163), (74, 20, 134)],
02118     '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)],
02119     '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)],
02120     '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)],
02121     '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)],
02122     'rdbu3': [(239, 138, 98), (247, 247, 247), (103, 169, 207)],
02123     'rdbu4': [(202, 0, 32), (244, 165, 130), (146, 197, 222), (5, 113, 176)],
02124     'rdbu5': [(202, 0, 32), (244, 165, 130), (247, 247, 247), (146, 197, 222), (5, 113, 176)],
02125     'rdbu6': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (209, 229, 240), (103, 169, 207), (33, 102, 172)],
02126     'rdbu7': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (247, 247, 247), (209, 229, 240), (103, 169, 207), (33, 102, 172)],
02127     '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)],
02128     '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)],
02129     '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)],
02130     '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)],
02131     'rdgy3': [(239, 138, 98), (255, 255, 255), (153, 153, 153)],
02132     'rdgy4': [(202, 0, 32), (244, 165, 130), (186, 186, 186), (64, 64, 64)],
02133     'rdgy5': [(202, 0, 32), (244, 165, 130), (255, 255, 255), (186, 186, 186), (64, 64, 64)],
02134     'rdgy6': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (224, 224, 224), (153, 153, 153), (77, 77, 77)],
02135     'rdgy7': [(178, 24, 43), (239, 138, 98), (253, 219, 199), (255, 255, 255), (224, 224, 224), (153, 153, 153), (77, 77, 77)],
02136     '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)],
02137     '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)],
02138     'rdpu3': [(253, 224, 221), (250, 159, 181), (197, 27, 138)],
02139     'rdpu4': [(254, 235, 226), (251, 180, 185), (247, 104, 161), (174, 1, 126)],
02140     'rdpu5': [(254, 235, 226), (251, 180, 185), (247, 104, 161), (197, 27, 138), (122, 1, 119)],
02141     'rdpu6': [(254, 235, 226), (252, 197, 192), (250, 159, 181), (247, 104, 161), (197, 27, 138), (122, 1, 119)],
02142     'rdpu7': [(254, 235, 226), (252, 197, 192), (250, 159, 181), (247, 104, 161), (221, 52, 151), (174, 1, 126), (122, 1, 119)],
02143     '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)],
02144     '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)],
02145     '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)],
02146     '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)],
02147     'rdylbu3': [(252, 141, 89), (255, 255, 191), (145, 191, 219)],
02148     'rdylbu4': [(215, 25, 28), (253, 174, 97), (171, 217, 233), (44, 123, 182)],
02149     'rdylbu5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (171, 217, 233), (44, 123, 182)],
02150     'rdylbu6': [(215, 48, 39), (252, 141, 89), (254, 224, 144), (224, 243, 248), (145, 191, 219), (69, 117, 180)],
02151     'rdylbu7': [(215, 48, 39), (252, 141, 89), (254, 224, 144), (255, 255, 191), (224, 243, 248), (145, 191, 219), (69, 117, 180)],
02152     '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)],
02153     '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)],
02154     '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)],
02155     '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)],
02156     'rdylgn3': [(252, 141, 89), (255, 255, 191), (145, 207, 96)],
02157     'rdylgn4': [(215, 25, 28), (253, 174, 97), (166, 217, 106), (26, 150, 65)],
02158     'rdylgn5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (166, 217, 106), (26, 150, 65)],
02159     'rdylgn6': [(215, 48, 39), (252, 141, 89), (254, 224, 139), (217, 239, 139), (145, 207, 96), (26, 152, 80)],
02160     'rdylgn7': [(215, 48, 39), (252, 141, 89), (254, 224, 139), (255, 255, 191), (217, 239, 139), (145, 207, 96), (26, 152, 80)],
02161     '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)],
02162     '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)],
02163     'reds3': [(254, 224, 210), (252, 146, 114), (222, 45, 38)],
02164     'reds4': [(254, 229, 217), (252, 174, 145), (251, 106, 74), (203, 24, 29)],
02165     'reds5': [(254, 229, 217), (252, 174, 145), (251, 106, 74), (222, 45, 38), (165, 15, 21)],
02166     'reds6': [(254, 229, 217), (252, 187, 161), (252, 146, 114), (251, 106, 74), (222, 45, 38), (165, 15, 21)],
02167     'reds7': [(254, 229, 217), (252, 187, 161), (252, 146, 114), (251, 106, 74), (239, 59, 44), (203, 24, 29), (153, 0, 13)],
02168     '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)],
02169     '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)],
02170     'set13': [(228, 26, 28), (55, 126, 184), (77, 175, 74)],
02171     'set14': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163)],
02172     'set15': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0)],
02173     'set16': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51)],
02174     'set17': [(228, 26, 28), (55, 126, 184), (77, 175, 74), (152, 78, 163), (255, 127, 0), (255, 255, 51), (166, 86, 40)],
02175     '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)],
02176     '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)],
02177     'set23': [(102, 194, 165), (252, 141, 98), (141, 160, 203)],
02178     'set24': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195)],
02179     'set25': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84)],
02180     'set26': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84), (255, 217, 47)],
02181     'set27': [(102, 194, 165), (252, 141, 98), (141, 160, 203), (231, 138, 195), (166, 216, 84), (255, 217, 47), (229, 196, 148)],
02182     '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)],
02183     '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)],
02184     '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)],
02185     '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)],
02186     'set33': [(141, 211, 199), (255, 255, 179), (190, 186, 218)],
02187     'set34': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114)],
02188     'set35': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211)],
02189     'set36': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98)],
02190     'set37': [(141, 211, 199), (255, 255, 179), (190, 186, 218), (251, 128, 114), (128, 177, 211), (253, 180, 98), (179, 222, 105)],
02191     '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)],
02192     '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)],
02193     '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)],
02194     '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)],
02195     'spectral3': [(252, 141, 89), (255, 255, 191), (153, 213, 148)],
02196     'spectral4': [(215, 25, 28), (253, 174, 97), (171, 221, 164), (43, 131, 186)],
02197     'spectral5': [(215, 25, 28), (253, 174, 97), (255, 255, 191), (171, 221, 164), (43, 131, 186)],
02198     'spectral6': [(213, 62, 79), (252, 141, 89), (254, 224, 139), (230, 245, 152), (153, 213, 148), (50, 136, 189)],
02199     'spectral7': [(213, 62, 79), (252, 141, 89), (254, 224, 139), (255, 255, 191), (230, 245, 152), (153, 213, 148), (50, 136, 189)],
02200     '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)],
02201     '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)],
02202     'ylgn3': [(247, 252, 185), (173, 221, 142), (49, 163, 84)],
02203     'ylgn4': [(255, 255, 204), (194, 230, 153), (120, 198, 121), (35, 132, 67)],
02204     'ylgn5': [(255, 255, 204), (194, 230, 153), (120, 198, 121), (49, 163, 84), (0, 104, 55)],
02205     'ylgn6': [(255, 255, 204), (217, 240, 163), (173, 221, 142), (120, 198, 121), (49, 163, 84), (0, 104, 55)],
02206     'ylgn7': [(255, 255, 204), (217, 240, 163), (173, 221, 142), (120, 198, 121), (65, 171, 93), (35, 132, 67), (0, 90, 50)],
02207     '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)],
02208     '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)],
02209     'ylgnbu3': [(237, 248, 177), (127, 205, 187), (44, 127, 184)],
02210     'ylgnbu4': [(255, 255, 204), (161, 218, 180), (65, 182, 196), (34, 94, 168)],
02211     'ylgnbu5': [(255, 255, 204), (161, 218, 180), (65, 182, 196), (44, 127, 184), (37, 52, 148)],
02212     'ylgnbu6': [(255, 255, 204), (199, 233, 180), (127, 205, 187), (65, 182, 196), (44, 127, 184), (37, 52, 148)],
02213     'ylgnbu7': [(255, 255, 204), (199, 233, 180), (127, 205, 187), (65, 182, 196), (29, 145, 192), (34, 94, 168), (12, 44, 132)],
02214     '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)],
02215     '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)],
02216     'ylorbr3': [(255, 247, 188), (254, 196, 79), (217, 95, 14)],
02217     'ylorbr4': [(255, 255, 212), (254, 217, 142), (254, 153, 41), (204, 76, 2)],
02218     'ylorbr5': [(255, 255, 212), (254, 217, 142), (254, 153, 41), (217, 95, 14), (153, 52, 4)],
02219     'ylorbr6': [(255, 255, 212), (254, 227, 145), (254, 196, 79), (254, 153, 41), (217, 95, 14), (153, 52, 4)],
02220     'ylorbr7': [(255, 255, 212), (254, 227, 145), (254, 196, 79), (254, 153, 41), (236, 112, 20), (204, 76, 2), (140, 45, 4)],
02221     '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)],
02222     '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)],
02223     'ylorrd3': [(255, 237, 160), (254, 178, 76), (240, 59, 32)],
02224     'ylorrd4': [(255, 255, 178), (254, 204, 92), (253, 141, 60), (227, 26, 28)],
02225     'ylorrd5': [(255, 255, 178), (254, 204, 92), (253, 141, 60), (240, 59, 32), (189, 0, 38)],
02226     'ylorrd6': [(255, 255, 178), (254, 217, 118), (254, 178, 76), (253, 141, 60), (240, 59, 32), (189, 0, 38)],
02227     'ylorrd7': [(255, 255, 178), (254, 217, 118), (254, 178, 76), (253, 141, 60), (252, 78, 42), (227, 26, 28), (177, 0, 38)],
02228     '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)],
02229 }
02230 
02231 
02232 if __name__ == '__main__':
02233     main()


smach_viewer
Author(s): Jonathan Bohren
autogenerated on Thu Jun 22 2017 02:19:21