はじめに
さて、Python(Django + Anaconda)が、MySQLからデータを抜いて、テンプレートに渡して、htmlがテーブルとして表示する、というところまでできました!感動!
LAMP+Wordpress+Python(Django)のセットアップメモ2018
つぎは、データを絵にする工程が必要だ。ここで出てくるのが d3.js。
いっきにリファレンス見ながらやろうとするとなにが書いてあるかちっともわかんないよね。
ぐえぇぇ...!とかなってくるわ。は?なに書いてあるの?これ、みたいな。
Radar Chart d3 v4
なるほどね、「描く」処理を書かないといけないのね...。グラフでポン出しできないのね...。「おーい、なかじまー!C++でペイント.exe 作ろうぜー!」って言ってるようなもんだよなぁ...。でもほかのチャートライブラリってどこかダサいので d3 にたどり着いちゃうのよね...。あ~ん、どうしよう。jsファイルにギュッと隠蔽して見えないことにしてええんやろうか...。
いったん描画の基本をなんとなく押さえたうえで、
d3.js超初心者向け①→②を表現してみる
これをできるだけ簡略にしながら攻略していこう。
Radar Chart d3 v4
とりあえずコピペして置いちゃえ
わかんねーときはコピペだよなぁ~?
とりあえずtemplateフォルダに両方置いてみて動くのかな?ふつーに考えると、htmlがカレントディレクトリにおいたjsを読んでくれそうだよね。
$ tree
.
┝ app1
│ ┝ __init__.py
│ ┝ __pycache__
│ ┝ admin.py
│ ┝ apps.py
│ ┝ migrations
│ ┝ models.py
│ ┝ static
│ ┝ templates
│ │ ┝ index.html #New
│ │ ┝ radarChart.js #New
│ │ └ index.html.org #いままでのやつはいったん退避
│ ┝ tests
│ │ ┝ __init__.py
│ │ ┝ __pycache__
│ │ └ tests.py
│ ┝ urls.py
│ └ views.py
┝ manage.py
└ pj1
┝ __init__.py
┝ __pycache__
┝ settings.py
┝ urls.py
└ wsgi.py
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/ >
<title>Smoothed Radar Chart</title>
<!-- Google fonts -->
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<!-- D3.js -->
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<script src="radarChart.js" charset="utf-8"></script>
<style>
body {
font-family: 'Open Sans', sans-serif;
font-size: 11px;
font-weight: 300;
fill: #242424;
text-align: center;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
cursor: default;
}
.legend {
font-family: 'Raleway', sans-serif;
fill: #333333;
}
</style>
</head>
<body>
<div class="radarChart" style="display: inline-flex;"></div>
<div class="radarChart2" style="display: inline-flex;"></div>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var margin = { top: 50, right: 80, bottom: 50, left: 80 },
width = Math.min(700, window.innerWidth / 4) - margin.left - margin.right,
height = Math.min(width, window.innerHeight - margin.top - margin.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
{ name: 'Allocated budget',
axes: [
{axis: 'Sales', value: 42},
{axis: 'Marketing', value: 20},
{axis: 'Development', value: 60},
{axis: 'Customer Support', value: 26},
{axis: 'Information Technology', value: 35},
{axis: 'Administration', value: 20}
]
},
{ name: 'Actual Spending',
axes: [
{axis: 'Sales', value: 50},
{axis: 'Marketing', value: 45},
{axis: 'Development', value: 20},
{axis: 'Customer Support', value: 20},
{axis: 'Information Technology', value: 25},
{axis: 'Administration', value: 23}
]
}
];
//////////////////////////////////////////////////////////////
////// First example /////////////////////////////////////////
///// (not so much options) //////////////////////////////////
//////////////////////////////////////////////////////////////
var radarChartOptions = {
w: 290,
h: 350,
margin: margin,
levels: 5,
roundStrokes: true,
color: d3.scaleOrdinal().range(["#26AF32", "#762712"]),
format: '.0f'
};
// Draw the chart, get a reference the created svg element :
let svg_radar1 = RadarChart(".radarChart", data, radarChartOptions);
//////////////////////////////////////////////////////////////
///// Second example /////////////////////////////////////////
///// Chart legend, custom color, custom unit, etc. //////////
//////////////////////////////////////////////////////////////
var radarChartOptions2 = {
w: 290,
h: 350,
margin: margin,
maxValue: 60,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: { title: 'Organization XYZ', translateX: 100, translateY: 40 },
unit: '$'
};
// Draw the chart, get a reference the created svg element :
let svg_radar2 = RadarChart(".radarChart2", data, radarChartOptions2);
</script>
</body>
</html>
動かない
んー、いろいろ考えたけどsourceはあってるとしよう(そこを疑うとなにもできん)ってことは、jsへのパスが悪いんだよな?ってことで試行錯誤したら下記2か所の修正の必要があった。
1.(Djangoのパス指定文化で)jsへのパスを指定する。
2.Djangoのstaticキーワードを使えるようにする
3.js ファイルの static/js フォルダへの移動
解決方法はここに書いてあった。ここわかりやすいなぁ
Python Django チュートリアル(6) ~htmlデザイン~
<script src="{% static 'js/radarChart.js' %}" charset="utf-8"></script>
{% load staticfiles %}
$ tree
.
┝ app1
│ ┝ __init__.py
│ ┝ __pycache__
│ ┝ admin.py
│ ┝ apps.py
│ ┝ migrations
│ ┝ models.py
│ ┝ static
│ │ └ js
│ │ └ radarChart.js #move
│ ┝ templates
│ │ ┝ index.html
│ │ └ index.html.org
│ ┝ tests
│ │ ┝ __init__.py
│ │ └ tests.py
│ ┝ urls.py
│ └ views.py
┝ manage.py
└ pj1
┝ __init__.py
┝ __pycache__
┝ settings.py
┝ urls.py
└ wsgi.py
とりあえず動いた!
勘所としては、RadarChartが呼ばれると「.radarChart」クラスをグラフが置き換える。という流れのようだ。
「.radarChart」クラスはここにある(radarChart2も同じように置き換えられる)。
よろしい。「radarChart.js」キミはブラックボックスだ
HTMLファイルの合成
グラフサンプルhtmlが動いたのを確認したところで、今度は最初に作った「index.htm.org」と、グラフサンプルhtmlを合成しよう。
headの理解
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
charset
これは文字コードだよな
<meta charset="utf-8">
http-equiv
edge?俺はchromeだ!とかいう宗教戦争は置いといて、、。AをBに置き換えていいかが知りたい。なんとなく一緒だもんね。
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/ >
meta要素のhttp-equiv属性で動作を指示しよう
要素は「http-equiv属性」が指定されると プラグマ指示子(pragma directive)となります。プラグマ指示子とは、ブラウザに対して HTML文書の「状態・動作」を指示するもの。
例えば「http-equiv="refresh"」で時限リダイレクトとして動作します。
OK. そういう仕込みの場所ね。
X-UA-Compatible(IE のレンダリング方式を 標準モードに)
IEの互換表示設定の指示です。「content属性」の値を「IE=Edge」とすることで、IE がそのバージョンの標準モードでレンダリングするよう促します。CSSを読み込んでいる link要素、Javascriptを読み込んでいる script要素より前に書く必要があります。
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
content-type(文字エンコーディング)
HTML5での文字エンコーディングの指定は 要素の「charset属性」を使います。この「http-equiv属性」を使う方法は、その代替でしかありません。「charset属性」の文字エンコーディング指定と一緒に使ってはいけません(どっちか1つ)。
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
結論:AはBに置き換えられそう!
Viewport
もう逃げない。HTMLのviewportをちゃんと理解する
スマホでの映り方ってことか。
<meta name="viewport" content="width=device-width, initial-scale=1">
結論:横幅倍率をデバイス依存の倍率1にする、なのでそのまま放置
stylesheet
Bootstrap4移行ガイド
コードにはCDNからのクロスサイトスクリプティング(XSS)を防ぐため integrity 属性と crossorigin 属性を追加する。
要はセキュリティ上必要ってことか。ってことは、ここは見えないものとしたほうが理解が早そうだ。英語の勉強のときも余計な修飾語は抜いて考えるでしょう?以下のようにすると、CDNでスタイルシート呼んでるだけだから視認性もいいね。
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
$ mv pj1/app1/templates/index.html.org pj1/app1/templates/index.html
$ systemctl restart httpd
んん??文字がすっげぇ重なってるぞ??
そういえば「Hello, Bootstrap world!」も勝手に真ん中にいったし...
お前か~!Bodyに対してエフェクトかかってるね。これは消そう。「.legend」はグラフの「汎用」の文字のハズなのでいまは消さなくてもいいけどBodyのやつはまるごとイラネ。
データバインディング
SQL組み立て
$ vi pj1/app1/views.py
1.ナマSQLの場合
僕はこちらのほうが経験値があるので細かい説明は省いちゃうけど
・MySQLへのコネクションを確立して
・カーソルを取得して
・SQLを作って
・抽出(fetchall)して
・カーソルを手離して
・MySQLへのコネクションを手放す
from django.shortcuts import render
import mysql.connector
def index(request):
conn = mysql.connector.connect(user='python', password='python123', database='pythondb')
cur = conn.cursor()
cur.execute("SELECT DATE_FORMAT(ym, '%Y%m') AS YM, field_homogeneity AS homogeneity, plowing, biological, chemical, soil_hardness_distribution AS hardness FROM w_evaluation WHERE (SELECT MAX(ym) FROM w_evaluation);") #ここ
data = cur.fetchall()
colname = cur.column_names
cur.close
conn.close
return render(
request=request,
template_name='index.html',
context={'recordset': data, 'colname': colname})
2.ORMの場合(推奨)
Object-relational mapping(オーアールマッパー)を使う場合は
setting.pyに接続情報を書いて、models.pyにテーブル定義を書いているので、ここではSQLを指定するだけだが、これも メソッドチェーン でいける
from django.shortcuts import render
from .models import evaluation
def index(request):
#SELECT
qry = evaluation.objects.order_by('-ym')[:1].values('ym','homogeneity','plowing','biological','chemical','hardness')
return render(request, 'index.html', {'rs': qry, 'cs':qry[0].keys()})
テンプレートの編集
$ vi pj1/app1/templates/index.html
<!-- csとrsは views.py から来たんだよ -->
<thead>
<tr>
{% for c in cs %}
<td>{{ c }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
{% for r in rs %}
<td>{{ r.ym }}</td>
<td>{{ r.homogeneity }}</td>
<td>{{ r.plowing }}</td>
<td>{{ r.biological }}</td>
<td>{{ r.chemical }}</td>
<td>{{ r.hardness }}</td>
{% endfor %}
</tr>
</tbody>
rsとcsのデータの持ち方の違い
なんでrs(データ部)とcs(項目名部)でデータの持ち方違うのよ?一緒のデータ構造にしろよって思った人は鋭い!これは、index.htmlにおいてd3連携するときに、1つずつ値を取り出す必要があって、qry[0].values()で取り出すよりもデータの取り回しが良いのだ。改善の余地ありかも。
テンプレート言語で変数の代入はどうやって記述するのでしょうか?
ここ見ても、意図的にできないようになってるみたいだし。
jsの配列操作
キモはこれだよなぁ~。これってjsonじゃないの?連想配列?
JSONとJavaScriptオブジェクトの違い
javascriptの連想配列と配列の違い
あぁ、JSONと連想配列は同じなのか(同じというよりは親クラス子クラスの関係)。要は配列。連想配列は結構ゆるいsyntaxでいいけどJSONはsyntaxにシバりがある。ってことはこれは連想配列なのね
くっそハマったんだけど、ひょっとしてブラックボックスにした js に配列の読み方書いてあるんじゃね?ってエウレカがきて見てみたら書いてあった!!
maxValue = data[j].axes[i]['value'];
// Djangoからの代入
data[1].axes[0].value = parseFloat("{% for r in rs %}{{ r.homogeneity }}{% endfor %}");
data[1].axes[1].value = parseFloat("{% for r in rs %}{{ r.plowing }}{% endfor %}");
data[1].axes[2].value = parseFloat("{% for r in rs %}{{ r.biological }}{% endfor %}");
data[1].axes[3].value = parseFloat("{% for r in rs %}{{ r.chemical }}{% endfor %}");
data[1].axes[4].value = parseFloat("{% for r in rs %}{{ r.hardness }}{% endfor %}");
$ systemctl restart httpd
これでデータをビジュアル化できた。
ソース(フル)
<!DOCTYPE html>
{% load staticfiles %}
<html lang="ja">
<head>
<!-- Stylesheet and meta necessary for Bootstrap -->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<!-- Google fonts -->
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<!-- D3.js -->
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script src="https://d3js.org/d3-path.v1.min.js" charset="utf-8"></script>
<script src="{% static 'js/radarChart.js' %}" charset="utf-8"></script>
<style>
.legend {
font-family: 'Raleway', sans-serif;
fill: #333333;
}
</style>
<title>Anaconda!</title>
</head>
<body>
<h1>Hello, Bootstrap world!</h1>
<table class="table table-dark table-hover">
<thead>
<tr>
{% for c in cs %}
<td>{{ c }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
{% for r in rs %}
<td>{{ r.ym }}</td>
<td>{{ r.homogeneity }}</td>
<td>{{ r.plowing }}</td>
<td>{{ r.biological }}</td>
<td>{{ r.chemical }}</td>
<td>{{ r.hardness }}</td>
{% endfor %}
</tr>
</tbody>
</table>
<div class="radarChart" style="display: inline-flex;"></div>
<script>
//////////////////////////////////////////////////////////////
//////////////////////// Set-Up //////////////////////////////
//////////////////////////////////////////////////////////////
var margin = { top: 50, right: 80, bottom: 50, left: 80 },
width = Math.min(700, window.innerWidth / 4) - margin.left - margin.righ
t,
height = Math.min(width, window.innerHeight - margin.top - margin.bottom);
//////////////////////////////////////////////////////////////
////////////////////////// Data //////////////////////////////
//////////////////////////////////////////////////////////////
var data = [
{ name: 'Mean',
axes: [
{axis: 'homogeneity', value: 40},
{axis: 'plowing', value: 40},
{axis: 'biological', value: 40},
{axis: 'chemical', value: 40},
{axis: 'hardness', value: 40}
]
},
{ name: 'You',
axes: [
{axis: 'homogeneity', value: 0},
{axis: 'chemical', value: 0},
{axis: 'hardness', value: 0}
]
}
];
// Djangoからの代入
data[1].axes[0].value = parseFloat("{% for r in rs %}{{ r.homogeneity }}{% endfor %}");
data[1].axes[1].value = parseFloat("{% for r in rs %}{{ r.plowing }}{% endfor %}");
data[1].axes[2].value = parseFloat("{% for r in rs %}{{ r.biological }}{% endfor %}");
data[1].axes[3].value = parseFloat("{% for r in rs %}{{ r.chemical }}{% endfor %}");
data[1].axes[4].value = parseFloat("{% for r in rs %}{{ r.hardness }}{% endfor %}");
var radarChartOptions = {
w: 290,
h: 350,
margin: margin,
maxValue: 60,
levels: 6,
roundStrokes: false,
color: d3.scaleOrdinal().range(["#AFC52F", "#ff6600"]),
format: '.0f',
legend: { title: 'TOTAL EVALUATION', translateX: 100, translateY: 40 }
};
RadarChart(".radarChart", data, radarChartOptions);
</script>
<!-- jQuery necessary for Bootstrap -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
</body>
</html>