Skip to content

Commit b89f650

Browse files
authored
Add files via upload
1 parent 2fce198 commit b89f650

File tree

2 files changed

+516
-0
lines changed

2 files changed

+516
-0
lines changed

diagram.py

+386
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
import matplotlib.pyplot as plt
2+
import matplotlib.patches as patches
3+
4+
from matplotlib.transforms import Bbox, TransformedBbox
5+
6+
# TODO: Study this https://matplotlib.org/stable/tutorials/text/annotations.html#sphx-glr-tutorials-text-annotations-py
7+
8+
9+
def override(d1, **d2):
10+
"""Add key-value pairs to d.
11+
12+
d1: dictionary
13+
d2: keyword args to add to d
14+
15+
returns: new dict
16+
"""
17+
d = d1.copy()
18+
d.update(d2)
19+
return d
20+
21+
def underride(d1, **d2):
22+
"""Add key-value pairs to d only if key is not in d.
23+
24+
d1: dictionary
25+
d2: keyword args to add to d
26+
27+
returns: new dict
28+
"""
29+
d = d2.copy()
30+
d.update(d1)
31+
return d
32+
33+
def diagram(width=5, height=1, **options):
34+
fig, ax = plt.subplots(**options)
35+
36+
# TODO: dpi in the notebook should be 100, in the book it should be 300 or 600
37+
fig.set_dpi(300)
38+
39+
# Set figure size
40+
fig.set_size_inches(width, height)
41+
42+
plt.rc('font', size=8)
43+
44+
# Set axes position
45+
ax.set_position([0, 0, 1, 1])
46+
47+
# Set x and y limits
48+
ax.set_xlim(0, width)
49+
ax.set_ylim(0, height)
50+
51+
# Remove the spines, ticks, and labels
52+
despine(ax)
53+
return ax
54+
55+
def despine(ax):
56+
# Remove the spines
57+
ax.spines['right'].set_visible(False)
58+
ax.spines['top'].set_visible(False)
59+
ax.spines['bottom'].set_visible(False)
60+
ax.spines['left'].set_visible(False)
61+
62+
# Remove the axis labels
63+
ax.set_xticklabels([])
64+
ax.set_yticklabels([])
65+
66+
# Remove the tick marks
67+
ax.tick_params(axis='both', which='both', length=0, width=0)
68+
69+
def adjust(x, y, bbox):
70+
"""Adjust the coordinates of a point based on a bounding box.
71+
72+
x: x coordinate
73+
y: y coordinate
74+
bbox: Bbox object
75+
76+
returns: tuple of coordinates
77+
"""
78+
width = bbox.width
79+
height = bbox.height + 0.2
80+
t = width, height, x - bbox.x0, y - bbox.y0 + 0.1
81+
return [round(x, 2) for x in t]
82+
83+
def get_bbox(ax, handle):
84+
bbox = handle.get_window_extent()
85+
transformed = TransformedBbox(bbox, ax.transData.inverted())
86+
return transformed
87+
88+
def draw_bbox(ax, bbox, **options):
89+
options = underride(options, facecolor='gray', alpha=0.1, linewidth=0)
90+
rect = patches.Rectangle((bbox.xmin, bbox.ymin), bbox.width, bbox.height, **options)
91+
handle = ax.add_patch(rect)
92+
bbox = get_bbox(ax, handle)
93+
return bbox
94+
95+
def draw_box_around(ax, bboxes, **options):
96+
bbox = Bbox.union(bboxes)
97+
return draw_bbox(ax, padded(bbox), **options)
98+
99+
def padded(bbox, dx=0.1, dy=0.1):
100+
"""Add padding to a bounding box.
101+
"""
102+
[x0, y0], [x1, y1] = bbox.get_points()
103+
return Bbox([[x0-dx, y0-dy], [x1+dx, y1+dy]])
104+
105+
def make_binding(name, value, **options):
106+
"""Make a binding between a name and a value.
107+
108+
name: string
109+
value: any type
110+
111+
returns: Binding object
112+
"""
113+
if not isinstance(value, Frame):
114+
value = Value(repr(value))
115+
116+
return Binding(Value(name), value, **options)
117+
118+
def make_mapping(key, value, **options):
119+
"""Make a binding between a key and a value.
120+
121+
key: any type
122+
value: any type
123+
124+
returns: Binding object
125+
"""
126+
return Binding(Value(repr(key)), Value(repr(value)), **options)
127+
128+
def make_dict(d, name='dict', **options):
129+
"""Make a Frame that represents a dictionary.
130+
131+
d: dictionary
132+
name: string
133+
options: passed to Frame
134+
"""
135+
mappings = [make_mapping(key, value) for key, value in d.items()]
136+
return Frame(mappings, name=name, **options)
137+
138+
def make_frame(d, name='frame', **options):
139+
"""Make a Frame that represents a stack frame.
140+
141+
d: dictionary
142+
name: string
143+
options: passed to Frame
144+
"""
145+
bindings = [make_binding(key, value) for key, value in d.items()]
146+
return Frame(bindings, name=name, **options)
147+
148+
class Binding(object):
149+
def __init__(self, name, value=None, **options):
150+
""" Represents a binding between a name and a value.
151+
152+
name: Value object
153+
value: Value object
154+
"""
155+
self.name = name
156+
self.value = value
157+
self.options = options
158+
159+
def draw(self, ax, x, y, **options):
160+
options = override(self.options, **options)
161+
dx = options.pop('dx', 0.4)
162+
dy = options.pop('dy', 0)
163+
draw_value = options.pop('draw_value', True)
164+
165+
bbox1 = self.name.draw(ax, x, y, ha='right')
166+
bboxes = [bbox1]
167+
168+
arrow = Arrow(dx=dx, dy=dy, **options)
169+
bbox2 = arrow.draw(ax, x, y)
170+
171+
if draw_value:
172+
bbox3 = self.value.draw(ax, x+dx, y+dy)
173+
# only include the arrow if we drew the value
174+
bboxes.extend([bbox2, bbox3])
175+
176+
bbox = Bbox.union(bboxes)
177+
# draw_bbox(ax, self.bbox)
178+
self.bbox = bbox
179+
return bbox
180+
181+
182+
class Element(object):
183+
def __init__(self, index, value, **options):
184+
""" Represents a an element of a list.
185+
186+
index: integer
187+
value: Value object
188+
"""
189+
self.index = index
190+
self.value = value
191+
self.options = options
192+
193+
def draw(self, ax, x, y, dx=0.15, **options):
194+
options = override(self.options, **options)
195+
draw_value = options.pop('draw_value', True)
196+
197+
bbox1 = self.index.draw(ax, x, y, ha='right', fontsize=6, color='gray')
198+
bboxes = [bbox1]
199+
200+
if draw_value:
201+
bbox2 = self.value.draw(ax, x+dx, y)
202+
bboxes.append(bbox2)
203+
204+
bbox = Bbox.union(bboxes)
205+
self.bbox = bbox
206+
# draw_bbox(ax, self.bbox)
207+
return bbox
208+
209+
210+
class Value(object):
211+
def __init__(self, value):
212+
self.value = value
213+
self.options = dict(ha='left', va='center')
214+
self.bbox = None
215+
216+
def draw(self, ax, x, y, **options):
217+
options = override(self.options, **options)
218+
219+
handle = ax.text(x, y, self.value, **options)
220+
bbox = self.bbox = get_bbox(ax, handle)
221+
# draw_bbox(ax, bbox)
222+
self.bbox = bbox
223+
return bbox
224+
225+
226+
class Arrow(object):
227+
def __init__(self, **options):
228+
# Note for the future about dotted arrows
229+
# self.arrowprops = dict(arrowstyle="->", ls=':')
230+
arrowprops = dict(arrowstyle="->", color='gray')
231+
options = underride(options, arrowprops=arrowprops)
232+
self.options = options
233+
234+
def draw(self, ax, x, y, **options):
235+
options = override(self.options, **options)
236+
dx = options.pop('dx', 0.5)
237+
dy = options.pop('dy', 0)
238+
shim = options.pop('shim', 0.02)
239+
240+
handle = ax.annotate("", [x+dx, y+dy], [x+shim, y], **options)
241+
bbox = get_bbox(ax, handle)
242+
self.bbox = bbox
243+
return bbox
244+
245+
246+
class ReturnArrow(object):
247+
def __init__(self, **options):
248+
style = "Simple, tail_width=0.5, head_width=4, head_length=8"
249+
options = underride(options, arrowstyle=style, color="gray")
250+
self.options = options
251+
252+
def draw(self, ax, x, y, **options):
253+
options = override(self.options, **options)
254+
value = options.pop('value', None)
255+
dx = options.pop('dx', 0)
256+
dy = options.pop('dy', 0.4)
257+
shim = options.pop('shim', 0.02)
258+
259+
x += shim
260+
arrow = patches.FancyArrowPatch((x, y), (x+dx, y+dy),
261+
connectionstyle="arc3,rad=.6", **options)
262+
handle = ax.add_patch(arrow)
263+
bbox = get_bbox(ax, handle)
264+
265+
if value is not None:
266+
handle = plt.text(x+0.15, y+dy/2, str(value), ha='left', va='center')
267+
bbox2 = get_bbox(ax, handle)
268+
bbox = Bbox.union([bbox, bbox2])
269+
270+
self.bbox = bbox
271+
return bbox
272+
273+
274+
class Frame(object):
275+
def __init__(self, bindings, **options):
276+
self.bindings = bindings
277+
self.options = options
278+
279+
def draw(self, ax, x, y, **options):
280+
options = override(self.options, **options)
281+
name = options.pop('name', '')
282+
value = options.pop('value', None)
283+
dx = options.pop('dx', 0)
284+
dy = options.pop('dy', 0)
285+
offsetx = options.pop('offsetx', 0)
286+
offsety = options.pop('offsety', 0)
287+
shim = options.pop('shim', 0)
288+
loc = options.pop('loc', 'top')
289+
box_around = options.pop('box_around', None)
290+
291+
x += offsetx
292+
y += offsety
293+
save_y = y
294+
295+
if len(self.bindings) == 0:
296+
bbox = Bbox([[x, y], [x, y]])
297+
bboxes = [bbox]
298+
else:
299+
bboxes = []
300+
301+
# draw the bindings
302+
for binding in self.bindings:
303+
bbox = binding.draw(ax, x, y)
304+
bboxes.append(bbox)
305+
x += dx
306+
y += dy
307+
308+
if box_around:
309+
bbox1 = draw_bbox(ax, box_around, **options)
310+
else:
311+
bbox1 = draw_box_around(ax, bboxes, **options)
312+
bboxes.append(bbox1)
313+
314+
if value is not None:
315+
arrow = ReturnArrow(value=value)
316+
x = bbox1.xmax + shim
317+
bbox2 = arrow.draw(ax, x, save_y, value=value)
318+
bboxes.append(bbox2)
319+
320+
if name:
321+
if loc == 'top':
322+
x = bbox1.xmin
323+
y = bbox1.ymax + 0.02
324+
handle = plt.text(x, y, name, ha='left', va='bottom')
325+
elif loc == 'left':
326+
x = bbox1.xmin - 0.1
327+
y = save_y
328+
handle = plt.text(x, y, name, ha='right', va='center')
329+
bbox3 = get_bbox(ax, handle)
330+
bboxes.append(bbox3)
331+
332+
bbox = Bbox.union(bboxes)
333+
self.bbox = bbox
334+
return bbox
335+
336+
337+
class Stack(object):
338+
def __init__(self, frames, **options):
339+
self.frames = frames
340+
self.options = options
341+
342+
def draw(self, ax, x, y, **options):
343+
options = override(self.options, **options)
344+
dx = options.pop('dx', 0)
345+
dy = options.pop('dy', -0.4)
346+
347+
# draw the frames
348+
bboxes = []
349+
for frame in self.frames:
350+
bbox = frame.draw(ax, x, y)
351+
bboxes.append(bbox)
352+
x += dx
353+
y += dy
354+
355+
bbox = Bbox.union(bboxes)
356+
self.bbox = bbox
357+
return bbox
358+
359+
def make_rebind(name, seq):
360+
bindings = []
361+
for i, value in enumerate(seq):
362+
dy = dy=-0.3*i
363+
if i == len(seq)-1:
364+
binding = make_binding(name, value, dy=dy)
365+
else:
366+
arrowprops = dict(arrowstyle="->", color='gray', ls=':')
367+
binding = make_binding('', value, dy=dy, arrowprops=arrowprops)
368+
bindings.append(binding)
369+
370+
return bindings
371+
372+
def make_element(index, value):
373+
return Element(Value(index), Value(repr(value)))
374+
375+
def make_list(seq, name='list', **options):
376+
elements = [make_element(index, value) for index, value in enumerate(seq)]
377+
return Frame(elements, name=name, **options)
378+
379+
def draw_bindings(bindings, ax, x, y):
380+
bboxes = []
381+
for binding in bindings:
382+
bbox = binding.draw(ax, x, y)
383+
bboxes.append(bbox)
384+
385+
bbox = Bbox.union(bboxes)
386+
return bbox

0 commit comments

Comments
 (0)