Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[MNT]: Revamp draw_path_collection() API #30547

@r3kste

Description

@r3kste

Summary

I am opening this issue in order to discuss the possibility of enhancing the API of draw_path_collection() for the long term. This was initially discussed in this and the following comments by @anntzer and @timhoffm.

Adding/ vectorizing new attributes in collections requires changing the signature of draw_path_collection(). This causes incompatibility with third party backends, such as mplcairo , because they still use the older signature.

To workaround this in #29044 (that added the hatchcolors argument) a provisional API was used, which relies on making an empty call to draw_path_collection() to infer the signature. This current API is temporary and requires a rewrite.

Proposed fix

A solution proposed by @anntzer in this comment is to use an API similar to draw_path() - send parameters through a dataclass and use getters to retrieve the required parameters, analogous to GraphicsContextBase in draw_path().
We could call this dataclass something like VectorizedGraphicsContextBase . It is similar to GraphicsContextBase, but each attribute is vectorized. A simple draft of the proposed solution is shown below.

Why is this change needed?

This would cause a one time break for third party backends. However, in the long term, it would become much simpler to add / vectorize new attributes in collections, without completely breaking third party backends. For example, it would greatly simplify #27937, that aims to vectorize the hatch attribute.

Code Draft

# backend_bases.py
class VectorizedGraphicsContextBase:
    def __init__(self):
        # vectorized attributes
        self._facecolor = [(0.0, 0.0, 0.0, 1.0)]
        self._rgb = [(0.0, 0.0, 0.0, 1.0)]
        self._linewidth = [1]
        ...

        # non-vectorized attributes
        self._cliprect = None
        self._clippath = None
        ...
# collections.py

# `vgc` is an instance of VectorizedGraphicsComtextBase
def draw(self, renderer):
    ...
    transform, offset_trf, offsets, paths = self._prepare_points()

    vgc = renderer.new_vgc()
    self._set_gc_clip(vgc)

    vgc._facecolor = self.get_facecolor()
    vgc._rgb = self.get_edgecolor()
    vgc._linewidth = self.get_linewidth()
    ...

    renderer.draw_path_collection(vgc, transform.frozen(), paths, self.get_transforms(),
                                  offsets, offset_trf)
# backend_bases.py
def draw_path_collection(self, vgc, master_transform, paths, all_transforms,
                         offsets, offset_trans):
    path_ids = self._iter_collection_raw_paths(
        master_transform, paths, all_transforms
    )

    for xo, yo, path_id, gc, rgbFace in self._iter_collection(
        vgc, list(path_ids), offsets, offset_trans
    ):
        path, transform = path_id

        if xo != 0 or yo != 0:
            transform = transform.frozen()
            transform.translate(xo, yo)
        self.draw_path(gc, path, transform, rgbFace)


def _iter_collection(self, vgc, path_ids, offsets, offset_trans):
    Npaths = len(path_ids)
    Noffsets = len(offsets)
    N = max(Npaths, Noffsets)
    Nfacecolors = len(vgc._facecolor)
    Nedgecolors = len(vgc._rgb)
    Nlinewidths = len(vgc._linewidth)

    pathids = cycle_or_default(path_ids)
    toffsets = cycle_or_default(offset_trans.transform(offsets), (0, 0))
    fcs = cycle_or_default(vgc._facecolor)
    ecs = cycle_or_default(vgc._rgb)
    lws = cycle_or_default(vgc._linewidth)

    gc = self.new_gc()
    # attributes that are not vectorized
    gc._clip_path = vgc._clip_path
    gc._clip_rect = vgc._clip_rect
    ...

    for pathid, (xo, yo), fc, ec, lw in itertools.islice(
        zip(pathids, toffsets, fcs, ecs, lws), N
    ):
        if not (np.isfinite(xo) and np.isfinite(yo)):
            continue

        if Nedgecolors:
            if Nlinewidths:
                gc.set_linewidth(lw)
            if len(ec) == 4 and ec[3] == 0.0:
                gc.set_linewidth(0)
            else:
                gc.set_foreground(ec)
        if fc is not None and len(fc) == 4 and fc[3] == 0:
            fc = None
        ...

        yield xo, yo, pathid, gc, fc

    gc.restore()

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions