0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-01-23

Wijmo Scatter Chart 上でドラッグ&ドロップを自前実装する場合は、以下の流れがおすすめです。

  1. Chart の hostElement に対して mousedown, mousemove, mouseup を登録する。
    • 既存イベント(クリックイベントなど)を上書きしないために、stopPropagation()preventDefault() の呼び出し場所を慎重に選ぶ。
    • 可能な限り既存のイベント処理を壊さないように、ドラッグが “開始されている” 状態のときのみ後続イベントを抑制する。
  2. マウスを押したタイミングで「どの点をドラッグしているか」を特定し、ドラッグ中フラグや座標オフセットを保持する。
  3. マウス移動時は座標変換(画面座標→データ座標)を行い、ドラッグ中の点の値を更新する。
  4. データソースを更新後、chart.invalidate()chart.refresh() などでリアルタイムに再描画する。
    • ドラッグ動作をスムーズに見せたい場合は、requestAnimationFrame 等と合わせて最適化を検討。
  5. マウス解放 (mouseup) でドラッグ終了とし、フラグ等をリセット。
    • このタイミングで既存のクリックイベントと競合しないよう、stopPropagation() を使うかどうかを判断。

実装アプローチ:ステップバイステップ

以下では、最もオーソドックスな「Chart.hostElement に直接マウスイベントを付与」して実装する例を示します。

1. イベントリスナーの登録

Wijmo の Scatter Chart は FlexChart クラスで実装されるため、たとえば以下のように取得します。

// 例: HTMLに <div id="scatterChart"></div> がある場合
var chart = new wijmo.chart.FlexChart('#scatterChart', {
    chartType: wijmo.chart.ChartType.Scatter,
    itemsSource: myData, // [{ x: ..., y: ... }, ...]
    bindingX: 'x',
    series: [
        { name: 'Series1', binding: 'y' }
    ]
});
  • ドラッグ管理用の変数を用意します。

    var isDragging = false;
    var dragPointIndex = null;
    var dragSeriesIndex = null;  // シリーズが複数ある場合に備える
    var offset = { x: 0, y: 0 }; // ドラッグ開始時のマウス座標とデータ点のオフセット
    
  • イベント登録mousedown, mousemove, mouseup

    var host = chart.hostElement; // イベントを紐づける要素
    
    // マウス押下
    host.addEventListener('mousedown', function (e) {
        onMouseDown(e);
    });
    
    // マウス移動
    host.addEventListener('mousemove', function (e) {
        onMouseMove(e);
    });
    
    // マウス解放
    host.addEventListener('mouseup', function (e) {
        onMouseUp(e);
    });
    

既存イベントを壊さない工夫

  • mousedownmouseup では、即座に stopPropagation() はしないのが無難です。
  • ドラッグ対象の点が見つかった場合・ドラッグ中だけ event.preventDefault() を行うなど、必要最小限の処理にとどめましょう。
  • クリックイベントが必要な場面があるなら、ドラッグ量がある一定距離以上だったときだけクリックとはみなさない、というロジックを入れると衝突が減ります。

2. mousedown でドラッグ対象を特定する

Wijmo Chart には座標変換やヒットテストのメソッドがあります。

  • chart.hitTest(e) : ピクセル座標を Chart 上の要素(データ点、凡例など)にマッピングできます。

例えば以下のようにして、Scatter の点をクリックしたかどうか判定します。

function onMouseDown(e) {
    // マウスのスクリーン座標 → Chart内座標系(ピクセル) へ変換
    var rect = host.getBoundingClientRect();
    var cx = e.clientX - rect.left;
    var cy = e.clientY - rect.top;

    // ヒットテスト(デフォルトでは近いデータ点などを返す)
    var hti = chart.hitTest(new wijmo.Point(cx, cy));

    // ポイントが Seriesのアイテム (chartElement = 0:PlotArea, 1:ChartArea, 6:SeriesSymbol, etc.)
    if (hti.chartElement === wijmo.chart.ChartElement.SeriesSymbol) {
        isDragging = true;
        dragSeriesIndex = hti.seriesIndex;
        dragPointIndex = hti.pointIndex;

        // ドラッグ開始時の誤差を計算する場合に offset を保持
        // データ座標をすぐに更新しても良いが、誤差が許容できるなら省略可能
        var item = chart.collectionView.items[dragPointIndex];
        var dataPt = new wijmo.Point(item.x, item.y);
        var screenPt = chart.dataToPoint(dataPt);

        offset.x = cx - screenPt.x;
        offset.y = cy - screenPt.y;

        // 他のイベントを阻害したくない場合は最小限にとどめる
        // e.stopPropagation(); // 必要に応じて
        // e.preventDefault();  // 必要に応じて
    }
}

3. mousemove でリアルタイムに座標を更新

mousemove イベント時に、ドラッグ中(isDragging === true)であれば、マウス位置をデータ座標に変換し、該当データを更新→再描画します。

function onMouseMove(e) {
    if (!isDragging || dragPointIndex == null) {
        return;
    }

    var rect = host.getBoundingClientRect();
    var cx = e.clientX - rect.left;
    var cy = e.clientY - rect.top;

    // offset を考慮してドラッグ中のスクリーン座標を求める
    var dragScreenX = cx - offset.x;
    var dragScreenY = cy - offset.y;

    // スクリーン座標 → データ座標へ変換
    var newDataPoint = chart.pointToData(new wijmo.Point(dragScreenX, dragScreenY));

    // 実際に chart の dataSource (配列や CollectionView) の該当アイテムを書き換え
    var item = chart.collectionView.items[dragPointIndex];
    item.x = newDataPoint.x;
    item.y = newDataPoint.y;

    // 変更を反映させる
    chart.invalidate(); 
    // chart.refresh();  // invalidate() で十分な場合が多い(差分再描画)

    // ドラッグ中は、マウスイベントを独占していいなら preventDefault()するのもアリ
    // e.preventDefault(); 
}
  • chart.pointToData(pt) を使うと、画面上のマウス座標(ピクセル)をチャートのデータ座標に変換できます。
  • 毎回 chart.invalidate() or chart.refresh() を呼ぶと再描画が走り、移動中の点がリアルタイムに更新されます。
  • ただし、ドラッグが高速で動く場合、再描画が多すぎるとパフォーマンスに影響する可能性があります。
    • 対策例: requestAnimationFrame を使い、一定フレームごとに更新する。
    • あるいは: マウス移動イベントをある程度間引きする (throttle/debounce)。

4. mouseup でドラッグ終了

マウスを離したらドラッグ状態を解除します。

function onMouseUp(e) {
    if (isDragging) {
        // 必要に応じて最終的な微調整を行う
        // 例: アプリ側のビジネスロジックを呼んで、補正や保存処理をする

        isDragging = false;
        dragPointIndex = null;
        dragSeriesIndex = null;
    }
    // ここでは stopPropagation() や preventDefault() は通常不要
    // クリックイベントと競合するなら、ドラッグ量が一定以上ならばクリック無効化など工夫する
}

サンプルコード(簡易フルスクリプト)

<!DOCTYPE html>
<html>
<head>
    <title>Wijmo Scatter Chart Drag Sample</title>
    <link rel="stylesheet" href="https://cdn.grapecity.com/wijmo/5.latest/styles/wijmo.css" />
    <script src="https://cdn.grapecity.com/wijmo/5.latest/scripts/wijmo.min.js"></script>
    <script src="https://cdn.grapecity.com/wijmo/5.latest/scripts/wijmo.input.min.js"></script>
    <script src="https://cdn.grapecity.com/wijmo/5.latest/scripts/wijmo.chart.min.js"></script>
</head>
<body>
    <div id="scatterChart" style="width:600px;height:400px;"></div>
    <script>
        // 適当なデータ
        var myData = [
            { x: 0, y: 2 },
            { x: 1, y: 4 },
            { x: 2, y: 1 },
            { x: 3, y: 5 },
            { x: 4, y: 3 }
        ];

        var chart = new wijmo.chart.FlexChart('#scatterChart', {
            chartType: wijmo.chart.ChartType.Scatter,
            itemsSource: myData,
            bindingX: 'x',
            series: [
                { name: 'Series1', binding: 'y' }
            ]
        });

        // ドラッグ管理用
        var host = chart.hostElement;
        var isDragging = false;
        var dragSeriesIndex = null;
        var dragPointIndex = null;
        var offset = { x: 0, y: 0 };

        host.addEventListener('mousedown', onMouseDown);
        host.addEventListener('mousemove', onMouseMove);
        host.addEventListener('mouseup', onMouseUp);

        function onMouseDown(e) {
            var rect = host.getBoundingClientRect();
            var cx = e.clientX - rect.left;
            var cy = e.clientY - rect.top;

            // ヒットテスト
            var hti = chart.hitTest(new wijmo.Point(cx, cy));
            if (hti.chartElement === wijmo.chart.ChartElement.SeriesSymbol) {
                isDragging = true;
                dragSeriesIndex = hti.seriesIndex;
                dragPointIndex = hti.pointIndex;

                var item = chart.collectionView.items[dragPointIndex];
                var dataPt = new wijmo.Point(item.x, item.y);
                var screenPt = chart.dataToPoint(dataPt);

                offset.x = cx - screenPt.x;
                offset.y = cy - screenPt.y;

                // 必要に応じてイベント伝搬抑制
                // e.preventDefault();
                // e.stopPropagation();
            }
        }

        function onMouseMove(e) {
            if (!isDragging || dragPointIndex == null) return;

            var rect = host.getBoundingClientRect();
            var cx = e.clientX - rect.left;
            var cy = e.clientY - rect.top;

            var dragScreenX = cx - offset.x;
            var dragScreenY = cy - offset.y;

            var newDataPoint = chart.pointToData(new wijmo.Point(dragScreenX, dragScreenY));

            // データ更新
            var item = chart.collectionView.items[dragPointIndex];
            item.x = newDataPoint.x;
            item.y = newDataPoint.y;

            chart.invalidate();
            // e.preventDefault();
        }

        function onMouseUp(e) {
            if (isDragging) {
                isDragging = false;
                dragPointIndex = null;
                dragSeriesIndex = null;
            }
        }
    </script>
</body>
</html>

イベントリスナー登録・解除の最適化

  • Vue/React/Angular 等のフレームワークを使う場合は、コンポーネントのライフサイクルに応じて
    イベントリスナーを登録・解除するようにしましょう。
    • マウント(描画時)に addEventListener、アンマウント(破棄時)に removeEventListener
  • 生の JavaScript なら、ページ遷移時などにリスナーを外すかどうか検討します。

パフォーマンス面・注意点

  1. リアルタイム更新
    • chart.invalidate() or chart.refresh() を都度呼ぶと再描画が頻繁に走るため、CPU 負荷が高くなる場合があります。
    • 必要に応じて requestAnimationFrame や イベント間引き (throttle/debounce) を入れる。
  2. 既存のクリックイベントとの共存
    • シンプルな実装だと、ドラッグ後にクリックイベントが誤作動するなどの問題が起きる場合があります。
    • クリック判定したい場合は、マウスダウン→マウスアップまでのドラッグ量が小さいときにだけ「クリック」とみなす、といった判定を入れる。
  3. 範囲外移動の制限
    • ユーザーがチャート座標外までマウスを移動したとき、どう扱うかを検討(データが負の値にならないように制限するなど)。
  4. シリーズやバインディングが複数ある場合
    • hti.seriesIndexhti.pointIndex を活用し、正しいデータの更新対象を特定する必要があります。

まとめ

  • 自前でドラッグ機能を実装する場合chart.hostElement に直接マウスイベントを付け、
    hitTest, pointToData, dataToPoint を組み合わせて座標変換を行うのが王道。
  • 既存のクリックイベント等を壊したくない場合は、ドラッグしている間のみ preventDefault()stopPropagation() を呼び出すなど、必要最小限の干渉にとどめる。
  • リアルタイム描画invalidate()refresh() で行い、パフォーマンス面はマウス移動の頻度に応じてチューニングする。

このように実装すれば、Wijmo Scatter Chart 上のデータ点をドラッグで動かしつつ、既存のイベントを上書きせずに共存させることが可能です。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?