Skip to content

Commit

Permalink
Merge pull request #40 from ksunden/artists
Browse files Browse the repository at this point in the history
Introduce artist classes, starting with Line
  • Loading branch information
tacaswell authored Jun 6, 2024
2 parents a38c182 + e5b5455 commit a33566d
Show file tree
Hide file tree
Showing 14 changed files with 889 additions and 168 deletions.
236 changes: 236 additions & 0 deletions data_prototype/artist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
from bisect import insort
from typing import Sequence

import numpy as np

from .containers import DataContainer, ArrayContainer, DataUnion
from .description import Desc, desc_like
from .conversion_edge import Edge, Graph, TransformEdge


class Artist:
required_keys: dict[str, Desc]

# defaults?
def __init__(
self, container: DataContainer, edges: Sequence[Edge] | None = None, **kwargs
):
kwargs_cont = ArrayContainer(**kwargs)
self._container = DataUnion(container, kwargs_cont)

edges = edges or []
self._visible = True
self._graph = Graph(edges)
self._clip_box: DataContainer = ArrayContainer(
{"x": "parent", "y": "parent"},
**{"x": np.asarray([0, 1]), "y": np.asarray([0, 1])}
)

def draw(self, renderer, graph: Graph) -> None:
return

def set_clip_box(self, container: DataContainer) -> None:
self._clip_box = container

def get_clip_box(self, container: DataContainer) -> DataContainer:
return self._clip_box

def get_visible(self):
return self._visible

def set_visible(self, visible):
self._visible = visible


class CompatibilityArtist:
"""A compatibility shim to ducktype as a classic Matplotlib Artist.
At this time features are implemented on an "as needed" basis, and many
are only implemented insofar as they do not fail, not necessarily providing
full functionality of a full MPL Artist.
The idea is to keep the new Artist class as minimal as possible.
As features are added this may shrink.
The main thing we are trying to avoid is the reliance on the axes/figure
Ultimately for useability, whatever remains shimmed out here may be rolled in as
some form of gaurded option to ``Artist`` itself, but a firm dividing line is
useful for avoiding accidental dependency.
"""

def __init__(self, artist: Artist):
self._artist = artist

self._axes = None
self.figure = None
self._clippath = None
self._visible = True
self.zorder = 2
self._graph = Graph([])

@property
def axes(self):
return self._axes

@axes.setter
def axes(self, ax):
self._axes = ax

if self._axes is None:
self._graph = Graph([])
return

desc: Desc = Desc(("N",), coordinates="data")
xy: dict[str, Desc] = {"x": desc, "y": desc}
self._graph = Graph(
[
TransformEdge(
"data",
xy,
desc_like(xy, coordinates="axes"),
transform=self._axes.transData - self._axes.transAxes,
),
TransformEdge(
"axes",
desc_like(xy, coordinates="axes"),
desc_like(xy, coordinates="display"),
transform=self._axes.transAxes,
),
],
aliases=(("parent", "axes"),),
)

def set_figure(self, fig):
self.figure = fig

def is_transform_set(self):
return True

def get_mouseover(self):
return False

def get_clip_path(self):
self._clippath

def set_clip_path(self, path):
self._clippath = path

def get_animated(self):
return False

def get_visible(self):
return self._visible

def set_visible(self, visible):
self._visible = visible

def draw(self, renderer, graph=None):
if not self.get_visible():
return

if graph is None:
graph = Graph([])
self._artist.draw(renderer, graph + self._graph)


class CompatibilityAxes:
"""A compatibility shim to add to traditional matplotlib axes.
At this time features are implemented on an "as needed" basis, and many
are only implemented insofar as they do not fail, not necessarily providing
full functionality of a full MPL Artist.
The idea is to keep the new Artist class as minimal as possible.
As features are added this may shrink.
The main thing we are trying to avoid is the reliance on the axes/figure
Ultimately for useability, whatever remains shimmed out here may be rolled in as
some form of gaurded option to ``Artist`` itself, but a firm dividing line is
useful for avoiding accidental dependency.
"""

def __init__(self, axes):
self._axes = axes
self.figure = None
self._clippath = None
self._visible = True
self.zorder = 2
self._children: list[tuple[float, Artist]] = []

@property
def axes(self):
return self._axes

@axes.setter
def axes(self, ax):
self._axes = ax

if self._axes is None:
self._graph = Graph([])
return

desc: Desc = Desc(("N",), coordinates="data")
xy: dict[str, Desc] = {"x": desc, "y": desc}
self._graph = Graph(
[
TransformEdge(
"data",
xy,
desc_like(xy, coordinates="axes"),
transform=self._axes.transData - self._axes.transAxes,
),
TransformEdge(
"axes",
desc_like(xy, coordinates="axes"),
desc_like(xy, coordinates="display"),
transform=self._axes.transAxes,
),
],
aliases=(("parent", "axes"),),
)

def set_figure(self, fig):
self.figure = fig

def is_transform_set(self):
return True

def get_mouseover(self):
return False

def get_clip_path(self):
self._clippath

def set_clip_path(self, path):
self._clippath = path

def get_animated(self):
return False

def draw(self, renderer, graph=None):
if not self.visible:
return
if graph is None:
graph = Graph([])

graph = graph + self._graph

for _, c in self._children:
c.draw(renderer, graph)

def add_artist(self, artist, zorder=1):
insort(self._children, (zorder, artist), key=lambda x: x[0])

def set_xlim(self, min_=None, max_=None):
self.axes.set_xlim(min_, max_)

def set_ylim(self, min_=None, max_=None):
self.axes.set_ylim(min_, max_)

def get_visible(self):
return self._visible

def set_visible(self, visible):
self._visible = visible
27 changes: 14 additions & 13 deletions data_prototype/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ class NoNewKeys(ValueError): ...


class ArrayContainer:
def __init__(self, **data):
def __init__(self, coordinates: dict[str, str] | None = None, /, **data):
coordinates = coordinates or {}
self._data = data
self._cache_key = str(uuid.uuid4())
self._desc = {
k: (
Desc(v.shape, v.dtype)
Desc(v.shape, coordinates.get(k, "auto"))
if isinstance(v, np.ndarray)
else Desc((), type(v))
else Desc(())
)
for k, v in data.items()
}
Expand Down Expand Up @@ -117,7 +118,7 @@ def update(self, **data):

class RandomContainer:
def __init__(self, **shapes):
self._desc = {k: Desc(s, np.dtype(float)) for k, s in shapes.items()}
self._desc = {k: Desc(s) for k, s in shapes.items()}

def query(
self,
Expand Down Expand Up @@ -171,7 +172,7 @@ def __init__(
def _split(input_dict):
out = {}
for k, (shape, func) in input_dict.items():
self._desc[k] = Desc(shape, np.dtype(float))
self._desc[k] = Desc(shape)
out[k] = func
return out

Expand All @@ -196,7 +197,7 @@ def query(
# if hash_key in self._cache:
# return self._cache[hash_key], hash_key

desc = Desc(("N",), np.dtype("f8"))
desc = Desc(("N",))
xy = {"x": desc, "y": desc}
data_lim = graph.evaluator(
desc_like(xy, coordinates="data"),
Expand Down Expand Up @@ -243,8 +244,8 @@ def __init__(self, raw_data, num_bins: int):
self._raw_data = raw_data
self._num_bins = num_bins
self._desc = {
"edges": Desc((num_bins + 1 + 2,), np.dtype(float)),
"density": Desc((num_bins + 2,), np.dtype(float)),
"edges": Desc((num_bins + 1 + 2,)),
"density": Desc((num_bins + 2,)),
}
self._full_range = (raw_data.min(), raw_data.max())
self._cache: MutableMapping[Union[str, int], Any] = LFUCache(64)
Expand All @@ -256,7 +257,7 @@ def query(
) -> Tuple[Dict[str, Any], Union[str, int]]:
dmin, dmax = self._full_range

desc = Desc(("N",), np.dtype("f8"))
desc = Desc(("N",))
xy = {"x": desc, "y": desc}
data_lim = graph.evaluator(
desc_like(xy, coordinates="data"),
Expand Down Expand Up @@ -302,8 +303,8 @@ def __init__(self, series: pd.Series, *, index_name: str, col_name: str):
self._index_name = index_name
self._col_name = col_name
self._desc = {
index_name: Desc((len(series),), series.index.dtype),
col_name: Desc((len(series),), series.dtype),
index_name: Desc((len(series),)),
col_name: Desc((len(series),)),
}
self._hash_key = str(uuid.uuid4())

Expand Down Expand Up @@ -343,9 +344,9 @@ def __init__(

self._desc: Dict[str, Desc] = {}
if self._index_name is not None:
self._desc[self._index_name] = Desc((len(df),), df.index.dtype)
self._desc[self._index_name] = Desc((len(df),))
for col, out in self._col_name_dict.items():
self._desc[out] = Desc((len(df),), df[col].dtype)
self._desc[out] = Desc((len(df),))

self._hash_key = str(uuid.uuid4())

Expand Down
Loading

0 comments on commit a33566d

Please sign in to comment.