I'm new to Python, I still have issues with the semantics of class inheritance.
The following is the relevant class from the module games.py module that I am importing:
class Text(Sprite):
"""
Alphanumeric values displayed on the screen.
"""
def __init__(self, value, size, color, angle=0,
x=0, y=0,
top=None, bottom=None, left=None, right=None,
dx=0, dy=0,
interval=1, is_collideable=True):
self._size = size
self._color = color
self._value = value
self._font = pygame.font.Font(None, self._size)
Sprite.__init__(self, self._create_surface(), angle,
x, y,
top, bottom, left, right,
dx, dy,
interval, is_collideable)
and the following is from where I'm trying to call it in my own program:
self.scorebox = games.Text (value = self.scorevar,
pygame.font.Font(ardarlingopentype, 50),
color = color.white,
x = 550,
y = 50)
As you can see the syntax is wrong, but how do I go about fixing this such that I can inherit the class Text from my own program and make FONT an accessible argument that I can change?
Thanks.
Your problem is, that you you are ordering the arguments incorrectly: there are positional and keyword arguments. All keywords arguments must succeed the positional arguments.
This would work:
self.scorebox = games.Text (
pygame.font.Font(ardarlingopentype, 50),
value = self.scorevar,
color = color.white,
x = 550,
y = 50
)
Not sure(note that you can't used not named arguments after named and/or mix them - you have used not named argument after 'value') but seems that you need to modify code the following way:
class Text(Sprite):
"""
Alphanumeric values displayed on the screen.
"""
def __init__(self, value, size, color, angle=0,
x=0, y=0,
top=None, bottom=None, left=None, right=None, font=None,
dx=0, dy=0,
interval=1, is_collideable=True):
self._size = size
self._color = color
self._value = value
if font:
self.font_ = font
else:
self._font = pygame.font.Font(None, self._size)
Sprite.__init__(self, self._create_surface(), angle,
x, y,
top, bottom, left, right,
dx, dy,
interval, is_collideable)
And then:
import pygame
import games
self.scorebox = games.Text (value = self.scorevar,
size = 50,
color = color.white,
x = 550,
y = 50)
OR:
import pygame
import games
self.scorebox = games.Text (value = self.scorevar,
size = 50,
font = pygame.font.Font(ardarlingopentype, 50),
color = color.white,
x = 550,
y = 50)
So guys I wrote to the developers of the Livewires package; and I was fortunate enough to receive a reply from one of them.
First, make a backup copy of games.py and put it somewhere safe. That
way if you do make a mistake, you can always recover the original
code.
Now our games.py is written on top of the PyGame library, which does
provide a way of setting the font. As you might have guessed, it's to
do with that line reading:
> self._font = pygame.font.Font(None, self._size)
The documentation is available online at
http://www.pygame.org/docs/ref/font.html#pygame.font.Font but I'll
just quickly summarise here. pygame.font.Font() creates a new PyGame
font object, which PyGame uses to tell it how to draw text. The
"None" parameter tells it to use the default font, but you can replace
that with the full name of a font file instead. The easiest way to do
that is to modify the Text classes initialiser to pass it as an
optional parameter.
class Text(Sprite):
def __init__(self, value, size, color, angle=0,
> x=0, y=0,
> top=None, bottom=None, left=None, right=None,
> dx=0, dy=0,
> interval=1, is_collideable=True,
> fontfile=None):
> self._size = size
> self._color = color
> self._value = value
> self._font = pygame.font.Font(fontfile, self._size)
> Sprite.__init__(self, self._create_surface(), angle,
> x, y,
> top, bottom, left, right,
> dx, dy,
> interval, is_collideable)
You would then create your Text object by calling 'Text(blah blah
blah, fontfile="/some/font/file/name.ttf")' or whatever the filename
is. Any other Text objects that don't specify a "fontfile" will
automatically use "None" instead, which will give them the default
font exactly as before.
So what's the fully-qualified pathname of the font file for
"TimesNewRoman"? I have no idea what it would be on your computer.
Fortunately PyGame provides a way of not having to know:
pygame.font.match_font(). You can use that in your own program
(rather than modifying games.py any more), but you will have to either
"import pygame.font" for yourself or call it
"games.pygame.font.match_font()" -- either should work equally well.
Related
I have a customized Text object, which has the same logic as the following simplified object:
import matplotlib.pyplot as plt
from matplotlib.text import Text
class MyText(Text):
def __init__(self, x, y, txt, height, **kwargs):
super().__init__(x, y, txt, **kwargs)
self.height = height
def mydraw(self, ax):
txt = ax.add_artist(self)
myset_fontsize(txt, self.height)
return self
def set_height(self, height):
self.height = height
#myset_fontsize(self, height)
def myset_fontsize(txtobj, height):
trans = txtobj.get_transform()
pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0,0))
dpi = txtobj.axes.get_figure().get_dpi()
points = pixels / dpi * 72
txtobj.set_fontsize(points)
if __name__ == '__main__':
fig, ax = plt.subplots()
ax.grid(True)
txt = MyText(0.2, 0.2, 'hello', 0.1)
txt.mydraw(ax)
MyText is different from the built-in Text in that the fontsize is dependent on the height, which, for example, specifies the height of the text in the data coordinates. Except this, MyText is almost the same as Text. The example code gives the following figure:
This works fine for a static image. However, I want MyTest to be interactive, which includes the following goals:
In an interactive plot mode, txt.set_height(0.5) shoule change the fontsize dynamically. I know I can add a snippet as the comment shows, but if MyText object is not added to the axes, txt.set_height(0.5) will throw an AttributeError. In short, txt.set_height() should behave similarly to txt.set_fontsize().
When the figure is resized by dragging the plot window, MyText should change the fontsize accordingly, that is, the height of text in the data coordinates should keep the same. But currently the fontsize is unchanged when resizing the figure. I have found this answer, but mpl_connect needs some way to get the Figure object, and I want MyText interactive after calling txt.mydraw(ax).
When I change the aspect ratio of the figure, MyText should change the fontsize accordingly, same as the second point.
Thanks for any ideas!
If you only need to change the font from the window size.
Installed an event handler triggered by resizing the window.
Fonts size-bound to one side of the window size(in this case to the width).
import matplotlib.pyplot as plt
from matplotlib.text import Text
class MyText(Text):
def __init__(self, x, y, txt, height, **kwargs):
super().__init__(x, y, txt, **kwargs)
self.height = height
self.event_text = fig.canvas.mpl_connect('resize_event', self.mysize)
def mysize(event, ax):
fig = plt.gcf()
size_ = fig.get_size_inches()
txt.set_fontsize(size_[0]*5)
def mydraw(self, ax):
txt = ax.add_artist(self)
myset_fontsize(txt, self.height)
return self
def set_height(self, height):
self.height = height
# myset_fontsize(self, height)
def myset_fontsize(txtobj, height):
trans = txtobj.get_transform()
pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0, 0))
dpi = txtobj.axes.get_figure().get_dpi()
points = pixels / dpi * 72
txtobj.set_fontsize(points)
if __name__ == '__main__':
fig, ax = plt.subplots()
ax.grid(True)
txt = MyText(0.2, 0.2, 'hello', 0.1)
txt.mydraw(ax)
plt.show()
After reading the user guide, I found that every artist has a stale attribite, which is some signal to re-render the figure. The complete solution is as follows:
import matplotlib.pyplot as plt
from matplotlib.text import Text
class MyText(Text):
def __init__(self, x, y, txt, height, **kwargs):
super().__init__(x, y, txt, **kwargs)
self.height = height
def __call__(self, event):
# When calling myset_fontsize, `self.stale` will be `True` due to `self.set_fontsize()` in the function body.
myset_fontsize(self, self.height)
def mydraw(self, ax):
txt = ax.add_artist(self)
# Connect "draw_event" so that once a draw event happens, a new fontsize is calculated and mark the `Text` object is stale.
ax.get_figure().canvas.mpl_connect('draw_event', self)
return txt
def set_height(self, height):
self.height = height
# When a new height is set, then the
#`Text` object is stale, which will
# forward the signal of re-rendering
# the figure to its parent.
self.stale = True
def myset_fontsize(txtobj, height):
trans = txtobj.get_transform()
pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0,0))
dpi = txtobj.axes.get_figure().get_dpi()
points = pixels / dpi * 72
txtobj.set_fontsize(points)
This solution almost solves my problem although it's not perfect. It's a little inefficient.
Any improvements are appreciated.
I'm from NON computer science background (Biochemist). In Python programming under "Create a class function", I didn't get the output for the following written function:
class circle (object):
def __init__(self, 10, 'red'):
self.radius=10;
self.color='red'
I got syntax error like this:
File "<ipython-input-1-ab699251caa9>", line 2
def_init_(self,10,'red'):
^
SyntaxError: invalid syntax
you cant pass pure value you need to pass it with variables
class circle (object):
def __init__(self,number=10,color='red'):
self.radius=number
self.color=color
Function parameters must be variables, not numbers or strings. Change this:
class circle (object):
def __init__(self, 10, 'red'):
self.radius = 10
self.color = 'red'
to this:
class circle (object):
def __init__(self, radius, color):
self.radius = radius
self.color = color
This class can be instantiated with:
myCircleObject = circle(10, 'red')
or, if you want to make the association parameter-value more clear, you can instantiate with:
myCircleObject = circle(radius = 10, color = 'red')
You can also set default values for the parameters:
class circle (object):
def __init__(self, radius = 10, color = 'red'):
self.radius = radius
self.color = color
In this way, you can still instantiate the object as before but you can also init with:
myCircleObject = circle()
In this case, radius and color will have the default values specified in the signature (radius will be 10 and the color will be 'red').
Note that to avoid misinterpretations, parameters with default values can only be listed in the rightmost part. So if you want to specify a default value only for the parameter radius, you need to move it to the right:
class circle (object):
def __init__(self, color, radius = 10):
self.radius = radius
self.color = color
I'm a beginner and i'm trying to make paint with python turtle but my code gives an error. I've tried everything I could think of but it still isn't working.
from turtle import *
from menuitem import MenuItem
def changePenColor(c):
"""Changes the system turtle's color to c."""
color(c)
def createMenu(callBack):
"""Displays 6 menu items to respond to the given callback function."""
x = - (window_width() / 2) + 30
y = 100
colors = ('red', 'green', 'blue', 'yellow', 'black', 'purple')
shape = "circle"
for color in colors:
MenuItem(x, y, shape, color, callBack)
y -= 30
def main():
"""Creates a menu for selecting colors."""
reset()
shape("turtle")
createMenu(color)
return "done!"
if __name__=='__main__':
msg = main()
print(msg)
mainloop()
And this code in a different file:
from turtle import Turtle
class MenuItem(Turtle):
"""Represents a menu item."""
def __init__(self, x, y, shape, color, callBack):
"""Sets the initial state of a menu item."""
Turtle.__init__(x, y, self, shape = shape, visible = False)
self.speed(0)
self.up()
self.goto(x, y)
self.color(color, color)
self._callBack=callBack
self.onclick(lambda x,y: self._callBack(color))
self.showturtle()
If anyone knows what I can do to fix this, I'd be happy to know.
Thanks 😊
Your code is somewhat confused. Specifically:
from turtle import *
Just don't. Particularly in a module. Import as little as you need to get the job done.
createMenu(color)
This should be createMenu(changePenColor) and changePenColor() should be defined in the main module, not the MenuItem class module.
Turtle.__init__(x, y, self, shape = shape, visible = False)
first three arguments to __init__ shouldn't be there and you should use super, all as #Evan notes.
reset()
self._callBack=callBack
These two statments are effectively no-ops and can be left out.
Below is my rework of your code that I believe does what you're attempting to do. For example purposes, instead of the main module, I just used a if __name__ == '__main__': for testing:
from turtle import Screen, Turtle
COLORS = ('red', 'green', 'blue', 'yellow', 'black', 'purple')
CURSOR_SIZE = 20
class MenuItem(Turtle):
''' Represents a menu item. '''
def __init__(self, x, y, shape, color, callBack):
''' Sets the initial state of a menu item '''
super().__init__(shape=shape, visible=False)
self.penup()
self.goto(x, y)
self.color(color)
self.onclick(lambda x, y: callBack(color))
self.showturtle()
def createMenu(callBack):
''' Displays 6 menu items to respond to the given callback function. '''
screen = Screen()
x = CURSOR_SIZE * 1.5 - screen.window_width() / 2
y = 100
for color in COLORS:
MenuItem(x, y, 'circle', color, callBack)
y -= CURSOR_SIZE * 1.5
if __name__ == '__main__':
from turtle import getscreen, getturtle
def changePenColor(c):
''' Changes the turtle's color to c. '''
turtle.color(c)
screen = getscreen() # singular screen instance
turtle = getturtle() # default turtle
turtle.shape('turtle')
# Create a menu for selecting colors.
createMenu(changePenColor)
screen.mainloop()
In your first line of the __init__ function on your MenuItem class, use this
super().__init__(shape=shape, visible=False)
instead of
Turtle.__init__(x, y, self, shape = shape, visible = False)
You don't need to pass in x, y, or self, because you are already setting the position by saying self.goto(x, y). Also, use super() instead of Turtle, because you need to initialize the superclass, not just another instance of Turtle. By saying Turtle.__init__(...) you are creating an instance of that object and doing nothing with it. By saying super().__init__(...), you are initializing the superclass of your object, which is what you always need to do when you are subclassing an object (in this case Turtle).
Note: your __init__ function needs to be indented, but I'll assume that was a pasting error.
Beginner here trying to make sense of classes. Below is the code for my class Cell:
import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
class Cell:
def __init__(self, x, y, r):
self.x = random(x)
self.y = random(y)
self.r = 200
def show(self):
canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
top.mainloop()
I'm attempting to draw the cell in my main program by calling the function show from the class. Here is the code for my main window:
import tkinter
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
from Cell import Cell
cell = Cell()
cell.show()
top.mainloop()
This is resulting in the canvas being drawn correctly, but the oval is nowhere to be found. I am not getting any errors either.
Any help would be appreciated. Thank you!
====================
Turns out, I misunderstood the arguments for create_oval. I found some code that converts the clunky create_oval function into a function which receives a set of coordinates for the center of the oval and a radius.
In addition to this, the help I received in understanding classes and other Python functionality helped significantly as well. Thanks to those who helped!
This is my revised code which works as intended.
import tkinter as tk
import random
top = tk.Tk()
canvas = tk.Canvas(top, width=400, height=400, bg="grey")
canvas.grid()
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
class Cell:
def __init__(self, canvas, x, y, r):
self.canvas = canvas
self.x = x
self.y = y
self.r = r
def show(self):
self.canvas.create_circle(self.x, self. y, self.r, fill = "blue")
cell = Cell(canvas, random.randrange(50, 350), random.randrange(50, 350), 25)
cell.show()
top.mainloop()
The problem here is that your main script and your Cell module are both creating a new Tk instance, adding a Canvas to it, and then calling its mainloop method.
If you trace through the order in which statements get executed, you'll find that the cell = Cell() and cell.show() don't happen until after the first top.mainloop() returns, and mainloop() doesn't return until you quit the program. (In fact, if your code did get that far, it would fail with a TypeError, which I'll get to below.)
But, more generally, you only want one Tk in your program, and everyone else should refer to that.
And, in this case, you want the same for the Canvas: just one of them, packed onto the one Tk main window.
So, how can Cell.show refer to the canvas global from another module?
The best solution is to not refer to it as a global at all, and instead pass it in to the initializer, the same way you do with x, y, and r:
class Cell:
def __init__(self, canvas, x, y, r):
self.canvas = canvas
self.x = random(x)
self.y = random(y)
self.r = 200
def show(self):
self.canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
And then in the main script:
cell = Cell(canvas, ?, ?. ?)
cell.show()
But notice those ?s I put there. Your Cell class definition demands x, y, and r values in its initializer, but your Cell() constructor call doesn't pass any. That will raise a TypeError complaining that you're missing required arguments.
What do you want to pass here? Since the canvas is 400x400, maybe you want to pass something like 400, 400, 200? If so:
cell = Cell(canvas, 400, 400, 200)
cell.show()
Going back to that initializer, you've got some other problems there:
self.x = random(x)
self.y = random(y)
That random is a module. You can't call a module. You probably wanted something like this:
self.x = random.randrange(x)
That calls a function from the random module, one which is defined to return a random number in range(0, x), which seems like what you want.
Also:
self.r = 200
Why take an r parameter, just to ignore it? You probably wanted this:
self.r = r
Or, maybe you didn't actually want x, y, and r as parameters? Maybe you want to hardcode randrange(400), randrange(400), and 200, or maybe you want to compute them from the width and height of the canvas parameter, or… you can do almost anything you want, you just have to think through what you want, and make sure the interface you declare in the def matches the way you call it in the Cell(…) later.
I think your execution path is never getting to the cell.show() line.
When you import Cell, you have code at the top level top.mainloop(). This enters the main loop and never exits, so you never get to the lines below it.
It's a good rule of thumb to avoid putting code at the base level. Leave that for defining classes and functions. If you want code to run when the file is called like a script, put it in a if __name__ == __main__: condition.
You also had some syntax issues using random and calling the Cell constructor.
The example below works as expected.
import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
class Cell:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def show(self):
canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
if __name__ == "__main__":
cell = Cell(100, 50, 5)
cell.show()
top.mainloop()
I have this class to create a Statusbar:
class Statusbar(Canvas):
'''Creates a statusbar widget'''
def __init__(self, master = None, **options):
if not master: master = Tk()
self.master, self.options = master, options
self.barFill, self.addText, self.value = self.options.get('barFill', 'red'), self.options.get('addText', True), 0
for option in ('barFill', 'addText'):
if option in self.options: del self.options[option]
Canvas.__init__(self, master, **self.options)
self.offset = self.winfo_reqwidth() / 100
self.height = self.winfo_reqwidth()
if self.addText: self.text = self.create_text(self.winfo_reqwidth()/2, self.winfo_reqheight()/2, text = '0%')
self.bar = self.create_rectangle(0, 0, self.value, self.height, fill = self.barFill)
def setValue(self, value):
'''Sets the value of the status bar as a percent'''
self.value = value * self.offset
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
def change(self, value):
'''Changes the value as a percent'''
self.value += (value * self.offset)
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
My issue is that the text is always drawn under rectangle. So when the rectangle reaches the text, you can't see the text anymore. How can I fix this? Thanks in advance.
The fact that one object sits atop another is called the stacking order. By default, objects created later have a higher stacking order than those that were created earlier. So, one solution is to draw the rectangle and then draw the text.
You can also move things up or down the stacking order using the lift and lower commands of a canvas. You must give it an id or tag of what you want to lift or lower, and optionally an id or tag of the object you want the first object(s) to be above or below.
So, for example, you could raise the text above the rectangle like this:
self.lift(self.text, self.bar)
If you want to get really fancy, you can create the notion of layers. I gave an example in another answer, here: https://stackoverflow.com/a/9576938/7432
In my programming class, we said put whatever text you don't want to be blocked drawn last. So put the text at the bottom of what function you are using to draw with