Plotlyで作るグラフをDjangoで使う

# 本記事について
Plotlyでガントチャートを作成する方法と、それをDjangoのテンプレートに配置して表示する方法を書きます。



## 先に結論
1. `plotly.figure_factory.create_gantt()`を読み込む
2. `fig = ff.create_gantt（df）`でガントチャートのオブジェクトを作成する
3. `plotly.offline.offline.plot()`を読み込む
4. `gantt = plot(fig, output_type='div', include_plotlyjs=False)`でグラフとグラフを描画するスクリプトを含むhtmlコードの文字列を取得する
5. `autoescape`と`endautoescape`でエスケープ機能をoffにした上で、4.で取得した文字列をテンプレートにわたす


# Plotlyとは
オンラインデータの分析や視覚化のツールを開発している会社で、JavaScriptやPython、Rで使えるグラフ作成ライブラリを提供しています。
Python用のライブラリを使えば、例えば以下のリンク先のようなグラフを描画することができます。
[Plotly Python Open Source Graphing Library](https://plot.ly/python/)



# ガントチャートの作成方法
以下のリンク先に例が記載されています。
[Gantt Charts in Python](https://plot.ly/python/gantt/)

```python:simpleなsample
import plotly.figure_factory as ff

# 日時のフォーマットは `2000-01-01 00:00:00`
df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
      dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
      dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')]

fig = ff.create_gantt(df)
fig.show()
```
![newplot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/8445ec24-92fd-2406-3b5e-b2b96f63386f.png)

データフレームもしくはリスト型のデータを使います。

キーが`Tast`,`Start`,`Finish`の辞書型が、ガントチャートのバー１本分に相当し、それをリスト(変数`fig`とする)に格納します
`ff.create_gantt()`の第一引数に変数`fig`セットすることで、ガントチャートのオブジェクトが作成されます。
`.show()`はブラウザを起動して、引数に入れたオブジェクトのガントチャートを描画します。


又、`create_gantt()`には様々な引数があり、グラフをカスタマイズすることが可能です。
（カラーバーを表示したり、色を変えたり、太さを変えたりなどなど）


`create_gantt(df, colors=None, index_col=None, show_colorbar=False, reverse_colors=False, title='Gantt Chart', bar_width=0.2, showgrid_x=False, showgrid_y=False, height=600, width=None, tasks=None, task_names=None, data=None, group_tasks=False, show_hover_fill=True)`

*help(ff.create_gantt)で説明が見られます。*





# 作成したグラフをHTMLとして取得する
上記の`.show()`の代わりに`plotly.offline.offline.plot()`を使うことで、HTML形式のファイルを作成したり、divで囲まれたHTMLのコードを出力してくれます。Djangoのテンプレートに配置したい場合は、こちらの関数を使います。

```python:sample2
from plotly.offline import plot

df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28'),
      dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'),
      dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')]

fig = ff.create_gantt(df)
plot_fig = plot(fig, output_type='div', include_plotlyjs=False)
print(plot_fig)
```
↓
<img width="1179" alt="スクリーンショット 2020-01-16 23.28.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/cb9f4457-79da-bcc6-af47-5775e407828b.png">

`plot()`の引数
Chart StudiotというGUIでグラフをカスタマイズできるサービスへのリンクを表示するか否か・アニメーションを自動再生するかなど、色々ある

`plot(figure_or_data, show_link=False, link_text='Export to plot.ly', validate=True, output_type='file', include_plotlyjs=True, filename='temp-plot.html', auto_open=True, image=None, image_filename='plot_image', image_width=800, image_height=600, config=None, include_mathjax=False, auto_play=True, animation_opts=None)`


**Djangoでplotlyを使う際は、主に`include_plotlyjs`と`output_type`を設定します**

## output_type
グラフと、グラフを生成するスクリプトを含むHTMLを、どのように出力するかの設定。

**`output_type='div'`**
グラフとグラフを生成するスクリプトを含むHTMLのコード(divに囲まれている)が、文字列で出力される

**`output_type=file`**
グラフとグラフを生成するスクリプトを含むHTMLファイルが生成される


## include_plotlyjs
plotly.jsライブラリのソースコードをどのように読み込むか設定できます。

**`include_plotlyjs=False`**
plotly.js CDNを参照するコードは含まれない
`output_type = 'div'`で出力したコードが、plotly.jsを読み込んでいるHTMLドキュメント内に配置されれば利用可能

**`include_plotlyjs=con`**
plotly.js CDNを参照するスクリプトタグが出力に含まれまれる。

**`include_plotlyjs=True`**
plotly.jsのソースコートが丸々含まれる。3MB程度になる。


## Djangoで使うとき
Djangoは`base.html`などの基盤となるテンプレートに`css`や`javascript`の読み込みを記述し、他のテンプレートにおいて`{% extends 'app/base.html' %}`としてbase.htmｌを引き継ぎます。そのため、基盤となるテンプレートにplotly.jsを読み込む旨記述し、`include_plotlyjs=False`とするのがよいかなと思います。

```html:base.html
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
```

又、`output_type='div'`で取得したHTMLコードの文字列をテンプレートに渡せば、そのテンプレート内でグラフを描画できます。
ただし、テンプレートタグの`{% autoescape off %}`と`{% endautoescape %}`を使い、**エスケープ機能をoff**にしないと正しく描画されません。

```html:グラフを描画するhtml
{% extends 'blog/base.html' %}
{% block content %}

{% autoescape off %}
{{ plot_gantt }}　 <!-- HTMLコードの文字列が格納されたコンテキスト -->
{% endautoescape %}
```

### まとめ
1. `plotly.figure_factory.create_gantt()`を読み込む
2. `fig = ff.create_gantt（df）`でガントチャートのオブジェクトを作成する
3. `plotly.offline.offline.plot()`を読み込む
4. `gantt = plot(fig, output_type='div', include_plotlyjs=False)`でグラフとグラフを描画するスクリプトを含むhtmlコードの文字列を取得する
5. `autoescape`と`endautoescape`でエスケープ機能をoffにした上で、4.で取得した文字列をテンプレートにわたす



--- 
プロジェクトを立ち上げそれに向けてタスクを作成するというアプリケーションを作っており、そこで使ってみた。
すでに完了したタスクのスケジュールを取得し、ガントチャートを作成、テンプレートに配置してみる

```python:models.py
# Projectsと、その小モデルTasks
class Projects(models.Model):
    # 略

class Tasks(models.Model):
    STATUS_LIST = ((0, '待機'), (1, '取組中'), (2, '完了'))

    project = models.ForeignKey(Projects, on_delete=models.CASCADE, related_name='project_task')
    taskName = models.CharField(max_length=100, verbose_name='タスク')
    status = models.IntegerField(choices=STATUS_LIST, verbose_name='状況')
    createdDate = models.DateTimeField(default=now, null=True, blank=True, verbose_name='開始日')
    deadline = DateTimeField(blank=True,null=True, verbose_name='締め切り')
    finishedDate = DateTimeField(blank=True,null=True, verbose_name='達成した日')
```

```python:views.py
def projects_detail_view(request, p_pk):
    project = Projects.objects.get(pk=p_pk)
    # 完了済のタスクを取得し、開始日の昇順でタスク一覧を取得
    tasks = project.project_task.filter(status=2).order_by('createdDate')
    df = []
    for task in tasks:
        df.append(
            dict(Task=task.taskName,
                 # create_ganttのStartとFinishで使うため、フォーマットを整形
                 Start="{0:%Y-%m-%d %H:%M:%S}".format(task.createdDate),
                 Finish="{0:%Y-%m-%d %H:%M:%S}".format(task.finishedDate),))
    fig = ff.create_gantt(df, title='これまでの軌跡', bar_width=0.5, showgrid_x=True, showgrid_y=False,)
    plot_html = plot(fig, output_type='div', include_plotlyjs=False)

    context = {
        'plot_html': plot_html,
    }
    return render(request, 'detail.html', context)
```

```html:detail.html
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
{% autoescape off %}
{{ plot_html }}
{% endautoescape %}
```
**結果**
<img width="1408" alt="スクリーンショット 2020-01-17 1.00.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441606/c73cdd22-57c1-1d90-b8aa-c51864355ae6.png">

タスク名が長いため、グラフを圧迫している。