I'm building a GUI with Tkinter and ttk and using matplotlib in order to creat interactive plots - again, like millions other people do. Even though most problems I encountered so far are well documented, this one seems rare:
When plotting in 3d and adjusting the axis scale with set_lim()
commands afterwards, the plotted line exceeds the coordinate-system which looks not good. Also, I'm not happy with the frame that seems to be a little to small. Here is an example:
# Missmatch.py
"""Graphical User Interface for plotting the results
calculated in the script in Octave"""
# importing libraries
import matplotlib, ttk, threading
matplotlib.use('TkAgg')
import numpy as nm
import scipy as sc
import pylab as pl
import decimal as dc
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
from oct2py import octave as oc
import Tkinter as tki
class CS:
"""CS - Controlset. This part creates the GUI with all important
Elements. Major changes and calculations will be executed
in the Calculation-Class in a seperate thread. This prevents the
GUI from hanging"""
def __init__(self,parent):
"""Building the main GUI"""
self.ThisParent=parent
### Entire Window
# Mainframe that contains everything.
self.main=tki.Frame(parent)
# Pack manager to expand the mainframe as the windowsize changes.
self.main.pack(fill=tki.BOTH, expand=tki.YES)
# Configure the grid of the mainframe so that only the top left
# cell grows if the users expands the window.
self.main.grid_rowconfigure(0, weight=1)
self.main.grid_rowconfigure(1, weight=1)
### Canvas for drawings
# Creating a figure of desired size
self.f = Figure(figsize=(6,6), dpi=100)
# Creating a canvas that lives inside the figure
self.Paper=FigureCanvasTkAgg(self.f, master=self.main)
# Making the canvas's drawings visible (updating)
self.Paper.show()
# positioning the canvas
self.Paper.get_tk_widget().grid(row=0,rowspan=3, column=0, sticky='NSWE')
# creating a toolbarframe for options regarding the plots
self.toolbarframe=tki.Frame(self.main)
self.toolbarframe.grid(row=3, column=0, sticky='NWE')
# Creating a toolbar for saving, zooming etc. (matplotlib standard)
self.toolbar = NavigationToolbar2TkAgg(self.Paper, self.toolbarframe)
self.toolbar.grid(row=0,column=0, sticky='NWE')
# setting the standard option on zoom
self.toolbar.zoom()
### Axis configuration toolbar
# A frame containing the axis config-menu
self.axisscaleframe=tki.Frame(self.main)
self.axisscaleframe.grid(row=5, column=0, sticky='SNEW')
# In that Frame, some Entry-boxes to specify scale
self.xaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.xaxisscalef.insert(0,0)
self.xaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.xaxisscalet.insert(0,15)
self.yaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.yaxisscalef.insert(0,0)
self.yaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.yaxisscalet.insert(0,15)
self.zaxisscalef=ttk.Entry(self.axisscaleframe, width=10)
self.zaxisscalef.insert(0,0)
self.zaxisscalet=ttk.Entry(self.axisscaleframe, width=10)
self.zaxisscalet.insert(0,15)
# And some Labels so we know what the boxes are for
self.xaxlab=ttk.Label(self.axisscaleframe, text='X-Axis', width=10)
self.yaxlab=ttk.Label(self.axisscaleframe, text='Y-Axis', width=10)
self.zaxlab=ttk.Label(self.axisscaleframe, text='Z-Axis', width=10)
self.axinfolab=ttk.Label(self.axisscaleframe, text='Adjust axis scale:')
# And a Button to validate the desired configuration
self.scaleset=ttk.Button(self.axisscaleframe, text='Set', command=self.SetAxis2)
self.scaleset.bind('<Return>', self.SetAxis)
# Let's organize all this in the axisscaleframe-grid
self.axinfolab.grid(row=0, column=0, sticky='W')
self.xaxlab.grid(row=1, column=0, sticky='W')
self.yaxlab.grid(row=2, column=0, sticky='W')
self.zaxlab.grid(row=3, column=0, sticky='W')
self.xaxisscalef.grid(row=1,column=1, sticky='W')
self.yaxisscalef.grid(row=2,column=1, sticky='W')
self.xaxisscalet.grid(row=1,column=2, sticky='W')
self.yaxisscalet.grid(row=2,column=2, sticky='W')
self.zaxisscalef.grid(row=3,column=1,sticky='W')
self.zaxisscalet.grid(row=3,column=2,sticky='W')
self.scaleset.grid(row=3,column=3,sticky='E')
def SetAxis(self,event):
self.SetAxis2()
def SetAxis2(self):
self.x1=float(self.xaxisscalef.get())
self.x2=float(self.xaxisscalet.get())
self.y1=float(self.yaxisscalef.get())
self.y2=float(self.yaxisscalet.get())
self.z1=float(self.zaxisscalef.get())
self.z2=float(self.zaxisscalet.get())
self.a.set_xlim(self.x1, self.x2)
self.a.set_ylim(self.y1, self.y2)
self.a.set_zlim(self.z1, self.z2)
self.Paper.show()
print "Set axis"
class Calculate3D(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.x=range(100)
self.y=range(100)
self.z=range(100)
print 'Done!'
controlset.a = controlset.f.add_subplot(111, projection='3d')
controlset.a.clear()
controlset.a.plot(self.x,self.y,self.z)
controlset.a.mouse_init()
controlset.a.set_xlabel('X')
controlset.a.set_ylabel('Y')
controlset.a.set_zlabel('Z')
controlset.a.set_title('Title')
controlset.Paper.show()
return
mainw=tki.Tk()
mainw.title("Example")
mainw.geometry('+10+10')
controlset=CS(mainw)
#for this example code, we run our Calculate3D class automatically
CL=Calculate3D()
CL.run()
mainw.mainloop()
Just run the code, and hit the "SET" Button. There is my problem.
Edit: Added Screenshot:
How to Set X-Limit (xlim) in Matplotlib. Let's first set the X-limit, using both the PyPlot and Axes instances. Both of these methods accept a tuple - the left and right limits. So, for example, if we wanted to truncate the view to only show the data in the range of 25-50 on the X-axis, we'd use xlim([25, 50]):
xlim, ylim, and zlim set or query values of the axes object XLim, YLim, ZLim, and XLimMode, YLimMode, ZLimMode properties. When the axis limit modes are auto (the default), MATLAB uses limits that span the range of the data being displayed and are round numbers.
matplotlib.axes.Axes.set_ylim () Function: 1 bottom : This parameter is the bottom ylim in data coordinates 2 top : This parameter is the top ylim in data coordinates 3 emit : This parameter is used to notify observers of limit change. 4 auto : This parameter is used to turn on autoscaling of the x-axis. More items...
The Axes.set_ylim () function in axes module of matplotlib library is used to set the y-axis view limits. Syntax: Axes.set_ylim (self, bottom=None, top=None, emit=True, auto=False, *, ymin=None, ymax=None) Parameters: This method accepts the following parameters. bottom : This parameter is the bottom ylim in data coordinates
Following code works even for meshgrid data representation:
@ numpy.vectorize
def clip_z_data(z):
return z if Z_MIN <= z <= Z_MAX else n.nan
z = clip_z_data(z)
Z_MIN and Z_MAX are global, because vectorize can't handle extra attributes.
The Problem here is, that mplot3d has no OpenGL backend. The calculations for displaying the data are thus based on 2d. I found the same issue here and a workaround here. Even though the workaround is not the best in my opinion because it depends on the resolution of your data.
I followed the second link anyway. So, what I'm doing now is copying the array and setting all the values above and under my desired scale to NaN. When plotting those, the lines will be cut off where the datapoints exceed the desired limit.
def SetAxis2(self):
self.dummyx=CL.x*1
self.dummyy=CL.y*1
self.dummyz=CL.z*1
#clipping manually
for i in nm.arange(len(self.dummyx)):
if self.dummyx[i] < self.x1:
self.dummyx[i] = nm.NaN
else:
pass
for i in nm.arange(len(self.dummyy)):
if self.dummyy[i] < self.y1:
self.dummyy[i] = nm.NaN
else:
pass
for i in nm.arange(len(self.dummyz)):
if self.dummyz[i] < self.z1:
self.dummyz[i] = nm.NaN
else:
pass
controlset.a.plot(self.dummyx,\
self.dummyy,\
self.dummyz)
self.a.set_xlim3d(self.x1, self.x2)
self.a.set_ylim3d(self.y1, self.y2)
self.a.set_zlim3d(self.z1, self.z2)
If now your scale is set from 0 to 10 and you have six datapoints: [-1, 3 4 12 5 1]
The line will go from 3 to 4 and 5 to 1 because -1 and 12 will be set to NaN. An improvement regarding that problem would be good.
Mayavi might be better, but I haven't tried this as I wanted to stick with matplotlib.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With