# Real Python Matplotlib Tutorial

### Resources¶

In [56]:
```import matplotlib.pyplot as plt
```
In [57]:
```import numpy as np
```
In [58]:
```np.random.seed(444)
```
• One important big-picture matplotlib concept is its object hierarchy.

• A “hierarchy” here means that there is a tree-like structure of matplotlib objects underlying each plot.

• A Figure object is the outermost container for a matplotlib graphic, which can contain multiple Axes objects. One source of confusion is the name: an Axes actually translates into what we think of as an individual plot or graph (rather than the plural of “axis,” as we might expect).

In [59]:
```fig, _ = plt.subplots()
```
In [60]:
```type(one_tick := fig.axes[0].yaxis.get_major_ticks()[0])
```
Out[60]:
`matplotlib.axis.YTick`
• Notice that we didn’t pass arguments to subplots() here. The default call is subplots(nrows=1, ncols=1)

In [61]:
```fig, ax = plt.subplots()
```
In [62]:
```type(ax)
```
Out[62]:
`matplotlib.axes._subplots.AxesSubplot`
• We can call its instance methods to manipulate the plot similarly to how we call pyplots functions. Let’s illustrate with a stacked area graph of three time series

In [63]:
```(rng := np.arange(50))
```
Out[63]:
```array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49])```
In [64]:
```(rnd := np.random.randint(0, 10, size=(3, rng.size)))
```
Out[64]:
```array([[3, 0, 7, 8, 3, 4, 7, 6, 8, 9, 2, 2, 2, 0, 3, 8, 0, 6, 6, 0, 3, 0,
6, 7, 9, 3, 8, 7, 3, 2, 6, 9, 2, 9, 8, 9, 3, 2, 2, 8, 1, 5, 6, 7,
6, 0, 0, 0, 0, 4],
[8, 1, 9, 8, 5, 8, 9, 4, 6, 6, 4, 1, 8, 2, 7, 9, 3, 4, 2, 5, 0, 0,
8, 1, 0, 9, 9, 3, 2, 7, 6, 0, 5, 5, 4, 8, 3, 4, 9, 4, 7, 1, 5, 4,
4, 0, 2, 2, 5, 8],
[5, 6, 6, 1, 1, 6, 8, 4, 1, 0, 9, 2, 3, 7, 3, 3, 2, 7, 8, 6, 6, 7,
5, 7, 3, 9, 1, 3, 0, 4, 7, 5, 1, 5, 1, 4, 9, 7, 2, 4, 3, 7, 9, 2,
2, 0, 1, 5, 2, 4]])```
In [65]:
```(yrs := 1950 + rng)
```
Out[65]:
```array([1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960,
1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971,
1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982,
1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993,
1994, 1995, 1996, 1997, 1998, 1999])```
In [66]:
```fig, ax = plt.subplots(figsize=(5, 3))
```
In [67]:
```ax.stackplot(yrs, rng + rnd, labels=["Eastasia", "Eurasia", "Oceania"])
```
Out[67]:
```[<matplotlib.collections.PolyCollection at 0x7ff3b986e640>,
<matplotlib.collections.PolyCollection at 0x7ff3b9842c30>,
<matplotlib.collections.PolyCollection at 0x7ff3b981aaf0>]```
In [68]:
```ax.set_title("Combined debt growth over time")
```
Out[68]:
`Text(0.5, 1, 'Combined debt growth over time')`
In [69]:
```ax.legend(loc="upper left")
```
Out[69]:
`<matplotlib.legend.Legend at 0x7ff3b97ccfa0>`
In [70]:
```ax.set_ylabel("Total debt")
```
Out[70]:
`Text(3.200000000000003, 0.5, 'Total debt')`
In [71]:
```ax.set_xlim(xmin=yrs[0], xmax=yrs[-1])
```
Out[71]:
`(1950, 1999)`
In [72]:
```fig.tight_layout()
```
In [73]:
```fig
```
Out[73]:

Let’s look at an example with multiple subplots (Axes) within one Figure, plotting two correlated arrays that are drawn from the discrete uniform distribution:

In [74]:
```(x := np.random.randint(low=1, high=11, size=50))
```
Out[74]:
```array([ 9,  1,  5,  6, 10,  9,  7,  7, 10,  6,  8,  6,  4,  9,  3,  7,  6,
9,  2, 10,  7,  2,  2,  5,  7,  9,  5,  9,  9,  8,  6,  3,  4,  3,
1,  1,  5,  7,  6,  4,  4,  1,  9,  5, 10,  3,  5,  4,  1,  9])```
In [75]:
```(y := x + np.random.randint(1, 5, size=x.size))
```
Out[75]:
```array([11,  5,  6,  7, 14, 13,  8, 10, 11,  8, 10,  7,  6, 11,  6, 10,  8,
13,  6, 11, 10,  6,  6,  9,  9, 13,  8, 12, 12, 11,  9,  4,  6,  5,
4,  2,  9,  8,  7,  8,  6,  3, 13,  8, 12,  4,  9,  7,  4, 11])```
In [76]:
```(data := np.column_stack((x, y)))
```
Out[76]:
```array([[ 9, 11],
[ 1,  5],
[ 5,  6],
[ 6,  7],
[10, 14],
[ 9, 13],
[ 7,  8],
[ 7, 10],
[10, 11],
[ 6,  8],
[ 8, 10],
[ 6,  7],
[ 4,  6],
[ 9, 11],
[ 3,  6],
[ 7, 10],
[ 6,  8],
[ 9, 13],
[ 2,  6],
[10, 11],
[ 7, 10],
[ 2,  6],
[ 2,  6],
[ 5,  9],
[ 7,  9],
[ 9, 13],
[ 5,  8],
[ 9, 12],
[ 9, 12],
[ 8, 11],
[ 6,  9],
[ 3,  4],
[ 4,  6],
[ 3,  5],
[ 1,  4],
[ 1,  2],
[ 5,  9],
[ 7,  8],
[ 6,  7],
[ 4,  8],
[ 4,  6],
[ 1,  3],
[ 9, 13],
[ 5,  8],
[10, 12],
[ 3,  4],
[ 5,  9],
[ 4,  7],
[ 1,  4],
[ 9, 11]])```
In [77]:
```fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))
```
In [78]:
```ax1.scatter(x=x, y=y, marker="o", c="r", edgecolor="b")
```
Out[78]:
`<matplotlib.collections.PathCollection at 0x7ff3bb7f7a00>`
In [79]:
```ax1.set_title("Scatter: \$x\$ versus \$y\$")
```
Out[79]:
`Text(0.5, 1, 'Scatter: \$x\$ versus \$y\$')`
In [80]:
```ax1.set_xlabel("\$x\$")
```
Out[80]:
`Text(0.5, 3.1999999999999993, '\$x\$')`
In [81]:
```ax1.set_ylabel("\$y\$")
```
Out[81]:
`Text(3.200000000000003, 0.5, '\$y\$')`
In [82]:
```ax2.hist(data, bins=np.arange(data.min(), data.max()), label=("x", "y"))
```
Out[82]:
```([array([5., 3., 4., 5., 6., 6., 6., 2., 9., 4., 0., 0.]),
array([0., 1., 1., 4., 2., 8., 4., 7., 5., 4., 6., 7.])],
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13]),
<a list of 2 Lists of Patches objects>)```
In [83]:
```ax2.legend(loc=(0.65, 0.8))
```
Out[83]:
`<matplotlib.legend.Legend at 0x7ff3bb794dc0>`
In [84]:
```ax2.set_title("Frequencies of \$x\$ and \$y\$")
```
Out[84]:
`Text(0.5, 1, 'Frequencies of \$x\$ and \$y\$')`
In [85]:
```ax2.yaxis.tick_right()
```
• Text inside dollar signs utilizes TeXmarkup to put variables in italics.

• Because we’re creating a “1x2” Figure, the returned result of plt.subplots(1, 2) is now a Figure object and a NumPy array of Axes objects. (You can inspect this with fig, axs = plt.subplots(1, 2) and taking a look at axs.)

In [86]:
```fig
```
Out[86]:
In [87]:
```tuple(fig.axes[i] is ax for i, ax in zip(range(2), (ax1, ax2)))
```
Out[87]:
`(True, True)`
• Taking this one step further, we could alternatively create a figure that holds a 2x2 grid of Axes objects

In [88]:
```fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(7, 7))
```
• Now, what is ax? It’s no longer a single Axes, but a two-dimensional NumPy array of them

In [89]:
```type(ax)
```
Out[89]:
`numpy.ndarray`
In [90]:
```ax
```
Out[90]:
```array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7ff3b97b0a00>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7ff3b975d3c0>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x7ff3b971a730>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7ff3b97da410>]],
dtype=object)```
In [91]:
```ax1, ax2, ax3, ax4 = ax.flatten()
```

To illustrate some more advanced subplot features, let’s pull some macroeconomic California housing data extracted from a compressed tar archive, using io, tarfile, and urllib from Python’s Standard Library.

In [92]:
```import tarfile
from urllib.request import urlretrieve
from pathlib import Path
```
In [93]:
```filepath, response = urlretrieve(
"http://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.tgz"
)
```
In [94]:
```filepath
```
Out[94]:
`'/tmp/tmpyh987137'`
In [95]:
```with tarfile.open(name=filepath, mode="r") as archive:
housing = np.loadtxt(
archive.extractfile("CaliforniaHousing/cal_housing.data"), delimiter=","
)
```
In [96]:
```housing
```
Out[96]:
```array([[-1.2223e+02,  3.7880e+01,  4.1000e+01, ...,  1.2600e+02,
8.3252e+00,  4.5260e+05],
[-1.2222e+02,  3.7860e+01,  2.1000e+01, ...,  1.1380e+03,
8.3014e+00,  3.5850e+05],
[-1.2224e+02,  3.7850e+01,  5.2000e+01, ...,  1.7700e+02,
7.2574e+00,  3.5210e+05],
...,
[-1.2122e+02,  3.9430e+01,  1.7000e+01, ...,  4.3300e+02,
1.7000e+00,  9.2300e+04],
[-1.2132e+02,  3.9430e+01,  1.8000e+01, ...,  3.4900e+02,
1.8672e+00,  8.4700e+04],
[-1.2124e+02,  3.9370e+01,  1.6000e+01, ...,  5.3000e+02,
2.3886e+00,  8.9400e+04]])```
In [97]:
```(y := housing[:, -1])
```
Out[97]:
`array([452600., 358500., 352100., ...,  92300.,  84700.,  89400.])`

The property T is an accessor to the method transpose().

In [98]:
```pop, age = housing[:, [4, 7]].T
```
In [99]:
```pop, age
```
Out[99]:
```(array([ 129., 1106.,  190., ...,  485.,  409.,  616.]),
array([8.3252, 8.3014, 7.2574, ..., 1.7   , 1.8672, 2.3886]))```

Next let’s define a “helper function” that places a text box inside of a plot and acts as an “in-plot title”:

In [100]:
```def add_titlebox(ax, text):
ax.text(
0.55,
0.8,
text,
horizontalalignment="center",
transform=ax.transAxes,
bbox=dict(facecolor="white", alpha=0.6),
fontsize=12.5,
)
return ax
```
In [101]:
```gridsize = (3, 2)
fig = plt.figure(figsize=(12, 8))
ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid(gridsize, (2, 0))
ax3 = plt.subplot2grid(gridsize, (2, 1))
```
In [102]:
```ax1.set_title("Home value as a function of home age & area population", fontsize=14)
sctr = ax1.scatter(x=age, y=pop, c=y, cmap="RdYlGn")
plt.colorbar(sctr, ax=ax1, format="\$%d")
ax1.set_yscale("log")
ax2.hist(age, bins="auto")
ax3.hist(pop, bins="auto", log=True)
add_titlebox(ax2, "Histogram: home age")
add_titlebox(ax3, "Histogram: area population (log scl.)")
```
Out[102]:
`<matplotlib.axes._subplots.AxesSubplot at 0x7ff3b8a668c0>`
In [103]:
```fig
```
Out[103]:
In [104]:
```[plt.figure(i) for i in plt.get_fignums()]
```
Out[104]:
`[]`
In [105]:
```plt.get_fignums()
```
Out[105]:
`[]`

While ax.plot() is one of the most common plotting methods on an Axes, there are a whole host of others, as well. (We used ax.stackplot() above. You can find the complete list here.)

Methods that get heavy use are imshow() and matshow(), with the latter being a wrapper around the former. These are useful anytime that a raw numerical array can be visualized as a colored grid.

In [106]:
```(x := np.diag(np.arange(2, 12))[::-1])
```
Out[106]:
```array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0, 11],
[ 0,  0,  0,  0,  0,  0,  0,  0, 10,  0],
[ 0,  0,  0,  0,  0,  0,  0,  9,  0,  0],
[ 0,  0,  0,  0,  0,  0,  8,  0,  0,  0],
[ 0,  0,  0,  0,  0,  7,  0,  0,  0,  0],
[ 0,  0,  0,  0,  6,  0,  0,  0,  0,  0],
[ 0,  0,  0,  5,  0,  0,  0,  0,  0,  0],
[ 0,  0,  4,  0,  0,  0,  0,  0,  0,  0],
[ 0,  3,  0,  0,  0,  0,  0,  0,  0,  0],
[ 2,  0,  0,  0,  0,  0,  0,  0,  0,  0]])```
In [107]:
```x[np.diag_indices_from(x[::-1])] = np.arange(2, 12)
x
```
Out[107]:
```array([[ 2,  0,  0,  0,  0,  0,  0,  0,  0, 11],
[ 0,  3,  0,  0,  0,  0,  0,  0, 10,  0],
[ 0,  0,  4,  0,  0,  0,  0,  9,  0,  0],
[ 0,  0,  0,  5,  0,  0,  8,  0,  0,  0],
[ 0,  0,  0,  0,  6,  7,  0,  0,  0,  0],
[ 0,  0,  0,  0,  6,  7,  0,  0,  0,  0],
[ 0,  0,  0,  5,  0,  0,  8,  0,  0,  0],
[ 0,  0,  4,  0,  0,  0,  0,  9,  0,  0],
[ 0,  3,  0,  0,  0,  0,  0,  0, 10,  0],
[ 2,  0,  0,  0,  0,  0,  0,  0,  0, 11]])```
In [109]:
```(x2 := np.arange(x.size).reshape(x.shape))
```
Out[109]:
```array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])```
In [110]:
```sides = (
"left",
"right",
"top",
"bottom",
)
```
In [111]:
```nolabels = {s: False for s in sides}
```
In [113]:
```nolabels.update({f"label{s}": False for s in sides})
```
In [114]:
```nolabels
```
Out[114]:
```{'left': False,
'right': False,
'top': False,
'bottom': False,
'labelleft': False,
'labelright': False,
'labeltop': False,
'labelbottom': False}```
In [115]:
```from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
```
In [122]:
```with plt.rc_context(rc={"axes.grid": False}):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
ax1.matshow(x)
img2 = ax2.matshow(x2, cmap="RdYlGn_r")
for ax in (ax1, ax2):
ax.tick_params(axis="both", which="both", **nolabels)
for i, j in zip(*x.nonzero()):
ax1.text(j, i, x[i, j], color="white", ha="center", va="center")
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0)
plt.colorbar(img2, cax=cax, ax=[ax1, ax2])
fig.suptitle("Heatmaps with `Axes.matshow`", fontsize=16)
```

The pandas library has become popular for not just for enabling powerful data analysis, but also for its handy pre-canned plotting methods. Interestingly though, pandas plotting methods are really just convenient wrappers around existing matplotlib calls.

In [123]:
```import pandas as pd
```
In [124]:
```(s := pd.Series(np.arange(5), index=list("abcde")))
```
Out[124]:
```a    0
b    1
c    2
d    3
e    4
dtype: int64```
In [125]:
```(ax := s.plot())
```
Out[125]:
`<matplotlib.axes._subplots.AxesSubplot at 0x7ff3adf9ef50>`
In [126]:
```type(ax)
```
Out[126]:
`matplotlib.axes._subplots.AxesSubplot`
In [128]:
```id(plt.gca()), id(ax)
```
Out[128]:
`(140684556626192, 140684572618576)`
In [129]:
```import matplotlib.transforms as mtransforms
```
In [179]:
```url = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=VIXCLS"
```
In [186]:
```vix = pd.read_csv(
url, index_col=0, parse_dates=True, infer_datetime_format=True, squeeze=True
).dropna()
```
In [188]:
```vix = vix[vix != "."]
```
In [189]:
```(ma := vix.rolling("90d").mean())
```
Out[189]:
```DATE
1990-01-02    17.240000
1990-01-03    17.715000
1990-01-04    18.216667
1990-01-05    18.690000
1990-01-08    19.004000
...
2020-02-21    14.044500
2020-02-24    14.310000
2020-02-25    14.582881
2020-02-26    14.799167
2020-02-27    15.241500
Name: VIXCLS, Length: 7595, dtype: float64```
In [193]:
```{type(item) for item in vix}
```
Out[193]:
`{str}`
In [222]:
```vix_ = pd.Series([float(item) for item in vix])
```
In [223]:
```state = pd.cut(ma, bins=[-np.inf, 14, 18, 24, np.inf], labels=range(4))
cmap = plt.get_cmap("RdYlGn_r")
ma.plot(color="black", linewidth=1.5, marker="", figsize=(8, 4), label="VIX 90d MA")
ax = plt.gca()  # Get the current Axes that ma.plot() references
ax.set_ylabel("90d moving average: CBOE VIX")
ax.set_title("Volatility Regime State")
ax.grid(False)
ax.legend(loc="upper center")
ax.set_xlim(xmin=ma.index[0], xmax=ma.index[-1])
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
for i, color in enumerate(cmap([0.2, 0.4, 0.6, 0.8])):
ax.fill_between(ma.index, 0, 1, where=state == i, facecolor=color, transform=trans)
ax.axhline(
vix_.mean(),
linestyle="dashed",
color="xkcd:dark grey",
alpha=0.6,
label="Full-period mean",
marker="",
)
```
Out[223]:
`<matplotlib.lines.Line2D at 0x7ff3ac632370>`

# Use JSON Web Tokens

### Use JSON web tokens.¶

In [1]:
```import jwt
```

`jwt.encode?`

```Signature:
jwt.encode(
payload,
key,
algorithm='HS256',
headers=None,
json_encoder=None,
)
Type:      method
```
In [2]:
```(encoded := jwt.encode({"some": "payload"}, "secret"))
```
Out[2]:
`b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U'`
In [3]:
```jwt.decode(encoded, "secret", algorithms=["HS256"])
```
Out[3]:
`{'some': 'payload'}`

### Set an expiration date.¶

In [4]:
```from datetime import datetime, timedelta
import time
import arrow
```
In [5]:
```(
encoded := jwt.encode(
{"exp": (exp := datetime.utcnow() + timedelta(seconds=3))}, "secret"
)
)
```
Out[5]:
`b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1Nzc3MDg2ODV9.3sInaD8S16T9Iva3I-OI0-4BtXbxh7PGIkHfepXNGGQ'`
In [6]:
```print(len(encoded))
```
```105
```
In [7]:
```time.sleep(4)  # Allow time to expire.
```
In [8]:
```try:
jwt.decode(encoded, "secret")
except jwt.ExpiredSignatureError:
print(f"Signature expired {arrow.get(exp).humanize()}.")
```
```Signature expired just now.
```

# Insert a Menu and Anchor Tags in a Long Jupyter Notebook Output Cell

### Download a previously stored dataframe¶

In [1]:
```from pathlib import Path
import urllib.request
from urllib import parse
import pickle
from string import digits
from functools import partial

from IPython.display import HTML, Image, Markdown
```
In [2]:
```(df,) = (
pickle.loads(Path(fp).read_bytes())
for fp, _ in (
urllib.request.urlretrieve(
(Path.home() / ".texpander" / "iowa_sports_pk_url").read_text()
),
)
)
```

### Display data frame¶

In [3]:
```display(df)
```
xpath url sport sport_id sex
0 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/BBSB/TeamStand... Baseball B25923B5-D303-41CA-B9B3-DF2527D84CDD boys
1 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Basketball/Tea... Basketball 57C38F60-B323-4087-A557-9ED925DC546D boys
2 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Basketball/Tea... Basketball B657ECDF-ECD0-4429-810A-9F9274EC4AAA girls
3 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Bowling/TeamSt... Bowling DA3506E8-E4CA-4175-BF69-BEBBDC2FD878 boys
4 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Bowling/TeamSt... Bowling 0C6DFBCF-98C4-4B01-9F56-17B02E9E47E1 girls
5 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Golf/TeamStand... Fall Golf 92A34DE4-ACB3-4282-BF29-571A97DE1946 boys
6 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Football/TeamS... Football 91A308DE-5763-4DAA-8C03-9AF66611E0BC boys
7 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Golf/TeamStand... Golf 6DC124A1-D8C4-4F88-84EF-5C6B4FD4A688 girls
8 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Soccer/TeamSta... Soccer 9D4214D2-EBE6-429E-9005-C11D2A29C89B boys
9 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Soccer/TeamSta... Soccer 65E5DA09-90C6-45F5-847A-F9A84FD9C5B0 girls
10 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/BBSB/TeamStand... Softball D97DD7D0-0BEF-404A-B041-7E51ACFDBD16 girls
11 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Golf/TeamStand... Spring Golf FC614ADE-B5DA-4012-A95E-0FD2A594FE9D boys
12 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Swimming/Indiv... Swimming 139DCB57-4343-4FB8-BAF9-970E5D64597F boys
13 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Swimming/Indiv... Swimming 71F7113B-576F-4372-9E9B-4C746F251946 girls
14 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Tennis/TeamSta... Tennis 19786FF3-ADA3-4C7A-A94F-FAC0811118F5 boys
15 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Tennis/TeamSta... Tennis 6086C2DF-4661-4701-BFF1-3BB32C081B88 girls
16 /html/body/form/div/div[3]/table/tr[2]/td[1]/t... http://quikstatsiowa.com/Public/Track/Individu... Track & Field EB178641-26F1-464D-97F1-A1D101AE35D6 boys
17 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Track/Individu... Track & Field 93AAC882-9E72-4621-B16F-95F389BA7F15 girls
18 /html/body/form/div/div[3]/table/tr[2]/td[2]/t... http://quikstatsiowa.com/Public/Volleyball/Tea... Volleyball 83298383-D7D7-4670-9C6B-24DDB8B2E773 girls
In [4]:
```from chamelboots import ChameleonTemplate as CT
from chamelboots import TalStatement as TS
from chamelboots.constants import FAKE, JoinWith
from chamelboots.html.utils import prettify_html
```

### Define TAL statements¶

In [15]:
```TSCS, TSR, TSA = (
TS(*args)
for args in (
("content", f"structure content"),
("repeat", "content items"),
("attributes", "attributes"),
)
)
```
In [14]:
```LINK = CT("a", (TSCS, TSA)).render
SPAN = partial(
CT("span", (TSCS, TSA)).render, attributes=dict(style="font-size: 2.5rem;")
)
SPAN(content="foo")
```
Out[14]:
`'<span style="font-size: 2.5rem;">foo</span>'`

### Create anchor tags¶

An html id cannot start with digits so strip them.

In [7]:
```menu_items, anchors = zip(
*(
(
LINK(
content=f"{item.sex}' {item.sport}",
attributes={
"href": f"#{(id_ := item.sport_id.strip(digits))}",
"id": (menu_id := f"menu-{id_}"),
},
),
LINK(
content=SPAN(content="back to menu"),
attributes={"id": id_, "href": f"#{menu_id}"},
),
)
for item in df.itertuples()
)
)
```
In [8]:
```list_items = prettify_html(
CT("ul", (TSCS,)).render(content=CT("li", (TSR, TSCS)).render(items=menu_items))
)
```

### Display truncated portion of HTML¶

In [9]:
```print(JoinWith.LINES(list_items.splitlines()[:10]))
```
```<ul>
<li>
<a href="#B25923B5-D303-41CA-B9B3-DF2527D84CDD" id="menu-B25923B5-D303-41CA-B9B3-DF2527D84CDD">
boys' Baseball
</a>
</li>
<li>
<a href="#C38F60-B323-4087-A557-9ED925DC546D" id="menu-C38F60-B323-4087-A557-9ED925DC546D">
boys' Basketball
</a>
```

#### Anchors¶

In [10]:
```anchors[:5]
```
Out[10]:
```('<a id="B25923B5-D303-41CA-B9B3-DF2527D84CDD" href="#menu-B25923B5-D303-41CA-B9B3-DF2527D84CDD"><span style="font-size: 2.5rem;">back to menu</span></a>',
'<a id="C38F60-B323-4087-A557-9ED925DC546D" href="#menu-C38F60-B323-4087-A557-9ED925DC546D"><span style="font-size: 2.5rem;">back to menu</span></a>',
'<a id="B657ECDF-ECD0-4429-810A-9F9274EC4AAA" href="#menu-B657ECDF-ECD0-4429-810A-9F9274EC4AAA"><span style="font-size: 2.5rem;">back to menu</span></a>',
'<a id="DA3506E8-E4CA-4175-BF69-BEBBDC2FD" href="#menu-DA3506E8-E4CA-4175-BF69-BEBBDC2FD"><span style="font-size: 2.5rem;">back to menu</span></a>',
'<a id="C6DFBCF-98C4-4B01-9F56-17B02E9E47E" href="#menu-C6DFBCF-98C4-4B01-9F56-17B02E9E47E"><span style="font-size: 2.5rem;">back to menu</span></a>')```

### Display list of links.¶

In [11]:
```HTML(list_items)
```
Out[11]:

### Display scaled and cropped screenshots of each website page¶

In [12]:
```from chamelboots.imageutils import get_scaled_screenshot
```
In [13]:
```for anchor, item in zip(anchors, df.itertuples()):
for item in (HTML(anchor), Image(filename=get_scaled_screenshot(item.url))):
display(item)
```