matplotlibを使ってJupyterLab上でインタラクティブな3Dグラフを作成する

## 1. はじめに
* インタラクティブなグラフは、数値の挙動を確認する時に何かと便利です。
* また、３Dのグラフを表示した際には、インタラクティブに見る方向を変えられると便利です。
* Jupyter notebookを使っていた頃は、[ipywidget](https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html)という仕組みを使って、インタラクティブに動くグラフを作れたのですが、現時点ではJupyterLabでは対応していないように思います。
* JupyterLab上でこのような仕組みについて紹介した記事が、私が調べた限りではあまり多くはなかったように思いましたので、投稿してみようと思いました。

## 2. 概要
* matplotlibのwidgetという仕組みを使うと、GUI backendに依存せずに、Axesに配置するような形でインタラクティブなWidgetを作ることが可能です。

* 以下、[widgetのドキュメント](https://matplotlib.org/3.1.1/api/widgets_api.html)からの抜粋です。

>Widgets that are designed to work for any of the GUI backends. All of these widgets require you to predefine a matplotlib.axes.Axes instance and pass that as the first arg. 

* JupyterLab上で、こんな感じのグラフが簡単に作れます。（このグラフはブラウザのJupyerLab上で動いています。）
![Moving_3D_graph.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/ca17894f-feec-d993-33d4-1f43c9aa408c.gif)

## 3. 具体的なやり方

　ということで、上のようなグラフの作成方法をご説明します。なお、以下をJupyterLab上で作りましたが、先ほどのご説明の通り、matplotlib.widgetはGUI Backendに依存しないということなので、Jupyter notebookはもちろん、LinuxやWindows, MacのGUI上でも同様の結果になるものと思います。（手元では試してませんが）

### (1) 必要なライブラリをインポート
まず、必要なライブラリのインポートを行います。また、バックエンドをwidgetに設定する必要があります。

```python
%matplotlib widget
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.widgets import Slider
import numpy as np
```

### (2) サンプルとして表示するデータを作成
ここでは、例として、$z=x^2+2y^2$のグラフを書いてみることにします。

```python
x = y = np.arange(-20, 20, 0.5)
X, Y = np.meshgrid(x, y)
Z = X*X + 2 * Y*Y
```

### (3) Figureを作成
　ご存知の通り、Figureは、matplotlibでグラフを書くキャンパスのようなものです。Figureの上にAxesを載せ、その上にPlotをしていくイメージです。

```python
# Figureの設定
fig = plt.figure(figsize=(5,5))
```

### (4) Axesの作成
次に、Figure上にAxesを作成します。

* 今回は、３Dグラフのプロット用のAxesを１つ、Slider表示用のAxesを２つ作成します。
* どんなやり方でもいいのですが、今回はgridspecを使ってみました、
* 下をコードを実行することで、図のように、Figureを20x20に分割し、1-17行目に３Dグラフ描写用のAxesを、18, 19行目にそれぞれSlider用のAxesを配置できます。

![Axes配置.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50769/d05ac35e-09e8-c025-a2bf-28f88125ac3e.png)

```python
# Figureの中に３Dグラフ、Slider用のAxesを追加
axcolor = 'gold'
gs = fig.add_gridspec(20, 20)
ax1 = fig.add_subplot(gs[:17,:], projection='3d')
ax_slider_z = fig.add_subplot(gs[18,:], facecolor=axcolor)
ax_slider_xy = fig.add_subplot(gs[19,:], facecolor=axcolor)
```

### (5) Sliderの設定

次に、Sliderオブジェクトを作成します。以下のコードでは、

* slider_zとslider_xyというSiderオブジェクトを作成し、それぞれ、上で作成したax_slider_z, ax_slider_xy上に描写
* Sliderの動く範囲は-180〜180
* Sliderの初期値としてz0(=0)を設定
* Sliderの動く幅としてdelta(=10)を設定

を行っています。

```python
# Sliderの設定
z0 = 0
xy0 = 0
delta = 10
slider_z = Slider(ax_slider_z, 'z-axis', -180, 180, valinit=z0, valstep=delta)
slider_xy = Slider(ax_slider_xy, 'xy-axis', -180, 180, valinit=xy0, valstep=delta)
```

### (6) 3Dグラフの見る角度の初期値を設定後、３Dグラフを表示。
次に、３Dグラフの、z軸周り、xy平面周りの見える角度の初期値を設定します。この数値を変化させることで、３Dグラフの見る角度を動かすことができます。

```python
# 3Dグラフの見る方向の初期値を設定
ax1.view_init(elev=z0, azim=xy0)

# 3Dグラフを表示
ax1.plot_surface(X, Y, Z)
```

### (7) Sliderを動かした時に呼ばれるコールバック関数を作成
次に、Sliderを動かした時に呼ばれるコールバック関数を作成します。ここで、３Dグラフを見る角度を指定し、グラフを再描写することで動くグラフを作成することができます。

```python
# Sliderを動かした時に呼ばれるコールバック関数
def view_change(val):

    sz = slider_z.val
    sxy = slider_xy.val
    ax1.view_init(elev=sxy, azim=sz)
    fig.canvas.draw_idle()    
```

### (8) コールバック関数の設定
最後に、Sliderオブジェクトに先ほど作成したコールバック関数を設定します。

```python
slider_z.on_changed(view_change)
slider_xy.on_changed(view_change)
plt.show()
```

以上で、JupyterLab上で簡単に動くグラフを作成することができます！
[こちら](https://gist.github.com/mxl00474/b35ba7a48667265e8551ad20dc2f909a)にgistも置いておきましたので、よろしければお使いください。（上でご説明したものと同じですが。。。）