How to set canvas size properly in Tkinter? - python

I am using Tkinter to visualize my data points. My problem is that I cannot make the data points appear in the center of the canvas and meanwhile the canvas is big enough.
To make the canvas look good, I wish to fix it at around 800*600 (I think the unit is pixel). So I did the following:
class DisplayParticles(Canvas):
def __init__(self):
# Canvas
Canvas.__init__(self)
self.configure(width=800, height=600)
# Particles
self.particle_radius = 1
self.particle_color = 'red'
# User
self.user_radius = 4
self.user_color = 'blue'
self.ghost_color = None
However, my data to be plotted are in the unit of meter. Plus, they center around the origin (0, 0), which means that there are negative coordinates for both x and y.
Then when I plot them on the canvas I get something like this
Obviously, the data points were plotted in pixel!
I wish the canvas to be big enough on the screen and meanwhile the data are plotted in a proper scale centered at the canvas. (Place my origin (0, 0) at the canvas center)
How may I do that?

The visible center of the canvas can be changed by setting the scrollregion attribute. For example, if you set the scrollregion to (-400,-400, 400, 400) in a canvas that is 800 pixels wide, the coordinate 0,0 will appear in the center of the screen.
Here's an example that draws a square at 0,0, and which appears in the center of the screen:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self,*args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.canvas = tk.Canvas(width=800, height=800)
self.canvas.pack(side="top", fill="both", expand=True)
self.canvas.configure(scrollregion=(-400, -400, 400, 400))
self.canvas.create_rectangle(-10,-10,10,10, fill="red", outline="black")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
You can also use the xview_scroll and yview_scroll methods to move 0,0 into the center of the visible portion of the canvas. Instead of setting the scrollregion as in the above example, you can do this after creating your canvas:
self.canvas.xview_scroll(800, "units")
self.canvas.yview_scroll(800, "units")
This will programmatically scroll the canvas over and down 800 pixels, so that 0,0 is in the center of the screen.

Use the xview and yview methods to scroll the canvas view so the origin is in the center.
Scaling is not supported, so if you need that, you need to transform the source data, like Brionius suggests.

Your Canvas is not going to automatically scale to fit what you've drawn - you'll have to figure out what the proper size is and set it.
Also, Canvas coordinates will always start with (0, 0) in the upper left corner - no way to change that. That means you'll have to translate all the points you plot on the canvas. Luckily, that's easy:
width = ... # Determine the correct width
height = ... # Determine the correct height
self.configure(width=width, height=height)
coords = (-20, -30, 10, 60) # The coordinates of a shape you want to draw
# add width/2 and height/2 to x and y coordinates respectively so that the (0, 0) coordinate is shifted to the center of the canvas:
newCoords = (coords[0]+width/2, coords[1]+height/2, coords[2]+width/2, coords[3]+height/2)
self.create_oval(*newCoords) # Create the translated shape

Related

How to show only a portion of the tkinter canvas by cropping the tkinter window?

I want to be able to zoom into my tkinter canvas. My tkinter canvas is 500x500px, and I only want my window to display the center 200x200px portion of this canvas. How do I do this? I know that I can just specify my window size as 200x200px using root.geometry("200x200+0+0"), but this causes my window to display the top left corner of my canvas, and not the center. Before I do anything, my entire canvas looks like this:
Ultimately, I want my window to look like this, with the canvas centered within the window:
This is my code:
import tkinter
root = tkinter.Tk()
root.title("")
root.geometry("200x200+0+0")
canvas = tkinter.Canvas(master = root, width = 500, height = 500)
canvas.create_oval(200, 200, 300, 300, outline = "black", fill = "blue")
canvas.pack()
which returns:
As you can see, the canvas is not centered, and the window is showing the upper left hand corner at the moment. Does anyone have any suggestions?
Ok, thanks to this stackoverflow post, I found out there is an option when creating a tkinter canvas called scrollregion. The format of the argument is "x0 y0 x1 y1" for anyone that is wondering, where (x0, y0) is the upper-left corner of the area of the canvas I want to show and (x1, y1) is the bottom-right corner of the same area. My code should be fixed to this:
canvas = tkinter.Canvas(master = root, width = 500, height = 500, scrollregion = "150 150 350 350")
Be wary that these coordinates do not account for a scrollbar...I'm still working on figuring that out. Much thanks to this stackoverflow post as well, specifically the following words:
I don't see any difference between putting the y-scrollbar to the bottom or putting the canvas view to the bottom because the two are linked.

Tkinter - relation between font type and width

I apologize in advance if my question is a duplicate however I have not found an answer to this question.
I'm learning Tkinter and I'm struggling with understanding the relation between a label's font type, it's size and it's width and the length of the string in it.
Specifically, what my problem is:
I have created a widget: a 800x640 canvas on which I want to place other
widgets.
On this canvas I want to place a label with some text which has the following
attributes: font: Helvetica, font size: 20, text = "main application". I want
to place this label widget at the very most top left corner of the
widget(meaning at point 0,0 with respect to the canvas). I want the label to
be 200 in width meaning it's background to take almost 1/3 of the canvas's
size(after I manage to do this I plan to add 2 more labels as well). I guess
the height of the label is determined by the font size in this case 20. I
placed the label at coordinate y=20 and this coordinate seems to be ok.
I did some googling and found out that the width parameter of the label widget is not the actual width but something related to the font and size of the label's text: something like if I understood correctly: if the width is 6 than the label will be wide enough to contain 6 characters of, in my case verdana size 20. But I was not able to figure out what width and what x coordinate I should give my label so it starts at the x point of the canvas. Here is the code that I wrote:
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
from tkinter import Menu
# Define the application class where we will implement our widgets
class Application(Frame):
def __init__(self, master):
super(Application, self).__init__(master)
# CANVAS COLOUR DEFAULTS TO THE COLOUR OF THE WORKING WINDOW
canvas = Canvas(master, width=800, height = 640, bg="gray") # IF YOU DO .PACK() HERE IT WILL RETURN NONE AND THEN YOU WILL HAVE PROBLEMS BECAUSE .PACK() RETURNS A 'NONE' TYPE OBJECT
canvas.place(relx=0.5, rely=0.5, anchor=CENTER)
# The 'menu' of the application. The selection labels
main_application_label = Label(master, text="main_application", font=("Helvetica", 20))
main_application_window = canvas.create_window(103,20, window=main_application_label)
main_application = Tk()
main_application.title("main_application")
app = Application(main_application)
app_width = 800
app_height = 640
screen_width = main_application.winfo_screenwidth()
screen_height = main_application.winfo_screenheight()
x_coord = (screen_width/2) - (app_width/2)
y_coord = (screen_height/2) - (app_height/2)
main_application.geometry("%dx%d+%d+%d" % (app_width, app_height, x_coord, y_coord))
main_application.mainloop()
I have managed to somehow get the label at around point 0,0(by giving more values till I got it right) but the actual width of the label is not 200 pixels(~1/3 of the canvas). Please help me understand what values to the width parameter I should give so that my label's background is 1/3 of the canvas's size and if possible explain the relation between character font and width parameter so I can do that for any widgets regardless of their text's length. Thank you for reading my post!
Edit: What I wanted to do was to place 3 widgets(labels in this case but it doesn't matter) on the canvas. I did not understand what the 'anchor' option does and that was confusing me because I was expecting the center of the widget to be placed at the given coordinates all times but as I was changing anchor the placement of the center of the widget was changing and that was confusing me. It's all clear now thanks to #Bryan Oakley. Thanks.
If you want the upper left corner of the text to be at (0,0), you don't have to adjust the coordinates based on the width. You can use the anchor option when creating the canvas object:
main_application_window = canvas.create_window(0, 0, anchor="nw",
window=main_application_label)
If you really need to compute the actual size of the string, you can create a Font object and then use the measure method to find the actual width of a string in the given font.
from tkinter.font import Font
font = Font(family="Helvetica", size=20)
string_width = font.measure("main_application")
string_height = font.metrics("linespace")
This gives you the size of the rendered string. If you're using a label widget you'll also need to take into account the amount of padding and borders that the widget uses.
When you create items on a canvas, you can specify the width and height. For example, this makes the widget 200 pixels wide:
main_application_window = canvas.create_window(0, 0, anchor="nw", width=200,
window=main_application_label, width=400)

position of line when resizing Tkinter window

The following code:
from tkinter import *
root=Tk()
for x in range(10):
for y in range(10):
canvas=Canvas(root, width='15',height='15',highlightthickness=0,bg='red')
canvas.create_line(canvas.winfo_x(),canvas.winfo_y(),canvas.winfo_x()+15,canvas.winfo_y()+15,width=2,fill='black')
canvas.grid(row=y,column=x,sticky='NESW')
for x in range(10):
for y in range(10):
root.columnconfigure(x,weight=1)
root.rowconfigure(y,weight=1)
root.mainloop()
produces this, which is a 10 by 10 grid filled with canvases; there is a line extending from the top left to the bottom right corner of each canvas.
When I resize the window, the canvas widgets resize correctly, but the lines retain their shape like this. The lines need to adjust according to the window/widget size.
The core of the problem is that the lines are made using the coordinates of the top left corner of the widget, and are extended 15 pixels in each direction. Is there a way of getting the coordinates of the bottom right corner of the widget, so that the lines can change their shape dynamically, or some other way of keeping the lines shape, relative to the widget?
You can get the current width and height of any widget with the winfo_width and winfo_height methods. If you are binding to the <Configure> method to track when the canvas changes size, the event object has a width and height attribute.
For example:
from tkinter import *
def redraw_line(event):
width = event.width
height = event.height
canvas = event.widget
canvas.coords("diagonal", 0, 0, width, height)
root=Tk()
for x in range(10):
for y in range(10):
canvas=Canvas(root, width='15',height='15',highlightthickness=0,bg='red')
canvas.bind("<Configure>", redraw_line)
# coordinates are irrelevant; they will change as soon as
# the widget is mapped to the screen.
canvas.create_line(0,0,0,0, tags=("diagonal",))
canvas.grid(row=y,column=x,sticky='NESW')
for x in range(10):
for y in range(10):
root.columnconfigure(x,weight=1)
root.rowconfigure(y,weight=1)
root.mainloop()

Drawing rectangle on python Tkinter canvas that covers the entire canvas does not show border on top and left

I'm trying to create a rectangle inside a Tkinter (Python 2.7) canvas that is the same dimension as the canvas. Here is the relevant part of the code:
self.canvas = Canvas(self, width=100, height=100, backround="yellow")
self.canvas.create_rectangle(0,0,100,100)
This draws a rectangle but I can't see the left and top border of the rectangle. If I start the rectangle from let's say 5,5 instead of 0,0 I can see the border of the rectangle. Any ideas as to why this happens, and how I can get around it?
Unfortunately, the canvas border is included in the drawable region. Try setting the borderwidth and highlightthickness attributes to zero on the canvas.
You'll also want to adjust the coordinates of your rectangle to end at 99, since counting starts at zero (if the width is 100, the coordinates go from 0 to 99).

Set Max Width for Frame with ScrolledWindow in wxPython

I created a Frame object and I want to limit the width it can expand to. The only window in the frame is a ScrolledWindow object and that contains all other children. I have a lot of objects arranged with a BoxSizer oriented vertically so the ScrolledWindow object gets pretty tall. There is often a scrollbar to the right so you can scroll up and down.
The problem comes when I try to set a max size for the frame. I'm using the scrolled_window.GetBestSize() (or scrolled_window.GetEffectiveMinSize()) functions of ScrolledWindow, but they don't take into account the vertical scrollbar. I end up having a frame that's just a little too narrow and there's a horizontal scrollbar that will never go away.
Is there a method that will account compensate for the vertical scrollbar's width? If not, how would I get the scrollbar's width so I can manually add it to the my frame's max size?
Here's an example with a tall but narrow frame:
class TallFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='Tall Frame')
self.scroll = wx.ScrolledWindow(parent=self) # our scroll area where we'll put everything
scroll_sizer = wx.BoxSizer(wx.VERTICAL)
# Fill the scroll area with something...
for i in xrange(10):
textbox = wx.StaticText(self.scroll, -1, "%d) Some random text" % i, size=(400, 100))
scroll_sizer.Add(textbox, 0, wx.EXPAND)
self.scroll.SetSizer(scroll_sizer)
self.scroll.Fit()
width, height = self.scroll.GetBestSize()
self.SetMaxSize((width, -1)) # Trying to limit the width of our frame
self.scroll.SetScrollbars(1, 1, width, height) # throwing up some scrollbars
If you create this frame you'll see that self.SetMaxSize is set too narrow. There will always be a horizontal scrollbar since self.scroll.GetBestSize() didn't account for the width of scrollbar.
This is a little ugly, but seems to work on Window and Linux. There is difference, though. The self.GetVirtualSize() seems to return different values on each platform. At any rate, I think this may help you.
width, height = self.scroll.GetBestSize()
width_2, height_2 = self.GetVirtualSize()
print width
print width_2
dx = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
print dx
self.SetMaxSize((width + (width - width_2) + dx, -1)) # Trying to limit the width of our frame
self.scroll.SetScrollbars(1, 1, width, height) # throwing up some scrollbars

Categories

Resources