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