Skip to content

Trig Projections

Unit circle with sine/cosine projections — useful for teaching angles and the Y-down canvas convention.

Paste into main.js in the editor, then click Restart.

Complete example

javascript
const cx = 160, cy = h / 2;
const r = 100;
const angle = time * 0.8;
const px = cx + r * Math.cos(angle);
const py = cy - r * Math.sin(angle); // flip Y → math "up" is screen up

return {
  particles: [
    { x: px, y: py, r: 7, color: '#8B5CF6', label: 'P' },
  ],
  circles: [
    { cx, cy, r, color: 'rgba(255,255,255,0.12)' },
  ],
  lines: [
    { x1: cx, y1: cy, x2: px, y2: py, color: '#F59E0B', width: 1.5 },
    { x1: px, y1: py, x2: px, y2: cy, color: '#EF4444', width: 1, dashed: true },
    { x1: px, y1: py, x2: cx, y2: py, color: '#3B82F6', width: 1, dashed: true },
    { x1: cx - r - 20, y1: cy, x2: cx + r + 20, y2: cy, color: 'rgba(255,255,255,0.08)', width: 1 },
    { x1: cx, y1: cy - r - 20, x2: cx, y2: cy + r + 20, color: 'rgba(255,255,255,0.08)', width: 1 },
  ],
  arcs: [
    { cx, cy, r: 25, startAngle: 0, endAngle: angle % (Math.PI * 2), color: '#8B5CF6', width: 2 },
  ],
  bars: [
    {
      x: cx + r + 40,
      y: cy - Math.abs(Math.sin(angle)) * r,
      w: 30,
      h: Math.abs(Math.sin(angle)) * r,
      color: '#EF4444',
      label: 'sin',
    },
    {
      x: cx + r + 80,
      y: cy - Math.abs(Math.cos(angle)) * r,
      w: 30,
      h: Math.abs(Math.cos(angle)) * r,
      color: '#3B82F6',
      label: 'cos',
    },
  ],
  text: {
    angle: ((angle % (Math.PI * 2)) * 180 / Math.PI).toFixed(1) + '°',
    sin: Math.sin(angle).toFixed(3),
    cos: Math.cos(angle).toFixed(3),
  },
};

Y-up vs canvas

javascript
// Math textbook (Y up):
py = cy - r * Math.sin(angle)

// Raw canvas (Y down) — point would travel the other way:
py = cy + r * Math.sin(angle)

See Coordinate System.

Circular orbit (no state)

Same idea without flipping Y — good when you want screen-native motion:

javascript
const cx = w / 2, cy = h / 2;
const angle = time * 1.5;
const r = 120;
const px = cx + r * Math.cos(angle);
const py = cy + r * Math.sin(angle);

return {
  particles: [
    { x: px, y: py, r: 10, color: '#8B5CF6', label: 'orbiter' },
    { x: cx, y: cy, r: 6, color: '#F59E0B', label: 'center' },
  ],
  lines: [{ x1: cx, y1: cy, x2: px, y2: py, color: '#F59E0B', width: 1.5 }],
  circles: [{ cx, cy, r, color: 'rgba(255,255,255,0.15)' }],
  arcs: [
    { cx, cy, r: 30, startAngle: 0, endAngle: angle % (Math.PI * 2), color: '#8B5CF6', width: 2 },
  ],
  vectors: [{
    x: 60 * Math.cos(angle + Math.PI / 2),
    y: 60 * Math.sin(angle + Math.PI / 2),
    ox: px, oy: py, color: '#22C55E', label: 'v',
  }],
  text: {
    theta: (angle % (Math.PI * 2)).toFixed(2) + ' rad',
  },
};

No state needed — position is a pure function of time.