react-konvaで楕円描画を実装する（Ellipse vs SVGパス）

# はじめに

本記事はreact-konvaで楕円描画を実装したい方、SVGで楕円描画を実装したい方（またはSVGの円弧コマンド（Aコマンド）について学びたい方）向けの参考になればと。

react-konvaの基本編で詳しく解説しているので、まずはこちらもみてみてね。
https://qiita.com/yukinonyukinon/items/4cc4b0a36dfa20723c0d

## 今回作るもの
楕円描画ツールを**2つの方法**で実装していく：
1. **Ellipseコンポーネント**：シンプルな実装
2. **SVGパス（円弧コマンド）**：より柔軟で正確な描画

SVGパスの方がちょっと複雑だけど、マウス位置に楕円のラインが正確に追従するため使いやすいきがする・・:sunny:

**ソースコード全量**

gitのリンク[こちら](https://github.com/rainkorainko/react-konva/blob/main/src/drawing/Drawing08_EllipsevsSVG.tsx)


**デモアプリ**

https://codesandbox.io/embed/react-konva-demo-ellipsesvg-forked-46f95y?from-embed=


## 環境
今回はReact19・TypeScriptを利用している

```
"react": "19.1.0",
"typescript": "^4.9.5",
"konva": "9.3.20",
"react-konva": "^19.0.4",
``` 

Konva・react-konvaのインストール
```
npm install react-konva konva --save
```

# konvaのEllipsesで楕円を描く

## ■ 楕円を描画する仕組み
縦横それぞれの半径と中心の位置を指定してあげると楕円を書くことができる
```tsx
<Ellipse
    // 水平方向の半径
    radiusX={80}
    // 垂直方向の半径
    radiusY={30}
    // 中心の位置
    x={10}
    y={10}
    stroke="black"
/>
```

楕円データは以下のようにもつことにする。
```ts
{
    x: number;
    y: number;
    radiusX: number;
    radiusY: number;
}      
```

![image.pn](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/d7c9ae7f-c0e3-4b53-9c30-e442bafb252f.png)

（参考）https://konvajs.org/docs/shapes/Ellipse.html

## ■ クリック＆ドラッグでサイズ決定する

操作の流れは以下（正円を描画するときと同じである）
クリック地点：楕円の中心として固定
ドラッグ：マウスを動かした距離が楕円のx,yそれぞれの半径になる

**距離の設定**
中心地点（Mouse Down地点）から現在のマウス位置までの距離をx,yそれぞれの半径として、マウスを動かすたび更新し続ける。
ちなみに、[abs関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/abs)は絶対値を返す関数。これで中心点よりマウスが左上側にあっても負の値にならない。

```ts
const handleMouseMove = () => {
    // 半径を更新
    const dx = point.x - ellipse.x;
    const dy = point.y - ellipse.y;
    const radiusX = Math.abs(dx);
    const radiusY = Math.abs(dy);
    
    // Konva用の楕円データ
    const ellipseData = {
        ...ellipse,
        radiusX: radiusX,
        radiusY: radiusY,
    };
    
    setEllipse(ellipseData);
}
```
複数楕円を書きたい場合も円や線と同様、配列で管理すればいいだけであるので省略。

## ■ ソースコード全量
gitのリンクは[こちら](https://github.com/rainkorainko/react-konva/blob/main/src/drawing/Drawing07_Ellipse.tsx)


# SVG Pathを使って楕円を描く

## ■ そもそも・・・SVGパスの基本
SVGパスでは以下のようなコマンドと座標を指定しながら書きたい図形を描画していく。

**MはMoveToコマンド**：Mの位置が始点となり描画がはじまる
**LはLineToコマンド**：直線が描画できる

以下は、（0,0)から始まり（50,50)までの直線が赤い線で描画される。
```ts
<svg width="320" height="320" xmlns="http://www.w3.org/2000/svg">
    {/* 直線　始点M(0,0) から Line L(100,100) */}
    <path d="M0 0 L50 50" stroke="red" />
</svg>
```

この直線を組み合わせて、四角を書いてみる。

```ts
<path d="M 100 100 L 150 100 L 150 150 L 100 150 L 100 100" />
```
以下のようなイメージで四角形が描画されることとなる。
①M100 100：座標（100,100）からスタート
②L150 100：直線で座標(150,100)まで移動
③L150 150：直線で座標(150,150)まで移動
④L100 150：直線で座標(100,150)まで移動
⑤L100 100：直線で座標(100,100)まで移動

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/aabe9508-8c88-424a-9b10-887c247e599a.png" width="300">

上記書き方でもOKだが、Hは水平方向コマンド、Vは垂直方向コマンド、などさまざまな便利コマンドが用意されているみたい。

```ts
<path d="M 100 100 H 150 V 150 H 100 V 100" />
```
ソースコード

参考）https://developer.mozilla.org/ja/docs/Web/SVG/Tutorials/SVG_from_scratch/Paths

## ■ 楕円をSVGでかいてみる

次はいよいよ楕円をかいてみる。
楕円を描くには円弧コマンドを利用して書いていくみたい。

円弧コマンドは以下のようにたくさんの引数が必要とのこと（ﾑｽﾞｶｼｲ...）

```
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
```

それぞれどんな意味なのか調べてみたら以下のような役割を持つらしい。

rx： x方向の半径
ry： y方向の半径
x-axis-rotation：回転角
large-arc-flag：大弧フラグ
sweep-flag：方向フラグ
x：終点x
y：終点y

#### x-axis-rotation：回転角
それぞれ以下の角度を指定しており、角に応じて傾いていく
赤：0
青：45
緑：90

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/7b529b18-7249-4f4f-910e-d7c70923e967.png" width="300">

#### large-arc-flag：大弧フラグ
同じ楕円上で2点を結ぶ弧は、2つ存在するためどちらを描くか選べるフラグ
赤：短い弧（flg=0）
青：長い弧（flg=1）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/778c00a8-2493-4b60-8f7c-5b45fcc6d130.png" width="300">

#### sweep-flag：方向フラグ
これは結構文字のまま、２点に対してどっち側の方向に円弧を書くかという意味らしい。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/8fa42f25-9fc2-4be3-b9ea-d4a845e1c691.png" width="300">

実際に楕円にするにはAコマンド２つ組み合わせて1つの楕円を書き表す

```ts
{/*  A 半径x 半径y 回転角 大弧フラグ 方向フラグ 終点x 終点y*/}
<path d="M100 150 A40 20 0 1 1 180 150 A40 20 0 1 1 100 150" />
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4068391/79e24666-e1a9-40e7-a2e9-51f6b7c90f34.png)

参考）https://developer.mozilla.org/ja/docs/Web/SVG/Tutorials/SVG_from_scratch/Paths#%E5%86%86%E5%BC%A7

Gitサンプルソースコード）https://github.com/rainkorainko/react-konva/blob/main/src/intro/Intro05_SVGElipses.tsx


## ■ マウスドラッグに合わせた楕円をSVGでかいてみる（react-konva）

実際にマウスのドラッグに合わせて楕円を描画していく。

### konva内でSVGパスを利用する基本の形

SVGPathのデータをそのまま入れてあげれば描画可能である。
```tsx
<Stage width={500} height={500} >
    <Layer>
        <Path data={"M180 150 A40 20 0 1 1 100 150"} stroke="black" />
    </Layer>
</Stage>
```

### マウスドラッグ時
半径がマウスドラッグに合わせて大きくなるよう更新していく。

`point.x point.y`：マウスの現在地点
`startPoint.x startPoint.y`：クリックした最初の地点

１つ目の円弧Aはクリック地点からマウス現在地点まで、２つ目はマウス現在地点から最初のクリック地点までの戻りを表している。

```ts
const dx = point.x - startPoint.x;
const dy = point.y - startPoint.y;
// dxdyの２点間は楕円の直径となるため半分に
const radiusX = Math.abs(dx / 2);
const radiusY = Math.abs(dy / 2);

const pathData = `M${startPoint.x},${startPoint.y}
                  A${radiusX},${radiusY} 0 1,1 ${point.x},${point.y}
                  A${radiusX},${radiusY} 0 1,1 ${startPoint.x},${startPoint.y}`;
```

### ソースコード全量
gitソースコード全量は[こちら](https://github.com/rainkorainko/react-konva/blob/main/src/drawing/Drawing07_EllipseSVG.tsx)