Skip to content

Shape

A Shape is a specialized Element that draws using a path. While Element is the base class for all drawable objects, Shape adds path-based rendering with automatic fill and stroke, plus pixel-accurate hit testing via the path geometry.

Most built-in elements (Circle, Rect, Arc, Line, Polygon, Polyline, Ellipse) extend Shape. The notable exception is Text, which extends Element directly.

Element vs Shape

FeatureElementShape
Base classEventBusElement
RenderingManual (override render)Path-based (override render with path callback)
Auto fill/strokeNoYes — fills and strokes automatically based on style
Hit testingBounding box onlyPixel-accurate via path geometry
Use caseText, images, custom non-path elementsGeometric shapes with fill/stroke

Shape Options

In addition to all Element options, shapes accept:

OptionTypeDefaultDescription
autoFillbooleantrueAutomatically fill the path if fillStyle is set
autoStrokebooleantrueAutomatically stroke the path if strokeStyle is set
ts
const circle = createCircle({
    fillStyle: '#3a86ff',
    strokeStyle: '#1a56db',
    autoStroke: false, // Don't stroke, only fill
    cx: 100,
    cy: 100,
    radius: 50,
});

How Shape Rendering Works

When you call shape.render(context), the following happens:

  1. The element's render method saves the context state
  2. All style properties (fillStyle, lineWidth, etc.) are applied to the context
  3. A new path is created via context.createPath()
  4. The shape's drawing callback builds the path geometry (e.g. path.circle(x, y, r))
  5. If autoFill is true and fillStyle is set, the path is filled
  6. If autoStroke is true and strokeStyle is set, the path is stroked
  7. The context state is restored

This is why shapes "just work" — you set fillStyle and/or strokeStyle and the shape handles the rest.

Hit Testing

Shapes provide pixel-accurate hit testing through the intersectsWith method. Instead of using a simple bounding box check (like the base Element), shapes test whether a point is inside the actual path geometry:

ts
// Pixel-accurate — tests against the actual circle path
circle.intersectsWith(mouseX, mouseY);

The pointerEvents property controls which parts of the shape respond to hits:

  • 'all' — fill area OR stroke area (default)
  • 'fill' — only the fill area
  • 'stroke' — only the stroke area
  • 'none' — no hit testing

Demo

The demo below shows the difference between autoFill and autoStroke. The left circle has both enabled, the middle has only fill, and the right has only stroke.