2

I'm creating a violinplot of some data and afterwards I render a scatterplot with individual data points (red points in example) to three subplots.

enter image description here

Since the generation of the violinplot is relatively time consuming, I'm generating the violinplot only once, then add the scatterplot for one data row, write the result file, remove the scatterplots from the axes and add the scatterplots for the next row.

Everything works, but I would like to add the option, to show() each plot prior to saving it.

If I'm using plt.show(), the figure is shown correctly, but afterwards the figure seems to be cleared and in the next iteration I'm getting the plot without the violin plots.

Is there any way to preserve the content of the figure after plt.show()?

In short, my code is

fig = generate_plot(ws, show=False) #returns the fig instance of the violin plot

#if I do plt.show() here (or in "generate_plot()"), the violin plots are gone.

ax1, ax3, ax2 = fig.get_axes()
scatter1 = ax1.scatter(...) #draw scatter plot for first axes
[...] #same vor every axis
plt.savefig(...)
scatter1.remove()
OBu
  • 4,977
  • 3
  • 29
  • 45
  • 1
    I had similar situation. Does this post help? I'm reading now. https://stackoverflow.com/questions/21875356/saving-a-figure-after-invoking-pyplot-show-results-in-an-empty-file – dkato Dec 02 '17 at 08:08
  • 2
    `plt.show()` is meant to be called exactly once at the end of the script. After that the figure is removed from the pyplot statemachine, such that a second call to `plt.show()` will not show this figure again. You may still save it though using `fig.savefig()` instead of `plt.savefig()`. But that will not help for showing it again. So you'd best be using interactive mode or a `FuncAnimation` or some event handling inside the event loop. – ImportanceOfBeingErnest Dec 02 '17 at 12:35

3 Answers3

2

I was thinking that a possible option is to use the event loop to advance through the plots. The following would define an updating function, which changes only the scatter points, draws the image and saves it. We can manage this via a class with a callback on the key_press - such then when you hit Space the next image is shown; upon pressing Space on the last image, the plot is closed.

import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import numpy as np

class NextPlotter(object):
    def __init__(self, fig, func, n):
        self.__dict__.update(locals())
        self.i = 0
        self.cid = self.fig.canvas.mpl_connect("key_press_event", self.adv)

    def adv(self, evt):
        if evt.key == " " and self.i < self.n:
            self.func(self.i)
            self.i+=1
        elif self.i >= self.n:
            plt.close("all")

#Start of code:
# Create data
pos = [1, 2, 4, 5, 7, 8]
data = [np.random.normal(0, std, size=100) for std in pos]
data2 = [np.random.rayleigh(std, size=100) for std in pos]
scatterdata = np.random.normal(0, 5, size=(10,len(pos)))

#Create plot
fig, axes = plt.subplots(ncols=2)

axes[0].violinplot(data, pos, points=40, widths=0.9,
                      showmeans=True, showextrema=True, showmedians=True)
axes[1].violinplot(data2, pos, points=40, widths=0.9,
                      showmeans=True, showextrema=True, showmedians=True)

scatter = axes[0].scatter(pos, scatterdata[0,:], c="crimson", s=60)
scatter2 = axes[1].scatter(pos, scatterdata[1,:], c="crimson", s=60)  

# define updating function
def update(i):
    scatter.set_offsets(np.c_[pos,scatterdata[2*i,:]])
    scatter2.set_offsets(np.c_[pos,scatterdata[2*i+1,:]])
    fig.canvas.draw()
    plt.savefig("plot{i}.png".format(i=i))

# instantiate NextPlotter; press <space> to advance to the next image        
c = NextPlotter(fig, update, len(scatterdata)//2)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
0

A workaround could be to not remove the scatterplot.

Why not keep the scatter plot axis, and just update the data for that set of axis?

You will most likely need a plt.draw() after update of scatter plot data to force a new rendering.

ahed87
  • 1,240
  • 10
  • 10
  • that might be an easier solution for exchanging the scatter plot, but in fact this part f my solution works quite well. My problem is: If I issue a plot.show() prior to adding the scatter plot, the violin plot is gone. I'll update the sample code to clarify this. – OBu Dec 02 '17 at 08:04
  • or by doing it differently you don't have the original problem any longer, so just a different way to the same goal - plots that does what you want. `plt.show()` is doing all sort of things behind the scenes which is best to try and stay away from. – ahed87 Dec 02 '17 at 10:15
0

I found a way to draw figures interactively here. plt.ion() and block the process with input() seems to be important.

import matplotlib.pyplot as plt
plt.ion()

fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.set_xlim([-1, 5])
ax.set_ylim([-1, 5])
ax.grid('on')

for i in range(5):
    lineObject = ax.plot(i,i,'ro')
    fig.savefig('%02d.png'%i)
    # plt.draw() # not necessary?
    input()
    lineObject[0].remove()

I also tried to block the process with time.sleep(1), but it does not work at all.

dkato
  • 895
  • 10
  • 28
  • Well, I still managed to save all figs using your method, but plt.show() did not show anything but a blank screen (with and without calling draw()) – OBu Dec 02 '17 at 18:24