Skip to content

Instantly share code, notes, and snippets.

@Golodhros
Last active May 17, 2016 15:43
Show Gist options
  • Save Golodhros/dfe7c0c8be07a461e6ba to your computer and use it in GitHub Desktop.
Save Golodhros/dfe7c0c8be07a461e6ba to your computer and use it in GitHub Desktop.
TDD D3 Template

Gist to serve as template for future TDD D3 blocks

var graphs = graphs || {};
graphs.dataManager = function module() {
var exports = {},
dispatch = d3.dispatch('dataReady', 'dataLoading', 'dataError'),
data;
d3.rebind(exports, dispatch, 'on');
exports.loadJsonData = function(_file, _cleaningFn) {
var loadJson = d3.json(_file);
loadJson.on('progress', function(){
dispatch.dataLoading(d3.event.loaded);
});
loadJson.get(function (_err, _response){
if(!_err){
_response.data.forEach(function(d){
_cleaningFn(d);
});
data = _response.data;
dispatch.dataReady(_response.data);
}else{
dispatch.dataError(_err.statusText);
}
});
};
exports.loadTsvData = function(_file, _cleaningFn) {
var loadTsv = d3.tsv(_file);
loadTsv.on('progress', function() {
dispatch.dataLoading(d3.event.loaded);
});
loadTsv.get(function (_err, _response) {
if(!_err){
_response.forEach(function(d){
_cleaningFn(d);
});
data = _response;
dispatch.dataReady(_response);
}else{
dispatch.dataError(_err.statusText);
}
});
};
// If we need more types of data geoJSON, csv, etc. we will need
// to create methods for them
exports.getCleanedData = function(){
return data;
};
return exports;
};
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link type="text/css" rel="stylesheet" href="style.css"/>
</head>
<body>
<h2 class="block-title">TDD Template</h2>
<div class="graph"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="dataManager.js"></script>
<script src="src.js"></script>
<script type="text/javascript">
// Code that instantiates the graph and uses the data manager to load the data
var app = {
// D3 Reusable API Chart
graph: {
dataManager: null,
config: {
margin : {
top : 20,
bottom: 30,
right : 20,
left : 40
},
aspectWidth: 13,
aspectHeight: 4,
animation: 'linear',
dataURL: 'data.tsv'
},
init: function(ele){
this.$el = ele;
this.requestNewData();
this.addEvents();
},
addEvents: function(){
//Callback triggered by browser
window.onresize = $.proxy(this.drawGraph, this);
},
calculateRatioHeight: function(width) {
var config = this.config;
return Math.ceil((width * config.aspectHeight) / config.aspectWidth);
},
dataCleaningFunction: function(d){
d.frequency = +d.frequency;
d.letter = d.letter;
},
drawGraph: function(){
var config = this.config,
width = this.$el.width(),
height = this.calculateRatioHeight(width);
this.resetGraph();
this.chart = graphs.chart()
.width(width).height(height).margin(config.margin);
this.container = d3.select(this.$el[0])
.datum(this.data)
.call(this.chart);
},
handleReceivedData: function(result){
this.data = result;
this.drawGraph();
},
requestNewData: function(el){
this.dataManager = graphs.dataManager();
this.dataManager.on('dataError', function(errorMsg){
console.log('error:', errorMsg);
});
this.dataManager.on('dataReady', $.proxy(this.handleReceivedData, this));
this.dataManager.loadTsvData(this.config.dataURL, this.dataCleaningFunction);
},
resetGraph: function(){
this.$el.find('svg').remove();
}
}
};
$(function(){
app.graph.init($('.graph'));
});
</script>
</body>
var graphs = graphs || {};
graphs.chart = function module(){
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960,
height = 500,
data,
chartW, chartH,
xScale, yScale,
xAxis, yAxis;
var svg;
function buildContainerGroups(){
var container = svg.append("g").classed("container-group", true);
container.append("g").classed("chart-group", true);
container.append("g").classed("x-axis-group", true);
container.append("g").classed("y-axis-group", true);
}
function buildScales(){
xScale = d3.scale.ordinal()
.domain(data.map(function(d) { return d.letter; }))
.rangeRoundBands([0, chartW], 0.1);
yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.frequency; })])
.range([chartH, 0]);
}
function buildAxis(){
xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10, "%");
}
function drawAxis(){
svg.select('.x-axis-group')
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + chartH + ")")
.call(xAxis);
svg.select(".y-axis-group")
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
}
function drawBars(){
// Setup the enter, exit and update of the actual bars in the chart.
// Select the bars, and bind the data to the .bar elements.
var bars = svg.select('.chart-group').selectAll(".bar")
.data(data);
// If there aren't any bars create them
bars.enter().append('rect')
.attr("class", "bar")
.attr("x", function(d) { return xScale(d.letter); })
.attr("width", xScale.rangeBand())
.attr("y", function(d) { return yScale(d.frequency); })
.attr("height", function(d) { return chartH - yScale(d.frequency); });
}
function exports(_selection){
_selection.each(function(_data){
chartW = width - margin.left - margin.right;
chartH = height - margin.top - margin.bottom;
data = _data;
buildScales();
buildAxis();
if (!svg) {
svg = d3.select(this)
.append('svg')
.classed('bar-chart', true);
}
svg.attr({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
});
buildContainerGroups();
drawBars();
drawAxis();
});
}
exports.margin = function(_x) {
if (!arguments.length) return margin;
margin = _x;
return this;
};
exports.width = function(_x) {
if (!arguments.length) return width;
width = _x;
return this;
};
exports.height = function(_x) {
if (!arguments.length) return height;
height = _x;
return this;
};
return exports;
};
// Simple tests for the bar chart
describe('Reusable barChart Test Suite', function() {
var barChart, dataset, containerFixture, f;
beforeEach(function() {
dataset = [
{
letter: 'A',
frequency: .08167
},
{
letter: 'B',
frequency: .01492
},
{
letter: 'C',
frequency: .02782
},
{
letter: 'D',
frequency: .04253
},
{
letter: 'E',
frequency: .12702
},
{
letter: 'F',
frequency: .02288
},
{
letter: 'G',
frequency: .02015
},
{
letter: 'H',
frequency: .06094
},
{
letter: 'I',
frequency: .06966
},
{
letter: 'J',
frequency: .00153
},
{
letter: 'K',
frequency: .00772
},
{
letter: 'L',
frequency: .04025
},
{
letter: 'M',
frequency: .02406
},
{
letter: 'N',
frequency: .06749
},
{
letter: 'O',
frequency: .07507
},
{
letter: 'P',
frequency: .01929
},
{
letter: 'Q',
frequency: .00095
},
{
letter: 'R',
frequency: .05987
},
{
letter: 'S',
frequency: .06327
},
{
letter: 'T',
frequency: .09056
},
{
letter: 'U',
frequency: .02758
},
{
letter: 'V',
frequency: .00978
},
{
letter: 'W',
frequency: .02360
},
{
letter: 'X',
frequency: .00150
},
{
letter: 'Y',
frequency: .01974
},
{
letter: 'Z',
frequency: .00074
}
];
barChart = graphs.barChart();
$('body').append($('<div class="test-container"></div>'));
containerFixture = d3.select('.test-container');
containerFixture.datum(dataset).call(barChart);
});
afterEach(function() {
containerFixture.remove();
});
it('should render a chart with minimal requirements', function() {
expect(containerFixture.select('.chart')).toBeDefined(1);
});
it('should render container, axis and chart groups', function() {
expect(containerFixture.select('g.container-group')[0][0]).not.toBeNull();
expect(containerFixture.select('g.chart-group')[0][0]).not.toBeNull();
expect(containerFixture.select('g.x-axis-group')[0][0]).not.toBeNull();
expect(containerFixture.select('g.y-axis-group')[0][0]).not.toBeNull();
});
it('should render an X and Y axis', function() {
expect(containerFixture.select('.x.axis')[0][0]).not.toBeNull();
expect(containerFixture.select('.y.axis')[0][0]).not.toBeNull();
});
it('should render a bar for each data entry', function() {
var numBars = dataset.length;
expect(containerFixture.selectAll('.bar')[0].length).toEqual(numBars);
});
it('should provide margin getter and setter', function() {
var defaultMargin = barChart.margin(),
testMargin = {top: 4, right: 4, bottom: 4, left: 4},
newMargin;
barChart.margin(testMargin);
newMargin = barChart.margin();
expect(defaultMargin).not.toBe(testMargin);
expect(newMargin).toBe(testMargin);
});
it('should provide width getter and setter', function() {
var defaultWidth = barChart.width(),
testWidth = 200,
newWidth;
barChart.width(testWidth);
newWidth = barChart.width();
expect(defaultWidth).not.toBe(testWidth);
expect(newWidth).toBe(testWidth);
});
it('should provide height getter and setter', function() {
var defaultHeight = barChart.height(),
testHeight = 200,
newHeight;
barChart.height(testHeight);
newHeight = barChart.height();
expect(defaultHeight).not.toBe(testHeight);
expect(newHeight).toBe(testHeight);
});
});
@import url("//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,700");
body {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Helvetica, Arial, sans-serif;
}
.block-title {
color: #222;
font-size: 44px;
font-style: normal;
font-weight: 300;
text-rendering: optimizelegibility;
}
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.0/jasmine.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.0/jasmine.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.0/jasmine-html.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.0/boot.js"></script>
<!-- Favicon -->
<link rel="shortcut icon" type="image/png" href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine_favicon.png" />
<!-- End Favicon -->
<!-- source files... -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="dataManager.js"></script>
<script src="src.js"></script>
<!-- spec files... -->
<script src="src.spec.js"></script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment