Interactive SVG

Open in Colab

This notebook demostrates how to develope components with direct manipulations. This example implements the Command pattern using PyGlove patcher, which allows instructions from the commandline be easily applied on symbolic objects.

!pip install pyglove
import pyglove as pg
import abc
from colabtools import publish

def color_spec():
  return pg.typing.Enum(
      'white', ['white', 'black', 'yellow', 'blue', 'red', 'green'])

@pg.members([
  ('fill', color_spec()),
  ('stroke', color_spec()),
  ('stroke_width', pg.typing.Int(min_value=1))
])
class Shape(pg.Object):
  
  TAG = None
  
  def _on_init(self):
    _CANVAS.shapes.append(self)

  def __del__(self):
    _CANVAS.shapes.remove(self)
    super().__del__(self)

  def to_svg(self):
    return self._to_svg(self.TAG, **self.sym_init_args)

  def _to_svg(self, tag_name, **kwargs):
    svg = f'<{tag_name}'
    for k, v in kwargs.items():
      svg += f' {k}="{v}"'
    svg += '/>'
    return svg


@pg.members([
  ('cx', pg.typing.Int()),
  ('cy', pg.typing.Int()),
  ('r', pg.typing.Int(min_value=1))
])
class Circle(Shape):
  TAG = 'circle'


@pg.members([
   ('shapes', pg.typing.List(pg.typing.Object(Shape), default=[]))
])
class Canvas(pg.Object):

  def _on_bound(self):
    super()._on_bound()
    if self.shapes:
      self.render()

  def render(self):
    svg = '<html><body><svg>\n'
    for s in self.shapes:
      svg += s.to_svg() + '\n'
      svg += '</svg></body></html>'
    publish.html(svg)

_CANVAS = Canvas()
# Create a circle and render it using SVG.
circle = Circle(cx=50, cy=50, r=25, stroke='blue', stroke_width=4, fill='white')

Let’s create patcher to move a circle by command. As a result, we can use URI-like string to manipulate a circle object.

@pg.patcher([
  ('x', pg.typing.Int()),
  ('y', pg.typing.Int()),
])
def move(circle, x, y):
  return {
      'cx': circle.cx + x,
      'cy': circle.cy + y,
  }

def action(shape, command):
  pg.patch(shape, [command])
  

Invoke the patcher to move the circle:

action(circle, 'move?x=50&y=20')
action(circle, 'move?x=50&y=0')
action(circle, 'move?x=50&y=0')
del circle