Tableau で3Dプロットをしてみた３（軸、グリッド有Ver.）

# はじめに

この記事は [Ateam LifeDesign Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ateam-life-design) の 10日目の記事です。

[前々回](https://qiita.com/hironey/items/27f6464fb1c94fcc1791)、[前回](https://qiita.com/hironey/items/3b069d8763943be1f6f0) に引き続き、Tableau で 3D をプロットをしてみる、というお話です。
前々回で散布図的に点描画での表現をして、前回でそれを塗りつぶして（滑らかではないものの）曲面で覆われている感じまでいけました。当初の目標の Excel の等高線にだいぶ近づいてきたかと思います。

（再掲）
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/1d7869b6-8be2-4d59-69a5-be6338850501.png">

今回は、Excel の等高線にあって、やりたいけどできていなかった最後のイシューである、軸やグリッド線の描画、です。

ではいきます。

# データ準備

まず、軸とグラフ（描画する点）を分けるために、データに型（タイプ）をもたせます。

x,y,z軸として以下のデータを作成。
原点の０地点ではなく、グラフの奥側に軸を表示するために、x,y軸方向に（描画範囲を多めに取った） -3 ずらしたところに描画することにします。

```axis.csv
axis_x,1,-3,-3,0
axis_x,2,3,-3,0
axis_y,1,-3,-3,0
axis_y,2,-3,3,0
axis_z,1,-3,-3,0
axis_z,2,-3,-3,3
```

グラフ描画部分の既存の data.csv にも行の先頭に 'points' という値を入れる。

```data.csv
points,1,-2.0,-2.0
points,2,-2.0,-1.9
points,3,-2.0,-1.8
・・・
points,1679,2.0,1.8
points,1680,2.0,1.9
points,1681,2.0,2.0
```

これらの２つのCSVファイルを Tableau で取り込み、ユニオンする。

points 側が z を持っていない（後で計算フィールドで作成する）ので、
axis 側 の z は一旦、z_orig という名前で取り込んでおいて、
z の計算フィールドで以下のように z_orig の値があるかどうかで切り分ける。

```
IF ISNULL([z_orig]) THEN
  EXP(-SQUARE([x])-SQUARE([y]))*3 // ガウス関数
ELSE [z_orig] END
```

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/41012b22-894c-ef86-7a8d-8927c5375c59.png">

x', y', z', x'', y'', z'', h, v, d の作成（三次元座標から二次元座標への投影変換）は[前々回の記事](https://qiita.com/hironey/items/27f6464fb1c94fcc1791)を参照してください。

# 軸（axis）を描画

フィルタで type の points を一旦除外しておき、axis_* だけをプロットする。

今までと同様に、列に h、行に v を入れ、「詳細」に「id」を入れる。今回はそれぞれの軸（および points）を別で扱いたいので、「詳細」に「type」も入れる。
マークを「線」にして、「パス」を「id」とする。

θ、φ をいじると、x,y,z軸をノートに手書きする時のようになった。
前後が分かりやすいように、d をサイズに入れてみる。
（この時点で今まで縦方向の視点角度がプラスマイナス逆向きの方が分かりやすかったのではと気がついたが、一旦無視）

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/5a5a226b-c1d4-723a-e1dc-f3419059bcb5.png">

軸が交わる点は `(x, y, z) = (-3, -3, 0)`

# グラフ(points)も描画

ここで、points も描画してみる。フィルタで points も追加する。

ベタ塗りになっちゃってるけど、やりたいことの方向性は良さそう。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/543bd20d-2167-2d1e-8999-f2738795e411.png">

## 軸とグラフの分離

このままだと points の方もパスで線になっちゃってるので、axis_* と points で表示形式を変えるために、h のフィールドを分けて、別の軸として表示させてみる。
```
h_axis   = IF [type] <> 'points' THEN [h] END
h_points = IF [type] = 'points' THEN [h] END
```
列の h の代わりに、h_axis と h_points を入れて、二重軸にする。（軸の同期をしておく）
左に来ている項目が後ろ側になるので、h_axis を左側に置く。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/d3feedd4-bdce-3e69-973b-4d1f865f2ff0.png">
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/53325a31-fd0d-37b2-a9da-9356513635f3.png">

さっきと見た目は同じですが、マークが別々になっているので、h_points の方を前回の記事と同様に、マークを「円」にしてサイズを調整し、z で色分け、id を d で並び替えする。
（h_axis の方は特にいじることはない）

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/ce813960-4bf9-feac-98b6-c58cd28df1ba.png">

θ と φ を変化させれば、グラフの動きに軸がついてくる。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/0f480d4e-383a-9f84-4913-7367f3fc4ace.png">

これでベースができたので、後はより見やすいように、軸に加えてグリッドや目盛りを入れていく。

## グリッド

グリッド線の表現は、単に x,y,z軸を引いたのと同様に線を増やせば良い。
今回は上で引いた、`(x, y, z) = (-3, -3, 0)` を交点とする x,y,z軸に加えて、
グリッド線として、z 軸方向には、0〜3 まで １刻み、x と y 軸方向には -3〜3 まで 2 刻みで、計18本の線の始点、終点の座標を定義した。

追加した結果の axis.csv は以下の通り。

```axis.csv
axis_x,1,-3,-3,0
axis_x,2,3,-3,0
axis_y,1,-3,-3,0
axis_y,2,-3,3,0
axis_z,1,-3,-3,0
axis_z,2,-3,-3,3
axis_xz1,1,-3,-3,1
axis_xz1,2,3,-3,1
axis_xz2,1,-3,-3,2
axis_xz2,2,3,-3,2
axis_xz3,1,-3,-3,3
axis_xz3,2,3,-3,3
axis_xy1,1,-3,-1,0
axis_xy1,2,3,-1,0
axis_xy2,1,-3,1,0
・・・
axis_zy1,1,-3,-1,0
axis_zy1,2,-3,-1,3
axis_zy2,1,-3,1,0
axis_zy2,2,-3,1,3
axis_zy3,1,-3,3,0
```

グリッド線はグラフと別軸になっているため、一緒に（混ぜて） id の並び順を決められないので、
グリッド線がグラフにかぶると（二重軸の順番で）グラフ側が常に前に来てしまって遠近感がおかしくなる。
ので、かぶらないように、
θ と φ の範囲が -180 〜 180 だったのを、
θ：0 〜 90
φ：-90 〜 0
とした。

下側から覗くことができなくなっちゃうのは残念…

これで、グリッド線とともにぐるぐる動く。
グリッド線もグラフ側と同じように z の高さでグラデーションをかけているのでなんとなく高さのイメージも湧く。
パスの線の種類（実線、点線とか）は変えられるが、太さは変えられないっぽい。もう少し細くしたい…

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/3cd1658b-912c-9613-90a5-0a40bda31a40.png">

あとは、これに目盛りの数値を入れられれば完成。
ついでに軸の範囲固定とヘッダの非表示化をしておく。

## 目盛り

考え方としては、グリッド線にテキストラベルをつけて、その値を目盛りにしてあげれば良さそう。
とりあえず見た目がどうなるか確認するため、axisの方にテキストラベルで type を入れてみたもの。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/09ba73dc-11aa-1209-29a8-e873bfced328.png">

軸側についてていいんだけど、グリッド線やラベルとグラフがかぶって見づらい。
パスの順序を変えれば、外側にラベルが付くのでは。
と思って、とりあえず id を逆順（昇順）にしてみたら、ラベルの位置は良くなったけど、
グラフ側も描画順が変わってしまった。
おっと、この並び替えは二重軸にしている points 側と共通になっている、と。なんと…

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/4d1a6576-12e8-3f7f-03c0-3eb4c4621b41.png">

並び順を元に戻し、ラベルのオプションでなんとかできないか探ったところ、
「ラベルを付けるマーク」のところで「終点」を選ぶとオプションが出てくるので、
そこで「終了行にラベル」のチェックを外すと求める結果になった。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/b87074e9-286e-bf6b-49a3-e1913eecc606.png">

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/ee946f09-cc88-002f-9d5f-c63efb7de80c.png">

あとは、このラベルを type ではなく、そのグリッド線の値にしてあげれば良い。

グリッド線によって表示したい値の元となる項目は異なるので、一律なロジックではできなさそう。計算フィールドで、CASE文を駆使して場合分けしまくればできるかもだけど、あんまり生産的じゃない。

ちょっとイマイチだけど、グリッドの type ごとに表示したい数値を別項目で用意してしまおう。
（全部のグリッド線に目盛りの値が欲しいわけでもないので、入力データでやったほうが好きなようにできるメリットはある）

axis.csv の最後の項目に、目盛りの数字が表示したければその値（または軸の名前）を入れるようにした。

```axis.csv
axis_x,1,-3,-3,0,x
axis_x,2,3,-3,0,x
axis_y,1,-3,-3,0,y
axis_y,2,-3,3,0,y
axis_z,1,-3,-3,0,z
axis_z,2,-3,-3,3,z
axis_xz1,1,-3,-3,1,1
axis_xz1,2,3,-3,1,1
axis_xz2,1,-3,-3,2,2
axis_xz2,2,3,-3,2,2
axis_xz3,1,-3,-3,3,
axis_xz3,2,3,-3,3,
axis_xy1,1,-3,-1,0,-1
axis_xy1,2,3,-1,0,-1
axis_xy2,1,-3,1,0,1
・・・
axis_zy1,1,-3,-1,0,-1
axis_zy1,2,-3,-1,3,-1
axis_zy2,1,-3,1,0,1
axis_zy2,2,-3,1,3,1
axis_zy3,1,-3,3,0,3
```
新しいフィールドを scale_label とし、「ラベル」の type と入れ替え。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/83672abd-e07e-ecba-c686-e196ae2dd0a2.png">

完成！

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81599/68310ca8-c996-537c-2900-7246f4a2dbc5.png">

※ 上記はアニメーションになっていますが、動いていない場合はクリックしてみてください

# まとめ

今回の Tableau での構築を通じて、初めて "パス" を使って線を描画することを学べました。
また、異なる概念のデータ（今回で言えば 軸axis と グラフpoints）を同一ビューで扱いたい場合にどうデータを持たせて、どう描画させるのかはよく悩んでいましたが、カラムとしてデータタイプを持たせて、ユニオンで繋いで、二重軸でそれぞれを別のグラフとして表現する方法も、ケースによっては一つの手段として使えそうなことも分かりました。

今回試行した3Dプロットに関しては、当初目標として描いていた状態にはだいぶ近づけたかなと思いますが、軸やグリッド線を予めデータとして用意してしまったのは、汎用性がなくてあまりスマートではありません。
描画したいデータ範囲を元に、軸の範囲やグリッド線の間隔が動的に決まる、というのが理想です。
せめてビュー側の入力パラメータで変更できるようにしたい。
データ元がDBであれば、パラメータを用いたカスタムSQLでいけそうな気もするんですが、CSVではカスタムSQLが使えず…

このあたりは今後の課題かなと思います。
ひとまず今回のチャレンジはここで終了です。

ここまでお付き合いいただいてありがとうございました。