0
1

More than 1 year has passed since last update.

大量の点の 2 次元プロット(グリッドライン付き)(regl + d3.js)

Last updated at Posted at 2021-03-19

こんにちは。
大量の点の 2 次元プロットを行いました。regl (WelGL ライブラリ)の利用に加え1、d3.js を利用し2、マウスイベント処理(ズーム、スクロール)およびグリッドライン描画を行いました。

scatter.jpg
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>
  1. 参考:「大量の点の 2 次元プロット(regl)

  2. 参考:「Zoomable Scatterplot (d3.js)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1