Source code for plotnine_extra.positions.position_lineartrans
"""
Linear (2x2) coordinate transform position adjustment.
A direct port of ``ggh4x::position_lineartrans``: every (x, y)
data point is left-multiplied by a 2x2 matrix and optionally
translated. Useful for shearing, rotating or scaling layers
without touching the underlying data.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from plotnine.positions.position import position
if TYPE_CHECKING:
import pandas as pd
[docs]
class position_lineartrans(position):
"""
Apply an affine 2x2 linear transformation to ``x`` and ``y``.
Parameters
----------
scale : tuple of float, default ``(1, 1)``
Scale factors ``(sx, sy)``.
shear : tuple of float, default ``(0, 0)``
Shear factors ``(shx, shy)``.
angle : float, default 0
Rotation in degrees, applied after scale + shear.
M : array-like, optional
A 2x2 matrix that overrides ``scale`` / ``shear`` /
``angle`` if supplied.
"""
REQUIRED_AES = {"x", "y"}
def __init__(
self,
scale: tuple[float, float] = (1.0, 1.0),
shear: tuple[float, float] = (0.0, 0.0),
angle: float = 0.0,
M: np.ndarray | None = None,
):
super().__init__()
self.scale = scale
self.shear = shear
self.angle = angle
self.M = (
np.asarray(M, dtype=float)
if M is not None
else self._build_matrix()
)
def _build_matrix(self) -> np.ndarray:
sx, sy = self.scale
shx, shy = self.shear
# scale + shear
S = np.array([[sx, shx], [shy, sy]], dtype=float)
if self.angle:
theta = np.deg2rad(self.angle)
c, s = np.cos(theta), np.sin(theta)
R = np.array([[c, -s], [s, c]], dtype=float)
return R @ S
return S
[docs]
def setup_params(self, data):
return {"M": self.M}
[docs]
@classmethod
def compute_layer(
cls,
data: "pd.DataFrame",
params,
layout,
) -> "pd.DataFrame":
M = params["M"]
xy = np.column_stack([data["x"].to_numpy(), data["y"].to_numpy()])
new = xy @ M.T
data = data.copy()
data["x"] = new[:, 0]
data["y"] = new[:, 1]
return data