D3.jsでスライドショーを作ってみる
- 2015/08/31
- 近藤
おはようございます、制作部の近藤です。
今回は、データビジュアライゼーションJavaScriptライブラリ、
D3.jsを使ってスライドショーを作ってみようと思います。
目次
D3.jsのレイアウト
D3.jsには、棒グラフや円グラフなど様々なグラフの表示形式があり、
これをレイアウトと呼びます。
レイアウトには、本当に様々な種類がありますので、興味もたれた方はぜひ本家サイトをご覧ください。
treemapレイアウトは、各データの任意の数値に応じて表示させる四角形の面積を変え、
うまいこと指定領域一杯に敷き詰めることで、各データの比率を分かりやすく表示させるレイアウトです。
例)釣った魚リスト
See the Pen d3 treemap slideshow test1 by kinoborisan (@kinoborisan) on CodePen.
※通常はSVGで、各データの領域をrectで表示するのですが、今回はスライドショー向けにDIVを敷き詰める形にしてます。
The beauty of data visualization - David McCandless
でも、開始1分ちょいくらいのとこで紹介されてますね。
今回は、このtreemapレイアウトを利用したフォトスライドショーを作ります。
treemapの使い方
以下のようなデータがあったとします。
魚の名前 | 釣れた数 |
---|---|
さば | 10 |
いわし | 40 |
キス | 4 |
めじな | 2 |
あじ | 22 |
ごんずい | 1 |
このデータを元に、冒頭の例、「釣った魚リスト」を作ってみましょう。
HTML
まずは、HTMLを準備します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <style> #secGraph > div { background: rgba(0,0,0,.5); position: relative; } .node { position: absolute; overflow: hidden; color: #fff; font-size: 10px; text-indent: 10px; line-height: 2; } </style> <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js" charset="utf-8"></script> <script src="treemap.js"></script><!-- treemap表示用のJS --> <title>釣った魚リスト</title> </head> <body> <header id="header"> <h1>釣った魚リスト</h1> <!-- /#header --></header> <section id="main"> <div id="secGraph"></div> <!-- /#main --></section> </body> </html> |
HTMLの準備は簡単で、数行のstyle記述、
d3.jsの読み込み、スライドショー用のtreemap.jsの読み込みをし、
グラフ描画領域として div#secGraph をbody内に記述するだけです。
JavaScript
以下のJavaScriptは、「treemap.js」として保存してください。
まずは、参考データの配列を記述します。
1 2 3 4 5 6 7 8 9 10 11 |
var data = { 'name': 'fishes', 'children': [ {'name': 'さば',num: 10}, {'name': 'いわし',num: 40}, {'name': 'キス',num: 4}, {'name': 'めじな',num: 2}, {'name': 'あじ',num: 22}, {'name': 'ごんずい',num: 1}, ] }; |
配列内の各データはオブジェクトにし、name:魚の名前、num:釣った数、として記述します。
さて、ここからお楽しみのD3.jsの記述に入っていきます。
まずは表示領域のサイズを指定し、div#secGraph をD3で選択、グラフ領域描画用のDIVを生成します。
1 2 3 4 5 6 7 |
var graph_width = 720//表示幅 var graph_height = 300;//表示高さ var photoGraph = d3.select("#secGraph").append('div') .attr('style', function(){//.attr()メソッドで、選択したDOMのstyleを定義 return 'width: ' + graph_width + 'px; height: ' + graph_height+'px;' }) |
jQueryの記述に似ていますね。d3.selectでDOMを選択し、appendでDIVを追加しています。
そして、.attrでstyle属性を指定し、描画領域のサイズを指示しています。
D3.jsでは、jQueryのように、「.」のチェイン構文でメソッドをつなげていきます。
続いて、データをツリーマップの表示形式にするための準備をします。
1 2 3 4 5 |
var treemap = d3.layout.treemap() .size([graph_width, graph_height]) .value(function(d) { return ( parseInt( d.num ) ); }); |
var treemapは、D3の関数オブジェクトで、.sizeメソッドで表示領域のサイズを、
.valueメソッドで、レイアウトの基準となる、個別データの値を設定します。
ここでreturnされる数値を元に、
・表示領域内での位置
・横幅
・高さ
などが算出されます。
最後に、描画を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
photoGraph .selectAll('.node')//photoGraph から、「.node」クラスを持つ要素全てを選択 .data( treemap.nodes(data) )//選択された全ての「.node」クラスを持つ要素群と、treemapに渡した釣りデータの返り値をデータバインド※1 .enter()//設定されたデータ分、以下の処理を実行 .append('div')//個別データ用のDIV追加 .attr('class','node')//DIVに.nodeのクラス指定 .attr('style',function(d , i){//スタイルの設定。引数のdはデータバインドされた、※1 の各データオブジェクト、i はインデックス var hue = parseInt( i * 40 + 120 );//色分けのため、HUE(色相)を指定 var styleCode = ''; styleCode += 'width:' + d.dx + 'px;';//横幅を指定 styleCode += 'height:' + d.dy + 'px;';//高さを指定 styleCode += 'top:' + ( graph_height - d.y - d.dy) + 'px;';//縦位置 styleCode += 'left:' + ( graph_width - d.x - d.dx ) + 'px;';//横位置 styleCode += 'background-color:hsl(' + hue + ', 50%, 40%);';//背景色 return styleCode; }) .text(function(d){//テキストを指定 return d.name; }) |
.data()は、D3.jsのもっとも重要なものの一つで、DOMとデータを結びつける働きをします。
.enter()も同じくらい重要なもので、結び付けられたデータに対応して、処理を行う働きを持ちます。
.attr('style',...)では、CSSで要素の背景色や配置・領域などを定義しています。
.text()は、この要素に表示させるテキストを指定できます。
DOMとデータを結びつける一連の流れは、D3.jsの中で一番ダイナミックで面白いところだと思います!
さて、続いてフォトスライドショーの実作に入っていきたいと思います。
D3.jsのtreemapでスライドショー
今回は、弊社サービスのGreenSnapのデータを元に、フォトスライドショーを作ってみたいと思います。
作るもの
完成図は以下です。
See the Pen d3 treemap slideshow 1 by kinoborisan (@kinoborisan) on CodePen.
GreenSnapに投稿されたみなさんの写真を、いいね数に応じて大きく表示させています。
また、スライドショーらしく、画面上部のバーが延びきったところで次のスライドへ遷移する動きも設定しました。
まずは、HTMLから見ていきます。
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <link rel="stylesheet" href="./css/base.css" media="screen"> <script src="./js/d3.v3.min.js" charset="utf-8"></script> <script src="./js/greensnapSlideshow.js"></script> <title>GreenSnapのスライドショー</title> </head> <body> <div id="bar"><div id="barInner"></div></div> <header id="header"> <h1><a href="http://greensnap.jp/" target="_blank">greensnap slideshow</h1> <!-- /#header --></header> <section id="main"> <div id="secGraph"></div> <!-- /#main --></section> </body> </html> |
魚リストとの違いは、div#bar , div#barInner が追加されているくらいです。
CSS
次に、CSSです。
まずは、表示時間で伸びるバー部分から。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#bar { position: fixed; top: 0; left: 0; right: 0; height: 10px; background: rgba(0,0,0,.85); z-index: 500; } #barInner { content: ""; position: fixed; top: 0; left: 0; height: 10px; width: 0; background: rgba(255,255,255,.95); } |
画面上部にバーの枠(div#bar)を、その内側に、バーの幅表示用の枠(div#barInner)を設定しています。
この部分を、d3.jsのtransitionで0→100%と切り替え、そのコールバックで、次のスライドに遷移させる形にしています。
続いて、各写真にもエフェクトを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
.node { position: absolute; opacity: 1; transition: all .4s; overflow:hidden; } .node.on { opacity: 1; } .nodeInner { width: 100%; height: 100%; transition: all .4s; opacity: 0; transform: translate(-50%,0); } .nodeInner.on { opacity:1; transform: translate(0,0); } .node.on .nodeInner { transform: scale(1.2, 1.2); } |
.nodeが写真表示の大枠、その内側に、.nodeInnerとして、写真を表示させるための枠を設けています。
.nodeがD3.jsで指示する領域を確保し、.nodeInnerは画像、CSSによるエフェクト(透過・移動)をコントロールする形です。
なお、マウスオーバーでのエフェクトなどは、D3.js側でクラスをトグルしています。
JavaScript
続いて、JavaScriptです。
こちらのJavaScriptは、greensnapSlideshow.jsのファイル名で保存ください。
まずは、画像のパス・ライク数の2項目からなるオブジェクトの配列を準備します。
1 2 3 4 |
var dataOrigin = [ {"imageUrlDetail": "画像のパス","totalLikes": 81}, ... ]; |
続いて、スライドショー向けの変数・グラフ描画領域・バー部分を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//ギャラリー用変数 var pageNum = 0;//ページ番号 var thumbNum = 10;//サムネイル数 var slideTime = 5000;//スライド切り替え時間 var maxPageNum;//最大ページ数 var graph_width = window.innerWidth;//表示幅 var graph_height = window.innerHeight;//表示高さ var tree = {name: "greensnap"}//D3.jsのtreemapオブジェクト宣言 //DOMをD3オブジェクトに var photoGraph = d3.select("#secGraph") .append('div') .attr("width", graph_width+'px') .attr("height", graph_height+'px'); var bar = d3.select('#bar'); var barInner = d3.select('#barInner'); maxPageNum = Math.floor( dataOrigin.length / thumbNum );//最大ページ数を設定。端数は切り捨て |
スライドショー全体の横幅・高さは、画面サイズ全体に合わせる形で調整しています。
これで、スライドショーの実行準備が整いましたので、つづいて、スライドショーの実行部分を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
setTreemap();//ツリーマップセット関数実行 function setTreemap() { barInner.attr('style','width:0;');//遷移バーの幅を初期化。 var dataArr = [];//1ページに表示させる画像配列の宣言 if( pageNum === maxPageNum ){ for(var i = pageNum * thumbNum ; i < dataOrigin.length; i++){ dataArr.push( dataOrigin[i] ); } pageNum = 0; } else { for(var i = pageNum * thumbNum ; i < ( pageNum + 1 ) * ( thumbNum ); i++){ dataArr.push( dataOrigin[i] ); } pageNum++; } |
ここまでで、バーの初期化・1ページに表示させるスライドの配列準備が完了しました。
続いて、D3.jsのツリーマップの準備と、スライドの配置を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* スライドの描画 */ tree.children = dataArr;//treeに1ページ分の配列をセット。 var treemap = d3.layout.treemap() .size([graph_width, graph_height]) .sort(null) .value(function(d) { return ( parseInt( d.totalLikes ) );//表示領域の大きさの基準を、ライク数に設定。 }); var node = photoGraph.datum(tree); //node remove スライドのデータなどを削除し、初期化。 node .selectAll(".node") .data({}) .exit() .remove(); //node enter node .selectAll(".node") .data(treemap.nodes) .enter() .append("div") .attr('class','node') .attr('style',function(d){ return 'width:' + d.dx + "px; height:" + d.dy + "px; top:" + ( graph_height - d.y - d.dy ) + "px; left:" + ( graph_width - d.x - d.dx ) + "px"; }) .on('click',function(d){ //モーダル表示などあればここで }) //mouseover/outのエフェクト追加。 .on('mouseover',function(d,i){ d3.select(this).attr('class','node on'); }) .on('mouseout',function(d,i){ d3.select(this).attr('class','node'); }) |
魚マップでは、1回のみの表示でしたが、今回は同一領域に複数回スライドを実行しますので、
データバインドの解除などを行います。
このほか、マウスオーバーやアウトのイベントもここで仕込んでおきます。
D3.jsでのマウスイベントなどの設定は、.onで簡単に行えますので便利ですね。
最後に、背景画像表示用の枠の設定・次のページへの遷移設定などを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//背景画像表示枠の設定 node .selectAll(".node") .append("div")//.nodeにDIVをappend。 .attr('class','nodeInner')//appendしたDIVにnodeInnerクラスを付与。 .attr('style',function(d){//画像URLを、背景画像で設定。 return 'background:url(' + d.imageUrlDetail + ') 50% 50% no-repeat; background-size:cover;'; }) .transition()//transition(遷移)の指定。 .delay(function(d,i){//delay(遅延)マイクロ秒の指定関数。同時に、バーの表現・次のスライドへの遷移を実装。 if( dataArr.length == i ){//最後のスライド画像の場合 barInner .attr('class','on') // .transition()//バーにもtransitionを指定。 .duration(slideTime)//遷移時間は、あらかじめ変数化してあります。 .attr('style','width:100%;')//遷移の内容。CSSで、widthを100%にします。 .each("end",function(){//遷移のコールバック。 setTreemap();//遷移完了 → setTreemap()を再実行。 }) } return 1200 / dataArr.length * i;//インデックス数で掛け合わせ、パラパラした動きを実装。 }) .duration(0)//遷移時間は無しにしてますが、ここでなんか処理かけても面白いかも。 .attr("class",'nodeInner on')//あらかじめCSSで設定しておいた、表示時の効果(opacity:1など)が掛かるようにクラス指定 } |
以上です。
各画像のフェードインの効果や、スライドが切り替わる際のエフェクトなど、
調整すれば色々と面白い表現が出来るかと思いますので試してみてください。
まとめ
今回は、GreenSnapの画像をお借りしてスライドショーにしてみました。
データと絡めてアレンジすることで、より興味深い文脈が作れる気がしました。
いいね数/ポイント/価格など、数値が絡む写真のスライドショーには適しているかと思います。
漁獲高に応じたお魚図鑑とか、釣りえさごとの釣果写真図鑑とかいいですね。
もちろん、外部のAPIを使う場合には、レギュレーションはしっかり守って使いましょう。
D3.jsの可視化の力は、とても楽しいものが出来そうですね。