Colleagues,
I am designing a GUI with two buttons and one is to display a graph, hourly temperature.
The issue that I am facing is that I can not make a function(update_graph) that updates the value with self.after.
This part creates page 1 and i working fine, until I call update_graph
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page One!!!", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.pack()
button2 = tk.Button(self, text="Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack()
canvas = Canvas(self, width=400, height=400, bg = 'white')
canvas.pack()
# create x and y axes
canvas.create_line(100,250,400,250, width=2)
canvas.create_line(100,250,100,50, width=2)
# creates divisions for each axle
for i in range(11):
x = 100 + (i * 30)
canvas.create_line(x,250,x,245, width=2)
canvas.create_text(x,254, text='%d'% (10*i), anchor=N)
for i in range(6):
y = 250 - (i * 40)
canvas.create_line(100,y,105,y, width=2)
canvas.create_text(96,y, text='%5.1f'% (50.*i), anchor=E)
self.update_graph()
def update_graph(self):
# here is canvas create line that causes a trouble
canvas.create_line(100,250,140,200, width=2)
self.after(100,self.update_graph)
Whith this code I get an error "canvas is not defined".
If I add self to canvas in update_graph, I get
self.canvas.create_line(100,250,140,200, width=2)
AttributeError: 'PageOne' object has no attribute 'canvas'
What am I missing here?
canvas is only defined in the scope of the constructor (__init__) method. If you want to be able to access it elsewhere in the class, you need to make it an instance variable. Instead of,
canvas = Canvas(self, width=400, height=400, bg = 'white')
make it,
self.canvas = Canvas(self, width=400, height=400, bg = 'white')
now, everywhere else in the code where you reference canvas, change it to self.canvas. This should fix the problem.
On an unrelated note, a problem that I'm seeing in update_graph is that it calls itself recursively, or over and over. Perhaps you could change it to something like this:
def update_graph(self):
# This line is quite long. Maybe you could shorten it?
self.after(100, lambda: canvas.create_line(100,250,
140,200, width=2))
Hope this helps!
EDIT: My redefinition of update_graph only makes sense if you want one fixed line drawn. If you intend to add other functionality, such as periodic updates, the original code is correct, as Bryan pointed out.
Related
I want to place a widget in my canvas.
I have written the following function to create a button and display it.
in the center of the x coordinate of my canvas.
def Button_Placer():
label1=Label(canvas, width =20, height = 2, font = font2, text="")
label1.pack()
def Show_Name():
name=NameGenerator()
label1.config(text=name)
button1=Button(canvas, text="Generate Name", width=20, height=4, font=font1, command=Show_Name)
button1=canvas.create_window(250, 300)
I have also created a canvas at the top:
canvas = Canvas(root, width=500, height = 500, bg="red")
canvas.pack()
Unfortunately the canvas.create_window does not work. .pack() works however is not what i need. I have looked at other examples however they are all off OOP and therefore do not find it relevant for my understanding.
As #CommonSense writes, try canvas.create_window(200, 200, window=button1).
Also; you create label1 inside a function and when the functioin exits the name label1 will be garbage collected.
When you create widgets on canvas the reference is an integer; the index of that widget on the canvas. If you use the widget name as reference for the canvas widget you lose the reference to the actual widget.
Try the example below:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=500, height=500, bg="red")
canvas.pack()
def Show_Name():
name = 'A name' # No function NameGenerator()
label1.config(text=name)
button1 = Button(canvas, text="Generate Name", width=20, height=4,
command=Show_Name)
canvas_button1 = canvas.create_window(250, 300, window=button1)
label1 = Label(canvas, width=20, height=2, text="")
canvas_label1 = canvas.create_window(250, 200, window=label1)
I am using the following code to draw a line graph on a custom NSView
for var index = 0; index < (dataPointsArray.count - 1); index++ {
NSBezierPath().lineWidth = 20.0
NSBezierPath.strokeLineFromPoint(dataPointsArray[index], toPoint: dataPointsArray[index + 1])
}
This snippet is contained within a function that is called by drawRect() within the Custom View.
The line draws correctly within the coordinate system of the view. However, the line is drawn at the same width (one pixel width) regardless of the .lineWidth setting (e.g., 5.0, 10.0, 20.0 etc) which seems to have no impact on the line that is actually drawn).
Is anyone able to advise what might be creating this issue for me. I haven't been able to find a previous question that raises this issue.
NSBezierPath().lineWidth = 20.0
The () means you are initializing a new instance of the class and set its lineWidth to 20.0. You should create a variable and use it to draw your path:
var bezierPath = NSBezierPath()
bezierPath.lineWidth = 20.0
bezierPath.moveToPoint(dataPointsArray[index])
bezierPath.lineToPoint(dataPointsArray[index + 1])
Is there a way to get a fade-in/-out animation on a canvas rectangle background in kivy? I tried it by using the Clock.schedule_interval() function. But several problems occured concerning concurrency and data raise.
One of my tries looks as follows:
def calendarClicked(self, *args):
self.alpha = 1.0
self.alphaDelta = 0.01
self.root.canvas.add(Color(1,0,0,self.alpha))
self.root.canvas.add(Rectangle(size=self.root.size))
def fadeIn(self, dt):
self.alpha -= self.alphaDelta
self.root.canvas.add(Color(1,0,0,self.alpha))
self.root.canvas.add(Rectangle(size=self.root.size))
return self.alpha >= 0.5
Clock.schedule_interval(partial(fadeIn, self), 0.1)
Another idea was to use the kivy.animation. But I coudn't find a way to edit the color instead of the position of an object/widget.
Thanks in advance!
You can use an Animation on canvas instructions just like you would a widget. The problem is that you're not modifying the canvas instructions at all, you just keep adding more and more canvas instructions. You need to add the instructions one time only, then update the values via Animation.
def calendarClicked(self, *args):
if not hasattr(self, 'color'):
with self.root.canvas:
self.color = Color(1, 0, 0, 0)
self.rect = Rectangle(size=self.root.size)
self.color.a = 0
anim = Animation(a=1.0)
anim.start(self.color)
I am using tkinter when and trying to set up a window with a background image. In some of the processes I have a frame that fills up with checkboxes so I created a scrollbar so the user can see all the options. The problem is the scroll bar also moves the background image of the canvas. Is there a way I can fix the image to not move or somehow move the frame by itself.
code is
def canvasScroll():
canvas = gui.createCanvas()
fFrame = gui.createNewFrame()
scrollbar = Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand = scrollbar.set)
scrollbar.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand= True)
canvas.create_window((150,50),window = fFrame, anchor='nw', tags = "frame")
gOb.change_canvas(canvas)
fFrame.bind("<Configure>", gui.scroll)
gOb.change_scrollbar(scrollbar)
gOb.change_frame(fFrame)
def createCanvas():
canvas = Canvas(root,height = _h, width = _w,highlightthickness = 0)
canvas.pack(side='top',fill='both',expand='yes')
canvas.create_image(-200,-200,image=bground,anchor='nw')
return canvas
def createNewFrame():
frame = Frame(root,height = _h, width = _w,background='white')
frame.pack()
return frame
Just to clear things up, these guys are all part of a class name gui and gOb is an object that hold several gui objects.
Here's one idea - it's kind of kludgy, but it would work. Every time the scrollbar scrolls, shift the background image's position so it appears to stay in the same place:
Tag your image so you can access it later:
canvas.create_image(-200,-200,image=bground,anchor='nw',tags="background")
# ^^^^^^^^^^^^^^^^^
Make your scrollbar call a function that you define:
scrollbar = Scrollbar(root, orient="vertical", command=scrollCallback)
Define your scroll callback:
def scrollCallback(*args):
canvas.yview(*args)
# Arrange for your background image to move so it appears to stay in the same position as the canvas scrolls.
I have a 3D array, of which the first two dimensions are spatial, so say (x,y). The third dimension contains point-specific information.
print H.shape # --> (200, 480, 640) spatial extents (200,480)
Now, by selecting a certain plane in the third dimension, I can display an image with
imdat = H[:,:,100] # shape (200, 480)
img = ax.imshow(imdat, cmap='jet',vmin=imdat.min(),vmax=imdat.max(), animated=True, aspect='equal')
I want to now rotate the cube, so that I switch from (x,y) to (y,x).
H = np.rot90(H) # could also use H.swapaxes(0,1) or H.transpose((1,0,2))
print H.shape # --> (480, 200, 640)
Now, when I call:
imdat = H[:,:,100] # shape (480,200)
img.set_data(imdat)
ax.relim()
ax.autoscale_view(tight=True)
I get weird behavior. The image along the rows displays the data till 200th row, and then it is black until the end of the y-axis (480). The x-axis extends from 0 to 200 and shows the rotated data. Now on, another rotation by 90-degrees, the image displays correctly (just rotated 180 degrees of course)
It seems to me like after rotating the data, the axis limits, (or image extents?) or something is not refreshing correctly. Can somebody help?
PS: to indulge in bad hacking, I also tried to regenerate a new image (by calling ax.imshow) after each rotation, but I still get the same behavior.
Below I include a solution to your problem. The method resetExtent uses the data and the image to explicitly set the extent to the desired values. Hopefully I correctly emulated the intended outcome.
import matplotlib.pyplot as plt
import numpy as np
def resetExtent(data,im):
"""
Using the data and axes from an AxesImage, im, force the extent and
axis values to match shape of data.
"""
ax = im.get_axes()
dataShape = data.shape
if im.origin == 'upper':
im.set_extent((-0.5,dataShape[0]-.5,dataShape[1]-.5,-.5))
ax.set_xlim((-0.5,dataShape[0]-.5))
ax.set_ylim((dataShape[1]-.5,-.5))
else:
im.set_extent((-0.5,dataShape[0]-.5,-.5,dataShape[1]-.5))
ax.set_xlim((-0.5,dataShape[0]-.5))
ax.set_ylim((-.5,dataShape[1]-.5))
def main():
fig = plt.gcf()
ax = fig.gca()
H = np.zeros((200,480,10))
# make distinguishing corner of data
H[100:,...] = 1
H[100:,240:,:] = 2
imdat = H[:,:,5]
datShape = imdat.shape
im = ax.imshow(imdat,cmap='jet',vmin=imdat.min(),
vmax=imdat.max(),animated=True,
aspect='equal',
# origin='lower'
)
resetExtent(imdat,im)
fig.savefig("img1.png")
H = np.rot90(H)
imdat = H[:,:,0]
im.set_data(imdat)
resetExtent(imdat,im)
fig.savefig("img2.png")
if __name__ == '__main__':
main()
This script produces two images:
First un-rotated:
Then rotated:
I thought just explicitly calling set_extent would do everything resetExtent does, because it should adjust the axes limits if 'autoscle' is True. But for some unknown reason, calling set_extent alone does not do the job.