Delete JunctionProjectDefs.cmake; simplify AddSample.cmake and move it to samples
[junction.git] / samples / MapMemoryBench / RenderGraphs.py
1 #!/usr/bin/env python
2 import os
3 import cairo
4 import math
5 import glob
6
7
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)
17
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]
25     with Saved(cr):
26         cr.set_scaled_font(scaledFont)
27         cr.move_to(math.floor(x + 0.5 - width * alignment), math.floor(y + 0.5))
28         cr.text_path(text)
29         cr.fill()
30
31 class Saved():
32     """ Preserve cairo state inside the scope of a with statement. """
33     def __init__(self, cr):
34         self.cr = cr
35     def __enter__(self):
36         self.cr.save()
37         return self.cr
38     def __exit__(self, type, value, traceback):
39         self.cr.restore()
40
41
42 #---------------------------------------------------
43 #  AxisAttribs
44 #---------------------------------------------------
45 class AxisAttribs:
46     """ Describes one axis on the graph. Can be linear or logarithmic. """
47     
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)
57
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
62     
63     def iterLabels(self):
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):
68             value = i * self.step
69             if self.min == 0 and i == 0:
70                 continue
71             yield self.mapAxisValue(value), self.labeler(self.fromAxis(value))
72
73
74 #---------------------------------------------------
75 #  Graph
76 #---------------------------------------------------
77 class Curve:
78     def __init__(self, name, points, color):
79         self.name = name
80         self.points = points
81         self.color = color
82         
83 class Graph:
84     """ Renders a graph. """
85     
86     def __init__(self, xAttribs, yAttribs):
87         self.xAttribs = xAttribs
88         self.yAttribs = yAttribs
89         self.curves = []
90
91     def addCurve(self, curve):
92         self.curves.append(curve)
93
94     def renderTo(self, fileName):
95         xAttribs = self.xAttribs
96         yAttribs = self.yAttribs
97
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)
102         cr.paint()
103         cr.set_miter_limit(1.414)
104         cr.translate(58, 11 + yAttribs.size)
105
106         # Draw axes
107         labelFont = createScaledFont('Arial', 11)
108         with Saved(cr):
109             cr.set_line_width(1)
110             cr.set_source_rgb(.4, .4, .4)
111
112             # Horizontal axis
113             cr.move_to(0, -0.5)
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
117                 cr.move_to(x, -1)
118                 cr.rel_line_to(0, 4)
119             cr.stroke()
120             for pos, label in xAttribs.iterLabels():    # Labels
121                 x = math.floor(pos + 0.5)
122                 with Saved(cr):
123                     cr.translate(x - 1, 5)
124                     cr.rotate(-math.pi / 4)
125                     fillAlignedText(cr, 0, 6, labelFont, label, 1)
126
127             # Vertical axis
128             cr.move_to(0.5, 0)
129             cr.rel_line_to(0, -yAttribs.size - 0.5)
130             for pos, label in yAttribs.iterLabels():    # Tick marks
131                 if label == '0':
132                     continue
133                 y = -math.floor(pos + 0.5) - 0.5
134                 cr.move_to(1, y)
135                 cr.rel_line_to(-4, 0)
136             cr.stroke()
137             for pos, label in yAttribs.iterLabels():    # Labels
138                 if label == '0':
139                     continue
140                 fillAlignedText(cr, -4, -pos + 4, labelFont, label, 1)
141
142         # Draw curves
143         for curve in self.curves:
144             points = curve.points
145             width = 2.5
146             color = curve.color
147             with Saved(cr):
148                 cr.set_line_width(width)
149                 cr.set_source_rgba(*color)
150                 with Saved(cr):
151                     cr.rectangle(0, 5, xAttribs.size, -yAttribs.size - 15)
152                     cr.clip()
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)
156                     cr.stroke()
157
158                 # Label
159                 labelFont = createScaledFont('Arial', 11)
160                 label = curve.name
161                 x, y, yHi = points[-1]
162                 fillAlignedText(cr, xAttribs.mapAxisValue(x) + 3, -yAttribs.mapAxisValue(y) + 4, labelFont, label, 0)
163
164         # Draw axis names
165         cr.set_source_rgb(0, 0, 0)
166         axisFont = createScaledFont('Helvetica', 14, weight=cairo.FONT_WEIGHT_BOLD)
167         with Saved(cr):
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)
172
173         # Save PNG file
174         surface.write_to_png(fileName)
175
176
177 #---------------------------------------------------
178 #  main
179 #---------------------------------------------------
180 graph = Graph(AxisAttribs(600, 0, 1000000, 200000), AxisAttribs(320, 0, 50000000, 10000000))    
181 COLORS = [
182     (1, 0, 0),
183     (1, 0.5, 0),
184     (0.5, 0.5, 0),
185     (0, 1, 0),
186     (0, 0.5, 1),
187     (0, 0, 1),
188     (1, 0, 1)
189 ]
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')