素のHTML+JavaScriptで複素関数を描画する

はじめに

特別なライブラリを使わなくても、HTMLのcanvas要素を使ってJavaScriptで色々描ける。

例えば複素対数関数とかをこんな感じで。

f:id:tatsuzaki98:20200118132627p:plain

canvas要素の取り扱いについてはこちらから詳しくCanvasRenderingContext2D - Web API | MDN

とりあえずなんか描く

body要素にcanvasを設置してグラデーションを描いてみる

👇コード全文

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>famous canvas</title>
<style>canvas {border: 1px solid black;}</style>
<script>
window.onload = () => {
  // 👇JavaScriptでcanvas要素を取得、キャンバスサイズの定義とコンテキストの取得
  const canvas = document.getElementById('famousCanvas');
  const size = 256;
  [canvas.width, canvas.height] = [size, size];
  const context = canvas.getContext('2d');

  // 👇グラデーションを描画。fillStyleで色指定、fillRectで描画座標指定。
  for(let i = 0; i < size; i++){
    for(let j = 0; j < size; j++){
      context.fillStyle = `rgba(${i}, ${j}, 128, 1)`
      context.fillRect(i, j, 1, 1);
    }
  }
};
</script>
</head>
// 👇canvas要素を置く。
<body><canvas id="famousCanvas"/></body>
</html>

👇結果

f:id:tatsuzaki98:20200118134001p:plain

偏角を色に変換する

直交座標系x, yの偏角を色環に変換する。
原点(0,0)を視点としてある点(x, y)を通る半直線が下に示す六角形のどの辺に交わるかでRGBの値を決定する。

f:id:tatsuzaki98:20200118141735j:plain

👇コード(script部分のみ、rectToRgba関数が該当部分。)

const rectToRgba = (x, y) => {
  const max = 255;
  const sqrt3 = Math.sqrt(3);

  // 0点は例外処理。
  if (x == 0 && y > 0) return [max*0.5, max, 0];
  if (x == 0 && y < 0) return [max*0.5, 0, max];
  if (x > 0 && y == 0) return [max, 0, 0];
  if (x < 0 && y == 0) return [0, max, max];

  const tan = y / x;
  const cot = x / y;

  // 場合わけしてrgb算定(上の六角形と見比べるとわかりやすいかも)
  if (0 < tan && tan < sqrt3)
    return y > 0
      ? [max, tan/sqrt3*max, 0]
      : [0, (1-tan/sqrt3)*max, max]

  else if (-1/sqrt3 < cot && cot < 1/sqrt3)
    return y > 0
      ? [(1+cot*sqrt3)*max*0.5, max, 0]
      : [(1-cot*sqrt3)*max*0.5, 0, max]

  else
    return y > 0 
      ? [0, max, (1+tan)*max]
      : [max, 0, -tan*max]
};

window.onload = () => {
  const canvas = document.getElementById('famousCanvas');
  const size = 256;
  [canvas.width, canvas.height] = [size, size];
  const context = canvas.getContext('2d');

  for(let i = -size*0.5; i < size*0.5; i++){
    for(let j = -size*0.5; j < size*0.5; j++){
      const rgbArray = rectToRgba(i, j);
      context.fillStyle = `rgba(${rgbArray[0]}, ${rgbArray[1]}, ${rgbArray[2]}, 1)`
      context.fillRect(i+size*0.5, -j+size*0.5-1, 1, 1);
    }
  }
};

👇結果(恒等写像f(z) = zの図示になっている)

f:id:tatsuzaki98:20200118142306p:plain

いろんな複素関数を描いてみる

先ほどのコードにもう少してを加えて、いろいろ複素関数を描いてみる

👇コード全文 (complexFunction()で複素関数の定義するようにした。)

const complexFunction = (x, y) => {
  // ここに複素関数の定義を書く。たとえばf(z)=z^2なら以下の通り。
   return [x*x - y*y, 2*x*y];
};

const rectToRgba = (x, y) => {
  const max = 255;
  const sqrt3 = Math.sqrt(3);

  if (x == 0 && y > 0) return [max*0.5, max, 0];
  if (x == 0 && y < 0) return [max*0.5, 0, max];
  if (x > 0 && y == 0) return [max, 0, 0];
  if (x < 0 && y == 0) return [0, max, max];

  const tan = y / x;
  const cot = x / y;

  if (0 < tan && tan < sqrt3)
    return y > 0
      ? [max, tan/sqrt3*max, 0]
      : [0, (1-tan/sqrt3)*max, max]

  else if (-1/sqrt3 < cot && cot < 1/sqrt3)
    return y > 0
      ? [(1+cot*sqrt3)*max*0.5, max, 0]
      : [(1-cot*sqrt3)*max*0.5, 0, max]

  else
    return y > 0 
      ? [0, max, (1+tan)*max]
      : [max, 0, -tan*max]
};

window.onload = () => {
  const canvas = document.getElementById('famousCanvas');
  const size = 256;
  [canvas.width, canvas.height] = [size, size];
  const context = canvas.getContext('2d');

  for(let i = -size*0.5; i < size*0.5; i++){
    for(let j = -size*0.5; j < size*0.5; j++){
      const [x, y] = [i/size*5, j/size*5];
      const rgbArray = rectToRgba(...complexFunction(x, y));
      context.fillStyle = `rgba(${rgbArray[0]}, ${rgbArray[1]}, ${rgbArray[2]}, 1)`
      context.fillRect(i+size*0.5, -j+size*0.5-1, 1, 1);
    }
  }
};


👇結果

f:id:tatsuzaki98:20200118142946p:plain
f(z) = z*z = (x*x - y*y, 2*x*y)
色環が2周しているのがわかる🤔

f:id:tatsuzaki98:20200118132627p:plain
f(z) = log(z) = (log(sqrt(x*x + y*y)), atan2(y, x))
二次元ではリーマン面が表示しきれない🤔

f:id:tatsuzaki98:20200118143610p:plain
f(z) = exp(z) = (exp(x)*cos(y), exp(x)*sin(y))
偏角はyだけに依存していることがわかる🤔

f:id:tatsuzaki98:20200118144419p:plain
f(z) = sin(z) = (sin(x)*cosh(y), cos(x)*sinh(y));
🤔

以上