Skip to content

Instantly share code, notes, and snippets.

@wheaties
Last active August 29, 2015 13:56
Show Gist options
  • Select an option

  • Save wheaties/9157023 to your computer and use it in GitHub Desktop.

Select an option

Save wheaties/9157023 to your computer and use it in GitHub Desktop.
Playing around with making a reusable factory for sunburst d3 charts, inspired by a Backbone like View
(function(window, d3){
var gb = window.gb = window.gb || {};
gb.arc = gb.arc || {};
gb.arc.radialScale = function(r, e, o){
var extent = e || 2 * Math.PI;
var offset = o || 0;
var radius = r || 350;
var rScale = d3.scale.sqrt()
.range([0, radius]);
var thetaScale = d3.scale.linear()
.range([0, extent]);
var dispatch = d3.dispatch('extent', 'offset', 'radius');
var arc = d3.svg.arc()
.startAngle(function(d){ return Math.max(offset, Math.min(offset + extent, thetaScale(d.x) + offset)); })
.endAngle(function(d){ return Math.max(offset, Math.min(offset + extent, thetaScale(d.x + d.dx) + offset)); })
.innerRadius(function(d){ return Math.max(0, rScale(d.y)); })
.outerRadius(function(d){ return Math.max(0, rScale(d.y + d.dy)); });
var out = function(d, i){
return arc(d, i);
}
out.extent = function(value){
if(!!value && value > 0){
extent = value;
thetaScale.range([0, value]);
dispatch.extent(value);
return this;
}
else return extent;
}
out.offset = function(value){
if(!!value){
offset = value;
dispatch.offset(value);
return this;
}
else return offset;
}
out.radius = function(value){
if(!!value && radius > 0){
radius = value;
rScale.range([0, value]);
dispatch.radius(value);
return this;
}
else return radius;
}
out.on = function(name, f){
if(!!name && !!f){
dispatch.on(name, f);
return this;
}
else return dispatch.on(name);
}
out.off = function(name){
if(!!name){
dispatch.on(name, null);
return this;
}
}
out.arcTween = function(d, r){
var thetaInterp = d3.interpolate(thetaScale.domain(), [d.x, d.x + d.dx]),
domainInterp = d3.interpolate(rScale.domain(), [d.y, 1]),
rangeInterp = d3.interpolate(rScale.range(), [d.y ? (r || 20) : 0, radius]);
var shift = function(x){
thetaScale.domain(thetaInterp(x));
rScale.domain(domainInterp(x))
.range(rangeInterp(x));
}
return function(p, i){
return i ?
function(t){ return arc(p); } :
function(t){
shift(t);
return arc(p);
}
}
}
out.centroid = function(d, i){
return arc.centroid(d, i);
}
return out;
}
})(window, d3)
(function(window, d3){
'use strict';
var gb = window.gb = window.gb || {};
gb.color = gb.color || {}
//TODO: is this reasonable? Does it depend on the size of the bands instead of the depth of the band?
var radialScale = function(color){
return function(d){ return color(d.y); }
}
gb.color.constantRadial20 = function(){
return radialScale(d3.scale.category20());
}
gb.color.constantRadial20c = function(){
return radialScale(d3.scale.category20c());
}
gb.sunburst = function(el){
var dispatch = d3.dispatch('render', 'radius', 'partition', 'arc', 'style', 'attr');
var svg = d3.select(el).append('svg');
var g = svg.append('g');
var partition = d3.layout.partition()
.sort(null)
.value(d3.functor(1));
var chart = function(data){
return chart.render(data);
}
//Reset the 'children' function of the partition layout. Causes a 'partition' event to be fired.
chart.children = function(f){
if(typeof(f) == 'string') f = function(d){ return d[f]; }
if(typeof(f) != 'function') return;
partition.children(f);
dispatch.partition('children', f);
return this;
}
//Reset the 'sort' function of the partition layout. Accepts 'null' argument. Causes a 'partition' event to be fired.
chart.sort = function(f){
if(typeof(f) == 'string') f = function(d){ return d[f]; }
if(!!f && typeof(f) != 'function') return;
partition.sort(f);
dispatch.partition('sort', f);
return this;
}
chart.value = function(f){
if(typeof(f) == 'string') f = function(d){ return d[f]; }
if(typeof(f) == 'number') f = d3.functor(f);
if(typeof(f) != 'function') return;
partition.value(f);
dispatch.partition('value', f);
return this;
}
var arc = gb.arc.radialScale();
chart.arc = function(f){
if(typeof(f) != 'function') return arc;
arc = f;
dispatch.arc(f);
return this;
}
var radius = 350;
chart.radius = function(r){
if(typeof(r) != 'number') return radius;
radius = r;
dispatch.radius(r);
return this;
}
//TODO: Should be just an attribute of the 'chart.' Also need to be able to handle an object in 'name.'
var style = function(enter){
for(var name in style){
enter.style(name, style[name]);
}
}
style.stroke = '#fff';
style.fill = gb.color.constantRadial20c();
style['fill-rule'] = 'evenodd';
chart.style = function(name, f){
if(!arguments.length) return;
if(!f) return style[name];
style[name] = f;
dispatch.style(name, f);
return this;
}
var attr = function(enter){
for(var name in attr){
enter.attr(name, attr[name]);
}
}
chart.attr = function(name, f){
if(!arguments.length || name == 'd') return;
if(!f) return attr[name];
attr[name] = f;
dispatch.attr(name, f);
return this;
}
chart.dispatchOn = function(name, f){
if(!arguments.length) return;
if(!f) return display.on(name);
dispatch.on(name, f);
return this;
}
chart.dispatchOff = function(name){
if(!!name){
dispatch.on(name, null);
return this;
}
}
var on = function(enter){
for(var name in on){
enter.on(name, on[name]);
}
}
chart.on = function(name, f){
if(!arguments.length) return;
if(!f) return on[name];
on[name] = f;
g.selectAll('path')
.on(name, f); //TODO: make sure this works
return this;
}
chart.off = function(name){
var paths = g.selectAll('path');
if(!name){
for(var name in on){
paths.on(name, null);
on[name] = undefined;
}
}
else{
paths.on(name, null);
on[name] = undefined;
}
return this;
}
chart.render = function(data){
svg.attr('width', radius * 2)
.attr('height', radius * 2);
g.attr('transform', 'translate(' + radius + ', ' + radius + ')');
var paths = g.datum(data)
.selectAll('path')
.data(partition);
paths.enter()
.append('path')
.attr('d', arc);
attr(paths);
style(paths);
on(paths);
paths.exit()
.remove();
dispatch.render();
return this;
}
chart.transitionTo = function(f){
this.value(f);
var tween = function(d){
var interpolate = d3.interpolate({x: d.x0, dx: d.dx0}, d);
return function(s){
return arc(interpolate(s));
}
}
var backup = function(d){
d.x0 = d.x;
d.dx0 = d.dx;
}
g.selectAll('path')
.each(backup)
.data(partition)
.transition()
.duration(750)
.attrTween('d',tween);
return this;
}
return chart;
}
})(window, d3);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment