I often want to highlight a point along a curve using matplotlib
to make a plot that looks like:
The following code was used to create the plot
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
y = np.array([0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100])/100.
x = 100. - np.array([
99.79,98.96,98.65,98.39,98.13,97.88,97.61,97.33,97.01,96.65,96.21,
95.72,95.16,94.46,93.52,92.31,90.66,88.48,84.04,79.34,19.32])
ax = plt.subplot(111)
line = plt.plot(x,y)
def highlight_point(ax,line,point,linestyle=':'):
c = line.get_color()
xmin = ax.get_xlim()[0]
ymin = ax.get_ylim()[0]
ax.plot([xmin,point[0]],[point[1],point[1]],color=c,linestyle=linestyle)
ax.plot([point[0],point[0]],[ymin,point[1]],color=c,linestyle=linestyle)
plt.xlim([0,85])
plt.ylim([0,1])
highlight_point(ax,line[0],[x[10],y[10]])
plt.show()
The above method fails when the xlim
and ylim
are failed to be entered or if another plot were added to the figure later. I would like some combination of axhline
or hlines
where I can specify the left/bottom of the plot to a certain mathematical point.
A way to go could be to update the lines each time the canvas gets redrawn. To this end we could create a class PointMarkers
with an update method that is connected to the draw_event
listener. This way the lines will update not only if points are added after the marker lines' creation, but also when the canvas is resized or panned.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
class PointMarker():
def __init__(self, ax, point, **kwargs):
self.ax = ax
self.point = point
if "line" in kwargs:
self.c = kwargs.get("line").get_color()
else:
self.c = kwargs.get("color", "b")
self.ls=kwargs.get("linestyle", ':')
self.vline, = self.ax.plot([],[],color=self.c,linestyle=self.ls)
self.hline, = self.ax.plot([],[],color=self.c,linestyle=self.ls)
self.draw()
def draw(self):
xmin = ax.get_xlim()[0]
ymin = ax.get_ylim()[0]
self.vline.set_data([self.point[0], self.point[0]], [ymin,self.point[1]])
self.hline.set_data([xmin, self.point[0]], [self.point[1], self.point[1]])
class PointMarkers():
pointmarkers = []
def add(self,ax, point, **kwargs ):
pm = PointMarker(ax, point, **kwargs)
self.pointmarkers.append(pm)
def update(self, event=None):
for pm in self.pointmarkers:
pm.draw()
x = np.arange(1,17)
y = np.log(x)
ax = plt.subplot(111)
line = plt.plot(x,y)
# register the markers
p = PointMarkers()
p.add(ax,[x[5],y[5]], line=line[0])
p.add(ax,[x[12],y[12]], color="purple", linestyle="-.")
# connect event listener
cid = plt.gcf().canvas.mpl_connect("draw_event", p.update)
#testing: draw some new points or change axis limits
plt.plot([5,11],[-0.5,0.6])
#plt.xlim([0,85])
#plt.ylim([0,1])
plt.show()
For saving, the redrawing would need to be performed manually directly before the save command, like
plt.gcf().canvas.draw()
plt.savefig(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With