matplotlib colorbar update/remove destroys axes layout - python

I have matplotlib embedded in a tkinter gui. With the seaborn heatmap function, I create a heatmap and a colorbar which works as I want it to when creating the first plot. However, if I plot again, this will not overwrite the colorbar but add another colorbar to my figure. I end up with to many colorbars this way.
The figure I create for the plot contains two axes:
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001F36C8C3390>, <matplotlib.axes._subplots.AxesSubplot object at 0x000001F36D6ABF98>]
the first one is the plot itself and the second the colorbar and looks like this:
plot 1
If I plot again, the result is:
plot 2
Simply deleting the colorbar with
self.fig.axes[1].remove()
before creating the next plot doesn't do the trick because it will just remove the colorbar but the layout of the plot keeps shrinking:
plot 3
plot 4
Note that the figure size stays the same but the size of the plot keeps getting smaller when I plot again and the colorbar moves futher to the left while the entire right part of the plot stays white.
As I create a tkinter gui, the ploting window is initialized when the program is first run.
self.fig = plt.figure.Figure( facecolor = "white", figsize = (7,4))
self.ax = self.fig.subplots()
self.x_data = x_data
self.y_data = y_data
when somebody presses a plot button the plot is created
def plot_on_plotframe(self):
self.ax.cla()
#executes required matplotlib layout
self.plotlayout[self.plot_type]()
print('plottype: {}'.format(self.plot_type))
#print('Plot xdata: {}'.format(self.x_data))
self.canvas.draw()
I need to make different types of plot and the proper type is selected by plotlayout:
self.ax = sns.heatmap(self.x_data, vmin=self.settings[2][0], vmax=self.settings[2][1], cmap='viridis', fmt=self.settings[0], annot=self.settings[1], linewidths=0.5, annot_kws={'size': 8}, ax = self.ax)
self.ax.set_xticklabels(self.ax.get_xticklabels(), rotation=0)
self.fig.tight_layout()
It would be fantastic if somebody could tell me why matplotlib messes with the layout even after I delete the old colorbar in the first place. I think this has something to do with the gridSpec but I don't get how I tell matplotlib to reset the layout properly. Also any other suggestions on how to resovle this?
Thanks in advance

Related

trying to save the figure of stacked bar chart in canvas widget

I grouped the data and tried for a stacked bar chart and it worked fine the figure is also plotted in the interpreter with an ipynb jupyter notebook file as show in the picture attached, but when I want to use that figure in the canvas of matplotlib.backends canvas for displaying plots figure was not plotted just empty axes,
need some solution to save the subplot and utilize it for displaying in canvas
can any share their knowledge with me to solve my problem
dfd = df.groupby(['Region','Sub-Category']).aggregate({'Sales':sum}).unstack(-2)
fig, ax = plt.subplots(figsize =(7, 5))
below is the core detailed code
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_excel("C:/Users/OneDrive/Desktop/Excel_files/Sample - Superstore2.xlsx")
df.groupby(['Category','Sub-Category']).aggregate({'Sales':sum}).unstack(-2).plot(kind = 'bar',stacked= True,figsize = (15,7) )
plt.show()
this is my full code what I want is like I to want use its figure to display in the Tkinter canvas widget, as like we have option in below possible way as far as my knowledge
fig, ax = plt.subplots(1, figsize=(7, 5))
by using ax we will plot graphs and those graphs will be stored in fig variable as subplots and this fig variable can be used to display the figure in canvas.
type of fig varibale is -> <class 'matplotlib.figure.Figure'>
but when i use ax for plotting the stacked bar plot its not been plotted properly

Problem updating imshow in loop and colormap after loop

I have two imshow() problems that I suspect are closely related.
First, I can't figure out how to use set_data() to update an image I've created with imshow().
Second, I can't figure out why the colorbar I add to the imshow() plot after I'm done updating the plot doesn't match the colorbar I add to an imshow() plot of the same data that I create from scratch after I'm done taking data. The colorbar of the second plot appears to be correct.
Background.
I'm collecting measurement data in two nested loops, with each loop controlling one of the measurement conditions. I'm using pyplot.imshow() to plot my results, and I'm updating the imshow() plot every time I take data in the inner loop.
What I have works in terms of updating the imshow() plot but it seems to be getting increasingly slower as I add more loop iterations, so it's not scaling well. (The program I've included in with this post creates a plot that is eight rows high and six columns wide. A "real" plot might be 10x or 20x this size, in both dimensions.)
I think what I want to do is use the image's set_data() method but I can't figure out how. What I've tried either throws an error or doesn't appear to have any effect on the imshow() plot.
Once I'm done with the "take the data" loops, I add a colorbar to the imshow() plot I've been updating. However, the colorbar scale is obviously bogus.
In contrast, if I take create an entirely new imshow() plot, using the data I took in the loops, and then add a colorbar, the colorbar appears to be correct.
My hunch is the problem is with the vmin and vmax values associated with the imshow() plot I'm updating in the loops but I can't figure out how to fix it.
I've already looked at several related StackOverflow posts. For example:
update a figure made with imshow(), contour() and quiver()
Update matplotlib image in a function
How to update matplotlib's imshow() window interactively?
These have helped, in that they've pointed me to set_data() and given me solutions to some other
problems I had, but I still have the two problems I mentioned at the start.
Here's a simplified version of my code. Note that there are repeated zero values on the X and Y axes. This is on purpose.
I'm running Python 3.5.1, matplotlib 1.5.1, and numpy 1.10.4. (Yes, some of these are quite old. Corporate IT reasons.)
import numpy as np
import matplotlib.pyplot as plt
import random
import time
import warnings
warnings.filterwarnings("ignore", ".*GUI is implemented.*") # Filter out bogus matplotlib warning.
# Create the simulated data for plotting
v_max = 120
v_step_size = 40
h_max = 50
h_step_size = 25
scale = 8
v_points = np.arange(-1*abs(v_max), 0, abs(v_step_size))
v_points = np.append(v_points, [-0.0])
reversed_v_points = -1 * v_points[::-1] # Not just reverse order, but reversed sign
v_points = np.append(v_points, reversed_v_points)
h_points = np.arange(-1*abs(h_max), 0, abs(h_step_size))
h_points = np.append(h_points, [-0.0])
reversed_h_points = -1 * h_points[::-1] # Not just reverse order, but reversed sign
h_points = np.append(h_points, reversed_h_points)
h = 0 # Initialize
v = 0 # Initialize
plt.ion() # Turn on interactive mode.
fig, ax = plt.subplots() # So I have access to the figure and the axes of the plot.
# Initialize the data_points
data_points = np.zeros((v_points.size, h_points.size))
im = ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax.set_title('Dummy title for initial plot')
# Set up the X-axis ticks and label them
ax.set_xticks(np.arange(len(h_points)))
ax.set_xticklabels(h_points)
ax.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax.set_yticks(np.arange(len(v_points)))
ax.set_yticklabels(v_points)
ax.set_ylabel('Vertical axis measurement values')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
for v, v_value in enumerate(v_points):
for h, h_value in enumerate(h_points):
# Measurement goes here.
time.sleep(0.1) # Simulate the measurement delay.
measured_value = scale * random.uniform(0.0, 1.0) # Create simulated data
data_points[v][h] = measured_value # Update data_points with the simulated data
# Update the heat map with the latest point.
# - I *think* I want to use im.set_data() here, not ax.imshow(), but how?
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.draw()
# Create a colorbar
# - Except the colorbar here is wrong. It goes from -0.10 to +0.10 instead
# of matching the colorbar in the second imshow() plot, which goes from
# 0.0 to "scale". Why?
cbar = ax.figure.colorbar(im, ax=ax)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the colorbar to appear
plt.show()
fig2, ax2 = plt.subplots() # So I have access to the figure and the axes of the plot.
im = ax2.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax2.set_title('Dummy title for plot with pseudo-data')
# Set up the X-axis ticks and label them
ax2.set_xticks(np.arange(len(h_points)))
ax2.set_xticklabels(h_points)
ax2.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax2.set_yticks(np.arange(len(v_points)))
ax2.set_yticklabels(v_points)
ax2.set_ylabel('Vertical axis measurement values')
# Create a colorbar
cbar = ax2.figure.colorbar(im, ax=ax2)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
dummy = input("In interactive mode, press the Enter key when you're done with the plots.")
OK, I Googled some more. More importantly, I Googled smarter and figured out my own answer.
To update my plot inside my nested loops, I was using the command:
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
What I tried using to update my plot more efficiently was:
im.set_data(data_points)
What I should have used was:
im.set_data(data_points)
im.autoscale()
This updates the pixel scaling, which fixed both my "plot doesn't update" problem and my "colorbar has the wrong scale" problem.

How do I position the axis frame inside a figure without changing the size of the figure? [Python, matplotlib]

I'm trying to create a video of many figures, so I need the axis to remain steady across multiple, independent figures. However, the y-axis changes scale, so the framing of the axis keeps moving as the ticklabels change. I'm trying to manually tell matplotlib exactly what size the whole figure should be and tell it exactly the position of the axis within the figure, but it's not working properly.
Here's what a base figure looks like:
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_subplot()
ax.plot([5,10],[800,900])
plt.show()
Here is one way for how I'm trying to change it if I want the axis frame to start at left=0.5, bottom=0.5, width=0.2, and height=0.2. I've tried many different ways, and all have failed, so this is illustrative of what I'm trying to do:
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_axes((0.5,0.5,0.2,0.2))
ax.plot([5,10],[800,900])
plt.show()
Now, I want it to look more like this so that the black box of the axis frame will be in the exact same position for every figure, and each figure will be the exact same size. That way, when I make it an animation, the black frame won't be jerking around. (Obviously, I wouldn't make the buffer that big in the real video.)
You need to use ax.set_position.
If your ax box initially occupies the full figure, you can create a new size relatively to the old one, for example:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 4), facecolor=(0.5, 0.5, 0.5))
ax = fig.add_subplot(111)
bbox = ax.get_position()
new_bbox = (bbox.x0+0.40, bbox.y0+0.40, bbox.width*0.5, bbox.height*0.5)
ax.set_position(new_bbox)
ax.plot([5, 10], [800, 900])
plt.show()

how to make a graph fill all the window

I am ploting a graph in an application created with QtDesigner, the problem is that, when the grapth is showed, a big "grey edge" appears between the graph space and the mplwidget space. That makes the plot smaller, so how could I delete this big "grey border" that appears when I show my graph in the main Window??
I would like my graph to fill all the available space for the widget.
The short answer is: "Use fig.tight_layout()."
Let me give a bit more explanation about what's going on, though.
You're seeing the interaction between the figure and the axes.
A Figure contains one or more Axes (plots/subplots/etc). Everything is drawn on the figure's Canvas (basically, the backend-specific pixel buffer or vector page).
When you make an axes, it does not fill up all of the figure.
The default for a single axes is for the lower left corner of the axes to be a 12.5% of the width of the figure and 10% of the height and for the axes to take up 90% of the width and height of the figure. (The asymmetry is to leave room for the tick labels on the left.)
The position you set is the extent of the white box in the figure below. It doesn't include the tick labels, title, axes labels, etc (which is why the axes doesn't fill up the entire figure).
The default will look like this:
Side note: To keep the code short, I'm going to use the pyplot interface to automatically generate a Figure, Axes, and Canvas, while you're probably explicitly creating each one to work with your gui framework. The result is the same, though.
The percentage of the figure that each axes instance takes up is set at the time that it's created. You can either explicitly specify it:
fig = plt.figure()
ax = fig.add_axes([left, bottom, width, height])
Or use a grid of subplots, which can be easier to adjust through fig.subplots_adjust:
fig, axes = plt.subplots(nrows=2, ncols=2)
# Expand the grid of subplots out (Notice that
fig.subplots_adjust(left=0.05, bottom=0.05, right=0.98, top=0.98)
What tight_layout does is to calculate the extent of the tick labels, title, axis labels, etc and determine parameters for fig.subplots_adjust such that everything will be just barely inside the figure. (Remember that subplots_adjust and the axes position specification control the extent of the "white box" -- the actual axes itself -- and doesn't include the tick labels, etc.)
So, if we do just what we did before:
fig, ax = plt.subplots()
And then call:
fig.tight_layout()
We'll get something that has less of a "border", as the axes will take up a larger percentage of the figure:
If you want to control the color of the "border" use fig.set_facecolor(color) (or fig.patch.set_facecolor).
#newPyUser, if you have an embedded graph as self.ui.mplwidget do
self.ui.mplwidget.canvas.fig.tight_layout()
assuming your widgets are defined like here:
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MplWidget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.canvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.canvas)
self.setLayout(self.vbl)
With an embedded graph as self.ui.mplwidget, a very simple self.ui.mplwidget.figure.tight_layout() will do the job, without defining a canvas before.
If you have other features on the graph such as axis labels or title, just be sure to put the self.ui.mplwidget.figure.tight_layout() after those.

Redrawing plot on Basemap in Python

I am plotting a scatter plot on a Basemap. However, the data with this scatter plot changes based on user input. I would like to clear the data (only the data -- not the entire basemap figure) and re-plot new scatter points.
This question is similar but was not answered (http://stackoverflow.com/questions/8429693/python-copy-basemap-or-remove-data-from-figure)
Currently I am closing the figure with clf(); however, this requires me to re-draw the entire basemap and scatter plot together. On top of this, I am doing all of the redrawing inside of a wx panel. The basemap redraw takes too long and am hoping that there is an easy way to simply re-plot scatter points only.
#Setting up Map Figure
self.figure = Figure(None,dpi=75)
self.canvas = FigureCanvas(self.PlotPanel, -1, self.figure)
self.axes = self.figure.add_axes([0,0,1,1],frameon=False)
self.SetColor( (255,255,255) )
#Basemap Setup
self.map = Basemap(llcrnrlon=-119, llcrnrlat=22, urcrnrlon=-64,
urcrnrlat=49, projection='lcc', lat_1=33, lat_2=45,
lon_0=-95, resolution='h', area_thresh=10000,ax=self.axes)
self.map.drawcoastlines()
self.map.drawcountries()
self.map.drawstates()
self.figure.canvas.draw()
#Set up Scatter Plot
m = Basemap(llcrnrlon=-119, llcrnrlat=22, urcrnrlon=-64,
urcrnrlat=49, projection='lcc', lat_1=33, lat_2=45,
lon_0=-95, resolution='h', area_thresh=10000,ax=self.axes)
x,y=m(Long,Lat)
#Scatter Plot (they plot the same thing)
self.map.plot(x,y,'ro')
self.map.scatter(x,y,90)
self.figure.canvas.draw()
Then I do an some type of update on my (x,y)...
#Clear the Basemap and scatter plot figures
self.figure.clf()
Then I repeat all of the above code. (I also have to redo my box sizers for my panel -- I did not include these).
Thanks!
The matplotlib.pyplot.plot documentation mentions that the plot() command returns a Line2D artist which has xdata and ydata properties, so you might be able to do the following:
# When plotting initially, save the handle
plot_handle, = self.map.plot(x,y,'ro')
...
# When changing the data, change the xdata and ydata and redraw
plot_handle.set_ydata(new_y)
plot_handle.set_xdata(new_x)
self.figure.canvas.draw()
I haven't managed to get the above to work for collections, or 3d projections, unfortunately.
Most plotting functions return Collections object. If so then you can use remove() method. In your case, I would do the following:
# Use the Basemap method for plotting
points = m.scatter(x,y,marker='o')
some_function_before_remove()
points.remove()

Categories