UNPKG

vega-scenegraph

Version:

Vega scenegraph and renderers.

149 lines (126 loc) 3.47 kB
import {DegToRad, Epsilon, HalfPi, Tau} from '../util/constants'; const circleThreshold = Tau - 1e-8; let bounds, lx, ly, rot, ma, mb, mc, md; const add = (x, y) => bounds.add(x, y); const addL = (x, y) => add(lx = x, ly = y); const addX = x => add(x, bounds.y1); const addY = y => add(bounds.x1, y); const px = (x, y) => ma * x + mc * y; const py = (x, y) => mb * x + md * y; const addp = (x, y) => add(px(x, y), py(x, y)); const addpL = (x, y) => addL(px(x, y), py(x, y)); export default function(_, deg) { bounds = _; if (deg) { rot = deg * DegToRad; ma = md = Math.cos(rot); mb = Math.sin(rot); mc = -mb; } else { ma = md = 1; rot = mb = mc = 0; } return context; } const context = { beginPath() {}, closePath() {}, moveTo: addpL, lineTo: addpL, rect(x, y, w, h) { if (rot) { addp(x + w, y); addp(x + w, y + h); addp(x, y + h); addpL(x, y); } else { add(x + w, y + h); addL(x, y); } }, quadraticCurveTo(x1, y1, x2, y2) { const px1 = px(x1, y1), py1 = py(x1, y1), px2 = px(x2, y2), py2 = py(x2, y2); quadExtrema(lx, px1, px2, addX); quadExtrema(ly, py1, py2, addY); addL(px2, py2); }, bezierCurveTo(x1, y1, x2, y2, x3, y3) { const px1 = px(x1, y1), py1 = py(x1, y1), px2 = px(x2, y2), py2 = py(x2, y2), px3 = px(x3, y3), py3 = py(x3, y3); cubicExtrema(lx, px1, px2, px3, addX); cubicExtrema(ly, py1, py2, py3, addY); addL(px3, py3); }, arc(cx, cy, r, sa, ea, ccw) { sa += rot; ea += rot; // store last point on path lx = r * Math.cos(ea) + cx; ly = r * Math.sin(ea) + cy; if (Math.abs(ea - sa) > circleThreshold) { // treat as full circle add(cx - r, cy - r); add(cx + r, cy + r); } else { const update = a => add(r * Math.cos(a) + cx, r * Math.sin(a) + cy); let s, i; // sample end points update(sa); update(ea); // sample interior points aligned with 90 degrees if (ea !== sa) { sa = sa % Tau; if (sa < 0) sa += Tau; ea = ea % Tau; if (ea < 0) ea += Tau; if (ea < sa) { ccw = !ccw; // flip direction s = sa; sa = ea; ea = s; // swap end-points } if (ccw) { ea -= Tau; s = sa - (sa % HalfPi); for (i=0; i<4 && s>ea; ++i, s-=HalfPi) update(s); } else { s = sa - (sa % HalfPi) + HalfPi; for (i=0; i<4 && s<ea; ++i, s=s+HalfPi) update(s); } } } } }; function quadExtrema(x0, x1, x2, cb) { const t = (x0 - x1) / (x0 + x2 - 2 * x1); if (0 < t && t < 1) cb(x0 + (x1 - x0) * t); } function cubicExtrema(x0, x1, x2, x3, cb) { const a = x3 - x0 + 3 * x1 - 3 * x2, b = x0 + x2 - 2 * x1, c = x0 - x1; let t0 = 0, t1 = 0, r; // solve for parameter t if (Math.abs(a) > Epsilon) { // quadratic equation r = b * b + c * a; if (r >= 0) { r = Math.sqrt(r); t0 = (-b + r) / a; t1 = (-b - r) / a; } } else { // linear equation t0 = 0.5 * c / b; } // calculate position if (0 < t0 && t0 < 1) cb(cubic(t0, x0, x1, x2, x3)); if (0 < t1 && t1 < 1) cb(cubic(t1, x0, x1, x2, x3)); } function cubic(t, x0, x1, x2, x3) { const s = 1 - t, s2 = s * s, t2 = t * t; return (s2 * s * x0) + (3 * s2 * t * x1) + (3 * s * t2 * x2) + (t2 * t * x3); }