data_prototype Documentation#
This is prototype development for the next generation of data structures for Matplotlib. This is the version “to throw away”. Everything in this repository should be considered experimental and used at you own risk.
Source : matplotlib/data-prototype
Examples#
Examples#
Examples used in the rapid prototyping of this project.

An simple scatter plot using PathCollectionWrapper
Note
Go to the end to download the full example code
Errorbar graph#
Using containers.ArrayContainer
and wrappers.ErrorbarWrapper
to plot a graph with error bars.

import matplotlib.pyplot as plt
import numpy as np
from data_prototype.wrappers import ErrorbarWrapper
from data_prototype.containers import ArrayContainer
x = np.arange(10)
y = x**2
yupper = y + np.sqrt(y)
ylower = y - np.sqrt(y)
xupper = x + 0.5
xlower = x - 0.5
ac = ArrayContainer(
x=x, y=y, yupper=yupper, ylower=ylower, xlower=xlower, xupper=xupper
)
fig, ax = plt.subplots()
ew = ErrorbarWrapper(ac)
ax.add_artist(ew)
ax.set_xlim(0, 10)
ax.set_ylim(0, 100)
plt.show()
Note
Go to the end to download the full example code
A re-binning histogram#
A containers.HistContainer
which is used with wrappers.StepWrapper
to provide a histogram which recomputes the bins based on a range selected.

import matplotlib.pyplot as plt
import numpy as np
from data_prototype.wrappers import StepWrapper
from data_prototype.containers import HistContainer
hc = HistContainer(
np.concatenate([np.random.randn(5000), 0.1 * np.random.randn(500) + 5]), 25
)
fig, (ax1, ax2) = plt.subplots(1, 2, layout="constrained")
for ax in (ax1, ax2):
ax.add_artist(StepWrapper(hc, lw=0, color="green"))
ax.set_ylim(0, 1)
ax1.set_xlim(-7, 7)
ax1.axvspan(4.5, 5.5, facecolor="none", zorder=-1, lw=5, edgecolor="k")
ax1.set_title("full range")
ax2.set_xlim(4.5, 5.5)
ax2.set_title("zoom to small peak")
plt.show()
Note
Go to the end to download the full example code
An simple scatter plot using PathCollectionWrapper#
A quick scatter plot using containers.ArrayContainer
and
wrappers.PathCollectionWrapper
.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.markers as mmarkers
from data_prototype.containers import ArrayContainer
from data_prototype.wrappers import PathCollectionWrapper
marker_obj = mmarkers.MarkerStyle("o")
cont = ArrayContainer(
x=np.array([0, 1, 2]),
y=np.array([1, 4, 2]),
paths=np.array([marker_obj.get_path()]),
sizes=np.array([12]),
edgecolors=np.array(["k"]),
facecolors=np.array(["C3"]),
)
fig, ax = plt.subplots()
ax.set_xlim(-0.5, 2.5)
ax.set_ylim(0, 5)
lw = PathCollectionWrapper(cont, offset_transform=ax.transData)
ax.add_artist(lw)
plt.show()
Note
Go to the end to download the full example code
A functional line#
Demonstrating the differences between containers.FuncContainer
and
containers.SeriesContainer
using wrappers.LineWrapper
.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from data_prototype.wrappers import LineWrapper
from data_prototype.containers import FuncContainer, SeriesContainer
fc = FuncContainer({"x": (("N",), lambda x: x), "y": (("N",), np.sin)})
lw = LineWrapper(fc, lw=5, color="green", label="sin (function)")
th = np.linspace(0, 2 * np.pi, 16)
sc = SeriesContainer(pd.Series(index=th, data=np.cos(th)), index_name="x", col_name="y")
lw2 = LineWrapper(sc, lw=3, color="blue", label="cos (pandas)")
fig, ax = plt.subplots()
ax.add_artist(lw)
ax.add_artist(lw2)
ax.set_xlim(0, np.pi * 4)
ax.set_ylim(-1.1, 1.1)
plt.show()
Note
Go to the end to download the full example code
A functional 2D image#
A 2D image generated using containers.FuncContainer
and
wrappers.ImageWrapper
.

import matplotlib.pyplot as plt
import numpy as np
from data_prototype.wrappers import ImageWrapper
from data_prototype.containers import FuncContainer
from matplotlib.colors import Normalize
fc = FuncContainer(
{},
xyfuncs={
"xextent": ((2,), lambda x, y: [x[0], x[-1]]),
"yextent": ((2,), lambda x, y: [y[0], y[-1]]),
"image": (
("N", "M"),
lambda x, y: np.sin(x).reshape(1, -1) * np.cos(y).reshape(-1, 1),
),
},
)
norm = Normalize(vmin=-1, vmax=1)
im = ImageWrapper(fc, norm=norm)
fig, ax = plt.subplots()
ax.add_artist(im)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
fig.colorbar(im)
plt.show()
Note
Go to the end to download the full example code
Show data frame#
Wrapping a pandas.DataFrame
using containers.DataFrameContainer
and wrappers.LineWrapper
.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from data_prototype.wrappers import LineWrapper
from data_prototype.containers import DataFrameContainer
th = np.linspace(0, 4 * np.pi, 256)
dc1 = DataFrameContainer(
pd.DataFrame({"x": th, "y": np.cos(th)}), index_name=None, col_names=lambda n: n
)
df = pd.DataFrame(
{
"cos": np.cos(th),
"sin": np.sin(th),
},
index=th,
)
dc2 = DataFrameContainer(df, index_name="x", col_names={"sin": "y"})
dc3 = DataFrameContainer(df, index_name="x", col_names={"cos": "y"})
fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.add_artist(LineWrapper(dc1, lw=5, color="green", label="sin"))
ax2.add_artist(LineWrapper(dc2, lw=5, color="green", label="sin"))
ax2.add_artist(LineWrapper(dc3, lw=5, color="blue", label="cos"))
for ax in (ax1, ax2):
ax.set_xlim(0, np.pi * 4)
ax.set_ylim(-1.1, 1.1)
plt.show()
Note
Go to the end to download the full example code
(Infinitly) Zoomable Mandelbrot Set#
A mandelbrot set which is computed using a containers.FuncContainer
and represented using a wrappers.ImageWrapper
.
The mandelbrot recomputes as it is zoomed in and/or panned.

import matplotlib.pyplot as plt
import numpy as np
from data_prototype.wrappers import ImageWrapper
from data_prototype.containers import FuncContainer
from matplotlib.colors import Normalize
maxiter = 75
def mandelbrot_set(X, Y, maxiter, *, horizon=3, power=2):
C = X + Y[:, None] * 1j
N = np.zeros_like(C, dtype=int)
Z = np.zeros_like(C)
for n in range(maxiter):
I = abs(Z) < horizon
N += I
Z[I] = Z[I] ** power + C[I]
N[N == maxiter] = -1
return Z, N
fc = FuncContainer(
{},
xyfuncs={
"xextent": ((2,), lambda x, y: [x[0], x[-1]]),
"yextent": ((2,), lambda x, y: [y[0], y[-1]]),
"image": (("N", "M"), lambda x, y: mandelbrot_set(x, y, maxiter)[1]),
},
)
cmap = plt.get_cmap()
cmap.set_under("w")
im = ImageWrapper(fc, norm=Normalize(0, maxiter), cmap=cmap)
fig, ax = plt.subplots()
ax.add_artist(im)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_aspect("equal")
fig.colorbar(im)
plt.show()
Total running time of the script: ( 0 minutes 4.574 seconds)
Note
Go to the end to download the full example code
Custom bivariate colormap#
Using nu
functions to account for two values when computing the color
of each pixel.

import matplotlib.pyplot as plt
import numpy as np
from data_prototype.wrappers import ImageWrapper
from data_prototype.containers import FuncContainer
from data_prototype.conversion_node import FunctionConversionNode
from matplotlib.colors import hsv_to_rgb
def func(x, y):
return (
(np.sin(x).reshape(1, -1) * np.cos(y).reshape(-1, 1)) ** 2,
np.arctan2(np.cos(y).reshape(-1, 1), np.sin(x).reshape(1, -1)),
)
def image_nu(image):
saturation, angle = image
hue = (angle + np.pi) / (2 * np.pi)
value = np.ones_like(hue)
return np.clip(hsv_to_rgb(np.stack([hue, saturation, value], axis=2)), 0, 1)
fc = FuncContainer(
{},
xyfuncs={
"xextent": ((2,), lambda x, y: [x[0], x[-1]]),
"yextent": ((2,), lambda x, y: [y[0], y[-1]]),
"image": (("N", "M", 2), func),
},
)
im = ImageWrapper(fc, FunctionConversionNode.from_funcs({"image": image_nu}))
fig, ax = plt.subplots()
ax.add_artist(im)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
plt.show()
Total running time of the script: ( 0 minutes 1.248 seconds)
Note
Go to the end to download the full example code
Simple patch artists#
Draw two fully specified rectangle patches.
Demonstrates patches.RectangleWrapper
using
containers.ArrayContainer
.

import numpy as np
import matplotlib.pyplot as plt
from data_prototype.containers import ArrayContainer
from data_prototype.patches import RectangleWrapper
cont1 = ArrayContainer(
x=np.array([-3]),
y=np.array([0]),
width=np.array([2]),
height=np.array([3]),
angle=np.array([0]),
rotation_point=np.array(["center"]),
edgecolor=np.array([0, 0, 0]),
facecolor=np.array([0.0, 0.7, 0, 0.5]),
linewidth=np.array([3]),
linestyle=np.array(["-"]),
antialiased=np.array([True]),
hatch=np.array(["*"]),
fill=np.array([True]),
capstyle=np.array(["round"]),
joinstyle=np.array(["miter"]),
)
cont2 = ArrayContainer(
x=np.array([0]),
y=np.array([1]),
width=np.array([2]),
height=np.array([3]),
angle=np.array([30]),
rotation_point=np.array(["center"]),
edgecolor=np.array([0, 0, 0]),
facecolor=np.array([0.7, 0, 0]),
linewidth=np.array([6]),
linestyle=np.array(["-"]),
antialiased=np.array([True]),
hatch=np.array([""]),
fill=np.array([True]),
capstyle=np.array(["butt"]),
joinstyle=np.array(["round"]),
)
fig, ax = plt.subplots()
ax.set_xlim(-5, 5)
ax.set_ylim(0, 5)
rect1 = RectangleWrapper(cont1, {})
rect2 = RectangleWrapper(cont2, {})
ax.add_artist(rect1)
ax.add_artist(rect2)
ax.set_aspect(1)
plt.show()
Note
Go to the end to download the full example code
Mapping Line Properties#
Leveraging the converter functions to transform users space data to visualization data.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Normalize
from data_prototype.wrappers import LineWrapper, FormatedText
from data_prototype.containers import ArrayContainer
from data_prototype.conversion_node import FunctionConversionNode
cmap = plt.colormaps["viridis"]
cmap.set_over("k")
cmap.set_under("r")
norm = Normalize(1, 8)
line_converter = FunctionConversionNode.from_funcs(
{
# arbitrary functions
"lw": lambda lw: min(1 + lw, 5),
# standard color mapping
"color": lambda j: cmap(norm(j)),
# categorical
"ls": lambda cat: {"A": "-", "B": ":", "C": "--"}[cat[()]],
},
)
text_converter = FunctionConversionNode.from_funcs(
{
"text": lambda j, cat: f"index={j[()]} class={cat[()]!r}",
"y": lambda j: j,
"x": lambda x: 2 * np.pi,
},
)
th = np.linspace(0, 2 * np.pi, 128)
delta = np.pi / 9
fig, ax = plt.subplots()
for j in range(10):
ac = ArrayContainer(
**{
"x": th,
"y": np.sin(th + j * delta) + j,
"j": np.asarray(j),
"lw": np.asarray(j),
"cat": np.asarray({0: "A", 1: "B", 2: "C"}[j % 3]),
}
)
ax.add_artist(
LineWrapper(
ac,
line_converter,
)
)
ax.add_artist(
FormatedText(
ac,
text_converter,
x=2 * np.pi,
ha="right",
bbox={"facecolor": "gray", "alpha": 0.5},
)
)
ax.set_xlim(0, np.pi * 2)
ax.set_ylim(-1.1, 10.1)
plt.show()
Note
Go to the end to download the full example code
Dynamic Downsampling#
Generates a large image with three levels of detail.
When zoomed out, appears as a difference of 2D Gaussians. At medium zoom, a diagonal sinusoidal pattern is apparent. When zoomed in close, noise is visible.
The image is dynamically subsampled using a local mean which hides the finer details.

from typing import Tuple, Dict, Any, Union
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
import numpy as np
from data_prototype.wrappers import ImageWrapper
from data_prototype.containers import _MatplotlibTransform, Desc
from skimage.transform import downscale_local_mean
x = y = np.linspace(-3, 3, 3000)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-(X**2) - Y**2) + 0.08 * np.sin(50 * (X + Y))
Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
Z = (Z1 - Z2) * 2
Z += np.random.random(Z.shape) - 0.5
class Subsample:
def describe(self):
return {
"xextent": Desc([2], float),
"yextent": Desc([2], float),
"image": Desc([], float),
}
def query(
self,
coord_transform: _MatplotlibTransform,
size: Tuple[int, int],
) -> Tuple[Dict[str, Any], Union[str, int]]:
(x1, y1), (x2, y2) = coord_transform.transform([[0, 0], [1, 1]])
xi1 = np.argmin(np.abs(x - x1))
yi1 = np.argmin(np.abs(y - y1))
xi2 = np.argmin(np.abs(x - x2))
yi2 = np.argmin(np.abs(y - y2))
xscale = int(np.ceil((xi2 - xi1) / 50))
yscale = int(np.ceil((yi2 - yi1) / 50))
return {
"xextent": [x1, x2],
"yextent": [y1, y2],
"image": downscale_local_mean(Z[xi1:xi2, yi1:yi2], (xscale, yscale)),
}, hash((x1, x2, y1, y2))
sub = Subsample()
cmap = mpl.colormaps["coolwarm"]
norm = Normalize(-2.2, 2.2)
im = ImageWrapper(sub, cmap=cmap, norm=norm)
fig, ax = plt.subplots()
ax.add_artist(im)
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)
plt.show()
Total running time of the script: ( 0 minutes 1.545 seconds)
Note
Go to the end to download the full example code
An animated line#
An animated line using a custom container class,
wrappers.LineWrapper
, and wrappers.FormatedText
.
import time
from typing import Dict, Tuple, Any, Union
from functools import partial
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from data_prototype.containers import _MatplotlibTransform, Desc
from data_prototype.conversion_node import FunctionConversionNode
from data_prototype.wrappers import LineWrapper, FormatedText
class SinOfTime:
N = 1024
# cycles per minutes
scale = 10
def describe(self):
return {
"x": Desc([self.N], float),
"y": Desc([self.N], float),
"phase": Desc([], float),
"time": Desc([], float),
}
def query(
self,
coord_transformtransform: _MatplotlibTransform,
size: Tuple[int, int],
) -> Tuple[Dict[str, Any], Union[str, int]]:
th = np.linspace(0, 2 * np.pi, self.N)
def next_time():
cur_time = time.time()
phase = 2 * np.pi * (self.scale * cur_time % 60) / 60
return {
"x": th,
"y": np.sin(th + phase),
"phase": phase,
"time": cur_time,
}, hash(cur_time)
return next_time()
def update(frame, art):
return art
sot_c = SinOfTime()
lw = LineWrapper(sot_c, lw=5, color="green", label="sin(time)")
fc = FormatedText(
sot_c,
FunctionConversionNode.from_funcs(
{"text": lambda phase: f"ϕ={phase:.2f}", "x": lambda: 2 * np.pi, "y": lambda: 1}
),
ha="right",
)
fig, ax = plt.subplots()
ax.add_artist(lw)
ax.add_artist(fc)
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.1, 1.1)
ani = FuncAnimation(
fig,
partial(update, art=(lw, fc)),
frames=25,
interval=1000 / 60,
# TODO: blitting does not work because wrappers do not inherent from Artist
# blit=True,
)
plt.show()
Total running time of the script: ( 0 minutes 5.851 seconds)
Note
Go to the end to download the full example code
An animated lissajous ball#
Inspired by https://twitter.com/_brohrer_/status/1584681864648065027
An animated scatter plot using a custom container and wrappers.PathCollectionWrapper