Matplotlib with arrows centered directly in the middle of the spines

The following question got some answers showing how to implement arrows on a matplotlib rectilinear axis such that the arrows stick to the end of the spines. I have two problems with these answers. The first is that, on my screen, the implementations using something like ax.plot(1, 0, transform=ax.get_yaxis_transform(), clip_on=False) all result in an arrow that is not directly centered on top of the spine. Second, I don’t like the appearance of the arrows in those answers. I therefore set out to try to fix it myself, resulting in the code below. Having attempted to place the arrows with a correction equal to half the thickness of the spine, I am now in the following situation: Debugging tells me that the parameters have been set correctly, but when plotting with certain parameters I can see visually that this is not the case.

Question: How can I get the arrows perfectly centered on the end of the spine?

import matplotlib.pyplot as plt
import matplotlib.lines
import matplotlib.axes
import matplotlib.figure
import matplotlib.spines


class AxesArrows:
    """
    Place (90 degree) arrows on the tips of the spines in a figure.
    The arrow ( '>', say), is given a fixed horizontal width in pixels; and its height
    will remain twice this number (in pixels).
    """
    
    def __init__(self, fig, ax: matplotlib.axes.Axes):
        self.fig, self.ax = fig, ax
        
        self.arrow_with_pixels = 10
        self.linewidth = 1.5
        clip_on = False
        
        self.x_symbol = matplotlib.lines.Line2D(xdata=[0.98, 1, 0.98], ydata=[0.02, 0, -0.02], linewidth=self.linewidth,
                                                color='black', transform=self.ax.get_yaxis_transform(),
                                                solid_joinstyle='miter', clip_on=clip_on)

        self.y_symbol = matplotlib.lines.Line2D(xdata=[0.02, 0, -0.02], ydata=[0.98, 1, 0.98], linewidth=self.linewidth,
                                                color='black', transform=self.ax.get_xaxis_transform(),
                                                solid_joinstyle='miter', clip_on=clip_on)

        # Recalibrate arrows
        self.adjust_symbol(event=None)
        self.ax.add_artist(self.x_symbol)
        self.ax.add_artist(self.y_symbol)

        # Connection id(s)
        self.cid_xlim_change = None
        self.cid_ylim_change = None
        self.cid_resize = None

        self.connect()

    def connect(self):
        """Connect to all the events we need."""
        cb_registry = ax.callbacks
        self.cid_xlim_change = cb_registry.connect('xlim_changed', self.adjust_symbol)
        self.cid_ylim_change = cb_registry.connect('ylim_changed', self.adjust_symbol)
        self.cid_resize = self.fig.canvas.mpl_connect('resize_event', self.adjust_symbol)

    def adjust_symbol(self, event):
        # 1. Get the Axes bounding box in display space. I.e., the pixel location of the axes
        # in the figure, where (0,0) is the lower left corner of the figure (window) and (px,py) is the
        # upper right corner of the figure; px an py being the number of pixels of the window in x and y directions.
        # Looks something like [[93.75  82.5] [675.  660.]]
        ax_corners_in_pixels = self.ax.get_window_extent().get_points()

        # 2. Get the display (pixel) coordinates of the ax's lower left corner
        x0, y0 = ax_corners_in_pixels[0]

        # 3. Get the display (pixel) coordinates of the ax's upper right corner
        x1, y1 = ax_corners_in_pixels[1]

        # 4. Compute the fraction that self.arrow_with_pixels will occupy of the ax's width.
        # This is equivalent to the width it will have in the ax's "axes" coordinate system.
        arrow_horizontal_fraction = self.arrow_with_pixels / (x1 - x0)

        # 5. Compute the fraction that self.arrow_with_pixels would occupy of the ax's height.
        arrow_vertical_fraction = self.arrow_with_pixels / (y1 - y0)

        # 6. Calculate the number of data points (of the axis, in the vertical direction) that
        # The number from 5) corresponds to.
        a, b = self.ax.get_ylim()
        arrow_vertical_data = arrow_vertical_fraction * (b - a)

        # 8. In the (ax's axes coords, ax's data coords) system, the arrow pointing to the right
        # and attached to the real axis is now given by the following three points:
        # A = (1 - arrow_horizontal_fraction, arrow_vertical_data)
        # B = (1, 0)
        # C = (1 - arrow_horizontal_fraction, -arrow_vertical_data)

        # 8*: An attempt to fix 8.:
        bot_spine = self.ax.spines['bottom']
        bot_spine_thickness_ax_data_points = bot_spine.get_linewidth() / 72. * self.fig.dpi / (
                self.ax.get_window_extent().get_points()[1, 1] - self.ax.get_window_extent().get_points()[0, 1]) * (
                                                     self.ax.get_ylim()[1] - self.ax.get_ylim()[0])
        A = (1 - arrow_horizontal_fraction, arrow_vertical_data - bot_spine_thickness_ax_data_points / 2)
        B = (1, - bot_spine_thickness_ax_data_points / 2)
        C = (1 - arrow_horizontal_fraction, -arrow_vertical_data - bot_spine_thickness_ax_data_points / 2)

        self.x_symbol.set(xdata=[A[0], B[0], C[0]], ydata=[A[1], B[1], C[1]])

        # 9. Does the same as 8), but for the upward arrow. Here, however, the coordinate system is the other way
        # around, i.e. (ax's data coords, ax's axes coords)
        c, d = self.ax.get_xlim()
        arrow_horizontal_data = arrow_horizontal_fraction * (d - c)

        D = (-arrow_horizontal_data, 1 - arrow_vertical_fraction)
        E = (0, 1)
        F = (arrow_horizontal_data, 1 - arrow_vertical_fraction)
        self.y_symbol.set(xdata=[D[0], E[0], F[0]], ydata=[D[1], E[1], F[1]])


# Try figsize=(6, 6), dpi=800 (both arrows clearly not centered on spines)
fig = plt.figure(figsize=(6, 6), dpi=200)
ax = fig.add_subplot(projection='rectilinear')
ax.plot([0, 1, 2], [0, 1, 2])
ax.spines[['right', 'top']].set_visible(False)
ax.spines[['left', 'bottom']].set_position(('data', 0))
_ = AxesArrows(fig=fig, ax=ax)
plt.show()

Goal of the code: Place 90 degree arrows centered at the end of each spine. If my implementation is correct, the angle and pixel size of the arrows should remain the same no matter how the axes’ limits are changed and the figure resized.

As far as I understand the spine(‘s lines), the bottom spine’s upper edge lies along axes’ (x-)data=0 line, and that something similar applies to the left spine. Using the code at comment 8), I have the impression that the right-pointing arrow is not correctly centered (perhaps I’m mistaken). Using the code at comment 8*) adjusts the
right-pointing arrow down by half the size of the bottom spine. Still, the plot is not quite correct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật