I had to create some graphics to explain a problem in a blog post - I needed royalty free pictures of different balls in a row. I couldn't find it so wrote a Python turtle graphics program to do the job.
The Turtle graphics version:
from turtle import width, Vec2D, pencolor, penup, pendown, goto, fillcolor, \
circle, begin_fill, end_fill, write, hideturtle, title, done
title('4 CIRCLES')
COLORS = ['yellow', 'green', 'blue', 'red']
c10 = 'red orange yellow green blue indigo violet cyan aquamarine purple'.split()
num2colour = {n: c for n, c in enumerate(c10)}
def turtle_balls(colour_numbers, position, radius,
underline=True, show_num=True, pen_width=0):
offset = Vec2D(2 * radius, 0)
offset_n = Vec2D(0, radius / 2)
if underline:
u_start = position - offset
u_end = position + offset * (len(colour_numbers) + 0)
pencolor('black')
fillcolor('black')
width(3)
penup()
goto(u_start)
pendown()
goto(u_end)
penup()
width(pen_width)
for cnum in colour_numbers:
color = num2colour[cnum % 10]
if pen_width == 0:
pencolor(color)
penup()
goto(position)
pendown()
fillcolor(color)
begin_fill()
circle(radius)
end_fill()
if show_num:
# goto(position + offset_n)
# pencolor('white')
# write(str(cnum), align='center', font=("Arial", 18, "bold"))
goto(position + offset_n)
pencolor('black')
write(str(cnum), align='center', font=("Arial", 16, "normal"))
position += offset
turtle_balls(range(10), Vec2D(-100, 0), 25)
hideturtle()
done()
Things change
I decided that it would be best to use Jupyter so hunted around for a Jupyter compatible turtle graphics module. There was one, but I also came across ipycanvas which initially intrigued me as it stated it was a thin shell over HTML canvas.
The ipycanvas documentation does show it can do a wealth of things, but knowing what I wanted to do from the turtle program helped me focus on what I needed to do.
I liked how in ipycanvas the canvas.fill_*
routines accepted their own coordinates rather than having to first move somewhere - the ethos of turtle graphics (along with penup/down).
I developed the ipycanvas routine in Jupyter as a function. Exported it as a python program then tidied it up and now just import it as a module.
The ipycanvas version
#!/usr/bin/env python
# coding: utf-8
"""
Created on Tue Jan 19 11:10:25 2021
@author: Paddy3118
"""
# In[1]:
from ipycanvas import Canvas
c10 = 'red orange yellow green blue lime violet tan aqua purple'.split() # CSS colours
_num2colour = {n: c for n, c in enumerate(c10)}
def ball_graphic(colour_numbers=[0, 1, 2, 3, 4], title='', radius=25, position=(40, 60),
underline=True, show_num=False, show_colour_name=False, show_list=False):
"""
Global _num2colour maps integers 0 to 9 to valid CSS colour names.
Function creates a picture of several balls on a line.
colour_numbers is list/tuple/range of integers representing an ordered collection
of balls by their number which must be a key in _num2colour.
underline default True, shows the line the balls rest on.
show_num default False, shows ball colour number inside each ball.
show_colour_name default False, shows the balss colour name above each ball.
show_list default False, adds a list format of the colour_numbers.
"""
global _num2colour
ht = 100 if not show_list else 150
canvas = Canvas(width=750, height=ht)
x0, y0 = position
if title:
canvas.font = 'bold 16px serif'
canvas.text_baseline = 'middle'
canvas.text_align = 'left'
canvas.fill_text(title, x0 - radius, y0 - radius - 27)
if underline:
canvas.fill_rect(x0 - radius, y0 + radius, x0 + radius * 2 * len(colour_numbers), 4)
for i, cnum in enumerate(colour_numbers):
xx = x0 + i * radius *2
colour = _num2colour[cnum % 10]
if colour == 'blue':
canvas.fill_style = 'dodgerblue'
else:
canvas.fill_style = colour
canvas.fill_circle(xx, y0, radius)
if show_num:
canvas.fill_style = 'black'
canvas.font = 'bold 16px serif'
canvas.text_baseline = 'middle'
canvas.text_align = 'center'
canvas.fill_text(str(cnum), xx, y0)
if show_colour_name:
canvas.fill_style = 'black'
canvas.font = '12px serif'
canvas.text_baseline = 'middle'
canvas.text_align = 'center'
canvas.fill_text(colour.capitalize(), xx, y0 - radius - 6)
if show_list:
canvas.font = '14px serif'
canvas.text_baseline = 'middle'
canvas.text_align = 'left'
canvas.fill_text(f"colour_numbers = {list(colour_numbers)}", x0 - radius, y0 + radius * 2.5)
return canvas
if __name__ == "__main__":
title = "All the ball numbers, colours, and options"
c = ball_graphic(range(10), title=title,
show_num=True, show_colour_name=True, underline=True, show_list=True)
display(c)
# In[2]:
if __name__ == "__main__":
c = ball_graphic([2,2,1,1,1,2,1], title="Some arrangement of balls",
show_num=True, show_colour_name=False, underline=True, show_list=True)
display(c)
Conclusion
ipycanvas isn't too difficult to get into and if all you want is to recreate something you could do in turtle graphics then the learning curve isn't that different (but I guess I didn't try and learn it all before starting).
It seems that ipycanvas is cementing my thoughts that constraining needs for rich-text and graphical output to what can be accomplished in a browser is the way to go!
- Paddy.
Top comments (0)