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"]
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.
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
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)
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)
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)
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)
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)
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()
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()
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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)
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
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
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
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()
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)
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)
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)
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)
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)
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)
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)
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()
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)
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)
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)
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)
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)
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)