$(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 Chart = function() { /* ------------------------- メニューとして表示したい名称 ------------------------- */ var varAll = "すべて"; //定性的データ var varString1 = "メーカー名"; var varString2 = "特徴"; //定量的データ var varNum1 = "容量(g)"; var varNum2 = "エネルギー(kcal)"; //配列化 var kirikuchiLabel = [ varAll, varString1, varString2, varNum1, varNum2 ]; var margin = 80; 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 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 coreContainer = svg.append("svg:g") .attr("id", "coreCircle") .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.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 ''; }).join(''); keyString2 = d3.set( data.map(function(d){ return d.valueString2 }) ).values(), options = keyString2.map(function(d) { return ''; }).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= parseInt(lengthArray1[j].min)) && (o.valueNum1 <= parseInt(lengthArray1[j].max))) { centerx = wScale(j); } }; break; case 4: labelsArray.length=0; for (var i=0; i= 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('.coreC').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 coreCircles = coreContainer.selectAll('.coreC') .data(data) coreCircles.enter() .append('circle') .attr('class', 'coreC') .attr('r', 0) .attr('fill', "#FFF") .call( self.force.drag ) coreCircles .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([ '

', d.name, '

', '

', varString1 + ":", d.valueString1, '

', '

', varString2 + ":", d.valueString2, '

', '

', varNum1 + ":", d.valueNum1, '

', '

', varNum2 + ":", d.valueNum2, '

' ].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; });