SVGを描くJavaScriptライブラリ
この記事は、SVG Advent Calendar 2015 - Adventarの15日目の記事です。
チャートやグラフ用のJavaScriptライブラリはどれがいいんだろう?
2015年は「Ajaxで取ってきたデータを、SVGのチャートやグラフにして表示する」という作業をわりとたくさんやりました。
実装方法もいろいろやっていて、
- ピュアJavaScript+テンプレートエンジン
- d3.js
- Snap.svg
- Raphaël
などなど・・・。
小規模サイトだったり、公開範囲の限定されたサイトだったり、という事情に甘えて、色々なものに手を出した感じですね(笑)。
ピュアJavaScriptは、まさに13日のこちらの記事(気づいて築く: パイチャートのSVG仕立て 〜アニメーションを添えて〜)みたいなことを四苦八苦しながらやってました。
どれがベストかは、ケースバイケース、だとは思いますが、
「普段使ってるようなWebサービスはどうやって実装されてるんだろう?」と思って、いくつかソースコードを見てみた、というのがこの記事です。
Qiita
※ユーザーの「投稿タイプ」のパイチャートのところ
2016/1/26追記:
UIがリニューアルされましたね!
新しいチャートは、d3.jsのラッパーライブラリc3.jsを使用しているようです。
ちょうどIE8のサポートも切れたので、Raphaëlである必要がなくなった、ということかもしれません。
RaphaëlはIE8でも表示できるので重宝してました。
最近はIE8対応不要なケースが増えて、私はあまり使わなくなってしまいましたが、
Raphaëlの作者さんがSnap.svgを作っているので、今度はそちらでお世話になろうと思います。
d3.jsは一番よく見かけるし、サンプルも多くて便利だな、と思います。
反面、サンプルそのままで事足りてしまうケースも多くて、そうすると「いかにもd3.js」な見た目になるので、
ゼロベースで実装できるように勉強したい・・・というのを来年の目標にしようと思います。
また見つけたら追記します!
普段、いろいろなところでSVGのチャートを見ている気がしていましたが、いざ探そうとすると思い出せないものですね><
12/16追記:
SVG Advent Calendar、翌日16日は空きだった・・・と思いきや、
カレンダーの作成者でもあるrikuoさんがSVGでFizzBuzz(XSLT版) - 週刊SVGという記事をアップされていました。XSLTでプログラミングしていてすごい・・・!
もしかして簡単なチャートならJavaScript使わなくても書けちゃったりするんでしょうか。画像でもありテキストでもあるSVGって本当におもしろいですね。
writing-modeがFirefoxでも使えるようになっていた
CSSで縦書きテキスト
CSSのみで縦書きテキストを実現するには、writing-modeプロパティを使いますが、Firefoxだけが長いあいだ未対応でした。
Can I use... Support tables for HTML5, CSS3, etc
2015年11月現在、この仕様は勧告候補の段階なので、ブラウザによってベンダープレフィックスが必要だったり、指定する値の記述方法がばらばらだったりもします。
そして、未対応だったFirefoxは、バージョン41からは標準で対応するようになったとのこと(ベンダープレフィックスもなし!)。
Firefox 41 for developers - Mozilla | MDN
ほぼ全てのブラウザが対応したことに
IEは(値の記述方法は独自仕様ですが)6から使えますし、Chrome、Safariはベンダープレフィックス付きで対応しているので、Firefoxが対応したことでほぼ足並みが揃ったことになります。
現時点で各ブラウザに対応させるには、こんな感じになります。
writing-mode: tb-rl; /*IE*/ -o-writing-mode: vertical-rl; /*Opera*/ -webkit-writing-mode: vertical-rl; /*Chrome, Safari*/ writing-mode: vertical-rl; /*Firefox, Edge*/
※縦書きで、行が右から左へ進む場合の指定方法です
まだフォールバックが必要?
writing-modeに対応したFirefox41のリリースは2015年9月22日と(これを書いている時点からすると)かなり最近なので、基本的に自動アップデートがかかるとはいえ、まだ40以前のバージョンのユーザーもいるかも?という不安もあります。
さらに、FirefoxにはESRという法人向けの延長サポート版が存在していて、こちらはまだバージョン38です。これが2016年5月末までのサポート予定とのことなので、やはりそれまでは何かしらフォールバックを用意したほうが良さそうです。
フォールバックも書いてみた
縦書きのテキストが横書きになってしまった場合、コンテンツのレイアウトにかなり影響が出るので、それを回避するべく以下のような方法をとりました。
- writing-modeが使えない場合、横書きのテキストを90度回転させる
- 回転はCSSのtransformプロパティで行う(これはFirefox16から対応している)
- ブラウザがFirefox40以前かどうか、という点はJavaScriptで判別する
日本語の場合はテキストが横倒しになるので、writing-modeが適用された状態と全く同じにはなりませんが、フォールバックということで、そこまでの対応としました。
Chrome(writing-modeに対応)
Firefox38(writing-modeに非対応)
HTML
<div class="box"> <p class="text">日本語のテキスト<br />改行</p> </div>
.box { margin: 20px; border: solid 1px #E00; line-height: 0; } .text { display: inline-block; margin: 0; font-size: 18px; line-height: 1.5; color: #000; background: #CCC; writing-mode: tb-rl; -o-writing-mode: vertical-rl; -webkit-writing-mode: vertical-rl; writing-mode: vertical-rl; } .text-rotate { /*writing-mode fallback*/ transform-origin: 0 0; transform: rotate(90deg) translateY(-100%); }
JavaScript(※DOMを読み込み終わってから実行)
var ua = window.navigator.userAgent; if (!('indexOf' in Array.prototype) || ua.indexOf('Firefox') === -1) { return; } var browser_str = ua.match(/Firefox\/.*$/); var version = parseFloat(browser_str[0].replace(/^Firefox\//, ''), 10); if (version < 41) { document.querySelector('.text').classList.add('text-rotate'); }
包含ブロック(.box)に赤い枠線を、テキストの要素(.text)にはグレーの背景をつけました。
テキストの要素(.text)に「display: inline-block」を指定しているのは、rotateを使用した(フォールバックの)場合に、.textの高さが.boxの横幅100%になってしまうからです。
また、画像で分かるように、rotateを使用した場合は包含ブロックの高さは伸びません。
包含ブロックがテキストの要素(.text)よりも縦に長い場合は問題ありませんが、必要に応じてmin-heightを指定するなどすると良さそうです。
CADDYで手軽にHTTP/2サーバを立てる
CADDYとは
公式サイト
Caddy - The HTTP/2 Web Server with Fully Managed TLS
GitHub
GitHub - mholt/caddy: Fast, cross-platform HTTP/2 web server with automatic HTTPS
公式サイトから抜粋:
- Windows, Mac, Linuxと、Androidで動く
- Apacheやnginxのような細かな設定はできないかわりに、設定に関する専門知識は不要
- 静的なファイルのサーブを主目的にしていて、環境の移行も簡単
ローカルでちょっとテストするのに使えるかなー、と思って興味を持ったのですが、
もう少し本格的なサーバなのかも?という印象。
HTTP/2で動かすのは本当に簡単で、PHPもHTTP/2でサービスさせることができました。
以下、Macでの設定〜起動までの手順です。
※他のOSでは試していませんが、サーバ自体が実行ファイル形式になっているので、基本的には同じような方法で動かせるのではないかと思います。
ダウンロードとインストール
Download Caddy
本体の「Caddy Core」をダウンロードします。
ダウンロードしたフォルダ直下にある「caddy」が実行ファイルです。
好きな場所に置いて、シェルからコマンドを実行できるようにパスを通しておきます。
ターミナルから、サーバのドキュメントルートにしたいディレクトリに移動して、
$ caddy
でサーバが起動します。
デフォルトでは「2015」番ポートを使うようになっているので(来年になったら変わるとか?)、ブラウザで「http://localhost:2015/」にアクセスしてみます。
「index.html」があればその内容が表示されるはずです。なくても「404 Not Found」が出ていればサーバは動いています。
↑デフォルトの404画面はすごくシンプル。
HTTP/2でサービスさせるために、SSLの設定をする
HTTP/2に対応させるにはSSLの設定をして、HTTPSを使用する必要があります。
2017年2月追記:
CADDYに、自動的に自己証明書を生成するオプションが追加されましたので、テスト用途であれば以下の証明書の準備は不要になりました。便利です!
公式ページ:tls - Caddy Directives (追記ここまで)
今回は、ローカルのテストサーバということでセキュリティは考えずに、opensslで自己証明書を用意します。
※証明書について詳しいことは省きますが、以下が参考になりました:
オレオレ証明書をopensslで作る(詳細版) - ろば電子が詰まっている
OpenSSLコマンドによる公開鍵暗号、電子署名の方法 - Qiita
鍵ファイルを置きたいディレクトリに移動してopensslのコマンドを実行します。
$ openssl genrsa -aes128 -out server.key 2048 $ openssl req -new -key server.key -sha256 -out server.csr $ openssl x509 -in server.csr -days 365 -req -signkey server.key -sha256 -out server.crt
※openssl reqのところで、いろいろ入力項目が出てきますが、パスワード以外はすべて空欄のままで問題ありませんでした。
これで鍵セットは完成ですが、暗号化されたままだとうまく動かなかったので、キーを復号したものも作りました。
$ openssl rsa -in server.key -out server.noencrypt.key
これでSSLの準備は完了です。
(※復号化しないと動かなかったのは、何かミスっているような気もするのですが、ローカルサーバなので追求していません。)
とりあえず、サーバもSSL keyもコンテンツもこんな感じで一ヶ所にまとめました。
もちろんどこに何を置いてもいいのですが、まとめておいたほうがパスの指定などがやりやすいかな、という印象です。
caddyfileを用意して細かな設定をする
CADDYでは「caddyfile」というテキストファイルに様々な設定を記述することができます。
設定可能な項目は公式サイトの以下のページにまとまっています。かなりたくさんあります。
The Caddyfile - Caddy
シンプルなHTTP/2(HTTPS)のローカルサーバの場合:
localhost:2015 { tls ./sslkey/server.crt ./sslkey/server.noencrypt.key root ./public/ }
毎回ドキュメントルートに移動してサーバを起動するのも面倒なので、「root」をcaddyfileに記述しました。
※caddyfile内に記述するパスは、カレントディレクトリからのパスです。上の例では相対パスになっていますが、もちろん絶対パスでも大丈夫です。
2017年2月追記:
CADDYに自己証明書を自動生成させる場合は、
tls self_signed
と記述します。(追記ここまで)
caddyfileを保存したら、ファイルの内容をcaddyに渡して実行します:
$ caddy -conf="/path/to/Caddyfile"
もしくは、
$ cat Caddyfile | caddy
ブラウザから、今度は「https://localhost:2015」にアクセスしてみます。(証明書の警告が出ると思いますが、無視して接続を選択します。)
ブラウザが対応していれば、HTTP/2での接続になっているはずです。
ChromeのDevToolsで見てみると、「Protocol」のところが「h2」となっているのが確認できます。
HTTP/1.1 vs HTTP/2
簡単なテストとして、画像を10枚配置したHTMLを作ってアクセスしてみました。
上がHTTP/1.1で下がHTTP/2です。HTTP/2では10枚が並列に読み込まれています。
(ローカルサーバにしては時間がかかっていますが、差が分かりにくいのでブラウザのほうでわざと速度を落としています。)
FastCGIを使う
CADDYでは、そのままでは静的なファイルのサーブしかできませんが、リクエストをFastCGIにプロキシすることで、PHPの実行が可能になります。
fastcgi - Caddy Directives
まず、PHPのFastCGIマネージャであるphp-fpmを起動しておきます。
php-fpmが9000番ポートでサービスしている場合、caddyfileを以下のように記述します。
localhost:2015 { tls ./sslkey/server.crt ./sslkey/server.noencrypt.key root ./public/ fastcgi /api 127.0.0.1:9000 }
この設定では、「/api」以下のリクエストを、FastCGIにプロキシします。
phpinfo()を呼び出すと以下のようになりました。
おわりに
テストサーバとしてやりたかったことはこれで全部できたので、ひとまずここまで。
処理速度やどのくらいのトラフィックをさばけるのか、というパフォーマンス面と、まだいろいろなことができそう、という機能面を、もう少し調査できたらまた続編を書いてみたいと思います。
language-sparqlというAtomのPackageを作った
AtomでSPARQL(RDFデータを検索する言語です)のシンタックスハイライトがなかったので、作りました。
「作った」と言っても、文法の定義はSublime Text用のパッケージから持ってきたので、大したことはしていません。
AtomのPackageを公開する手順は、オフィシャルのドキュメントでひととおり把握できました。
あとは、他の言語用の(シンタックスハイライトの)Packageのソースコードを見て参考にしたりもしました。
公開したのは1ヶ月前ですが、だいたい1日1回くらいのペースでダウンロードされているようです。
なんですが、ちょっとミスって最初のバージョンが「0.2.0」になっています・・・恥ずかしい・・・。
package.jsonに自分で「"version": "0.1.0"」って書いたのが原因です。
「apm public minor」をしたときに、Atomのほうでバージョン上げてくれるので「0.0.0」のままで良かったんですね。
SPARQL自体は、今のところはお手伝いでちょっと書いたくらいです。
オープンデータを使ったwebアプリとか、何かアイデアが浮かんだらやってみたいです。
JavaScriptのPromiseとarray.reduceを合わせて使う
順番に非同期の処理をしたい要素たちを配列に入れて、各要素に対してPromiseを返す関数を実行する。
例)['a', 'b', 'c']という配列があったときに、それぞれの文字を2秒おきにコンソールに表示したい
//これだと2秒後にほぼ同時に「a」「b」「c」がコンソールに表示されるのでNG function wait2sec (item) { //2秒後に引数itemをコンソールに表示するfunction return new Promise( function(resolve, reject) { window.setTimeout(function() { console.log(item); resolve(); }, 2000); }); } ['a', 'b', 'c'].forEach(function (item) { wait2sec(item); });
//2秒おきにconsole.log()される! function wait2sec (item) { //このfunctionはさっきと同じ return new Promise(function (resolve, reject) { window.setTimeout(function () { console.log(item); resolve(); }, 2000); }); } ['a', 'b', 'c'].reduce(function (prevValue, currentValue) { return prevValue.then(function () { ////直前の要素の処理(2秒かかる)が終わってから次の要素をwait2sec()に渡す return wait2sec(currentValue); }); }, Promise.resolve());
たまにIE8から対応とかあるのでjQueryだけでどうにかする(だいぶ無理矢理感が・・・)
function wait2sec (list, i) { var dfd = new $.Deferred(); window.setTimeout(function () { console.log(list[i]); dfd.resolve(i + 1); }, 2000); return dfd.promise(); } var list = ['a', 'b', 'c']; var i = 0; var prevValue = new $.Deferred().resolve(0); for (i = 0; i < list.length; i += 1) { prevValue = prevValue.then( function (next_i) { return wait2sec(list, next_i); }); };
Array.prototype.reduceについて
使ったことなかったですが、便利ですね!
Array.prototype.reduce() - JavaScript | MDN
IE9以降で使えるそう。過去に書いたコードの中にも、これを使えばもう少し簡潔に書けたものがありそうな気する・・・。