ipycc.sketch

   1import math
   2import time
   3from typing import Callable, Self
   4from ipycanvas import Canvas, hold_canvas
   5from IPython.display import display
   6import numpy as np
   7
   8
   9class SketchError(Exception):
  10    """Some Sketch Error.
  11    """
  12
  13
  14class Sketch:
  15    """A class to describe a 2D drawing canvas."""
  16
  17    # Constants
  18    HALF_PI: float = math.pi / 2.0
  19    """A number constant that's approximately 1.5708."""
  20
  21    PI: float = math.pi
  22    """A number constant that's approximately 3.1416."""
  23
  24    QUARTER_PI: float = math.pi / 4.0
  25    """A number constant that's approximately 0.7854."""
  26
  27    TAU: float = math.tau
  28    """A number constant that's approximately 6.2382."""
  29
  30    TWO_PI: float = math.tau
  31    """A number constant that's approximately 6.2382."""
  32
  33    NORMAL: str = "normal"
  34    """A string constant used with the `text_style()` method."""
  35
  36    ITALIC: str = "italic"
  37    """A string constant used with the `text_style()` method."""
  38
  39    BOLD: str = "bold"
  40    """A string constant used with the `text_style()` method."""
  41
  42    BOLDITALIC: str = "bolditalic"
  43    """A string constant used with the `text_style()` method."""
  44
  45    LEFT: str = "left"
  46    """A string constant used with the `text_align()` method."""
  47
  48    CENTER: str = "center"
  49    """A string constant used with the `text_align()` method."""
  50
  51    RIGHT: str = "right"
  52    """A string constant used with the `text_align()` method."""
  53
  54    BOTTOM: str = "bottom"
  55    """A string constant used with the `text_align()` method."""
  56
  57    TOP: str = "top"
  58    """A string constant used with the `text_align()` method."""
  59
  60    BASELINE: str = "alphabetic"
  61    """A string constant used with the `text_align()` method."""
  62
  63    MIDDLE: str = "middle"
  64    """A string constant used with the `text_align()` method."""
  65
  66    _DEFAULT_STROKE = "black"
  67    _DEFAULT_STROKE_WEIGHT = 1
  68    _DEFAULT_LINE_CAP = "round"
  69    _DEFAULT_FILL = "white"
  70    _DEFAULT_TEXT_FILL = "black"
  71    _DEFAULT_TEXT_WEIGHT = 0.3
  72    _DEFAULT_TEXT_ALIGN = LEFT
  73    _DEFAULT_TEXT_BASELINE = BASELINE
  74    _TRANSPARENT = "#00000000"
  75    _DEFAULT_FONT = "Arial"
  76    _DEFAULT_FONT_SIZE = 12
  77    _DEFAULT_FONT_STYLE = "normal"
  78    _DEFAULT_FONT_WEIGHT = "normal"
  79
  80    def __init__(
  81        self,
  82        width: int = 100,
  83        height: int = 100,
  84        pixel_denstiy: int = 2,
  85    ):
  86        # Create the Canvas.
  87        self.width: int = width
  88        """The width of the canvas in pixels."""
  89
  90        self.height: int = height
  91        """The height of the canvas in pixels."""
  92
  93        self.pixel_density: int = pixel_denstiy
  94        """The number of physical pixels used to draw a pixel on the canvas."""
  95
  96        self.canvas: Canvas = Canvas(
  97            width=width * pixel_denstiy,
  98            height=height * pixel_denstiy,
  99            layout={"width": f"{width}px", "height": f"{height}px"})
 100        """The `Canvas` widget used for drawing."""
 101        
 102        # Set default the styles.
 103        self._init_style()
 104        # Set the default transformations.
 105        self._init_transformation()
 106        # Create an empty list for shape vertices.
 107        self._vertices = []
 108        # Set the current frame count (for animation).
 109        self.frame_count: int = 0
 110        """The number of frames drawn since the sketch started."""
 111
 112        self._is_looping = False
 113
 114    def _init_transformation(self):
 115        sx = self.pixel_density
 116        sy = self.pixel_density
 117        self._matrix = np.array([[sx,  0,  0],
 118                                 [ 0, sy,  0],
 119                                 [ 0,  0,  1]], dtype=float)
 120        self.canvas.scale(self.pixel_density, y=self.pixel_density)
 121
 122    def _init_style(self):
 123        # Set initial styles.
 124        self.canvas.fill_style = Sketch._DEFAULT_FILL
 125        self.canvas.stroke_style = Sketch._DEFAULT_STROKE
 126        self.canvas.line_width = Sketch._DEFAULT_STROKE_WEIGHT
 127        self.canvas.line_cap = Sketch._DEFAULT_LINE_CAP
 128        self._is_fill_set = False
 129        self._is_stroke_set = False
 130        self._is_stroke_weight_set = False
 131        self._font = Sketch._DEFAULT_FONT
 132        self._font_size = Sketch._DEFAULT_FONT_SIZE
 133        self._font_style = Sketch._DEFAULT_FONT_STYLE
 134        self._font_weight = Sketch._DEFAULT_FONT_WEIGHT
 135        self._text_align = Sketch._DEFAULT_TEXT_ALIGN
 136        self._text_baseline = Sketch._DEFAULT_TEXT_BASELINE
 137        self.canvas.text_align = self._text_align
 138        self.canvas.text_baseline = self._text_baseline
 139        self.canvas.font = (
 140            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
 141        )
 142
 143    # ========================================
 144    #                 Color
 145    # ========================================
 146
 147    def background(self, *args):
 148        """Sets the color used for the background of the canvas.
 149
 150        The version of `background()` with one parameter interprets the value
 151        one of four ways. If the parameter is an int or float, it's
 152        interpreted as a grayscale value. If the parameter is a string,
 153        it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex,
 154        and named color strings are supported.
 155
 156        The version of `background()` with two parameters interprets the first
 157        one as a grayscale value. The second parameter sets the alpha
 158        (transparency) value.
 159
 160        The version of `background()` with three parameters interprets them as
 161        RGB. Calling `background(255, 204, 0)` sets the background a bright
 162        yellow color.
 163
 164        The version of `background()` with four parameters interprets them as
 165        RGBA. Calling `background(255, 204, 0, 20)` sets the background a
 166        bright yellow color that is transparent.
 167
 168        **Example**
 169        ```python
 170        from ipycc.sketch import Sketch
 171
 172        s = Sketch()
 173        s.show()
 174        ```
 175
 176        ```python
 177        # A grayscale value.
 178        s.background(51)
 179        ```
 180
 181        ```python
 182        # A grayscale value and an alpha value.
 183        s.background(51, 0.4)
 184        ```
 185
 186        ```python
 187        # R, G & B values.
 188        s.background(255, 204, 0)
 189        ```
 190
 191        ```python
 192        # A CSS named color.
 193        s.background("red")
 194        ```
 195
 196        ```python
 197        # Integer RGBA notation.
 198        s.background("rgba(0, 255, 0, 0.25)")
 199        ```
 200
 201        ```python
 202        # R, G, B & A values.
 203        s.background(0, 255, 0, 64)
 204        ```
 205        """
 206        if len(args) == 0:
 207            return
 208        color = self._colorstr(*args)
 209        self.canvas.save()
 210        self.canvas.reset_transform()
 211        self.canvas.scale(self.pixel_density, y=self.pixel_density)
 212        old_fill = self.canvas.fill_style
 213        old_stroke = self.canvas.stroke_style
 214        old_weight = self.canvas.line_width
 215        self.canvas.fill_style = color
 216        self.canvas.stroke_style = color
 217        self.canvas.line_width = 1
 218        self.canvas.fill_rect(0, 0, self.canvas.width, self.canvas.height)
 219        self.canvas.stroke_rect(0, 0, self.canvas.width, self.canvas.height)
 220        self.canvas.fill_style = old_fill
 221        self.canvas.stroke_style = old_stroke
 222        self.canvas.line_width = old_weight
 223        self.canvas.restore()
 224
 225    def _colorstr(self, *args) -> str:
 226        """Return a CSS color string corresponding to args.
 227        """
 228        num = (int, float)
 229        color = ""
 230        if len(args) == 1:
 231            c = args[0]
 232            if isinstance(c, num):
 233                color = f"rgb({c}, {c}, {c})"
 234            elif isinstance(color, str):
 235                color = c
 236        elif len(args) == 2:
 237            c = args[0]
 238            a = args[1]
 239            if isinstance(c, num) and isinstance(a, num):
 240                color = f"rgba({c}, {c}, {c}, {a / 255})"
 241        elif len(args) == 3:
 242            r = args[0]
 243            g = args[1]
 244            b = args[2]
 245            if isinstance(r, num) and isinstance(g, num) and isinstance(b, num):
 246                color = f"rgb({r}, {g}, {b})"
 247        elif len(args) == 4:
 248            r = args[0]
 249            g = args[1]
 250            b = args[2]
 251            a = args[3]
 252            if isinstance(r, num) and isinstance(g, num) and isinstance(b, num) and isinstance(a, num):
 253                color = f"rgba({r}, {g}, {b}, {a / 255})"
 254        return color
 255
 256    def fill(self, *args):
 257        """Sets the color used to fill shapes.
 258
 259        Calling `fill(255, 165, 0)` or `fill("orange")` means all shapes drawn
 260        after calling `fill()` will be filled with the color orange.
 261
 262        The version of `fill()` with one parameter interprets the value one of
 263        three ways. If the parameter is an int or float, it's interpreted as a
 264        grayscale value. If the parameter is a string, it's interpreted as a
 265        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
 266        are supported.
 267
 268        The version of `fill()` with two parameters interprets the first one
 269        as a grayscale value. The second parameter sets the alpha
 270        (transparency) value.
 271
 272        The version of `fill()` with three parameters interprets them as RGB
 273        colors.
 274
 275        The version of `fill()` with four parameters interprets them as RGBA
 276        colors. The last parameter sets the alpha (transparency) value.
 277
 278        **Example**
 279        ```python
 280        from ipycc.sketch import Sketch
 281
 282        s = Sketch()
 283        s.show()
 284        ```
 285
 286        ```python
 287        # A grayscale value.
 288        s.background(200)
 289        s.no_stroke()
 290        s.fill(51)
 291        s.square(20, 20, 60)
 292        ```
 293
 294        ```python
 295        # Grayscale and alpha values.
 296        s.background(200)
 297        s.no_stroke()
 298        s.fill(51, 64)
 299        s.square(20, 20, 60)
 300        ```
 301
 302        ```python
 303        # R, G & B values.
 304        s.background(200)
 305        s.no_stroke()
 306        s.fill(255, 204, 0)
 307        s.square(20, 20, 60)
 308        ```
 309
 310        ```python
 311        # A CSS named color.
 312        s.background(200)
 313        s.no_stroke()
 314        s.fill("red")
 315        s.square(20, 20, 60)
 316        ```
 317
 318        ```python
 319        # Integer RGBA notation.
 320        s.background(200)
 321        s.no_stroke()
 322        s.fill("rgba(0, 255, 0, 0.25)")
 323        s.square(20, 20, 60)
 324        ```
 325
 326        ```python
 327        # R, G, B & A values.
 328        s.background(200)
 329        s.no_stroke()
 330        s.fill(0, 255, 0, 64)
 331        s.square(20, 20, 60)
 332        ```
 333        """
 334        if len(args) == 0:
 335            return
 336        color = self._colorstr(*args)
 337        if not self._is_fill_set:
 338            self._is_fill_set = True
 339        self.canvas.fill_style = color
 340
 341    def no_fill(self):
 342        """Disables setting the fill color for shapes.
 343
 344        **Example**
 345        ```python
 346        from ipycc.sketch import Sketch
 347
 348        s = Sketch()
 349        s.show()
 350
 351        s.background(200)
 352        s.no_stroke()
 353        s.square(20, 20, 60)
 354        ```
 355        """
 356        if not self._is_fill_set:
 357            self._is_fill_set = True
 358        self.canvas.fill_style = Sketch._TRANSPARENT
 359
 360    def no_stroke(self):
 361        """Disables drawing points, lines, and the outlines of shapes.
 362
 363        **Example**
 364        ```python
 365        from ipycc.sketch import Sketch
 366
 367        s = Sketch()
 368        s.show()
 369
 370        s.background(200)
 371        s.no_stroke()
 372        s.square(20, 20, 60)
 373        ```
 374        """
 375        if not self._is_stroke_set:
 376            self._is_stroke_set = True
 377        self.canvas.stroke_style = Sketch._TRANSPARENT
 378
 379    def stroke(self, *args):
 380        """Sets the color used to draw points, lines, and the outlines of shapes.
 381
 382        Calling `stroke(255, 165, 0)` or `stroke("orange")` means all shapes
 383        drawn after calling `stroke()` will be outlined with the color orange.
 384
 385        The version of `stroke()` with one parameter interprets the value one
 386        of three ways. If the parameter is a number, it's interpreted as a
 387        grayscale value. If the parameter is a string, it's interpreted as a
 388        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
 389        are supported.
 390
 391        The version of `stroke()` with two parameters interprets the first one
 392        as a grayscale value. The second parameter sets the alpha
 393        (transparency) value.
 394
 395        The version of `stroke()` with three parameters interprets them as RGB
 396        colors.
 397
 398        The version of `stroke()` with four parameters interprets them as RGBA
 399        colors. The last parameter sets the alpha (transparency) value.
 400
 401        **Example**
 402        ```python
 403        from ipycc.sketch import Sketch
 404
 405        s = Sketch()
 406        s.show()
 407        ```
 408
 409        ```python
 410        # A grayscale value.
 411        s.background(200)
 412        s.stroke_weight(4)
 413        s.stroke(51)
 414        s.square(20, 20, 60)
 415        ```
 416
 417        ```python
 418        # Grayscale and alpha values.
 419        s.background(200)
 420        s.stroke(51, 64)
 421        s.square(20, 20, 60)
 422        ```
 423
 424        ```python
 425        # R, G & B values.
 426        s.background(200)
 427        s.stroke(255, 204, 0)
 428        s.square(20, 20, 60)
 429        ```
 430
 431        ```python
 432        # A CSS named color.
 433        s.background(200)
 434        s.stroke("red")
 435        s.square(20, 20, 60)
 436        ```
 437
 438        ```python
 439        # Integer RGBA notation.
 440        s.background(200)
 441        s.stroke("rgba(0, 255, 0, 0.25)")
 442        s.square(20, 20, 60)
 443        ```
 444
 445        ```python
 446        # R, G, B & A values.
 447        s.background(200)
 448        s.stroke(0, 255, 0, 64)
 449        s.square(20, 20, 60)
 450        ```
 451        """
 452        if len(args) == 0:
 453            return
 454        color = self._colorstr(*args)
 455        if not self._is_stroke_set:
 456            self._is_stroke_set = True
 457        self.canvas.stroke_style = color
 458
 459    def clear(self):
 460        """Clears all drawings on the canvas.
 461
 462        **Example**
 463        ```python
 464        from ipycc.sketch import Sketch
 465
 466        s = Sketch()
 467        s.show()
 468
 469        s.background(200)
 470        s.clear()
 471        ```
 472        """
 473        self.canvas.clear()
 474
 475    def reset(self):
 476        """Resets the canvas to its default state.
 477
 478        **Example**
 479        ```python
 480        from ipycc.sketch import Sketch
 481
 482        s = Sketch()
 483        s.show()
 484
 485        s.background(200)
 486        s.reset()
 487        ```
 488        """
 489        self.clear()
 490        self.canvas.reset_transform()
 491        self._init_style()
 492        self._init_transformation()
 493
 494    # ========================================
 495    #              2D Primitives
 496    # ========================================
 497
 498    def _acute_arc_to_bezier(self, start: int | float, size: int | float) -> dict:
 499        # Evaluate constants.
 500        alpha = size * 0.5
 501        cos_alpha = math.cos(alpha)
 502        sin_alpha = math.sin(alpha)
 503        cot_alpha = 1 / math.tan(alpha)
 504        # This is how far the arc needs to be rotated.
 505        phi = start + alpha
 506        cos_phi = math.cos(phi)
 507        sin_phi = math.sin(phi)
 508        lam = (4.0 - cos_alpha) / 3
 509        mu = sin_alpha + (cos_alpha - lam) * cot_alpha
 510
 511        # Return rotated waypoints.
 512        return {
 513            "ax": round(math.cos(start), 7),
 514            "ay": round(math.sin(start), 7),
 515            "bx": round((lam * cos_phi + mu * sin_phi), 7),
 516            "by": round((lam * sin_phi - mu * cos_phi), 7),
 517            "cx": round((lam * cos_phi - mu * sin_phi), 7),
 518            "cy": round((lam * sin_phi + mu * cos_phi), 7),
 519            "dx": round(math.cos(start + size), 7),
 520            "dy": round(math.sin(start + size), 7),
 521        }
 522
 523    def arc(
 524        self,
 525        x: int | float,
 526        y: int | float,
 527        w: int | float,
 528        h: int | float,
 529        start: int | float,
 530        stop: int | float,
 531    ):
 532        """Draws an arc.
 533
 534        An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
 535        `h` parameters. `x` and `y` set the location of the arc's center. `w`
 536        and `h` set the arc's width and height.
 537        
 538        The fifth and sixth parameters, `start` and `stop`, set the angles
 539        between which to draw the arc. Arcs are always drawn clockwise from
 540        `start` to `stop`.
 541
 542        **Example**
 543        ```python
 544        from ipycc.sketch import Sketch
 545
 546        s = Sketch()
 547        s.show()
 548
 549        s.background(200)
 550
 551        # Bottom-right.
 552        s.arc(50, 55, 50, 50, 0, s.HALF_PI)
 553
 554        s.no_fill()
 555        
 556        # Bottom-left.
 557        s.arc(50, 55, 60, 60, s.HALF_PI, s.PI)
 558        
 559        # Top-left.
 560        s.arc(50, 55, 70, 70, s.PI, s.PI + s.QUARTER_PI)
 561        
 562        # Top-right.
 563        s.arc(50, 55, 80, 80, s.PI + s.QUARTER_PI, s.TWO_PI)
 564        ```
 565        """
 566        rx = w * 0.5
 567        ry = h * 0.5
 568        epsilon = 0.00001  # Smallest visible angle on displays up to 4K.
 569        arc_to_draw = 0
 570        curves = []
 571
 572        # Create curves
 573        while stop - start >= epsilon:
 574            arc_to_draw = min(stop - start, Sketch.HALF_PI)
 575            curves.append(self._acute_arc_to_bezier(start, arc_to_draw))
 576            start += arc_to_draw
 577
 578        self.canvas.begin_path()
 579        for index, curve in enumerate(curves):
 580            if index == 0:
 581                self.canvas.move_to(x + curve["ax"] * rx, y + curve["ay"] * ry)
 582            self.canvas.bezier_curve_to(
 583                x + curve["bx"] * rx,
 584                y + curve["by"] * ry,
 585                x + curve["cx"] * rx,
 586                y + curve["cy"] * ry,
 587                x + curve["dx"] * rx,
 588                y + curve["dy"] * ry,
 589            )
 590        self.canvas.line_to(x, y)
 591        self.canvas.close_path()
 592        self.canvas.fill()
 593        self.canvas.stroke()
 594
 595    def ellipse(self, x: int | float, y: int | float, w: int | float, h: int | float):
 596        """Draws an ellipse (oval).
 597
 598        An ellipse is a round shape defined by the `x`, `y`, `w`, and `h`
 599        parameters. `x` and `y` set the location of its center. `w` and `h`
 600        set its width and height.
 601
 602        **Example**
 603        ```python
 604        from ipycc.sketch import Sketch
 605
 606        s = Sketch()
 607        s.show()
 608
 609        s.background(200)
 610
 611        # A circle.
 612        s.ellipse(20, 20, 40, 40)
 613        
 614        # An oval.
 615        s.ellipse(80, 80, 40, 20)
 616        ```
 617        """
 618        dx = w * 0.5
 619        dy = h * 0.5
 620        cx = x - dx
 621        cy = y - dy
 622
 623        kappa = 0.5522847498
 624        # control point offset horizontal
 625        ox = w * 0.5 * kappa
 626        # control point offset vertical
 627        oy = h * 0.5 * kappa
 628        # x-end
 629        xe = cx + w
 630        # y-end
 631        ye = cy + h
 632        # x-middle
 633        xm = cx + w * 0.5
 634        # y-middle
 635        ym = cy + h * 0.5
 636        self.canvas.begin_path()
 637        self.canvas.move_to(cx, ym)
 638        self.canvas.bezier_curve_to(cx, ym - oy, xm - ox, cy, xm, cy)
 639        self.canvas.bezier_curve_to(xm + ox, cy, xe, ym - oy, xe, ym)
 640        self.canvas.bezier_curve_to(xe, ym + oy, xm + ox, ye, xm, ye)
 641        self.canvas.bezier_curve_to(xm - ox, ye, cx, ym + oy, cx, ym)
 642        self.canvas.fill()
 643        self.canvas.stroke()
 644
 645    def circle(self, x: int | float, y: int | float, d: int | float):
 646        """Draws a circle.
 647
 648        A circle is a round shape defined by the `x`, `y`, and `d` parameters.
 649        `x` and `y` set the location of its center. `d` sets its width and
 650        height (diameter). Every point on the circle's edge is the same
 651        distance, `0.5 * d`, from its center. `0.5 * d` (half the diameter) is
 652        the circle's radius.
 653
 654        **Example**
 655        ```python
 656        from ipycc.sketch import Sketch
 657
 658        s = Sketch()
 659        s.show()
 660
 661        s.background(200)
 662        s.circle(50, 50, 25)
 663        ```
 664        """
 665        r = d * 0.5
 666        self.canvas.fill_circle(x, y, r)
 667        self.canvas.stroke_circle(x, y, r)
 668
 669    def line(self, x1: int | float, y1: int | float, x2: int | float, y2: int | float):
 670        """Draws a straight line between two points.
 671
 672        A line's default width is one pixel. The first two parameters set the
 673        starting coordinates of the line. The next two parameters set the
 674        ending coordinates of the line. To color a line, use the `stroke()`
 675        method. To change its width, use the `stroke_weight()` method.
 676
 677        **Example**
 678        ```python
 679        from ipycc.sketch import Sketch
 680
 681        s = Sketch()
 682        s.show()
 683
 684        s.background(200)
 685        s.line(30, 20, 85, 75)
 686        
 687        # Style the line.
 688        s.background(200)
 689        s.stroke("magenta")
 690        s.stroke_weight(5)
 691        s.line(30, 20, 85, 75)
 692        ```
 693        """
 694        self.canvas.stroke_line(x1, y1, x2, y2)
 695
 696    def point(self, x: int | float, y: int | float):
 697        """Draws a single point in space.
 698
 699        A point's default width is one pixel. To color a point, use the
 700        `stroke()` method. To change its width, use the `stroke_weight()`
 701        method. A point can't be filled, so the `fill()` method won't
 702        affect the point's color.
 703
 704        **Example**
 705        ```python
 706        from ipycc.sketch import Sketch
 707
 708        s = Sketch()
 709        s.show()
 710
 711        s.background(200)
 712        
 713        # Top-left.
 714        s.point(30, 20)
 715        
 716        # Top-right. 
 717        s.point(85, 20)
 718        
 719        # Style the next points.
 720        s.stroke("purple")
 721        s.stroke_weight(10)
 722        
 723        # Bottom-right.
 724        s.point(85, 75)
 725        
 726        # Bottom-left.
 727        s.point(30, 75)
 728        ```
 729        """
 730        s = f"{self.canvas.stroke_style}"
 731        f = f"{self.canvas.fill_style}"
 732        self.canvas.fill_style = s
 733        self.canvas.begin_path()
 734        self.canvas.arc(x, y, self.canvas.line_width * 0.5, 0, self.TWO_PI, False)
 735        self.canvas.fill()
 736        self.canvas.fill_style = f
 737
 738    def quad(
 739        self,
 740        x1: int | float,
 741        y1: int | float,
 742        x2: int | float,
 743        y2: int | float,
 744        x3: int | float,
 745        y3: int | float,
 746        x4: int | float,
 747        y4: int | float,
 748    ):
 749        """Draws a quadrilateral (four-sided shape).
 750
 751        Quadrilaterals include rectangles, squares, rhombuses, and trapezoids.
 752        The first pair of parameters `(x1, y1)` sets the quad's first point.
 753        The next three pairs of parameters set the coordinates for its next
 754        three points `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be
 755        added in either clockwise or counter-clockwise order.
 756
 757        **Example**
 758        ```python
 759        from ipycc.sketch import Sketch
 760
 761        s = Sketch()
 762        s.show()
 763
 764        s.background(200)
 765        s.quad(50, 62, 86, 50, 50, 38, 14, 50)
 766        ```
 767        """
 768        self.begin_shape()
 769        self.vertex(x1, y1)
 770        self.vertex(x2, y2)
 771        self.vertex(x3, y3)
 772        self.vertex(x4, y4)
 773        self.end_shape()
 774
 775    def rect(self, x: int | float, y: int | float, w: int | float, h: int | float):
 776        """Draws a rectangle.
 777
 778        A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and
 779        `h` parameters. `x` and `y` set the location of its top-left corner.
 780        `w` sets its width and `h` sets its height. Every angle in the
 781        rectangle measures 90Ëš.
 782
 783        **Example**
 784        ```python
 785        from ipycc.sketch import Sketch
 786
 787        s = Sketch()
 788        s.show()
 789
 790        s.background(200)
 791        s.rect(30, 20, 55, 40)
 792        ```
 793        """
 794        self.canvas.fill_rect(x, y, w, h)
 795        self.canvas.stroke_rect(x, y, w, h)
 796
 797    def square(self, x: int | float, y: int | float, s: int | float):
 798        """Draws a square.
 799
 800        A square is a four-sided shape defined by the `x`, `y`, and `s`
 801        parameters. `x` and `y` set the location of its top-left corner. `s`
 802        sets its width and height. Every angle in the square measures 90Ëš
 803        and all its sides are the same length. 
 804
 805        **Example**
 806        ```python
 807        from ipycc.sketch import Sketch
 808
 809        s = Sketch()
 810        s.show()
 811
 812        s.background(200)
 813        s.square(30, 20, 55)
 814        ```
 815        """
 816        self.canvas.fill_rect(x, y, s, s)
 817        self.canvas.stroke_rect(x, y, s, s)
 818
 819    def triangle(
 820        self,
 821        x1: int | float,
 822        y1: int | float,
 823        x2: int | float,
 824        y2: int | float,
 825        x3: int | float,
 826        y3: int | float,
 827    ):
 828        """Draws a triangle.
 829
 830        A triangle is a three-sided shape defined by three points. The first
 831        two parameters specify the triangle's first point `(x1, y1)`. The
 832        middle two parameters specify its second point `(x2, y2)`. And the
 833        last two parameters specify its third point `(x3, y3)`.
 834
 835        **Example**
 836        ```python
 837        from ipycc.sketch import Sketch
 838
 839        s = Sketch()
 840        s.show()
 841
 842        s.background(200)
 843        s.triangle(30, 75, 58, 20, 86, 75)
 844        ```
 845        """
 846        self.begin_shape()
 847        self.vertex(x1, y1)
 848        self.vertex(x2, y2)
 849        self.vertex(x3, y3)
 850        self.end_shape()
 851
 852    # ========================================
 853    #               Attributes
 854    # ========================================
 855
 856    def stroke_weight(self, weight: int | float):
 857        """Sets the width of the stroke used for points, lines, and the outlines of shapes.
 858
 859        Note: stroke_weight() is affected by transformations, especially calls to scale().
 860        
 861        **Example**
 862        ```python
 863        from ipycc.sketch import Sketch
 864
 865        s = Sketch()
 866        s.show()
 867
 868        s.background(200)
 869
 870        # Top.
 871        s.line(20, 20, 80, 20)
 872
 873        # Middle.
 874        s.stroke_weight(4)
 875        s.line(20, 40, 80, 40)
 876
 877        # Bottom.
 878        s.stroke_weight(10)
 879        s.line(20, 70, 80, 70)
 880        ```
 881        """
 882        if not self._is_stroke_weight_set:
 883            self._is_stroke_weight_set = True
 884        self.canvas.line_width = weight
 885
 886    # ========================================
 887    #               Curves
 888    # ========================================
 889
 890    def bezier(
 891        self,
 892        x1: int | float,
 893        y1: int | float,
 894        x2: int | float,
 895        y2: int | float,
 896        x3: int | float,
 897        y3: int | float,
 898        x4: int | float,
 899        y4: int | float,
 900    ):
 901        """Draws a Bézier curve.
 902
 903        Bézier curves can form shapes and curves that slope gently. They're
 904        defined by two anchor points and two control points.
 905
 906        The first two parameters, `x1` and `y1`, set the first anchor point.
 907        The first anchor point is where the curve starts.
 908
 909        The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two
 910        control points. The control points "pull" the curve towards them.
 911
 912        The seventh and eighth parameters, `x4` and `y4`, set the last anchor
 913        point. The last anchor point is where the curve ends.
 914
 915        **Example**
 916        ```python
 917        from ipycc.sketch import Sketch
 918
 919        s = Sketch()
 920        s.show()
 921
 922        s.background(200)
 923
 924        # Draw the anchor points in black.
 925        s.stroke(0)
 926        s.stroke_weight(5)
 927        s.point(85, 20)
 928        s.point(15, 80)
 929
 930        # Draw the control points in red.
 931        s.stroke(255, 0, 0)
 932        s.point(10, 10)
 933        s.point(90, 90)
 934
 935        # Draw a black bezier curve.
 936        s.no_fill()
 937        s.stroke(0)
 938        s.stroke_weight(1)
 939        s.bezier(85, 20, 10, 10, 90, 90, 15, 80)
 940
 941        # Draw red lines from the anchor points to the control points.
 942        s.stroke(255, 0, 0)
 943        s.line(85, 20, 10, 10)
 944        s.line(15, 80, 90, 90)
 945        ```
 946        """
 947        self.canvas.begin_path()
 948        self.canvas.move_to(x1, y1)
 949        self.canvas.bezier_curve_to(x2, y2, x3, y3, x4, y4)
 950        self.canvas.stroke()
 951
 952    def bezier_point(
 953        self,
 954        a: int | float,
 955        b: int | float,
 956        c: int | float,
 957        d: int | float,
 958        t: int | float,
 959    ) -> float:
 960        """Calculates coordinates along a Bézier curve using interpolation.
 961
 962        `bezier_point()` calculates coordinates along a Bézier curve using the
 963        anchor and control points. It expects points in the same order as the
 964        bezier() method. `bezier_point()` works one axis at a time. Passing
 965        the anchor and control points' x-coordinates will calculate the
 966        x-coordinate of a point on the curve. Passing the anchor and control
 967        points' y-coordinates will calculate the y-coordinate of a point on
 968        the curve.
 969
 970        The first parameter, `a`, is the coordinate of the first anchor point.
 971
 972        The second and third parameters, `b` and `c`, are the coordinates of
 973        the control points.
 974
 975        The fourth parameter, `d`, is the coordinate of the last anchor point.
 976
 977        The fifth parameter, `t`, is the amount to interpolate along the
 978        curve. 0 is the first anchor point, 1 is the second anchor point, and
 979        0.5 is halfway between them.
 980
 981        **Example**
 982        ```python
 983        from ipycc.sketch import Sketch
 984
 985        s = Sketch()
 986        s.show()
 987
 988        s.background(200)
 989
 990        # Set the coordinates for the curve's anchor and control points.
 991        x1 = 85
 992        x2 = 10
 993        x3 = 90
 994        x4 = 15
 995        y1 = 20
 996        y2 = 10
 997        y3 = 90
 998        y4 = 80
 999
1000        # Style the curve.
1001        s.no_fill()
1002
1003        # Draw the curve.
1004        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1005
1006        # Draw circles along the curve's path.
1007        s.fill(255)
1008
1009        # Top-right.
1010        x = s.bezier_point(x1, x2, x3, x4, 0)
1011        y = s.bezier_point(y1, y2, y3, y4, 0)
1012        s.circle(x, y, 5)
1013
1014        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1015        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1016        s.circle(x, y, 5) # center circle
1017        x = s.bezier_point(x1, x2, x3, x4, 1)
1018        y = s.bezier_point(y1, y2, y3, y4, 1)
1019        s.circle(x, y, 5) # bottom-left circle
1020        ```
1021        """
1022        adjusted_t = 1 - t
1023        return (
1024            pow(adjusted_t, 3) * a
1025            + 3 * pow(adjusted_t, 2) * t * b
1026            + 3 * adjusted_t * pow(t, 2) * c
1027            + pow(t, 3) * d
1028        )
1029
1030    def bezier_tangent(
1031        self,
1032        a: int | float,
1033        b: int | float,
1034        c: int | float,
1035        d: int | float,
1036        t: int | float,
1037    ) -> float:
1038        """Calculates coordinates along a line that's tangent to a Bézier curve.
1039
1040        Tangent lines skim the surface of a curve. A tangent line's slope
1041        equals the curve's slope at the point where it intersects.
1042
1043        `bezier_tangent()` calculates coordinates along a tangent line using
1044        the Bézier curve's anchor and control points. It expects points in the
1045        same order as the `bezier()` method. `bezier_tangent()` works one axis
1046        at a time. Passing the anchor and control points' x-coordinates will
1047        calculate the x-coordinate of a point on the tangent line. Passing the
1048        anchor and control points' y-coordinates will calculate the
1049        y-coordinate of a point on the tangent line.
1050
1051        The first parameter, `a`, is the coordinate of the first anchor point.
1052
1053        The second and third parameters, `b` and `c`, are the coordinates of
1054        the control points.
1055
1056        The fourth parameter, `d`, is the coordinate of the last anchor point.
1057
1058        The fifth parameter, `t`, is the amount to interpolate along the curve.
1059        0 is the first anchor point, 1 is the second anchor point, and 0.5 is
1060        halfway between them.
1061
1062        **Example**
1063        ```python
1064        from ipycc.sketch import Sketch
1065
1066        s = Sketch()
1067        s.show()
1068
1069        s.background(200)
1070
1071        # Set the coordinates for the curve's anchor and control points.
1072        x1 = 85
1073        x2 = 10
1074        x3 = 90
1075        x4 = 15
1076        y1 = 20
1077        y2 = 10
1078        y3 = 90
1079        y4 = 80
1080
1081        # Style the curve.
1082        s.no_fill()
1083
1084        # Draw the curve.
1085        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1086
1087        # Draw tangents along the curve's path.
1088        s.fill(255)
1089
1090        # Top-right circle.
1091        s.stroke(0)
1092        x = s.bezier_point(x1, x2, x3, x4, 0)
1093        y = s.bezier_point(y1, y2, y3, y4, 0)
1094        s.circle(x, y, 5)
1095
1096        # Top-right tangent line.
1097        # Scale the tangent point to draw a shorter line.
1098        s.stroke(255, 0, 0) 
1099        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0)
1100        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0)
1101        s.line(x + tx, y + ty, x - tx, y - ty)
1102
1103        # Center circle.
1104        s.stroke(0)
1105        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1106        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1107        s.circle(x, y, 5)
1108        
1109        # Center tangent line.
1110        # Scale the tangent point to draw a shorter line.
1111        stroke(255, 0, 0)
1112        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0.5)
1113        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0.5)
1114        s.line(x + tx, y + ty, x - tx, y - ty)
1115
1116        # Bottom-left circle.
1117        stroke(0)
1118        x = s.bezier_point(x1, x2, x3, x4, 1)
1119        y = s.bezier_point(y1, y2, y3, y4, 1)
1120        s.circle(x, y, 5)
1121        
1122        # Bottom-left tangent.
1123        # Scale the tangent point to draw a shorter line.
1124        s.stroke(255, 0, 0)
1125        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 1)
1126        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 1)
1127        s.line(x + tx, y + ty, x - tx, y - ty)
1128        ```
1129        """
1130        adjusted_t = 1 - t
1131        return (
1132            3 * d * pow(t, 2)
1133            - 3 * c * pow(t, 2)
1134            + 6 * c * adjusted_t * t
1135            - 6 * b * adjusted_t * t
1136            + 3 * b * pow(adjusted_t, 2)
1137            - 3 * a * pow(adjusted_t, 2)
1138        )
1139
1140    # ========================================
1141    #                Vertex
1142    # ========================================
1143
1144    def begin_shape(self):
1145        """Begins adding vertices to a custom shape.
1146
1147        The `begin_shape()` and `end_shape()` methods allow for creating
1148        custom shapes. `begin_shape()` begins adding vertices to a custom
1149        shape and `end_shape()` stops adding them. After calling
1150        `begin_shape()`, shapes can be built by calling `vertex()`.
1151
1152        **Example**
1153        ```python
1154        from ipycc.sketch import Sketch
1155
1156        s = Sketch()
1157        s.show()
1158
1159        s.background(200)
1160        s.begin_shape() # begin drawing
1161        s.vertex(30, 20)
1162        s.vertex(85, 20)
1163        s.vertex(85, 75)
1164        s.vertex(30, 75)
1165        s.end_shape() # end drawing
1166        ```
1167        """
1168        self._vertices.clear()
1169
1170    def end_shape(self):
1171        """Stops adding vertices to a custom shape.
1172
1173        The `begin_shape()` and `end_shape()` methods allow for creating
1174        custom shapes. `begin_shape()` begins adding vertices to a custom
1175        shape and `end_shape()` stops adding them. After calling
1176        `begin_shape()`, shapes can be built by calling `vertex()`.
1177
1178        **Example**
1179        ```python
1180        from ipycc.sketch import Sketch
1181
1182        s = Sketch()
1183        s.show()
1184
1185        s.background(200)
1186        s.begin_shape() # begin drawing
1187        s.vertex(30, 20)
1188        s.vertex(85, 20)
1189        s.vertex(85, 75)
1190        s.vertex(30, 75)
1191        s.end_shape() # end drawing
1192        ```
1193        """
1194        if len(self._vertices) > 0:
1195            self.canvas.fill_polygon(self._vertices)
1196            self.canvas.stroke_polygon(self._vertices)
1197            self._vertices.clear()
1198
1199    def vertex(self, x: int | float, y: int | float):
1200        """Adds a vertex to a custom shape.
1201
1202        `vertex()` sets the coordinates of vertices drawn between the
1203        `begin_shape()` and `end_shape()` methods.
1204
1205        **Example**
1206        ```python
1207        from ipycc.sketch import Sketch
1208
1209        s = Sketch()
1210        s.show()
1211
1212        s.background(200)
1213        s.begin_shape() # begin drawing
1214        s.vertex(30, 20)
1215        s.vertex(85, 20)
1216        s.vertex(85, 75)
1217        s.vertex(30, 75)
1218        s.end_shape() # end drawing
1219        ```
1220        """
1221        self._vertices.append((x, y))
1222
1223    # ========================================
1224    #                Structure
1225    # ========================================
1226
1227    def show(self):
1228        """Display the sketch beneath the current code cell.
1229
1230        **Example**
1231        ```python
1232        from ipycc.sketch import Sketch
1233
1234        s = Sketch()
1235        s.show()
1236
1237        s.background(200)
1238        s.show()
1239        ```
1240        """
1241        display(self.canvas)
1242
1243    # ========================================
1244    #               Transform
1245    # ========================================
1246
1247    def apply_matrix(
1248        self,
1249        a: int | float,
1250        b: int | float,
1251        c: int | float,
1252        d: int | float,
1253        e: int | float,
1254        f: int | float,
1255    ):
1256        """Applies a transformation matrix to the coordinate system.
1257
1258        Transformations such as `translate()`, `rotate()`, and `scale()` use
1259        matrix-vector multiplication behind the scenes. A table of numbers,
1260        called a matrix, encodes each transformation. The values in the matrix
1261        then multiply each point on the canvas, which is represented by a
1262        vector.
1263
1264        `apply_matrix()` allows for many transformations to be applied at once.
1265        See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web)
1266        for more details about transformations.
1267
1268        **Example**
1269        ```python
1270        from ipycc.sketch import Sketch
1271
1272        s = Sketch()
1273        s.show()
1274
1275        s.background(200)
1276        
1277        # Translate the origin to the center.
1278        s.apply_matrix(1, 0, 0, 1, 50, 50)
1279        
1280        # Draw the circle at coordinates (0, 0).
1281        s.circle(0, 0, 40)
1282        ```
1283        """
1284        m = np.array(((a, c, e), (b, d, f), (0, 0, 1)), dtype=float)
1285        self._matrix = m @ self._matrix
1286        self.canvas.transform(a, b, c, d, e, f)
1287
1288    def reset_matrix(self):
1289        """Clears all transformations applied to the coordinate system.
1290
1291        **Example**
1292        ```python
1293        from ipycc.sketch import Sketch
1294
1295        s = Sketch()
1296        s.show()
1297
1298        s.background(200)
1299        
1300        # Translate the origin to the center.
1301        s.translate(50, 50)
1302        
1303        # Draw a blue circle at the coordinates (25, 25).
1304        s.fill("blue")
1305        s.circle(25, 25, 20)
1306        
1307        # Clear all transformations.
1308        # The origin is now at the top-left corner.
1309        s.reset_matrix()
1310        
1311        # Draw a red circle at the coordinates (25, 25).
1312        s.fill("red")
1313        s.circle(25, 25, 20)
1314        ```
1315        """
1316        self._matrix = np.eye(3)
1317        self.canvas.reset_transform()
1318
1319    def rotate(self, angle: int | float):
1320        """Rotates the coordinate system.
1321
1322        By default, the positive x-axis points to the right and the positive
1323        y-axis points downward. The `rotate()` method changes this orientation
1324        by rotating the coordinate system about the origin. Everything drawn
1325        after `rotate()` is called will appear to be rotated. Angles are
1326        measured in radians.
1327
1328        **Example**
1329        ```python
1330        from ipycc.sketch import Sketch
1331
1332        s = Sketch()
1333        s.show()
1334
1335        s.background(200)
1336
1337        # Rotate the coordinate system 1/8 turn.
1338        s.rotate(s.QUARTER_PI)
1339
1340        # Draw a rectangle at coordinates (50, 0).
1341        s.rect(50, 0, 40, 20)
1342        ```
1343        """
1344        ca, sa = math.cos(angle), math.sin(angle)
1345        m = np.array(((ca, -sa, 0), (sa, ca, 0), (0, 0, 1)), dtype=float)
1346        self._matrix = m @ self._matrix
1347        self.canvas.rotate(angle)
1348
1349    def scale(self, x: int | float, y: int | float = None):
1350        """Scales the coordinate system.
1351
1352        By default, shapes are drawn at their original scale. A rectangle
1353        that's 50 pixels wide appears to take up half the width of a 100
1354        pixel-wide canvas. The `scale()` method can shrink or stretch the
1355        coordinate system so that shapes appear at different sizes.
1356
1357        The first parameter, `s`, sets the amount to scale each axis. For
1358        example, calling `scale(2)` stretches the x- and y-axes by a factor
1359        of 2. The next parameter, `y`, is optional. It sets the amount to
1360        scale the y-axis. For example, calling `scale(2, 0.5)` stretches the
1361        x-axis by a factor of 2 and shrinks the y-axis by a factor of 0.5.
1362
1363        **Example**
1364        ```python
1365        from ipycc.sketch import Sketch
1366
1367        s = Sketch()
1368        s.show()
1369
1370        s.background(200)
1371        
1372        # Draw a square at (30, 20).
1373        s.square(30, 20, 40)
1374        
1375        # Scale the coordinate system by a factor of 0.5.
1376        s.scale(0.5)
1377        
1378        # Draw a square at (30, 20).
1379        # It appears at (15, 10) after scaling.
1380        s.square(30, 20, 40)
1381        s.background(200)
1382        s.reset_matrix()
1383        
1384        # Draw a square at (30, 20).
1385        s.square(30, 20, 40)
1386        
1387        # Scale the coordinate system by factors of
1388        # 0.5 along the x-axis and
1389        # 1.3 along the y-axis.
1390        s.scale(0.5, 1.3)
1391        
1392        # Draw a square at (30, 20).
1393        # It appears as a rectangle at (15, 26) after scaling.
1394        s.square(30, 20, 40)
1395        ```
1396        """
1397        if y is None:
1398            m = np.array(((x, 0, 0), (0, x, 0), (0, 0, 1)), dtype=float)
1399            self._matrix = m @ self._matrix
1400            self.canvas.scale(x)
1401        else:
1402            m = np.array(((x, 0, 0), (0, y, 0), (0, 0, 1)), dtype=float)
1403            self._matrix = m @ self._matrix
1404            self.canvas.scale(x, y=y)
1405
1406    def shear_x(self, angle: int | float):
1407        """Shears the x-axis so that shapes appear skewed.
1408
1409        By default, the x- and y-axes are perpendicular. The `shear_x()`
1410        method transforms the coordinate system so that x-coordinates are
1411        translated while y-coordinates are fixed.
1412
1413        **Example**
1414        ```python
1415        from ipycc.sketch import Sketch
1416
1417        s = Sketch()
1418        s.show()
1419
1420        s.background(200)
1421
1422        # Shear the coordinate system along the x-axis.
1423        s.shear_x(s.QUARTER_PI)
1424
1425        # Draw the square.
1426        s.square(0, 0, 50)
1427        ```
1428        """
1429        self.apply_matrix(1, 0, math.tan(angle), 1, 0, 0)
1430
1431    def shear_y(self, angle: int | float):
1432        """Shears the y-axis so that shapes appear skewed.
1433
1434        By default, the x- and y-axes are perpendicular. The `shear_y()`
1435        method transforms the coordinate system so that y-coordinates are
1436        translated while x-coordinates are fixed.
1437
1438        **Example**
1439        ```python
1440        from ipycc.sketch import Sketch
1441
1442        s = Sketch()
1443        s.show()
1444
1445        s.background(200)
1446
1447        # Shear the coordinate system along the y-axis.
1448        s.shear_y(s.QUARTER_PI)
1449
1450        # Draw the square.
1451        s.square(0, 0, 50)
1452        ```
1453        """
1454        self.apply_matrix(1, math.tan(angle), 0, 1, 0, 0)
1455
1456    def translate(self, x: int | float, y: int | float):
1457        """Translates the coordinate system.
1458
1459        By default, the origin (0, 0) is at the sketch's top-left corner. The
1460        `translate()` method shifts the origin to a different position.
1461        Everything drawn after `translate()` is called will appear to be
1462        shifted.
1463    
1464        **Example**
1465        ```python
1466        from ipycc.sketch import Sketch
1467
1468        s = Sketch()
1469        s.show()
1470
1471        s.background(200)
1472
1473        # Translate the origin to the center.
1474        s.translate(50, 50)
1475
1476        # Draw a circle at coordinates (0, 0).
1477        s.circle(0, 0, 40)
1478        ```
1479        """
1480        m = np.array(((0, 0, x), (0, 0, y), (0, 0, 1)), dtype=float)
1481        self._matrix = m @ self._matrix
1482        self.canvas.translate(x, y)
1483
1484    # ========================================
1485    #                  Image
1486    # ========================================
1487
1488    def image(
1489        self,
1490        img: Self,
1491        x: int | float,
1492        y: int | float,
1493        width: int | float = None,
1494        height: int | float = None,
1495    ):
1496        """Draws an image to the canvas.
1497
1498        The first parameter, `img`, is the source image to be drawn. `img` can be
1499        another `Sketch` instance.
1500
1501        The second and third parameters, `x` and `y`, set the coordinates of the
1502        destination image's top left corner.
1503
1504        The fourth and fifth parameters, `width` and `height`, are optional. They
1505        set the the width and height to draw the destination image. By
1506        default, `image()` draws the full source image at its original size.
1507
1508        **Example**
1509        ```python
1510        from ipycc.sketch import Sketch
1511
1512        # Create the Sketches.
1513        s1 = Sketch()
1514        s2 = Sketch()
1515
1516        # Draw to s1.
1517        s1.background(200)
1518        s1.circle(50, 50, 20)
1519
1520        # Draw s1 on s2 at full size.
1521        s2.image(s1, 0, 0)
1522
1523        # Draw s1 on s2 at half size.
1524        s2.image(s1, 0, 0, 50, 50)
1525
1526        # Show s2.
1527        s2.show()
1528        ```
1529        """
1530        if width is None:
1531            width = img.width
1532        if height is None:
1533            height = img.height
1534        self.canvas.draw_image(img.canvas, x=x, y=y, width=width, height=height)
1535
1536    # ========================================
1537    #                Typography
1538    # ========================================
1539
1540    def text(self, text: str, x: int | float, y: int | float):
1541        """Draws text to the canvas.
1542
1543        The first parameter, `text`, is the text to be drawn. The second and
1544        third parameters, `x` and `y`, set the coordinates of the text's
1545        bottom-left corner. See `text_align()` for other ways to align text.
1546
1547        **Example**
1548        ```python
1549        from ipycc.sketch import Sketch
1550
1551        s = Sketch()
1552        s.show()
1553        ```
1554
1555        ```python
1556        # Plain text.
1557        s.background(200)
1558        s.text("hi", 50, 50)
1559        ```
1560        
1561        
1562        ```python
1563        # Emoji.
1564        s.background("skyblue")
1565        s.text_size(100)
1566        s.text("🌈", 0, 100)
1567        ```
1568        
1569        ```python
1570        # No fill.
1571        s.background(200)
1572        s.text_size(32)
1573        s.fill(255)
1574        s.stroke(0)
1575        s.stroke_weight(4)
1576        s.text("hi", 50, 50)
1577        ```
1578        
1579        ```python
1580        # Multicolor text.
1581        s.background("black")
1582        s.text_size(22)
1583        s.fill("yellow")
1584        s.text("rainbows", 6, 20)
1585        s.fill("cornflowerblue")
1586        s.text("rainbows", 6, 45)
1587        s.fill("tomato")
1588        s.text("rainbows", 6, 70)
1589        s.fill("limegreen")
1590        s.text("rainbows", 6, 95)
1591        ```
1592        """
1593        if self._is_fill_set:
1594            self.canvas.fill_text(text, x, y)
1595        else:
1596            self.canvas.fill_style = Sketch._DEFAULT_TEXT_FILL
1597            self.canvas.fill_text(text, x, y)
1598            self.canvas.fill_style = Sketch._DEFAULT_FILL
1599
1600        if self._is_stroke_set:
1601            if self._is_stroke_weight_set:
1602                self.canvas.stroke_text(text, x, y)
1603            else:
1604                self.canvas.line_width = Sketch._DEFAULT_TEXT_WEIGHT
1605                self.canvas.stroke_text(text, x, y)
1606                self.canvas.line_width = Sketch._DEFAULT_STROKE_WEIGHT
1607
1608    def text_font(self, font: str):
1609        """Sets the font used by the `text()` method.
1610
1611        The font should be a string with the name of a system font such as
1612        `"Courier New"`.
1613
1614        **Example**
1615        ```python
1616        from ipycc.sketch import Sketch
1617
1618        s = Sketch()
1619        s.show()
1620
1621        s.background(200)
1622        s.text_font("Courier New")
1623        s.text_size(24)
1624        s.text("hi", 35, 55)
1625        ```
1626        """
1627        self._font = font
1628        self.canvas.font = (
1629            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1630        )
1631
1632    def text_size(self, size: int | float):
1633        """Sets the font size when `text()` is called.
1634
1635        Note: Font size is measured in pixels.
1636
1637        **Example**
1638        ```python
1639        from ipycc.sketch import Sketch
1640
1641        s = Sketch()
1642        s.show()
1643
1644        s.background(200)
1645
1646        # Top row.
1647        s.text_size(12)
1648        s.text("Font Size 12", 10, 30)
1649
1650        # Middle row.
1651        s.text_size(14)
1652        s.text("Font Size 14", 10, 60)
1653
1654        # Bottom row.
1655        s.text_size(16)
1656        s.text("Font Size 16", 10, 90)
1657        ```
1658        """
1659        self._font_size = size
1660        self.canvas.font = (
1661            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1662        )
1663
1664    def text_align(self, horizontal: str, vertical: str = None):
1665        """Sets the way text is aligned when `text()` is called.
1666
1667        By default, calling `text("hi", 10, 20)` places the bottom-left corner
1668        of the text's bounding box at (10, 20).
1669
1670        The first parameter, `horizontal`, changes the way `text()` interprets
1671        x-coordinates. By default, the x-coordinate sets the left edge of the
1672        bounding box. `text_align()` accepts the following values for horizontal:
1673        `LEFT`, `CENTER`, or `RIGHT`.
1674
1675        The second parameter, `vertical`, is optional. It changes the way `text()`
1676        interprets y-coordinates. By default, the y-coordinate sets the bottom
1677        edge of the bounding box. `text_align()` accepts the following values
1678        for vertical: `TOP`, `BOTTOM`, `CENTER`, or `BASELINE`.
1679
1680        **Example**
1681        ```python
1682        from ipycc.sketch import Sketch
1683
1684        s = Sketch()
1685        s.show()
1686
1687        s.background(200)
1688
1689        # Draw a vertical line.
1690        s.stroke_weight(0.5)
1691        s.line(50, 0, 50, 100)
1692
1693        # Top row.
1694        s.text_size(16)
1695        s.text_align(s.RIGHT)
1696        s.text("ABCD", 50, 30)
1697        
1698        # Middle row.
1699        s.text_align(s.CENTER)
1700        s.text("EFGH", 50, 50)
1701        
1702        # Bottom row.
1703        s.text_align(s.LEFT)
1704        s.text("IJKL", 50, 70)
1705        ```
1706        """
1707        if horizontal == Sketch.LEFT:
1708            self._text_align = Sketch.LEFT
1709        elif horizontal == Sketch.RIGHT:
1710            self._text_align = Sketch.RIGHT
1711        elif horizontal == Sketch.CENTER:
1712            self._text_align = Sketch.CENTER
1713        self.canvas.text_align = self._text_align
1714
1715        if vertical is None:
1716            return
1717        if vertical == Sketch.TOP:
1718            self._text_baseline = Sketch.TOP
1719        elif vertical == Sketch.BOTTOM:
1720            self._text_baseline = Sketch.BOTTOM
1721        elif vertical == Sketch.CENTER:
1722            self._text_baseline = Sketch.CENTER
1723        elif vertical == Sketch.BASELINE:
1724            self._text_baseline = Sketch.BASELINE
1725        self.canvas.text_baseline = vertical
1726
1727    def text_style(self, style: str):
1728        """Sets the style for system fonts when `text()` is called.
1729
1730        The parameter, `style`, can be either `NORMAL`, `ITALIC`, `BOLD`, or
1731        `BOLDITALIC`.
1732
1733        **Example**
1734        ```python
1735        from ipycc.sketch import Sketch
1736
1737        s = Sketch()
1738        s.show()
1739
1740        s.background(200)
1741
1742        # First row.
1743        s.text_size(12)
1744        s.text_style(s.NORMAL)
1745        s.text("Normal", 20, 15)
1746
1747        # Second row.
1748        s.text_style(s.ITALIC)
1749        s.text("Italic", 20, 40)
1750
1751        # Third row.
1752        s.text_style(s.BOLD)
1753        s.text("Bold", 20, 65)
1754
1755        # Fourth row.
1756        s.text_style(s.BOLDITALIC)
1757        s.text("Bold Italic", 20, 90)
1758        ```
1759        """
1760        if style == Sketch.NORMAL:
1761            self._font_weight = Sketch.NORMAL
1762            self._font_style = Sketch.NORMAL
1763        elif style == Sketch.ITALIC:
1764            self._font_weight = Sketch.NORMAL
1765            self._font_style = Sketch.ITALIC
1766        elif style == Sketch.BOLD:
1767            self._font_weight = Sketch.BOLD
1768            self._font_style = Sketch.NORMAL
1769        elif style == Sketch.BOLDITALIC:
1770            self._font_weight = Sketch.BOLD
1771            self._font_style = Sketch.ITALIC
1772        self.canvas.font = (
1773            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1774        )
1775
1776    # ========================================
1777    #                Utilities
1778    # ========================================
1779
1780    def _unpack_transform(self) -> tuple[float]:
1781        """Unpacks the sketch's transformation matrix."""
1782        a = self._matrix[0][0]
1783        b = self._matrix[1][0]
1784        c = self._matrix[0][1]
1785        d = self._matrix[1][1]
1786        e = self._matrix[2][0]
1787        f = self._matrix[2][2]
1788        return a, b, c, d, e, f
1789
1790    def run_sketch(self, draw: Callable, seconds: int | float, delay: float = 20):
1791        """Draws frames in an animation by calling a function repeatedly.
1792
1793        `run_sketch()` repeatedly calls a function that contains drawing
1794        commands. The rate at which each frame is drawn depends on many
1795        factors. `run_sketch()` doesn't attempt to maintain a constant
1796        framerate.
1797
1798        The first parameter, `draw`, is a function containing the commands for
1799        drawing each frame.
1800
1801        The second parameter, `seconds`, sets the number of seconds the
1802        animation should run.
1803
1804        The third parameter, `delay`, is optional. It sets the number of
1805        milliseconds the sketch should pause after drawing the current frame.
1806        The default value is 20. If `draw` contains many drawing commands,
1807        each frame may take much longer than `delay` milliseconds to render.
1808
1809        **Example**
1810        ```python
1811        from ipycc.sketch import Sketch
1812
1813        s = Sketch()
1814        s.show()
1815
1816        def draw():
1817            # Paint the background.
1818            s.background(200)
1819
1820            # Calculate the circle's x-coordinate.
1821            x = s.frame_count
1822
1823            # Draw the circle.
1824            s.circle(x, 50, 20)
1825
1826        # Run the animation for 5 seconds.
1827        s.run_sketch(draw, 5)
1828        ```
1829        """
1830        if delay < 0:
1831            raise SketchError("Your delay value must be positive.")
1832        start = time.time()
1833        end = start + seconds
1834        delay *= 0.001
1835        self.frame_count = 0
1836        while time.time() < end:
1837            with hold_canvas():
1838                a, b, c, d, e, f = self._unpack_transform()
1839                draw()
1840                self.reset_matrix()
1841                self.apply_matrix(a, b, c, d, e, f)
1842                self.frame_count += 1
1843                time.sleep(delay)
1844
1845
1846__all__ = ["Sketch"]
class Sketch:
  15class Sketch:
  16    """A class to describe a 2D drawing canvas."""
  17
  18    # Constants
  19    HALF_PI: float = math.pi / 2.0
  20    """A number constant that's approximately 1.5708."""
  21
  22    PI: float = math.pi
  23    """A number constant that's approximately 3.1416."""
  24
  25    QUARTER_PI: float = math.pi / 4.0
  26    """A number constant that's approximately 0.7854."""
  27
  28    TAU: float = math.tau
  29    """A number constant that's approximately 6.2382."""
  30
  31    TWO_PI: float = math.tau
  32    """A number constant that's approximately 6.2382."""
  33
  34    NORMAL: str = "normal"
  35    """A string constant used with the `text_style()` method."""
  36
  37    ITALIC: str = "italic"
  38    """A string constant used with the `text_style()` method."""
  39
  40    BOLD: str = "bold"
  41    """A string constant used with the `text_style()` method."""
  42
  43    BOLDITALIC: str = "bolditalic"
  44    """A string constant used with the `text_style()` method."""
  45
  46    LEFT: str = "left"
  47    """A string constant used with the `text_align()` method."""
  48
  49    CENTER: str = "center"
  50    """A string constant used with the `text_align()` method."""
  51
  52    RIGHT: str = "right"
  53    """A string constant used with the `text_align()` method."""
  54
  55    BOTTOM: str = "bottom"
  56    """A string constant used with the `text_align()` method."""
  57
  58    TOP: str = "top"
  59    """A string constant used with the `text_align()` method."""
  60
  61    BASELINE: str = "alphabetic"
  62    """A string constant used with the `text_align()` method."""
  63
  64    MIDDLE: str = "middle"
  65    """A string constant used with the `text_align()` method."""
  66
  67    _DEFAULT_STROKE = "black"
  68    _DEFAULT_STROKE_WEIGHT = 1
  69    _DEFAULT_LINE_CAP = "round"
  70    _DEFAULT_FILL = "white"
  71    _DEFAULT_TEXT_FILL = "black"
  72    _DEFAULT_TEXT_WEIGHT = 0.3
  73    _DEFAULT_TEXT_ALIGN = LEFT
  74    _DEFAULT_TEXT_BASELINE = BASELINE
  75    _TRANSPARENT = "#00000000"
  76    _DEFAULT_FONT = "Arial"
  77    _DEFAULT_FONT_SIZE = 12
  78    _DEFAULT_FONT_STYLE = "normal"
  79    _DEFAULT_FONT_WEIGHT = "normal"
  80
  81    def __init__(
  82        self,
  83        width: int = 100,
  84        height: int = 100,
  85        pixel_denstiy: int = 2,
  86    ):
  87        # Create the Canvas.
  88        self.width: int = width
  89        """The width of the canvas in pixels."""
  90
  91        self.height: int = height
  92        """The height of the canvas in pixels."""
  93
  94        self.pixel_density: int = pixel_denstiy
  95        """The number of physical pixels used to draw a pixel on the canvas."""
  96
  97        self.canvas: Canvas = Canvas(
  98            width=width * pixel_denstiy,
  99            height=height * pixel_denstiy,
 100            layout={"width": f"{width}px", "height": f"{height}px"})
 101        """The `Canvas` widget used for drawing."""
 102        
 103        # Set default the styles.
 104        self._init_style()
 105        # Set the default transformations.
 106        self._init_transformation()
 107        # Create an empty list for shape vertices.
 108        self._vertices = []
 109        # Set the current frame count (for animation).
 110        self.frame_count: int = 0
 111        """The number of frames drawn since the sketch started."""
 112
 113        self._is_looping = False
 114
 115    def _init_transformation(self):
 116        sx = self.pixel_density
 117        sy = self.pixel_density
 118        self._matrix = np.array([[sx,  0,  0],
 119                                 [ 0, sy,  0],
 120                                 [ 0,  0,  1]], dtype=float)
 121        self.canvas.scale(self.pixel_density, y=self.pixel_density)
 122
 123    def _init_style(self):
 124        # Set initial styles.
 125        self.canvas.fill_style = Sketch._DEFAULT_FILL
 126        self.canvas.stroke_style = Sketch._DEFAULT_STROKE
 127        self.canvas.line_width = Sketch._DEFAULT_STROKE_WEIGHT
 128        self.canvas.line_cap = Sketch._DEFAULT_LINE_CAP
 129        self._is_fill_set = False
 130        self._is_stroke_set = False
 131        self._is_stroke_weight_set = False
 132        self._font = Sketch._DEFAULT_FONT
 133        self._font_size = Sketch._DEFAULT_FONT_SIZE
 134        self._font_style = Sketch._DEFAULT_FONT_STYLE
 135        self._font_weight = Sketch._DEFAULT_FONT_WEIGHT
 136        self._text_align = Sketch._DEFAULT_TEXT_ALIGN
 137        self._text_baseline = Sketch._DEFAULT_TEXT_BASELINE
 138        self.canvas.text_align = self._text_align
 139        self.canvas.text_baseline = self._text_baseline
 140        self.canvas.font = (
 141            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
 142        )
 143
 144    # ========================================
 145    #                 Color
 146    # ========================================
 147
 148    def background(self, *args):
 149        """Sets the color used for the background of the canvas.
 150
 151        The version of `background()` with one parameter interprets the value
 152        one of four ways. If the parameter is an int or float, it's
 153        interpreted as a grayscale value. If the parameter is a string,
 154        it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex,
 155        and named color strings are supported.
 156
 157        The version of `background()` with two parameters interprets the first
 158        one as a grayscale value. The second parameter sets the alpha
 159        (transparency) value.
 160
 161        The version of `background()` with three parameters interprets them as
 162        RGB. Calling `background(255, 204, 0)` sets the background a bright
 163        yellow color.
 164
 165        The version of `background()` with four parameters interprets them as
 166        RGBA. Calling `background(255, 204, 0, 20)` sets the background a
 167        bright yellow color that is transparent.
 168
 169        **Example**
 170        ```python
 171        from ipycc.sketch import Sketch
 172
 173        s = Sketch()
 174        s.show()
 175        ```
 176
 177        ```python
 178        # A grayscale value.
 179        s.background(51)
 180        ```
 181
 182        ```python
 183        # A grayscale value and an alpha value.
 184        s.background(51, 0.4)
 185        ```
 186
 187        ```python
 188        # R, G & B values.
 189        s.background(255, 204, 0)
 190        ```
 191
 192        ```python
 193        # A CSS named color.
 194        s.background("red")
 195        ```
 196
 197        ```python
 198        # Integer RGBA notation.
 199        s.background("rgba(0, 255, 0, 0.25)")
 200        ```
 201
 202        ```python
 203        # R, G, B & A values.
 204        s.background(0, 255, 0, 64)
 205        ```
 206        """
 207        if len(args) == 0:
 208            return
 209        color = self._colorstr(*args)
 210        self.canvas.save()
 211        self.canvas.reset_transform()
 212        self.canvas.scale(self.pixel_density, y=self.pixel_density)
 213        old_fill = self.canvas.fill_style
 214        old_stroke = self.canvas.stroke_style
 215        old_weight = self.canvas.line_width
 216        self.canvas.fill_style = color
 217        self.canvas.stroke_style = color
 218        self.canvas.line_width = 1
 219        self.canvas.fill_rect(0, 0, self.canvas.width, self.canvas.height)
 220        self.canvas.stroke_rect(0, 0, self.canvas.width, self.canvas.height)
 221        self.canvas.fill_style = old_fill
 222        self.canvas.stroke_style = old_stroke
 223        self.canvas.line_width = old_weight
 224        self.canvas.restore()
 225
 226    def _colorstr(self, *args) -> str:
 227        """Return a CSS color string corresponding to args.
 228        """
 229        num = (int, float)
 230        color = ""
 231        if len(args) == 1:
 232            c = args[0]
 233            if isinstance(c, num):
 234                color = f"rgb({c}, {c}, {c})"
 235            elif isinstance(color, str):
 236                color = c
 237        elif len(args) == 2:
 238            c = args[0]
 239            a = args[1]
 240            if isinstance(c, num) and isinstance(a, num):
 241                color = f"rgba({c}, {c}, {c}, {a / 255})"
 242        elif len(args) == 3:
 243            r = args[0]
 244            g = args[1]
 245            b = args[2]
 246            if isinstance(r, num) and isinstance(g, num) and isinstance(b, num):
 247                color = f"rgb({r}, {g}, {b})"
 248        elif len(args) == 4:
 249            r = args[0]
 250            g = args[1]
 251            b = args[2]
 252            a = args[3]
 253            if isinstance(r, num) and isinstance(g, num) and isinstance(b, num) and isinstance(a, num):
 254                color = f"rgba({r}, {g}, {b}, {a / 255})"
 255        return color
 256
 257    def fill(self, *args):
 258        """Sets the color used to fill shapes.
 259
 260        Calling `fill(255, 165, 0)` or `fill("orange")` means all shapes drawn
 261        after calling `fill()` will be filled with the color orange.
 262
 263        The version of `fill()` with one parameter interprets the value one of
 264        three ways. If the parameter is an int or float, it's interpreted as a
 265        grayscale value. If the parameter is a string, it's interpreted as a
 266        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
 267        are supported.
 268
 269        The version of `fill()` with two parameters interprets the first one
 270        as a grayscale value. The second parameter sets the alpha
 271        (transparency) value.
 272
 273        The version of `fill()` with three parameters interprets them as RGB
 274        colors.
 275
 276        The version of `fill()` with four parameters interprets them as RGBA
 277        colors. The last parameter sets the alpha (transparency) value.
 278
 279        **Example**
 280        ```python
 281        from ipycc.sketch import Sketch
 282
 283        s = Sketch()
 284        s.show()
 285        ```
 286
 287        ```python
 288        # A grayscale value.
 289        s.background(200)
 290        s.no_stroke()
 291        s.fill(51)
 292        s.square(20, 20, 60)
 293        ```
 294
 295        ```python
 296        # Grayscale and alpha values.
 297        s.background(200)
 298        s.no_stroke()
 299        s.fill(51, 64)
 300        s.square(20, 20, 60)
 301        ```
 302
 303        ```python
 304        # R, G & B values.
 305        s.background(200)
 306        s.no_stroke()
 307        s.fill(255, 204, 0)
 308        s.square(20, 20, 60)
 309        ```
 310
 311        ```python
 312        # A CSS named color.
 313        s.background(200)
 314        s.no_stroke()
 315        s.fill("red")
 316        s.square(20, 20, 60)
 317        ```
 318
 319        ```python
 320        # Integer RGBA notation.
 321        s.background(200)
 322        s.no_stroke()
 323        s.fill("rgba(0, 255, 0, 0.25)")
 324        s.square(20, 20, 60)
 325        ```
 326
 327        ```python
 328        # R, G, B & A values.
 329        s.background(200)
 330        s.no_stroke()
 331        s.fill(0, 255, 0, 64)
 332        s.square(20, 20, 60)
 333        ```
 334        """
 335        if len(args) == 0:
 336            return
 337        color = self._colorstr(*args)
 338        if not self._is_fill_set:
 339            self._is_fill_set = True
 340        self.canvas.fill_style = color
 341
 342    def no_fill(self):
 343        """Disables setting the fill color for shapes.
 344
 345        **Example**
 346        ```python
 347        from ipycc.sketch import Sketch
 348
 349        s = Sketch()
 350        s.show()
 351
 352        s.background(200)
 353        s.no_stroke()
 354        s.square(20, 20, 60)
 355        ```
 356        """
 357        if not self._is_fill_set:
 358            self._is_fill_set = True
 359        self.canvas.fill_style = Sketch._TRANSPARENT
 360
 361    def no_stroke(self):
 362        """Disables drawing points, lines, and the outlines of shapes.
 363
 364        **Example**
 365        ```python
 366        from ipycc.sketch import Sketch
 367
 368        s = Sketch()
 369        s.show()
 370
 371        s.background(200)
 372        s.no_stroke()
 373        s.square(20, 20, 60)
 374        ```
 375        """
 376        if not self._is_stroke_set:
 377            self._is_stroke_set = True
 378        self.canvas.stroke_style = Sketch._TRANSPARENT
 379
 380    def stroke(self, *args):
 381        """Sets the color used to draw points, lines, and the outlines of shapes.
 382
 383        Calling `stroke(255, 165, 0)` or `stroke("orange")` means all shapes
 384        drawn after calling `stroke()` will be outlined with the color orange.
 385
 386        The version of `stroke()` with one parameter interprets the value one
 387        of three ways. If the parameter is a number, it's interpreted as a
 388        grayscale value. If the parameter is a string, it's interpreted as a
 389        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
 390        are supported.
 391
 392        The version of `stroke()` with two parameters interprets the first one
 393        as a grayscale value. The second parameter sets the alpha
 394        (transparency) value.
 395
 396        The version of `stroke()` with three parameters interprets them as RGB
 397        colors.
 398
 399        The version of `stroke()` with four parameters interprets them as RGBA
 400        colors. The last parameter sets the alpha (transparency) value.
 401
 402        **Example**
 403        ```python
 404        from ipycc.sketch import Sketch
 405
 406        s = Sketch()
 407        s.show()
 408        ```
 409
 410        ```python
 411        # A grayscale value.
 412        s.background(200)
 413        s.stroke_weight(4)
 414        s.stroke(51)
 415        s.square(20, 20, 60)
 416        ```
 417
 418        ```python
 419        # Grayscale and alpha values.
 420        s.background(200)
 421        s.stroke(51, 64)
 422        s.square(20, 20, 60)
 423        ```
 424
 425        ```python
 426        # R, G & B values.
 427        s.background(200)
 428        s.stroke(255, 204, 0)
 429        s.square(20, 20, 60)
 430        ```
 431
 432        ```python
 433        # A CSS named color.
 434        s.background(200)
 435        s.stroke("red")
 436        s.square(20, 20, 60)
 437        ```
 438
 439        ```python
 440        # Integer RGBA notation.
 441        s.background(200)
 442        s.stroke("rgba(0, 255, 0, 0.25)")
 443        s.square(20, 20, 60)
 444        ```
 445
 446        ```python
 447        # R, G, B & A values.
 448        s.background(200)
 449        s.stroke(0, 255, 0, 64)
 450        s.square(20, 20, 60)
 451        ```
 452        """
 453        if len(args) == 0:
 454            return
 455        color = self._colorstr(*args)
 456        if not self._is_stroke_set:
 457            self._is_stroke_set = True
 458        self.canvas.stroke_style = color
 459
 460    def clear(self):
 461        """Clears all drawings on the canvas.
 462
 463        **Example**
 464        ```python
 465        from ipycc.sketch import Sketch
 466
 467        s = Sketch()
 468        s.show()
 469
 470        s.background(200)
 471        s.clear()
 472        ```
 473        """
 474        self.canvas.clear()
 475
 476    def reset(self):
 477        """Resets the canvas to its default state.
 478
 479        **Example**
 480        ```python
 481        from ipycc.sketch import Sketch
 482
 483        s = Sketch()
 484        s.show()
 485
 486        s.background(200)
 487        s.reset()
 488        ```
 489        """
 490        self.clear()
 491        self.canvas.reset_transform()
 492        self._init_style()
 493        self._init_transformation()
 494
 495    # ========================================
 496    #              2D Primitives
 497    # ========================================
 498
 499    def _acute_arc_to_bezier(self, start: int | float, size: int | float) -> dict:
 500        # Evaluate constants.
 501        alpha = size * 0.5
 502        cos_alpha = math.cos(alpha)
 503        sin_alpha = math.sin(alpha)
 504        cot_alpha = 1 / math.tan(alpha)
 505        # This is how far the arc needs to be rotated.
 506        phi = start + alpha
 507        cos_phi = math.cos(phi)
 508        sin_phi = math.sin(phi)
 509        lam = (4.0 - cos_alpha) / 3
 510        mu = sin_alpha + (cos_alpha - lam) * cot_alpha
 511
 512        # Return rotated waypoints.
 513        return {
 514            "ax": round(math.cos(start), 7),
 515            "ay": round(math.sin(start), 7),
 516            "bx": round((lam * cos_phi + mu * sin_phi), 7),
 517            "by": round((lam * sin_phi - mu * cos_phi), 7),
 518            "cx": round((lam * cos_phi - mu * sin_phi), 7),
 519            "cy": round((lam * sin_phi + mu * cos_phi), 7),
 520            "dx": round(math.cos(start + size), 7),
 521            "dy": round(math.sin(start + size), 7),
 522        }
 523
 524    def arc(
 525        self,
 526        x: int | float,
 527        y: int | float,
 528        w: int | float,
 529        h: int | float,
 530        start: int | float,
 531        stop: int | float,
 532    ):
 533        """Draws an arc.
 534
 535        An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
 536        `h` parameters. `x` and `y` set the location of the arc's center. `w`
 537        and `h` set the arc's width and height.
 538        
 539        The fifth and sixth parameters, `start` and `stop`, set the angles
 540        between which to draw the arc. Arcs are always drawn clockwise from
 541        `start` to `stop`.
 542
 543        **Example**
 544        ```python
 545        from ipycc.sketch import Sketch
 546
 547        s = Sketch()
 548        s.show()
 549
 550        s.background(200)
 551
 552        # Bottom-right.
 553        s.arc(50, 55, 50, 50, 0, s.HALF_PI)
 554
 555        s.no_fill()
 556        
 557        # Bottom-left.
 558        s.arc(50, 55, 60, 60, s.HALF_PI, s.PI)
 559        
 560        # Top-left.
 561        s.arc(50, 55, 70, 70, s.PI, s.PI + s.QUARTER_PI)
 562        
 563        # Top-right.
 564        s.arc(50, 55, 80, 80, s.PI + s.QUARTER_PI, s.TWO_PI)
 565        ```
 566        """
 567        rx = w * 0.5
 568        ry = h * 0.5
 569        epsilon = 0.00001  # Smallest visible angle on displays up to 4K.
 570        arc_to_draw = 0
 571        curves = []
 572
 573        # Create curves
 574        while stop - start >= epsilon:
 575            arc_to_draw = min(stop - start, Sketch.HALF_PI)
 576            curves.append(self._acute_arc_to_bezier(start, arc_to_draw))
 577            start += arc_to_draw
 578
 579        self.canvas.begin_path()
 580        for index, curve in enumerate(curves):
 581            if index == 0:
 582                self.canvas.move_to(x + curve["ax"] * rx, y + curve["ay"] * ry)
 583            self.canvas.bezier_curve_to(
 584                x + curve["bx"] * rx,
 585                y + curve["by"] * ry,
 586                x + curve["cx"] * rx,
 587                y + curve["cy"] * ry,
 588                x + curve["dx"] * rx,
 589                y + curve["dy"] * ry,
 590            )
 591        self.canvas.line_to(x, y)
 592        self.canvas.close_path()
 593        self.canvas.fill()
 594        self.canvas.stroke()
 595
 596    def ellipse(self, x: int | float, y: int | float, w: int | float, h: int | float):
 597        """Draws an ellipse (oval).
 598
 599        An ellipse is a round shape defined by the `x`, `y`, `w`, and `h`
 600        parameters. `x` and `y` set the location of its center. `w` and `h`
 601        set its width and height.
 602
 603        **Example**
 604        ```python
 605        from ipycc.sketch import Sketch
 606
 607        s = Sketch()
 608        s.show()
 609
 610        s.background(200)
 611
 612        # A circle.
 613        s.ellipse(20, 20, 40, 40)
 614        
 615        # An oval.
 616        s.ellipse(80, 80, 40, 20)
 617        ```
 618        """
 619        dx = w * 0.5
 620        dy = h * 0.5
 621        cx = x - dx
 622        cy = y - dy
 623
 624        kappa = 0.5522847498
 625        # control point offset horizontal
 626        ox = w * 0.5 * kappa
 627        # control point offset vertical
 628        oy = h * 0.5 * kappa
 629        # x-end
 630        xe = cx + w
 631        # y-end
 632        ye = cy + h
 633        # x-middle
 634        xm = cx + w * 0.5
 635        # y-middle
 636        ym = cy + h * 0.5
 637        self.canvas.begin_path()
 638        self.canvas.move_to(cx, ym)
 639        self.canvas.bezier_curve_to(cx, ym - oy, xm - ox, cy, xm, cy)
 640        self.canvas.bezier_curve_to(xm + ox, cy, xe, ym - oy, xe, ym)
 641        self.canvas.bezier_curve_to(xe, ym + oy, xm + ox, ye, xm, ye)
 642        self.canvas.bezier_curve_to(xm - ox, ye, cx, ym + oy, cx, ym)
 643        self.canvas.fill()
 644        self.canvas.stroke()
 645
 646    def circle(self, x: int | float, y: int | float, d: int | float):
 647        """Draws a circle.
 648
 649        A circle is a round shape defined by the `x`, `y`, and `d` parameters.
 650        `x` and `y` set the location of its center. `d` sets its width and
 651        height (diameter). Every point on the circle's edge is the same
 652        distance, `0.5 * d`, from its center. `0.5 * d` (half the diameter) is
 653        the circle's radius.
 654
 655        **Example**
 656        ```python
 657        from ipycc.sketch import Sketch
 658
 659        s = Sketch()
 660        s.show()
 661
 662        s.background(200)
 663        s.circle(50, 50, 25)
 664        ```
 665        """
 666        r = d * 0.5
 667        self.canvas.fill_circle(x, y, r)
 668        self.canvas.stroke_circle(x, y, r)
 669
 670    def line(self, x1: int | float, y1: int | float, x2: int | float, y2: int | float):
 671        """Draws a straight line between two points.
 672
 673        A line's default width is one pixel. The first two parameters set the
 674        starting coordinates of the line. The next two parameters set the
 675        ending coordinates of the line. To color a line, use the `stroke()`
 676        method. To change its width, use the `stroke_weight()` method.
 677
 678        **Example**
 679        ```python
 680        from ipycc.sketch import Sketch
 681
 682        s = Sketch()
 683        s.show()
 684
 685        s.background(200)
 686        s.line(30, 20, 85, 75)
 687        
 688        # Style the line.
 689        s.background(200)
 690        s.stroke("magenta")
 691        s.stroke_weight(5)
 692        s.line(30, 20, 85, 75)
 693        ```
 694        """
 695        self.canvas.stroke_line(x1, y1, x2, y2)
 696
 697    def point(self, x: int | float, y: int | float):
 698        """Draws a single point in space.
 699
 700        A point's default width is one pixel. To color a point, use the
 701        `stroke()` method. To change its width, use the `stroke_weight()`
 702        method. A point can't be filled, so the `fill()` method won't
 703        affect the point's color.
 704
 705        **Example**
 706        ```python
 707        from ipycc.sketch import Sketch
 708
 709        s = Sketch()
 710        s.show()
 711
 712        s.background(200)
 713        
 714        # Top-left.
 715        s.point(30, 20)
 716        
 717        # Top-right. 
 718        s.point(85, 20)
 719        
 720        # Style the next points.
 721        s.stroke("purple")
 722        s.stroke_weight(10)
 723        
 724        # Bottom-right.
 725        s.point(85, 75)
 726        
 727        # Bottom-left.
 728        s.point(30, 75)
 729        ```
 730        """
 731        s = f"{self.canvas.stroke_style}"
 732        f = f"{self.canvas.fill_style}"
 733        self.canvas.fill_style = s
 734        self.canvas.begin_path()
 735        self.canvas.arc(x, y, self.canvas.line_width * 0.5, 0, self.TWO_PI, False)
 736        self.canvas.fill()
 737        self.canvas.fill_style = f
 738
 739    def quad(
 740        self,
 741        x1: int | float,
 742        y1: int | float,
 743        x2: int | float,
 744        y2: int | float,
 745        x3: int | float,
 746        y3: int | float,
 747        x4: int | float,
 748        y4: int | float,
 749    ):
 750        """Draws a quadrilateral (four-sided shape).
 751
 752        Quadrilaterals include rectangles, squares, rhombuses, and trapezoids.
 753        The first pair of parameters `(x1, y1)` sets the quad's first point.
 754        The next three pairs of parameters set the coordinates for its next
 755        three points `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be
 756        added in either clockwise or counter-clockwise order.
 757
 758        **Example**
 759        ```python
 760        from ipycc.sketch import Sketch
 761
 762        s = Sketch()
 763        s.show()
 764
 765        s.background(200)
 766        s.quad(50, 62, 86, 50, 50, 38, 14, 50)
 767        ```
 768        """
 769        self.begin_shape()
 770        self.vertex(x1, y1)
 771        self.vertex(x2, y2)
 772        self.vertex(x3, y3)
 773        self.vertex(x4, y4)
 774        self.end_shape()
 775
 776    def rect(self, x: int | float, y: int | float, w: int | float, h: int | float):
 777        """Draws a rectangle.
 778
 779        A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and
 780        `h` parameters. `x` and `y` set the location of its top-left corner.
 781        `w` sets its width and `h` sets its height. Every angle in the
 782        rectangle measures 90Ëš.
 783
 784        **Example**
 785        ```python
 786        from ipycc.sketch import Sketch
 787
 788        s = Sketch()
 789        s.show()
 790
 791        s.background(200)
 792        s.rect(30, 20, 55, 40)
 793        ```
 794        """
 795        self.canvas.fill_rect(x, y, w, h)
 796        self.canvas.stroke_rect(x, y, w, h)
 797
 798    def square(self, x: int | float, y: int | float, s: int | float):
 799        """Draws a square.
 800
 801        A square is a four-sided shape defined by the `x`, `y`, and `s`
 802        parameters. `x` and `y` set the location of its top-left corner. `s`
 803        sets its width and height. Every angle in the square measures 90Ëš
 804        and all its sides are the same length. 
 805
 806        **Example**
 807        ```python
 808        from ipycc.sketch import Sketch
 809
 810        s = Sketch()
 811        s.show()
 812
 813        s.background(200)
 814        s.square(30, 20, 55)
 815        ```
 816        """
 817        self.canvas.fill_rect(x, y, s, s)
 818        self.canvas.stroke_rect(x, y, s, s)
 819
 820    def triangle(
 821        self,
 822        x1: int | float,
 823        y1: int | float,
 824        x2: int | float,
 825        y2: int | float,
 826        x3: int | float,
 827        y3: int | float,
 828    ):
 829        """Draws a triangle.
 830
 831        A triangle is a three-sided shape defined by three points. The first
 832        two parameters specify the triangle's first point `(x1, y1)`. The
 833        middle two parameters specify its second point `(x2, y2)`. And the
 834        last two parameters specify its third point `(x3, y3)`.
 835
 836        **Example**
 837        ```python
 838        from ipycc.sketch import Sketch
 839
 840        s = Sketch()
 841        s.show()
 842
 843        s.background(200)
 844        s.triangle(30, 75, 58, 20, 86, 75)
 845        ```
 846        """
 847        self.begin_shape()
 848        self.vertex(x1, y1)
 849        self.vertex(x2, y2)
 850        self.vertex(x3, y3)
 851        self.end_shape()
 852
 853    # ========================================
 854    #               Attributes
 855    # ========================================
 856
 857    def stroke_weight(self, weight: int | float):
 858        """Sets the width of the stroke used for points, lines, and the outlines of shapes.
 859
 860        Note: stroke_weight() is affected by transformations, especially calls to scale().
 861        
 862        **Example**
 863        ```python
 864        from ipycc.sketch import Sketch
 865
 866        s = Sketch()
 867        s.show()
 868
 869        s.background(200)
 870
 871        # Top.
 872        s.line(20, 20, 80, 20)
 873
 874        # Middle.
 875        s.stroke_weight(4)
 876        s.line(20, 40, 80, 40)
 877
 878        # Bottom.
 879        s.stroke_weight(10)
 880        s.line(20, 70, 80, 70)
 881        ```
 882        """
 883        if not self._is_stroke_weight_set:
 884            self._is_stroke_weight_set = True
 885        self.canvas.line_width = weight
 886
 887    # ========================================
 888    #               Curves
 889    # ========================================
 890
 891    def bezier(
 892        self,
 893        x1: int | float,
 894        y1: int | float,
 895        x2: int | float,
 896        y2: int | float,
 897        x3: int | float,
 898        y3: int | float,
 899        x4: int | float,
 900        y4: int | float,
 901    ):
 902        """Draws a Bézier curve.
 903
 904        Bézier curves can form shapes and curves that slope gently. They're
 905        defined by two anchor points and two control points.
 906
 907        The first two parameters, `x1` and `y1`, set the first anchor point.
 908        The first anchor point is where the curve starts.
 909
 910        The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two
 911        control points. The control points "pull" the curve towards them.
 912
 913        The seventh and eighth parameters, `x4` and `y4`, set the last anchor
 914        point. The last anchor point is where the curve ends.
 915
 916        **Example**
 917        ```python
 918        from ipycc.sketch import Sketch
 919
 920        s = Sketch()
 921        s.show()
 922
 923        s.background(200)
 924
 925        # Draw the anchor points in black.
 926        s.stroke(0)
 927        s.stroke_weight(5)
 928        s.point(85, 20)
 929        s.point(15, 80)
 930
 931        # Draw the control points in red.
 932        s.stroke(255, 0, 0)
 933        s.point(10, 10)
 934        s.point(90, 90)
 935
 936        # Draw a black bezier curve.
 937        s.no_fill()
 938        s.stroke(0)
 939        s.stroke_weight(1)
 940        s.bezier(85, 20, 10, 10, 90, 90, 15, 80)
 941
 942        # Draw red lines from the anchor points to the control points.
 943        s.stroke(255, 0, 0)
 944        s.line(85, 20, 10, 10)
 945        s.line(15, 80, 90, 90)
 946        ```
 947        """
 948        self.canvas.begin_path()
 949        self.canvas.move_to(x1, y1)
 950        self.canvas.bezier_curve_to(x2, y2, x3, y3, x4, y4)
 951        self.canvas.stroke()
 952
 953    def bezier_point(
 954        self,
 955        a: int | float,
 956        b: int | float,
 957        c: int | float,
 958        d: int | float,
 959        t: int | float,
 960    ) -> float:
 961        """Calculates coordinates along a Bézier curve using interpolation.
 962
 963        `bezier_point()` calculates coordinates along a Bézier curve using the
 964        anchor and control points. It expects points in the same order as the
 965        bezier() method. `bezier_point()` works one axis at a time. Passing
 966        the anchor and control points' x-coordinates will calculate the
 967        x-coordinate of a point on the curve. Passing the anchor and control
 968        points' y-coordinates will calculate the y-coordinate of a point on
 969        the curve.
 970
 971        The first parameter, `a`, is the coordinate of the first anchor point.
 972
 973        The second and third parameters, `b` and `c`, are the coordinates of
 974        the control points.
 975
 976        The fourth parameter, `d`, is the coordinate of the last anchor point.
 977
 978        The fifth parameter, `t`, is the amount to interpolate along the
 979        curve. 0 is the first anchor point, 1 is the second anchor point, and
 980        0.5 is halfway between them.
 981
 982        **Example**
 983        ```python
 984        from ipycc.sketch import Sketch
 985
 986        s = Sketch()
 987        s.show()
 988
 989        s.background(200)
 990
 991        # Set the coordinates for the curve's anchor and control points.
 992        x1 = 85
 993        x2 = 10
 994        x3 = 90
 995        x4 = 15
 996        y1 = 20
 997        y2 = 10
 998        y3 = 90
 999        y4 = 80
1000
1001        # Style the curve.
1002        s.no_fill()
1003
1004        # Draw the curve.
1005        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1006
1007        # Draw circles along the curve's path.
1008        s.fill(255)
1009
1010        # Top-right.
1011        x = s.bezier_point(x1, x2, x3, x4, 0)
1012        y = s.bezier_point(y1, y2, y3, y4, 0)
1013        s.circle(x, y, 5)
1014
1015        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1016        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1017        s.circle(x, y, 5) # center circle
1018        x = s.bezier_point(x1, x2, x3, x4, 1)
1019        y = s.bezier_point(y1, y2, y3, y4, 1)
1020        s.circle(x, y, 5) # bottom-left circle
1021        ```
1022        """
1023        adjusted_t = 1 - t
1024        return (
1025            pow(adjusted_t, 3) * a
1026            + 3 * pow(adjusted_t, 2) * t * b
1027            + 3 * adjusted_t * pow(t, 2) * c
1028            + pow(t, 3) * d
1029        )
1030
1031    def bezier_tangent(
1032        self,
1033        a: int | float,
1034        b: int | float,
1035        c: int | float,
1036        d: int | float,
1037        t: int | float,
1038    ) -> float:
1039        """Calculates coordinates along a line that's tangent to a Bézier curve.
1040
1041        Tangent lines skim the surface of a curve. A tangent line's slope
1042        equals the curve's slope at the point where it intersects.
1043
1044        `bezier_tangent()` calculates coordinates along a tangent line using
1045        the Bézier curve's anchor and control points. It expects points in the
1046        same order as the `bezier()` method. `bezier_tangent()` works one axis
1047        at a time. Passing the anchor and control points' x-coordinates will
1048        calculate the x-coordinate of a point on the tangent line. Passing the
1049        anchor and control points' y-coordinates will calculate the
1050        y-coordinate of a point on the tangent line.
1051
1052        The first parameter, `a`, is the coordinate of the first anchor point.
1053
1054        The second and third parameters, `b` and `c`, are the coordinates of
1055        the control points.
1056
1057        The fourth parameter, `d`, is the coordinate of the last anchor point.
1058
1059        The fifth parameter, `t`, is the amount to interpolate along the curve.
1060        0 is the first anchor point, 1 is the second anchor point, and 0.5 is
1061        halfway between them.
1062
1063        **Example**
1064        ```python
1065        from ipycc.sketch import Sketch
1066
1067        s = Sketch()
1068        s.show()
1069
1070        s.background(200)
1071
1072        # Set the coordinates for the curve's anchor and control points.
1073        x1 = 85
1074        x2 = 10
1075        x3 = 90
1076        x4 = 15
1077        y1 = 20
1078        y2 = 10
1079        y3 = 90
1080        y4 = 80
1081
1082        # Style the curve.
1083        s.no_fill()
1084
1085        # Draw the curve.
1086        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1087
1088        # Draw tangents along the curve's path.
1089        s.fill(255)
1090
1091        # Top-right circle.
1092        s.stroke(0)
1093        x = s.bezier_point(x1, x2, x3, x4, 0)
1094        y = s.bezier_point(y1, y2, y3, y4, 0)
1095        s.circle(x, y, 5)
1096
1097        # Top-right tangent line.
1098        # Scale the tangent point to draw a shorter line.
1099        s.stroke(255, 0, 0) 
1100        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0)
1101        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0)
1102        s.line(x + tx, y + ty, x - tx, y - ty)
1103
1104        # Center circle.
1105        s.stroke(0)
1106        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1107        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1108        s.circle(x, y, 5)
1109        
1110        # Center tangent line.
1111        # Scale the tangent point to draw a shorter line.
1112        stroke(255, 0, 0)
1113        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0.5)
1114        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0.5)
1115        s.line(x + tx, y + ty, x - tx, y - ty)
1116
1117        # Bottom-left circle.
1118        stroke(0)
1119        x = s.bezier_point(x1, x2, x3, x4, 1)
1120        y = s.bezier_point(y1, y2, y3, y4, 1)
1121        s.circle(x, y, 5)
1122        
1123        # Bottom-left tangent.
1124        # Scale the tangent point to draw a shorter line.
1125        s.stroke(255, 0, 0)
1126        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 1)
1127        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 1)
1128        s.line(x + tx, y + ty, x - tx, y - ty)
1129        ```
1130        """
1131        adjusted_t = 1 - t
1132        return (
1133            3 * d * pow(t, 2)
1134            - 3 * c * pow(t, 2)
1135            + 6 * c * adjusted_t * t
1136            - 6 * b * adjusted_t * t
1137            + 3 * b * pow(adjusted_t, 2)
1138            - 3 * a * pow(adjusted_t, 2)
1139        )
1140
1141    # ========================================
1142    #                Vertex
1143    # ========================================
1144
1145    def begin_shape(self):
1146        """Begins adding vertices to a custom shape.
1147
1148        The `begin_shape()` and `end_shape()` methods allow for creating
1149        custom shapes. `begin_shape()` begins adding vertices to a custom
1150        shape and `end_shape()` stops adding them. After calling
1151        `begin_shape()`, shapes can be built by calling `vertex()`.
1152
1153        **Example**
1154        ```python
1155        from ipycc.sketch import Sketch
1156
1157        s = Sketch()
1158        s.show()
1159
1160        s.background(200)
1161        s.begin_shape() # begin drawing
1162        s.vertex(30, 20)
1163        s.vertex(85, 20)
1164        s.vertex(85, 75)
1165        s.vertex(30, 75)
1166        s.end_shape() # end drawing
1167        ```
1168        """
1169        self._vertices.clear()
1170
1171    def end_shape(self):
1172        """Stops adding vertices to a custom shape.
1173
1174        The `begin_shape()` and `end_shape()` methods allow for creating
1175        custom shapes. `begin_shape()` begins adding vertices to a custom
1176        shape and `end_shape()` stops adding them. After calling
1177        `begin_shape()`, shapes can be built by calling `vertex()`.
1178
1179        **Example**
1180        ```python
1181        from ipycc.sketch import Sketch
1182
1183        s = Sketch()
1184        s.show()
1185
1186        s.background(200)
1187        s.begin_shape() # begin drawing
1188        s.vertex(30, 20)
1189        s.vertex(85, 20)
1190        s.vertex(85, 75)
1191        s.vertex(30, 75)
1192        s.end_shape() # end drawing
1193        ```
1194        """
1195        if len(self._vertices) > 0:
1196            self.canvas.fill_polygon(self._vertices)
1197            self.canvas.stroke_polygon(self._vertices)
1198            self._vertices.clear()
1199
1200    def vertex(self, x: int | float, y: int | float):
1201        """Adds a vertex to a custom shape.
1202
1203        `vertex()` sets the coordinates of vertices drawn between the
1204        `begin_shape()` and `end_shape()` methods.
1205
1206        **Example**
1207        ```python
1208        from ipycc.sketch import Sketch
1209
1210        s = Sketch()
1211        s.show()
1212
1213        s.background(200)
1214        s.begin_shape() # begin drawing
1215        s.vertex(30, 20)
1216        s.vertex(85, 20)
1217        s.vertex(85, 75)
1218        s.vertex(30, 75)
1219        s.end_shape() # end drawing
1220        ```
1221        """
1222        self._vertices.append((x, y))
1223
1224    # ========================================
1225    #                Structure
1226    # ========================================
1227
1228    def show(self):
1229        """Display the sketch beneath the current code cell.
1230
1231        **Example**
1232        ```python
1233        from ipycc.sketch import Sketch
1234
1235        s = Sketch()
1236        s.show()
1237
1238        s.background(200)
1239        s.show()
1240        ```
1241        """
1242        display(self.canvas)
1243
1244    # ========================================
1245    #               Transform
1246    # ========================================
1247
1248    def apply_matrix(
1249        self,
1250        a: int | float,
1251        b: int | float,
1252        c: int | float,
1253        d: int | float,
1254        e: int | float,
1255        f: int | float,
1256    ):
1257        """Applies a transformation matrix to the coordinate system.
1258
1259        Transformations such as `translate()`, `rotate()`, and `scale()` use
1260        matrix-vector multiplication behind the scenes. A table of numbers,
1261        called a matrix, encodes each transformation. The values in the matrix
1262        then multiply each point on the canvas, which is represented by a
1263        vector.
1264
1265        `apply_matrix()` allows for many transformations to be applied at once.
1266        See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web)
1267        for more details about transformations.
1268
1269        **Example**
1270        ```python
1271        from ipycc.sketch import Sketch
1272
1273        s = Sketch()
1274        s.show()
1275
1276        s.background(200)
1277        
1278        # Translate the origin to the center.
1279        s.apply_matrix(1, 0, 0, 1, 50, 50)
1280        
1281        # Draw the circle at coordinates (0, 0).
1282        s.circle(0, 0, 40)
1283        ```
1284        """
1285        m = np.array(((a, c, e), (b, d, f), (0, 0, 1)), dtype=float)
1286        self._matrix = m @ self._matrix
1287        self.canvas.transform(a, b, c, d, e, f)
1288
1289    def reset_matrix(self):
1290        """Clears all transformations applied to the coordinate system.
1291
1292        **Example**
1293        ```python
1294        from ipycc.sketch import Sketch
1295
1296        s = Sketch()
1297        s.show()
1298
1299        s.background(200)
1300        
1301        # Translate the origin to the center.
1302        s.translate(50, 50)
1303        
1304        # Draw a blue circle at the coordinates (25, 25).
1305        s.fill("blue")
1306        s.circle(25, 25, 20)
1307        
1308        # Clear all transformations.
1309        # The origin is now at the top-left corner.
1310        s.reset_matrix()
1311        
1312        # Draw a red circle at the coordinates (25, 25).
1313        s.fill("red")
1314        s.circle(25, 25, 20)
1315        ```
1316        """
1317        self._matrix = np.eye(3)
1318        self.canvas.reset_transform()
1319
1320    def rotate(self, angle: int | float):
1321        """Rotates the coordinate system.
1322
1323        By default, the positive x-axis points to the right and the positive
1324        y-axis points downward. The `rotate()` method changes this orientation
1325        by rotating the coordinate system about the origin. Everything drawn
1326        after `rotate()` is called will appear to be rotated. Angles are
1327        measured in radians.
1328
1329        **Example**
1330        ```python
1331        from ipycc.sketch import Sketch
1332
1333        s = Sketch()
1334        s.show()
1335
1336        s.background(200)
1337
1338        # Rotate the coordinate system 1/8 turn.
1339        s.rotate(s.QUARTER_PI)
1340
1341        # Draw a rectangle at coordinates (50, 0).
1342        s.rect(50, 0, 40, 20)
1343        ```
1344        """
1345        ca, sa = math.cos(angle), math.sin(angle)
1346        m = np.array(((ca, -sa, 0), (sa, ca, 0), (0, 0, 1)), dtype=float)
1347        self._matrix = m @ self._matrix
1348        self.canvas.rotate(angle)
1349
1350    def scale(self, x: int | float, y: int | float = None):
1351        """Scales the coordinate system.
1352
1353        By default, shapes are drawn at their original scale. A rectangle
1354        that's 50 pixels wide appears to take up half the width of a 100
1355        pixel-wide canvas. The `scale()` method can shrink or stretch the
1356        coordinate system so that shapes appear at different sizes.
1357
1358        The first parameter, `s`, sets the amount to scale each axis. For
1359        example, calling `scale(2)` stretches the x- and y-axes by a factor
1360        of 2. The next parameter, `y`, is optional. It sets the amount to
1361        scale the y-axis. For example, calling `scale(2, 0.5)` stretches the
1362        x-axis by a factor of 2 and shrinks the y-axis by a factor of 0.5.
1363
1364        **Example**
1365        ```python
1366        from ipycc.sketch import Sketch
1367
1368        s = Sketch()
1369        s.show()
1370
1371        s.background(200)
1372        
1373        # Draw a square at (30, 20).
1374        s.square(30, 20, 40)
1375        
1376        # Scale the coordinate system by a factor of 0.5.
1377        s.scale(0.5)
1378        
1379        # Draw a square at (30, 20).
1380        # It appears at (15, 10) after scaling.
1381        s.square(30, 20, 40)
1382        s.background(200)
1383        s.reset_matrix()
1384        
1385        # Draw a square at (30, 20).
1386        s.square(30, 20, 40)
1387        
1388        # Scale the coordinate system by factors of
1389        # 0.5 along the x-axis and
1390        # 1.3 along the y-axis.
1391        s.scale(0.5, 1.3)
1392        
1393        # Draw a square at (30, 20).
1394        # It appears as a rectangle at (15, 26) after scaling.
1395        s.square(30, 20, 40)
1396        ```
1397        """
1398        if y is None:
1399            m = np.array(((x, 0, 0), (0, x, 0), (0, 0, 1)), dtype=float)
1400            self._matrix = m @ self._matrix
1401            self.canvas.scale(x)
1402        else:
1403            m = np.array(((x, 0, 0), (0, y, 0), (0, 0, 1)), dtype=float)
1404            self._matrix = m @ self._matrix
1405            self.canvas.scale(x, y=y)
1406
1407    def shear_x(self, angle: int | float):
1408        """Shears the x-axis so that shapes appear skewed.
1409
1410        By default, the x- and y-axes are perpendicular. The `shear_x()`
1411        method transforms the coordinate system so that x-coordinates are
1412        translated while y-coordinates are fixed.
1413
1414        **Example**
1415        ```python
1416        from ipycc.sketch import Sketch
1417
1418        s = Sketch()
1419        s.show()
1420
1421        s.background(200)
1422
1423        # Shear the coordinate system along the x-axis.
1424        s.shear_x(s.QUARTER_PI)
1425
1426        # Draw the square.
1427        s.square(0, 0, 50)
1428        ```
1429        """
1430        self.apply_matrix(1, 0, math.tan(angle), 1, 0, 0)
1431
1432    def shear_y(self, angle: int | float):
1433        """Shears the y-axis so that shapes appear skewed.
1434
1435        By default, the x- and y-axes are perpendicular. The `shear_y()`
1436        method transforms the coordinate system so that y-coordinates are
1437        translated while x-coordinates are fixed.
1438
1439        **Example**
1440        ```python
1441        from ipycc.sketch import Sketch
1442
1443        s = Sketch()
1444        s.show()
1445
1446        s.background(200)
1447
1448        # Shear the coordinate system along the y-axis.
1449        s.shear_y(s.QUARTER_PI)
1450
1451        # Draw the square.
1452        s.square(0, 0, 50)
1453        ```
1454        """
1455        self.apply_matrix(1, math.tan(angle), 0, 1, 0, 0)
1456
1457    def translate(self, x: int | float, y: int | float):
1458        """Translates the coordinate system.
1459
1460        By default, the origin (0, 0) is at the sketch's top-left corner. The
1461        `translate()` method shifts the origin to a different position.
1462        Everything drawn after `translate()` is called will appear to be
1463        shifted.
1464    
1465        **Example**
1466        ```python
1467        from ipycc.sketch import Sketch
1468
1469        s = Sketch()
1470        s.show()
1471
1472        s.background(200)
1473
1474        # Translate the origin to the center.
1475        s.translate(50, 50)
1476
1477        # Draw a circle at coordinates (0, 0).
1478        s.circle(0, 0, 40)
1479        ```
1480        """
1481        m = np.array(((0, 0, x), (0, 0, y), (0, 0, 1)), dtype=float)
1482        self._matrix = m @ self._matrix
1483        self.canvas.translate(x, y)
1484
1485    # ========================================
1486    #                  Image
1487    # ========================================
1488
1489    def image(
1490        self,
1491        img: Self,
1492        x: int | float,
1493        y: int | float,
1494        width: int | float = None,
1495        height: int | float = None,
1496    ):
1497        """Draws an image to the canvas.
1498
1499        The first parameter, `img`, is the source image to be drawn. `img` can be
1500        another `Sketch` instance.
1501
1502        The second and third parameters, `x` and `y`, set the coordinates of the
1503        destination image's top left corner.
1504
1505        The fourth and fifth parameters, `width` and `height`, are optional. They
1506        set the the width and height to draw the destination image. By
1507        default, `image()` draws the full source image at its original size.
1508
1509        **Example**
1510        ```python
1511        from ipycc.sketch import Sketch
1512
1513        # Create the Sketches.
1514        s1 = Sketch()
1515        s2 = Sketch()
1516
1517        # Draw to s1.
1518        s1.background(200)
1519        s1.circle(50, 50, 20)
1520
1521        # Draw s1 on s2 at full size.
1522        s2.image(s1, 0, 0)
1523
1524        # Draw s1 on s2 at half size.
1525        s2.image(s1, 0, 0, 50, 50)
1526
1527        # Show s2.
1528        s2.show()
1529        ```
1530        """
1531        if width is None:
1532            width = img.width
1533        if height is None:
1534            height = img.height
1535        self.canvas.draw_image(img.canvas, x=x, y=y, width=width, height=height)
1536
1537    # ========================================
1538    #                Typography
1539    # ========================================
1540
1541    def text(self, text: str, x: int | float, y: int | float):
1542        """Draws text to the canvas.
1543
1544        The first parameter, `text`, is the text to be drawn. The second and
1545        third parameters, `x` and `y`, set the coordinates of the text's
1546        bottom-left corner. See `text_align()` for other ways to align text.
1547
1548        **Example**
1549        ```python
1550        from ipycc.sketch import Sketch
1551
1552        s = Sketch()
1553        s.show()
1554        ```
1555
1556        ```python
1557        # Plain text.
1558        s.background(200)
1559        s.text("hi", 50, 50)
1560        ```
1561        
1562        
1563        ```python
1564        # Emoji.
1565        s.background("skyblue")
1566        s.text_size(100)
1567        s.text("🌈", 0, 100)
1568        ```
1569        
1570        ```python
1571        # No fill.
1572        s.background(200)
1573        s.text_size(32)
1574        s.fill(255)
1575        s.stroke(0)
1576        s.stroke_weight(4)
1577        s.text("hi", 50, 50)
1578        ```
1579        
1580        ```python
1581        # Multicolor text.
1582        s.background("black")
1583        s.text_size(22)
1584        s.fill("yellow")
1585        s.text("rainbows", 6, 20)
1586        s.fill("cornflowerblue")
1587        s.text("rainbows", 6, 45)
1588        s.fill("tomato")
1589        s.text("rainbows", 6, 70)
1590        s.fill("limegreen")
1591        s.text("rainbows", 6, 95)
1592        ```
1593        """
1594        if self._is_fill_set:
1595            self.canvas.fill_text(text, x, y)
1596        else:
1597            self.canvas.fill_style = Sketch._DEFAULT_TEXT_FILL
1598            self.canvas.fill_text(text, x, y)
1599            self.canvas.fill_style = Sketch._DEFAULT_FILL
1600
1601        if self._is_stroke_set:
1602            if self._is_stroke_weight_set:
1603                self.canvas.stroke_text(text, x, y)
1604            else:
1605                self.canvas.line_width = Sketch._DEFAULT_TEXT_WEIGHT
1606                self.canvas.stroke_text(text, x, y)
1607                self.canvas.line_width = Sketch._DEFAULT_STROKE_WEIGHT
1608
1609    def text_font(self, font: str):
1610        """Sets the font used by the `text()` method.
1611
1612        The font should be a string with the name of a system font such as
1613        `"Courier New"`.
1614
1615        **Example**
1616        ```python
1617        from ipycc.sketch import Sketch
1618
1619        s = Sketch()
1620        s.show()
1621
1622        s.background(200)
1623        s.text_font("Courier New")
1624        s.text_size(24)
1625        s.text("hi", 35, 55)
1626        ```
1627        """
1628        self._font = font
1629        self.canvas.font = (
1630            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1631        )
1632
1633    def text_size(self, size: int | float):
1634        """Sets the font size when `text()` is called.
1635
1636        Note: Font size is measured in pixels.
1637
1638        **Example**
1639        ```python
1640        from ipycc.sketch import Sketch
1641
1642        s = Sketch()
1643        s.show()
1644
1645        s.background(200)
1646
1647        # Top row.
1648        s.text_size(12)
1649        s.text("Font Size 12", 10, 30)
1650
1651        # Middle row.
1652        s.text_size(14)
1653        s.text("Font Size 14", 10, 60)
1654
1655        # Bottom row.
1656        s.text_size(16)
1657        s.text("Font Size 16", 10, 90)
1658        ```
1659        """
1660        self._font_size = size
1661        self.canvas.font = (
1662            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1663        )
1664
1665    def text_align(self, horizontal: str, vertical: str = None):
1666        """Sets the way text is aligned when `text()` is called.
1667
1668        By default, calling `text("hi", 10, 20)` places the bottom-left corner
1669        of the text's bounding box at (10, 20).
1670
1671        The first parameter, `horizontal`, changes the way `text()` interprets
1672        x-coordinates. By default, the x-coordinate sets the left edge of the
1673        bounding box. `text_align()` accepts the following values for horizontal:
1674        `LEFT`, `CENTER`, or `RIGHT`.
1675
1676        The second parameter, `vertical`, is optional. It changes the way `text()`
1677        interprets y-coordinates. By default, the y-coordinate sets the bottom
1678        edge of the bounding box. `text_align()` accepts the following values
1679        for vertical: `TOP`, `BOTTOM`, `CENTER`, or `BASELINE`.
1680
1681        **Example**
1682        ```python
1683        from ipycc.sketch import Sketch
1684
1685        s = Sketch()
1686        s.show()
1687
1688        s.background(200)
1689
1690        # Draw a vertical line.
1691        s.stroke_weight(0.5)
1692        s.line(50, 0, 50, 100)
1693
1694        # Top row.
1695        s.text_size(16)
1696        s.text_align(s.RIGHT)
1697        s.text("ABCD", 50, 30)
1698        
1699        # Middle row.
1700        s.text_align(s.CENTER)
1701        s.text("EFGH", 50, 50)
1702        
1703        # Bottom row.
1704        s.text_align(s.LEFT)
1705        s.text("IJKL", 50, 70)
1706        ```
1707        """
1708        if horizontal == Sketch.LEFT:
1709            self._text_align = Sketch.LEFT
1710        elif horizontal == Sketch.RIGHT:
1711            self._text_align = Sketch.RIGHT
1712        elif horizontal == Sketch.CENTER:
1713            self._text_align = Sketch.CENTER
1714        self.canvas.text_align = self._text_align
1715
1716        if vertical is None:
1717            return
1718        if vertical == Sketch.TOP:
1719            self._text_baseline = Sketch.TOP
1720        elif vertical == Sketch.BOTTOM:
1721            self._text_baseline = Sketch.BOTTOM
1722        elif vertical == Sketch.CENTER:
1723            self._text_baseline = Sketch.CENTER
1724        elif vertical == Sketch.BASELINE:
1725            self._text_baseline = Sketch.BASELINE
1726        self.canvas.text_baseline = vertical
1727
1728    def text_style(self, style: str):
1729        """Sets the style for system fonts when `text()` is called.
1730
1731        The parameter, `style`, can be either `NORMAL`, `ITALIC`, `BOLD`, or
1732        `BOLDITALIC`.
1733
1734        **Example**
1735        ```python
1736        from ipycc.sketch import Sketch
1737
1738        s = Sketch()
1739        s.show()
1740
1741        s.background(200)
1742
1743        # First row.
1744        s.text_size(12)
1745        s.text_style(s.NORMAL)
1746        s.text("Normal", 20, 15)
1747
1748        # Second row.
1749        s.text_style(s.ITALIC)
1750        s.text("Italic", 20, 40)
1751
1752        # Third row.
1753        s.text_style(s.BOLD)
1754        s.text("Bold", 20, 65)
1755
1756        # Fourth row.
1757        s.text_style(s.BOLDITALIC)
1758        s.text("Bold Italic", 20, 90)
1759        ```
1760        """
1761        if style == Sketch.NORMAL:
1762            self._font_weight = Sketch.NORMAL
1763            self._font_style = Sketch.NORMAL
1764        elif style == Sketch.ITALIC:
1765            self._font_weight = Sketch.NORMAL
1766            self._font_style = Sketch.ITALIC
1767        elif style == Sketch.BOLD:
1768            self._font_weight = Sketch.BOLD
1769            self._font_style = Sketch.NORMAL
1770        elif style == Sketch.BOLDITALIC:
1771            self._font_weight = Sketch.BOLD
1772            self._font_style = Sketch.ITALIC
1773        self.canvas.font = (
1774            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1775        )
1776
1777    # ========================================
1778    #                Utilities
1779    # ========================================
1780
1781    def _unpack_transform(self) -> tuple[float]:
1782        """Unpacks the sketch's transformation matrix."""
1783        a = self._matrix[0][0]
1784        b = self._matrix[1][0]
1785        c = self._matrix[0][1]
1786        d = self._matrix[1][1]
1787        e = self._matrix[2][0]
1788        f = self._matrix[2][2]
1789        return a, b, c, d, e, f
1790
1791    def run_sketch(self, draw: Callable, seconds: int | float, delay: float = 20):
1792        """Draws frames in an animation by calling a function repeatedly.
1793
1794        `run_sketch()` repeatedly calls a function that contains drawing
1795        commands. The rate at which each frame is drawn depends on many
1796        factors. `run_sketch()` doesn't attempt to maintain a constant
1797        framerate.
1798
1799        The first parameter, `draw`, is a function containing the commands for
1800        drawing each frame.
1801
1802        The second parameter, `seconds`, sets the number of seconds the
1803        animation should run.
1804
1805        The third parameter, `delay`, is optional. It sets the number of
1806        milliseconds the sketch should pause after drawing the current frame.
1807        The default value is 20. If `draw` contains many drawing commands,
1808        each frame may take much longer than `delay` milliseconds to render.
1809
1810        **Example**
1811        ```python
1812        from ipycc.sketch import Sketch
1813
1814        s = Sketch()
1815        s.show()
1816
1817        def draw():
1818            # Paint the background.
1819            s.background(200)
1820
1821            # Calculate the circle's x-coordinate.
1822            x = s.frame_count
1823
1824            # Draw the circle.
1825            s.circle(x, 50, 20)
1826
1827        # Run the animation for 5 seconds.
1828        s.run_sketch(draw, 5)
1829        ```
1830        """
1831        if delay < 0:
1832            raise SketchError("Your delay value must be positive.")
1833        start = time.time()
1834        end = start + seconds
1835        delay *= 0.001
1836        self.frame_count = 0
1837        while time.time() < end:
1838            with hold_canvas():
1839                a, b, c, d, e, f = self._unpack_transform()
1840                draw()
1841                self.reset_matrix()
1842                self.apply_matrix(a, b, c, d, e, f)
1843                self.frame_count += 1
1844                time.sleep(delay)

A class to describe a 2D drawing canvas.

Sketch(width: int = 100, height: int = 100, pixel_denstiy: int = 2)
 81    def __init__(
 82        self,
 83        width: int = 100,
 84        height: int = 100,
 85        pixel_denstiy: int = 2,
 86    ):
 87        # Create the Canvas.
 88        self.width: int = width
 89        """The width of the canvas in pixels."""
 90
 91        self.height: int = height
 92        """The height of the canvas in pixels."""
 93
 94        self.pixel_density: int = pixel_denstiy
 95        """The number of physical pixels used to draw a pixel on the canvas."""
 96
 97        self.canvas: Canvas = Canvas(
 98            width=width * pixel_denstiy,
 99            height=height * pixel_denstiy,
100            layout={"width": f"{width}px", "height": f"{height}px"})
101        """The `Canvas` widget used for drawing."""
102        
103        # Set default the styles.
104        self._init_style()
105        # Set the default transformations.
106        self._init_transformation()
107        # Create an empty list for shape vertices.
108        self._vertices = []
109        # Set the current frame count (for animation).
110        self.frame_count: int = 0
111        """The number of frames drawn since the sketch started."""
112
113        self._is_looping = False
HALF_PI: float = 1.5707963267948966

A number constant that's approximately 1.5708.

PI: float = 3.141592653589793

A number constant that's approximately 3.1416.

QUARTER_PI: float = 0.7853981633974483

A number constant that's approximately 0.7854.

TAU: float = 6.283185307179586

A number constant that's approximately 6.2382.

TWO_PI: float = 6.283185307179586

A number constant that's approximately 6.2382.

NORMAL: str = 'normal'

A string constant used with the text_style() method.

ITALIC: str = 'italic'

A string constant used with the text_style() method.

BOLD: str = 'bold'

A string constant used with the text_style() method.

BOLDITALIC: str = 'bolditalic'

A string constant used with the text_style() method.

LEFT: str = 'left'

A string constant used with the text_align() method.

CENTER: str = 'center'

A string constant used with the text_align() method.

RIGHT: str = 'right'

A string constant used with the text_align() method.

BOTTOM: str = 'bottom'

A string constant used with the text_align() method.

TOP: str = 'top'

A string constant used with the text_align() method.

BASELINE: str = 'alphabetic'

A string constant used with the text_align() method.

MIDDLE: str = 'middle'

A string constant used with the text_align() method.

width: int

The width of the canvas in pixels.

height: int

The height of the canvas in pixels.

pixel_density: int

The number of physical pixels used to draw a pixel on the canvas.

canvas: ipycanvas.canvas.Canvas

The Canvas widget used for drawing.

frame_count: int

The number of frames drawn since the sketch started.

def background(self, *args):
148    def background(self, *args):
149        """Sets the color used for the background of the canvas.
150
151        The version of `background()` with one parameter interprets the value
152        one of four ways. If the parameter is an int or float, it's
153        interpreted as a grayscale value. If the parameter is a string,
154        it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex,
155        and named color strings are supported.
156
157        The version of `background()` with two parameters interprets the first
158        one as a grayscale value. The second parameter sets the alpha
159        (transparency) value.
160
161        The version of `background()` with three parameters interprets them as
162        RGB. Calling `background(255, 204, 0)` sets the background a bright
163        yellow color.
164
165        The version of `background()` with four parameters interprets them as
166        RGBA. Calling `background(255, 204, 0, 20)` sets the background a
167        bright yellow color that is transparent.
168
169        **Example**
170        ```python
171        from ipycc.sketch import Sketch
172
173        s = Sketch()
174        s.show()
175        ```
176
177        ```python
178        # A grayscale value.
179        s.background(51)
180        ```
181
182        ```python
183        # A grayscale value and an alpha value.
184        s.background(51, 0.4)
185        ```
186
187        ```python
188        # R, G & B values.
189        s.background(255, 204, 0)
190        ```
191
192        ```python
193        # A CSS named color.
194        s.background("red")
195        ```
196
197        ```python
198        # Integer RGBA notation.
199        s.background("rgba(0, 255, 0, 0.25)")
200        ```
201
202        ```python
203        # R, G, B & A values.
204        s.background(0, 255, 0, 64)
205        ```
206        """
207        if len(args) == 0:
208            return
209        color = self._colorstr(*args)
210        self.canvas.save()
211        self.canvas.reset_transform()
212        self.canvas.scale(self.pixel_density, y=self.pixel_density)
213        old_fill = self.canvas.fill_style
214        old_stroke = self.canvas.stroke_style
215        old_weight = self.canvas.line_width
216        self.canvas.fill_style = color
217        self.canvas.stroke_style = color
218        self.canvas.line_width = 1
219        self.canvas.fill_rect(0, 0, self.canvas.width, self.canvas.height)
220        self.canvas.stroke_rect(0, 0, self.canvas.width, self.canvas.height)
221        self.canvas.fill_style = old_fill
222        self.canvas.stroke_style = old_stroke
223        self.canvas.line_width = old_weight
224        self.canvas.restore()

Sets the color used for the background of the canvas.

The version of background() with one parameter interprets the value one of four ways. If the parameter is an int or float, it's interpreted as a grayscale value. If the parameter is a string, it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings are supported.

The version of background() with two parameters interprets the first one as a grayscale value. The second parameter sets the alpha (transparency) value.

The version of background() with three parameters interprets them as RGB. Calling background(255, 204, 0) sets the background a bright yellow color.

The version of background() with four parameters interprets them as RGBA. Calling background(255, 204, 0, 20) sets the background a bright yellow color that is transparent.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()
# A grayscale value.
s.background(51)
# A grayscale value and an alpha value.
s.background(51, 0.4)
# R, G & B values.
s.background(255, 204, 0)
# A CSS named color.
s.background("red")
# Integer RGBA notation.
s.background("rgba(0, 255, 0, 0.25)")
# R, G, B & A values.
s.background(0, 255, 0, 64)
def fill(self, *args):
257    def fill(self, *args):
258        """Sets the color used to fill shapes.
259
260        Calling `fill(255, 165, 0)` or `fill("orange")` means all shapes drawn
261        after calling `fill()` will be filled with the color orange.
262
263        The version of `fill()` with one parameter interprets the value one of
264        three ways. If the parameter is an int or float, it's interpreted as a
265        grayscale value. If the parameter is a string, it's interpreted as a
266        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
267        are supported.
268
269        The version of `fill()` with two parameters interprets the first one
270        as a grayscale value. The second parameter sets the alpha
271        (transparency) value.
272
273        The version of `fill()` with three parameters interprets them as RGB
274        colors.
275
276        The version of `fill()` with four parameters interprets them as RGBA
277        colors. The last parameter sets the alpha (transparency) value.
278
279        **Example**
280        ```python
281        from ipycc.sketch import Sketch
282
283        s = Sketch()
284        s.show()
285        ```
286
287        ```python
288        # A grayscale value.
289        s.background(200)
290        s.no_stroke()
291        s.fill(51)
292        s.square(20, 20, 60)
293        ```
294
295        ```python
296        # Grayscale and alpha values.
297        s.background(200)
298        s.no_stroke()
299        s.fill(51, 64)
300        s.square(20, 20, 60)
301        ```
302
303        ```python
304        # R, G & B values.
305        s.background(200)
306        s.no_stroke()
307        s.fill(255, 204, 0)
308        s.square(20, 20, 60)
309        ```
310
311        ```python
312        # A CSS named color.
313        s.background(200)
314        s.no_stroke()
315        s.fill("red")
316        s.square(20, 20, 60)
317        ```
318
319        ```python
320        # Integer RGBA notation.
321        s.background(200)
322        s.no_stroke()
323        s.fill("rgba(0, 255, 0, 0.25)")
324        s.square(20, 20, 60)
325        ```
326
327        ```python
328        # R, G, B & A values.
329        s.background(200)
330        s.no_stroke()
331        s.fill(0, 255, 0, 64)
332        s.square(20, 20, 60)
333        ```
334        """
335        if len(args) == 0:
336            return
337        color = self._colorstr(*args)
338        if not self._is_fill_set:
339            self._is_fill_set = True
340        self.canvas.fill_style = color

Sets the color used to fill shapes.

Calling fill(255, 165, 0) or fill("orange") means all shapes drawn after calling fill() will be filled with the color orange.

The version of fill() with one parameter interprets the value one of three ways. If the parameter is an int or float, it's interpreted as a grayscale value. If the parameter is a string, it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings are supported.

The version of fill() with two parameters interprets the first one as a grayscale value. The second parameter sets the alpha (transparency) value.

The version of fill() with three parameters interprets them as RGB colors.

The version of fill() with four parameters interprets them as RGBA colors. The last parameter sets the alpha (transparency) value.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()
# A grayscale value.
s.background(200)
s.no_stroke()
s.fill(51)
s.square(20, 20, 60)
# Grayscale and alpha values.
s.background(200)
s.no_stroke()
s.fill(51, 64)
s.square(20, 20, 60)
# R, G & B values.
s.background(200)
s.no_stroke()
s.fill(255, 204, 0)
s.square(20, 20, 60)
# A CSS named color.
s.background(200)
s.no_stroke()
s.fill("red")
s.square(20, 20, 60)
# Integer RGBA notation.
s.background(200)
s.no_stroke()
s.fill("rgba(0, 255, 0, 0.25)")
s.square(20, 20, 60)
# R, G, B & A values.
s.background(200)
s.no_stroke()
s.fill(0, 255, 0, 64)
s.square(20, 20, 60)
def no_fill(self):
342    def no_fill(self):
343        """Disables setting the fill color for shapes.
344
345        **Example**
346        ```python
347        from ipycc.sketch import Sketch
348
349        s = Sketch()
350        s.show()
351
352        s.background(200)
353        s.no_stroke()
354        s.square(20, 20, 60)
355        ```
356        """
357        if not self._is_fill_set:
358            self._is_fill_set = True
359        self.canvas.fill_style = Sketch._TRANSPARENT

Disables setting the fill color for shapes.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.no_stroke()
s.square(20, 20, 60)
def no_stroke(self):
361    def no_stroke(self):
362        """Disables drawing points, lines, and the outlines of shapes.
363
364        **Example**
365        ```python
366        from ipycc.sketch import Sketch
367
368        s = Sketch()
369        s.show()
370
371        s.background(200)
372        s.no_stroke()
373        s.square(20, 20, 60)
374        ```
375        """
376        if not self._is_stroke_set:
377            self._is_stroke_set = True
378        self.canvas.stroke_style = Sketch._TRANSPARENT

Disables drawing points, lines, and the outlines of shapes.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.no_stroke()
s.square(20, 20, 60)
def stroke(self, *args):
380    def stroke(self, *args):
381        """Sets the color used to draw points, lines, and the outlines of shapes.
382
383        Calling `stroke(255, 165, 0)` or `stroke("orange")` means all shapes
384        drawn after calling `stroke()` will be outlined with the color orange.
385
386        The version of `stroke()` with one parameter interprets the value one
387        of three ways. If the parameter is a number, it's interpreted as a
388        grayscale value. If the parameter is a string, it's interpreted as a
389        CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings
390        are supported.
391
392        The version of `stroke()` with two parameters interprets the first one
393        as a grayscale value. The second parameter sets the alpha
394        (transparency) value.
395
396        The version of `stroke()` with three parameters interprets them as RGB
397        colors.
398
399        The version of `stroke()` with four parameters interprets them as RGBA
400        colors. The last parameter sets the alpha (transparency) value.
401
402        **Example**
403        ```python
404        from ipycc.sketch import Sketch
405
406        s = Sketch()
407        s.show()
408        ```
409
410        ```python
411        # A grayscale value.
412        s.background(200)
413        s.stroke_weight(4)
414        s.stroke(51)
415        s.square(20, 20, 60)
416        ```
417
418        ```python
419        # Grayscale and alpha values.
420        s.background(200)
421        s.stroke(51, 64)
422        s.square(20, 20, 60)
423        ```
424
425        ```python
426        # R, G & B values.
427        s.background(200)
428        s.stroke(255, 204, 0)
429        s.square(20, 20, 60)
430        ```
431
432        ```python
433        # A CSS named color.
434        s.background(200)
435        s.stroke("red")
436        s.square(20, 20, 60)
437        ```
438
439        ```python
440        # Integer RGBA notation.
441        s.background(200)
442        s.stroke("rgba(0, 255, 0, 0.25)")
443        s.square(20, 20, 60)
444        ```
445
446        ```python
447        # R, G, B & A values.
448        s.background(200)
449        s.stroke(0, 255, 0, 64)
450        s.square(20, 20, 60)
451        ```
452        """
453        if len(args) == 0:
454            return
455        color = self._colorstr(*args)
456        if not self._is_stroke_set:
457            self._is_stroke_set = True
458        self.canvas.stroke_style = color

Sets the color used to draw points, lines, and the outlines of shapes.

Calling stroke(255, 165, 0) or stroke("orange") means all shapes drawn after calling stroke() will be outlined with the color orange.

The version of stroke() with one parameter interprets the value one of three ways. If the parameter is a number, it's interpreted as a grayscale value. If the parameter is a string, it's interpreted as a CSS color string. RGB, RGBA, HSL, HSLA, hex, and named color strings are supported.

The version of stroke() with two parameters interprets the first one as a grayscale value. The second parameter sets the alpha (transparency) value.

The version of stroke() with three parameters interprets them as RGB colors.

The version of stroke() with four parameters interprets them as RGBA colors. The last parameter sets the alpha (transparency) value.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()
# A grayscale value.
s.background(200)
s.stroke_weight(4)
s.stroke(51)
s.square(20, 20, 60)
# Grayscale and alpha values.
s.background(200)
s.stroke(51, 64)
s.square(20, 20, 60)
# R, G & B values.
s.background(200)
s.stroke(255, 204, 0)
s.square(20, 20, 60)
# A CSS named color.
s.background(200)
s.stroke("red")
s.square(20, 20, 60)
# Integer RGBA notation.
s.background(200)
s.stroke("rgba(0, 255, 0, 0.25)")
s.square(20, 20, 60)
# R, G, B & A values.
s.background(200)
s.stroke(0, 255, 0, 64)
s.square(20, 20, 60)
def clear(self):
460    def clear(self):
461        """Clears all drawings on the canvas.
462
463        **Example**
464        ```python
465        from ipycc.sketch import Sketch
466
467        s = Sketch()
468        s.show()
469
470        s.background(200)
471        s.clear()
472        ```
473        """
474        self.canvas.clear()

Clears all drawings on the canvas.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.clear()
def reset(self):
476    def reset(self):
477        """Resets the canvas to its default state.
478
479        **Example**
480        ```python
481        from ipycc.sketch import Sketch
482
483        s = Sketch()
484        s.show()
485
486        s.background(200)
487        s.reset()
488        ```
489        """
490        self.clear()
491        self.canvas.reset_transform()
492        self._init_style()
493        self._init_transformation()

Resets the canvas to its default state.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.reset()
def arc( self, x: int | float, y: int | float, w: int | float, h: int | float, start: int | float, stop: int | float):
524    def arc(
525        self,
526        x: int | float,
527        y: int | float,
528        w: int | float,
529        h: int | float,
530        start: int | float,
531        stop: int | float,
532    ):
533        """Draws an arc.
534
535        An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
536        `h` parameters. `x` and `y` set the location of the arc's center. `w`
537        and `h` set the arc's width and height.
538        
539        The fifth and sixth parameters, `start` and `stop`, set the angles
540        between which to draw the arc. Arcs are always drawn clockwise from
541        `start` to `stop`.
542
543        **Example**
544        ```python
545        from ipycc.sketch import Sketch
546
547        s = Sketch()
548        s.show()
549
550        s.background(200)
551
552        # Bottom-right.
553        s.arc(50, 55, 50, 50, 0, s.HALF_PI)
554
555        s.no_fill()
556        
557        # Bottom-left.
558        s.arc(50, 55, 60, 60, s.HALF_PI, s.PI)
559        
560        # Top-left.
561        s.arc(50, 55, 70, 70, s.PI, s.PI + s.QUARTER_PI)
562        
563        # Top-right.
564        s.arc(50, 55, 80, 80, s.PI + s.QUARTER_PI, s.TWO_PI)
565        ```
566        """
567        rx = w * 0.5
568        ry = h * 0.5
569        epsilon = 0.00001  # Smallest visible angle on displays up to 4K.
570        arc_to_draw = 0
571        curves = []
572
573        # Create curves
574        while stop - start >= epsilon:
575            arc_to_draw = min(stop - start, Sketch.HALF_PI)
576            curves.append(self._acute_arc_to_bezier(start, arc_to_draw))
577            start += arc_to_draw
578
579        self.canvas.begin_path()
580        for index, curve in enumerate(curves):
581            if index == 0:
582                self.canvas.move_to(x + curve["ax"] * rx, y + curve["ay"] * ry)
583            self.canvas.bezier_curve_to(
584                x + curve["bx"] * rx,
585                y + curve["by"] * ry,
586                x + curve["cx"] * rx,
587                y + curve["cy"] * ry,
588                x + curve["dx"] * rx,
589                y + curve["dy"] * ry,
590            )
591        self.canvas.line_to(x, y)
592        self.canvas.close_path()
593        self.canvas.fill()
594        self.canvas.stroke()

Draws an arc.

An arc is a section of an ellipse defined by the x, y, w, and h parameters. x and y set the location of the arc's center. w and h set the arc's width and height.

The fifth and sixth parameters, start and stop, set the angles between which to draw the arc. Arcs are always drawn clockwise from start to stop.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Bottom-right.
s.arc(50, 55, 50, 50, 0, s.HALF_PI)

s.no_fill()

# Bottom-left.
s.arc(50, 55, 60, 60, s.HALF_PI, s.PI)

# Top-left.
s.arc(50, 55, 70, 70, s.PI, s.PI + s.QUARTER_PI)

# Top-right.
s.arc(50, 55, 80, 80, s.PI + s.QUARTER_PI, s.TWO_PI)
def ellipse(self, x: int | float, y: int | float, w: int | float, h: int | float):
596    def ellipse(self, x: int | float, y: int | float, w: int | float, h: int | float):
597        """Draws an ellipse (oval).
598
599        An ellipse is a round shape defined by the `x`, `y`, `w`, and `h`
600        parameters. `x` and `y` set the location of its center. `w` and `h`
601        set its width and height.
602
603        **Example**
604        ```python
605        from ipycc.sketch import Sketch
606
607        s = Sketch()
608        s.show()
609
610        s.background(200)
611
612        # A circle.
613        s.ellipse(20, 20, 40, 40)
614        
615        # An oval.
616        s.ellipse(80, 80, 40, 20)
617        ```
618        """
619        dx = w * 0.5
620        dy = h * 0.5
621        cx = x - dx
622        cy = y - dy
623
624        kappa = 0.5522847498
625        # control point offset horizontal
626        ox = w * 0.5 * kappa
627        # control point offset vertical
628        oy = h * 0.5 * kappa
629        # x-end
630        xe = cx + w
631        # y-end
632        ye = cy + h
633        # x-middle
634        xm = cx + w * 0.5
635        # y-middle
636        ym = cy + h * 0.5
637        self.canvas.begin_path()
638        self.canvas.move_to(cx, ym)
639        self.canvas.bezier_curve_to(cx, ym - oy, xm - ox, cy, xm, cy)
640        self.canvas.bezier_curve_to(xm + ox, cy, xe, ym - oy, xe, ym)
641        self.canvas.bezier_curve_to(xe, ym + oy, xm + ox, ye, xm, ye)
642        self.canvas.bezier_curve_to(xm - ox, ye, cx, ym + oy, cx, ym)
643        self.canvas.fill()
644        self.canvas.stroke()

Draws an ellipse (oval).

An ellipse is a round shape defined by the x, y, w, and h parameters. x and y set the location of its center. w and h set its width and height.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# A circle.
s.ellipse(20, 20, 40, 40)

# An oval.
s.ellipse(80, 80, 40, 20)
def circle(self, x: int | float, y: int | float, d: int | float):
646    def circle(self, x: int | float, y: int | float, d: int | float):
647        """Draws a circle.
648
649        A circle is a round shape defined by the `x`, `y`, and `d` parameters.
650        `x` and `y` set the location of its center. `d` sets its width and
651        height (diameter). Every point on the circle's edge is the same
652        distance, `0.5 * d`, from its center. `0.5 * d` (half the diameter) is
653        the circle's radius.
654
655        **Example**
656        ```python
657        from ipycc.sketch import Sketch
658
659        s = Sketch()
660        s.show()
661
662        s.background(200)
663        s.circle(50, 50, 25)
664        ```
665        """
666        r = d * 0.5
667        self.canvas.fill_circle(x, y, r)
668        self.canvas.stroke_circle(x, y, r)

Draws a circle.

A circle is a round shape defined by the x, y, and d parameters. x and y set the location of its center. d sets its width and height (diameter). Every point on the circle's edge is the same distance, 0.5 * d, from its center. 0.5 * d (half the diameter) is the circle's radius.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.circle(50, 50, 25)
def line( self, x1: int | float, y1: int | float, x2: int | float, y2: int | float):
670    def line(self, x1: int | float, y1: int | float, x2: int | float, y2: int | float):
671        """Draws a straight line between two points.
672
673        A line's default width is one pixel. The first two parameters set the
674        starting coordinates of the line. The next two parameters set the
675        ending coordinates of the line. To color a line, use the `stroke()`
676        method. To change its width, use the `stroke_weight()` method.
677
678        **Example**
679        ```python
680        from ipycc.sketch import Sketch
681
682        s = Sketch()
683        s.show()
684
685        s.background(200)
686        s.line(30, 20, 85, 75)
687        
688        # Style the line.
689        s.background(200)
690        s.stroke("magenta")
691        s.stroke_weight(5)
692        s.line(30, 20, 85, 75)
693        ```
694        """
695        self.canvas.stroke_line(x1, y1, x2, y2)

Draws a straight line between two points.

A line's default width is one pixel. The first two parameters set the starting coordinates of the line. The next two parameters set the ending coordinates of the line. To color a line, use the stroke() method. To change its width, use the stroke_weight() method.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.line(30, 20, 85, 75)

# Style the line.
s.background(200)
s.stroke("magenta")
s.stroke_weight(5)
s.line(30, 20, 85, 75)
def point(self, x: int | float, y: int | float):
697    def point(self, x: int | float, y: int | float):
698        """Draws a single point in space.
699
700        A point's default width is one pixel. To color a point, use the
701        `stroke()` method. To change its width, use the `stroke_weight()`
702        method. A point can't be filled, so the `fill()` method won't
703        affect the point's color.
704
705        **Example**
706        ```python
707        from ipycc.sketch import Sketch
708
709        s = Sketch()
710        s.show()
711
712        s.background(200)
713        
714        # Top-left.
715        s.point(30, 20)
716        
717        # Top-right. 
718        s.point(85, 20)
719        
720        # Style the next points.
721        s.stroke("purple")
722        s.stroke_weight(10)
723        
724        # Bottom-right.
725        s.point(85, 75)
726        
727        # Bottom-left.
728        s.point(30, 75)
729        ```
730        """
731        s = f"{self.canvas.stroke_style}"
732        f = f"{self.canvas.fill_style}"
733        self.canvas.fill_style = s
734        self.canvas.begin_path()
735        self.canvas.arc(x, y, self.canvas.line_width * 0.5, 0, self.TWO_PI, False)
736        self.canvas.fill()
737        self.canvas.fill_style = f

Draws a single point in space.

A point's default width is one pixel. To color a point, use the stroke() method. To change its width, use the stroke_weight() method. A point can't be filled, so the fill() method won't affect the point's color.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Top-left.
s.point(30, 20)

# Top-right. 
s.point(85, 20)

# Style the next points.
s.stroke("purple")
s.stroke_weight(10)

# Bottom-right.
s.point(85, 75)

# Bottom-left.
s.point(30, 75)
def quad( self, x1: int | float, y1: int | float, x2: int | float, y2: int | float, x3: int | float, y3: int | float, x4: int | float, y4: int | float):
739    def quad(
740        self,
741        x1: int | float,
742        y1: int | float,
743        x2: int | float,
744        y2: int | float,
745        x3: int | float,
746        y3: int | float,
747        x4: int | float,
748        y4: int | float,
749    ):
750        """Draws a quadrilateral (four-sided shape).
751
752        Quadrilaterals include rectangles, squares, rhombuses, and trapezoids.
753        The first pair of parameters `(x1, y1)` sets the quad's first point.
754        The next three pairs of parameters set the coordinates for its next
755        three points `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be
756        added in either clockwise or counter-clockwise order.
757
758        **Example**
759        ```python
760        from ipycc.sketch import Sketch
761
762        s = Sketch()
763        s.show()
764
765        s.background(200)
766        s.quad(50, 62, 86, 50, 50, 38, 14, 50)
767        ```
768        """
769        self.begin_shape()
770        self.vertex(x1, y1)
771        self.vertex(x2, y2)
772        self.vertex(x3, y3)
773        self.vertex(x4, y4)
774        self.end_shape()

Draws a quadrilateral (four-sided shape).

Quadrilaterals include rectangles, squares, rhombuses, and trapezoids. The first pair of parameters (x1, y1) sets the quad's first point. The next three pairs of parameters set the coordinates for its next three points (x2, y2), (x3, y3), and (x4, y4). Points should be added in either clockwise or counter-clockwise order.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.quad(50, 62, 86, 50, 50, 38, 14, 50)
def rect(self, x: int | float, y: int | float, w: int | float, h: int | float):
776    def rect(self, x: int | float, y: int | float, w: int | float, h: int | float):
777        """Draws a rectangle.
778
779        A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and
780        `h` parameters. `x` and `y` set the location of its top-left corner.
781        `w` sets its width and `h` sets its height. Every angle in the
782        rectangle measures 90Ëš.
783
784        **Example**
785        ```python
786        from ipycc.sketch import Sketch
787
788        s = Sketch()
789        s.show()
790
791        s.background(200)
792        s.rect(30, 20, 55, 40)
793        ```
794        """
795        self.canvas.fill_rect(x, y, w, h)
796        self.canvas.stroke_rect(x, y, w, h)

Draws a rectangle.

A rectangle is a four-sided shape defined by the x, y, w, and h parameters. x and y set the location of its top-left corner. w sets its width and h sets its height. Every angle in the rectangle measures 90Ëš.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.rect(30, 20, 55, 40)
def square(self, x: int | float, y: int | float, s: int | float):
798    def square(self, x: int | float, y: int | float, s: int | float):
799        """Draws a square.
800
801        A square is a four-sided shape defined by the `x`, `y`, and `s`
802        parameters. `x` and `y` set the location of its top-left corner. `s`
803        sets its width and height. Every angle in the square measures 90Ëš
804        and all its sides are the same length. 
805
806        **Example**
807        ```python
808        from ipycc.sketch import Sketch
809
810        s = Sketch()
811        s.show()
812
813        s.background(200)
814        s.square(30, 20, 55)
815        ```
816        """
817        self.canvas.fill_rect(x, y, s, s)
818        self.canvas.stroke_rect(x, y, s, s)

Draws a square.

A square is a four-sided shape defined by the x, y, and s parameters. x and y set the location of its top-left corner. s sets its width and height. Every angle in the square measures 90Ëš and all its sides are the same length.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.square(30, 20, 55)
def triangle( self, x1: int | float, y1: int | float, x2: int | float, y2: int | float, x3: int | float, y3: int | float):
820    def triangle(
821        self,
822        x1: int | float,
823        y1: int | float,
824        x2: int | float,
825        y2: int | float,
826        x3: int | float,
827        y3: int | float,
828    ):
829        """Draws a triangle.
830
831        A triangle is a three-sided shape defined by three points. The first
832        two parameters specify the triangle's first point `(x1, y1)`. The
833        middle two parameters specify its second point `(x2, y2)`. And the
834        last two parameters specify its third point `(x3, y3)`.
835
836        **Example**
837        ```python
838        from ipycc.sketch import Sketch
839
840        s = Sketch()
841        s.show()
842
843        s.background(200)
844        s.triangle(30, 75, 58, 20, 86, 75)
845        ```
846        """
847        self.begin_shape()
848        self.vertex(x1, y1)
849        self.vertex(x2, y2)
850        self.vertex(x3, y3)
851        self.end_shape()

Draws a triangle.

A triangle is a three-sided shape defined by three points. The first two parameters specify the triangle's first point (x1, y1). The middle two parameters specify its second point (x2, y2). And the last two parameters specify its third point (x3, y3).

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.triangle(30, 75, 58, 20, 86, 75)
def stroke_weight(self, weight: int | float):
857    def stroke_weight(self, weight: int | float):
858        """Sets the width of the stroke used for points, lines, and the outlines of shapes.
859
860        Note: stroke_weight() is affected by transformations, especially calls to scale().
861        
862        **Example**
863        ```python
864        from ipycc.sketch import Sketch
865
866        s = Sketch()
867        s.show()
868
869        s.background(200)
870
871        # Top.
872        s.line(20, 20, 80, 20)
873
874        # Middle.
875        s.stroke_weight(4)
876        s.line(20, 40, 80, 40)
877
878        # Bottom.
879        s.stroke_weight(10)
880        s.line(20, 70, 80, 70)
881        ```
882        """
883        if not self._is_stroke_weight_set:
884            self._is_stroke_weight_set = True
885        self.canvas.line_width = weight

Sets the width of the stroke used for points, lines, and the outlines of shapes.

Note: stroke_weight() is affected by transformations, especially calls to scale().

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Top.
s.line(20, 20, 80, 20)

# Middle.
s.stroke_weight(4)
s.line(20, 40, 80, 40)

# Bottom.
s.stroke_weight(10)
s.line(20, 70, 80, 70)
def bezier( self, x1: int | float, y1: int | float, x2: int | float, y2: int | float, x3: int | float, y3: int | float, x4: int | float, y4: int | float):
891    def bezier(
892        self,
893        x1: int | float,
894        y1: int | float,
895        x2: int | float,
896        y2: int | float,
897        x3: int | float,
898        y3: int | float,
899        x4: int | float,
900        y4: int | float,
901    ):
902        """Draws a Bézier curve.
903
904        Bézier curves can form shapes and curves that slope gently. They're
905        defined by two anchor points and two control points.
906
907        The first two parameters, `x1` and `y1`, set the first anchor point.
908        The first anchor point is where the curve starts.
909
910        The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two
911        control points. The control points "pull" the curve towards them.
912
913        The seventh and eighth parameters, `x4` and `y4`, set the last anchor
914        point. The last anchor point is where the curve ends.
915
916        **Example**
917        ```python
918        from ipycc.sketch import Sketch
919
920        s = Sketch()
921        s.show()
922
923        s.background(200)
924
925        # Draw the anchor points in black.
926        s.stroke(0)
927        s.stroke_weight(5)
928        s.point(85, 20)
929        s.point(15, 80)
930
931        # Draw the control points in red.
932        s.stroke(255, 0, 0)
933        s.point(10, 10)
934        s.point(90, 90)
935
936        # Draw a black bezier curve.
937        s.no_fill()
938        s.stroke(0)
939        s.stroke_weight(1)
940        s.bezier(85, 20, 10, 10, 90, 90, 15, 80)
941
942        # Draw red lines from the anchor points to the control points.
943        s.stroke(255, 0, 0)
944        s.line(85, 20, 10, 10)
945        s.line(15, 80, 90, 90)
946        ```
947        """
948        self.canvas.begin_path()
949        self.canvas.move_to(x1, y1)
950        self.canvas.bezier_curve_to(x2, y2, x3, y3, x4, y4)
951        self.canvas.stroke()

Draws a Bézier curve.

Bézier curves can form shapes and curves that slope gently. They're defined by two anchor points and two control points.

The first two parameters, x1 and y1, set the first anchor point. The first anchor point is where the curve starts.

The next four parameters, x2, y2, x3, and y3, set the two control points. The control points "pull" the curve towards them.

The seventh and eighth parameters, x4 and y4, set the last anchor point. The last anchor point is where the curve ends.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Draw the anchor points in black.
s.stroke(0)
s.stroke_weight(5)
s.point(85, 20)
s.point(15, 80)

# Draw the control points in red.
s.stroke(255, 0, 0)
s.point(10, 10)
s.point(90, 90)

# Draw a black bezier curve.
s.no_fill()
s.stroke(0)
s.stroke_weight(1)
s.bezier(85, 20, 10, 10, 90, 90, 15, 80)

# Draw red lines from the anchor points to the control points.
s.stroke(255, 0, 0)
s.line(85, 20, 10, 10)
s.line(15, 80, 90, 90)
def bezier_point( self, a: int | float, b: int | float, c: int | float, d: int | float, t: int | float) -> float:
 953    def bezier_point(
 954        self,
 955        a: int | float,
 956        b: int | float,
 957        c: int | float,
 958        d: int | float,
 959        t: int | float,
 960    ) -> float:
 961        """Calculates coordinates along a Bézier curve using interpolation.
 962
 963        `bezier_point()` calculates coordinates along a Bézier curve using the
 964        anchor and control points. It expects points in the same order as the
 965        bezier() method. `bezier_point()` works one axis at a time. Passing
 966        the anchor and control points' x-coordinates will calculate the
 967        x-coordinate of a point on the curve. Passing the anchor and control
 968        points' y-coordinates will calculate the y-coordinate of a point on
 969        the curve.
 970
 971        The first parameter, `a`, is the coordinate of the first anchor point.
 972
 973        The second and third parameters, `b` and `c`, are the coordinates of
 974        the control points.
 975
 976        The fourth parameter, `d`, is the coordinate of the last anchor point.
 977
 978        The fifth parameter, `t`, is the amount to interpolate along the
 979        curve. 0 is the first anchor point, 1 is the second anchor point, and
 980        0.5 is halfway between them.
 981
 982        **Example**
 983        ```python
 984        from ipycc.sketch import Sketch
 985
 986        s = Sketch()
 987        s.show()
 988
 989        s.background(200)
 990
 991        # Set the coordinates for the curve's anchor and control points.
 992        x1 = 85
 993        x2 = 10
 994        x3 = 90
 995        x4 = 15
 996        y1 = 20
 997        y2 = 10
 998        y3 = 90
 999        y4 = 80
1000
1001        # Style the curve.
1002        s.no_fill()
1003
1004        # Draw the curve.
1005        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1006
1007        # Draw circles along the curve's path.
1008        s.fill(255)
1009
1010        # Top-right.
1011        x = s.bezier_point(x1, x2, x3, x4, 0)
1012        y = s.bezier_point(y1, y2, y3, y4, 0)
1013        s.circle(x, y, 5)
1014
1015        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1016        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1017        s.circle(x, y, 5) # center circle
1018        x = s.bezier_point(x1, x2, x3, x4, 1)
1019        y = s.bezier_point(y1, y2, y3, y4, 1)
1020        s.circle(x, y, 5) # bottom-left circle
1021        ```
1022        """
1023        adjusted_t = 1 - t
1024        return (
1025            pow(adjusted_t, 3) * a
1026            + 3 * pow(adjusted_t, 2) * t * b
1027            + 3 * adjusted_t * pow(t, 2) * c
1028            + pow(t, 3) * d
1029        )

Calculates coordinates along a Bézier curve using interpolation.

bezier_point() calculates coordinates along a Bézier curve using the anchor and control points. It expects points in the same order as the bezier() method. bezier_point() works one axis at a time. Passing the anchor and control points' x-coordinates will calculate the x-coordinate of a point on the curve. Passing the anchor and control points' y-coordinates will calculate the y-coordinate of a point on the curve.

The first parameter, a, is the coordinate of the first anchor point.

The second and third parameters, b and c, are the coordinates of the control points.

The fourth parameter, d, is the coordinate of the last anchor point.

The fifth parameter, t, is the amount to interpolate along the curve. 0 is the first anchor point, 1 is the second anchor point, and 0.5 is halfway between them.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Set the coordinates for the curve's anchor and control points.
x1 = 85
x2 = 10
x3 = 90
x4 = 15
y1 = 20
y2 = 10
y3 = 90
y4 = 80

# Style the curve.
s.no_fill()

# Draw the curve.
s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)

# Draw circles along the curve's path.
s.fill(255)

# Top-right.
x = s.bezier_point(x1, x2, x3, x4, 0)
y = s.bezier_point(y1, y2, y3, y4, 0)
s.circle(x, y, 5)

x = s.bezier_point(x1, x2, x3, x4, 0.5)
y = s.bezier_point(y1, y2, y3, y4, 0.5)
s.circle(x, y, 5) # center circle
x = s.bezier_point(x1, x2, x3, x4, 1)
y = s.bezier_point(y1, y2, y3, y4, 1)
s.circle(x, y, 5) # bottom-left circle
def bezier_tangent( self, a: int | float, b: int | float, c: int | float, d: int | float, t: int | float) -> float:
1031    def bezier_tangent(
1032        self,
1033        a: int | float,
1034        b: int | float,
1035        c: int | float,
1036        d: int | float,
1037        t: int | float,
1038    ) -> float:
1039        """Calculates coordinates along a line that's tangent to a Bézier curve.
1040
1041        Tangent lines skim the surface of a curve. A tangent line's slope
1042        equals the curve's slope at the point where it intersects.
1043
1044        `bezier_tangent()` calculates coordinates along a tangent line using
1045        the Bézier curve's anchor and control points. It expects points in the
1046        same order as the `bezier()` method. `bezier_tangent()` works one axis
1047        at a time. Passing the anchor and control points' x-coordinates will
1048        calculate the x-coordinate of a point on the tangent line. Passing the
1049        anchor and control points' y-coordinates will calculate the
1050        y-coordinate of a point on the tangent line.
1051
1052        The first parameter, `a`, is the coordinate of the first anchor point.
1053
1054        The second and third parameters, `b` and `c`, are the coordinates of
1055        the control points.
1056
1057        The fourth parameter, `d`, is the coordinate of the last anchor point.
1058
1059        The fifth parameter, `t`, is the amount to interpolate along the curve.
1060        0 is the first anchor point, 1 is the second anchor point, and 0.5 is
1061        halfway between them.
1062
1063        **Example**
1064        ```python
1065        from ipycc.sketch import Sketch
1066
1067        s = Sketch()
1068        s.show()
1069
1070        s.background(200)
1071
1072        # Set the coordinates for the curve's anchor and control points.
1073        x1 = 85
1074        x2 = 10
1075        x3 = 90
1076        x4 = 15
1077        y1 = 20
1078        y2 = 10
1079        y3 = 90
1080        y4 = 80
1081
1082        # Style the curve.
1083        s.no_fill()
1084
1085        # Draw the curve.
1086        s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
1087
1088        # Draw tangents along the curve's path.
1089        s.fill(255)
1090
1091        # Top-right circle.
1092        s.stroke(0)
1093        x = s.bezier_point(x1, x2, x3, x4, 0)
1094        y = s.bezier_point(y1, y2, y3, y4, 0)
1095        s.circle(x, y, 5)
1096
1097        # Top-right tangent line.
1098        # Scale the tangent point to draw a shorter line.
1099        s.stroke(255, 0, 0) 
1100        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0)
1101        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0)
1102        s.line(x + tx, y + ty, x - tx, y - ty)
1103
1104        # Center circle.
1105        s.stroke(0)
1106        x = s.bezier_point(x1, x2, x3, x4, 0.5)
1107        y = s.bezier_point(y1, y2, y3, y4, 0.5)
1108        s.circle(x, y, 5)
1109        
1110        # Center tangent line.
1111        # Scale the tangent point to draw a shorter line.
1112        stroke(255, 0, 0)
1113        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0.5)
1114        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0.5)
1115        s.line(x + tx, y + ty, x - tx, y - ty)
1116
1117        # Bottom-left circle.
1118        stroke(0)
1119        x = s.bezier_point(x1, x2, x3, x4, 1)
1120        y = s.bezier_point(y1, y2, y3, y4, 1)
1121        s.circle(x, y, 5)
1122        
1123        # Bottom-left tangent.
1124        # Scale the tangent point to draw a shorter line.
1125        s.stroke(255, 0, 0)
1126        tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 1)
1127        ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 1)
1128        s.line(x + tx, y + ty, x - tx, y - ty)
1129        ```
1130        """
1131        adjusted_t = 1 - t
1132        return (
1133            3 * d * pow(t, 2)
1134            - 3 * c * pow(t, 2)
1135            + 6 * c * adjusted_t * t
1136            - 6 * b * adjusted_t * t
1137            + 3 * b * pow(adjusted_t, 2)
1138            - 3 * a * pow(adjusted_t, 2)
1139        )

Calculates coordinates along a line that's tangent to a Bézier curve.

Tangent lines skim the surface of a curve. A tangent line's slope equals the curve's slope at the point where it intersects.

bezier_tangent() calculates coordinates along a tangent line using the Bézier curve's anchor and control points. It expects points in the same order as the bezier() method. bezier_tangent() works one axis at a time. Passing the anchor and control points' x-coordinates will calculate the x-coordinate of a point on the tangent line. Passing the anchor and control points' y-coordinates will calculate the y-coordinate of a point on the tangent line.

The first parameter, a, is the coordinate of the first anchor point.

The second and third parameters, b and c, are the coordinates of the control points.

The fourth parameter, d, is the coordinate of the last anchor point.

The fifth parameter, t, is the amount to interpolate along the curve. 0 is the first anchor point, 1 is the second anchor point, and 0.5 is halfway between them.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Set the coordinates for the curve's anchor and control points.
x1 = 85
x2 = 10
x3 = 90
x4 = 15
y1 = 20
y2 = 10
y3 = 90
y4 = 80

# Style the curve.
s.no_fill()

# Draw the curve.
s.bezier(x1, y1, x2, y2, x3, y3, x4, y4)

# Draw tangents along the curve's path.
s.fill(255)

# Top-right circle.
s.stroke(0)
x = s.bezier_point(x1, x2, x3, x4, 0)
y = s.bezier_point(y1, y2, y3, y4, 0)
s.circle(x, y, 5)

# Top-right tangent line.
# Scale the tangent point to draw a shorter line.
s.stroke(255, 0, 0) 
tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0)
ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0)
s.line(x + tx, y + ty, x - tx, y - ty)

# Center circle.
s.stroke(0)
x = s.bezier_point(x1, x2, x3, x4, 0.5)
y = s.bezier_point(y1, y2, y3, y4, 0.5)
s.circle(x, y, 5)

# Center tangent line.
# Scale the tangent point to draw a shorter line.
stroke(255, 0, 0)
tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 0.5)
ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 0.5)
s.line(x + tx, y + ty, x - tx, y - ty)

# Bottom-left circle.
stroke(0)
x = s.bezier_point(x1, x2, x3, x4, 1)
y = s.bezier_point(y1, y2, y3, y4, 1)
s.circle(x, y, 5)

# Bottom-left tangent.
# Scale the tangent point to draw a shorter line.
s.stroke(255, 0, 0)
tx = 0.1 * s.bezier_tangent(x1, x2, x3, x4, 1)
ty = 0.1 * s.bezier_tangent(y1, y2, y3, y4, 1)
s.line(x + tx, y + ty, x - tx, y - ty)
def begin_shape(self):
1145    def begin_shape(self):
1146        """Begins adding vertices to a custom shape.
1147
1148        The `begin_shape()` and `end_shape()` methods allow for creating
1149        custom shapes. `begin_shape()` begins adding vertices to a custom
1150        shape and `end_shape()` stops adding them. After calling
1151        `begin_shape()`, shapes can be built by calling `vertex()`.
1152
1153        **Example**
1154        ```python
1155        from ipycc.sketch import Sketch
1156
1157        s = Sketch()
1158        s.show()
1159
1160        s.background(200)
1161        s.begin_shape() # begin drawing
1162        s.vertex(30, 20)
1163        s.vertex(85, 20)
1164        s.vertex(85, 75)
1165        s.vertex(30, 75)
1166        s.end_shape() # end drawing
1167        ```
1168        """
1169        self._vertices.clear()

Begins adding vertices to a custom shape.

The begin_shape() and end_shape() methods allow for creating custom shapes. begin_shape() begins adding vertices to a custom shape and end_shape() stops adding them. After calling begin_shape(), shapes can be built by calling vertex().

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.begin_shape() # begin drawing
s.vertex(30, 20)
s.vertex(85, 20)
s.vertex(85, 75)
s.vertex(30, 75)
s.end_shape() # end drawing
def end_shape(self):
1171    def end_shape(self):
1172        """Stops adding vertices to a custom shape.
1173
1174        The `begin_shape()` and `end_shape()` methods allow for creating
1175        custom shapes. `begin_shape()` begins adding vertices to a custom
1176        shape and `end_shape()` stops adding them. After calling
1177        `begin_shape()`, shapes can be built by calling `vertex()`.
1178
1179        **Example**
1180        ```python
1181        from ipycc.sketch import Sketch
1182
1183        s = Sketch()
1184        s.show()
1185
1186        s.background(200)
1187        s.begin_shape() # begin drawing
1188        s.vertex(30, 20)
1189        s.vertex(85, 20)
1190        s.vertex(85, 75)
1191        s.vertex(30, 75)
1192        s.end_shape() # end drawing
1193        ```
1194        """
1195        if len(self._vertices) > 0:
1196            self.canvas.fill_polygon(self._vertices)
1197            self.canvas.stroke_polygon(self._vertices)
1198            self._vertices.clear()

Stops adding vertices to a custom shape.

The begin_shape() and end_shape() methods allow for creating custom shapes. begin_shape() begins adding vertices to a custom shape and end_shape() stops adding them. After calling begin_shape(), shapes can be built by calling vertex().

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.begin_shape() # begin drawing
s.vertex(30, 20)
s.vertex(85, 20)
s.vertex(85, 75)
s.vertex(30, 75)
s.end_shape() # end drawing
def vertex(self, x: int | float, y: int | float):
1200    def vertex(self, x: int | float, y: int | float):
1201        """Adds a vertex to a custom shape.
1202
1203        `vertex()` sets the coordinates of vertices drawn between the
1204        `begin_shape()` and `end_shape()` methods.
1205
1206        **Example**
1207        ```python
1208        from ipycc.sketch import Sketch
1209
1210        s = Sketch()
1211        s.show()
1212
1213        s.background(200)
1214        s.begin_shape() # begin drawing
1215        s.vertex(30, 20)
1216        s.vertex(85, 20)
1217        s.vertex(85, 75)
1218        s.vertex(30, 75)
1219        s.end_shape() # end drawing
1220        ```
1221        """
1222        self._vertices.append((x, y))

Adds a vertex to a custom shape.

vertex() sets the coordinates of vertices drawn between the begin_shape() and end_shape() methods.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.begin_shape() # begin drawing
s.vertex(30, 20)
s.vertex(85, 20)
s.vertex(85, 75)
s.vertex(30, 75)
s.end_shape() # end drawing
def show(self):
1228    def show(self):
1229        """Display the sketch beneath the current code cell.
1230
1231        **Example**
1232        ```python
1233        from ipycc.sketch import Sketch
1234
1235        s = Sketch()
1236        s.show()
1237
1238        s.background(200)
1239        s.show()
1240        ```
1241        """
1242        display(self.canvas)

Display the sketch beneath the current code cell.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.show()
def apply_matrix( self, a: int | float, b: int | float, c: int | float, d: int | float, e: int | float, f: int | float):
1248    def apply_matrix(
1249        self,
1250        a: int | float,
1251        b: int | float,
1252        c: int | float,
1253        d: int | float,
1254        e: int | float,
1255        f: int | float,
1256    ):
1257        """Applies a transformation matrix to the coordinate system.
1258
1259        Transformations such as `translate()`, `rotate()`, and `scale()` use
1260        matrix-vector multiplication behind the scenes. A table of numbers,
1261        called a matrix, encodes each transformation. The values in the matrix
1262        then multiply each point on the canvas, which is represented by a
1263        vector.
1264
1265        `apply_matrix()` allows for many transformations to be applied at once.
1266        See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web)
1267        for more details about transformations.
1268
1269        **Example**
1270        ```python
1271        from ipycc.sketch import Sketch
1272
1273        s = Sketch()
1274        s.show()
1275
1276        s.background(200)
1277        
1278        # Translate the origin to the center.
1279        s.apply_matrix(1, 0, 0, 1, 50, 50)
1280        
1281        # Draw the circle at coordinates (0, 0).
1282        s.circle(0, 0, 40)
1283        ```
1284        """
1285        m = np.array(((a, c, e), (b, d, f), (0, 0, 1)), dtype=float)
1286        self._matrix = m @ self._matrix
1287        self.canvas.transform(a, b, c, d, e, f)

Applies a transformation matrix to the coordinate system.

Transformations such as translate(), rotate(), and scale() use matrix-vector multiplication behind the scenes. A table of numbers, called a matrix, encodes each transformation. The values in the matrix then multiply each point on the canvas, which is represented by a vector.

apply_matrix() allows for many transformations to be applied at once. See MDN for more details about transformations.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Translate the origin to the center.
s.apply_matrix(1, 0, 0, 1, 50, 50)

# Draw the circle at coordinates (0, 0).
s.circle(0, 0, 40)
def reset_matrix(self):
1289    def reset_matrix(self):
1290        """Clears all transformations applied to the coordinate system.
1291
1292        **Example**
1293        ```python
1294        from ipycc.sketch import Sketch
1295
1296        s = Sketch()
1297        s.show()
1298
1299        s.background(200)
1300        
1301        # Translate the origin to the center.
1302        s.translate(50, 50)
1303        
1304        # Draw a blue circle at the coordinates (25, 25).
1305        s.fill("blue")
1306        s.circle(25, 25, 20)
1307        
1308        # Clear all transformations.
1309        # The origin is now at the top-left corner.
1310        s.reset_matrix()
1311        
1312        # Draw a red circle at the coordinates (25, 25).
1313        s.fill("red")
1314        s.circle(25, 25, 20)
1315        ```
1316        """
1317        self._matrix = np.eye(3)
1318        self.canvas.reset_transform()

Clears all transformations applied to the coordinate system.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Translate the origin to the center.
s.translate(50, 50)

# Draw a blue circle at the coordinates (25, 25).
s.fill("blue")
s.circle(25, 25, 20)

# Clear all transformations.
# The origin is now at the top-left corner.
s.reset_matrix()

# Draw a red circle at the coordinates (25, 25).
s.fill("red")
s.circle(25, 25, 20)
def rotate(self, angle: int | float):
1320    def rotate(self, angle: int | float):
1321        """Rotates the coordinate system.
1322
1323        By default, the positive x-axis points to the right and the positive
1324        y-axis points downward. The `rotate()` method changes this orientation
1325        by rotating the coordinate system about the origin. Everything drawn
1326        after `rotate()` is called will appear to be rotated. Angles are
1327        measured in radians.
1328
1329        **Example**
1330        ```python
1331        from ipycc.sketch import Sketch
1332
1333        s = Sketch()
1334        s.show()
1335
1336        s.background(200)
1337
1338        # Rotate the coordinate system 1/8 turn.
1339        s.rotate(s.QUARTER_PI)
1340
1341        # Draw a rectangle at coordinates (50, 0).
1342        s.rect(50, 0, 40, 20)
1343        ```
1344        """
1345        ca, sa = math.cos(angle), math.sin(angle)
1346        m = np.array(((ca, -sa, 0), (sa, ca, 0), (0, 0, 1)), dtype=float)
1347        self._matrix = m @ self._matrix
1348        self.canvas.rotate(angle)

Rotates the coordinate system.

By default, the positive x-axis points to the right and the positive y-axis points downward. The rotate() method changes this orientation by rotating the coordinate system about the origin. Everything drawn after rotate() is called will appear to be rotated. Angles are measured in radians.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Rotate the coordinate system 1/8 turn.
s.rotate(s.QUARTER_PI)

# Draw a rectangle at coordinates (50, 0).
s.rect(50, 0, 40, 20)
def scale(self, x: int | float, y: int | float = None):
1350    def scale(self, x: int | float, y: int | float = None):
1351        """Scales the coordinate system.
1352
1353        By default, shapes are drawn at their original scale. A rectangle
1354        that's 50 pixels wide appears to take up half the width of a 100
1355        pixel-wide canvas. The `scale()` method can shrink or stretch the
1356        coordinate system so that shapes appear at different sizes.
1357
1358        The first parameter, `s`, sets the amount to scale each axis. For
1359        example, calling `scale(2)` stretches the x- and y-axes by a factor
1360        of 2. The next parameter, `y`, is optional. It sets the amount to
1361        scale the y-axis. For example, calling `scale(2, 0.5)` stretches the
1362        x-axis by a factor of 2 and shrinks the y-axis by a factor of 0.5.
1363
1364        **Example**
1365        ```python
1366        from ipycc.sketch import Sketch
1367
1368        s = Sketch()
1369        s.show()
1370
1371        s.background(200)
1372        
1373        # Draw a square at (30, 20).
1374        s.square(30, 20, 40)
1375        
1376        # Scale the coordinate system by a factor of 0.5.
1377        s.scale(0.5)
1378        
1379        # Draw a square at (30, 20).
1380        # It appears at (15, 10) after scaling.
1381        s.square(30, 20, 40)
1382        s.background(200)
1383        s.reset_matrix()
1384        
1385        # Draw a square at (30, 20).
1386        s.square(30, 20, 40)
1387        
1388        # Scale the coordinate system by factors of
1389        # 0.5 along the x-axis and
1390        # 1.3 along the y-axis.
1391        s.scale(0.5, 1.3)
1392        
1393        # Draw a square at (30, 20).
1394        # It appears as a rectangle at (15, 26) after scaling.
1395        s.square(30, 20, 40)
1396        ```
1397        """
1398        if y is None:
1399            m = np.array(((x, 0, 0), (0, x, 0), (0, 0, 1)), dtype=float)
1400            self._matrix = m @ self._matrix
1401            self.canvas.scale(x)
1402        else:
1403            m = np.array(((x, 0, 0), (0, y, 0), (0, 0, 1)), dtype=float)
1404            self._matrix = m @ self._matrix
1405            self.canvas.scale(x, y=y)

Scales the coordinate system.

By default, shapes are drawn at their original scale. A rectangle that's 50 pixels wide appears to take up half the width of a 100 pixel-wide canvas. The scale() method can shrink or stretch the coordinate system so that shapes appear at different sizes.

The first parameter, s, sets the amount to scale each axis. For example, calling scale(2) stretches the x- and y-axes by a factor of 2. The next parameter, y, is optional. It sets the amount to scale the y-axis. For example, calling scale(2, 0.5) stretches the x-axis by a factor of 2 and shrinks the y-axis by a factor of 0.5.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Draw a square at (30, 20).
s.square(30, 20, 40)

# Scale the coordinate system by a factor of 0.5.
s.scale(0.5)

# Draw a square at (30, 20).
# It appears at (15, 10) after scaling.
s.square(30, 20, 40)
s.background(200)
s.reset_matrix()

# Draw a square at (30, 20).
s.square(30, 20, 40)

# Scale the coordinate system by factors of
# 0.5 along the x-axis and
# 1.3 along the y-axis.
s.scale(0.5, 1.3)

# Draw a square at (30, 20).
# It appears as a rectangle at (15, 26) after scaling.
s.square(30, 20, 40)
def shear_x(self, angle: int | float):
1407    def shear_x(self, angle: int | float):
1408        """Shears the x-axis so that shapes appear skewed.
1409
1410        By default, the x- and y-axes are perpendicular. The `shear_x()`
1411        method transforms the coordinate system so that x-coordinates are
1412        translated while y-coordinates are fixed.
1413
1414        **Example**
1415        ```python
1416        from ipycc.sketch import Sketch
1417
1418        s = Sketch()
1419        s.show()
1420
1421        s.background(200)
1422
1423        # Shear the coordinate system along the x-axis.
1424        s.shear_x(s.QUARTER_PI)
1425
1426        # Draw the square.
1427        s.square(0, 0, 50)
1428        ```
1429        """
1430        self.apply_matrix(1, 0, math.tan(angle), 1, 0, 0)

Shears the x-axis so that shapes appear skewed.

By default, the x- and y-axes are perpendicular. The shear_x() method transforms the coordinate system so that x-coordinates are translated while y-coordinates are fixed.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Shear the coordinate system along the x-axis.
s.shear_x(s.QUARTER_PI)

# Draw the square.
s.square(0, 0, 50)
def shear_y(self, angle: int | float):
1432    def shear_y(self, angle: int | float):
1433        """Shears the y-axis so that shapes appear skewed.
1434
1435        By default, the x- and y-axes are perpendicular. The `shear_y()`
1436        method transforms the coordinate system so that y-coordinates are
1437        translated while x-coordinates are fixed.
1438
1439        **Example**
1440        ```python
1441        from ipycc.sketch import Sketch
1442
1443        s = Sketch()
1444        s.show()
1445
1446        s.background(200)
1447
1448        # Shear the coordinate system along the y-axis.
1449        s.shear_y(s.QUARTER_PI)
1450
1451        # Draw the square.
1452        s.square(0, 0, 50)
1453        ```
1454        """
1455        self.apply_matrix(1, math.tan(angle), 0, 1, 0, 0)

Shears the y-axis so that shapes appear skewed.

By default, the x- and y-axes are perpendicular. The shear_y() method transforms the coordinate system so that y-coordinates are translated while x-coordinates are fixed.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Shear the coordinate system along the y-axis.
s.shear_y(s.QUARTER_PI)

# Draw the square.
s.square(0, 0, 50)
def translate(self, x: int | float, y: int | float):
1457    def translate(self, x: int | float, y: int | float):
1458        """Translates the coordinate system.
1459
1460        By default, the origin (0, 0) is at the sketch's top-left corner. The
1461        `translate()` method shifts the origin to a different position.
1462        Everything drawn after `translate()` is called will appear to be
1463        shifted.
1464    
1465        **Example**
1466        ```python
1467        from ipycc.sketch import Sketch
1468
1469        s = Sketch()
1470        s.show()
1471
1472        s.background(200)
1473
1474        # Translate the origin to the center.
1475        s.translate(50, 50)
1476
1477        # Draw a circle at coordinates (0, 0).
1478        s.circle(0, 0, 40)
1479        ```
1480        """
1481        m = np.array(((0, 0, x), (0, 0, y), (0, 0, 1)), dtype=float)
1482        self._matrix = m @ self._matrix
1483        self.canvas.translate(x, y)

Translates the coordinate system.

By default, the origin (0, 0) is at the sketch's top-left corner. The translate() method shifts the origin to a different position. Everything drawn after translate() is called will appear to be shifted.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Translate the origin to the center.
s.translate(50, 50)

# Draw a circle at coordinates (0, 0).
s.circle(0, 0, 40)
def image( self, img: Self, x: int | float, y: int | float, width: int | float = None, height: int | float = None):
1489    def image(
1490        self,
1491        img: Self,
1492        x: int | float,
1493        y: int | float,
1494        width: int | float = None,
1495        height: int | float = None,
1496    ):
1497        """Draws an image to the canvas.
1498
1499        The first parameter, `img`, is the source image to be drawn. `img` can be
1500        another `Sketch` instance.
1501
1502        The second and third parameters, `x` and `y`, set the coordinates of the
1503        destination image's top left corner.
1504
1505        The fourth and fifth parameters, `width` and `height`, are optional. They
1506        set the the width and height to draw the destination image. By
1507        default, `image()` draws the full source image at its original size.
1508
1509        **Example**
1510        ```python
1511        from ipycc.sketch import Sketch
1512
1513        # Create the Sketches.
1514        s1 = Sketch()
1515        s2 = Sketch()
1516
1517        # Draw to s1.
1518        s1.background(200)
1519        s1.circle(50, 50, 20)
1520
1521        # Draw s1 on s2 at full size.
1522        s2.image(s1, 0, 0)
1523
1524        # Draw s1 on s2 at half size.
1525        s2.image(s1, 0, 0, 50, 50)
1526
1527        # Show s2.
1528        s2.show()
1529        ```
1530        """
1531        if width is None:
1532            width = img.width
1533        if height is None:
1534            height = img.height
1535        self.canvas.draw_image(img.canvas, x=x, y=y, width=width, height=height)

Draws an image to the canvas.

The first parameter, img, is the source image to be drawn. img can be another Sketch instance.

The second and third parameters, x and y, set the coordinates of the destination image's top left corner.

The fourth and fifth parameters, width and height, are optional. They set the the width and height to draw the destination image. By default, image() draws the full source image at its original size.

Example

from ipycc.sketch import Sketch

# Create the Sketches.
s1 = Sketch()
s2 = Sketch()

# Draw to s1.
s1.background(200)
s1.circle(50, 50, 20)

# Draw s1 on s2 at full size.
s2.image(s1, 0, 0)

# Draw s1 on s2 at half size.
s2.image(s1, 0, 0, 50, 50)

# Show s2.
s2.show()
def text(self, text: str, x: int | float, y: int | float):
1541    def text(self, text: str, x: int | float, y: int | float):
1542        """Draws text to the canvas.
1543
1544        The first parameter, `text`, is the text to be drawn. The second and
1545        third parameters, `x` and `y`, set the coordinates of the text's
1546        bottom-left corner. See `text_align()` for other ways to align text.
1547
1548        **Example**
1549        ```python
1550        from ipycc.sketch import Sketch
1551
1552        s = Sketch()
1553        s.show()
1554        ```
1555
1556        ```python
1557        # Plain text.
1558        s.background(200)
1559        s.text("hi", 50, 50)
1560        ```
1561        
1562        
1563        ```python
1564        # Emoji.
1565        s.background("skyblue")
1566        s.text_size(100)
1567        s.text("🌈", 0, 100)
1568        ```
1569        
1570        ```python
1571        # No fill.
1572        s.background(200)
1573        s.text_size(32)
1574        s.fill(255)
1575        s.stroke(0)
1576        s.stroke_weight(4)
1577        s.text("hi", 50, 50)
1578        ```
1579        
1580        ```python
1581        # Multicolor text.
1582        s.background("black")
1583        s.text_size(22)
1584        s.fill("yellow")
1585        s.text("rainbows", 6, 20)
1586        s.fill("cornflowerblue")
1587        s.text("rainbows", 6, 45)
1588        s.fill("tomato")
1589        s.text("rainbows", 6, 70)
1590        s.fill("limegreen")
1591        s.text("rainbows", 6, 95)
1592        ```
1593        """
1594        if self._is_fill_set:
1595            self.canvas.fill_text(text, x, y)
1596        else:
1597            self.canvas.fill_style = Sketch._DEFAULT_TEXT_FILL
1598            self.canvas.fill_text(text, x, y)
1599            self.canvas.fill_style = Sketch._DEFAULT_FILL
1600
1601        if self._is_stroke_set:
1602            if self._is_stroke_weight_set:
1603                self.canvas.stroke_text(text, x, y)
1604            else:
1605                self.canvas.line_width = Sketch._DEFAULT_TEXT_WEIGHT
1606                self.canvas.stroke_text(text, x, y)
1607                self.canvas.line_width = Sketch._DEFAULT_STROKE_WEIGHT

Draws text to the canvas.

The first parameter, text, is the text to be drawn. The second and third parameters, x and y, set the coordinates of the text's bottom-left corner. See text_align() for other ways to align text.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()
# Plain text.
s.background(200)
s.text("hi", 50, 50)
# Emoji.
s.background("skyblue")
s.text_size(100)
s.text("🌈", 0, 100)
# No fill.
s.background(200)
s.text_size(32)
s.fill(255)
s.stroke(0)
s.stroke_weight(4)
s.text("hi", 50, 50)
# Multicolor text.
s.background("black")
s.text_size(22)
s.fill("yellow")
s.text("rainbows", 6, 20)
s.fill("cornflowerblue")
s.text("rainbows", 6, 45)
s.fill("tomato")
s.text("rainbows", 6, 70)
s.fill("limegreen")
s.text("rainbows", 6, 95)
def text_font(self, font: str):
1609    def text_font(self, font: str):
1610        """Sets the font used by the `text()` method.
1611
1612        The font should be a string with the name of a system font such as
1613        `"Courier New"`.
1614
1615        **Example**
1616        ```python
1617        from ipycc.sketch import Sketch
1618
1619        s = Sketch()
1620        s.show()
1621
1622        s.background(200)
1623        s.text_font("Courier New")
1624        s.text_size(24)
1625        s.text("hi", 35, 55)
1626        ```
1627        """
1628        self._font = font
1629        self.canvas.font = (
1630            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1631        )

Sets the font used by the text() method.

The font should be a string with the name of a system font such as "Courier New".

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)
s.text_font("Courier New")
s.text_size(24)
s.text("hi", 35, 55)
def text_size(self, size: int | float):
1633    def text_size(self, size: int | float):
1634        """Sets the font size when `text()` is called.
1635
1636        Note: Font size is measured in pixels.
1637
1638        **Example**
1639        ```python
1640        from ipycc.sketch import Sketch
1641
1642        s = Sketch()
1643        s.show()
1644
1645        s.background(200)
1646
1647        # Top row.
1648        s.text_size(12)
1649        s.text("Font Size 12", 10, 30)
1650
1651        # Middle row.
1652        s.text_size(14)
1653        s.text("Font Size 14", 10, 60)
1654
1655        # Bottom row.
1656        s.text_size(16)
1657        s.text("Font Size 16", 10, 90)
1658        ```
1659        """
1660        self._font_size = size
1661        self.canvas.font = (
1662            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1663        )

Sets the font size when text() is called.

Note: Font size is measured in pixels.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Top row.
s.text_size(12)
s.text("Font Size 12", 10, 30)

# Middle row.
s.text_size(14)
s.text("Font Size 14", 10, 60)

# Bottom row.
s.text_size(16)
s.text("Font Size 16", 10, 90)
def text_align(self, horizontal: str, vertical: str = None):
1665    def text_align(self, horizontal: str, vertical: str = None):
1666        """Sets the way text is aligned when `text()` is called.
1667
1668        By default, calling `text("hi", 10, 20)` places the bottom-left corner
1669        of the text's bounding box at (10, 20).
1670
1671        The first parameter, `horizontal`, changes the way `text()` interprets
1672        x-coordinates. By default, the x-coordinate sets the left edge of the
1673        bounding box. `text_align()` accepts the following values for horizontal:
1674        `LEFT`, `CENTER`, or `RIGHT`.
1675
1676        The second parameter, `vertical`, is optional. It changes the way `text()`
1677        interprets y-coordinates. By default, the y-coordinate sets the bottom
1678        edge of the bounding box. `text_align()` accepts the following values
1679        for vertical: `TOP`, `BOTTOM`, `CENTER`, or `BASELINE`.
1680
1681        **Example**
1682        ```python
1683        from ipycc.sketch import Sketch
1684
1685        s = Sketch()
1686        s.show()
1687
1688        s.background(200)
1689
1690        # Draw a vertical line.
1691        s.stroke_weight(0.5)
1692        s.line(50, 0, 50, 100)
1693
1694        # Top row.
1695        s.text_size(16)
1696        s.text_align(s.RIGHT)
1697        s.text("ABCD", 50, 30)
1698        
1699        # Middle row.
1700        s.text_align(s.CENTER)
1701        s.text("EFGH", 50, 50)
1702        
1703        # Bottom row.
1704        s.text_align(s.LEFT)
1705        s.text("IJKL", 50, 70)
1706        ```
1707        """
1708        if horizontal == Sketch.LEFT:
1709            self._text_align = Sketch.LEFT
1710        elif horizontal == Sketch.RIGHT:
1711            self._text_align = Sketch.RIGHT
1712        elif horizontal == Sketch.CENTER:
1713            self._text_align = Sketch.CENTER
1714        self.canvas.text_align = self._text_align
1715
1716        if vertical is None:
1717            return
1718        if vertical == Sketch.TOP:
1719            self._text_baseline = Sketch.TOP
1720        elif vertical == Sketch.BOTTOM:
1721            self._text_baseline = Sketch.BOTTOM
1722        elif vertical == Sketch.CENTER:
1723            self._text_baseline = Sketch.CENTER
1724        elif vertical == Sketch.BASELINE:
1725            self._text_baseline = Sketch.BASELINE
1726        self.canvas.text_baseline = vertical

Sets the way text is aligned when text() is called.

By default, calling text("hi", 10, 20) places the bottom-left corner of the text's bounding box at (10, 20).

The first parameter, horizontal, changes the way text() interprets x-coordinates. By default, the x-coordinate sets the left edge of the bounding box. text_align() accepts the following values for horizontal: LEFT, CENTER, or RIGHT.

The second parameter, vertical, is optional. It changes the way text() interprets y-coordinates. By default, the y-coordinate sets the bottom edge of the bounding box. text_align() accepts the following values for vertical: TOP, BOTTOM, CENTER, or BASELINE.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# Draw a vertical line.
s.stroke_weight(0.5)
s.line(50, 0, 50, 100)

# Top row.
s.text_size(16)
s.text_align(s.RIGHT)
s.text("ABCD", 50, 30)

# Middle row.
s.text_align(s.CENTER)
s.text("EFGH", 50, 50)

# Bottom row.
s.text_align(s.LEFT)
s.text("IJKL", 50, 70)
def text_style(self, style: str):
1728    def text_style(self, style: str):
1729        """Sets the style for system fonts when `text()` is called.
1730
1731        The parameter, `style`, can be either `NORMAL`, `ITALIC`, `BOLD`, or
1732        `BOLDITALIC`.
1733
1734        **Example**
1735        ```python
1736        from ipycc.sketch import Sketch
1737
1738        s = Sketch()
1739        s.show()
1740
1741        s.background(200)
1742
1743        # First row.
1744        s.text_size(12)
1745        s.text_style(s.NORMAL)
1746        s.text("Normal", 20, 15)
1747
1748        # Second row.
1749        s.text_style(s.ITALIC)
1750        s.text("Italic", 20, 40)
1751
1752        # Third row.
1753        s.text_style(s.BOLD)
1754        s.text("Bold", 20, 65)
1755
1756        # Fourth row.
1757        s.text_style(s.BOLDITALIC)
1758        s.text("Bold Italic", 20, 90)
1759        ```
1760        """
1761        if style == Sketch.NORMAL:
1762            self._font_weight = Sketch.NORMAL
1763            self._font_style = Sketch.NORMAL
1764        elif style == Sketch.ITALIC:
1765            self._font_weight = Sketch.NORMAL
1766            self._font_style = Sketch.ITALIC
1767        elif style == Sketch.BOLD:
1768            self._font_weight = Sketch.BOLD
1769            self._font_style = Sketch.NORMAL
1770        elif style == Sketch.BOLDITALIC:
1771            self._font_weight = Sketch.BOLD
1772            self._font_style = Sketch.ITALIC
1773        self.canvas.font = (
1774            f"{self._font_style} {self._font_weight} {self._font_size}px {self._font}"
1775        )

Sets the style for system fonts when text() is called.

The parameter, style, can be either NORMAL, ITALIC, BOLD, or BOLDITALIC.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

s.background(200)

# First row.
s.text_size(12)
s.text_style(s.NORMAL)
s.text("Normal", 20, 15)

# Second row.
s.text_style(s.ITALIC)
s.text("Italic", 20, 40)

# Third row.
s.text_style(s.BOLD)
s.text("Bold", 20, 65)

# Fourth row.
s.text_style(s.BOLDITALIC)
s.text("Bold Italic", 20, 90)
def run_sketch(self, draw: Callable, seconds: int | float, delay: float = 20):
1791    def run_sketch(self, draw: Callable, seconds: int | float, delay: float = 20):
1792        """Draws frames in an animation by calling a function repeatedly.
1793
1794        `run_sketch()` repeatedly calls a function that contains drawing
1795        commands. The rate at which each frame is drawn depends on many
1796        factors. `run_sketch()` doesn't attempt to maintain a constant
1797        framerate.
1798
1799        The first parameter, `draw`, is a function containing the commands for
1800        drawing each frame.
1801
1802        The second parameter, `seconds`, sets the number of seconds the
1803        animation should run.
1804
1805        The third parameter, `delay`, is optional. It sets the number of
1806        milliseconds the sketch should pause after drawing the current frame.
1807        The default value is 20. If `draw` contains many drawing commands,
1808        each frame may take much longer than `delay` milliseconds to render.
1809
1810        **Example**
1811        ```python
1812        from ipycc.sketch import Sketch
1813
1814        s = Sketch()
1815        s.show()
1816
1817        def draw():
1818            # Paint the background.
1819            s.background(200)
1820
1821            # Calculate the circle's x-coordinate.
1822            x = s.frame_count
1823
1824            # Draw the circle.
1825            s.circle(x, 50, 20)
1826
1827        # Run the animation for 5 seconds.
1828        s.run_sketch(draw, 5)
1829        ```
1830        """
1831        if delay < 0:
1832            raise SketchError("Your delay value must be positive.")
1833        start = time.time()
1834        end = start + seconds
1835        delay *= 0.001
1836        self.frame_count = 0
1837        while time.time() < end:
1838            with hold_canvas():
1839                a, b, c, d, e, f = self._unpack_transform()
1840                draw()
1841                self.reset_matrix()
1842                self.apply_matrix(a, b, c, d, e, f)
1843                self.frame_count += 1
1844                time.sleep(delay)

Draws frames in an animation by calling a function repeatedly.

run_sketch() repeatedly calls a function that contains drawing commands. The rate at which each frame is drawn depends on many factors. run_sketch() doesn't attempt to maintain a constant framerate.

The first parameter, draw, is a function containing the commands for drawing each frame.

The second parameter, seconds, sets the number of seconds the animation should run.

The third parameter, delay, is optional. It sets the number of milliseconds the sketch should pause after drawing the current frame. The default value is 20. If draw contains many drawing commands, each frame may take much longer than delay milliseconds to render.

Example

from ipycc.sketch import Sketch

s = Sketch()
s.show()

def draw():
    # Paint the background.
    s.background(200)

    # Calculate the circle's x-coordinate.
    x = s.frame_count

    # Draw the circle.
    s.circle(x, 50, 20)

# Run the animation for 5 seconds.
s.run_sketch(draw, 5)