Vue.jsで作図する 凸レンズの焦点問題

突然ですが、こんな図みたことありますよね？
![スクリーンショット 2021-03-07 002551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/450a4bab-1d05-8ddb-5d29-b633713849fe.png)
これは理科で定番の凸レンズの焦点に関する問題です。
今回はこの焦点問題を理解しつつVue.jsで作図することを目指します。

github ソースコード:
https://github.com/tenugui-taro/convex-lens_public

~~作ったもの ※PC推奨：~~
※2021/04 削除しました

##準備
Vue2 × composition-api × Vuetify の構成で作ります。

```
$ vue create {project-name}
$ npm install @vue/composition-api
$ vue add vuetify
```
composition-apiを有効にするにはmain.tsで設定が必要になります。
[@vue/composition-api](https://github.com/vuejs/composition-api)

```javascript:src/main.ts
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)
```
##焦点問題とは
ざっくりいうと凸レンズに入射した光が集まる位置に<strong>実像</strong>ができる。
虫眼鏡でものが大きくみえる現象はこの原理を利用していたりする。

<strong>焦点</strong>は凸レンズに平行に入射した光が集まる場所
ちなみに、凸レンズの中心から焦点までの距離の２倍に光源があると光源と実像は同じ大きさになる。

解答のポイントは<strong>光の線を引きその交点を求めること。</strong>

- 光軸と平行な線
- 凸レンズの中心を通る線
- 焦点を通る線

![スクリーンショット 2021-03-08 043257.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/4ba81fef-f28b-48e7-b146-2b9fae623427.png)


##線を引く
解答のポイントとなる、光源から出る線を引く方法を考えます。
###vuetifyで作図
Vuetifyのドキュメントを眺めているとグラフ作成用UIコンポーネントがありました。
[v-sparkline](https://vuetifyjs.com/ja/components/sparklines/)

なんとなくおしゃれだしこれで。
まずはサンプルをcomposition-apiを使いつつコンポーネント化します。

```vuejs:src/components/Sparkline.vue
<template>
  <v-sparkline
    :value="value"
    :gradient="gradient"
    :smooth="radius || false"
    :padding="padding"
    :line-width="width"
    :stroke-linecap="lineCap"
    :gradient-direction="gradientDirection"
    :fill="fill"
    :type="type"
    :auto-line-width="autoLineWidth"
    auto-draw
  />
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "@vue/composition-api";

const GRADIENTS: string[][] = [
  ["#222"],
  ["#42b3f4"],
  ["red", "orange", "yellow"],
  ["purple", "violet"],
  ["#00c6ff", "#F0F", "#FF0"],
  ["#f72047", "#ffd200", "#1feaea"],
];

export default defineComponent({
  setup() {
    const state = reactive({
      width: 2, // 線の太さ
      radius: 10, // 線同士のつながり 小さいほど角張る
      padding: 8, // 描画エリアの余白
      lineCap: "round", // 線端の形状
      gradient: GRADIENTS[5], // 配色 
      value: [0, 2, 5, 9, 5, 10, 3, 5, 0, 0, 1, 8, 2, 9, 0], // 値
      gradientDirection: "top", // グラデーションの方向
      fill: false, // グラフの塗りつぶし
      type: "trend", // グラフの種類 trend or line
      autoLineWidth: false, // 自動で線を拡張してくれる？
    });

    return {
      ...toRefs(state),
    };
  },
});
</script>
```
おしゃれなグラフが表示されました。
![sparkline4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/86ab8266-8203-141b-b557-6fef0d779da2.gif)

焦点問題の作図をイメージして、３本の線を引きます。
線を重ねるためにpositionを設定しています。

```vuejs:src/App.vue
<div style="position: relative">
  <Sparkline style="position: absolute" :value="[100, 100, 0]" />
  <Sparkline style="position: absolute" :value="[100, 0, -100]" />
  <Sparkline style="position: absolute" :value="[100, -100, -100]" />
</div>
```

![sparkline5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/d28624d3-93c7-d77c-b555-d696009206cb.gif)

<strong>いい感じ......
あれ、他要素との座標をどうやって合わせる......?

ただ単純に用意した画像を重ねると以下の通り、これでは0点です。
![スクリーンショット 2021-03-07 162240.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/a4c446a1-3ef0-654c-1fd4-9c43e37abc50.png)
ここからどうするか......
ごまかしでwidthやheightを調整するうちに心が痛くなったので別の手段を考えます:innocent:

###svgで表現する
図形を組み合わせながら問題を用意します。

```vuejs:src/App.vue
<svg
  xmlns="http://www.w3.org/2000/svg"
  width="90vw"
  height="70vh"
  id="drawing-area"
>
  <ellipse cx="50%" cy="50%" rx="3%" ry="40%" fill="white" stroke="black" />
  <line
    x1="10%"
    y1="50%"
    x2="90%"
    y2="50%"
    stroke-width="1%"
    stroke="#455A64"
  />
  <circle cx="35%" cy="50%" r="1%" fill="#E0E0E0" stroke="black" />
  <circle cx="65%" cy="50%" r="1%" fill="#E0E0E0" stroke="black" />
  <rect x="20%" y="30%" width="1%" height="20%" fill="#e74c3c" />
</svg>
```
![スクリーンショット 2021-03-08 010407.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/2055ecb4-5b47-93de-4a9b-4db6e041afc5.png)
いよいよ作図します。
線を描画する関数、実像を描画する関数を用意しひとまず手計算した値を渡します。

```typescript:src/utils/addLine.ts
/**
 * 線を描画
 * @param {number[]} params
 */
export const addLine = (params: number[]) => {
    const drawingArea = document.getElementById("drawing-area");
    const line = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "line"
    );

    line.setAttribute("x1", `${params[0]}%`);
    line.setAttribute("y1", `${params[1]}%`);
    line.setAttribute("x2", `${params[2]}%`);
    line.setAttribute("y2", `${params[3]}%`);
    line.setAttribute("stroke", "black");
    drawingArea!.appendChild(line);
}
```
```typescript:src/utils/addRect.ts
/**
 * 四角形を描画
 * @param {number[]} params
 */
export const addRect = (params: number[]) => {
    const drawingArea = document.getElementById("drawing-area");
    const rect = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "rect"
    );

    rect.setAttribute("x", `${params[0]}%`);
    rect.setAttribute("y", `${params[1]}%`);
    rect.setAttribute("width", `${params[2]}%`);
    rect.setAttribute("height", `${params[3]}%`);
    rect.setAttribute("fill", `#e74c3c`);
    drawingArea!.appendChild(rect);
}
```
```typescript:src/App.vue
onMounted(() => {
  // addLine([x1, y1, x2, y2])
  // 凸レンズの中心を通る線
  addLine([20, 30, 80, 70]);

  // 光軸に対して平行な線
  addLine([20, 30, 50, 30]);
  addLine([50, 30, 80, 70]);

  // 焦点を通る線
  addLine([20, 30, 50, 70]);
  addLine([50, 70, 80, 70]);

  // addRect([x, y, width, height])
  // 実像
  addRect([80, 50, 1, 20]);
});
```
![スクリーンショット 2021-03-08 010125.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/1ad2c791-7662-ea22-3f3b-fa0972eed09e.png)
<strong>作図できました！100点:cherry_blossom::cherry_blossom:</strong>

##自動化
もちろん手計算は面倒です。
交点を算出する関数を用意し、vueファイルとは別に切り出します。
計算ばかりなので興味ある方は[ソースコード](https://github.com/tenugui-taro/convex-lens_public/tree/master/src/utils)を参照ください。

また、好きに動かせるようv-sliderコンポーネントを入れて完成です。
200点:cherry_blossom::cherry_blossom::cherry_blossom:
![sparkline5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1116978/fb7c910e-9ff1-58e7-62c9-ffe28439ad5f.gif)

ところで焦点の内側に入ると実像が出来ていませんね。
このときは、虚像が出来るのですが今回は実装見送りです......

##ホスティング
最後にfirebaseにホスティングして終了です。
https://firebase.google.com/docs/hosting?authuser=0

```
$ firebase init hosting
$ firebase deploy --only hosting
```

##まとめ
凸レンズをふと思い出して作ってみました。
失敗もしましたがv-sparklineの使い方を学べたのでよしということで。

また、[前回記事](https://qiita.com/tenugui_taro/items/c91b0ed208e448f1fce2)よりはまとまった記事にできた気がします。
今後もしょうもないことに取り組んでVueと仲良くなっていきたいです。