ipycanvas Documentation

repository·master·Indexed 20 days ago

https://github.com/jupyter-widgets-contrib/ipycanvas

An interactive widgets library that provides a Python interface to the browser's Canvas API for Jupyter notebooks and JupyterLab. It enables high-performance drawing of primitives like text, lines, polygons, and images. Key features include the MultiCanvas for layered surfaces, the hold_canvas context manager for batched updates and smooth animations, and vectorized drawing methods using NumPy for optimized performance.

Tokens
45.7K
Snippets
151
Records
175
Agent score
70%

What's inside ipycanvas

  1. Overview of ipycanvas

    master
    ipycanvas is a lightweight, fast, and stable library that exposes the browser's Canvas API to IPython. It allows developers to draw primitives—such as text, lines, polygons, arcs, and images—directly from Python code within a Jupyter environment. This enables the creation of interactive graphics, custom plotting libraries, and complex animations entirely in Python.
  2. Manage canvas state with save() and restore()

    master

    ipycanvas uses a stack-based system to manage drawing states. This allows you to temporarily change canvas attributes or transformations and then revert to a previous state without manually resetting every property.

    • save(): Pushes the current drawing state onto the stack. The state includes all active transformations (translate, rotate, scale) and specific drawing attributes.
    • restore(): Pops the most recently saved state from the stack and restores the canvas to that state.

    Saved Attributes

    A state saved via save() includes the current values of:

    • Transformations: All applied translations, rotations, and scales.
    • Styles: stroke_style, fill_style, global_alpha, global_composite_operation.
    • Line Properties: line_width, line_cap, line_join, miter_limit, line_dash_offset.
    • Text Properties: font, text_align, text_baseline, direction.

    You can nest these calls to create complex drawing workflows where different sections of a drawing use different settings.

    # Example pattern for state management
    canvas.save()
    # Apply transformations and styles
    canvas.translate(10, 10)
    canvas.fill_style = 'red'
    canvas.fill_rect(0, 0, 50, 50)
    canvas.restore()
    
    # The canvas is now back to its original state (no translation, original fill_style)
    canvas.fill_rect(0, 0, 50, 50)
  3. Use styled vectorized methods for batch drawing

    master

    Ipycanvas provides specialized 'styled' methods to draw multiple shapes simultaneously, where each shape can have its own unique color and alpha (transparency) value. This is highly efficient for rendering large numbers of objects using NumPy arrays.

    Available styled methods include:

    • Rectangles: fill_styled_rects, stroke_styled_rects
    • Circles: fill_styled_circles, stroke_styled_circles
    • Arcs: fill_styled_arcs, stroke_styled_arcs
    • Polygons: fill_styled_polygons, stroke_styled_polygons
    • Line Segments: fill_styled_line_segments, stroke_styled_line_segments
    import numpy as np
    from ipycanvas import Canvas, hold_canvas
    
    canvas = Canvas(width=400, height=300)
    n_rects = 300
    x = np.random.randint(0, canvas.width, size=(n_rects))
    y = np.random.randint(0, canvas.width, size=(n_rects))
    width = np.random.randint(10, 40, size=(n_rects))
    height = np.random.randint(10, 40, size=(n_rects))
    colors_fill = np.random.randint(0, 255, size=(n_rects, 3))
    colors_outline = np.random.randint(0, 255, size=(n_rects, 3))
    alphas = np.random.random(n_rects)
    
    with hold_canvas():
        canvas.fill_styled_rects(x, y, width, height, color=colors_fill, alpha=alphas)
        canvas.line_width = 2
        canvas.stroke_styled_rects(x, y, width, height, color=colors_outline, alpha=alphas)
  4. Understanding the ipycanvas coordinate system and API style

    master

    The ipycanvas widget exposes the Web Canvas API, but with two key differences for Python users:

    1. Naming Convention: All API methods use snake_case instead of the JavaScript camelCase. For example, canvas.fill_style = 'red' in Python replaces canvas.fillStyle = 'red' in JavaScript.
    2. Coordinate System: The canvas grid origin (0,0) is located at the top left corner. All coordinates are relative to this origin. An element at (x, y) is x pixels from the left and y pixels from the top.

    For more detailed information on the underlying API, refer to the MDN Canvas API documentation.

  5. Understand the ipycanvas API differences from Web Canvas API

    master

    While ipycanvas exposes the Web Canvas API, it follows Python conventions and includes specific helper methods. Key differences to note:

    1. Naming Convention: All API methods and properties use snake_case instead of the JavaScript camelCase. For example, use canvas.fill_style = 'red' instead of canvas.fillStyle = 'red'.
    2. Clear Shortcut: The canvas.clear() method is a convenient shortcut for canvas.clear_rect(0, 0, canvas.width, canvas.height).
    3. Enhanced put_image_data: Unlike the standard Web canvas putImageData method, ipycanvas.Canvas.put_image_data supports transparency and the current transformation state.
    4. Performance Optimization: Use the hold_canvas context manager when performing a large number of drawing commands at once to improve performance.
  6. Enable image synchronization for Canvas retrieval

    master

    By default, ipycanvas does not synchronize the image state between the TypeScript front-end and the Python back-end to maintain high performance. To use methods like to_file() or get_image_data(), you must explicitly enable synchronization by setting sync_image_data=True during the initialization of your Canvas or MultiCanvas object.

    Once you have finished retrieving the image data, you can set sync_image_data back to False to restore performance.

    from ipycanvas import Canvas
    
    # Must set sync_image_data=True to retrieve image data
    canvas = Canvas(width=200, height=200, sync_image_data=True)
  7. Draw shapes using Path commands

    master

    To create custom shapes from scratch, follow this lifecycle:

    1. Call begin_path() to start a new path.
    2. Use drawing commands (like line_to, arc, or bezier_curve_to) to build the path.
    3. Call stroke() to draw the outline or fill(rule) to fill the interior.

    Fill Rules: When calling fill(rule), you can specify:

    • nonzero (default)
    • evenodd
    from ipycanvas import Canvas
    
    canvas = Canvas(width=100, height=100)
    
    # 1. Start the path
    canvas.begin_path()
    # 2. Add segments
    canvas.move_to(75, 50)
    canvas.line_to(100, 75)
    canvas.line_to(100, 25)
    # 3. Render
    canvas.fill()
    
    canvas
  8. Handle asynchronous image retrieval in a single Notebook cell

    master

    Because drawing commands are processed asynchronously, you should use the observe method to wait for the image_data trait to update before attempting to save a file or process a NumPy array. This allows you to keep all setup, drawing, and retrieval logic within a single Notebook cell.

    Pattern for saving to file:

    from ipycanvas import Canvas
    
    canvas = Canvas(width=200, height=200, sync_image_data=True)
    
    def save_to_file(*args, **kwargs):
        canvas.to_file("my_file.png")
    
    canvas.observe(save_to_file, "image_data")
    # Perform drawings here...

    Pattern for retrieving NumPy array:

    from ipycanvas import Canvas
    
    canvas = Canvas(width=200, height=200, sync_image_data=True)
    
    def get_array(*args, **kwargs):
        arr = canvas.get_image_data()
        # Do something with arr
    
    canvas.observe(get_array, "image_data")
    # Perform drawings here...
  9. Create a Canvas or MultiCanvas

    master

    You can create a single drawing surface using Canvas or a multi-layered surface using MultiCanvas. MultiCanvas is useful for separating static backgrounds from frequently updated foreground objects. Layers in a MultiCanvas can be accessed via index notation.

    from ipycanvas import Canvas
    
    # Create a single canvas
    canvas = Canvas(width=200, height=200)
  10. Watch and run ipycanvas during development

    master

    When developing the extension in JupyterLab, you can use jlpm run watch in one terminal to monitor the source directory and automatically rebuild the extension when changes are detected. Use a second terminal to run jupyter lab.

    # Watch the source directory in one terminal, automatically rebuilding when needed
    jlpm run watch
    
    # Run JupyterLab in another terminal
    jupyter lab
  11. Create an animation loop using the 'slow' approach

    master

    For most use cases, you can create an animation loop using a standard Python for or while loop combined with time.sleep().

    Key Requirements:

    • Use the hold_canvas context manager inside the loop to wrap your drawing commands. This is essential for performance as it batches updates.
    • Call canvas.clear() at the start of each iteration to remove the previous frame.
    • Use time.sleep() to control the animation frequency (e.g., sleep(0.02) for ~50Hz).

    Limitations: This approach may suffer from latency if the Jupyter server and the client are on different machines (e.g., using MyBinder).

    from time import sleep
    from ipycanvas import Canvas, hold_canvas
    
    canvas = Canvas()
    display(canvas)
    
    # Number of steps in your animation
    steps_number = 200
    
    for i in range(steps_number):
        with hold_canvas():
            # Clear the old animation step
            canvas.clear()
    
            # Perform all your drawings here
            # ...
    
        # Animation frequency ~50Hz = 1./50. seconds
        sleep(0.02)