frappe-ganttでグラフを更新する方法
frappe-ganttのドキュメントからチャートの更新apiを探してみたのですが、見つけられませんでした。
issueを見てみたところ、解決策が見つかったので紹介します。
https://github.com/frappe/gantt/issues/44
jsfiddleにコードを公開してくださっているのでリンクを載せます。
https://jsfiddle.net/stvkas/jgtygxro/14/
コーディング
refresh関数を使用すると良いみたいです。
ボタンを押すと、ランダムなタスクが追加されます。
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Simple Gantt</title> <link rel="stylesheet" href="../dist/frappe-gantt.css" /> <link rel="stylesheet" href="./index.css" /> </head> <body> <div class="container"> <!-- <h2>Interactive Gantt Chart entirely made in SVG!</h2> --> <svg id="gantt"></svg> </div> <button id="add-task">Add Tasks</button> <script src="./moment.js"></script> <script src="./snap.svg-min.js"></script> <script src="./fg.min.js"></script> <script src="./index.js"></script> </body> </html>
javascript
function dinamic() { var names = [ ["Redesign website", [0, 7]], ["Write new content", [1, 4]], ["Apply new styles", [3, 6]], ["Review", [7, 7]], ["Deploy", [8, 9]], ["Go Live!", [10, 10]] ]; var tasks = names.map(function(name, i) { var today = new Date(); var start = new Date(today.getFullYear(), today.getMonth(), today.getDate()); var end = new Date(today.getFullYear(), today.getMonth(), today.getDate()); start.setDate(today.getDate() + name[1][0]); end.setDate(today.getDate() + name[1][1]); return { start: start, end: end, name: name[0], id: "Task " + i, progress: parseInt(Math.random() * 100, 10) } }); tasks[1].progress = 0; tasks[1].dependencies = "Task 0"; tasks[2].dependencies = "Task 1"; tasks[3].dependencies = "Task 2"; tasks[5].dependencies = "Task 4"; tasks[5].custom_class = "bar-milestone"; var gantt_chart = Gantt('#gantt', tasks); document.getElementById('add-task').addEventListener('click', function() { var task = RandomTask('Task 5'); tasks.push(task); gantt_chart.refresh(tasks); }); function RandomTask(deps) { var start = new Date(); start.setDate(start.getDate() + 11 + randomInt(2)); var end = new Date(); end.setDate(start.getDate() + randomInt(7)); return { start: start, end: end, name: 'Party', id: 'Task ' + tasks.length, progress: randomInt(100), dependencies: deps }; } function randomInt(limit) { return Math.floor(Math.random() * limit); } }
frappe-ganttを使ってガントチャートを作成する方法
ガントチャートを作成するライブラリは多くありますが、今回紹介するのはfrappe-ganttというライブラリです。
↓のようなチャートを作成できます。
残念ながら、マウス操作でタスクを追加することはできません。自分で実装することはできそうなので挑戦しても良いかもしれません。
使用方法
まずはソースコードをダウンロードします。
https://github.com/frappe/gantt/archive/master.zip
使用するファイルは以下のファイルです。(css以外使いません。)
- dist/frappe-gantt.css
次に、デモページを開きます。
そこから、frappe-gantt.min.jsをコピーしてきます。
2018/10/04からファイルに変更がなければ↓から取得できるはずです。
(https://frappe.io/gantt/js/frappe-gantt.min.js)
コピーする理由はv0.3.0に不具合があるためです。眼の前の動いているものを使ったほうが安全です。
frappe-ganttはmoment.js
とsnap-svg
に依存性を持っているので、両方ダウンロードします。
コーディング
↓のディレクトリ構造を作成します。
sample ├── frappe-gantt.min.js ├── index.css ⇠ 自分で作成する ├── index.html ⇠ 自分で作成する ├── index.js ⇠ 自分で作成する ├── moment.js └── snap.svg-min.js
あとはコードを書くだけです。
index.html
#gantt
がチャート追加の対象です。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Simple Gantt</title> <link rel="stylesheet" href="../dist/frappe-gantt.css" /> <link rel="stylesheet" href="./index.css" /> </head> <body> <div class="container"> <svg id="gantt"></svg> </div> <script src="./moment.js"></script> <script src="./snap.svg-min.js"></script> <script src="./fg.min.js"></script> <script src="./index.js"></script> </body> </html>
index.js
サンプルのタスクを画面に追加して、クリックイベントをつけています。
(function() { var tasks = [ { start: '2018-10-01', end: '2018-10-08', name: 'Redesign website', id: "Task 0", progress: 20 }, { start: '2018-10-03', end: '2018-10-06', name: 'Write new content', id: "Task 1", progress: 5, dependencies: 'Task 0' }, { start: '2018-10-04', end: '2018-10-08', name: 'Apply new styles', id: "Task 2", progress: 10, dependencies: 'Task 1' }, ] function init() { var gantt_chart = new Gantt("#gantt", tasks, { on_click: function (task) { console.log(task); }, on_date_change: function(task, start, end) { console.log(task, start, end); }, on_progress_change: function(task, progress) { console.log(task, progress); }, on_view_change: function(mode) { console.log(mode); }, view_mode: 'Month', language: 'en' }); console.log(gantt_chart); } document.addEventListener("DOMContentLoaded", function() { console.log("start application."); init(); }, false); })()
index.css
デモと同じような見た目で表示されるようにします。
body { font-family: sans-serif; background: #eee; } .container { width: 80%; margin: 0 auto; overflow: auto; border: 1px solid #d8d8d8; background-color: #fff; } /* custom frappe-gant class */ .gantt .bar-progress { fill: tomato !important; /* ↑ 進行度のバーがトマト色になります */ }
一番下のタスクと横スクロールの距離が離れすぎている問題
最新版(v0.3.0)を使ってみたところ、一番下のタスクと横スクロールの距離が離れすぎている問題がありました。
これはissueにも載っていたのでもしかしたら対応されるかもしれません。
https://github.com/frappe/gantt/issues/91
解決方法としては、この問題が起きないバージョンを使用することです。
私の場合は、デモページでサンプルを検証ツールで確認して、使用されているライブラリをそのままコピーしました。
(問題が起きないバージョンをcheckoutでたどるより早いと判断しました。)
postgresqlをmacで使う
環境設定
brew install postgresql
起動
postgres -D /usr/local/var/postgres
2018-09-22 05:36:28.073 JST [41369] LOG: listening on IPv6 address "::1", port 5432 2018-09-22 05:36:28.073 JST [41369] LOG: listening on IPv4 address "127.0.0.1", port 5432 2018-09-22 05:36:28.075 JST [41369] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432" 2018-09-22 05:36:28.099 JST [41370] LOG: database system was shut down at 2018-09-22 05:34:20 JST 2018-09-22 05:36:28.108 JST [41369] LOG: database system is ready to accept connections
ポートを指定したい場合
postgres -D /usr/local/var/postgres -p 54321
別のコンソールを開いて、DBにつなげる
psql -d postgres
psql (10.5) Type "help" for help. postgres=# postgres=# help You are using psql, the command-line interface to PostgreSQL. Type: \copyright for distribution terms \h for help with SQL commands \? for help with psql commands \g or terminate with semicolon to execute query \q to quit postgres=#
いろんな設定値を確認したい場合
postgres=# show all
表示を縦にしたいとき(もとに戻したいとき)
\x
テーブル一覧を確認したい場合。(事前に\connect
が必要)
<table_name>を入れなければすべて、入れると、そのテーブルの構造を表示する。
\d <table_name>
設定
create database XXX;
DBに接続するためのコマンド
Connection \c[onnect] {[DBNAME|- USER|- HOST|- PORT|-] | conninfo} connect to new database (currently "postgres") \conninfo display information about current connection \encoding [ENCODING] show or set client encoding \password [USERNAME] securely change the password for a user
\connect XXX
postgres=# \connect XXX; You are now connected to database "XXX" as user "ユーザ名".
ユーザ名は現在のユーザ名がデフォルトになっている。 ユーザ名を指定する場合は、connectコマンドのUSERに値を設定する。
ユーザ追加をする場合
CREATE USER UUU WITH PASSWORD 'pass';
frappe chartを使ってグラフを更新する方法
frappe chartを使ってグラフを更新する方法と、ドキュメントに載っているサンプルを書いています。
最終的に↓ができます。
html、javascriptをコピペするといくつかのサンプルも確認できます。
導入
まずはインストールします。
npm install frappe-charts
もしくは↓をhtmlにいれます。
動作を確認したい場合はわざわざビルドまでやるのは面倒なので、cdnを利用することが多いです。
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
サンプル
chart_01 ~ 5まではドキュメントに載っているサンプルを試しています。
コピペして試してみてください。
index.html
htmlは<html>
や<head>
など一部省略しています。
<body> <div id="chart_01"></div> <div id="chart_02"></div> <div id="chart_03"></div> <div id="chart_04"></div> <div id="chart_05"></div> <!-- ↓ 1秒ごとにグラフを更新する --> <div id="chart_06"></div> </body>
index.js
(function() { function frappe_01() { /** * quick start */ const data = { labels: ["12am-3am", "3am-6pm", "6am-9am", "9am-12am", "12pm-3pm", "3pm-6pm", "6pm-9pm", "9am-12am" ], datasets: [ { name: "Some Data", type: "bar", values: [25, 40, 30, 35, 8, 52, 17, -4] }, { name: "Another Set", type: "line", values: [25, 50, -10, 15, 18, 32, 27, 14] } ] } const chart = new frappe.Chart("#chart_01", { // or a DOM element, // new Chart() in case of ES6 module with above usage title: "My Awesome Chart", data: data, type: 'axis-mixed', // or 'bar', 'line', 'scatter', 'pie', 'percentage' height: 250, colors: ['#7cd6fd', '#743ee2'] }) } function frappe_02() { /** * Axis Charts: Axis chart: What Is It */ data = { labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], datasets: [ { values: [18, 40, 30, 35, 8, 52, 17, -4] } ] } new frappe.Chart( "#chart_02", { data: data, type: 'line', //bar or line height: 250, colors: ['red'] }); } function frappe_03() { /** * Axis Charts: Adding more datasets */ var data = { labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], datasets: [ { name: "Dataset 1", values: [18, 40, 30, 35, 8, 52, 17, -4] }, { name: "Dataset 2", values: [30, 50, -10, 15, 18, 32, 27, 14] } ] } new frappe.Chart( "#chart_03", { data: data, type: 'line', //bar or line height: 250, colors: ['red'] }); } function frappe_04() { /** * Axis Charts: Responsiveness */ var data = { labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], datasets: [ { name: "Dataset 1", values: [18, 40, 30, 35, 8, 52, 17, -4] }, { name: "Dataset 2", values: [30, 50, -10, 15, 18, 32, 27, 14] } ] } new frappe.Chart( "#chart_04", { data: data, type: 'bar', //bar height: 250, colors: ['red'], barOptions: { spaceRatio: 0.8 // default: 1 }, }); } function frappe_05() { /** * Axis Charts: More Tweaks */ var data = { labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], datasets: [ { name: "Dataset 1", values: [18, 40, 30, 35, 8, 52, 17, -4] }, ] } new frappe.Chart( "#chart_05", { data: data, type: 'bar', //bar height: 250, colors: ['red'], axisOptions: { xAxisMode: 'tick' // default: 'span' // ↑ 縦軸がなくなる }, }); } function frappe_06() { /** * original: data append */ var data = { labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], datasets: [ { name: "Dataset 1", values: [18, 40, 30, 35, 8, 52, 17, -4] }, ] } var chart = new frappe.Chart( "#chart_06", { data: data, type: 'bar', //bar height: 250, colors: ['red'], barOptions: { spaceRatio: 0.8 // default: 1 }, }); var data_push = function(){ var values_size = data.datasets[0].values.length; for (var vi = 0; vi < values_size; vi++) { data.datasets[0].values.splice( vi, 1, Math.random() * (100 - 0) + 0 ) } chart.update(data); var id = setTimeout(data_push, 1000); } data_push(); } function init() { console.log('called init function.'); frappe_01(); frappe_02(); frappe_03(); frappe_04(); frappe_05(); frappe_06(); } document.addEventListener('DOMContentLoaded', function() { init(); }, false); })()
可変グリッドレイアウトをMuuriで実装する
画面サイズを変えると、中のグリッドがグリグリと動く画面を作ってみます。
npmでインストールします。
npm install muuri
注意事項
ドラッグして要素を動かす場合は以下のライブラリをインストールする必要があります。
npm install hammerjs
npmを使わずに実装する場合は↓を全て入れると良いです。
(ドラッグを使わない場合はhammerjsを抜いて良いです。
<script src="https://unpkg.com/web-animations-js@2.3.1/web-animations.min.js"></script> <script src="https://unpkg.com/hammerjs@2.0.8/hammer.min.js"></script> <script src="https://unpkg.com/muuri@0.7.1/dist/muuri.min.js"></script>
実装例
この実装例では、scssやnpmを使っているため別途ビルドが必要です。
cssや、import箇所を修正することでビルドが必要なくなります。
index.html
<div class="main"> <div class="grid"> <div class="item"> <div class="item-content"> <div class="ss">hoge1</div> </div> </div> <div class="item"> <div class="item-content"> <div class="ss">hoge2</div> </div> </div> <div class="item"> <div class="item-content"> <div class="ss">hoge3</div> </div> </div> <div class="item"> <div class="item-content"> <div class="ss">hoge4</div> </div> </div> </div> </div>
script.js
jqueryを使っていた場合$(document).ready
ではうまく動作しないことがありました。
ページ更新時に画像要素が重なって表示されました。
おそらくreadyがDom構築が完了した段階で処理が実行されるためだと思います。
画像のサイズまで考慮されずに重なってしまったのかと。
loadにすると、画像の読み込みなどが完了してから実行されるので改善されました。
import $ from 'jquery'; import Muuri from 'muuri'; (function() { function init() { var grid = new Muuri('.grid'); } $(window).on('load', function() { console.log("called ready function"); init(); }) })();
style.scss
.main { padding-top: 56px; height: 100%; box-sizing: border-box; } .grid { position: relative; .item { position: absolute; z-index: 1; background: #fff; width: 100px; height: 100px; margin: 3px; border: 1px solid #d8d8d8; box-sizing: border-box; } .item.muuri-item-dragging { z-index: 3; } .item.muuri-item-releasing { z-index: 2; } .item.muuri-item-hidden { z-index: 0; } .item-content { position: relative; width: 100%; } }
エラー対応
Uncaught TypeError: url.indexOf is not a function
jqueryでloadを使おうとした際に置きました。
$(window).load(function() { ... });
ではなく、↓が正しいです。
$(window).on('load', function() { ... });
DjangoでGoogleアカウントの認証機能を入れる方法
Djangoのログイン機能でGoogleのアカウントを使った方法を入れます。
必要なライブラリをインストールします。
pip install django python3-openid social-auth-app-django
(ついでにherokuで必要なライブラリも入れたい場合は↓をどうぞ)
pip install django djangorestframework psycopg2 psycopg2-binary whitenoise dj-database-url gunicorn python3-openid social-auth-app-django
urls.py
from django.contrib import admin from django.contrib.auth.views import LogoutView, LoginView from django.urls import path, include from django.views.generic import TemplateView urlpatterns = [ path('admin/', admin.site.urls), # ↓ ログイン用の画面 path('login/', LoginView.as_view(template_name='top.html'), name='login'), # ↓ ログアウトしたあとの画面 path('logout/', LogoutView.as_view(), name='logout'), # トップ画面 path('', TemplateView.as_view(template_name='top.html'), name='home'), # ↓ socialログインに必要なpath path('auth/', include('social_django.urls', namespace='social')), ]
次にtemplatesディレクトリに以下のようにファイルを作成します。
Project/templates ├── registration │ └── logged_out.html └── top.html
registration以下にlogged_out.htmlを作成している理由について。
Logoutviewのtemplate_nameにそのように指定されているからです。(デフォルト)
もちろん、.as_view(template_name='logout.html')
のように書き換えても良いです。
class LogoutView(SuccessURLAllowedHostsMixin, TemplateView): """ Log out the user and display the 'You are logged out' message. """ next_page = None redirect_field_name = REDIRECT_FIELD_NAME template_name = 'registration/logged_out.html' extra_context = None
settings.py
以下の設定を追加します。
LOGIN_URL = 'login' # urls.pyで設定したnameに一致するように LOGIN_REDIRECT_URL = 'home' # urls.pyで設定したnameに一致するように # ↓ はgoogleから取得すること。 SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'xxxxxxxxxxxx.apps.googleusercontent.com' #Paste CLient Key SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'xxxxxxxxxxxx' #Paste Secret Key TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # ← を追加 # ry... }, ] INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'social_django', # ← を追加 ] AUTHENTICATION_BACKENDS = ( # 'social_core.backends.open_id.OpenIdAuth', # for Google authentication # 'social_core.backends.google.GoogleOpenId', # for Google authentication 'social_core.backends.google.GoogleOAuth2', # for Google authentication # 'social_core.backends.github.GithubOAuth2', # for Github authentication # 'social_core.backends.facebook.FacebookOAuth2', # for Facebook authentication 'django.contrib.auth.backends.ModelBackend', )
html
top.html
top画面では、ログインするためのボタンと、ログアウトするためのボタンを用意します。
それぞれのボタンは、ユーザがログインしているかどうかで表示されるかが決まります。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Sample</title> <meta name="description" content="google login project"> {% load static %} </head> <body class=""> {% if user.is_authenticated %} <a href="{% url 'logout' %}"> <span>ログアウト</span> </a> {% else %} <a href="{% url 'social:begin' 'google-oauth2' %}"> <span>ログイン</span> </a> {% endif %} </body> </html>
logged_out.html
めんどくさいので簡単に作りました。
ログアウト実行後の画面です。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Logout</title> </head> <body> <p class="display-4">You logged out.</p> <a class="lead" href="{% url 'home' %}">Home</a> </body> </html>
備考
わざわざlogged_out.htmlを作らなくても、以下の方法でも良いです。
settingsファイルにLOGOUT_REDIRECT_URL
を設定しても良いです。
LOGOUT_REDIRECT_URL = 'home
あるいは、urls.pyでnext_pageを指定しても良いです。
path('logout/', LogoutView.as_view(next_page='home'), name='logout'),
pythonでBeautifulSoupを使ったスクレイピング
pythonでBeautifulSoupを使ったスクレイピング
python3.6.4を使用しています。
必要なライブラリをインストールします。
pip install beautifulsoup4 requests lxml
beautifulsoup4
はスクレイピングのライブラリ。
requests
はgetとかpostのリクエストに使うライブラリ。
lxml
はhtmlをパースするためのライブラリ。
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation
実装例
検索するとヒットするのはurllibを使ってhtmlを取得するやり方だが、requestsも使えます。
大抵は別のAPIを実行したりするため、requestsで統一したほうが良いと考えています。
selectのやり方はドキュメントを読んで試すこと。listで帰ってきます。
jsで言うところのdocument.querySelectorAll
と同じです。一件取得したい場合はselect_one
を使います。
from bs4 import BeautifulSoup import requests def execute(): html_text = requests.get(amedas) # statusが200なら...などの条件を入れるのが一般的 soup = BeautifulSoup(html_text.text, "lxml") tbl_prefecture_tag = soup.select(selector="#tbl")[0] table_cols = tbl_prefecture_tag.select(selector="th['headers'='COL1']") # ... ry if __name__ == "__main__": execute()