|
/* |
|
変更可能な設定 |
|
- メニューとして表示したい名称 |
|
- 使いたい色リスト |
|
- valueNumの値をいくつかにグルーピング |
|
- 円の大きさの調整用 |
|
- 値と色を関連づける |
|
- 円の大きさと値を関連づける |
|
*/ |
|
|
|
|
|
$(document).ready(function(){ |
|
var Eventer=function(){if(!(this instanceof Eventer))return new Eventer;this.publish=function(c,d){topics=b(c),topics.forEach(function(b){"object"==typeof a[b]&&a[b].forEach(function(a){a.apply(this,d||[])})})},this.subscribe=function(b,c){var d=[].concat(c);return d.forEach(function(c){a[b]||(a[b]=[]),a[b].push(c)}),[b,c]},this.unsubscribe=function(b,c){a[b]&&a[b].forEach(function(d,e){d==c&&a[b].splice(e,1)})},this.queue=function(){return a},this.on=this.subscribe,this.off=this.unsubscribe,this.trigger=this.publish;var a={},b=function(a){return"string"==typeof a?a.split(" "):a};return this}; |
|
var eventer = new Eventer; |
|
|
|
|
|
/* |
|
メニューとして表示したい名称 |
|
*/ |
|
|
|
var varAll = "すべて"; |
|
|
|
//定性的データ |
|
var varString1 = "メーカー名"; |
|
var varString2 = "特徴"; |
|
|
|
//定量的データ |
|
var varNum1 = "容量(g)"; |
|
var varNum2 = "エネルギー(kcal)"; |
|
|
|
var kirikuchiLabel = [ |
|
varAll, |
|
varString1, |
|
varString2, |
|
varNum1, |
|
varNum2 |
|
]; |
|
|
|
|
|
var keyString1, keyValue2; |
|
var varForColor; |
|
|
|
var labelsArray; //選択した項目の選択肢を表示を入れる入れ物。 |
|
|
|
|
|
|
|
/* |
|
使いたい色リスト |
|
*/ |
|
var colorArray = [ |
|
"#CA03B0", |
|
"#60B00A", |
|
"#AB1008", |
|
"#243753", |
|
"#D0ABE5", |
|
"#225232", |
|
"#F869B6", |
|
"#7E0B21", |
|
"#E96754" |
|
]; |
|
|
|
|
|
/* |
|
valueNumの値をいくつかにグルーピング |
|
*/ |
|
|
|
// varNum1 |
|
var lengthArray1 = [ |
|
{min: 80, max: 100}, |
|
{min: 101, max: 150}, |
|
{min: 151, max: 220} |
|
]; |
|
|
|
// varNum2 |
|
var lengthArray2 = [ |
|
{min: 30, max: 90}, |
|
{min: 91, max: 120}, |
|
{min: 121, max: 150}, |
|
{min: 151, max: 180} |
|
]; |
|
|
|
|
|
/* |
|
円の大きさの調整用 |
|
*/ |
|
var circleScale = 2; |
|
|
|
|
|
|
|
var Chart = function() { |
|
|
|
/* -------------------- |
|
変数(データの入れ物)の設定 |
|
-------------------- */ |
|
var margin = 80; |
|
|
|
|
|
/* -------------------- |
|
描画エリアの設定 |
|
-------------------- */ |
|
var width = 962 - margin * 2, |
|
height = 600 - margin * 2; |
|
|
|
var svg = d3.select('#content').append('svg') |
|
.attr('width', width + margin) |
|
.attr('height', height + margin); |
|
|
|
var dataContainer = svg.append("svg:g") |
|
.attr("id", "dataCircle") |
|
.attr('width', width + margin) |
|
.attr('height', height + margin) |
|
.attr('transform', 'translate(' + [margin/2, margin/2].join(',') + ')'); |
|
|
|
var peopleContainer = svg.append("svg:g") |
|
.attr("id", "peopleCircle") |
|
.attr('width', width + margin) |
|
.attr('height', height + margin) |
|
.attr('transform', 'translate(' + [margin/2, margin/2].join(',') + ')'); |
|
|
|
|
|
/* -------------------- |
|
スケールの設定 |
|
-------------------- */ |
|
var wScale = d3.scale.linear(); |
|
|
|
|
|
/* -------------------- |
|
イベントリスナー |
|
-------------------- */ |
|
var self = this; |
|
this.e = new Eventer; |
|
|
|
this.init = function() { |
|
this.e.subscribe( 'load', [this.getData] ); |
|
this.e.subscribe( 'load:data', [this.canvas.setup] ); |
|
this.e.subscribe( 'draw:menu', [this.drawMenu] ); |
|
this.e.subscribe( 'draw', [this.canvas.draw] ); |
|
|
|
this.e.publish( 'load' ); |
|
}; |
|
|
|
|
|
/* -------------------- |
|
色んな機能 |
|
-------------------- */ |
|
|
|
//ボタンメニューの生成 |
|
this.drawMenu = function() { |
|
|
|
var menuItems = d3.select("#menuBlock").select('form').selectAll("span") |
|
.data( kirikuchiLabel ) |
|
.enter().append("span").attr("class", "navColumn"); |
|
|
|
menuItems.append("input") |
|
.attr({ |
|
type: "radio", |
|
class: "nav", |
|
name: "nav", |
|
value: function(d, i) {return i;} |
|
}) |
|
.attr('id', function(d, i) { |
|
return "id" + i; |
|
}) |
|
.attr('value', function(d, i) { |
|
return i; |
|
}) |
|
.property("checked", function(d, i) { |
|
if (i === 0) { return true; } else { return false; }; |
|
}) |
|
.on("change", function(d,i){ |
|
self.e.publish(['draw'], [self.data, {selected: i, value: this.value}]); |
|
}); |
|
|
|
menuItems.append("label") |
|
.attr('for', function(d, i) { |
|
return "id" + i; |
|
}) |
|
.text(function(d,i) { |
|
return d; |
|
}); |
|
|
|
} |
|
|
|
|
|
/* -------------------- |
|
データの取得 |
|
-------------------- */ |
|
this.getData = function() { |
|
|
|
d3.tsv('data_rev02.tsv', function(error, data){ |
|
|
|
//データ型を確定させる |
|
data.forEach(function(d){ |
|
d.valueString1 = String(d.valueString1); |
|
d.valueString2 = String(d.valueString2); |
|
d.valueNum1 = parseInt(d.valueNum1); |
|
d.valueNum2 = parseInt(d.valueNum2); |
|
}); |
|
|
|
self.data = data; |
|
self.e.publish('load:data', [data]); |
|
}); |
|
}; |
|
|
|
|
|
/* -------------------- |
|
データの描画 |
|
-------------------- */ |
|
this.canvas = { |
|
|
|
setup: function(data) { |
|
|
|
// force layoutの初期化 |
|
self.force = d3.layout.force().nodes(data); |
|
|
|
//定性的データに含まれるユニークな値を配列化する |
|
keyString1 = d3.set( data.map(function(d){ return d.valueString1 }) ).values(), |
|
options = keyString1.map(function(d) { |
|
return '<option>' + d + '</option>'; |
|
}).join(''); |
|
|
|
keyString2 = d3.set( data.map(function(d){ return d.valueString2 }) ).values(), |
|
options = keyString2.map(function(d) { |
|
return '<option>' + d + '</option>'; |
|
}).join(''); |
|
|
|
/* |
|
値と色を関連づける |
|
*/ |
|
varForColor = keyString1; |
|
|
|
self.colors = {}; |
|
for(i = 0; i < varForColor.length; i++) { |
|
var key = varForColor[i]; |
|
self.colors[varForColor[i]] = [colorArray[i]].join(''); |
|
} |
|
|
|
self.e.publish('draw', [ data ]); |
|
self.e.publish('draw:menu'); |
|
self.e.publish('draw:emptyinfo'); |
|
}, |
|
|
|
|
|
draw: function(data, filter) { |
|
|
|
self.force |
|
.nodes(data) |
|
.charge(function(d){ return -d.valueNum1/2 }) |
|
.size([width, height / 2]) |
|
.on('tick', function(e){ |
|
|
|
var center = { |
|
x: width / 2, |
|
y: height / 2 + height / 4 |
|
}, |
|
centerx; |
|
|
|
self.data.forEach(function(o, i) { |
|
|
|
if((filter || {}).value) { |
|
|
|
switch (filter.selected){ |
|
|
|
case 0: |
|
labelsArray = [varAll]; |
|
centerx = 1; |
|
break; |
|
|
|
case 1: |
|
labelsArray = keyString1.slice(); |
|
var _l = keyString1.length; |
|
|
|
wScale.domain([0, _l]).range([0, 3]); |
|
|
|
for (var j=0; j<_l; j++) { |
|
if (o.valueString1 == keyString1[j]) { |
|
centerx = wScale(j); |
|
} |
|
}; |
|
break; |
|
|
|
case 2: |
|
labelsArray = keyString2.slice(); |
|
var _l = keyString2.length; |
|
wScale.domain([0, _l]).range([0, 3]); |
|
for (var j=0; j<_l; j++) { |
|
if (o.valueString2 == keyString2[j]) { centerx = wScale(j); } |
|
}; |
|
break; |
|
|
|
case 3: |
|
labelsArray.length=0; |
|
for (var i=0; i<lengthArray1.length; i++) { |
|
labelsArray[i] = lengthArray1[i].min + "〜" + lengthArray1[i].max; |
|
}; |
|
|
|
var _l = lengthArray1.length; |
|
wScale.domain([0, _l]).range([0, 3]); |
|
|
|
for (var j=0; j<_l; j++) { |
|
if ((o.valueNum1 >= parseInt(lengthArray1[j].min)) && (o.valueNum1 <= parseInt(lengthArray1[j].max))) { |
|
centerx = wScale(j); |
|
} |
|
}; |
|
break; |
|
|
|
case 4: |
|
labelsArray.length=0; |
|
for (var i=0; i<lengthArray2.length; i++) { |
|
labelsArray[i] = lengthArray2[i].min + "〜" + lengthArray2[i].max; |
|
}; |
|
|
|
var _l = lengthArray2.length; |
|
wScale.domain([0, _l]).range([0, 3]); |
|
|
|
for (var j=0; j<_l; j++) { |
|
if ((o.valueNum2 >= parseInt(lengthArray2[j].min)) && (o.valueNum2 <= parseInt(lengthArray2[j].max))) { |
|
centerx = wScale(j); |
|
} |
|
}; |
|
break; |
|
} |
|
|
|
} else { |
|
|
|
labelsArray = [varAll]; |
|
centerx = 1; |
|
} |
|
|
|
o.x += (center.x * centerx - o.x) * e.alpha * 0.04; |
|
o.y += (center.y - o.y) * e.alpha * 0.04; |
|
|
|
}); |
|
|
|
d3.select("#labels").text( labelsArray.join(" - ") ); |
|
d3.selectAll('.peopleC').attr("transform", function(d) { return ['translate(', d.x, ', ', d.y, ')'].join(''); }); |
|
d3.selectAll('.dataC').attr("transform", function(d) { return ['translate(', d.x, ', ', d.y, ')'].join(''); }); |
|
|
|
}) |
|
.start(); |
|
|
|
|
|
/* |
|
各円の中心にある白い円 |
|
*/ |
|
var peopleCircles = peopleContainer.selectAll('.peopleC') |
|
.data(data) |
|
|
|
peopleCircles.enter() |
|
.append('circle') |
|
.attr('class', 'peopleC') |
|
.attr('r', 0) |
|
.attr('fill', "#FFF") |
|
.call( self.force.drag ) |
|
|
|
peopleCircles |
|
.transition() |
|
.duration(1100) |
|
.delay(function(d, i) { return i * 10; }) |
|
.attr('fill', "#FFF") |
|
.attr('r', 2); |
|
|
|
|
|
/* |
|
データによりサイズが変化する色のついた円 |
|
*/ |
|
var dataCircles = dataContainer.selectAll('.dataC') |
|
.data(data) |
|
|
|
dataCircles.enter() |
|
.append('circle') |
|
.attr('class', 'dataC') |
|
.attr('r', 0) |
|
.attr('fill', function(d){ |
|
return "#FFFFFF"; |
|
}) |
|
.call( self.force.drag ) |
|
|
|
dataCircles |
|
.transition() |
|
.duration(1100) |
|
.delay(function(d, i) { return i * 10; }) |
|
/* 円を描画 */ |
|
.attr('fill', function(d){ |
|
return self.colors[d.valueString1]; |
|
}) |
|
//円の大きさと値を関連づける |
|
.attr('r', function(d){ |
|
return Math.sqrt(d.valueNum1) * circleScale; |
|
}); |
|
|
|
dataCircles.on('mouseenter', function(d){ |
|
d3.select(this) |
|
.attr('class', 'dataC active') |
|
|
|
d3.select('#tooltip') |
|
.attr('class', 'active') |
|
.html([ |
|
'<p class="name">', d.name, '</p>', |
|
'<p class="valData">', varString1 + ":", d.valueString1, '</p>', |
|
'<p class="valData">', varString2 + ":", d.valueString2, '</p>', |
|
'<p class="valData">', varNum1 + ":", d.valueNum1, '</p>', |
|
'<p class="valData">', varNum2 + ":", d.valueNum2, '</p>' |
|
].join('')); |
|
}); |
|
|
|
dataCircles.on('mouseleave', function(d){ |
|
d3.select(this).attr('class', 'dataC') |
|
d3.select('#tooltip').attr('class', 'deactive'); |
|
}); |
|
} |
|
}; |
|
|
|
|
|
this.init.apply( this, arguments ); |
|
}; |
|
|
|
var chart = new Chart; |
|
|
|
}); |