DEV Community

Paddy3118
Paddy3118

Posted on

Python: Turtle vs ipycanvas graphics

balls

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)