python bokehで作ったplotをhtmlに埋め込む(Embed to html)



##前回作ったbokehのplotをhtmlに埋め込む

参考元：
https://www.udemy.com/the-python-mega-course/learn/v4/overview

bokehのチュートリアル：Embedding Plots
https://bokeh.pydata.org/en/latest/docs/user_guide/embed.html

bokhe.resourcesについて
https://bokeh.pydata.org/en/latest/docs/reference/resources.html


使うライブラリー：

```py
from bokeh.embed import components
from bokeh.resources import CDN
```

##htmlに埋め込むための部品を取り出す
bokehで作ったplotをhtmlに埋め込むためには4つの部品が居る。

1. bokehのplot情報。bokheを`show()`して表示されるhtmlで検証すれば確認できる。HTMLタグの`<div class="bk-root">...</div>`ブロック。
2. Javascriptブロック。HTMLタグでは`<script>`ブロック
3. cssのリンクURL
4. javascriptのリンクURL

これら4つがあればhtmlにbokehのplot(グラフ)を埋め込むことができる。

plot元。前回の記事と全く同じ

```py:bokehで作ったキャンドルスティックチャート(plot)
from datetime import datetime as dt
from bokeh.plotting import figure, show, output_file
from pandas_datareader import data as pdr
import fix_yahoo_finance as yf

start = dt(2018,4,1)
end = dt(2018,7,31)

yf.pdr_override()
df=pdr.get_data_yahoo(tickers="FB", start=start, end=end)

def inc_dec(close, open_):
    if close>open_:
        value="Increase"
    elif close<open_:
        value="Decrease"
    else:
        value="Equal"
    return value

df["Status"]=[inc_dec(c,o) for c,o in zip(df.Close, df.Open)]
df["Middle"]=(df.Close+df.Open)/2
df["Height"]=abs(df.Open-df.Close)

p=figure(x_axis_type="datetime", width=1000, height=300, title="Candlestick Chart", sizing_mode="scale_width")
p.grid.grid_line_alpha=0.3

p.segment(df.index, df.High, df.index, df.Low, color="black")

millisecond_of_12hour=12*60*60*1000
p.rect(df.index[df.Status=="Increase"], df.Middle[df.Status=="Increase"], millisecond_of_12hour, df.Height[df.Status=="Increase"],
       fill_color="#CCFFFF", line_color="black")
p.rect(df.index[df.Status=="Decrease"], df.Middle[df.Status=="Decrease"], millisecond_of_12hour, df.Height[df.Status=="Decrease"],
       fill_color="#FF3333", line_color="black")

output_file("Chandlestick Chart.html")
show(p)
```



```py:上記のpの要素を取得。
from bokeh.embed import components
from bokeh.resources import CDN

script1, div1  = components(p)
script1
>>>
<script type="application/json" id=...
#すげぇ長いので以下略
</script>

div1
>>>
<div class="bk-root">
    <div class="bk-plotdiv" id="65ac11b3-23d4-4103-8fde-209bf594f2f1"></div>
</div> 


cdn_js=CDN.js_files
>>>
['https://cdn.pydata.org/bokeh/release/bokeh-0.12.13.min.js',
 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.13.min.js',
 'https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.13.min.js',
 'https://cdn.pydata.org/bokeh/release/bokeh-gl-0.12.13.min.js']

cdn_css=CDN.css_files
>>>
['https://cdn.pydata.org/bokeh/release/bokeh-0.12.13.min.css',
 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.13.min.css',
 'https://cdn.pydata.org/bokeh/release/bokeh-tables-0.12.13.min.css']

#今回はindex0のみ必要
cdn_js=CDN.js_files[0]
cdn_css=CDN.css_files[0]

```

1. `components`メソッドにplotオブジェクトを渡せば、`<script>`ブロックと、`<div>`ブロック(plot情報)を`str`型で取得可能
2. javascriptのリンクURLとcssのリンクURLは、htmlから直接コピペしてきてもいい。しかし、`CDN`で取得可能。`CDN.js_files`ならjavascriptのURLがリストで得られる。widgetはスライダーなどを使ったplotなら必要。tableは表を使った場合。glは3Dグラフィックを使った場合に必要。今回はindex0のurlだけでおｋ。
3. cssには`CDN.css_files`を使う
4. これら`script1`と`div1`と`cdn_js`と`cdn_css`をhtmlに渡す。


##実際に表示させる
以前に`flask`と`virtualenv`で作った仮想環境のhtmlに埋め込む。
htmlのコード全部をここに記述はしないが、重要なのだけ。

```html:plot.html
{%extends "layout.html"%}
{%block content%}
<link rel="stylesheet" href={{cdn_css | safe}} type="text/css" />

<script type="text/javascript" src={{cdn_js | safe}}></script>
<div class="plot">
    <p>test for plot embeding</p>
</div>
{{script1 | safe}}
{{div1 | safe}}
{%endblock%}
```
1. `{%extends "layout.html"%}`はflaskで別のhtmlコードを継承できる。`{%block content%}`と`{%endblock%}`のコードが挿入される。
2. `{{ .py上の変数名 }}`で.pyがファイルの変数を受け取ることができる。`cdn_css`、`cdn_js`、`script1`、`div1`の4つ変数を受け取っている。`flask`に付属してる`jinja2`ライブラリーの機能。
3. `{{|safe}}`の`|safe`は文字列などのエスケープを無効にする。

```py:app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/plot/')
def plot():
	#venv。仮想環境上にここでimportされてるmodule全部入れなきゃ動かないぜ
	import pandas as pd
	from datetime import datetime
	from bokeh.plotting import figure,show,output_file
	pd.core.common.is_list_like = pd.api.types.is_list_like
	from pandas_datareader import data as pdr
	import fix_yahoo_finance as yf

	#追加
	from bokeh.embed import components
	from bokeh.resources import CDN

	start=datetime(2018,1,1)
	end=datetime(2018,6,10)

	yf.pdr_override()
	#GOOG google
	df=pdr.get_data_yahoo(tickers="GOOG",start=start,end=end)

	def inc_dec(close, open_):
	    if close > open_:
	        value="Increase"
	    elif close < open_:
	        value="Decrease"
	    else:
	        value="Equal"
	    return value

	df["Status"]=[inc_dec(c, o) for c, o in zip(df.Close, df.Open)]
	df["Middle"]=(df.Open+df.Close)/2
	df["Height"]=abs(df.Open-df.Close)

	p=figure(x_axis_type="datetime", width=1000, height=300, title="Candlestick Chart", sizing_mode="scale_width")
	p.grid.grid_line_alpha=0.3

	p.segment(df.index, df.High, df.index, df.Low, color="black")

	millisecond_of_hour12=12*60*60*1000
	p.rect(df.index[df.Status=="Increase"],df.Middle[df.Status=="Increase"],millisecond_of_hour12,
	    df.Height[df.Status=="Increase"], fill_color="#CCFFFF", line_color="black")
	p.rect(df.index[df.Status=="Decrease"],df.Middle[df.Status=="Decrease"],millisecond_of_hour12,
	    df.Height[df.Status=="Decrease"], fill_color="#FF3333", line_color="black")

	#追加
	script1, div1  = components(p)
	cdn_js=CDN.js_files[0]
	cdn_css=CDN.css_files[0]

	return render_template("plot.html",
	script1=script1,
	div1=div1,
	cdn_js=cdn_js,
	cdn_css=cdn_css)

	#output_file("CandileStick.html")
	#show(p)

@app.route('/')
def home():
    return render_template("home.html")

@app.route('/about/')
def about():
    return render_template("about.html")

if __name__=="__main__":
    app.run(debug=True)

```
`flask`と`virtualenv`についてここではあまり触らない。
ただ`flask`はhtmlと.pyを繋げる。`virtualenv`は仮想環境を構築できる。
1. `pd.core.common.is_list_like = pd.api.types.is_list_like`は仮想環境上で`pandas`を動かそうとするとエラーはく対策。pandasのversionが0.2~0.6だとエラーになるっぽい。v0.7で解決されるらしい。　参考元：stackoverflow :https://stackoverflow.com/questions/50394873/import-pandas-datareader-gives-importerror-cannot-import-name-is-list-like
2. `render_template`でtemplatesフォルダにあるhtmlがレンダリング(描写)される。ついでに4つの変数もhtmlに渡している。
3. plot.htmlのURLにアクセスしたときに、bokehのplotが表示されるようになった


##あとがき
日本人はFlaskよりDjangoのほうが好き？