5

I often want to highlight a point along a curve using matplotlib to make a plot that looks like: Basic Plot

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.

2
  • There might actually be the option to use some kind of blended transform on the points that constitute the lines. However since only 1 of the 4 coordinates lives in the axes system, while the other three live in the data system, I have no idea how to implement that. Commented Feb 20, 2017 at 19:18
  • What does "I could attack this using minor grid points" mean? Commented Feb 20, 2017 at 19:27

1 Answer 1

5

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()

enter image description here

For saving, the redrawing would need to be performed manually directly before the save command, like

plt.gcf().canvas.draw()
plt.savefig(...)
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.