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