Skip to content

Instantly share code, notes, and snippets.

@janispritzkau
Last active November 1, 2019 16:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janispritzkau/4b1582ce814f74b1cfd5aca1d8c38fd7 to your computer and use it in GitHub Desktop.
Save janispritzkau/4b1582ce814f74b1cfd5aca1d8c38fd7 to your computer and use it in GitHub Desktop.
Rendering area chart with WebGL (1 million points)
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL Chart</title>
<span id="fps">Loading</span>
<canvas></canvas>
<style>
body {
margin: 0;
overflow: hidden;
}
#fps {
position: absolute;
font-family: monospace;
pointer-events: none;
user-select: none;
color: white;
font-weight: bold;
background: black;
}
canvas {
display: block;
}
</style>
<script src="main.js"></script>
const fps = document.getElementById("fps")
const canvas = document.querySelector("canvas")
canvas.width = 1200, canvas.height = 800
canvas.style.width = `${canvas.width / devicePixelRatio}px`
const gl = canvas.getContext("webgl", { antialias: false })
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(vertexShader, /* glsl */`
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = position * matrix;
}
`)
gl.shaderSource(fragmentShader, /* glsl */`
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`)
const program = gl.createProgram()
for (const shader of [vertexShader, fragmentShader]) {
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader))
}
gl.attachShader(program, shader)
}
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(program))
}
gl.useProgram(program)
const posAttr = gl.getAttribLocation(program, "position")
gl.enableVertexAttribArray(posAttr)
const matrixU = gl.getUniformLocation(program, "matrix")
const colorU = gl.getUniformLocation(program, "color")
let value = 0
const points = [...Array(5000000)].map((_, i) => [i, value = (Math.random() * 16 - 8 + value * 4095) / 4096])
const areaArray = new Float32Array(points.length * 4)
for (let i = 0; i < points.length; i++) {
const [x, y] = points[i]
areaArray[i * 4 + 0] = areaArray[i * 4 + 2] = x
areaArray[i * 4 + 1] = -1
areaArray[i * 4 + 3] = y
}
const areaBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, areaBuffer)
gl.bufferData(gl.ARRAY_BUFFER, areaArray, gl.STATIC_DRAW)
let handle = 0
let avgTime = 0
let i = 0
let x = points.length / 2
let y = 0
let xScale = points.length
let yScale = 1
function frame() {
const start = performance.now()
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.uniformMatrix4fv(matrixU, false, [
2 / xScale, 0, 0, -2 / xScale * x,
0, 2 / yScale, 0, -2 / yScale * y,
0, 0, 1, 0,
0, 0, 0, 1
])
gl.bindBuffer(gl.ARRAY_BUFFER, areaBuffer)
gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0)
gl.uniform4f(colorU, 0.3, 0.4, 0.6, 1)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, areaArray.length / 2)
const error = gl.getError()
if (error) throw new Error(`WebGL error code: ${error}`)
const elapsed = performance.now() - start
avgTime = avgTime == 0 ? elapsed : (avgTime * 7 + elapsed) / 8
if (i++ % 20 == 0) fps.textContent = `${Math.min(60, 1000 / avgTime) | 0}fps ${avgTime.toFixed(2)}ms`
handle = requestAnimationFrame(frame)
}
addEventListener("wheel", event => {
const s = event.deltaY * 0.003
if (event.shiftKey) {
y += yScale * s * (event.clientY / canvas.height * devicePixelRatio - 0.5)
yScale *= 1 + s
} else {
x -= xScale * s * (event.clientX / canvas.width * devicePixelRatio - 0.5)
xScale *= 1 + s
}
})
let down = false
canvas.addEventListener("mousedown", () => down = true)
addEventListener("mouseup", () => down = false)
addEventListener("mousemove", event => {
if (!down) return
x -= event.movementX * xScale / canvas.width
y += event.movementY * yScale / canvas.height
})
addEventListener("blur", () => cancelAnimationFrame(handle))
addEventListener("focus", () => (cancelAnimationFrame(handle), handle = requestAnimationFrame(frame)))
handle = requestAnimationFrame(frame)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment