Better d3 charts with tdd

106
Better D3 Charts with TDD Slides: Code: http://golodhros.github.io/ https://github.com/Golodhros/d3-meetup

Transcript of Better d3 charts with tdd

Page 1: Better d3 charts with tdd

Better D3 Charts with TDD

Slides: Code:

http://golodhros.github.io/https://github.com/Golodhros/d3-meetup

Page 2: Better d3 charts with tdd

Marcos Iglesias

Page 3: Better d3 charts with tdd

El Bierzo

Page 5: Better d3 charts with tdd

Next upPresentationLive codingQ&A

Page 6: Better d3 charts with tdd
Page 7: Better d3 charts with tdd
Page 8: Better d3 charts with tdd

D3 introductionData-Driven DocumentsJavaScript library to manipulate data baseddocumentsOpen web standards (SVG, HTML and CSS)Allows interactions with your graphs

Page 9: Better d3 charts with tdd

How does it work?Loads dataBinds data to elementsTransforms those elementsTransitions between statesExample

Page 10: Better d3 charts with tdd

D3 NicetiesBased on attaching data to the DOMStyling of elements with CSSTransitions and animations baked inTotal control over our graphsAmazing communityDecent amount of publications

Page 11: Better d3 charts with tdd

WHAT CAN YOU DO WITHD3?

Page 12: Better d3 charts with tdd

Bar charts

Page 13: Better d3 charts with tdd

Pie charts

Page 18: Better d3 charts with tdd

Algorithm visualization

Page 19: Better d3 charts with tdd

Artistic visualizations

Page 21: Better d3 charts with tdd

CONTRACTING STORY

Page 22: Better d3 charts with tdd

Marketing guy: Hey, I saw this nice chart,could we do something like that?

Page 23: Better d3 charts with tdd
Page 24: Better d3 charts with tdd

HE LOVED IT!

Page 25: Better d3 charts with tdd

USUAL WORKFLOW

Page 26: Better d3 charts with tdd
Page 27: Better d3 charts with tdd

Search for an example

Page 28: Better d3 charts with tdd

READ AND ADAPT CODE

Page 29: Better d3 charts with tdd

ADD/REMOVE FEATURES

Page 30: Better d3 charts with tdd

Polish it up

Page 31: Better d3 charts with tdd

Usual workflowIdea or requirementSearch for an exampleAdapt the codeAdd/remove featuresPolish it up

Page 32: Better d3 charts with tdd

THE STANDARD WAY

Page 33: Better d3 charts with tdd

Code example

by Mike BostockBar chart example

Page 34: Better d3 charts with tdd

Creating containervar margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;

var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top +

Reference: Margin Convention

Page 35: Better d3 charts with tdd

Setting up scales and axesvar x = d3.scale.ordinal() .rangeRoundBands([0, width], .1);

var y = d3.scale.linear() .range([height, 0]);

var xAxis = d3.svg.axis() .scale(x) .orient("bottom");

var yAxis = d3.svg.axis() .scale(y) .orient("left") .ticks(10, "%");

Reference: Scales tutorial

Page 36: Better d3 charts with tdd

Loading data// Loads Data d3.tsv("data.tsv", type, function(error, data) { if (error) throw error; // Chart Code here });

// Cleans Data function type(d) { d.frequency = +d.frequency; return d; }

Page 37: Better d3 charts with tdd

Drawing axes// Rest of the scales x.domain(data.map(function(d) { return d.letter; })); y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

// Draws X axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis);

// Draws Y axis svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em")

Page 38: Better d3 charts with tdd

Drawing barssvg.selectAll(".bar") .data(data) .enter().append("rect") .attr("class", "bar") .attr("x", function(d) { return x(d.letter); }) .attr("width", x.rangeBand()) .attr("y", function(d) { return y(d.frequency); }) .attr("height", function(d) { return height - y(d.frequency); });

Page 39: Better d3 charts with tdd

Output

Page 40: Better d3 charts with tdd

Standard D3: drawbacksMonolithic functionsChained method callsHard to change codeImpossible to reuseDelicate

Page 41: Better d3 charts with tdd

STORY CONTINUES...

Page 42: Better d3 charts with tdd
Page 43: Better d3 charts with tdd

Marketing guy: What if we change this thinghere...

Page 44: Better d3 charts with tdd
Page 45: Better d3 charts with tdd
Page 46: Better d3 charts with tdd

TRIAL AND ERROR

Page 47: Better d3 charts with tdd
Page 48: Better d3 charts with tdd

Done!

Page 49: Better d3 charts with tdd

M-guy: nice, let’s change this other thing!

Page 50: Better d3 charts with tdd
Page 51: Better d3 charts with tdd
Page 52: Better d3 charts with tdd

Done!

Page 53: Better d3 charts with tdd

M-guy: Great! I love it so much I want it on theproduct!

Page 54: Better d3 charts with tdd

M-guy: So good you have it almost ready,right?

Page 55: Better d3 charts with tdd
Page 56: Better d3 charts with tdd
Page 57: Better d3 charts with tdd
Page 58: Better d3 charts with tdd
Page 59: Better d3 charts with tdd
Page 60: Better d3 charts with tdd

I WAS HATING MYSELF!

Page 61: Better d3 charts with tdd

Possible outcomesYou take it throughYou dump it and start all over againYou avoid refactoring

Page 62: Better d3 charts with tdd
Page 63: Better d3 charts with tdd

What if you could work with charts the sameway you work with the other code?

Page 64: Better d3 charts with tdd

REUSABLE CHART API

Page 65: Better d3 charts with tdd

jQuery VS MV*

Page 67: Better d3 charts with tdd

Reusable Chart API - codereturn function module(){ // @param {D3Selection} _selection A d3 selection that represents // the container(s) where the chart(s) will be rendered function exports(_selection){

// @param {object} _data The data to generate the chart _selection.each(function(_data){ // Assigns private variables // Builds chart }); }

// @param {object} _x Margin object to get/set // @return { margin | module} Current margin or Bar Chart module to chain calls exports.margin = function(_x) { if (!arguments.length) return margin; margin = _x;

Page 68: Better d3 charts with tdd

Reusable Chart API - use// Creates bar chart component and configures its margins barChart = chart() .margin({top: 5, left: 10});

container = d3.select('.chart-container');

// Calls bar chart with the data-fed selector container.datum(dataset).call(barChart);

Page 69: Better d3 charts with tdd

Reusable Chart API - benefitsModularComposableConfigurableConsistentTeamwork EnablingTestable

Page 70: Better d3 charts with tdd

THE TDD WAY

Page 71: Better d3 charts with tdd

The "before" blockcontainer = d3.select('.test-container'); dataset = [ { letter: 'A', frequency: .08167 },{ letter: 'B', frequency: .01492 },... ]; barChart = barChart();

container.datum(dataset).call(barChart);

Page 72: Better d3 charts with tdd

Test: basic chartit('should render a chart with minimal requirements', function(){ expect(containerFixture.select('.bar-chart').empty()).toBeFalsy(); });

Page 73: Better d3 charts with tdd

Code: basic chartreturn function module(){ var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960, height = 500, svg;

function exports(_selection){ _selection.each(function(_data){ var chartWidth = width - margin.left - margin.right, chartHeight = height - margin.top - margin.bottom;

if (!svg) { svg = d3.select(this) .append('svg') .classed('bar-chart', true); } }); }; return exports;

Reference: Towards Reusable Charts

Page 74: Better d3 charts with tdd

Test: containersit('should render container, axis and chart groups', function(){ expect(containerFixture.select('g.container-group').empty()).toBeFalsy(); expect(containerFixture.select('g.chart-group').empty()).toBeFalsy(); expect(containerFixture.select('g.x-axis-group').empty()).toBeFalsy(); expect(containerFixture.select('g.y-axis-group').empty()).toBeFalsy();});

Page 75: Better d3 charts with tdd

Code: containersfunction buildContainerGroups(){ var container = svg.append("g").attr("class", "container-group");

container.append("g").attr("class", "chart-group"); container.append("g").attr("class", "x-axis-group axis"); container.append("g").attr("class", "y-axis-group axis"); }

Page 76: Better d3 charts with tdd

Test: axesit('should render an X and Y axes', function(){ expect(containerFixture.select('.x-axis-group.axis').empty()).toBeFalsy(); expect(containerFixture.select('.y-axis-group.axis').empty()).toBeFalsy();});

Page 77: Better d3 charts with tdd

Code: scalesfunction buildScales(){ xScale = d3.scale.ordinal() .domain(data.map(function(d) { return d.letter; })) .rangeRoundBands([0, chartWidth], .1);

yScale = d3.scale.linear() .domain([0, d3.max(data, function(d) { return d.frequency; })]) .range([chartHeight, 0]); }

Page 78: Better d3 charts with tdd

Code: axesfunction buildAxis(){ xAxis = d3.svg.axis() .scale(xScale) .orient("bottom");

yAxis = d3.svg.axis() .scale(yScale) .orient("left") .ticks(10, "%"); }

Page 79: Better d3 charts with tdd

Code: axes drawingfunction drawAxis(){ svg.select('.x-axis-group') .append("g") .attr("class", "x axis") .attr("transform", "translate(0," + chartHeight + ")") .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"); }

Page 80: Better d3 charts with tdd

Test: bars drawingit('should render a bar for each data entry', function(){ var numBars = dataset.length;

expect(containerFixture.selectAll('.bar').size()).toEqual(numBars);});

Page 81: Better d3 charts with tdd

Code: bars drawingfunction 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 chartHeight - yScale(d.frequency); });}

Reference: , Thinking with joins General Update Pattern

Page 82: Better d3 charts with tdd

Test: margin accessorit('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); });

Page 83: Better d3 charts with tdd

Code: margin accessorexports.margin = function(_x) { if (!arguments.length) return margin; margin = _x; return this; };

Page 84: Better d3 charts with tdd

Looks the same, but is not

Page 85: Better d3 charts with tdd

Final code: standard wayvar margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;

var x = d3.scale.ordinal() .rangeRoundBands([0, width], .1);

var y = d3.scale.linear() .range([height, 0]);

var xAxis = d3.svg.axis() .scale(x) .orient("bottom");

var yAxis = d3.svg.axis() .scale(y) .orient("left") .ticks(10, "%");

Page 86: Better d3 charts with tdd

Final code: TDD wayreturn function module(){ var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960, height = 500, chartWidth, chartHeight, xScale, yScale, xAxis, yAxis, data, svg;

function exports(_selection){ _selection.each(function(_data){ chartWidth = width - margin.left - margin.right; chartHeight = height - margin.top - margin.bottom; data = _data;

buildScales(); buildAxis(); buildSVG(this); drawBars();

Page 87: Better d3 charts with tdd

TDD way - benefitsStress free refactorsGoal orientedDeeper understandingImproved communicationQuality, production ready output

Page 88: Better d3 charts with tdd

HOW TO GET STARTED

Page 89: Better d3 charts with tdd

Some ideasTest something that is in productionTDD the last chart you builtPick a block, refactor itTDD your next chart

Page 90: Better d3 charts with tdd

REPOSITORYWALKTHROUGH

https://github.com/Golodhros/d3-meetup

Page 91: Better d3 charts with tdd

What happened with my contracting gig?

Page 92: Better d3 charts with tdd

I used the Reusable Chart API

Page 93: Better d3 charts with tdd

Adding multiple dimensions?

Page 94: Better d3 charts with tdd

I had tests!

Page 95: Better d3 charts with tdd

Toogle dimensions, adding more y-axis?

Page 96: Better d3 charts with tdd
Page 97: Better d3 charts with tdd

ConclusionsExamples are great for exploration and prototyping,bad for production codeThere is a better way of building D3 ChartsReusable Chart API + TDD bring it to a Pro levelYou can build your own library and feel proud!

Page 98: Better d3 charts with tdd

Thanks for listening!Twitter: Check out Slides: Code:

@golodhrosmy Blog

http://golodhros.github.io/https://github.com/Golodhros/d3-meetup

Page 99: Better d3 charts with tdd

Live CodingRefactoring accessorsAdd EventsStart building a new chart

Page 100: Better d3 charts with tdd

Learning resourcesD3.js Resources to Level UpDashing D3 Newsletter

Page 101: Better d3 charts with tdd

Example searchSearch by chart type -> Search by D3 component ->

Christophe Viau's GalleryBlock Explorer

Page 102: Better d3 charts with tdd

Books

Page 103: Better d3 charts with tdd
Page 104: Better d3 charts with tdd
Page 105: Better d3 charts with tdd
Page 106: Better d3 charts with tdd