\n
EDIT
\n# (c) MIT License Copyright 2014 Ronald H Longo\n# Please reuse, modify or distribute freely.\n\nfrom collections import OrderedDict\nimport tkinter as tk\nfrom tkinter import Label, ttk\n\n\n# convert rate axis label to something useful and not too large...\ndef ratestr(f):\n if (f >= 1e6):\n return('{:.1f} MHz'.format(f/1000000.0))\n elif (f >= 1e3):\n return('{:.1f} kHz'.format(f/1000.0))\n else:\n return('{:.1f} Hz'.format(f))\n\nclass StripChart( tk.Frame ):\n def __init__( self, parent, scale, historySize, trackColors, *args, **opts ):\n # Initialize\n super().__init__( parent, *args, **opts )\n self._trackHist = OrderedDict() # Map: TrackName -> list of canvas objID \n self._trackColor = trackColors # Map: Track Name -> color\n #Set Scaling\n self._chartHeight = scale + 1\n self._chartLength = historySize * 2 # Stretch for readability\n self.max_x = self._chartLength\n self.max_y = self._chartHeight -1\n\n #Create main canvas\n self._canvas = tk.Canvas( self, height=self._chartHeight + 50,\n width=self._chartLength, background='black' )\n # self._canvas.grid( sticky=tk.N+tk.S+tk.E+tk.W )\n self._canvas.grid(row=1, column=1)\n self._canvas.create_text(6,50,text="Total Counts")\n\n #Labeling\n self.Ch_A = Label(self)\n self.Ch_A.grid(row=0, column=2)\n self.Ch_B = Label(self)\n self.Ch_B.grid(row=0, column=3)\n self.Ch_C = Label(self)\n self.Ch_C.grid(row=0, column=4)\n self.Ch_D = Label(self)\n self.Ch_D.grid(row=0, column=5)\n self.channel_labels = [self.Ch_A, self.Ch_B, self.Ch_C, self.Ch_D]\n # _y_label.grid(row=0, column=0)\n \n # Draw horizontal to divide plot from tick labels\n x, y = 0, self._chartHeight + 2\n x2, y2 = self._chartLength, y\n self._baseLine = self._canvas.create_line( x, y, x2, y2, fill='white' )\n\n # create axis labels\n self.maxy = Label(self, text=ratestr(self.max_y), font=("Arial", 15), width=8)\n self.maxy.grid(column=0,row=0)\n\n self.midy = Label(self, text=ratestr(self.max_y/2), font=("Arial", 15), width=8)\n self.midy.grid(column=0,row=1)\n\n\n self.midx = Label(self, text="Seconds", font=("Arial", 15), width=6)\n self.midx.grid(column=1,row=2)\n\n\n \n # Init track def and histories lists\n self._trackColor.update( { 'tick':'white', 'tickline':'white',\n 'ticklabel':'white' } )\n for trackName in self._trackColor.keys():\n self._trackHist[ trackName ] = [ None for x in range(historySize) ]\n\n #Make stop button\n self.Button1 = ttk.Button(master=self, text= "Quit", command=quit)\n self.Button1.grid(row=1,column=2)\n #Collect Data Button\n self.Button2 = ttk.Button(self, text="Collect Data" )\n self.Button2.grid(row=1, column=3)\n\n def plotValues( self, **vals ):\n row_val = 0\n for trackName, trackHistory in self._trackHist.items():\n\n self._canvas.delete( trackHistory.pop(0) )# Remove left-most canvas objs\n self._canvas.move( trackName, -2, 0 )# Scroll canvas objs 2 pixels left\n # Plot the new values\n \n try:\n val = vals[ trackName ]\n x = self._chartLength\n y = self._chartHeight - val #Makes random value maxy -> y=0\n color = self._trackColor[ trackName ]\n \n objId = self._canvas.create_line( x, y, x+1, y, fill=color,\n width=3, tags=trackName )\n trackHistory.append( objId )\n row_val += 1\n\n except:\n\n trackHistory.append( None )\n\n\n def makeLabels(self, **vals):\n for x, l in zip(vals,self.channel_labels): #change your for loops to this\n l.configure(text = x +": "+ str(vals[x]), fg=self._trackColor[x])\n #l['text'] = x + ": " + str(vals[x])\n # l.after(1,self.makeLabels(**vals))\n\n\n def drawTick( self, text=None, **lineOpts ):\n # draw vertical tick line\n x = self._chartLength\n y = 1\n x2 = x\n y2 = self._chartHeight\n color = self._trackColor[ 'tickline' ]\n \n objId = self._canvas.create_line( x, y, x2, y2, fill=color,\n tags='tick', **lineOpts )\n self._trackHist[ 'tickline' ].append( objId )\n \n # draw tick label\n if text is not None:\n x = self._chartLength\n y = self._chartHeight + 10\n color = self._trackColor[ 'ticklabel' ]\n \n objId = self._canvas.create_text( x, y, text=text,\n fill=color, tags='tick' )\n self._trackHist[ 'ticklabel' ].append( objId )\n\n \n\n def configTrackColors( self, **trackColors ):\n # Change plotted data color\n for trackName, colorName in trackColors.items( ):\n self._canvas.itemconfigure( trackName, fill=colorName )\n \n # Change settings so future data has the new color\n self._trackColor.update( trackColors )\n\n\n##########################\n#\n# MAIN PROGRAM\n#\n##########################\n\nif __name__ == '__main__':\n top = tk.Tk( )\n # style = ttk.Style(top)\n # style.theme_use('classic')\n scale_max = 600\n graph = StripChart( top, scale_max, 400, { 'Ch_A':'blue', 'Ch_B':'green', 'Ch_C':'red','Ch_D':'yellow' } )\n graph.grid( )\n \n val_A = 0\n val_B = 0\n val_C = 0\n val_D = 0\n delta = [ -4, -3, -2, -1, 0, 1, 2, 3, 4 ] # randomly vary the values by one of these\n tickCount = 0\n \n def nextVal( current, lowerBound, upperBound ):\n from random import choice\n \n current += choice( delta )\n if current < lowerBound:\n return lowerBound\n elif current > upperBound:\n return upperBound\n else:\n return current\n \n def plotNextVals( ): #Main function\n global val_A, val_B, val_C, val_D, tickCount\n \n if tickCount % 50 == 0:\n graph.drawTick( text=str(tickCount), dash=(1,4) )\n graph.makeLabels(Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )\n tickCount += 1\n \n val_A = nextVal( val_A, 0, scale_max )\n val_B = nextVal( val_B, 0, scale_max )\n val_C = nextVal( val_C, 0, scale_max )\n val_D = nextVal( val_D, 0, scale_max )\n\n graph.plotValues( Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )\n \n changeColor = { 800: 'black',\n 1200: 'yellow',\n 1600: 'orange',\n 2000: 'white',\n 2400: 'brown',\n 2800: 'blue' }\n if tickCount in changeColor:\n graph.configTrackColors( A=changeColor[tickCount] )\n \n top.after( 250, plotNextVals )#Repeats after 1000 ms\n\n top.after( 100, plotNextVals )\n top.mainloop( )\n\n
\n","author":{"@type":"Person","name":"Mitch Anthony"},"upvoteCount":2,"answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"If you use grid(..., sticky="N")
then it moves element to top border (North
)
#Make stop button\n self.Button1 = ttk.Button(self, text= "Quit", command=quit)\n self.Button1.grid(row=1, column=2, sticky='N')\n \n #Collect Data Button\n self.Button2 = ttk.Button(self, text="Collect Data" )\n self.Button2.grid(row=1, column=3, sticky='N')\n
\n\nBut buttons are in the same column as Ch_A
, Ch_B
so both elements in columns have the same width. You could put buttons or labels in Frame
and put this frame in grid using also columnspan=4
and this frame (and buttons will not change width of labels
self.frame_buttons = tk.Frame(self)\n self.frame_buttons.grid(row=1, column=2, sticky='N', columnspan=4)\n \n #Make stop button\n self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)\n self.Button1.grid(row=1, column=1)\n \n #Collect Data Button\n self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )\n self.Button2.grid(row=1, column=2)\n
\n\nAt this moment Ch_A
, Ch_B
don't depent on buttons. Because frame uses columnspan=4
so it is uses width of 4 columns and it is centered in this width. Frame doesn't use full width - there are free spaces on left and right.
If I use sticky='NEW'
then frame will use full width but buttons (or rather columns) will not use this space. I added red
background in frame to show it.
self.frame_buttons = tk.Frame(self, bg='red')\nself.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)\n
\n\nIf I use .columnconfigure(..., weight=1)
then I can reuse free space in existing column(s).
self.frame_buttons = tk.Frame(self, bg='red')\n self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)\n self.frame_buttons.columnconfigure(1, weight=1)\n self.frame_buttons.columnconfigure(2, weight=1)\n
\n\nSum of weight
is 2
and first column has weight=1 so it uses 1/2
of free space and the same for second row. But I can use bigger value in weight
to split free space in different way.
If I use sticky='WE'
for buttons then buttons will use full size in columns. And this way button will have the same width
self.frame_buttons = tk.Frame(self, bg='red')\n self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)\n self.frame_buttons.columnconfigure(1, weight=1)\n self.frame_buttons.columnconfigure(2, weight=1)\n \n #Make stop button\n self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)\n self.Button1.grid(row=1, column=1, sticky='WE')\n \n #Collect Data Button\n self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )\n self.Button2.grid(row=1, column=2, sticky='WE')\n
\n\nReputation: 23
I'm pasting the all the code I have been working on for a "strip Chart". I can provide the original code I've taken it from if there is interest. The code works well and generates a chart as expected, but the grid layout is strange. The buttons and label updates are all in awkwardly shaped "spaces". My assumption is the main canvas area takes up a large portion of the frame (window?), so when other widgets are added to the grid they are placed in accordance with the larger canvas window.
Is there a way to either subset the canvas frame/window that is doing the plotting so it doesn't affect the sizes of the other widgets? Or is there a generally better way to place everything together?
Let me know if I need to clarify anything.
Thanks!
EDIT
Here is an image of the GUI. First, the main plotting canvas is in grid(row=1,col=1). When I put the buttons to the right of the figure they are in the same row=1, but now in columns 2 and 3, respectively. Since the row height is so large, the buttons are placed in the center of that row. A similar thing happens with the column width of the buttons when looking at "Ch_A" and "Ch_B" labels.
Ideally, I would like the buttons and the associated "Channel Labels" to be neatly organized around each other. Is there a best practice way to go about this?
EDIT
# (c) MIT License Copyright 2014 Ronald H Longo
# Please reuse, modify or distribute freely.
from collections import OrderedDict
import tkinter as tk
from tkinter import Label, ttk
# convert rate axis label to something useful and not too large...
def ratestr(f):
if (f >= 1e6):
return('{:.1f} MHz'.format(f/1000000.0))
elif (f >= 1e3):
return('{:.1f} kHz'.format(f/1000.0))
else:
return('{:.1f} Hz'.format(f))
class StripChart( tk.Frame ):
def __init__( self, parent, scale, historySize, trackColors, *args, **opts ):
# Initialize
super().__init__( parent, *args, **opts )
self._trackHist = OrderedDict() # Map: TrackName -> list of canvas objID
self._trackColor = trackColors # Map: Track Name -> color
#Set Scaling
self._chartHeight = scale + 1
self._chartLength = historySize * 2 # Stretch for readability
self.max_x = self._chartLength
self.max_y = self._chartHeight -1
#Create main canvas
self._canvas = tk.Canvas( self, height=self._chartHeight + 50,
width=self._chartLength, background='black' )
# self._canvas.grid( sticky=tk.N+tk.S+tk.E+tk.W )
self._canvas.grid(row=1, column=1)
self._canvas.create_text(6,50,text="Total Counts")
#Labeling
self.Ch_A = Label(self)
self.Ch_A.grid(row=0, column=2)
self.Ch_B = Label(self)
self.Ch_B.grid(row=0, column=3)
self.Ch_C = Label(self)
self.Ch_C.grid(row=0, column=4)
self.Ch_D = Label(self)
self.Ch_D.grid(row=0, column=5)
self.channel_labels = [self.Ch_A, self.Ch_B, self.Ch_C, self.Ch_D]
# _y_label.grid(row=0, column=0)
# Draw horizontal to divide plot from tick labels
x, y = 0, self._chartHeight + 2
x2, y2 = self._chartLength, y
self._baseLine = self._canvas.create_line( x, y, x2, y2, fill='white' )
# create axis labels
self.maxy = Label(self, text=ratestr(self.max_y), font=("Arial", 15), width=8)
self.maxy.grid(column=0,row=0)
self.midy = Label(self, text=ratestr(self.max_y/2), font=("Arial", 15), width=8)
self.midy.grid(column=0,row=1)
self.midx = Label(self, text="Seconds", font=("Arial", 15), width=6)
self.midx.grid(column=1,row=2)
# Init track def and histories lists
self._trackColor.update( { 'tick':'white', 'tickline':'white',
'ticklabel':'white' } )
for trackName in self._trackColor.keys():
self._trackHist[ trackName ] = [ None for x in range(historySize) ]
#Make stop button
self.Button1 = ttk.Button(master=self, text= "Quit", command=quit)
self.Button1.grid(row=1,column=2)
#Collect Data Button
self.Button2 = ttk.Button(self, text="Collect Data" )
self.Button2.grid(row=1, column=3)
def plotValues( self, **vals ):
row_val = 0
for trackName, trackHistory in self._trackHist.items():
self._canvas.delete( trackHistory.pop(0) )# Remove left-most canvas objs
self._canvas.move( trackName, -2, 0 )# Scroll canvas objs 2 pixels left
# Plot the new values
try:
val = vals[ trackName ]
x = self._chartLength
y = self._chartHeight - val #Makes random value maxy -> y=0
color = self._trackColor[ trackName ]
objId = self._canvas.create_line( x, y, x+1, y, fill=color,
width=3, tags=trackName )
trackHistory.append( objId )
row_val += 1
except:
trackHistory.append( None )
def makeLabels(self, **vals):
for x, l in zip(vals,self.channel_labels): #change your for loops to this
l.configure(text = x +": "+ str(vals[x]), fg=self._trackColor[x])
#l['text'] = x + ": " + str(vals[x])
# l.after(1,self.makeLabels(**vals))
def drawTick( self, text=None, **lineOpts ):
# draw vertical tick line
x = self._chartLength
y = 1
x2 = x
y2 = self._chartHeight
color = self._trackColor[ 'tickline' ]
objId = self._canvas.create_line( x, y, x2, y2, fill=color,
tags='tick', **lineOpts )
self._trackHist[ 'tickline' ].append( objId )
# draw tick label
if text is not None:
x = self._chartLength
y = self._chartHeight + 10
color = self._trackColor[ 'ticklabel' ]
objId = self._canvas.create_text( x, y, text=text,
fill=color, tags='tick' )
self._trackHist[ 'ticklabel' ].append( objId )
def configTrackColors( self, **trackColors ):
# Change plotted data color
for trackName, colorName in trackColors.items( ):
self._canvas.itemconfigure( trackName, fill=colorName )
# Change settings so future data has the new color
self._trackColor.update( trackColors )
##########################
#
# MAIN PROGRAM
#
##########################
if __name__ == '__main__':
top = tk.Tk( )
# style = ttk.Style(top)
# style.theme_use('classic')
scale_max = 600
graph = StripChart( top, scale_max, 400, { 'Ch_A':'blue', 'Ch_B':'green', 'Ch_C':'red','Ch_D':'yellow' } )
graph.grid( )
val_A = 0
val_B = 0
val_C = 0
val_D = 0
delta = [ -4, -3, -2, -1, 0, 1, 2, 3, 4 ] # randomly vary the values by one of these
tickCount = 0
def nextVal( current, lowerBound, upperBound ):
from random import choice
current += choice( delta )
if current < lowerBound:
return lowerBound
elif current > upperBound:
return upperBound
else:
return current
def plotNextVals( ): #Main function
global val_A, val_B, val_C, val_D, tickCount
if tickCount % 50 == 0:
graph.drawTick( text=str(tickCount), dash=(1,4) )
graph.makeLabels(Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )
tickCount += 1
val_A = nextVal( val_A, 0, scale_max )
val_B = nextVal( val_B, 0, scale_max )
val_C = nextVal( val_C, 0, scale_max )
val_D = nextVal( val_D, 0, scale_max )
graph.plotValues( Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )
changeColor = { 800: 'black',
1200: 'yellow',
1600: 'orange',
2000: 'white',
2400: 'brown',
2800: 'blue' }
if tickCount in changeColor:
graph.configTrackColors( A=changeColor[tickCount] )
top.after( 250, plotNextVals )#Repeats after 1000 ms
top.after( 100, plotNextVals )
top.mainloop( )
Upvotes: 2
Views: 4237
Reputation: 143097
If you use grid(..., sticky="N")
then it moves element to top border (North
)
#Make stop button
self.Button1 = ttk.Button(self, text= "Quit", command=quit)
self.Button1.grid(row=1, column=2, sticky='N')
#Collect Data Button
self.Button2 = ttk.Button(self, text="Collect Data" )
self.Button2.grid(row=1, column=3, sticky='N')
But buttons are in the same column as Ch_A
, Ch_B
so both elements in columns have the same width. You could put buttons or labels in Frame
and put this frame in grid using also columnspan=4
and this frame (and buttons will not change width of labels
self.frame_buttons = tk.Frame(self)
self.frame_buttons.grid(row=1, column=2, sticky='N', columnspan=4)
#Make stop button
self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)
self.Button1.grid(row=1, column=1)
#Collect Data Button
self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )
self.Button2.grid(row=1, column=2)
At this moment Ch_A
, Ch_B
don't depent on buttons. Because frame uses columnspan=4
so it is uses width of 4 columns and it is centered in this width. Frame doesn't use full width - there are free spaces on left and right.
If I use sticky='NEW'
then frame will use full width but buttons (or rather columns) will not use this space. I added red
background in frame to show it.
self.frame_buttons = tk.Frame(self, bg='red')
self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)
If I use .columnconfigure(..., weight=1)
then I can reuse free space in existing column(s).
self.frame_buttons = tk.Frame(self, bg='red')
self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)
self.frame_buttons.columnconfigure(1, weight=1)
self.frame_buttons.columnconfigure(2, weight=1)
Sum of weight
is 2
and first column has weight=1 so it uses 1/2
of free space and the same for second row. But I can use bigger value in weight
to split free space in different way.
If I use sticky='WE'
for buttons then buttons will use full size in columns. And this way button will have the same width
self.frame_buttons = tk.Frame(self, bg='red')
self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)
self.frame_buttons.columnconfigure(1, weight=1)
self.frame_buttons.columnconfigure(2, weight=1)
#Make stop button
self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)
self.Button1.grid(row=1, column=1, sticky='WE')
#Collect Data Button
self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )
self.Button2.grid(row=1, column=2, sticky='WE')
Upvotes: 0