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}