Skip to content

Instantly share code, notes, and snippets.

@1wheel
Last active August 22, 2016 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 1wheel/52da010eeef9cc5aca2e to your computer and use it in GitHub Desktop.
Save 1wheel/52da010eeef9cc5aca2e to your computer and use it in GitHub Desktop.
expandable-rectanges
(function() {
function jetpack(d3) {
d3.selection.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
});
};
d3.transition.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
});
};
d3.selection.prototype.tspans = function(lines, lh) {
return this.selectAll('tspan')
.data(lines)
.enter()
.append('tspan')
.text(function(d) { return d; })
.attr('x', 0)
.attr('dy', function(d,i) { return i ? lh || 15 : 0; });
};
d3.selection.prototype.append =
d3.selection.enter.prototype.append = function(name) {
var n = d3_parse_attributes(name), s;
//console.log(name, n);
name = n.attr ? n.tag : name;
name = d3_selection_creator(name);
s = this.select(function() {
return this.appendChild(name.apply(this, arguments));
});
return n.attr ? s.attr(n.attr) : s;
};
d3.selection.prototype.insert =
d3.selection.enter.prototype.insert = function(name, before) {
var n = d3_parse_attributes(name), s;
name = n.attr ? n.tag : name;
name = d3_selection_creator(name);
before = d3_selection_selector(before);
s = this.select(function() {
return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
});
return n.attr ? s.attr(n.attr) : s;
};
var d3_parse_attributes_regex = /([\.#])/g;
function d3_parse_attributes(name) {
if (typeof name === "string") {
var attr = {},
parts = name.split(d3_parse_attributes_regex), p;
name = parts.shift();
while ((p = parts.shift())) {
if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift();
else if (p == '#') attr.id = parts.shift();
}
return attr.id || attr['class'] ? { tag: name, attr: attr } : name;
}
return name;
}
function d3_selection_creator(name) {
return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
return this.ownerDocument.createElementNS(name.space, name.local);
} : function() {
return this.ownerDocument.createElementNS(this.namespaceURI, name);
};
}
function d3_selection_selector(selector) {
return typeof selector === "function" ? selector : function() {
return this.querySelector(selector);
};
}
d3.wordwrap = function(line, maxCharactersPerLine) {
var w = line.split(' '),
lines = [],
words = [],
maxChars = maxCharactersPerLine || 40,
l = 0;
w.forEach(function(d) {
if (l+d.length > maxChars) {
lines.push(words.join(' '));
words.length = 0;
l = 0;
}
l += d.length;
words.push(d);
});
if (words.length) {
lines.push(words.join(' '));
}
return lines;
};
d3.ascendingKey = function(key) {
return typeof key == 'function' ? function (a, b) {
return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN;
} : function (a, b) {
return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN;
};
};
d3.descendingKey = function(key) {
return typeof key == 'function' ? function (a, b) {
return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN;
} : function (a, b) {
return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN;
};
};
d3.f = function(){
var functions = arguments;
//convert all string arguments into field accessors
var i = 0, l = functions.length;
while (i < l) {
if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){
functions[i] = (function(str){ return function(d){ return d[str] }; })(functions[i])
}
i++;
}
//return composition of functions
return function(d) {
var i=0, l = functions.length;
while (i++ < l) d = functions[i-1].call(this, d);
return d;
};
};
// store d3.f as convenient unicode character function (alt-f on macs)
if (!window.hasOwnProperty('ƒ')) window.ƒ = d3.f;
}
if (typeof d3 === 'object' && d3.version) jetpack(d3);
else if (typeof define === 'function' && define.amd) {
define(['d3'], jetpack);
}
})();
d3.conventions = function(c){
c = c || {}
c.width = c.width || 900
c.height = c.height || 500
c.margin = c.margin || {top: 20, right: 20, bottom: 20, left: 25}
c.parentSel = c.parentSel || d3.select('body')
c.svg = c.svg || c.parentSel.append("svg")
.attr("width", c.width + c.margin.left + c.margin.right)
.attr("height", c.height + c.margin.top + c.margin.bottom)
.append("g")
.attr("transform", "translate(" + c.margin.left + "," + c.margin.top + ")")
c.color = c.color || d3.scale.category10()
c.x = c.x || d3.scale.linear().range([0, c.width])
c.y = c.y || d3.scale.linear().range([c.height, 0])
c.rScale = c.rScale || d3.scale.sqrt().range([5, 20])
c.line = c.line || d3.svg.line()
c.xAxis = c.xAxis || d3.svg.axis().scale(c.x).orient("bottom");
c.yAxis = c.yAxis || d3.svg.axis().scale(c.y).orient("left")
c.drawAxis = function(){
c.svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + c.height + ")")
.call(c.xAxis);
c.svg.append("g")
.attr("class", "y axis")
.call(c.yAxis);
}
return c
}
d3.attachTooltip = function(sel, fieldFns){
sel
.on('mouseover.attachTooltip', ttDisplay)
.on('mousemove.attachTooltip', ttMove)
.on('mouseout.attachTooltip', ttHide)
var d = sel.datum()
fieldFns = fieldFns || d3.keys(d)
.filter(function(str){
return (typeof d[str] != 'object') && (d[str] != 'array')
})
.map(function(str){
return function(d){ return str + ': <b>' + d[str] + '</b>'} })
function ttDisplay(d){
d3.select('.tooltip')
.classed('tooltip-hidden', false)
.html('')
.dataAppend(fieldFns, 'div')
.html(function(fn){ return fn(d) })
d3.select(this).classed('tooltipped', true)
}
function ttMove(d){
var tt = d3.select('.tooltip')
if (!tt.size()) return
var e = d3.event,
x = e.clientX,
y = e.clientY,
doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop;
n = tt.node(),
nBB = n.getBoundingClientRect()
tt.style('top', (y+doctop-nBB.height-18)+"px");
tt.style('left', Math.min(Math.max(0, (x-nBB.width/2)), window.innerWidth - nBB.width)+"px");
}
function ttHide(d){
d3.select('.tooltip').classed('tooltip-hidden', true);
d3.selectAll('.tooltipped').classed('tooltipped', false)
}
}
d3.selection.prototype.dataAppend = function(data, name){
return this.selectAll(name)
.data(data).enter()
.append(name)
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
div.tooltip {
top: -1000px;
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, .90);
border: 1px solid lightgray;
pointer-events: none;
}
.tooltip-hidden{
opacity: 0;
}
rect.category{
stroke: white;
}
rect.subcategory{
stroke: steelblue;
}
path.dash{
stroke: black;
stroke-dasharray: 10 10 5 5;
}
</style>
<body>
<div id='graph'></div>
<div class='tooltip'></div>
</body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
<script src='d3-jetpack.js'></script>
<script src='d3-starterkit.js'></script>
<script src='script.js'></script>
// d3.tsv('data.tsv', function(data){
// c = d3.conventions({parentSel: d3.select('#graph')})
// c.x.domain(d3.extent(data, ƒ('sepalWidth')) ).nice()
// c.y.domain(d3.extent(data, ƒ('sepalLength'))).nice()
// c.drawAxis()
// c.svg.dataAppend(data, 'circle')
// .attr('cx', ƒ('sepalWidth', c.x))
// .attr('cy', ƒ('sepalLength',c.y))
// .attr('fill', ƒ('species', c.color))
// .attr({r: 5, stroke: '#000'})
// .call(d3.attachTooltip)
// var legend = c.svg.dataAppend(c.color.domain(), 'g.legend')
// .translate(function(d, i){ return [0, i*20] })
// legend.append('rect')
// .attr({x: c.width - 18, width: 18, height: 18})
// .style('fill', c.color)
// legend.append('text')
// .attr({x: c.width - 24, y: 9, dy: '.33em', 'text-anchor': 'end'})
// .text(ƒ())
// })
var height = 600,
width = 300
var categories = generateCatories(100)
categories.forEach(function(d){
d.subcategories = generateCatories(d.value)
})
var y = categories.y
var svg = d3.select('body')
.append('svg')
.attr({width: width, height: height})
svg.dataAppend(categories, 'rect.category')
.attr('height', ƒ('value', y))
.attr('y', ƒ('prevValue', y))
.attr('width', 100)
.on('click', expand)
var topPath = svg.append('path.dash')
var botPath = svg.append('path.dash')
var sideBoxes = svg.append('g')
function expand(d){
topPath
.attr('d', ['M', [100, y(d.prevValue)], 'L', [100, y(d.prevValue)]].join(''))
.transition().duration(700).ease('linear')
.attr('d', ['M', [100, y(d.prevValue)], 'L', [200, 0]].join(''))
botPath
.attr('d', ['M', [100, y(d.prevValue + d.value)], 'L', [100, y(d.prevValue + d.value)]].join(''))
.transition().duration(700).ease('linear')
.attr('d', ['M', [100, y(d.prevValue + d.value)], 'L', [200, height]].join(''))
//TODO - use a transition before removing
sideBoxes.selectAll('*').remove()
sideBoxes.dataAppend(d.subcategories, 'rect.subcategory')
.attr('width', 100)
.attr('x', 000)
.attr('y', function(e){ return y(d.prevValue + e.prevValue) })
.attr('height', ƒ('value', y))
.transition().duration(700).ease('linear')
.attr('x', 200)
.attr('width', 100)
.attr('y', ƒ('prevValue', d.subcategories.y))
.attr('height', ƒ('value', d.subcategories.y))
}
function generateCatories(normalize){
var categories = d3.range(10).map(function(d){
return {
name: 'cat ' + d,
value: Math.random()
}
})
var totalValue = d3.sum(categories, ƒ('value'))
categories.forEach(function(d){
d.value = d.value*normalize/totalValue
})
categories = _.sortBy(categories, 'value')
var curValue = 0
categories.forEach(function(d){
d.prevValue = curValue
curValue += d.value
})
var y = d3.scale.linear()
.domain([0, normalize])
.range([0, height])
categories.y = y
return categories
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment