Skip to content

Rendering ​

wity-rkf is rendering-target-agnostic. The evaluate() function returns a ComputedFrame with resolved marks, controls (dir="in" ports), envs, gates, outputs (dir="out" ports), and section structure. A renderer consumes this output and draws it to any target.

ComputedFrame shape ​

js
{
  state:    { x: 0.5, y: 0.25, mode: 'linear' },  // current variable values
  marks:    [ { tag: 'rkf-circle', cx: 200, cy: 150, r: 8, fill: '#3b82f6', visible: true }, ... ],
  controls: [ { tag: 'rkf-port', dir: 'in', type: 'continuous', var: 'x', min: 0, max: 1, step: 0.01, value: 0.5, visible: true }, ... ],
  envs:     [ { tag: 'rkf-env', type: 'prose', content: 'Some text', visible: true }, ... ],
  gates:    { isRunning: true, converged: false },
  outputs:  [ { tag: 'rkf-port', dir: 'out', var: 'energy', value: 42 }, ... ],
  elastics: [ { var: 'pulse', resetTo: 0, delayMs: 240 }, ... ],
  sections: [ { tag: 'rkf-section', id: 'intro', children: [...], visible: true }, ... ],
}

Renderer examples ​

SVG renderer ​

Map marks to SVG elements:

js
function renderSvg(frame, svgElement) {
  svgElement.innerHTML = '';

  for (const mark of frame.marks) {
    if (!mark.visible) continue;

    if (mark.tag === 'rkf-circle') {
      const el = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      el.setAttribute('cx', mark.cx);
      el.setAttribute('cy', mark.cy);
      el.setAttribute('r', mark.r);
      el.setAttribute('fill', mark.fill || '#000');
      svgElement.appendChild(el);
    }

    if (mark.tag === 'rkf-line') {
      const el = document.createElementNS('http://www.w3.org/2000/svg', 'line');
      el.setAttribute('x1', mark.x1);
      el.setAttribute('y1', mark.y1);
      el.setAttribute('x2', mark.x2);
      el.setAttribute('y2', mark.y2);
      el.setAttribute('stroke', mark.stroke || '#000');
      el.setAttribute('stroke-width', mark.strokeWidth || 1);
      svgElement.appendChild(el);
    }

    // ... handle other mark types
  }
}

Canvas 2D renderer ​

js
function renderCanvas(frame, ctx) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  for (const mark of frame.marks) {
    if (!mark.visible) continue;

    if (mark.tag === 'rkf-circle') {
      ctx.beginPath();
      ctx.arc(mark.cx, mark.cy, mark.r, 0, Math.PI * 2);
      ctx.fillStyle = mark.fill || '#000';
      ctx.fill();
    }

    if (mark.tag === 'rkf-rect') {
      ctx.fillStyle = mark.fill || '#000';
      ctx.fillRect(mark.x, mark.y, mark.width, mark.height);
    }

    if (mark.tag === 'rkf-text') {
      ctx.fillStyle = mark.color || '#000';
      ctx.font = `${mark.fontSize || 14}px sans-serif`;
      ctx.textAlign = mark.textAnchor || 'start';
      ctx.fillText(mark.content, mark.x, mark.y);
    }
  }
}

DOM renderer for environments ​

js
function renderEnvs(frame, container) {
  container.innerHTML = '';

  for (const section of frame.sections) {
    if (!section.visible) continue;

    const sectionEl = document.createElement('section');
    sectionEl.id = section.id;

    for (const child of section.children) {
      if (!child.visible) continue;

      if (child.tag === 'rkf-env' && child.type === 'prose') {
        const p = document.createElement('div');
        p.innerHTML = child.content;
        sectionEl.appendChild(p);
      }

      if (child.tag === 'rkf-env' && child.type === 'math') {
        const math = document.createElement('div');
        math.className = child.display === 'inline' ? 'math-inline' : 'math-block';
        math.textContent = child.content; // hand to KaTeX/MathJax
        sectionEl.appendChild(math);
      }
    }

    container.appendChild(sectionEl);
  }
}

Reactive loop ​

A typical interactive rendering loop:

js
import { parse, evaluate, initState, applyUpdates } from '@wity-ai/rkf-core';

const doc = parse(xml);
let state = initState(doc);

function render() {
  const frame = evaluate(doc, state);
  renderSvg(frame, svgEl);
  renderEnvs(frame, envEl);
  renderControls(frame, controlsEl, onChange);

  // Handle elastic variables
  for (const elastic of frame.elastics) {
    setTimeout(() => {
      state = { ...state, [elastic.var]: elastic.resetTo };
      render();
    }, elastic.delayMs);
  }
}

function onChange(varName, value) {
  state = { ...state, [varName]: value };
  render();
}

function onTick() {
  state = applyUpdates(doc.transforms, state, 'tick');
  render();
}

render(); // initial