FlaskとjQueryとsqliteについて少し勉強したのでメモ。
シンプルな例
まず、シンプルに文字を書き換えるだけの場合について。
バックエンド
flaskでサーバー側の処理を書きます。
ページを表示するための index()
と、フロントエンドからのAjax通信に対して受け答えするための show()
の2つのメソッドを用意します。
from flask import Flask, render_template, jsonify, request
import json
app = Flask(__name__)
@app.route('/')
def index():
return render_template("index.html")
@app.route('/show', methods=['POST'])
def show():
return_json = {
"message": f"Hello, {request.form['username']}"
}
return jsonify(values=json.dumps(return_json))
if __name__ == '__main__':
app.run(debug=True)
フロントエンド
入力フォームを用意しておいて、入力値に変更があるたびにajaxでサーバーにPOSTリクエストを送るようにしておきます。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
</head>
<body>
<h1 id="midashi">Hello</h1>
<form name="example_form" onsubmit="return false;">
<input type="text" name="username" placeholder="Write your name" onchange="changeHandler(this)">
</form>
<script>
function changeHandler(input_value) {
const username = $(input_value).serialize();
$.ajax("/show", {
type: "post",
data: username,
dataType: "json",
}).done(function(data) {
console.log("Ajax通信 成功");
const message = JSON.parse(data.values).message
$("#midashi").html(message);
}).fail(function(data) {
console.log("Ajax通信 失敗");
});
}
</script>
</body>
</html>
グラフを書き換える
次に、記事冒頭のやつを作る場合について。
バックエンド
DBに入っているデータを取り出してグラフを描く感じの想定で、sqlite3を使ってみます
グラフはC3.jsを使い、python側でやる処理は描画のためのデータをフロントに渡すだけです
import sqlite3
from flask import Flask, render_template, jsonify, request, g
import json
app = Flask(__name__)
DATABASE = "./example.db"
@app.route("/")
def index():
cur = get_db().cursor()
populations = get_data_as_wide(cur, col="population")
default_value = {
"bindto": "#chart",
"data": {
"rows": populations,
"x": "year"
},
"axis": {
"y": {}
}
}
return render_template("index.html", default_value=default_value)
@app.route("/show", methods=["POST"])
def show():
variable = request.form['variable']
cur = get_db().cursor()
populations = get_data_as_wide(cur, col=variable)
new_value = {
"bindto": "#chart",
"data": {
"rows": populations,
"x": "year"
},
"axis": {
"y": {}
}
}
return jsonify(values=json.dumps(new_value))
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
return db
def get_data_as_wide(cur, col):
"""データを横持ちで取得する"""
query = f"""
SELECT
year,
max(CASE WHEN prefecture = '東京都' THEN {col} END) AS "東京都",
max(CASE WHEN prefecture = '神奈川県' THEN {col} END) AS "神奈川県",
max(CASE WHEN prefecture = '埼玉県' THEN {col} END) AS "埼玉県"
FROM populations
GROUP BY year
"""
rows = cur.execute(query).fetchall()
header = ["year", "東京都", "神奈川県", "埼玉県"]
populations = [header] + [list(row_tuple) for row_tuple in rows]
return populations
def setup_database():
"""データベースを作っておく"""
con = sqlite3.connect(DATABASE)
cur = con.cursor()
cur.execute("DROP TABLE IF EXISTS populations")
cur.execute("""
CREATE TABLE populations
(prefecture text, year integer, population integer, population_male integer, population_female integer)
""")
populations = [
('埼玉県', 1995, 6759311, 3419218, 3340093),
('東京都', 1995, 11773605, 5892704, 5880901),
('神奈川県', 1995, 8245900, 4209525, 4036375),
('埼玉県', 2000, 6938006, 3500224, 3437782),
('東京都', 2000, 12064101, 6028562, 6035539),
('神奈川県', 2000, 8489974, 4308786, 4181188),
('埼玉県', 2005, 7054243, 3554843, 3499400),
('東京都', 2005, 12576601, 6264895, 6311706),
('神奈川県', 2005, 8791597, 4444555, 4347042),
('埼玉県', 2010, 7194556, 3608711, 3585845),
('東京都', 2010, 13159388, 6512110, 6647278),
('神奈川県', 2010, 9048331, 4544545, 4503786),
('埼玉県', 2015, 7266534, 3628418, 3638116),
('東京都', 2015, 13515271, 6666690, 6848581),
('神奈川県', 2015, 9126214, 4558978, 4567236)
]
cur.executemany('INSERT INTO populations VALUES (?,?,?,?,?)', populations)
con.commit()
con.close()
if __name__ == "__main__":
setup_database()
app.run(debug=True)
sqliteにもWebアプリ制作にも慣れていないので非常に非効率でコードが長くなることをやってしまっているような気がします…
フロントエンド
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js" integrity="sha512-FHsFVKQ/T1KWJDGSbrUhTJyS1ph3eRrxI228ND0EGaEp6v4a/vGwPWd3Dtd/+9cI7ccofZvl/wulICEurHN1pg==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.min.js" integrity="sha512-+IpCthlNahOuERYUSnKFjzjdKXIbJ/7Dd6xvUp+7bEw0Jp2dg6tluyxLs+zq9BMzZgrLv8886T4cBSqnKiVgUw==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.css" integrity="sha512-GQSxWe9Cj4o4EduO7zO9HjULmD4olIjiQqZ7VJuwBxZlkWaUFGCxRkn39jYnD2xZBtEilm0m4WBG7YEmQuMs5Q==" crossorigin="anonymous" />
</head>
<body>
<form onsubmit="return false;">
<select name="variable" onchange="changeHandler(this)">
<option value="population">人口(男女計)</option>
<option value="population_male">人口(男性)</option>
<option value="population_female">人口(女性)</option>
</select>
</form>
<div id="chart"></div>
<script>
let data = {{ default_value | safe }};
data = add_tooltip(data);
let chart= c3.generate(data);
function add_tooltip(data_for_c3) {
data_for_c3['tooltip'] = {format: {value: function(value){return d3.format(",.0f")(value)}}};
return data_for_c3;
}
function changeHandler(input_value) {
const value = $(input_value).serialize();
$.ajax("/show", {
type: "post",
data: value,
dataType: "json",
}).done(function(response) {
console.log("Ajax通信 成功");
let new_value = JSON.parse(response.values);
new_value = add_tooltip(new_value);
chart= c3.generate(new_value);
}).fail(function(data) {
console.log("Ajax通信 失敗");
});
}
</script>
</body>
</html>
こういうものを作るのに慣れてくればダッシュボードを自前で作ったりできそうですね
細部をカスタマイズしたいとかの要件がなければ、簡単にダッシュボードを作れるツールやライブラリを使ったほうが早いでしょうけど…