index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title></title>
<style>
#chart {
width: 960px;
height: 480px;
}
.bg {
display: none;
}
.axisLayer .axis {
}
.axisLayer .axis .domain {
stroke: #333333;
}
.axisLayer .tick line {
display: none;
}
.axisLayer .tick text {
fill: #333333;
font-size: 10px;
letter-spacing: .05em;
}
.backgroundLayer .grid line {
stroke: #cccccc;
stroke-dasharray: 3,3;
}
.backgroundLayer .grid .domain {
stroke: none;
}
.bar0 {
fill:#2851cc;
}
.bar1 {
fill:#837373;
}
.bar2 {
fill:#cc2828;
}
.label0 {
color:#2851cc;
}
.label1 {
color:#837373;
}
.label2 {
color:#cc2828;
}
</style>
</head>
<body>
<div id="sort_type">
<label class="label0"><input type="radio" name="sort" data-key="正答率(%)" checked="checked">正答率</label>
<label class="label1" ><input type="radio" name="sort" data-key="不明率(%)">不明率</label>
<label class="label2"><input type="radio" name="sort" data-key="誤答率(%)">誤答率</label>
</div>
<div id="chart"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script>
<script src="//bl.ocks.org/shimizu/raw/0b526eab82263c8443108c33e454d221/nChart.js"></script>
<script src="kodama.js"></script>
<script>
var cast = function(d){
Object.keys(d).forEach(function(key){
if (!isNaN(+d[key])) d[key] = +d[key]
})
return d
}
d3.tsv("data.tsv", cast, main)
function makeDataset(data){
var reslut = {
"正答率(%)": genData("正答率(%)"),
"不明率(%)": genData("不明率(%)"),
"誤答率(%)": genData("誤答率(%)"),
}
function genData(sortKey) {
var temp = []
data.sort(function(a,b){
return a[sortKey] - b[sortKey]
})
data.forEach(function(d){
[
{type:"正答率(%)", answer:"正答", count:"正答数"},
{type:"不明率(%)", answer:"不明", count:"不明数"},
{type:"誤答率(%)", answer:"誤答", count:"誤答数"}
]
.forEach(function(list){
var question = d["質問"]
var percent = d[list.type]
var count = d[list.count]
var answer = d[list.answer]
temp.push({question:question, type:list.type, percent:percent, count:count, answer:answer, title:list.answer})
})
})
return temp
}
return reslut
}
function main(data){
var tooltipFormat = function(d, i){
return {
title:d["title"],
items: [
{ title: '質問', value: d["question"]},
{ title: '解答', value: d["answer"]},
{ title: '人数', value: d["count"]},
{ title: '比率', value: d["percent"]+"%"}
]
}
}
var dataset = makeDataset(data)
var BarChart = nChart.createHStackBarChart()
.baseMargin({top:10, left:0, bottom:10, right:20})
.plotMargin({top:20, left:320, bottom:20, right:10})
.x(function(d){ return d["percent"] })
.y(function(d){ return d["question"] })
.s(function(d){ return d["type"] })
var Axis = nChart.createAxis()
.yAxisDomainLineVisible(false)
var selector = d3.selectAll("#chart")
.datum(dataset["正答率(%)"])
.call(BarChart)
.call(Axis)
selector.selectAll(".bar").call(d3.kodama.tooltip().format(tooltipFormat))
d3.select("#sort_type").selectAll("input").on("click", function(){
selector.update(dataset[this.dataset.key])
})
}
</script>
</body>
</html>
kodama.js
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['d3'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = function(d3) {
d3.kodama = factory(d3);
return d3.kodama;
}
} else {
root.d3.kodama = root.d3.bamboo = factory(root.d3);
}
}(this, function (d3) {
if(d3.kodama) return d3.kodama;
var kodama = {};
var validOptions = ['gravity','theme','distance','style','target','by',
'fadeInDuration', 'fadeOutDuration', 'holdDuration'];
var initialized = false;
var bodyNode = null;
var baseSel = null;
var tipSel = null;
var holderSel = null;
var fadingState = "none";
var gravityDefs = {
northwest: [-1, -1],
topleft: [-1, -1],
upperleft: [-1, -1],
northeast: [1, -1],
topright: [1, -1],
upperright: [1, -1],
southwest: [-1, 1],
bottomleft: [-1, 1],
lowerleft: [-1, 1],
southeast: [1, 1],
bottomright: [1, 1],
lowerright: [1, 1],
north: [0, -1],
top: [0, -1],
south: [0, 1],
bottom: [0, -1],
west: [-1, 0],
left: [-1, 0],
right: [1, 0],
east: [1, 0],
center: [0, 0]
};
function resolveGravity(name) {
name = name.split('-').join();
return gravityDefs[name];
}
var themesByName = {};
var offsetSwitch = [0, 0];
var offsetKey = "0:0";
var tipDisplayData = null;
var activated = false;
var lastSourceDataShown;
var lastFormatFuncShown;
var defaultTarget = null;
var defaultHoldDuration = 0;
var defaultFadeInDuration = 0;
var defaultFadeOutDuration = 500;
var defaultThemeName = 'kodama';
var defaultGravityDirection = 'top';
var defaultByDirection = 'top';
var defaultGravity = resolveGravity(defaultGravityDirection);
var defaultBy = resolveGravity(defaultByDirection);
var defaultDistance = 25;
var defaultTheme = themesByName[defaultThemeName] = {
frame: {
padding: '4px',
background: 'linear-gradient(to top, rgb(16, 74, 105) 0%, rgb(14, 96, 125) 90%)',
'font-family': '"Helvetica Neue", Helvetica, Arial, sans-serif',
'border': '1px solid rgb(57, 208, 204)',
color: 'rgb(245,240,220)',
'border-radius': '4px',
'font-size': '12px',
'box-shadow': '0px 1px 3px rgba(0,20,40,.5)'
},
title: {'text-align': 'center', 'padding': '4px'},
item_title: {'text-align': 'right', 'color': 'rgb(220,200,120)'},
item_value: {'padding': '1px 2px 1px 10px', 'color': 'rgb(234, 224, 184)'}
};
var multiStyles = function (styles) {
return function(selection) {
for (var property in styles) {
selection.style(property, styles[property]);
}
};
}
kodama.init = function(node){
bodyNode = node || document.body;
if(baseSel)
baseSel.remove();
baseSel = d3.select(bodyNode)
.append('div')
.style('position', 'absolute')
.style('left', 0)
.style('top', 0)
.style('visibility', 'hidden')
.style('pointer-events', 'none')
.attr('class','kodama-tooltip')
.attr('name', 'kodama');
tipSel = baseSel
.append('div')
.attr('name', 'kodamaTip')
.call(multiStyles({'position': 'relative', 'pointer-events': 'none', 'z-index': 9999}))
.style('opacity', 0);
holderSel = tipSel.append('div').style('position', 'relative');
};
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', function(){ kodama.init(); });
} else {
kodama.init();
}
kodama.holdDuration = function(duration){
if(arguments.length === 0) return defaultHoldDuration;
defaultHoldDuration = duration;
return kodama;
};
kodama.fadeInDuration = function(duration){
if(arguments.length === 0) return defaultFadeInDuration;
defaultFadeInDuration = duration;
return kodama;
};
kodama.fadeOutDuration = function(duration){
if(arguments.length === 0) return defaultFadeOutDuration;
defaultFadeOutDuration = duration;
return kodama;
};
kodama.distance = function(distance){
if(arguments.length === 0) return defaultDistance;
defaultDistance = distance || 25;
return kodama;
};
kodama.gravity = function(direction){
if(arguments.length === 0) return defaultGravityDirection;
defaultGravityDirection = direction || 'top';
defaultGravity = resolveGravity(defaultGravityDirection);
return kodama;
};
kodama.theme = function(name){
if(arguments.length === 0) return defaultThemeName;
defaultThemeName = name;
defaultTheme = themesByName[defaultThemeName];
return kodama;
};
kodama.themeRegistry = function(name, config){
if(arguments.length === 1) return themesByName[name];
themesByName[name] = config;
return kodama;
};
kodama.themeRegistry('white_wolf', {
frame: {
padding: '5px',
background: 'linear-gradient(to top, rgba(220, 230, 240, .6) 0%, rgba(235, 240, 245, .9) 90%, rgba(230, 235, 240, .8) 100%)',
'font-family': '"Helvetica Neue", Helvetica, Arial, sans-serif',
'border': '1px solid rgb(220, 230, 250)',
'border-radius': '6px',
'font-size': '14px',
'box-shadow': '0px 1px 3px rgba(0,20,70,.5)'
},
title: {'text-align': 'center', 'padding': '4px', color: 'rgb(115,130,140)', 'font-size': '15px','text-shadow': '0 -1px 0 rgba(255,255,255,.5)'},
item_title: {'text-align': 'right', 'color': 'rgb(80,100,110)','font-size': '14px','text-shadow': '0 -1px 0 rgba(255,255,255,.5)'},
item_value: {'padding': '1px 2px 1px 10px', 'color': 'rgb(90, 95, 85)','font-size': '14px','text-shadow': '0 -1px 0 rgba(255,255,255,.5)'}
});
kodama.tooltip = function() {
var _offsets = {};
var _options = undefined;
var _sourceKey = undefined;
var _sourceData = undefined;
var _formatFunc = null;
var _distance = defaultDistance;
var _gravityDirection = defaultGravityDirection;
var _gravity = defaultGravity;
var _byDirection = defaultByDirection;
var _by = defaultBy;
var _theme = defaultTheme;
var _holdDuration = defaultHoldDuration;
var _fadeInDuration = defaultFadeInDuration;
var _fadeOutDuration = defaultFadeOutDuration;
var _target = defaultTarget;
var attrs = {};
var styles = {};
var _tooltip = function _tooltip(selection) {
selection
.on('mouseover.tooltip', function (d, i) {
_tooltip.show(d, i);
})
.on('mousedown.tooltip', function () {
_tooltip.show(null)
})
.on('mouseup.tooltip', function () {
_tooltip.show(null)
})
.on('mousemove.tooltip', function () {
_tooltip._update();
})
.on('mouseout.tooltip', function () {
_tooltip.show(null)
});
};
_tooltip._build = function _build() {
if (!tipDisplayData) {
_tooltip.fadeOut();
return;
} else {
_tooltip.activateAfter(_holdDuration);
}
holderSel.selectAll('*').remove();
holderSel
.append('div')
.call(multiStyles(_theme.frame))
.datum(tipDisplayData)
.each(function (d) {
var sel = d3.select(this);
if (d.title) {
sel
.append('div')
.call(multiStyles(_theme.title))
.append('span')
.html(d.title);
}
if (d.items) {
var tbody = sel.append('table').append('tbody');
tbody.selectAll('tr').data(d.items)
.enter()
.append('tr')
.each(function (item) {
var tr = d3.select(this);
var titleCell = tr.append('td');
var valueCell = tr.append('td');
titleCell.html(item.title + ':').style(_theme.item_title);
valueCell.html(item.value).style(_theme.item_value);
});
}
});
var xOff = holderSel.node().clientWidth / 2;
var yOff = holderSel.node().clientHeight / 2;
for (var i = -1; i <= 1; i++) {
for (var j = -1; j <= 1; j++) {
var k = i + ":" + j;
_offsets[k] = {left: i * (xOff + _distance) + 'px', top: j * (yOff + _distance) + 'px'};
}
}
_tooltip._update(true);
};
_tooltip._update = function _update(justRebuilt) {
if (!tipDisplayData) return;
function getTargetPos(target, by){
if(!target) return d3.mouse(bodyNode);
var bounds = target.getBoundingClientRect();
var xOff = bounds.width / 2;
var yOff = bounds.height / 2;
return [bounds.left + (by[0] + 1) * xOff, bounds.top + (by[1] + 1) * yOff];
}
var pos = getTargetPos(_target, _by);
var x = pos[0];
var y = pos[1];
var bw = bodyNode.clientWidth;
var bh = bodyNode.clientHeight;
var tw = tipSel.node().clientWidth;
var th = tipSel.node().clientHeight;
var bestKey = null;
var bestDiff = 5;
var xkMax = (x > bw - tw) ? -1 : (x > bw - tw - _distance * 2 ? 0 : 1);
var ykMax = (y > bh - th) ? -1 : (y > bh - th - _distance * 2 ? 0 : 1);
var xkMin = (x < tw) ? 1 : (x < tw + _distance * 2 ? 0 : -1);
var ykMin = (y < th) ? 1 : (y < th + _distance * 2 ? 0 : -1);
for(var xk = xkMin; xk <= xkMax; xk++){
for(var yk = ykMin; yk <= ykMax; yk++){
if(xk === 0 && yk === 0 && _gravity[0] !== 0 && _gravity[1] !== 0) continue;
var diff = Math.abs(xk - _gravity[0]) + Math.abs(yk - _gravity[1]);
if(diff < bestDiff) {
bestKey = [xk, yk];
bestDiff = diff;
}
}
}
bestKey = bestKey || [0, 0];
var left = x - tw / 2;
var top = y - th / 2;
tipSel.call(multiStyles({
left: left + 'px',
top: top + 'px'
}))
var k = bestKey[0] + ':' + bestKey[1];
var moved = Math.max(Math.abs(offsetSwitch[0] - x), Math.abs(offsetSwitch[1] - y));
if (justRebuilt || (k !== offsetKey && moved > _distance)) {
offsetKey = k;
offsetSwitch = pos;
var offsetStyle = _offsets[k];
holderSel
.transition().ease(d3.easeCubicOut).duration(250)
.call(multiStyles(offsetStyle));
}
};
_tooltip.attr = function (_x) {
if (!arguments.length) return attrs;
attrs = _x;
return this;
};
_tooltip.style = _tooltip.css = function (_x) {
if (!arguments.length) return styles;
styles = _x;
return this;
};
_tooltip.fadeIn = function(){
if(fadingState === 'in') {
return;
}
fadingState = 'in';
var progress = tipSel.style('opacity') / 1.0;
var duration = (1.0 - progress) * _fadeInDuration;
tipSel.interrupt().transition().duration(duration).style('opacity', 1);
};
_tooltip.fadeOut = function(){
if(fadingState === 'out') {
return;
}
fadingState = 'out';
var progress = tipSel.style('opacity') / 1.0;
var duration = progress * _fadeOutDuration;
tipSel.transition().delay(50).duration(duration).style('opacity', 0);
};
_tooltip.activate = function(){
activated = true;
_tooltip.fadeIn();
};
_tooltip.deactivate = function(){
lastSourceDataShown = null;
activated = false;
_tooltip.fadeOut();
};
_tooltip.activateAfter = function(){
baseSel.interrupt().transition();
if(!activated) {
baseSel.transition()
.delay(_holdDuration)
.duration(0)
.on('start', _tooltip.activate)
.style('visibility','visible');
} else {
_tooltip.fadeIn();
}
};
_tooltip.theme = function(name){
if(arguments.length === 0) return _theme;
_theme = themesByName[name];
return this;
};
_tooltip.target = function(target){
if(arguments.length === 0) return _target;
var node = target;
while(node && node.length > 0){
node = node[0];
}
_target = node;
return this;
};
_tooltip.holdDuration = function(duration){
if(arguments.length === 0) return _holdDuration;
_holdDuration = duration;
return this;
};
_tooltip.fadeInDuration = function(duration){
if(arguments.length === 0) return _fadeInDuration;
_fadeInDuration = duration;
return this;
};
_tooltip.fadeOutDuration = function(duration){
if(arguments.length === 0) return _fadeOutDuration;
_fadeOutDuration = duration;
return this;
};
_tooltip.distance = function (distance) {
if(arguments.length === 0) return _distance;
_distance = distance;
return this;
};
_tooltip.gravity = function (direction) {
if(arguments.length === 0) return _gravity;
_gravityDirection = direction;
_gravity = resolveGravity(_gravityDirection);
return this;
};
_tooltip.by = function (direction) {
if(arguments.length === 0) return _by;
_byDirection = direction;
_by = resolveGravity(_byDirection);
return this;
};
_tooltip.options = function(options) {
if(arguments.length === 0) return _options;
if(!options) return this;
if(options.theme){
_tooltip.theme(options.theme);
if(_theme.options){
for(var prop2 in _theme.options){
if(!options.hasOwnProperty(prop2))
options[prop2] = _theme.options[prop2];
}
}
}
for(var prop in options){
if(validOptions.indexOf(prop)===-1) continue;
_tooltip[prop](options[prop]);
}
_options = options;
return this;
};
_tooltip.format = _tooltip.prep = _tooltip.data = function(formatFunc) {
_formatFunc = formatFunc;
return this;
};
_tooltip.show = function(sourceData, sourceKey){
_sourceData = sourceData;
_sourceKey = sourceKey;
if(_sourceData !== lastSourceDataShown || _formatFunc !== lastFormatFuncShown){
lastFormatFuncShown = _formatFunc;
lastSourceDataShown = _sourceData;
tipDisplayData = (_formatFunc && _sourceData) ? _formatFunc(_sourceData, _sourceKey) : _sourceData;
_tooltip.options(tipDisplayData);
_tooltip._build();
}
_tooltip._update();
};
return _tooltip;
};
var selector = typeof jQuery !== 'undefined' && jQuery !== null ? jQuery : null;
selector = selector || (typeof Zepto !== 'undefined' && Zepto !== null ? Zepto : null);
if(selector) {
selector.fn.kodama = $.fn.kodama_tooltip = $.fn.bamboo = $.fn.kodama || function(tooltipData){
var self = this;
var els = self.toArray();
var arr = d3.range(els.length).map(function(){return tooltipData;});
d3.selectAll(els).data(arr).call(d3.kodama.tooltip());
return this;
};
}
return kodama;
}));