X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from __future__ import annotations

from base64 import b64encode
import copy
import dataclasses
from functools import cache, lru_cache
import functools
Expand Down Expand Up @@ -767,15 +766,7 @@ def _from_any(cls, arg):
return cls(**arg)

def __hash__(self):
l = (tuple(self.get_family()),
self.get_slant(),
self.get_variant(),
self.get_weight(),
self.get_stretch(),
self.get_size(),
self.get_file(),
self.get_math_fontfamily())
return hash(l)
return hash(tuple(self.__dict__.values()))

def __eq__(self, other):
return hash(self) == hash(other)
Expand All @@ -791,7 +782,7 @@ def get_family(self):
from their respective rcParams when searching for a matching font) in
the order of preference.
"""
return self._family
return list(self._family)

def get_name(self):
"""
Expand Down Expand Up @@ -860,8 +851,8 @@ def set_family(self, family):
"""
family = mpl._val_or_rc(family, 'font.family')
if isinstance(family, str):
family = [family]
self._family = family
family = (family,)
self._family = tuple(family)

def set_style(self, style):
"""
Expand Down Expand Up @@ -1021,9 +1012,15 @@ def set_math_fontfamily(self, fontfamily):
_api.check_in_list(valid_fonts, math_fontfamily=fontfamily)
self._math_fontfamily = fontfamily

def __copy__(self):
# Bypass __init__ for speed, since values are already validated
new = FontProperties.__new__(FontProperties)
new.__dict__.update(self.__dict__)
return new

def copy(self):
"""Return a copy of self."""
return copy.copy(self)
return self.__copy__()

# Aliases
set_name = set_family
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/font_manager.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class FontProperties:
math_fontfamily: str | None = ...,
) -> None: ...
def __hash__(self) -> int: ...
def __copy__(self) -> FontProperties: ...
def __eq__(self, other: object) -> bool: ...
def get_family(self) -> list[str]: ...
def get_name(self) -> str: ...
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,8 @@ def recache(self, always=False):
y = self._y

self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
self._x, self._y = self._xy.T # views
self._x = self._xy[:, 0] # views of the x and y data
self._y = self._xy[:, 1]

self._subslice = False
if (self.axes
Expand Down
178 changes: 97 additions & 81 deletions lib/matplotlib/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from collections.abc import Sequence
import functools
import itertools
import logging
import math
from numbers import Real
Expand All @@ -24,6 +25,25 @@
_log = logging.getLogger(__name__)


@functools.lru_cache(maxsize=128)
def _rotate(theta):
"""
Return an Affine2D object that rotates by the given angle in radians.
"""
return Affine2D().rotate(theta)


def _rotate_point(angle, x, y):
"""
Rotate point (x, y) by rotation angle in degrees
"""
if angle == 0:
return (x, y)
angle_rad = math.radians(angle)
cos, sin = math.cos(angle_rad), math.sin(angle_rad)
return (cos * x - sin * y, sin * x + cos * y)


def _get_textbox(text, renderer):
"""
Calculate the bounding box of the text.
Expand All @@ -39,8 +59,8 @@ def _get_textbox(text, renderer):

projected_xys = []

theta = np.deg2rad(text.get_rotation())
tr = Affine2D().rotate(-theta)
theta = math.radians(text.get_rotation())
tr = _rotate(-theta)

_, parts = text._get_layout(renderer)

Expand All @@ -57,7 +77,7 @@ def _get_textbox(text, renderer):
xt_box, yt_box = min(projected_xs), min(projected_ys)
w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box

x_box, y_box = Affine2D().rotate(theta).transform((xt_box, yt_box))
x_box, y_box = _rotate(theta).transform((xt_box, yt_box))

return x_box, y_box, w_box, h_box

Expand Down Expand Up @@ -355,10 +375,10 @@ def _char_index_at(self, x):
return (np.abs(size_accum - std_x)).argmin()

def get_rotation(self):
"""Return the text angle in degrees between 0 and 360."""
"""Return the text angle in degrees in the range [0, 360)."""
if self.get_transform_rotates_text():
return self.get_transform().transform_angles(
[self._rotation], [self.get_unitless_position()]).item(0)
[self._rotation], [self.get_unitless_position()]).item(0) % 360
else:
return self._rotation

Expand Down Expand Up @@ -496,9 +516,6 @@ def _get_layout(self, renderer):
ymax = 0
ymin = ys[-1] - descent # baseline of last line minus its descent

# get the rotation matrix
M = Affine2D().rotate_deg(self.get_rotation())

# now offset the individual text lines within the box
malign = self._get_multialignment()
if malign == 'left':
Expand All @@ -511,16 +528,17 @@ def _get_layout(self, renderer):
for x, y, w in zip(xs, ys, ws)]

# the corners of the unrotated bounding box
corners_horiz = np.array(
[(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
corners_horiz = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]

# now rotate the bbox
corners_rotated = M.transform(corners_horiz)
angle = self.get_rotation()
rotate = functools.partial(_rotate_point, angle)
corners_rotated = [rotate(x, y) for x, y in corners_horiz]

# compute the bounds of the rotated box
xmin = corners_rotated[:, 0].min()
xmax = corners_rotated[:, 0].max()
ymin = corners_rotated[:, 1].min()
ymax = corners_rotated[:, 1].max()
xs, ys = zip(*corners_rotated)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
width = xmax - xmin
height = ymax - ymin

Expand All @@ -531,7 +549,6 @@ def _get_layout(self, renderer):

rotation_mode = self.get_rotation_mode()
if rotation_mode != "anchor":
angle = self.get_rotation()
if rotation_mode == 'xtick':
halign = self._ha_for_angle(angle)
elif rotation_mode == 'ytick':
Expand Down Expand Up @@ -577,15 +594,16 @@ def _get_layout(self, renderer):
else:
offsety = ymin1

offsetx, offsety = M.transform((offsetx, offsety))
offsetx, offsety = rotate(offsetx, offsety)

xmin -= offsetx
ymin -= offsety

bbox = Bbox.from_bounds(xmin, ymin, width, height)

# now rotate the positions around the first (x, y) position
xys = M.transform(offset_layout) - (offsetx, offsety)
xys = [(x - offsetx, y - offsety)
for x, y in itertools.starmap(rotate, offset_layout)]

return bbox, list(zip(lines, wads, xys))

Expand Down Expand Up @@ -726,11 +744,11 @@ def _get_wrap_line_width(self):
# Calculate available width based on text alignment
alignment = self.get_horizontalalignment()
self.set_rotation_mode('anchor')
rotation = self.get_rotation()
angle = self.get_rotation()

left = self._get_dist_to_box(rotation, x0, y0, figure_box)
left = self._get_dist_to_box(angle, x0, y0, figure_box)
right = self._get_dist_to_box(
(180 + rotation) % 360, x0, y0, figure_box)
(180 + angle) % 360, x0, y0, figure_box)

if alignment == 'left':
line_width = left
Expand Down Expand Up @@ -839,67 +857,65 @@ def draw(self, renderer):

renderer.open_group('text', self.get_gid())

with self._cm_set(text=self._get_wrapped_text()):
bbox, info = self._get_layout(renderer)
trans = self.get_transform()
bbox, info = self._get_layout(renderer)
trans = self.get_transform()

# don't use self.get_position here, which refers to text
# position in Text:
x, y = self._x, self._y
if np.ma.is_masked(x):
x = np.nan
if np.ma.is_masked(y):
y = np.nan
posx = float(self.convert_xunits(x))
posy = float(self.convert_yunits(y))
posx, posy = trans.transform((posx, posy))
if np.isnan(posx) or np.isnan(posy):
return # don't throw a warning here
if not np.isfinite(posx) or not np.isfinite(posy):
_log.warning("posx and posy should be finite values")
return
canvasw, canvash = renderer.get_canvas_width_height()

# don't use self.get_position here, which refers to text
# position in Text:
x, y = self._x, self._y
if np.ma.is_masked(x):
x = np.nan
if np.ma.is_masked(y):
y = np.nan
posx = float(self.convert_xunits(x))
posy = float(self.convert_yunits(y))
posx, posy = trans.transform((posx, posy))
if np.isnan(posx) or np.isnan(posy):
return # don't throw a warning here
if not np.isfinite(posx) or not np.isfinite(posy):
_log.warning("posx and posy should be finite values")
return
canvasw, canvash = renderer.get_canvas_width_height()

# Update the location and size of the bbox
# (`.patches.FancyBboxPatch`), and draw it.
if self._bbox_patch:
self.update_bbox_position_size(renderer)
self._bbox_patch.draw(renderer)

gc = renderer.new_gc()
gc.set_foreground(mcolors.to_rgba(self.get_color()), isRGBA=True)
gc.set_alpha(self.get_alpha())
gc.set_url(self._url)
gc.set_antialiased(self._antialiased)
gc.set_snap(self.get_snap())
self._set_gc_clip(gc)

angle = self.get_rotation()

for line, wad, (x, y) in info:

mtext = self if len(info) == 1 else None
x = x + posx
y = y + posy
if renderer.flipy():
y = canvash - y
clean_line, ismath = self._preprocess_math(line)

if self.get_path_effects():
from matplotlib.patheffects import PathEffectRenderer
textrenderer = PathEffectRenderer(
self.get_path_effects(), renderer)
else:
textrenderer = renderer

if self.get_usetex():
textrenderer.draw_tex(gc, x, y, clean_line,
self._fontproperties, angle,
mtext=mtext)
else:
textrenderer.draw_text(gc, x, y, clean_line,
self._fontproperties, angle,
ismath=ismath, mtext=mtext)
# Update the location and size of the bbox
# (`.patches.FancyBboxPatch`), and draw it.
if self._bbox_patch:
self.update_bbox_position_size(renderer)
self._bbox_patch.draw(renderer)

gc = renderer.new_gc()
gc.set_foreground(mcolors.to_rgba(self.get_color()), isRGBA=True)
gc.set_alpha(self.get_alpha())
gc.set_url(self._url)
gc.set_antialiased(self._antialiased)
gc.set_snap(self.get_snap())
self._set_gc_clip(gc)

angle = self.get_rotation()

for line, wad, (x, y) in info:

mtext = self if len(info) == 1 else None
x = x + posx
y = y + posy
if renderer.flipy():
y = canvash - y
clean_line, ismath = self._preprocess_math(line)

if self.get_path_effects():
from matplotlib.patheffects import PathEffectRenderer
textrenderer = PathEffectRenderer(self.get_path_effects(), renderer)
else:
textrenderer = renderer

if self.get_usetex():
textrenderer.draw_tex(gc, x, y, clean_line,
self._fontproperties, angle,
mtext=mtext)
else:
textrenderer.draw_text(gc, x, y, clean_line,
self._fontproperties, angle,
ismath=ismath, mtext=mtext)

gc.restore()
renderer.close_group('text')
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ def from_extents(*args, minpos=None):
set. This is useful when dealing with logarithmic scales and other
scales where negative bounds result in floating point errors.
"""
bbox = Bbox(np.reshape(args, (2, 2)))
bbox = Bbox(np.asarray(args, dtype=float).reshape((2, 2)))
if minpos is not None:
bbox._minpos[:] = minpos
return bbox
Expand Down
Loading
X Tutup