var PADDING = { top: 24, right: 16, bottom: 44, left: 48 } function drawChart(ctx, w, h, opts) { var xLabels = opts.xLabels var series = opts.series var colors = opts.colors if (!xLabels.length || !series.length) return ctx.clearRect(0, 0, w, h) var plotW = w - PADDING.left - PADDING.right var plotH = h - PADDING.top - PADDING.bottom var cx = PADDING.left var cy = PADDING.top var yMin = Infinity, yMax = -Infinity series.forEach(function (s) { s.data.forEach(function (v) { if (v !== null && v !== undefined) { if (v < yMin) yMin = v if (v > yMax) yMax = v } }) }) if (!isFinite(yMin) || !isFinite(yMax)) return var yRange = yMax - yMin || 1 yMin = Math.floor(yMin - yRange * 0.1) yMax = Math.ceil(yMax + yRange * 0.1) if (yMin < 0 && yMin + yRange * 0.1 > 0) yMin = 0 var realRange = yMax - yMin var toX = function (i) { return cx + (i / Math.max(xLabels.length - 1, 1)) * plotW } var toY = function (v) { return cy + plotH - ((v - yMin) / realRange) * plotH } var gridCount = Math.min(5, Math.ceil(realRange)) ctx.strokeStyle = "#edf2f7" ctx.lineWidth = 0.5 ctx.setLineDash([4, 4]) ctx.font = "10px -apple-system, sans-serif" ctx.fillStyle = "#a0aec0" ctx.textAlign = "right" ctx.textBaseline = "middle" for (var i = 0; i <= gridCount; i++) { var val = yMin + (realRange * i) / gridCount var y = toY(val) ctx.beginPath() ctx.moveTo(cx, y) ctx.lineTo(cx + plotW, y) ctx.stroke() ctx.fillText(formatVal(val), cx - 6, y) } ctx.setLineDash([]) var maxLabels = Math.floor(plotW / 50) var step = Math.max(1, Math.ceil(xLabels.length / maxLabels)) ctx.textAlign = "center" ctx.textBaseline = "top" ctx.fillStyle = "#718096" for (var i = 0; i < xLabels.length; i += step) { ctx.fillText(xLabels[i], toX(i), cy + plotH + 8) } ctx.strokeStyle = "#cbd5e0" ctx.lineWidth = 1 ctx.beginPath() ctx.moveTo(cx, cy) ctx.lineTo(cx, cy + plotH) ctx.lineTo(cx + plotW, cy + plotH) ctx.stroke() series.forEach(function (s, si) { var color = colors[si % colors.length] var points = s.data .map(function (v, i) { if (v !== null && v !== undefined) { return { x: toX(i), y: toY(v), v: v } } return null }) .filter(function (p) { return p !== null }) if (points.length < 2) return ctx.strokeStyle = color ctx.lineWidth = 2 ctx.lineJoin = "round" ctx.beginPath() ctx.moveTo(points[0].x, points[0].y) for (var i = 1; i < points.length; i++) { ctx.lineTo(points[i].x, points[i].y) } ctx.stroke() points.forEach(function (p) { ctx.fillStyle = "#fff" ctx.beginPath() ctx.arc(p.x, p.y, 3.5, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = color ctx.beginPath() ctx.arc(p.x, p.y, 2.5, 0, Math.PI * 2) ctx.fill() }) }) ctx.draw() } function formatVal(v) { if (v === 0) return "0" var abs = Math.abs(v) if (abs >= 1000) return (v / 1000).toFixed(1).replace(/\.0$/, "") + "k" if (abs >= 1) return Number(v.toFixed(1)).toString() return Number(v.toFixed(2)).toString() } module.exports = { drawChart: drawChart }