8 #---------------------------------------------------
9 # Cairo drawing helpers
10 #---------------------------------------------------
11 def createScaledFont(family, size, slant=cairo.FONT_SLANT_NORMAL, weight=cairo.FONT_WEIGHT_NORMAL):
12 """ Simple helper function to create a cairo ScaledFont. """
13 face = cairo.ToyFontFace(family, slant, weight)
14 DEFAULT_FONT_OPTIONS = cairo.FontOptions()
15 DEFAULT_FONT_OPTIONS.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
16 return cairo.ScaledFont(face, cairo.Matrix(xx=size, yy=size), cairo.Matrix(), DEFAULT_FONT_OPTIONS)
18 def fillAlignedText(cr, x, y, scaledFont, text, alignment = 0):
19 """ Draw some aligned text at the specified co-ordinates.
20 alignment = 0: left-justify
21 alignment = 0.5: center
22 alignment = 1: right-justify """
23 ascent, descent = scaledFont.extents()[:2]
24 x_bearing, y_bearing, width, height = scaledFont.text_extents(text)[:4]
26 cr.set_scaled_font(scaledFont)
27 cr.move_to(math.floor(x + 0.5 - width * alignment), math.floor(y + 0.5))
32 """ Preserve cairo state inside the scope of a with statement. """
33 def __init__(self, cr):
38 def __exit__(self, type, value, traceback):
42 #---------------------------------------------------
44 #---------------------------------------------------
46 """ Describes one axis on the graph. Can be linear or logarithmic. """
48 def __init__(self, size, min, max, step, logarithmic = False, labeler = lambda x: str(int(x + 0.5))):
49 self.size = float(size)
50 self.logarithmic = logarithmic
51 self.labeler = labeler
52 self.toAxis = lambda x: math.log(x) if logarithmic else float(x)
53 self.fromAxis = lambda x: math.exp(x) if logarithmic else float(x)
54 self.min = self.toAxis(min)
55 self.max = self.toAxis(max)
56 self.step = self.toAxis(step)
58 def mapAxisValue(self, x):
59 """ Maps x to a point along the axis.
60 x should already have been filtered through self.toAxis(), especially if logarithmic. """
61 return (x - self.min) / (self.max - self.min) * self.size
64 """ Helper to iterate through all the tick marks along the axis. """
65 lo = int(math.floor(self.min / self.step + 1 - 1e-9))
66 hi = int(math.floor(self.max / self.step + 1e-9))
67 for i in xrange(lo, hi + 1):
69 if self.min == 0 and i == 0:
71 yield self.mapAxisValue(value), self.labeler(self.fromAxis(value))
74 #---------------------------------------------------
76 #---------------------------------------------------
78 def __init__(self, name, points, color):
84 """ Renders a graph. """
86 def __init__(self, xAttribs, yAttribs):
87 self.xAttribs = xAttribs
88 self.yAttribs = yAttribs
91 def addCurve(self, curve):
92 self.curves.append(curve)
94 def renderTo(self, fileName):
95 xAttribs = self.xAttribs
96 yAttribs = self.yAttribs
98 # Create the image surface and cairo context
99 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 140 + int(xAttribs.size + 0.5), 65 + int(yAttribs.size + 0.5))
100 cr = cairo.Context(surface)
101 cr.set_source_rgb(1, 1, 1)
103 cr.set_miter_limit(1.414)
104 cr.translate(58, 11 + yAttribs.size)
107 labelFont = createScaledFont('Arial', 11)
110 cr.set_source_rgb(.4, .4, .4)
114 cr.rel_line_to(xAttribs.size + 1, 0)
115 for pos, label in xAttribs.iterLabels(): # Tick marks
116 x = math.floor(pos + 0.5) + 0.5
120 for pos, label in xAttribs.iterLabels(): # Labels
121 x = math.floor(pos + 0.5)
123 cr.translate(x - 1, 5)
124 cr.rotate(-math.pi / 4)
125 fillAlignedText(cr, 0, 6, labelFont, label, 1)
129 cr.rel_line_to(0, -yAttribs.size - 0.5)
130 for pos, label in yAttribs.iterLabels(): # Tick marks
133 y = -math.floor(pos + 0.5) - 0.5
135 cr.rel_line_to(-4, 0)
137 for pos, label in yAttribs.iterLabels(): # Labels
140 fillAlignedText(cr, -4, -pos + 4, labelFont, label, 1)
143 for curve in self.curves:
144 points = curve.points
148 cr.set_line_width(width)
149 cr.set_source_rgba(*color)
151 cr.rectangle(0, 5, xAttribs.size, -yAttribs.size - 15)
153 cr.move_to(xAttribs.mapAxisValue(points[0][0]), -yAttribs.mapAxisValue(points[0][1]))
154 for x, y, yHi in points[1:]:
155 cr.line_to(xAttribs.mapAxisValue(x) + 0.5, -yAttribs.mapAxisValue(y) - 0.5)
159 labelFont = createScaledFont('Arial', 11)
161 x, y, yHi = points[-1]
162 fillAlignedText(cr, xAttribs.mapAxisValue(x) + 3, -yAttribs.mapAxisValue(y) + 4, labelFont, label, 0)
165 cr.set_source_rgb(0, 0, 0)
166 axisFont = createScaledFont('Helvetica', 14, weight=cairo.FONT_WEIGHT_BOLD)
168 cr.translate(-47, -yAttribs.size / 2.0)
169 cr.rotate(-math.pi / 2)
170 fillAlignedText(cr, 0, 0, axisFont, "Bytes In Use", 0.5)
171 fillAlignedText(cr, xAttribs.size / 2.0, 50, axisFont, "Population", 0.5)
174 surface.write_to_png(fileName)
177 #---------------------------------------------------
179 #---------------------------------------------------
180 graph = Graph(AxisAttribs(600, 0, 1000000, 200000), AxisAttribs(320, 0, 50000000, 10000000))
190 for i, fn in enumerate(glob.glob('build*/results.txt')):
191 points = eval(open(fn, 'r').read())
192 graph.addCurve(Curve(os.path.split(fn)[0], points, COLORS[i % len(COLORS)]))
193 graph.renderTo('out.png')