我正在尝试创建一个圆,无论轴缩放如何,它都显示一个圆,但放置在数据坐标中,其半径取决于 y 轴的缩放。基于变换教程,更具体地说,关于在物理坐标中绘制,我需要一个如下所示的管道:
from matplotlib import pyplot as plt, patches as mpatch, transforms as mtrans
fig, ax = plt.subplots()
x, y = 5, 10
r = 3
transform = fig.dpi_scale_trans + fig_to_data_scaler + mtrans.ScaledTranslation(x, y, ax.transData)
ax.add_patch(mpatch.Circle((0, 0), r))
目标是创建一个在图形级别正确缩放的圆,将其缩放到正确的高度,然后在数据坐标中移动它。
fig.dpi_scale_trans
和 mtrans.ScaledTranslation(x, y, ax.transData)
按预期工作。但是,我无法为 fig_to_data_scaler
提供足够的定义。
很明显,我需要一个混合变换,将
ax.transData
与 fig.dpi_scale_trans
(反转?)相结合,然后对 x
使用相同的值,无论数据转换如何。我该怎么做?
我查看的另一个参考文献:https://stackoverflow.com/a/56079290/2988730。
这是我尝试构建但未成功的转换图:
vertical_scale_transform = mtrans.blended_transform_factory(mtrans.IdentityTransform(), fig.dpi_scale_trans.inverted() + mtrans.AffineDeltaTransform(ax.transData))
reflection = mtrans.Affine2D.from_values(0, 1, 1, 0, 0, 0)
fig_to_data_scaler = vertical_scale_transform + reflection + vertical_scale_transform # + reflection, though it's optional
aspect_ratio = fig.get_figheight() / fig.get_figwidth()
fig.get_figheight()
:此函数返回图形的高度(以英寸为单位)。fig.get_figwidth()
:此函数返回图形的宽度(以英寸为单位)。长宽比的计算方式为高度除以宽度。该比率用于确保缩放变换在应用于圆时保持正确的比例。
transform = mtrans.Affine2D().scale(aspect_ratio, 1.0) + ax.transData
mtrans.Affine2D()
:这将创建一个新的仿射变换,它是平移、缩放、旋转和剪切变换的组合。scale(aspect_ratio, 1.0)
:此方法沿 x 和 y 轴按指定因子缩放变换。按长宽比缩放 x 轴,按 1.0 缩放 y 轴,以确保根据图形的长宽比正确缩放圆的半径。ax.transData
:此转换将数据坐标映射到显示坐标。这将自定义缩放与从数据坐标到显示坐标的现有转换相结合。变换调整数据坐标中圆的半径,因此在图形上渲染时它看起来正确缩放,这一点很重要,因为如果不考虑图形的纵横比,图形的纵横比可能会扭曲对象,导致圆显示为椭圆形 .
import matplotlib.pyplot as plt
import matplotlib.patches as mpatch
import matplotlib.transforms as mtrans
from typing import Tuple
def create_scaled_circle(ax: plt.Axes, center: Tuple[float, float], radius: float) -> mpatch.Circle:
fig = ax.figure
x, y = center
# Calculate aspect ratio of the figure
aspect_ratio = fig.get_figheight() / fig.get_figwidth()
# Create a transform that scales the radius correctly in data coordinates
transform = mtrans.Affine2D().scale(aspect_ratio, 1.0) + ax.transData
circle = mpatch.Circle(center, radius, transform=transform, facecolor='none', edgecolor='red')
return circle
fig, ax = plt.subplots()
circle1 = create_scaled_circle(ax, (5, 10), 3)
circle2 = create_scaled_circle(ax, (7, 15), 5)
ax.add_patch(circle1)
ax.add_patch(circle2)
ax.set_xlim(0, 20)
ax.set_ylim(0, 20)
plt.show()
# Define 10 test cases with different centers, radii, axis limits, and quadrants
test_cases = [
((5, 10), 3, (-20, 20), (-20, 20)), # Quadrant I
((-7, 15), 5, (-20, 20), (-20, 20)), # Quadrant II
((-10, -5), 2, (-20, 20), (-20, 20)), # Quadrant III
((15, -10), 4, (-20, 20), (-20, 20)), # Quadrant IV
((8, 8), 3, (-30, 30), (-30, 30)), # Near origin, all quadrants
((-12, -12), 6, (-20, 20), (-20, 20)), # Quadrant III
((3, -18), 1.5, (-20, 20), (-20, 20)), # Quadrant IV
((-6, 6), 2.5, (-15, 15), (-15, 15)), # Quadrant II
((9, -9), 4.5, (-25, 25), (-25, 25)), # Quadrant IV
((-11, 7), 3.5, (-20, 20), (-20, 20)), # Quadrant II
]
# Create subplots for each test case
fig, axs = plt.subplots(5, 2, figsize=(15, 25))
for ax, (center, radius, xlim, ylim) in zip(axs.flatten(), test_cases):
circle = create_scaled_circle(ax, center, radius)
ax.add_patch(circle)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.set_aspect('equal') # Ensuring equal aspect ratio for display
plt.tight_layout()
plt.show()