Skip to content

Instantly share code, notes, and snippets.

@fogonwater
Last active July 20, 2016 02:27
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 fogonwater/e5aefdd8dbfdf91dfa995bb38b486067 to your computer and use it in GitHub Desktop.
Save fogonwater/e5aefdd8dbfdf91dfa995bb38b486067 to your computer and use it in GitHub Desktop.

My d3.js starter template.

This is the little bit of scaffolding I typically use when starting a d3.js project. I typically separate the Javascript from the HTML, but it's included here for convenience. In addition to d3.js v4, this example uses:

prop1 prop2 prop3
10.0 8.04 apple
8.0 6.95 banana
13.0 7.58 banana
9.0 8.81 apple
11.0 8.33 cranberry
14.0 9.96 banana
6.0 7.24 apple
4.0 4.26 cranberry
12.0 10.84 apple
7.0 4.82 banana
5.0 5.68 cranberry
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #444;
shape-rendering: crispEdges;
}
.dot {
stroke: #444;
}
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;
}
</style>
<body>
<div id="chart"></div>
<div class='tooltip'></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src='jetpack.js'></script>
<script src='starterkit.js'></script>
<script>
var q = d3.queue()
.defer(d3.csv, 'data.csv')
//.defer(d3.json, 'data.json')
.await(visualize);
var c = d3.conventions({
parentSel: d3.select('#chart'),
width: 600,
height: 300
});
function visualize(errors, data) {
data.forEach(function(d) {
d.prop1 = +d.prop1;
d.prop2 = +d.prop2;
//console.log(d);
});
c.x.domain(d3.extent(data, ƒ('prop1'))).nice();
c.y.domain(d3.extent(data, ƒ('prop2'))).nice();
c.drawAxis();
c.svg.dataAppend(data, "circle.dot")
.attr("r", 6.2)
.attr("cx", ƒ('prop1', c.x))
.attr("cy", ƒ('prop2', c.y))
.style("fill", ƒ('prop3', c.color))
.call(d3.attachTooltip);
};
</script>
(function(root, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory(require('d3'));
} else if (typeof define === 'function' && define.amd) {
define(['d3'], factory);
} else {
root.d3 = factory(root.d3);
}
}(this, function(d3) {
d3.selection.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy.call(this, d,i) : xy]+')';
});
};
d3.transition.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy.call(this, 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 = function(name) {
var n = d3_parse_attributes(name), s;
name = n.attr ? n.tag : name;
name = d3_selection_creator(name);
s = this.select(function() {
return this.appendChild(name.apply(this, arguments));
});
//attrs not provided by default in v4
for (var name in n.attr) { s.attr(name, n.attr[name]) }
return s;
};
d3.selection.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);
});
//attrs not provided by default in v4
for (var name in n.attr) { s.attr(name, n.attr[name]) }
return s;
};
//no selection.enter in v4
if (d3.selection.enter){
d3.selection.enter.prototype.append = d3.selection.prototype.append
d3.selection.enter.prototype.insert = d3.selection.prototype.insert
}
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) {
var qualify = d3.namespace || d3.ns.qualify //v4 API change
return typeof name === "function" ? name : (name = 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 (typeof window !== 'undefined' && !window.hasOwnProperty('ƒ')) window.ƒ = d3.f;
// this tweak allows setting a listener for multiple events, jquery style
var d3_selection_on = d3.selection.prototype.on;
d3.selection.prototype.on = function(type, listener, capture) {
if (typeof type == 'string' && type.indexOf(' ') > -1) {
type = type.split(' ');
for (var i = 0; i<type.length; i++) {
d3_selection_on.apply(this, [type[i], listener, capture]);
}
} else {
d3_selection_on.apply(this, [type, listener, capture]);
}
return this;
};
// for everyone's sake, let's add prop as alias for property
d3.selection.prototype.prop = d3.selection.prototype.property;
return d3;
}));
(function() {
function factory(d3){
d3.conventions = function(c){
c = c || {}
c.margin = c.margin || {top: 20, right: 20, bottom: 20, left: 80}
c.width = c.width || c.totalWidth - c.margin.left - c.margin.right || 900
c.height = c.height || c.totalHeight - c.margin.top - c.margin.bottom || 460
c.totalWidth = c.width + c.margin.left + c.margin.right
c.totalHeight = c.height + c.margin.top + c.margin.bottom
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.scaleOrdinal().range(d3.schemeCategory10)
c.x = c.x || d3.scaleLinear().range([0, c.width])
c.y = c.y || d3.scaleLinear().range([c.height, 0])
c.rScale = c.rScale || d3.scaleSqrt().range([5, 20])
c.line = c.line || d3.line()
c.xAxis = c.xAxis || d3.axisBottom().scale(c.x)
c.yAxis = c.yAxis || d3.axisLeft().scale(c.y)
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){
if (!sel.size()) return
sel
.on('mouseover.attachTooltip', ttDisplay)
.on('mousemove.attachTooltip', ttMove)
.on('mouseout.attachTooltip', ttHide)
.on('click.attachTooltip', function(d){ console.log(d) })
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(20, (x-nBB.width/2)), window.innerWidth - nBB.width - 20)+"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)
}
}
if (typeof d3 === 'object' && d3.version) factory(d3);
else if (typeof define === 'function' && define.amd) {
define(['lib/d3'], factory);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment