How to restrict a plot to positive area? [duplicate] - python

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:

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.

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.

Related

Python Tkinter: responsive grid

I have a frame with multiple child elements, that are placed in it using the grid() geometry manager.
How can I modify the code below to make the frame responsive?
content.grid(column=0, row=0, sticky='nwse')
userButt.grid(column=2, row=1, sticky='nwse')
favoButt.grid(column=3, row=1, sticky='nwse')
locaButt.grid(column=4, row=1, sticky='nwse')
histScal.grid(column=5, row=1, sticky='nwse')
As a rule of thumb, whenever you use grid you should always give at least one row and one column a non-zero weight so that tkinter knows where to allocate extra space. A weight of 0 (zero) is assigned by default.
The two most common cases are where you have a "hero" widget (eg: a text widget, canvas widget, etc) that should grow and shrink as necessary, or you want everything to resize equally. For the case where one widget gets all the extra space, give a weight just to the row and column where that widget is placed. If you want everything to resize equally, give each row and each column a weight.
Assuming that the parent of your widgets content, userButt, etc are root, you might do it like this:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
In the above example, all extra space would go to row zero and column 0.
Suppose you have a window that is managed by grid system and you want to make it responsive knowing the total number of rows and column you used. Let's say the total number of rows =6 and the total number of columns =10 making the window responsive under grid management system can be done as follows.
n_rows =6
n_columns =10
for i in range(n_rows):
root.grid_rowconfigure(i, weight =1)
for i in range(n_columns):
root.grid_columnconfigure(i, weight =1)
You need to use grid_rowconfigure(<row number>,weight=<row weight>) and grid_columnconfigure to stretch rows/columns when container is being stretched. Also use grid(sticky=NSEW) attribute to stretch items inside a grid
from tkinter import *
root = Tk()
for i in range(10):
root.grid_rowconfigure(i, weight=1)
for j in range(10):
root.grid_columnconfigure(j, weight=1)
Button(root, text=f'Button {i}-{j}').grid(row=i, column=j, sticky=NSEW)
root.mainloop()

Make tkinter Scale span it's full grid cell

I have a Tkinter ttk.Scale in a GUI. What I want to do is add some labels on the top or bottom (or side if its vertical) that show the values the user is selecting through. I have a minimum working example shown below.
My problem comes from the fact that I cannot figure out how to get the Scale to line up with the labels. It is set to span all the columns of the labels, but the Scale widget comes with a length keyword that automatically forces it to be 100 pixels in size. I can't know, a priori, what to set that length to and I can't figure out how to tell it to fill its grid space.
Is there a way to tell the Scale to choose a length such that it fills its grid space?
import tkinter as tk
from tkinter import ttk
def show_values():
print (w1.get())
def scaleFunc(val):
scaleVal = float(w1.get())
if int(scaleVal) != scaleVal:
w1.set(round(float(val)))
root = tk.Tk()
for i,text in enumerate(['O','B','A','F','G','K','M','L']):
ttk.Label(root, text = text).grid(row=0,column=i,padx=10)
w1 = ttk.Scale(root, to=7, command=scaleFunc, length=None)
w1.grid(row = 1, column = 0, columnspan = 8,ipadx=0)
ttk.Button(root, text='Show', command=show_values).grid(row=2,column=0,columnspan=8)
root.mainloop()
I suppose I should have played around a bit more. It looks like the Scale widget listens to the sticky keyword in the grid manager.
w1 = ttk.Scale(root, to=7, command=scaleFunc, length=None)
w1.grid(row=1,column=0,columnspan=8,padx=(10,7),sticky='NSEW')
You can use either one of these solutions:
Solution 1: w1.grid(row = 1, column = 0, columnspan=8, sticky='ew')
Solution 2: w1 = ttk.Scale(root, to=7, command=scaleFunc, length=250)
The 1st solution is cleaner.

Python / Tkinter grid sticky not filling vertical space

I'm trying to create a grid of buttons. The text within these buttons will change, so I want to buttons to be large - filling the borders of each grid element. The code below is a simplified version which illustrates the issue. The buttons mostly fill the horizontal space, but do not fill the vertical space (unless multiple lines of text are added as a button label).
from Tkinter import *
root = Tk()
buttons = {}
for y in range(3):
root.rowconfigure(y, minsize=60)
for x in range(3):
root.columnconfigure(x, minsize=60)
for y in range(3):
n = 3 * x + y
buttons[n] = Button(root).grid(column=x, row=y, sticky=W + E + N + S)
root.mainloop()
This is what this yields for me:
Buttons on OSX really want to look like OSX buttons, and won't expand the way you want. This is one of the pitfalls of using native widgets.

How to set where extra padding goes in tkinter

I have some Python tkinter code with a label that changes length and spans various columns. As it changes length, the extra padding seems to be shared among the columns it spans, whereas I want it to be added to just one column. As an example in the following simple interface, I'd like all the extra padding to be added to column 2 so that as the spinbox increases, the 'Move me' label is moved to the right, but the 'Keep me still' label stays where it is. Please could someone show me how to make this work? Preferably without classes (yes, I am aware that I need to learn about them soon :P)
from tkinter import *
from tkinter import ttk
gui = Tk()
toplabel = StringVar()
value = StringVar()
def combofunc(*args):
toplabel.set('* ' * 10 * int(value.get()))
ttk.Label(gui, textvariable=toplabel).grid(column=1, columnspan=3, row=1, sticky=W)
Spinbox(gui, from_=1, to=100, textvariable=value, command=combofunc).grid(column=1, row=2, sticky=W)
ttk.Label(gui, text='Keep me still').grid(column=2, row=2, sticky=W)
ttk.Label(gui, text='Move me').grid(column=3, row=2, sticky=W)
for child in gui.winfo_children():
child.grid_configure(padx=5, pady=5)
combofunc()
gui.mainloop()
EDIT: I meant I want the padding to be added either on the right hand side of column 2, or between column 2 and column 3.
You need to configure the columns with expansion weights. Add the following somewhere.
for i, j in ((1,0), (2,1), (3,0)):
gui.columnconfigure(i, weight=j)
I used this tkinter doc

TKinter - widgets not 'sticking' in Frame using grid layout

I am building a fairly complicated GUI in TKinter so naturally have been using the .grid function.
I have grouped some of my widgets into Frames to make them easier to handle, but for some reason when I use .grid with widgets in a frame, the sticky attribute does not seem to work - my widgets don't fill the whole frame.
Example:
f3 = tk.Frame(self, borderwidth=1, relief="ridge")
f3.grid(row=0, column=0, sticky="NS")
tk.Label(f3, text="Site List").grid(in_=f3, row=0, column=0)
self.sitelist = tk.Listbox(f3)
self.sitelist.grid(in_=f3, row=1, column=0, sticky="NS")
In the above code, the frame f3 fills the space in the 0,0 cell of my root widget, but the Listbox does not fill all the space in the Frame, even though I have asked it to be sticky "NS".
If put the Listbox in the root widget at 0,0 then it stretches and fills all the space fine. It just does not behave well if it is in the Frame.
Can anyone explain how to get around this?
I thought that using frames would simplify my layout, but it is not!!!
Cheers.
Chris
You need to give one or more columns and rows "weight". Typically you'll have exactly one row and one column with a weight of 1 (the default is zero). Any row or column with a weight will expand and contract when the containing widget or window is resized.
If more than one row or more than one column has a weight, the weight describes the proportion in which they expand. For example, if one column has a weight of 2 and another a weight of 1, the one with two will expand twice as much as the one with 1.
You can assign weight using the grid_rowconfigure and grid_columnconfigure options to the containing widget:
f3.grid_rowconfigure(0, weight=1)
f3.grid_columnconfigure(0, weight=1)
For a nice brief description of weights, see the section titled Handling Resize on the grid tutorial on the tkdocs.com website.
First of all you shouldn't grid the lable at the same time when you instantiate it. Then, you have to use Grid.rowconfigure to add 'weight' to the row and Grid.columnconfigure to add it to the column. The weight controls expansion and has to be non-zero to allow grid to expand widgets.
If you add the following lines to your code it should work:
Grid.columnconfigure(f3, 0, weight=1)
Grid.rowconfigure(f3, 0, weight=1)
Grid.rowconfigure(f3, 1, weight=1)
Hope it helps

Categories