ipycanvas#

provides the Web-Canvas-API. However, there are some differences:

  • The Canvas widget exposes the CanvasRenderingContext2D API directly

  • The entire API is written in snake_case instead of camelCase, so for example canvas.fillStyle = 'red' written in Python becomes canvas.fill_style = 'red' in JavaScript.

Installation#

$ pipenv install ipycanvas
Installing ipycanvas…
…

Creating canvas elements#

Before we can start creating canvas elements, first a note about the canvas grid. The origin of a grid is in the upper left corner at the coordinate (0,0). All elements are placed relative to this origin.

There are four methods of drawing rectangles:

  • fill_rect(x, y, width, height=None) draws a filled rectangle

  • stroke_rect(x, y, width, height=None) draws a rectangular outline

  • fill_rects(x, y, width, height=None) draws filled rectangles

  • stroke_rects(x, y, width, height=None) draws rectangular outlines

With height=None, the same value is used as with width.

For *_rects, x, y, width and height are integers, lists of integers or numpy arrays.

[17]:
from ipycanvas import Canvas


canvas = Canvas(size=(120, 100))
canvas.fill_style = "lime"
canvas.stroke_style = "green"

canvas.fill_rect(10, 20, 100, 50)
canvas.stroke_rect(10, 20, 100, 50)

canvas
[18]:
from ipycanvas import MultiCanvas


# Create a multi-layer canvas with 2 layers
multi_canvas = MultiCanvas(2, size=(165, 115))
multi_canvas[0]  #  Access first layer (background)
multi_canvas[0].fill_style = "lime"
multi_canvas[0].stroke_style = "green"
multi_canvas[0].fill_rect(10, 20, 100, 50)
multi_canvas[0].stroke_rect(10, 20, 100, 50)

multi_canvas[1]  #  Access last layer
multi_canvas[1].fill_style = "red"
multi_canvas[1].stroke_style = "brown"
multi_canvas[1].fill_rect(55, 45, 100, 50)
multi_canvas[1].stroke_rect(55, 45, 100, 50)

multi_canvas
[19]:
import numpy as np

from ipycanvas import Canvas


n_particles = 75_000

x = np.array(np.random.rayleigh(350, n_particles), dtype=np.int32)
y = np.array(np.random.rayleigh(150, n_particles), dtype=np.int32)
size = np.random.randint(1, 3, n_particles)

canvas = Canvas(size=(1000, 500))

canvas.fill_style = "green"
canvas.fill_rects(x, y, size)

canvas

Since Canvas is an ipywidget, it can

  • appear several times in a notebook

  • change the attributes

  • Link changed attributes to other widget attributes

Delete canvas#

[20]:
from ipycanvas import Canvas


canvas = Canvas(size=(120, 100))
# Perform some drawings…
canvas.clear()
[21]:
from ipycanvas import Canvas


canvas = Canvas(size=(165, 115))

canvas.fill_style = "lime"
canvas.stroke_style = "brown"

canvas.fill_rect(10, 20, 100, 50)
canvas.clear_rect(52, 42, 100, 50)
canvas.stroke_rect(55, 45, 100, 50)

canvas

Shapes#

The available drawing commands are:

  • move_to(x, y):

  • line_to(x, y):

  • arc(x, y, radius, start_angle, end_angle, anticlockwise=False):

  • arc_to(x1, y1, x2, y2, radius):

  • quadratic_curve_to(cp1x, cp1y, x, y):

  • bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y):

  • rect(x, y, width, height):

Draw circles#

There are four different ways to draw circles:

  • fill_arc(x, y, radius, start_angle, end_angle, anticlockwise=False)

  • stroke_arc(x, y, radius, start_angle, end_angle, anticlockwise=False)

  • fill_arcs(x, y, radius, start_angle, end_angle, anticlockwise=False)

  • stroke_arcs(x, y, radius, start_angle, end_angle, anticlockwise=False)

With *_arcs, x, y and radius are NumPy arrays, lists or scalar values.

[22]:
from math import pi

from ipycanvas import Canvas


canvas = Canvas(size=(200, 200))

canvas.fill_style = "red"
canvas.stroke_style = "green"

canvas.fill_arc(60, 60, 50, 0, pi)
canvas.stroke_arc(60, 60, 40, 0, 2 * pi)

canvas

Drawing paths#

A path is a list of points connected by line segments that can be different shapes, straight or curved, closed or open, different widths and colors. The following functions are available:

begin_path ()
close_path () adds a straight line to the path leading to the beginning of the current path
stroke () draws the shape along the contour
fill (rule) draws the shape using a fill within the path
  • begin_path() creates a new path

  • close_path() adds a straight line to the path leading to the beginning of the current path

  • stroke() draws the shape along the contour

  • fill(rule) draws the shape using a fill within the path

[23]:
from ipycanvas import Canvas

canvas = Canvas(size=(100, 100))

# Draw simple triangle shape
canvas.begin_path()
canvas.move_to(75, 50)
canvas.line_to(100, 75)
canvas.line_to(100, 25)
canvas.fill()

canvas

Examples#

arc#

[24]:
from math import pi

from ipycanvas import Canvas


canvas = Canvas(size=(200, 200))

# Draw smiley face
canvas.begin_path()
canvas.arc(75, 75, 50, 0, pi * 2, True)  # Outer circle
canvas.move_to(110, 75)
canvas.arc(75, 75, 35, 0, pi, False)  # Mouth (clockwise)
canvas.move_to(65, 65)
canvas.arc(60, 65, 5, 0, pi * 2, True)  # Left eye
canvas.move_to(95, 65)
canvas.arc(90, 65, 5, 0, pi * 2, True)  # Right eye
canvas.stroke()

canvas

bezier_curve_to#

[25]:
from ipycanvas import Canvas


canvas = Canvas(size=(200, 200))

# Cubic curves example
canvas.begin_path()
canvas.move_to(75, 40)
canvas.bezier_curve_to(75, 37, 70, 25, 50, 25)
canvas.bezier_curve_to(20, 25, 20, 62.5, 20, 62.5)
canvas.bezier_curve_to(20, 80, 40, 102, 75, 120)
canvas.bezier_curve_to(110, 102, 130, 80, 130, 62.5)
canvas.bezier_curve_to(130, 62.5, 130, 25, 100, 25)
canvas.bezier_curve_to(85, 25, 75, 37, 75, 40)
canvas.fill()

canvas

Styles and colors#

Colors#

Canvas has two color attributes, one for strokes and one for areas; the transparency can also be changed.

  • stroke_style expects a valid HTML color. The default is black.

  • fill_style expects a valid HTML color. The default is black.

  • global_alpha indicates the transparency. The default is 1.0.

Lines#

Line style#

Lines can be described by the following attributes:

  • line_width

  • line_cap

  • line_join

  • miter_limit

  • get_line_dash()

  • set_line_dash(segments)

  • line_dash_offset

Line width#

[26]:
from ipycanvas import Canvas


canvas = Canvas(size=(400, 280))
canvas.scale(2)

for i in range(10):
    width = 1 + i
    x = 5 + i * 20
    canvas.line_width = width

    canvas.fill_text(str(width), x - 5, 15)

    canvas.begin_path()
    canvas.move_to(x, 20)
    canvas.line_to(x, 140)
    canvas.stroke()
canvas

Line end#

[27]:
from ipycanvas import Canvas


canvas = Canvas(size=(320, 360))

# Possible line_cap values
line_caps = ["butt", "round", "square"]

canvas.scale(2)

# Draw guides
canvas.stroke_style = "#09f"
canvas.begin_path()
canvas.move_to(10, 30)
canvas.line_to(140, 30)
canvas.move_to(10, 140)
canvas.line_to(140, 140)
canvas.stroke()

# Draw lines
canvas.stroke_style = "black"
canvas.font = "15px serif"

for i in range(len(line_caps)):
    line_cap = line_caps[i]
    x = 25 + i * 50

    canvas.fill_text(line_cap, x - 15, 15)
    canvas.line_width = 15
    canvas.line_cap = line_cap
    canvas.begin_path()
    canvas.move_to(x, 30)
    canvas.line_to(x, 140)
    canvas.stroke()

canvas

Line connection#

defines the appearance of the corners where lines meet.

[28]:
from ipycanvas import Canvas


canvas = Canvas(size=(320, 360))

# Possible line_join values
line_joins = ["round", "bevel", "miter"]

min_y = 40
max_y = 80
spacing = 45

canvas.line_width = 10
canvas.scale(2)
for i in range(len(line_joins)):
    line_join = line_joins[i]

    y1 = min_y + i * spacing
    y2 = max_y + i * spacing

    canvas.line_join = line_join

    canvas.fill_text(line_join, 60, y1 - 10)

    canvas.begin_path()
    canvas.move_to(-5, y1)
    canvas.line_to(35, y2)
    canvas.line_to(75, y1)
    canvas.line_to(115, y2)
    canvas.line_to(155, y1)
    canvas.stroke()

canvas

Line pattern#

[29]:
from ipycanvas import Canvas


canvas = Canvas(size=(400, 280))
canvas.scale(2)

line_dashes = [[5, 10], [10, 5], [5, 10, 20], [10, 20], [20, 10], [20, 20]]

canvas.line_width = 2

for i in range(len(line_dashes)):
    x = 5 + i * 20

    canvas.set_line_dash(line_dashes[i])
    canvas.begin_path()
    canvas.move_to(x, 0)
    canvas.line_to(x, 140)
    canvas.stroke()
canvas

text#

There are two methods of designing text:

  • fill_text(text, x, y, max_width=None)

  • stroke_text(text, x, y, max_width=None)

[30]:
from ipycanvas import Canvas


canvas = Canvas(size=(400, 50))

canvas.font = "32px serif"
canvas.fill_text("Drawing from Python is cool!", 10, 32)
canvas
[31]:
from ipycanvas import Canvas


canvas = Canvas(size=(400, 50))

canvas.font = "32px serif"
canvas.stroke_text("Hello There!", 10, 32)
canvas

font indicates the current text style. The default value is “12px sans-serif”. text_align specifies the text alignment. Possible values are “start”, “end”, “left”, “right” or “center”. text_baseline indicates the alignment with the baseline. Possible values are “top”, “hanging”, “middle”, “alphabetic”, “ideographic” and “bottom”. The default value is “alphabetic”. direction indicates the text direction. Possible values are “ltr”, “rtl”, “inherit”. The default value is “inherit”.

Of course, fill_style and stroke_style can also be used to color the text.

  • font indicates the current text style. The default value is "12px sans-serif".

  • text_align specifies the text alignment. Possible values are "start", "end", "left", "right" or "center" .

  • text_baseline indicates the alignment with the baseline. Possible values are "top", "hanging", "middle", "alphabetic", "ideographic" and "bottom". The default value is "alphabetic".

  • direction indicates the text direction. Possible values are "ltr", "rtl", "inherit". The default value is "inherit".

Of course, fill_style and stroke_style can also be used to color the text.

Images#

… from a Numpy array#

With put_image_data(image_data, dx, dy) an image can be displayed, where image_data specifies a Numpy array and dx and dy the upper left corner of the image.

[32]:
import numpy as np

from ipycanvas import Canvas


x = np.linspace(-1, 1, 600)
y = np.linspace(-1, 1, 600)

x_grid, y_grid = np.meshgrid(x, y)

blue_channel = np.array(
    np.sin(x_grid**2 + y_grid**2) * 255, dtype=np.int32
)
red_channel = np.zeros_like(blue_channel) + 200
green_channel = np.zeros_like(blue_channel) + 50

image_data = np.stack((red_channel, blue_channel, green_channel), axis=2)

canvas = Canvas(size=(image_data.shape[0], image_data.shape[1]))
canvas.put_image_data(image_data, 0, 0)

canvas

Status#

The status can be specified with two values:

  • save() saves the status of the canvas element.

  • restore() restores the last saved status of the canvas element. This method can be called as often as required.

Transformations#

  • translate(x, y) moves the canvas element

  • rotate(angle) rotates the canvas element clockwise

  • scale(x, y=None) scales the canvas element

See also: