Skip to content

Commit 1198eff

Browse files
Add Chapters 5-8 to complete Part I - Prose
Chapter 5: Dates - Working with datetime and dateutil modules - Formatting date axes with DateFormatter - Time zone handling Chapter 6: Colors - Using colormaps and color cycles - RGB/RGBA color manipulation - Creating color gradients and 3D color cube visualization Chapter 7: Multiple Axes and Plots - Dual axes with twinx() and twiny() - Creating subplots with plt.subplots and add_subplot - Using GridSpec for complex layouts - ConnectionPatch for linking subplots Chapter 8: Style Configuration - Using rcParams for global styling - Creating custom .mplstyle files - NYT-style visualization example - Refactoring with helper functions All PDF figures converted to PNG at 300 DPI Complete Python code examples included 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9f7a66b commit 1198eff

38 files changed

Lines changed: 1104 additions & 1 deletion

jupyterbook/_toc.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ parts:
3636
- file: chapter3/index
3737
title: Chapter 3 - Plot Elements and Coordinate Systems
3838
- file: chapter4/index
39-
title: Chapter 4 - Text and Titles
39+
title: Chapter 4 - Text and Titles
40+
- file: chapter5/index
41+
title: Chapter 5 - Dates
42+
- file: chapter6/index
43+
title: Chapter 6 - Colors
44+
- file: chapter7/index
45+
title: Chapter 7 - Multiple Axes and Plots
46+
- file: chapter8/index
47+
title: Chapter 8 - Style Configuration

jupyterbook/chapter5/index.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Chapter 5: Dates
2+
3+
Matplotlib can handle dates, helping you to create better axis ticks and label formatting. Matplotlib's capabilities are built on the datetime and dateutil modules.
4+
5+
## 5.1 Plotting
6+
7+
Let's import some time series data. Below we use pandas integration and plot from a DataFrame with an index of pandas Timestamp values. Matplotlib recognizes these as dates and handles this reasonably well automatically, though the exact formatting could be improved.
8+
9+
```python
10+
url = 'https://github.com/alexanderthclark/MPL-Data/raw/main/WeatherAug1415Trends.csv'
11+
df = pd.read_csv(url, parse_dates = ['Time'])
12+
13+
fig, ax = plt.figure(), plt.axes()
14+
df.set_index("Time").plot(ax = ax)
15+
ax.set_title("Searches for \"weather\" spike in the morning.")
16+
ax.legend().set_visible(False)
17+
```
18+
19+
![Pandas dates](../images/chapter5/pd-dates.png)
20+
21+
Before we try to improve the formatting, see what happens if we try to use the axes plot method.
22+
23+
```python
24+
fig, ax = plt.figure(), plt.axes()
25+
ax.plot(df.Time, df.weather)
26+
```
27+
28+
![Axes dates](../images/chapter5/ax-dates.png)
29+
30+
You might find code using `plot_date()`, which used to be used in place of `plot()`. This is no longer necessary.
31+
32+
### 5.1.1 Time Zone Handling
33+
34+
For a deeper knowledge, see the `datetime.tzinfo` class and the `pytz` library. TK
35+
36+
## 5.2 Ticks and Formatting
37+
38+
### 5.2.1 Date Formats
39+
40+
The specific format of the displayed dates and times can be modified with `mdates.DateFormatter()`. This takes a format string and creates a formatter that can be passed to an axis method `set_major_formatter()` or `set_minor_formatter()`.
41+
42+
Here are some common format codes, applied to Sunday January 30, 2000, 11:59PM, local to Louisville, Kentucky. These can all be verified with `pd.Timestamp(year = 2000, month = 1, day = 30, hour = 23, minute = 59, tz = 'America/Kentucky/Louisville').strftime()`.
43+
44+
| Code | Output/Example |
45+
|------|----------------|
46+
| `'%Y'` | 4-Digit Year |
47+
| `'%m'` | Month Number |
48+
| `'%d'` | Day of Month |
49+
| `'%B'` | Month Name |
50+
| `'%H'` | 24-Hour Clock Hour |
51+
| `'%M'` | Minute |
52+
| `'%I'` | 12-Hour Clock Hour |
53+
| `'%p'` | AM or PM |
54+
| `'%A'` | Day of Week |
55+
| `'%Z'` | Timezone Name |
56+
| `'%Y-%m'` | `'2000-01'` |
57+
| `'%Y/%m/%d'` | `'2000/01/30'` |
58+
| `'%B %y'` | `'January 00'` |
59+
| `'%H:%M %Z'` | `'23:59 EST'` |
60+
| `'%A %I%p'` | `'Sunday 11PM'` |
61+
62+
A more complete list of format codes can be found at [strftime.org](https://strftime.org). Codes that generate actual names, like `'%A'` or `'%B'`, can be made lowercase to produce an abbreviated name. Notice that these formats create zero-padded numbers like `'07'` instead of `'7'`. On Mac or Linux, padding can be eliminated with the `'-'` modifier, using `'%-H'` or `'%-m'` instead of `'%H'` or `'%m'` for example. On Windows, use `'#'`.
63+
64+
```python
65+
fig, ax = plt.figure(), plt.axes()
66+
xformatter = mdates.DateFormatter('%H:%M')
67+
ax.plot(df.Time, df.weather)
68+
ax.set_title("Searches for \"weather\" spike in the morning.")
69+
ax.set_xlabel("Time (UTC)")
70+
ax.xaxis.set_major_formatter(xformatter)
71+
```
72+
73+
![Date format 1](../images/chapter5/date-fmt.png)
74+
75+
```python
76+
fig, ax = plt.figure(), plt.axes()
77+
xformatter = mdates.DateFormatter('%-I%p')
78+
ax.plot(df.Time, df.weather)
79+
ax.set_title("Searches for \"weather\" spike in the morning.")
80+
ax.set_xlabel("Time (UTC)")
81+
ax.xaxis.set_major_formatter(xformatter)
82+
```
83+
84+
![Date format 2](../images/chapter5/date-fmt2.png)

jupyterbook/chapter6/index.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Chapter 6: Colors
2+
3+
Methods like `plot` and `text` include a color parameter, which we've already made use of. While you can get pretty far simply using `color = 'blue'`, you might also make use of colormaps or set your own colors using hex strings or RGB(A) tuples.
4+
5+
## 6.1 Colormaps
6+
7+
According to the style sheet you are using, there will be some colormap and you will cycle through those colors by default when plotting (but not for text). The colors can be identified by the strings `'C0'`, `'C1'`, ... If, as in the default, your color map has only 10 distinct colors, then the eleventh color `'C10'` is valid, but simply refers to `'C0'` and the colors cycle from there. You'll notice that with successive plot calls on the same axes, the colors will automatically move through the colormap. This is not the case with text, as is demonstrated in the program below.
8+
9+
```python
10+
fig, ax = plt.figure(), plt.axes()
11+
for i in range(12):
12+
# Plot color automatically cycles through color map
13+
ax.plot([0,1], np.ones(2)*i)
14+
15+
# Text with default color on the left
16+
ax.text(0, i, 'C' + str(i),
17+
va = 'center', ha = 'right')
18+
19+
# Text with variable color on the right
20+
ax.text(1, i, 'C' + str(i),
21+
va = 'center', ha = 'left',
22+
color = 'C'+str(i))
23+
ax.axis('off')
24+
```
25+
26+
![Colors](../images/chapter6/colors.png)
27+
28+
## 6.2 Red, Green, Blue, Alpha
29+
30+
An RGB color is given by three values, specifying the amount of red, green, and blue. In matplotlib, these values are between zero and one (you might also see RGB values between zero and 255 elsewhere). These colors live inside a cube, as a particular color is a triple $(r,g,b) \in [0,1]^3$.
31+
32+
![Color cube front](../images/chapter6/color-cube.png) ![Color cube back](../images/chapter6/color-cube-back.png)
33+
34+
I like working with RGB tuples because they can be manipulated with mathematical operations. Two colors can easily be averaged or we can create a gradient between two.
35+
36+
```python
37+
# Set Colors
38+
green = 76, 217, 100
39+
green = np.array(green)/255
40+
blue = 90, 200, 250
41+
blue = np.array(blue)/255
42+
43+
# How many color changes
44+
segments = 100
45+
interval_starts = np.linspace(0, 1, segments)
46+
47+
fig, ax = plt.subplots(figsize = (8,8))
48+
49+
colors = dict()
50+
for i in range(3):
51+
colors[i] = np.linspace(blue[i], green[i], segments)
52+
53+
for i in range(segments-1):
54+
rgb = colors[0][i], colors[1][i], colors[2][i]
55+
x = interval_starts[i], interval_starts[i+1]
56+
y = (0.5, 0.5)
57+
ax.plot(x, y, color = rgb,
58+
linewidth = 20,
59+
solid_capstyle = 'round')
60+
61+
ax.set_aspect('equal')
62+
ax.axis('off')
63+
```
64+
65+
![Gradient](../images/chapter6/gradient.png)
66+
67+
Any color can be made lighter by averaging it with white, $(1,1,1)$, or darker by averaging it with black $(0,0,0)$. We can also find the inverse of an RGB color by simply subtracting that triple from $(1,1,1)$. RGBA tuples are very similar, adding a fourth *a*lpha value for the opacity.
68+
69+
With RGB and RGBA colors being so handy, you might want to convert strings like `'C0'` into RGB. `ColorConverter()` lets us do this, with the `to_rgb()` and `to_rgba()` methods. Below, we create another color gradient between the default `'C0'` blue, to `'C1'` orange, and on to light blue `'C9'`.
70+
71+
```python
72+
# Set Colors
73+
blue = mpl.colors.ColorConverter().to_rgb('C0')
74+
orange = mpl.colors.ColorConverter().to_rgb('C1')
75+
76+
n_colors = 10
77+
color_strings = dict()
78+
for i in range(n_colors):
79+
color_strings[i] = 'C'+str(i)
80+
segments = 1000 # How many color changes
81+
82+
fig, ax = plt.subplots(figsize = (14,8))
83+
84+
for c in range(n_colors - 1):
85+
color1 = mpl.colors.ColorConverter().to_rgb(color_strings[c])
86+
color2 = mpl.colors.ColorConverter().to_rgb(color_strings[c+1])
87+
88+
interval_starts = np.linspace(c, c+1, segments)
89+
colors = dict()
90+
for i in range(3):
91+
colors[i] = np.linspace(color1[i], color2[i], segments)
92+
93+
for i in range(segments-1):
94+
95+
rgb = colors[0][i], colors[1][i], colors[2][i]
96+
97+
x = interval_starts[i], interval_starts[i+1]
98+
y = [0.3,0.5]
99+
100+
ax.plot(x, y,
101+
color = rgb,
102+
linewidth = 20,
103+
solid_capstyle = 'round')
104+
105+
ax.text(c, .51,
106+
s = 'C'+str(c),
107+
va = 'bottom',
108+
size = 12,
109+
ha = 'center')
110+
111+
ax.text(9, .51,
112+
s = 'C9',
113+
va = 'bottom',
114+
size = 12,
115+
ha = 'center')
116+
117+
ax.set_aspect('equal')
118+
ax.axis('off')
119+
```
120+
121+
![Color map](../images/chapter6/color-map.png)
122+
123+
### Color Cube Code
124+
125+
Here is the code for one of the RGB color cubes.
126+
127+
```python
128+
from itertools import product
129+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
130+
131+
light_gray = [.98]*3
132+
fig = plt.figure(figsize = (6,6),
133+
facecolor = light_gray)
134+
ax = plt.axes(projection='3d',
135+
facecolor = light_gray)
136+
137+
# control how many cubes/color changes
138+
pieces = 10
139+
grid = np.linspace(0, 1, pieces)[:-1]
140+
width = grid[1] - grid[0]
141+
142+
# Make smaller cube units
143+
for x in grid:
144+
for y in grid:
145+
for z in grid:
146+
vertices = list()
147+
for prod in product([x,x+width],[y,y+width], [z,z+width]):
148+
vertices.append(list(prod))
149+
150+
faces = list()
151+
for key, face in enumerate([x,y,z]):
152+
# face is 0
153+
helper0 = [x for x in vertices if x[key] == face]
154+
helper1 = [x for x in vertices if x[key] == face + width]
155+
helper0.sort()
156+
helper0 = helper0[0:2] + helper0[::-1][0:2]
157+
helper1.sort()
158+
helper1 = helper1[0:2] + helper1[::-1][0:2]
159+
faces.append((helper0))
160+
faces.append(helper1)
161+
162+
facecolor = (x + width / 2,
163+
y + width / 2,
164+
z + width / 2)
165+
pc = Poly3DCollection(faces,
166+
facecolor = facecolor,
167+
edgecolor = 'black')
168+
ax.add_collection3d(pc)
169+
170+
# Label Axes
171+
ax.set_xlabel("Red")
172+
ax.set_ylabel('Green')
173+
ax.set_zlabel("Blue")
174+
175+
# Set Ticks
176+
ax.set_xticks([0,1])
177+
ax.set_yticks([0,1])
178+
ax.set_zticks([0,1])
179+
# Change padding
180+
ax.xaxis.set_tick_params(pad = 0.1)
181+
ax.yaxis.set_tick_params(pad = 0.1)
182+
ax.zaxis.set_tick_params(pad = 0.1)
183+
# Change azimuth
184+
angle = 45 # + 180 # for second cube
185+
ax.view_init(elev = None, azim = angle)
186+
# Zoom out so labels are not cut off
187+
ax.set_box_aspect([1,1,1], zoom = 0.86)
188+
```

0 commit comments

Comments
 (0)