Wijmo Scatter Chart 上でドラッグ＆ドロップを自前実装する

# はじめに
Wijmo の Scatter Chart において、クリックイベントなど他のイベントに影響を与えずにドラッグ移動を実現するには、チャートのホスト要素に対して「mousedown」でドラッグ開始のフラグを立て、「mousemove」で座標差分に基づきデータや表示位置を更新し、「mouseup」でドラッグ終了後にイベントリスナーを解除する実装が有効です。重要なのは、各イベントハンドラー内で必要な場合のみ preventDefault() を呼び出し、stopPropagation() は不要な場面では呼ばずに他のイベント（例：クリックイベント）が適切に動作するようにする点です。

以下、実装のポイントとサンプルコード、及び注意点を詳しく解説します。

---

### 1. イベントハンドラーの登録と解除

- **mousedown イベント**  
  - チャートのホスト要素（例：Chart.hostElement）に対して「mousedown」イベントを登録し、ドラッグ開始の判定と初期位置（clientX, clientY）を記録します。  
  - このタイミングでは、他のクリックイベントを妨げないために stopPropagation() は使用せず、必要な場合にのみ preventDefault() を使います

- **mousemove イベント**  
  - ドラッグ中は、document など広い範囲で「mousemove」イベントを監視することで、マウスがチャート外に出た場合も追跡可能とします
  - ここで、前回の位置との差分を計算し、その差分に応じてチャートの座標（またはデータ）を更新します
  - 更新処理はリアルタイムな再描画（chart.refresh() など）を呼び出すことで、ユーザーに即時のフィードバックを与えます

- **mouseup イベント**  
  - ドラッグ終了時に、document 上で「mouseup」イベントを監視し、ドラッグ状態のフラグを解除し、mousemove および mouseup のリスナーを解除します。  
  - これにより、イベントリスナーの不要な継続登録を防ぎ、パフォーマンスやメモリリークの問題を回避します

---

### 2. クリックイベントとの競合を避けるための工夫

- **イベント伝播の制御**  
  - mousedown では、ドラッグ開始判定に必要な最低限の処理を行い、不要な場合は stopPropagation() を呼び出さないようにします。  
  - これにより、後続のクリックやその他のイベントハンドラーが正常に動作できるように配慮します

- **preventDefault() の使用**  
  - ドラッグ中に不要なブラウザの既定動作（テキスト選択など）が発生する場合にのみ、preventDefault() を呼び出すようにします。  
  - クリックイベント自体を上書きしないよう、必要最小限に留めることがポイントです。

---

### 3. ドラッグ移動時の Scatter Chart の更新手順

1. **ドラッグ開始（mousedown）**  
   - ユーザーがマウスボタンを押下したとき、現在のマウス座標（clientX, clientY）を記録し、ドラッグ状態（例：`isDragging = true`）を設定します。

2. **座標の差分計算と更新（mousemove）**  
   - マウス移動時に、前回の座標との差分（dx, dy）を算出します。  
   - その差分をチャートの座標オフセット（例：`offsetX`, `offsetY`）に加算し、更新後の値をチャートに反映させます。  
   - チャート更新時には、内部データ（例えば、プロットデータの位置）を更新し、chart.refresh() などで再描画を実施します。  
   - 【パフォーマンス考慮】  
     - 連続するmousemove イベントが非常に高頻度になるため、必要に応じて requestAnimationFrame を用いて更新処理を最適化するか、または debounce/throttle を検討してください。

3. **ドラッグ終了（mouseup）**  
   - マウスボタンが離されたとき、ドラッグ状態を解除し、mousemove および mouseup のイベントリスナーを解除します。  
   - 最終的な座標更新を行い、必要に応じて再描画処理を確定させます。

---

### 4. 最適なイベントリスナーの登録・解除方法とパフォーマンス上の注意点

- **登録タイミングと解除**  
  - mousedown でドラッグ開始を検知したら、mousemove と mouseup のイベントリスナーは document などグローバルに一時的に登録し、ドラッグ終了時に必ず解除する実装としてください。  
  - これにより、ドラッグ中のみイベントハンドラーが有効となり、余分なイベント処理を避けることができます。

- **Passive イベントリスナー**  
  - 可能であれば、touch イベントや一部のマウスイベントに対しては passive オプションを設定し、スクロールパフォーマンスの最適化を検討してください（ただし、preventDefault() を必要とする場合は注意が必要です）。

- **再描画の最適化**  
  - 連続更新がパフォーマンスに与える影響を最小限にするため、requestAnimationFrame を利用して更新タイミングを調整するか、mousemove 内の更新処理を軽量に保つ工夫をしてください。

---

### 5. サンプルコード

以下は、シンプルなサンプルコードです。これは、Wijmo の Scatter Chart のホスト要素に対してドラッグ移動機能を追加するイメージになります。

```javascript
// 例：チャートのホスト要素を取得（ID 等で取得）
const chartHost = document.getElementById('chartHost');
let isDragging = false;
let startX = 0, startY = 0;
let offsetX = 0, offsetY = 0;

// mousedown イベントでドラッグ開始
chartHost.addEventListener('mousedown', function(e) {
  // 必要なら e.button をチェックし、左クリックのみ対応するなどの処理を行う
  if (e.button !== 0) return;
  
  // ドラッグ状態にする（他のクリックイベントを妨げないために stopPropagation() は呼ばない）
  isDragging = true;
  startX = e.clientX;
  startY = e.clientY;
  
  // （必要に応じて）ブラウザのデフォルト動作を防止
  // e.preventDefault();
});

// mousemove イベントで座標更新
document.addEventListener('mousemove', function(e) {
  if (!isDragging) return;
  
  const dx = e.clientX - startX;
  const dy = e.clientY - startY;
  
  // オフセットを更新（ここでチャートの内部データや表示位置も更新）
  offsetX += dx;
  offsetY += dy;
  
  // 例：チャートの移動処理（chart は Wijmo の Chart コントロールのインスタンス）
  // chart.setOffset(offsetX, offsetY);
  // または、内部データを更新して再描画
  // chart.refresh();
  
  // 次回の基準位置を更新
  startX = e.clientX;
  startY = e.clientY;
  
  // 必要に応じて、requestAnimationFrame を利用して負荷軽減を図る
});

// mouseup イベントでドラッグ終了
document.addEventListener('mouseup', function(e) {
  if (isDragging) {
    isDragging = false;
    // ドラッグ終了後、最終更新処理や再描画を行う
    // chart.refresh();
  }
});
```

このサンプルでは、mousedown 時にドラッグ開始のフラグを立て、mousemove でドラッグ移動に応じた座標更新と即時再描画を行い、mouseup 時にドラッグ状態を解除しています。なお、各イベント内で必要な場合にのみ preventDefault() を使用することで、クリックイベントなど他のイベントへの影響を最小限に留めています。

【参考】  
  (StackOverflow: stopPropagation と preventDefault の違い)

---

以上の実装アプローチにより、Wijmo の Scatter Chart 上でドラッグ移動機能を実装しつつ、既存のクリックイベントやその他のイベントとの競合を避け、リアルタイムにグラフが再描画される仕組みを構築できます。