Turtle setworldcoordinates and window/canvas sizing - python

I am new to Python and Stackoverflow but have previous general programming experience. I have Python 3.7 installed on Windows 11 and using Pyscripter as a development tool, display device is 1600 x 900 pixels.
I want to develop simple examples of visualising cartesian based fractal functions that the target audience can see how the fractal develops and change some parameters – hence adopting Turtle rather than more technical packages.
I am trying to understand how screensize(), setup() and especially setworldcoordinates() interact when using Turtle graphics. I found the examples at the link https://www.programmersought.com/article/38514262202/ helpful but am puzzled by some outputs in the following code, but have not delved into the underlying Tkinter yet.
The following code snippets and outputs illustrates my question.
import turtle as tu
tu.setup(400, 400)
tu.screensize(800, 800, bg='lightblue')
tu.setworldcoordinates(-1, -1, 5, 5) # Custom coordinate system
tu.goto(0, 0)
print("screensize is ",tu.screensize())
print("window size is ",tu.window_width(), tu.window_height())
tu.mainloop()
*** Remote Interpreter Reinitialized ***
screensize is (380, 380)
window size is 400 400
Reading the Python documentation I understand the initial screensize() assignment has been reset by setworldcoordinates() but not sure where the (380, 380) is derived from rather than (400,300) default. Can this be changed?
I also understood that setworldcoordinates() acted on the screensize “canvas”. However, in the code above, increasing tu.screensize to (2000,20000) had no effect on the output but then changing tu.setup to (800,800) gave the following output, changing both values.
*** Remote Interpreter Reinitialized ***
screensize is (780, 780)
window size is 800 800
Can anyone explain why changing setup() parameters appears to affect both screensize and window size when using setworldcoordinates?

In response to my own question I think I now know the answer. The following code snippet helps illustrate the solution:
import turtle as tu
#Define size of display window
tu.setup(800, 800)
#Define size of drawing screen/canvas and colour
tu.screensize(400, 400, bg='lightblue')
tu.setworldcoordinates(-1, -1, 5, 5) # Custom coordinate system
print("screensize is ",tu.screensize())
print("window size is ",tu.window_width(), tu.window_height())
tu.mainloop()
The snippet above produced the following output:
*** Remote Interpreter Reinitialized ***
screensize is (780, 780)
window size is 800 800
>>>
Changing the tu.screensize() parameters in the above code to 200,200 and leaving the tu-setup() parameters unchanged did not change the output.
Looking at the following code fragment from setworldcoordinates() function in the Turtle graphics module:
if self.mode() != "world":
self.mode("world")
xspan = float(urx - llx)
yspan = float(ury - lly)
wx, wy = self._window_size()
self.screensize(wx-20, wy-20)
The penultimate line assigns the variables wx and wy to the width and height of the Turtle window as defined under setup() function, NOT the screen (canvas), but does not change the underlying parameters of the window.
The last line assigns the values of wx and wy, -20, derived from the window size to change the screensize (canvas).
This would explain why the screensize is always 20 pixels less than the window size and why changes to the screensize are ignored by the setworldcoordinates() function.

Related

Full Screen Gui With Different Screen Sizes In PC Python

Situation:
I'm making a software that has to be full screen
It is all UIs and interfaces.
I want it to work on computers with screens that have different resolutions - So I need the GUI to adjust to the screen size: text will be smaller if the screen resolution is smaller, but it will still be in the middle of the screen
I've tried not using numbers in deciding the position of the text, but instead getting the screen resolution, and multiplying it
Problem:
The text is not getting smaller.
Question:
Is there an easy solution for my problem? Is there a module in python for this purpose? I'm currently using WxPython but I'm open to use any other GUI module.
def Title(object):
(sizeX, sizeY) = object.GetSize()
(displayX, displayY) = wx.GetDisplaySize()
print(displayX)
print(displayY)
print(sizeX)
print(sizeY)
object.SetPosition((displayX/2 - sizeX/2, displayY*0.01))
To adjust the text size to be appropriate to the screen size, you would have to define a custom font size.
Although I suspect that the default font size will already be roughly correct, as the user will have set the system font size, based on the screen size.
You can get the current font size as follows:
self.font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
point_size = self.font.GetPointSize()
Define a new, appropriate, font size based on the result from wx.GetDisplaySize():
self.font.SetPointSize(new size)
Then use SetFont(font) on the items in your UI:
self.panel.SetFont(self.font)

Turtle graphics window's canvas color *not* showing up in Postscript (.ps) file [duplicate]

I am new to Python and have been working with the turtle module as a way of learning the language.
Thanks to stackoverflow, I researched and learned how to copy the image into an encapsulated postscript file and it works great. There is one problem, however. The turtle module allows background color which shows on the screen but does not show in the .eps file. All other colors, i.e. pen color and turtle color, make it through but not the background color.
As a matter of interest, I do not believe the import of Tkinter is necessary since I do not believe I am using any of the Tkinter module here. I included it as a part of trying to diagnose the problem. I had also used bgcolor=Orange rather than the s.bgcolor="orange".
No Joy.
I am including a simple code example:
# Python 2.7.3 on a Mac
import turtle
from Tkinter import *
s=turtle.Screen()
s.bgcolor("orange")
bob = turtle.Turtle()
bob.circle(250)
ts=bob.getscreen()
ts.getcanvas().postscript(file = "turtle.eps")
I tried to post the images of the screen and the .eps file but stackoverflow will not allow me to do so as a new user. Some sort of spam prevention. Simple enough to visualize though, screen has background color of orange and the eps file is white.
I would appreciate any ideas.
Postscript was designed for making marks on some medium like paper or film, not raster graphics. As such it doesn't have a background color per se that can be set to given color because that would normally be the color of the paper or unexposed film being used.
In order to simulate this you need to draw a rectangle the size of the canvas and fill it with the color you want as the background. I didn't see anything in the turtle module to query the canvas object returned by getcanvas() and the only alternative I can think of is to read the turtle.cfg file if there is one, or just hardcode the default 300x400 size. You might be able to look at the source and figure out where the dimensions of the current canvas are stored and access them directly.
Update:
I was just playing around in the Python console with the turtle module and discovered that what the canvas getcanvas() returns has a private attribute called _canvas which is a <Tkinter.Canvas instance>. This object has winfo_width() and winfo_height() methods which seem to contain the dimensions of the current turtle graphics window. So I would try drawing a filled rectangle of that size and see if that gives you what you want.
Update 2:
Here's code showing how to do what I suggested. Note: The background must be drawn before any other graphics are because otherwise the solid filled background rectangle created will cover up everything else on the screen.
Also, the added draw_background() function makes an effort to save and later restore the graphics state to what it was. This may not be necessary depending on your exact usage case.
import turtle
def draw_background(a_turtle):
""" Draw a background rectangle. """
ts = a_turtle.getscreen()
canvas = ts.getcanvas()
height = ts.getcanvas()._canvas.winfo_height()
width = ts.getcanvas()._canvas.winfo_width()
turtleheading = a_turtle.heading()
turtlespeed = a_turtle.speed()
penposn = a_turtle.position()
penstate = a_turtle.pen()
a_turtle.penup()
a_turtle.speed(0) # fastest
a_turtle.goto(-width/2-2, -height/2+3)
a_turtle.fillcolor(turtle.Screen().bgcolor())
a_turtle.begin_fill()
a_turtle.setheading(0)
a_turtle.forward(width)
a_turtle.setheading(90)
a_turtle.forward(height)
a_turtle.setheading(180)
a_turtle.forward(width)
a_turtle.setheading(270)
a_turtle.forward(height)
a_turtle.end_fill()
a_turtle.penup()
a_turtle.setposition(*penposn)
a_turtle.pen(penstate)
a_turtle.setheading(turtleheading)
a_turtle.speed(turtlespeed)
s = turtle.Screen()
s.bgcolor("orange")
bob = turtle.Turtle()
draw_background(bob)
ts = bob.getscreen()
canvas = ts.getcanvas()
bob.circle(250)
canvas.postscript(file="turtle.eps")
s.exitonclick() # optional
And here's the actual output produced (rendered onscreen via Photoshop):
I haven't found a way to get the canvas background colour on the generated (Encapsulated) PostScript file (I suspect it isn't possible). You can however fill your circle with a colour, and then use Canvas.postscript(colormode='color') as suggested by #mgilson:
import turtle
bob = turtle.Turtle()
bob.fillcolor('orange')
bob.begin_fill()
bob.circle(250)
bob.begin_fill()
ts = bob.getscreen()
ts.getcanvas().postscript(file='turtle.eps', colormode='color')
Improving #martineau's code after a decade
import turtle as t
Screen=t.Screen()
Canvas=Screen.getcanvas()
Width, Height = Canvas.winfo_width(), Canvas.winfo_height()
HalfWidth, HalfHeight = Width//2, Height//2
Background = t.Turtle()
Background.ht()
Background.speed(0)
def BackgroundColour(Colour:str="white"):
Background.clear() # Prevents accumulation of layers
Background.penup()
Background.goto(-HalfWidth,-HalfHeight)
Background.color(Colour)
Background.begin_fill()
Background.goto(HalfWidth,-HalfHeight)
Background.goto(HalfWidth,HalfHeight)
Background.goto(-HalfWidth,HalfHeight)
Background.goto(-HalfWidth,-HalfHeight)
Background.end_fill()
Background.penup()
Background.home()
BackgroundColour("orange")
Bob=t.Turtle()
Bob.circle(250)
Canvas.postscript(file="turtle.eps")
This depends on what a person is trying to accomplish but generally, having the option to select which turtle to use to draw your background to me is unnecessary and can overcomplicate things so what one can do instead is have one specific turtle (which I named Background) to just update the background when desired.
Plus, rather than directing the turtle object via magnitude and direction with setheading() and forward(), its cleaner (and maybe faster) to simply give the direct coordinates of where the turtle should go.
Also for any newcomers: Keeping all of the constants like Canvas, Width, and Height outside the BackgroundColour() function speeds up your code since your computer doesn't have to recalculate or refetch any values every time the function is called.

my tkinter gui is invisible for unknown reasons

I've been trying to create a basic gui using tkinter, I've done it before on a different computer but for some reason its invisible. Is there something wrong with my code or the computer (windows)?
import sys
from tkinter import *
mygui = Tk()
mygui.geometry('300x300+0+982')
mygui.title("my gui")
mygui.mainloop()
Is your screen height is bigger than 982 pixel?
Following line place the window at (0, 982) with width 300, height 300. If your screen height smaller than 982 pixel, you can't see it.
mygui.geometry('300x300+0+982')
Replace it with following:
mygui.geometry('300x300+0+0')
and you will see it.

How to get the resolution of a monitor in Pygame?

I'm just wondering if it is possible for me to get the resolution of a monitor in Pygame and then use these dimensions to create a window so that launching the program detects the monitor resolution and then automatically fits the window to the screen in fullscreen.
I am currently using pygame.display.set_mode((AN_INTEGER, AN_INTEGER)) to create the window.
I am aware that you can get video info including the monitor resolution using pygame.display.Info() but how can I extract these values and then use them in pygame.display.set_mode()???
Thanks in advance,
Ilmiont
You can use pygame.display.Info():
The docs say:
current_h, current_w: Height and width of the current video mode, or of the
desktop mode if called before the display.set_mode is called.
(current_h, current_w are available since SDL 1.2.10, and pygame
1.8.0) They are -1 on error, or if an old SDL is being used.1.8.0)
pygame.display.Info() creates an Info Object with the attributes current_h and current_w.
Create the Info Object before you call display.set_mode and then call display.set_mode with current_h and current_w from the object.
Example:
infoObject = pygame.display.Info()
pygame.display.set_mode((infoObject.current_w, infoObject.current_h))
I don't know if this will work, but if you just want a fullscreen window use the following:
pygame.display.set_mode((0,0),pygame.FULLSCREEN)
(of course you still have to import pygame).
I don't know much about pygame, but here is a way using the module win32api:
from win32api import GetSystemMetrics
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
Update: After taking a glance at the docs, seems like you can get it from pygame.display.Info, like this:
width, height = pygame.display.Info().current_w, pygame.display.Info().current_h
Hope this helps!

Understanding performance limitations of the Tkinter Canvas

I've created a simple application to display a scatterplot of data using Tkinter's Canvas widget (see the simple example below). After plotting 10,000 data points, the application becomes very laggy, which can be seen by trying to change the size of the window.
I realize that each item added to the Canvas is an object, so there may be some performance issues at some point, however, I expected that level to be much higher than 10,000 simple oval objects. Further, I could accept some delays when drawing the points or interacting with them, but after they are drawn, why would just resizing the window be so slow?
After reading effbot's performance issues with the Canvas widget it seems there may be some unneeded continuous idle tasks during resizing that need to be ignored:
The Canvas widget implements a straight-forward damage/repair display
model. Changes to the canvas, and external events such as Expose, are
all treated as “damage” to the screen. The widget maintains a dirty
rectangle to keep track of the damaged area.
When the first damage event arrives, the canvas registers an idle task
(using after_idle) which is used to “repair” the canvas when the
program gets back to the Tkinter main loop. You can force updates by
calling the update_idletasks method.
So, the question is whether there is any way to use update_idletasks to make the application more responsive once the data has been plotted? If so, how?
Below is the simplest working example. Try resizing the window after it loads to see how laggy the application becomes.
Update
I originally observed this problem in Mac OS X (Mavericks), where I get a substantial spike in CPU usage when just resizing the window. Prompted by Ramchandra's comments I've tested this in Ubuntu and this doesn't seem to occur. Perhaps this is a Mac Python/Tk problem? Wouldn't be the first I've run into, see my other question:
PNG display in PIL broken on OS X Mavericks?
Could someone also try in Windows (I don't have access to a Windows box)?
I may try running on the Mac with my own compiled version of Python and see if the problem persists.
Minimal working example:
import Tkinter
import random
LABEL_FONT = ('Arial', 16)
class Application(Tkinter.Frame):
def __init__(self, master, width, height):
Tkinter.Frame.__init__(self, master)
self.master.minsize(width=width, height=height)
self.master.config()
self.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.main_frame = Tkinter.Frame(self.master)
self.main_frame.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.plot = Tkinter.Canvas(
self.main_frame,
relief=Tkinter.RAISED,
width=512,
height=512,
borderwidth=1
)
self.plot.pack(
anchor=Tkinter.NW,
fill=Tkinter.NONE,
expand=Tkinter.FALSE
)
self.radius = 2
self._draw_plot()
def _draw_plot(self):
# Axes lines
self.plot.create_line(75, 425, 425, 425, width=2)
self.plot.create_line(75, 425, 75, 75, width=2)
# Axes labels
for i in range(11):
x = 75 + i*35
y = x
self.plot.create_line(x, 425, x, 430, width=2)
self.plot.create_line(75, y, 70, y, width=2)
self.plot.create_text(
x, 430,
text='{}'.format((10*i)),
anchor=Tkinter.N,
font=LABEL_FONT
)
self.plot.create_text(
65, y,
text='{}'.format((10*(10-i))),
anchor=Tkinter.E,
font=LABEL_FONT
)
# Plot lots of points
for i in range(0, 10000):
x = round(random.random()*100.0, 1)
y = round(random.random()*100.0, 1)
# use floats to prevent flooring
px = 75 + (x * (350.0/100.0))
py = 425 - (y * (350.0/100.0))
self.plot.create_oval(
px - self.radius,
py - self.radius,
px + self.radius,
py + self.radius,
width=1,
outline='DarkSlateBlue',
fill='SteelBlue'
)
root = Tkinter.Tk()
root.title('Simple Plot')
w = 512 + 12
h = 512 + 12
app = Application(root, width=w, height=h)
app.mainloop()
There is actually a problem with some distributions of TKinter and OS Mavericks. Apparently you need to install ActiveTcl 8.5.15.1. There is a bug with TKinter and OS Mavericks. If it still isn't fast eneough, there are some more tricks below.
You could still save the multiple dots into one image. If you don't change it very often, it should still be faster. If you are changing them more often, here are some other ways to speed up a python program. This other stack overflow thread talks about using cython to make a faster class. Because most of the slowing down is probably due to the graphics this probably won't make it a lot faster but it could help.
Suggestions on how to speed up a distance calculation
you could also speed up the for loop by defining an iterator ( ex: iterator = (s.upper() for s in list_to_iterate_through) ) beforehand, but this is called to draw the window, not constantly as the window is maintained, so this shouldn't matter very much. Also, a another way to speed things up, taken from python docs, is to lower the frequency of python's background checks:
"The Python interpreter performs some periodic checks. In particular, it decides whether or not to let another thread run and whether or not to run a pending call (typically a call established by a signal handler). Most of the time there's nothing to do, so performing these checks each pass around the interpreter loop can slow things down. There is a function in the sys module, setcheckinterval, which you can call to tell the interpreter how often to perform these periodic checks. Prior to the release of Python 2.3 it defaulted to 10. In 2.3 this was raised to 100. If you aren't running with threads and you don't expect to be catching many signals, setting this to a larger value can improve the interpreter's performance, sometimes substantially."
Another thing I found online is that for some reason setting the time by changing os.environ['TZ'] will speed up the program a small amount.
If this still doesn't work, than it is likely that TKinter is not the best program to do this in. Pygame could be faster, or a program that uses the graphics card like open GL (I don't think that is available for python, however)
Tk must be getting bogged down looping over all of those ovals. I'm not
sure that the canvas was ever intended to hold so many items at once.
One solution is to draw your plot into an image object, then place the image
into your canvas.

Categories

Resources