0
2

More than 3 years have passed since last update.

大量の点の 2 次元プロット(regl)

Last updated at Posted at 2020-01-11

こんにちは。
大量の点の 2 次元プロットについて、regl (WebGL ライブラリ)を利用することによる高速性を試してみました(reglを利用する目的はWebGLを簡素に記述して利用したいためです)。mousedown, mouseup, wheel, resize イベントの処理については自前で行いました。

1000万点(ten millions)1 をプロットしてもスクロール・ズーム時の描画レスポンスについては良いようです。

scatter.jpg

scatter.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <style>
    html,body{
        height: 100%;
        margin: 0;
    }
    .canvas{
        height: 100%;
        padding: 0px;
    }
    </style>
  </head>
  <body>
    <div class="canvas"></div>
  </body>
  <script language="javascript" src="https://npmcdn.com/regl/dist/regl.min.js"></script>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script language="javascript">
  const pointSize = 8;
  const nPoints =   100000;
  const timerDelay = 100;
  const deltaY = 100;

  const rng = d3.randomNormal(0, 0.3);
  const points = d3.range(nPoints).map(() => 
     ({ x: rng(),
        y: rng(),
        color: [Math.random(), Math.random(), 0]})
  );

  let scale = 1, offset = [0, 0];
  let timer, dragStart;

  const mouseDown = e => {
    dragStart = {x: e.pageX, y: e.pageY};
  }

  const mouseUp = e => {
    offset[0] += (e.pageX - dragStart.x) * 2 / window.innerWidth;
    offset[1] += (e.pageY - dragStart.y) * 2 / window.innerHeight;
    draw();
  }

  const wheelScroll = e => {
    const magnification = Math.exp(-e.deltaY / deltaY);
    scale *= magnification;
    offset[0] *= magnification;
    offset[1] *= magnification;
    draw();
  }

  const resize = e => {
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      regl.poll();
      draw();
    }, timerDelay);
  };

  document.addEventListener("mousedown", mouseDown, false);
  document.addEventListener("mouseup", mouseUp, false);
  document.addEventListener("wheel", wheelScroll);
  window.addEventListener('resize', resize);

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 float scale;
  uniform vec2 offset;

  vec2 normalizeCoords(vec2 pos) {
    float x = pos[0] * scale + offset[0];
    float y = pos[1] * scale + offset[1];
    return vec2(x, -y);
  }

  void main() {
    fragColor = color;
    gl_PointSize = pointSize;
    gl_Position = vec4(normalizeCoords(pos), 0, 1);
  }`;


  const draw = () => {
    drawPoints({scale: scale, offset: offset});
  }

  const regl = createREGL({
    container: document.querySelector('.canvas'),
  });

  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'),
    }
  });

  draw();
  </script>
</html>

  1. ただし 1億点をプロットしようとすると、配列管理の上限を超えるようで、異常発生しました。 

0
2
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
2