From a5152cfa26f0ae0f6acbd82bea313c307c89b0ab Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Wed, 7 May 2025 12:05:32 -0400 Subject: [PATCH 1/4] Fix `label_line_ends` behaviour when lines go past right xlim --- niceplots/utils.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/niceplots/utils.py b/niceplots/utils.py index 61b9fef..08c1f85 100644 --- a/niceplots/utils.py +++ b/niceplots/utils.py @@ -303,10 +303,16 @@ def label_line_ends(ax, lines=None, labels=None, colors=None, x_offset_pts=6, y_ annotations = [] for line, label, color in zip(lines, labels, colors): - # Get the x, y coordinates of the right-most point on the line - maxXIndex = np.argmax(line.get_xdata()) - x = line.get_xdata()[maxXIndex] - y = line.get_ydata()[maxXIndex] + # Get the x, y coordinates of the right-most point on the line, or the right xlim of the axes if that is lower + xData = line.get_xdata() + yData = line.get_ydata() + maxXIndex = np.argmax(xData) + x = xData[maxXIndex] + y = yData[maxXIndex] + rightXlim = ax.get_xlim()[1] + if x > rightXlim: + x = rightXlim + y = yData[np.argmax(xData >= x)] annote = ax.annotate( label, xy=(x, y), From 7950370bb875866b8e04da4421283f0d0a68e3be Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Wed, 7 May 2025 12:14:02 -0400 Subject: [PATCH 2/4] Generalise for x and y limits --- niceplots/utils.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/niceplots/utils.py b/niceplots/utils.py index 08c1f85..1ea4635 100644 --- a/niceplots/utils.py +++ b/niceplots/utils.py @@ -303,16 +303,22 @@ def label_line_ends(ax, lines=None, labels=None, colors=None, x_offset_pts=6, y_ annotations = [] for line, label, color in zip(lines, labels, colors): - # Get the x, y coordinates of the right-most point on the line, or the right xlim of the axes if that is lower + # Get the x, y coordinates of the right-most point on the line that is within the axis limits xData = line.get_xdata() yData = line.get_ydata() + xlim = ax.get_xlim() + ylim = ax.get_ylim() + + pointsInXLimits = np.logical_and(xData >= xlim[0], xData <= xlim[1]) + pointsInYLimits = np.logical_and(yData >= ylim[0], yData <= ylim[1]) + pointsInLimits = np.logical_and(pointsInXLimits, pointsInYLimits) + xData = xData[pointsInLimits] + yData = yData[pointsInLimits] + maxXIndex = np.argmax(xData) x = xData[maxXIndex] y = yData[maxXIndex] - rightXlim = ax.get_xlim()[1] - if x > rightXlim: - x = rightXlim - y = yData[np.argmax(xData >= x)] + annote = ax.annotate( label, xy=(x, y), From 767edcc53a331a2be8e5766d84b73d89b30b7fde Mon Sep 17 00:00:00 2001 From: Alasdair Gray Date: Wed, 7 May 2025 12:18:26 -0400 Subject: [PATCH 3/4] Update docstring --- niceplots/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/niceplots/utils.py b/niceplots/utils.py index 1ea4635..a62ace4 100644 --- a/niceplots/utils.py +++ b/niceplots/utils.py @@ -250,9 +250,8 @@ def draggable_legend(axis=None, color_on=True, **kwargs): def label_line_ends(ax, lines=None, labels=None, colors=None, x_offset_pts=6, y_offset_pts=0, **kwargs): """Place a label just to the right of each line in the axes - Note: Because the labels are placed outside of the axes, this function works best for plots where all lines end as - close to the right edge of the axes as possible. Additionally you need to either use constrained_layout=True - (as NicePlots styles do), or call plt.tight_layout() after calling this function. + Note: If any of the lines you are labelling go beyond the axis limits, make sure to call this function after setting + the axis limits, otherwise the labels will not be placed correctly Parameters ---------- From 87d1c6c5a9f3f61f7cb35dec7d9905d3c0be8f03 Mon Sep 17 00:00:00 2001 From: Alasdair Christison Gray Date: Tue, 13 May 2025 16:04:04 -0400 Subject: [PATCH 4/4] Put labels at intersection of line and axis limits --- niceplots/utils.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/niceplots/utils.py b/niceplots/utils.py index a62ace4..bdda15c 100644 --- a/niceplots/utils.py +++ b/niceplots/utils.py @@ -311,16 +311,46 @@ def label_line_ends(ax, lines=None, labels=None, colors=None, x_offset_pts=6, y_ pointsInXLimits = np.logical_and(xData >= xlim[0], xData <= xlim[1]) pointsInYLimits = np.logical_and(yData >= ylim[0], yData <= ylim[1]) pointsInLimits = np.logical_and(pointsInXLimits, pointsInYLimits) - xData = xData[pointsInLimits] - yData = yData[pointsInLimits] - maxXIndex = np.argmax(xData) - x = xData[maxXIndex] - y = yData[maxXIndex] + # If the entre line is outside the axis limits, skip it + if not np.any(pointsInLimits): + continue + elif pointsInLimits[-1]: + # If the last point is within the limits, use that one + labelCoord = (xData[-1], yData[-1]) + else: + # If the last point is outside the limits, but part of the line is within the limits, put the label at the + # point where the line intersects the limits + + # get the index of the last point within the limits + lastIndex = np.where(pointsInLimits)[0][-1] + # get the x and y coordinates of the last point within the limits and the first point outside the limits + innerPoint = np.array([xData[lastIndex], yData[lastIndex]]) + outerPoint = np.array([xData[lastIndex + 1], yData[lastIndex + 1]]) + + # Find the intersection point with the x and y limits as a fraction of the line segment, need to check upper + # and lower limits + if not pointsInXLimits[lastIndex + 1]: + if outerPoint[0] > innerPoint[0]: + xIntersect = (xlim[1] - innerPoint[0]) / (outerPoint[0] - innerPoint[0]) + else: + xIntersect = (xlim[0] - innerPoint[0]) / (outerPoint[0] - innerPoint[0]) + else: + xIntersect = np.inf + if not pointsInYLimits[lastIndex + 1]: + if outerPoint[1] > innerPoint[1]: + yIntersect = (ylim[1] - innerPoint[1]) / (outerPoint[1] - innerPoint[1]) + else: + yIntersect = (ylim[0] - innerPoint[1]) / (outerPoint[1] - innerPoint[1]) + else: + yIntersect = np.inf + + # Place label at the first intersection + labelCoord = innerPoint + (outerPoint - innerPoint) * min(xIntersect, yIntersect) annote = ax.annotate( label, - xy=(x, y), + xy=labelCoord, xytext=(x_offset_pts, y_offset_pts), color=color, textcoords="offset points",