chart.js
4.2 kB · javascript · 126 lines
1import { ink, fit, role } from './mrly.js';23export function board(canvas, height, { pad = 14, left = pad, right = pad, top = 26, bottom = 22 } = {}) {4 const [ctx, w, h] = fit(canvas, height);5 ctx.clearRect(0, 0, w, h);6 const floor = h - bottom, wide = w - left - right, tall = floor - top;7 const mono = getComputedStyle(document.body).getPropertyValue('--mono');8 ctx.font = `11px ${mono}`;9 return { ctx, w, h, left, right, roof: top, floor, wide, tall, mono, x: (f) => left + wide * f, y: (f) => floor - tall * f };10}1112export function bars(b, values, { peak = Math.max(...values, 1e-12), color = ink.blue, inset = 1 } = {}) {13 const n = values.length, step = b.wide / n;14 const paint = typeof color === 'function' ? color : () => color;15 values.forEach((v, i) => {16 const tall = b.tall * v / peak;17 b.ctx.fillStyle = paint(i, v);18 b.ctx.fillRect(b.x(i / n) + inset, b.floor - tall, Math.max(1, step - 2 * inset), tall);19 });20}2122export function line(b, points, color, { width = 1.5, dots = 0, dash = [], fill = 0 } = {}) {23 const { ctx } = b;24 const path = new Path2D();25 points.forEach(([fx, fy], i) => (i ? path.lineTo(b.x(fx), b.y(fy)) : path.moveTo(b.x(fx), b.y(fy))));26 ctx.strokeStyle = color;27 ctx.fillStyle = color;28 if (fill) {29 const area = new Path2D(path);30 area.lineTo(b.x(points.at(-1)[0]), b.y(0));31 area.lineTo(b.x(points[0][0]), b.y(0));32 ctx.globalAlpha = fill;33 ctx.fill(area);34 ctx.globalAlpha = 1;35 }36 ctx.lineWidth = width;37 ctx.setLineDash(dash);38 ctx.stroke(path);39 ctx.setLineDash([]);40 for (const [fx, fy] of dots ? points : []) {41 ctx.beginPath();42 ctx.arc(b.x(fx), b.y(fy), dots, 0, Math.PI * 2);43 ctx.fill();44 }45}4647export function rules(b, marks, { color = ink.line, dash = [], width = 1 } = {}) {48 const { ctx } = b;49 ctx.strokeStyle = color;50 ctx.lineWidth = width;51 ctx.setLineDash(dash);52 ctx.beginPath();53 for (const f of marks) {54 ctx.moveTo(b.x(f), b.roof);55 ctx.lineTo(b.x(f), b.floor);56 }57 ctx.stroke();58 ctx.setLineDash([]);59}6061export function axis(b, labels = [], { wall = false } = {}) {62 const { ctx } = b;63 ctx.strokeStyle = ink.line;64 ctx.lineWidth = 1;65 ctx.beginPath();66 ctx.moveTo(b.x(0), wall ? b.roof : b.floor);67 ctx.lineTo(b.x(0), b.floor);68 ctx.lineTo(b.x(1), b.floor);69 ctx.stroke();70 ctx.fillStyle = ink.dim;71 for (const [f, text] of labels) {72 ctx.textAlign = f <= 0 ? 'left' : f >= 1 ? 'right' : 'center';73 ctx.fillText(text, b.x(f), b.h - 6);74 }75 ctx.textAlign = 'left';76}7778export function tag(b, text, color = ink.dim, align = 'left', x = align === 'right' ? b.x(1) : b.x(0), y = 14) {79 b.ctx.fillStyle = color;80 b.ctx.textAlign = align;81 b.ctx.fillText(text, x, y);82 b.ctx.textAlign = 'left';83 return x + b.ctx.measureText(text).width;84}8586export function web(canvas, height, nodes, branches, roles, radius) {87 const [ctx, w, h] = fit(canvas, height);88 ctx.clearRect(0, 0, w, h);89 const n = nodes.length / 2;90 if (!n) return;91 let [x0, y0, x1, y1] = [Infinity, Infinity, -Infinity, -Infinity];92 for (let i = 0; i < n; i++) {93 const x = nodes[2 * i], y = nodes[2 * i + 1];94 x0 = Math.min(x0, x);95 x1 = Math.max(x1, x);96 y0 = Math.min(y0, y);97 y1 = Math.max(y1, y);98 }99 const pad = radius + 8;100 const scale = Math.min((w - 2 * pad) / (x1 - x0 || 1), (h - 2 * pad) / (y1 - y0 || 1));101 const ox = (w - (x1 - x0) * scale) / 2 - x0 * scale, oy = (h - (y1 - y0) * scale) / 2 - y0 * scale;102 const px = (i) => ox + nodes[2 * i] * scale, py = (i) => oy + nodes[2 * i + 1] * scale;103 const hues = role();104 const lines = hues.map(() => new Path2D());105 for (let k = 0; k < branches.length; k += 2) {106 const [a, b] = [branches[k], branches[k + 1]];107 const path = lines[roles ? Math.min(roles[a], roles[b]) : 0];108 path.moveTo(px(a), py(a));109 path.lineTo(px(b), py(b));110 }111 ctx.lineWidth = 1;112 lines.forEach((path, r) => {113 ctx.strokeStyle = roles ? hues[r] : ink.line;114 ctx.stroke(path);115 });116 const dots = hues.map(() => new Path2D());117 for (let i = 0; i < n; i++) {118 const path = dots[roles ? roles[i] : 2];119 path.moveTo(px(i) + radius, py(i));120 path.arc(px(i), py(i), radius, 0, Math.PI * 2);121 }122 dots.forEach((path, r) => {123 ctx.fillStyle = hues[r];124 ctx.fill(path);125 });126}