Plotlyのhoverboxがモバイル環境で消えない問題とその回避策の巻

はじめに

Plotlyを使ってインタラクティブなチャートをモバイル端末で表示するときに、データポイントをタップすることでhoverboxが開いて設定しておいたtextの内容を表示できます。

でもこのhoverboxが、plotの内容を更新して他のチャートに切り替えたときに消えずに残ってしまいます。

一応、他の場所をタップすると消えるのですが、なんか気持ち悪い。チャートを切り替えた時に一緒に消えて欲しいのよ。。。

で、いろいろ試行錯誤した結果、HTMLにJavaScriptを追加することで解決できました、という話です。

<対応前:こんな感じでhoverboxが残る>

<対応後:チャートを切り替えた時にhoverboxは消える>

実施環境

Plotlyバージョン: plotly-3.0.1.min.js

これを動作させるHTMLファイルを作成し、ブラウザでHTMLファイルを開いてPlotlyのチャートを表示するというシチュエーションです。

原因

hoverboxが表示されるのは、

HTMLの記述において

<g class=”hoverlayer”></g>

の部分で、hoverboxが表示されるときにhoverlayerクラスの子にhoverboxの内容が挿入されます。

これが残ったままになることが原因のようでした。

対策

  • チャートの更新(plotly_update)イベントにイベントリスナーを仕掛ける
  • plotly_update イベントが発火したらhoverlayer内のHTMLを空にしてhover情報をリセットする

メモ

  • plotly_update は、Plotly.js で Plotly.update() などを使ってグラフのデータやレイアウトが更新されたときに発火するイベントです。
  • 他のイベント(plotly_relayoutplotly_restyle)でも使えるかも(試してないです)。

実装

具体的には、HTMLにおけるscriptタグ内の、Plotlyがプロットを作成する部分であるこのコードを、、、

Plotly.newPlot('plot', [graph1, graph2], layout)

こうする

Plotly.newPlot('plot', [graph1, graph2], layout).then(() => {
  const plotElem = document.getElementById('plot');
  plotElem.on('plotly_update', function () {
    const hoverLayer = plotElem.querySelector('.hoverlayer');
    if (hoverLayer) hoverLayer.innerHTML = '';
  });
});

これで、冒頭の動画のように、foverboxがチャートの切り替えで消えるようになりました。

ちなみに今回のサンプルのHTMLファイルの中身の全体はこうです

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>
  </head>
  <body>
    <div id="plot"></div>
    <script>
      const graph1 = {
        x: [1, 2, 3],
        y: [1, 2, 1],
        mode: 'markers',
        type: 'scatter',
        name: 'Graph 1',
        marker: { color: 'blue' },
        text: [
          'A: Point 1<br>X=1, Y=1',
          'A: Point 2<br>X=2, Y=2',
          'A: Point 3<br>X=3, Y=1'
        ],
        hoverinfo: 'text',
        visible: true
      };

      const graph2 = {
        x: [1, 2, 3],
        y: [3, 2, 4],
        mode: 'markers',
        type: 'scatter',
        name: 'Graph 2',
        marker: { color: 'red' },
        text: [
          'B: Point 1<br>X=1, Y=3',
          'B: Point 2<br>X=2, Y=2',
          'B: Point 3<br>X=3, Y=4'
        ],
        hoverinfo: 'text',
        visible: false
      };

      const layout = {
        title: {
          text: 'Graph 1',
          x: 0.03,
          xanchor: 'left',
          font: { size: 18, color: 'black' }
        },
        updatemenus: [
          {
            type: 'buttons',
            direction: 'down',
            showactive: true,
            x: -0.1,
            xanchor: 'left',
            y: 1.0,
            yanchor: 'top',
            buttons: [
              {
                label: 'Graph 1',
                method: 'update',
                args: [
                  { visible: [true, false] },
                  { title: 'Graph 1' }
                ]
              },
              {
                label: 'Graph 2',
                method: 'update',
                args: [
                  { visible: [false, true] },
                  { title: 'Graph 2' }
                ]
              }
            ]
          }
        ]
      };

      Plotly.newPlot('plot', [graph1, graph2], layout).then(() => {
        const plotElem = document.getElementById('plot');
        plotElem.on('plotly_update', function () {
          const hoverLayer = plotElem.querySelector('.hoverlayer');
          if (hoverLayer) hoverLayer.innerHTML = '';
        });
      });
    </script>
  </body>
</html>

試してみる

次のリンクをクリックしたら直接チャートが開きます(そのはず)

Open the graph BEFORE fix

Open the graph AFTER fix

というわけで、冒頭の動画で示したように修正することができました。

なお、こちらのリンク先にbefore afterのHTMLファイルを格納してあります。

GitHub repo: plotly-mobile-hover-demo

おわりに

モバイルでのPlotlyのhoverboxが残ってしまう問題を、Javascriptのイベントリスナーを使ってHTML内のhoverlayerをクリアすることで解決したというお話でした。おしまい。