こんにちは。
大量の点の 2 次元プロットを行いました。regl (WelGL ライブラリ)の利用に加え1、d3.js を利用し2、マウスイベント処理(ズーム、スクロール)およびグリッドライン描画を行いました。
scatter.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<style>
html,body{
height: 100%;
margin: 0;
}
.axis path {
display: none;
}
.axis line {
stroke-opacity: 0.1;
shape-rendering: crispEdges;
}
svg,
#canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="canvas"></div>
</body>
<script language="javascript" src="https://npmcdn.com/regl/dist/regl.min.js"></script>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script language="javascript">
const pointSize = 8, container = "#canvas";
const nPoints = 1000;
let transform = d3.zoomIdentity;
const canvas = document.querySelector(container);
const svg = d3.select(container).append('svg');
const gX = svg.append("g").attr("class", "axis axis--x");
const gY = svg.append("g").attr("class", "axis axis--y");
const regl = createREGL({container: canvas});
const createScatterPoints = () => {
const generatePoints = (nPoints) => {
const rng = d3.randomNormal(0, 100);
return d3.range(nPoints).map(() => (
{ x: rng(),
y: rng(),
color: [Math.random(), Math.random(), 0]})
);
}
const points = generatePoints(nPoints);
const frag = `
precision highp float;
varying vec3 fragColor;
void main() {
gl_FragColor = vec4(fragColor, 1);
}`;
const vert = `
varying vec3 fragColor;
attribute vec2 pos;
attribute vec3 color;
uniform float pointSize;
uniform vec2 scale;
uniform vec2 offset;
void main() {
fragColor = color;
gl_PointSize = pointSize;
gl_Position = vec4(pos * scale + offset, 0, 1);
}`;
const drawPoints = regl({
frag: frag,
vert: vert,
primitive: 'points',
count: points.length,
attributes: {
pos: points.map(d => [d.x, d.y]),
color: points.map(d => d.color)
},
uniforms: {
pointSize: pointSize,
scale: regl.prop('scale'),
offset: regl.prop('offset'),
}
});
const drawScatterPoints = () => {
const scale = [transform.k/window.innerWidth*2, transform.k/window.innerHeight*2];
const offset = [transform.x/window.innerWidth*2 + (transform.k-1), -transform.y/window.innerHeight*2 - (transform.k-1)];
regl.poll();
drawPoints({scale: scale, offset: offset});
};
return drawScatterPoints;
};
const nTick = 10;
const drawPoints = createScatterPoints();
const render = () => {
drawPoints();
const {width, height} = canvas.getBoundingClientRect();
const xScale = d3.scaleLinear()
.domain([-width / 2, width / 2])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([-height / 2, height / 2])
.range([height, 0]);
const xAxis = d3.axisBottom(xScale)
.ticks((width + 2) / (height + 2) * nTick)
.tickSize(height)
.tickPadding(-12);
const yAxis = d3.axisRight(yScale)
.ticks(nTick)
.tickSize(width)
.tickPadding(-width + 6);
const zoomed = (event, d) => {
transform = event.transform;
gX.call(xAxis.scale(transform.rescaleX(xScale)));
gY.call(yAxis.scale(transform.rescaleY(yScale)));
drawPoints();
}
const zoom = d3.zoom().on("zoom", zoomed);
svg.call(zoom).call(zoom.transform, transform);
};
render();
window.addEventListener('resize', render);
</script>
</html>
-
参考:「大量の点の 2 次元プロット(regl)」 ↩