user13456
user13456

Reputation: 41

Using matplotlib, how to update part of a figure with the action of sliders?

I need to interact with the following figure. I want to add two sliders to change the (x,y) position of the red point in the following figure. The dash line and circle should also be changed with the action of the red point, while the blue point should stay there.

TOF I have found some demos of sliders under matplotlib, while all of them updated a curve of the figure. My figure is a bit more complex than them, I do not know how to update parts of it.

import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import rcParams
from matplotlib.widgets import Slider

fig, ax = plt.subplots()
ax.axis([-5,15,-5,15])
ax.set_aspect(1) # x y轴等比例显示

anchorNum = 4
anchorX = [0,10,0,10]
anchorY = [0,0,10,10]
anchorName = ['A0', 'A1', 'A2', 'A3']
colorArray = ['blue', 'green', 'magenta', 'green'] 

ax.scatter(anchorX, anchorY, s=200) # plot stable blue point

for i, txt in enumerate(anchorName):
    ax.annotate(txt, (anchorX[i], anchorY[i]), fontsize = 20)

initTagX = np.random.random_sample()*10
initTagY = np.random.random_sample()*10

tagPos = [initTagX, initTagY]

ax.scatter(tagPos[0], tagPos[1], c='red', s=300) # plot the red point 

# plot the dash line
for i in range(anchorNum):
    anchorPos = (anchorX[i], anchorY[i])
    diffVector = [(tagPos[0] - anchorX[i]), (tagPos[1] - anchorY[i])]
    dist = np.linalg.norm(diffVector,ord = 2) # 计算二阶范数 平方和开根号
    circle = plt.Circle(anchorPos, dist, color = colorArray[i], fill = False, linestyle='--', linewidth=4)
    ax.add_artist(circle)
    ax.plot([anchorX[i], tagPos[0]], [anchorY[i], tagPos[1]], linestyle = '--',  color = colorArray[i], linewidth=4)

plt.show()

Upvotes: 1

Views: 739

Answers (1)

tmdavison
tmdavison

Reputation: 69164

The only difference between your figure and the examples you might have seen is that you need to update several things at once when the sliders are changed.

When the x or y position is changed, you need to update the x or y position of the red point, the radius of the 4 circles, and the position of the 4 dashed lines.

The code below does all that. When the sliders are moved, either update_rp_x or update_rp_y is called. That in turn called the function update_plot with the updated coordinates, and then moved all the relevant plot features around. For the red point, I switched from using ax.scatter to ax.plot, because its a little simpler to update its position (e.g. rp.set_xdata()). The lines and circles are stored in lists when first plotted, so when we update them, we can just loop overs those lists and update each one in turn. The circles just need to change their radius, which we can do with Circle.set_radius, and the lines with set_xdata and set_ydata again.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

fig, ax = plt.subplots()
ax.axis([-5,15,-5,15])
ax.set_aspect(1) # x y

# Make room at the bottom for sliders
fig.subplots_adjust(bottom=0.3)

# Initial tag location
initTagX = np.random.random_sample()*10
initTagY = np.random.random_sample()*10
tagPos = [initTagX, initTagY]

# Set up the anchors
anchorNum = 4
anchorX = [0,10,0,10]
anchorY = [0,0,10,10]
anchorName = ['A0', 'A1', 'A2', 'A3']
colorArray = ['blue', 'green', 'magenta', 'green'] 

# Plot the anchors
ax.scatter(anchorX, anchorY, s=100) # plot stable blue point

# Label the anchors
for i, txt in enumerate(anchorName):
    ax.annotate(txt, (anchorX[i], anchorY[i]), fontsize = 10)

# Plot initial location of red point
rp, = ax.plot(tagPos[0], tagPos[1], c='red', marker='o', ms=10) # plot the red point 

# Store circles and lines for update later
circles = []
lines = []

# Plot initial circles
for i in range(anchorNum):
    anchorPos = (anchorX[i], anchorY[i])
    diffVector = [(tagPos[0] - anchorX[i]), (tagPos[1] - anchorY[i])]
    dist = np.linalg.norm(diffVector,ord = 2) 
    circle = plt.Circle(anchorPos, dist, color = colorArray[i], fill = False, linestyle='--', linewidth=2)
    circles.append(ax.add_artist(circle))
    lines.append(ax.plot([anchorX[i], tagPos[0]], [anchorY[i], tagPos[1]], linestyle = '--',
        color = colorArray[i], linewidth=2)[0])

def update_plot(xpos, ypos):
    ''' 
    This function updates the radius of the circles
    and the position of the dashed lines
    '''

    # Update the tag position
    tagPos = [xpos, ypos]
    rp.set_xdata(xpos)
    rp.set_ydata(ypos)

    for i in range(anchorNum):
        anchorPos = (anchorX[i], anchorY[i])
        diffVector = [(tagPos[0] - anchorX[i]), (tagPos[1] - anchorY[i])]
        dist = np.linalg.norm(diffVector,ord = 2)

        # Now we actually update the circles and dashed lines
        circles[i].set_radius(dist)
        lines[i].set_xdata([anchorX[i], tagPos[0]])
        lines[i].set_ydata([anchorY[i], tagPos[1]])

    return tagPos

def update_rp_x(xpos):
    ''' This function updates the x position of the red point '''
    global tagPos
    tagPos = update_plot(xpos, tagPos[1])
    fig.canvas.draw_idle()

def update_rp_y(ypos):
    ''' This function updates the y position of the red point '''
    global tagPos
    tagPos = update_plot(tagPos[0], ypos)
    fig.canvas.draw_idle() 

# Create Axes for the sliders
axcolor = '#909090'
sax_x = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
sax_y = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)

# Create sliders
sx = Slider(sax_x, 'x position', -5, 15, valinit=initTagX)
sy = Slider(sax_y, 'y position', -5, 15, valinit=initTagY)

# Tell sliders which function to call when changed
sx.on_changed(update_rp_x)
sy.on_changed(update_rp_y)

plt.show()

enter image description here enter image description here

Upvotes: 2

Related Questions