120 lines
3.1 KiB
JavaScript
120 lines
3.1 KiB
JavaScript
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 }
|