Skip to content

Commit b7ba472

Browse files
Add Mathematical Interlude (Part II) to Jupyter Book
- Created part_math.md with Venice floor mosaic introduction - Converted Chapter 9 (Math) covering trigonometry, circles, and rotations - Converted Chapter 10 (Applications) with sloping text, circular arrangements, network graphs, and Tony Hawk animation - Included all 20 Python code examples with complete content - Converted 12 PDF figures to PNG at 300 DPI (4 for Ch9, 8 for Ch10) - Updated _toc.yml to include Mathematical Interlude section - Preserved all mathematical equations and LaTeX content in Markdown format 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1198eff commit b7ba472

17 files changed

Lines changed: 626 additions & 1 deletion

jupyterbook/_toc.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,12 @@ parts:
4444
- file: chapter7/index
4545
title: Chapter 7 - Multiple Axes and Plots
4646
- file: chapter8/index
47-
title: Chapter 8 - Style Configuration
47+
title: Chapter 8 - Style Configuration
48+
- caption: Part II - Mathematical Interlude
49+
chapters:
50+
- file: part_math
51+
title: Part II - Mathematical Interlude
52+
- file: chapter9/index
53+
title: Chapter 9 - Math
54+
- file: chapter10/index
55+
title: Chapter 10 - Applications

jupyterbook/chapter10/index.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
# Chapter 10: Applications
2+
3+
## Sloping Text
4+
5+
Suppose you'd like to label a line.
6+
7+
For a sloped line, you might rather the text sit parallel to the line instead of suffering the below.
8+
9+
```python
10+
plt.plot([0,1], [0,1])
11+
plt.text(0.65, 0.5,
12+
s = 'label',
13+
size = 30)
14+
15+
ax = plt.gca()
16+
# Cosmetics
17+
ax.grid(False)
18+
ax.set_xticks([])
19+
ax.set_yticks([])
20+
```
21+
22+
```{figure} ../images/chapter10/no-slope.png
23+
:width: 60%
24+
:align: center
25+
```
26+
27+
The `rotation` argument can help if you know the right angle in degrees. Here the angle is 45 degrees or $\frac{\pi}{4}$ radians. So we modify the second line to be `plt.text(0.65, 0.5, 'label', size = 30, rotation = 45)`.
28+
29+
But this doesn't do what we want! The plot coordinate system is stretched, because we didn't call `ax.set_aspect('equal')` and `text` doesn't recalculate the text angle to make it align.
30+
31+
```{figure} ../images/chapter10/bad-slope.png
32+
:width: 60%
33+
:align: center
34+
```
35+
36+
Now let's solve it for good in the general case, using trigonometry and then `transform_angles`. This is a method that we'll use with the transformation `ax.transData`. Try experimenting by replacing the `x2,y2` values to see this works for any angle.
37+
38+
```python
39+
x1, y1 = 0, 0
40+
x2, y2 = 1, 1
41+
x = (x1, x2)
42+
y = (y1, y2)
43+
44+
# plot
45+
fig, ax = plt.figure(), plt.axes()
46+
ax.plot(x,y)
47+
48+
# Find angles and then insert text
49+
slope = (y2 - y1) / (x2 - x1)
50+
true_angle = math.degrees(math.atan(slope))
51+
52+
# dummy_array is the point where the angles are anchored
53+
dummy_array = np.array([[0,0]]) # doesn't matter what pair you use
54+
# matplotlib.org/stable/api/transformations.html#matplotlib.transforms.Transform.transform_angles
55+
56+
plot_angle = ax.transData.transform_angles(
57+
np.array((true_angle,)),
58+
dummy_array)[0]
59+
60+
ax.text(np.mean(x), np.mean(y),
61+
s = 'label',
62+
rotation = plot_angle,
63+
fontsize = 30,
64+
va = 'top',
65+
ha = 'center')
66+
67+
ax.grid(False)
68+
ax.set_xticks([])
69+
ax.set_yticks([])
70+
print(true_angle, plot_angle)
71+
```
72+
73+
```{figure} ../images/chapter10/slope-label.png
74+
:width: 60%
75+
:align: center
76+
```
77+
78+
## Circular Arrangements
79+
80+
With our knowledge of the unit circle, we can arrange some points in a circle with no additional help beyond the math package. This might be useful if you want to avoid mixing polar and Cartesian axes.
81+
82+
```python
83+
n_points = 10
84+
pie_angle = 360/n_points # angle of each slice
85+
starting_angle = 90
86+
87+
fig, ax = plt.subplots()
88+
89+
for i in range(n_points):
90+
91+
angle = starting_angle + i*pie_angle
92+
angle = math.radians(angle)
93+
x = math.cos(angle)
94+
y = math.sin(angle)
95+
96+
ax.plot([x],[y], 'o', markersize = 17 - i)
97+
98+
ax.set_aspect('equal')
99+
ax.axis('off')
100+
```
101+
102+
This code produces the following.
103+
104+
```{figure} ../images/chapter10/circle.png
105+
:width: 60%
106+
:align: center
107+
```
108+
109+
The below makes similar use of trigonometry to create a circle colored according to a gradient, like in [Chapter 6](../chapter6/index.md). I make use of `solid_capstyle = 'round'` to round the endpoints of the plotted line, creating a cleaner look compared to the default.
110+
111+
```python
112+
# make a circle gradient
113+
start_color = 255/256, 59/256, 48/256 # red
114+
end_color = 255/256, 255/256, 85/256 # yellow
115+
116+
# How many color changes
117+
segments = 130
118+
119+
# Create figure
120+
fig, ax = plt.figure(figsize = (8,8)), plt.axes()
121+
122+
# Start at 90 degrees and return clockwise
123+
angles = np.linspace(2.5*np.pi, np.pi/2, segments + 1)
124+
125+
# Create the intermediate colors
126+
colors = dict()
127+
for i in range(3):
128+
colors[i] = np.linspace(start_color[i], end_color[i], segments)
129+
130+
# plot each arc
131+
for i in range(segments):
132+
133+
start_angle = angles[i]
134+
end_angle = angles[i+1]
135+
angle_slice = np.linspace(start_angle, end_angle, 100)
136+
137+
x_values = np.cos(angle_slice)
138+
y_values = np.sin(angle_slice)
139+
140+
rgb = colors[0][i], colors[1][i], colors[2][i]
141+
142+
ax.plot(x_values, y_values,
143+
color = rgb,
144+
linewidth = 20,
145+
solid_capstyle = 'round')
146+
147+
ax.set_aspect('equal')
148+
ax.axis('off')
149+
```
150+
151+
```{figure} ../images/chapter10/circle-grad.png
152+
:width: 80%
153+
:align: center
154+
```
155+
156+
## Network Graphs
157+
158+
Networks are represented mathematically as graphs—a set of vertices and edges between them. In drawing a graph, there are many drawing algorithms available. For large networks or sophisticated algorithms, you should use something off the shelf in a package like [nxviz](https://nxviz.readthedocs.io/en/latest/index.html). For a small network, you might avoid dealing with NetworkX and nxviz and do the drawing yourself. We will work through two simple layouts: arc diagrams and a circular layout for an undirected graph.
159+
160+
An arc diagram places all points on a straight line. The links are drawn as arcs from one point to another.
161+
162+
Let's consider the complete graph with four vertices, where every pair is connected.
163+
164+
```python
165+
fig, ax = plt.figure(), plt.axes()
166+
x = np.linspace(0,1,4)
167+
ax.plot(x, np.zeros(4),
168+
marker = 'o',
169+
linestyle = '',
170+
markersize = 13)
171+
172+
angles = np.linspace(0,np.pi,100)
173+
for point in x:
174+
# connect other points
175+
other_x = x[x > point]
176+
# construct a half circle
177+
unit_x, unit_y = np.cos(angles), np.sin(angles)
178+
for other in other_x:
179+
# arc is centered between the two points
180+
shift = np.mean([point,other])
181+
r = (other - point)/2
182+
new_x = r*unit_x + shift
183+
new_y = r*unit_y
184+
ax.plot(new_x, new_y, zorder = -1)
185+
186+
ax.axis('off')
187+
ax.set_aspect(1.5)
188+
```
189+
190+
```{figure} ../images/chapter10/arc-graph.png
191+
:width: 70%
192+
:align: center
193+
```
194+
195+
Next we move on to a circular layout. This layout places each vertex along a circle. Spaced evenly and with just four vertices in our graph, this will in fact produce a square. We also label each edge.
196+
197+
```python
198+
fig, ax = plt.figure(), plt.axes()
199+
200+
n_points = 4
201+
202+
# Draw vertices
203+
angles = np.linspace(0, 2*np.pi, n_points + 1)[0:n_points]
204+
x = np.cos(angles)
205+
y = np.sin(angles)
206+
ax.plot(x, y,
207+
marker = 'o',
208+
linestyle = '',
209+
markersize = 13)
210+
211+
# Draw Edges
212+
points = [p for p in zip(x,y)]
213+
counter = 1
214+
for point, other in combinations(points,2):
215+
216+
x = [p[0] for p in (point, other)]
217+
y = [p[1] for p in (point, other)]
218+
ax.plot(x, y, zorder = -1)
219+
220+
# add a label
221+
label_point = .65*np.array(point) + .35*np.array(other)
222+
223+
run = x[1]-x[0]
224+
rotation = 90
225+
ha = 'left'
226+
if run != 0:
227+
line_slope = (y[1]-y[0])/(x[1]-x[0])
228+
rotation = math.atan(line_slope)
229+
rotation = math.degrees(rotation)
230+
ha = 'center'
231+
else:
232+
print(point, other, rotation)
233+
234+
# get rgb then blend with white
235+
line_color = mpl.colors.to_rgb("C"+str(counter))
236+
lighter = .8*np.ones(3) + .2*np.array(line_color)
237+
ax.text(label_point[0], label_point[1],
238+
'label', rotation = rotation,
239+
bbox = dict(facecolor = lighter),
240+
va = 'center',
241+
ha = 'center'
242+
)
243+
counter += 1
244+
245+
ax.axis('off')
246+
ax.set_aspect('equal')
247+
```
248+
249+
```{figure} ../images/chapter10/circle-graph.png
250+
:width: 83%
251+
:align: center
252+
```
253+
254+
## Tony Hawk's Vertical Loop
255+
256+
Tony Hawk became the first skateboarder to skate a vertical loop in 1998. We honor that accomplishment in two dimensions with the help of a rotation matrix. The unit circle is our vertical loop and we add two smaller circles to represent a skateboard. This is trigonometry. The small circles are placed along a ray from the origin of the unit circle to ensure they will lie tangent inside in the loop. In the first subplot, we place the skateboard at the bottom of the ramp. Though the same figure could be produced without using a rotation matrix, we use one so that the first subplot is essentially reused over and over by rotating the skateboard wheels up and around the loop.
257+
258+
```python
259+
thetas = np.linspace(0,2*np.pi,8)[0:-1]
260+
fig = plt.figure(figsize = (12,3))
261+
262+
# Set radius for skateboard wheels
263+
radius = 0.1
264+
265+
# Make individual subplots
266+
for key, theta in enumerate(thetas):
267+
rotation_matrix = np.matrix([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
268+
269+
# Create panel for one frame
270+
ax = fig.add_subplot(1, len(thetas), key+1)
271+
ax.set_aspect('equal')
272+
273+
# Plot the loop itself
274+
angles = np.linspace(0, 2*np.pi, 100)
275+
x = np.cos(angles)
276+
y = np.sin(angles)
277+
ax.plot(x,y)
278+
279+
# Make skateboard wheels at bottom of the ramp
280+
# and then rotate them counter-clockwise according to theta
281+
centers = list()
282+
for ang in 1.5*np.pi, 1.6*np.pi:
283+
center = (1-radius)*np.cos(ang), (1-radius)*np.sin(ang)
284+
285+
# rotate
286+
point = np.matrix(center).T
287+
rotated_point = rotation_matrix*point
288+
rotated_point = np.array(rotated_point).flatten()
289+
centers.append(rotated_point)
290+
291+
# make wheel around new center
292+
wheel_x = radius*x + rotated_point[0]
293+
wheel_y = radius*y + rotated_point[1]
294+
295+
ax.plot(wheel_x, wheel_y)
296+
297+
# connect the two wheel centers
298+
c1, c2 = centers
299+
ax.plot([c1[0],c2[0]], [c1[1],c2[1]])
300+
301+
ax.axis('off')
302+
303+
xlim = ax.get_xlim()
304+
ax.plot(xlim, [-1,-1],
305+
color = 'C0',
306+
zorder = -1)
307+
```
308+
309+
```{figure} ../images/chapter10/tony-hawk.png
310+
:width: 100%
311+
:align: center
312+
```

0 commit comments

Comments
 (0)