I have a matplotlib figure embedded in a wxpython frame with a few sizers. Everything works fine until I include a legend but then the sizers don't seem to be working with the legend.
Even when I resize the window by dragging at the corner, the main figure changes size, but only the edge of the legend is ever shown.
That is, note that the legend is not visible in the wxFrame.
import wx
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from random import shuffle
class PlotFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, title="Plot", size=(-1, -1))
self.main_panel = wx.Panel(self, -1)
self.plot_panel = PlotPanel(self.main_panel)
s0 = wx.BoxSizer(wx.VERTICAL)
s0.Add(self.main_panel, 1, wx.EXPAND)
self.SetSizer(s0)
self.s0 = s0
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_sizer.Add(self.plot_panel, 1, wx.EXPAND)
self.main_panel.SetSizer(self.main_sizer)
class PlotPanel(wx.Panel):
def __init__(self, parent, id = -1, dpi = None, **kwargs):
wx.Panel.__init__(self, parent, id=id, **kwargs)
self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2,2))
self.canvas = Canvas(self, -1, self.figure)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas,1,wx.EXPAND)
self.SetSizer(sizer)
sizer.SetMinSize((600, 500))
self.sizer = sizer
def test(plot_panel):
axes = plot_panel.figure.gca()
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
return legend
if __name__=="__main__":
app = wx.PySimpleApp()
frame = PlotFrame()
legend = test(frame.plot_panel)
frame.Fit()
print "legend frame pre show: ", legend.get_frame()
frame.Show(True)
print "legend frame post show:", legend.get_frame()
frame.Fit()
app.MainLoop()
Edit:
For a solution to be useful to me, I would like it to look good when the figure is automatically drawn by the program, so adjustment parameters can be hard coded in the program, or, for example, on a window resize event, but not adjusted by hand for each plot. The main things that I expect to change here are: 1) the lengths of the labels (from, say, 1 to 25 characters), 2) the windows size (usually by the user dragging around the corner, and 3) the number of points and lines. (Also, if it matters, eventually, I'll want to have dates on the bottom axis.)
I've put the legend outside of the axes so that it won't cover any data points, and I'd prefer that it stay to the right of the axes.
I'm using Python 2.6.6, wxPython 2.8.12.1, and matplotlib 1.1.0 and am stuck with these for now.
It is re-sizing correctly, you just didn't tell it to do what you want it to do.
The problem is this line:
axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
Pretty sure the bbox_to_anchor kwarg is over-ridding the loc kwarg and you are pegging the bottom left of the legend to (1.05, 0.5) in axes units. If the axes expands to fill your window, the left edge of the legend will always be 5% of the width axes to the right of the right edge of you axes, hence always out of view.
You either need to put your legend someplace else or shrink your axes (in figure fraction).
option 1 move the legend:
axes.legend(bbox_to_anchor=(0.5, 0.5)) #find a better place this is in the center
option 2 move the axes + resize the figure:
axes.set_position([.1, .1, .5, .8]) # units are in figure fraction
set_position
fig = figure()
axes = fig.add_subplot(111)
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
legend = axes.legend(loc='center left', bbox_to_anchor=(1.05, 0.5))
# adjust the figure size (in inches)
fig.set_size_inches(fig.get_size_inches() * np.array([1.5, 1]), forward=True)
# and the axes size (in figure fraction)
# to (more-or-less) preserve the aspect ratio of the original axes
# and show the legend
pos = np.array(axes.get_position().bounds)
pos[2] = .66
axes.set_position(pos)
option 3: automate option 2
fig = figure() # use plt to set this up for demo purposes
axes = fig.add_subplot(111) # add a subplot
# control paramters
left_pad = .05
right_pad = .05
# plot data
for c in ['r', 'b', 'k']:
vals = [20, 30, 40, 50, 80, 20, 50, 60, 70, 70, 80]
shuffle(vals)
axes.plot(range(len(vals)), vals, "-o", color=c, label=c*10)
# set axes labels
axes.set_xlabel('test x')
axes.set_ylabel('test y')
# make the legend
legend = axes.legend(loc='center left', bbox_to_anchor=(1 + left_pad, 0.5))
# function to 'squeeze' the legend into place
def squeeze_legend(event):
fig.tight_layout()
right_frac = 1 - legend.get_window_extent().width / fig.get_window_extent().width - left_pad - right_pad
fig.subplots_adjust(right=right_frac)
fig.canvas.draw()
# call it so the first draw is right
squeeze_legend()
# use the resize event call-back to make sure it works even if the window is re-sized
fig.canvas.mpl_connect('resize_event', squeeze_legend)
Related
I am trying to make a slider that can adjust the x and y coordinates of the legend anchor, but this does not seem to be updating on the plot. I keep getting the message in console "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument", each time the slider value is updated.
Here is the code, taken from this example in the matplotlib docs
from cProfile import label
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
# The parametrized function to be plotted
def f(t, amplitude, frequency):
return amplitude * np.sin(2 * np.pi * frequency * t)
t = np.linspace(0, 1, 1000)
# Define initial parameters
init_amplitude = 5
init_frequency = 3
# Create the figure and the line that we will manipulate
fig, ax = plt.subplots()
line, = ax.plot(t, f(t, init_amplitude, init_frequency), lw=2, label = "wave")
ax.set_xlabel('Time [s]')
# adjust the main plot to make room for the sliders
fig.subplots_adjust(left=0.25, bottom=0.25)
initx = 0.4
inity = 0.2
def l(x,y):
return (x,y)
legend = fig.legend(title = 'title', prop={'size': 8}, bbox_to_anchor = l(initx,inity))
legend.remove( )
# Make a horizontal slider to control the frequency.
axfreq = fig.add_axes([0.25, 0.1, 0.3, 0.3])
freq_slider = Slider(
ax=axfreq,
label='Frequency [Hz]',
valmin=0.1,
valmax=30,
valinit=init_frequency,
)
# Make a vertically oriented slider to control the amplitude
axamp = fig.add_axes([0.1, 0.25, 0.0225, 0.63])
amp_slider = Slider(
ax=axamp,
label="Amplitude",
valmin=0,
valmax=10,
valinit=init_amplitude,
orientation="vertical"
)
# The function to be called anytime a slider's value changes
def update(val):
legend = plt.legend(title = '$J_{xx}$', prop={'size': 8}, bbox_to_anchor= l(amp_slider.val, freq_slider.val))
legend.remove()
#line.set_ydata(f(t, amp_slider.val, freq_slider.val))
fig.canvas.draw_idle()
# register the update function with each slider
freq_slider.on_changed(update)
amp_slider.on_changed(update)
# Create a `matplotlib.widgets.Button` to reset the sliders to initial values.
resetax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', hovercolor='0.975')
def reset(event):
freq_slider.reset()
amp_slider.reset()
button.on_clicked(reset)
plt.show()
Is it even possible to update other matplotlib plot parameters like xticks/yticks or xlim/ylim with a slider, rather than the actual plotted data? I am asking so that I can speed up the graphing process, as I tend to lose a lot of time just getting the right plot parameters whilst making plots presentable, and would like to automate this in some way.
I have a group bar chart that I would like to scale.
I am running matplotlib in a Jupyter Notebook and the bar chart is very squashed. I would like to make the axis bigger but can't get it to work in a group bar chart. If I could make it wider it would be much more readable. But if I just increase "width" then the bars start to overlap each other.
The second problem is what to do about the labels. How can the labels be printed to three decimal places?
Note: I recognise that the the values plotted are orders of magnitude different so you cannot really read the small values. Ordinarily you would not combine these onto a single chart - but this is a class exercise to demonstrating why you would not do it so I expect that.
Here is the self-contained code to demonstrate the problem:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
labels = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79', '80-89', '90-99']
t3=[1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
tt4= [116.33333333333333, 106.0, 106.93103448275862, 109.47540983606558, 98.734375, 99.84210526315789, 96.72839506172839, 99.40963855421687, 104.94117647058823, 203.0]
tsh= [1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
hypo_count= [2, 15, 55, 58, 59, 69, 72, 74, 33, 1]
x = np.arange(len(labels)) # the label locations
width = 0.2 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(x, t3, width, label='T3 avg')
rects2 = ax.bar(x+(width), tt4, width, label='TT4 avg')
rects3 = ax.bar(x+(width*2), tsh, width, label='TSH avg')
rects4 = ax.bar(x+(width*3), hypo_count, width, label='# Hypothyroid +ve')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_title('Age Bracket')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
# Print the value on top of each bar
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.bar_label(rects3, padding=3)
ax.bar_label(rects4, padding=3)
fig.tight_layout()
plt.show()
I'm brand new to matplotlib. I have found many examples of embedding bar graphs and plots into a wxPython panel. When I try to replace a graph or plot with a pie chart, the pie chart displays and only when it closes does the panel displays. It doesn't embed. I've been searching for 2 days.
I would like to embed this or this pie chart into this example.
I can't take the code for the pie chart and replace the code for the graph in the example. (Which means I'm missing something pretty big)
This is what I have tried:
import wx
import matplotlib
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.pyplot as plt
class MainFrame(wx.Frame):
"""docstring for MainFrame"""
def __init__(self, parent):
wx.Frame.__init__(self, parent, title=">>>> by www.UmarYusuf.com", size=(800, 580))
# Add SplitterWindow panels
self.split_win = wx.SplitterWindow(self)
self.top_split = MatplotPanel(self.split_win)
self.bottom_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER)
self.split_win.SplitHorizontally(self.top_split, self.bottom_split, 480)
# Add some contrls/widgets (StaticText and Buttons)
# Add Text control to the bottom_split window
self.text1 = wx.StaticText(self.bottom_split, -1, u"You can also plot from file", size=(250, 30), pos=(510, 10), style=wx.ALIGN_CENTER)
self.text1.SetBackgroundColour('Gray')
font = wx.Font(15, wx.SWISS, wx.NORMAL, wx.NORMAL)
self.text1.SetFont(font)
# Add Buttons to the bottom_split window and bind them to plot functions
self.Button1 = wx.Button(self.bottom_split, -1, "Plot1", size=(80, 40), pos=(10, 10))
self.Button1.Bind(wx.EVT_BUTTON, self.plot1)
self.Button2 = wx.Button(self.bottom_split, -1, "Plot2", size=(80, 40), pos=(110, 10))
self.Button2.Bind(wx.EVT_BUTTON, self.plot2)
self.Button3 = wx.Button(self.bottom_split, -1, "Plot3", size=(80, 40), pos=(210, 10))
self.Button3.Bind(wx.EVT_BUTTON, self.plot3)
def plot1(self, event):
pass
def plot2(self, event):
pass
def plot3(self, event):
pass
def plot4(self, event):
pass
def plot5(self, event):
pass
class MatplotPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent,-1,size=(50,50))
self.figure = Figure()
# Pie chart, where the slices will be ordered and plotted counter-clockwise:
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs')
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%',
shadow=True, startangle=90)
ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
plt.show()
self.canvas = FigureCanvas(self, -1, self.figure)
app = wx.App()
frame = MainFrame(None).Show()
app.MainLoop()
Thank you.
There is a question about embedding matplotlib into wxpython, where the answer is showing a minimal example. Several examples are also available on the matplotlib examples page.
The main problem you are facing here is that you are creating two different figures. The first one, self.figure = Figure(), is the one you connect to the canvas, but the second one, fig1 is the one you plot your graph to. The second one is the one shown in a new window when plt.show() gets called.
However, calling plt.show() does not make any sense when embedding into GUIs since the GUI should take care of showing the figure.
Therefore the MatplotPanel class from the question should look like this:
class MatplotPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent,-1,size=(50,50))
self.figure = Figure()
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
sizes = [15, 30, 45, 10]
ax1 = self.figure.add_subplot(111)
ax1.pie(sizes, labels=labels, autopct='%1.1f%%')
ax1.axis('equal')
self.canvas = FigureCanvas(self, -1, self.figure)
When I run this code in Jupyter notebook the labels overlap and are unreadable.
y = [72, 21, 114, 52, 114, 12, 101, 16, 68, 118]
x = np.arange(len(y))
columns = ['MAHC_A', 'MAHC_B', 'MAHC_C', 'MAHC_D', 'MAHC_E', 'MAHC_F','MAHC_G', 'MAHC_H', 'MAHC_I', 'MAHC_J']
fig, ax = plt.subplots()
ax.bar(x, y, width=bar_width)
ax.set_xticks(x)
ax.set_xticklabels(xlabels)
plt.show()
Is there a way to space these out?
There are several options:
Make the figure larger in the horizontal direction.
fig, ax = plt.subplots(figsize=(10,4))
Make the fontsize smaller
ax.set_xticklabels(columns, fontsize=8)
Rotate the labels, such that they won't overlap anymore.
ax.set_xticklabels(columns, rotation=45)
Or, of course any combination of those.
I've been googling a lot, but I haven't found a solution.
The problem is the same as here: Moving matplotlib legend outside of the axis makes it cutoff by the figure box
But I don't want to save the figure, I just want to have the legend inside the whole figure. I've also tried tight_layout but that didn't work. I'm totally new to matplotlib and can't figure it out. Here is my source code (I'm embedding a matplot into PyQt4):
class GraphACanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=100):
self.fig = Figure(figsize=(width, height), facecolor=backColorHexName, dpi=dpi)
self.axes = self.fig.add_subplot(1, 1, 1, autoscale_on=False)
self.axes.set_xticks(arange(-0.1, 1.4, 0.1))
self.axes.set_yticks(arange(-0.1, 1.4, 0.1))
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, Gui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def computeFigure(self):
col = []
for i in xrange(0, 100):
x = i/100
y = y/100
line, = self.axes.plot(x, y, 'o', color=blue, label='label')
col.append(line.get_c())
self.axes.set_xlabel('x', labelpad=10, rotation=0)
self.axes.set_ylabel('y', labelpad=10, rotation=0)
leg = self.axes.legend(loc=2, bbox_to_anchor=(1.0, 0.5), borderaxespad=0., fontsize='x-small', framealpha=0.25, markerscale=0.5, ncol=1, numpoints=1)
for color,text in zip(col,leg.get_texts()):
text.set_color(color)
test = GraphACanvas()
test.computeFigure()
I've put in here just dummy values. But in the application, a user can select nodes, and depending on how many nodes he select, the bigger/smaller is the legend. I would like to shrink the x-axis side to have more place for the legend.
--> The subplot should not fill out 100% (width/height) of the figure, rather 100% height and 80% width, so that my legend has enough space.
This line:
self.axes = self.fig.add_subplot(1, 1, 1, autoscale_on=False)
Will create an axes that fills out the whole figure (more or less) as you describe.
Try:
self.axes = self.fig.add_axes([0.1, 0.1, 0.7, 0.8])
The elements for that list are:
[left_edge, bottom_edge, width, height] where all values are fractions of the total figure dimensions.