Mais conteúdo relacionado
Semelhante a AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション (20)
Mais de Yosuke Onoue (17)
AngularJSとD3.jsによるインタラクティブデータビジュアライゼーション
- 9. select, selectAll
selector(CSS3に準拠)で指定された要素を一つ(先頭)選択!
var selection = d3.select(selector);
!
selectorで指定された要素を全て選択!
var selection = d3.selectAll(selector);
<div>
<div></div>
<p class=“small”></p>
<span class=“small”></span>
<div id=“contents”></div>
</div>
d3.select(‘div’)
d3.select(‘div div’)
d3.select(‘div p’)
d3.select(‘p’)
d3.selectAll(‘div’)
d3.selectAll(‘div div’)
d3.selectAll(‘.small’)
d3.selectAll(‘#contents’)
d3.selectAll(‘p.small’)
- 10. ✤ マークアップで記述するグラフィックス!
✤ 解像度に依存しない(拡大してもギザギザしない)
SVG
<svg width="500" height="500">
<rect x="100" y="100" width="100" height="100" fill="red"/>
<circle cx="350" cy="150" r="50" fill="yellow" stroke="blue"/>
<line x1="100" y1="300" x2="200" y2="400" stroke-width="5" stroke="green"/>
<text x="350" y="350" text-anchor="middle">AngularJS</text>
</svg>
- 11. attr, style, classed
<svg width="300" height="300">
<rect/>
<circle/>
</svg>
d3.select('svg>rect')
.attr('width', 200)
.attr('height', 200)
.attr('x', 50)
.attr('y', 50)
.style('fill', 'red')
.style('stroke', 'black')
.style('stroke-width', '5')
.classed('semitransparent', true);
!
d3.select('svg>circle')
.attr({
cx: 150,
cy: 150,
r: 100
})
.style({
fill: 'blue',
stroke: 'black'
});
<svg width="300" height="300">
<rect class=“semitransparent"
style="fill: red; stroke: black; stroke-width: 5;
y="50" x="50" height="200" width=“200"/>
<circle style="fill: blue; stroke: black;”
r="100" cy="150" cx=“150"/>
</svg>
✤ 属性、スタイル、クラスを設定する!
selection.attr(name, value)
selection.style(name, value)
selection.classed(name, value)
- 13. data, datum
var data = [
{x: 50, y: 50, color: 'red'},
{x: 150, y: 150, color: 'green'},
{x: 250, y: 250, color: 'blue'}
];
!
d3.selectAll('circle')
.data(data)
.attr({
cx: function(d) {
return d.x;
},
cy: function(d) {
return d.y;
},
r: 30
})
.style('fill', function(d) {
return d.color;
});
✤ 要素にデータを結びつける!
selection.datum(value)
selection.data(values)
!
✤ 属性に結びつけられたデータを反映!
selection.attr(name, f)
<svg width="300" height="300">
<circle/>
<circle/>
<circle/>
</svg>
<svg width="300" height="300">
<circle style="fill: red;”
r="30" cy="50" cx="50"></circle>
<circle style="fill: green;”
r="30" cy="150" cx="150"></circle>
<circle style="fill: blue;”
r="30" cy="250" cx="250"></circle>
</svg>
- 14. enter, exit
var data = [
{x: 50, y: 50, color: 'red'},
{x: 150, y: 150, color: 'green'},
{x: 250, y: 250, color: 'blue'}
];
!
d3.select('svg')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr({
cx: function(d) {
return d.x;
},
cy: function(d) {
return d.y;
},
r: 30
})
.style('fill', function(d) {
return d.color;
});
<svg width="300" height="300"></svg>
<svg width="300" height="300">
<circle style="fill: red;” r="30" cy="50" cx="50"></circle>
<circle style="fill: green;” r="30" cy="150" cx="150"></circle>
<circle style="fill: blue;” r="30" cy="250" cx="250"></circle>
</svg>
data
要素 Enter Selection
data
要素 Exit Selection
- 15. call
function draw(selection) {
selection
.append('circle')
.attr({
cx: function(d) {
return d.x;
},
cy: function(d) {
return d.y;
},
r: 30
})
.style('fill', function(d) {
return d.color;
});
}
var data = [
{x: 50, y: 50, color: 'red'},
{x: 150, y: 150, color: 'green'},
{x: 250, y: 250, color: 'blue'}
];
!
d3.select('svg')
.selectAll('circle')
.data(data)
.enter()
.call(draw);
cf. NVD3 ( http://nvd3.org )!
!
d3.select(‘svg’)
.datum(data)
.call(chart);
✤ f(selection)のショートカット!
selection.call(f)
!
✤ 描画のモジュール化に有効
- 16. 本日の題材
✤ 麻雀スコア管理 (先週困った)!
✤ スコアのグラフを描く!
✤ AngularJSでスコア編集を実装!
✤ 双方向データバインドで
即座にグラフ更新!
✤ https://github.com/likr/gdgkobe20140823
d3.csv('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
}, function(data) {
d3.select('svg')
.datum(data)
.call(draw());
});
!
!
function draw() {
var verticalMargin = 30;
var axisWidth = 50;
var chartWidth = 500;
var chartHeight = 400;
var barWidth = 10;
var colorScale = d3.scale.category10();
!
return function(selection) {
selection.each(function(data) {
var svg = d3.select(this)
.attr({
width: chartWidth + axisWidth * 2,
height: chartHeight + verticalMargin * 2
});
!
svg.selectAll('g.chart')
.data([data])
.enter()
.call(initialize);
!
svg.selectAll('g.chart')
.call(drawBars)
.call(drawLines);
});
});
}
- 18. まずはそのまま
var app = angular.module('app', []);
!
app.controller('MainController', function() {
d3.csv('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
}, function(data) {
d3.select('svg')
.datum(data)
.call(draw());
});
!
function draw() {
// …
}
});
(作法はともかく) グラフが描ける
- 19. $scopeでデータをバインドする
var app = angular.module('app', []);
!
app.controller('MainController', function($scope) {
$scope.data = [];
$scope.players = [];
!
d3.csv('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
}, function(data) {
d3.select('svg')
.datum(data)
.call(draw());
!
$scope.data = data;
$scope.players = Object.keys(data[0]);
$scope.$apply();
});
!
function draw() {
// …
}
});
<div class="form-group">
<button class="btn btn-default" ng-click="add()">Add</button>
</div>
<table class="table table-bordered">
<tr>
<th class="col-sm-2">Game</th>
<th class="col-sm-2"><input class="form-control" ng-model="players[0]"></th>
<th class="col-sm-2"><input class="form-control" ng-model="players[1]"></th>
<th class="col-sm-2"><input class="form-control" ng-model="players[2]"></th>
<th class="col-sm-2"><input class="form-control" ng-model="players[3]"></th>
<th class="col-sm-2">Error</th>
</tr>
<tr ng-repeat="game in data">
<td>{{$index + 1}}</td>
<td><input class="form-control" type="number" ng-model="game[players[0]]"></td>
<td><input class="form-control" type="number" ng-model="game[players[1]]"></td>
<td><input class="form-control" type="number" ng-model="game[players[2]]"></td>
<td><input class="form-control" type="number" ng-model="game[players[3]]"></td>
<td>{{sumGame(game)}}</td>
</tr>
<tr>
<td>Total</td>
<td>{{sumTotal(players[0])}}</td>
<td>{{sumTotal(players[1])}}</td>
<td>{{sumTotal(players[2])}}</td>
<td>{{sumTotal(players[3])}}</td>
<td></td>
</tr>
</table>
- 20. アプリの機能を整える
var app = angular.module('app', []);
!
app.controller('MainController', function($scope) {
d3.csv(/* … */);
!
$scope.add = function() {
var game = {};
$scope.players.forEach(function(player) {
game[player] = 0;
});
$scope.data.push(game);
};
!
$scope.sumGame = function(game) {
return $scope.players.reduce(function(sum, player) {
return sum + game[player];
}, 0);
};
!
$scope.sumTotal = function(player) {
return $scope.data.reduce(function(sum, game) {
return sum + game[player];
}, 0);
};
!
function draw() {
// …
}
});
データの編集がグラフに反映されない
- 21. $scope.$watchで更新を監視
app.controller('MainController', function($scope) {
$scope.data = [];
$scope.players = [];
!
d3.csv(/* … */;
!
// …
!
$scope.$watch('data', function() {
d3.select('svg')
.call(draw());
}, true);
!
function draw() {
// …
}
});
✤ Scope中の変数の変更を監視する
$scope.$watch(name, callback[, objectEquality])
✤ objectEqualityをtrueでdeep watch!
!
✤ $scope.$watchCollectionを使う方法も!
✤ AngularJSでD3.jsをラップしてみよう
(http://qiita.com/Quramy/items/b701b824c2f42c683aa7)
- 22. 膨れ上がったコントローラー…
app.controller('MainController', function($scope) {
$scope.data = [];
$scope.players = [];
!
d3.csv('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
}, function(data) {
var players = Object.keys(data[0]);
!
d3.select('svg')
.datum({
keys: players,
values: data
})
.call(draw());
!
$scope.data = data;
$scope.players = players;
$scope.$apply();
});
!
$scope.add = function() {
var game = {};
$scope.players.forEach(function(player) {
game[player] = 0;
});
$scope.data.push(game);
};
!
$scope.sumGame = function(game) {
return $scope.players.reduce(function(sum, player)
{
return sum + game[player];
}, 0);
};
!
$scope.sumTotal = function(player) {
return $scope.data.reduce(function(sum, game) {
return sum + game[player];
}, 0);
};
!
$scope.$watch('data', function() {
d3.select('svg')
.call(draw());
}, true);
!
function draw() {
// …
}
});
- 25. resolveでデータを取得する
var app = angular.module('app', ['ui.router']);
!
// d3.csvをラップ
app.factory('getCSV', function($q) {
return function(url, accessor) {
var deferred = $q.defer();
d3.csv(url, accessor, function(data) {
deferred.resolve(data);
});
return deferred.promise;
};
});
!
app.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('main', {
controller: 'MainController',
templateUrl: 'partials/main.html',
url: '/',
resolve: {
scoreData: function(getCSV) {
return getCSV('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
});
}
}
});
!
$urlRouterProvider.otherwise('/');
});
!
app.controller('MainController', function($scope, scoreData) {
$scope.players = Object.keys(scoreData[0]);
$scope.data = scoreData;
!
d3.select('svg')
.datum({
keys: $scope.players,
values: $scope.data
});
!
// …
})
✤ d3.csvをPromiseを返す関数としてラップ!
✤ 汎用的な関数の部品化なのでfactoryを使用!
✤ ngRoute/ui.router のresolveを利用してデータ取得を切り分け!
✤ 前回勉強会参照( http://www.slideshare.net/likr/angularjs-35612212 )
- 26. View用ロジックの切り分け
app.filter('sumGame', function() {
return function(game, keys) {
return keys.reduce(function(sum, key) {
return sum + game[key];
}, 0);
};
});
!
app.filter('sumTotal', function() {
return function(data, key) {
return data.reduce(function(sum, game) {
return sum + game[key];
}, 0);
};
});
<table class="table table-bordered">
<tr>
<th class="col-sm-2">Game</th>
<th class="col-sm-2"><input class="form-control"
<th class="col-sm-2"><input class="form-control"
<th class="col-sm-2"><input class="form-control"
<th class="col-sm-2"><input class="form-control"
<th class="col-sm-2">Error</th>
</tr>
<tr ng-repeat="game in data">
<td>{{$index + 1}}</td>
<td><input class="form-control" type="number" ng
<td><input class="form-control" type="number" ng
<td><input class="form-control" type="number" ng
<td><input class="form-control" type="number" ng
<td>{{game|sumGame:players}}</td>
</tr>
<tr>
<td>Total</td>
<td>{{data|sumTotal:players[0]}}</td>
<td>{{data|sumTotal:players[1]}}</td>
<td>{{data|sumTotal:players[2]}}</td>
<td>{{data|sumTotal:players[3]}}</td>
<td></td>
</tr>
</table>
{{value|filter:arg}}
↓
filter(value, arg)
- 27. SVGのDirective化
app.directive('chart', function() {
return {
restrict: 'AE',
scope: {
keys: '=',
values: '='
},
link: function(scope, element) {
d3.select(element[0])
.append('svg')
.datum({
keys: scope.keys,
values: scope.values
});
!
scope.$watch('values', function() {
d3.select('svg')
.call(draw());
}, true);
}
};
!
function draw() {
// …
}
});
E: <chart keys=“players” values=“data”></chart>
A: <div chart keys=“players” values=“data”></div>
✤ directiveのオプション!
✤ restrict!
✤ A: Attribute!
✤ C: Class!
✤ E: Element!
✤ scope!
✤ Viewから受け取る変数!
✤ link!
✤ DOMの構築
- 28. Before / After
app.controller('MainController', function($scope) {
$scope.data = [];
$scope.players = [];
!d3.csv('data/data.csv', function(row) {
for (var attr in row) {
row[attr] = +row[attr];
}
return row;
}, function(data) {
var players = Object.keys(data[0]);
! d3.select('svg')
.datum({
keys: players,
values: data
})
.call(draw());
! $scope.data = data;
$scope.players = players;
$scope.$apply();
});
!$scope.add = function() {
var game = {};
$scope.players.forEach(function(player) {
game[player] = 0;
});
$scope.data.push(game);
};
!$scope.sumGame = function(game) {
return $scope.players.reduce(function(sum, player) {
return sum + game[player];
}, 0);
};
!$scope.sumTotal = function(player) {
return $scope.data.reduce(function(sum, game) {
return sum + game[player];
}, 0);
};
!$scope.$watch('data', function() {
d3.select('svg')
.call(draw());
}, true);
!function draw() {
// …
}
});
app.controller('MainController', function($scope, scoreData) {
$scope.players = Object.keys(scoreData[0]);
$scope.data = scoreData;
!
$scope.add = function() {
var game = {};
$scope.players.forEach(function(player) {
game[player] = 0;
});
$scope.data.push(game);
};
});
✤ コントローラーがシンプルに!!
!
✤ 「お前のAngular.jsはもうMVCではない。と言われないためのTutorial
( http://qiita.com/icoxfog417/items/2ac773c33a8b34288551 )」
コントローラーではイベントハンドリングと
データバインディングに集中
- 30. まとめ
✤ D3.js たのしい!
✤ 凝った表現はd3 pluginを探すことを推奨!
✤ とりあえずControllerに乗っければアプリが動く!
✤ まずは動かしてみる!!
✤ ProviderでControllerをスリムに保つ