d4 and friendly charting dsl for d3

Post on 11-Aug-2014

538 Views

Category:

Data & Analytics

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

This presentation gives a basic overview of d4 the charting DSL for D3.

TRANSCRIPT

d4a friendly charting DSL for D3

What’s a chart?Let’s get our definitions straight

–Garry Klass

“A graphical chart provides a visual display of data that otherwise would be presented in a table; a table, one that would otherwise be presented in text. Ideally, a chart should convey ideas

about the data that would not be readily apparent if they were displayed in a table

or as text.”

Charts are not about displaying data, they are about conveying

ideas visually through data.

Successful charts are succinct, self-explanatory visual systems which answer

questions about the comparison, composition, distribution or interrelationship

in the data they present.

Successful charts are fit for purpose

Being fit for purpose is hard to do using reusable charts because visual

expressiveness is often the first quality scraped away when trying to wedge an idea into the mold of a pre-made chart.

Most prebuilt charts are not fit for purpose.

Build Jigs Not ToolsFirst insight

–Hunt & Thomas “The Pragmatic Programmer: From Journeyman to Master”

“When woodworkers are faced with the task of producing the same thing over and over, they

cheat. They build themselves a jig or a template. If they get the jig right once, they can reproduce a piece of work time after time. The

jig takes away complexity and reduces the chances of making mistakes, leaving the craftsman free to concentrate on quality.”

Data is your raw material

D3 is a tool for visually shaping !your data

d4 guides your data !through D3

Image from: www.finewoodworking.com

A chart is a jig• Used to control the process our output of another

tool

• Provides repeatability, accuracy and interchangeability in the production process

• Simplifies a complexities of a task

• Acts as a template or a guide

Elements of a d4 jig• Chart - A collection of features, and a builder which renders

data using d3 into a graphical representation.

• Builder - A routine to apply sensible defaults to the features.

• Feature - A visual component of a chart, which helps convey meaning in the data.

• Dimension - A segment of the data described by the chart.

• Parser - A routine to prepare incoming data into a format appropriate for a given chart.

A Basic Chart

// Let’s create a reusable column chart !d4.chart('column', function column() { return d4.baseChart() .mixin([{ 'name': 'bars', 'feature': d4.features.rectSeries }, { 'name': 'barLabels', 'feature': d4.features.stackedLabels }, { 'name': 'xAxis', 'feature': d4.features.xAxis }, { 'name': 'yAxis', 'feature': d4.features.yAxis }]); });

Chart Definition

Chart Feature

Chart Feature

Chart Feature

Chart Feature

A Basic Chart

'use strict'; !$(document).ready(function(){ var data = [ { x: '2010', y:-10 }, { x: '2011', y:20 }, { x: '2012', y:30 }, { x: '2013', y:40 }, { x: '2014', y:50 }, ]; ! // now we create our chart instance. var chart = d4.charts.column(); ! // then we use our chart like a jig to // get d3 to render our data. d3.select('#example') .datum(data) .call(chart); });

Context Over Configuration

Second Insight

There is a software design concept called “convention over configuration,” which states that

software should specify a collection of opinionated defaults for the developer.

!The goal of this approach is to lessen the number

of obvious choices a developer must make before they are able to use the software.

d4 extends this concept a bit and suggests that configuration should also be highly contextual to

the object the developer needs changed. !

Instead of making choices in some abstract config file, developers instead use a highly

declarative API to make changes directly to the object they want augment.

A Basic Chart

'use strict'; !$(document).ready(function(){ var data = [ { x: '2010', y:-10 }, { x: '2011', y:20 }, { x: '2012', y:30 }, { x: '2013', y:40 }, { x: '2014', y:50 }, ]; ! var chart = d4.charts.column() ! // lets get rid of the yaxis .mixout(‘yAxis'); ! d3.select('#example') .datum(data) .call(chart); });

#mixout()

d4’s base charts make assumptions on what features are wanted. It is easy to remove an unwanted feature using mixout().

A Basic Chart'use strict'; !$(document).ready(function(){ var data = [ { x: '2010', y:-10 }, { x: '2011', y:20 }, { x: '2012', y:30 }, { x: '2013', y:40 }, { x: '2014', y:50 }, ]; ! var chart = d4.charts.column() .mixin({ 'name' : 'zeroLine', 'feature' : d4.features.referenceLine }); ! d3.select('#example') .datum(data) .call(chart); });

#mixin()zeroLine

You can add features by mixing them in. However, some features do not look that great using their default settings, as is the case with our reference line.

A Basic Chart'use strict'; !$(document).ready(function(){ var data = [ { x: '2010', y:-10 }, { x: '2011', y:20 }, { x: '2012', y:30 }, { x: '2013', y:40 }, { x: '2014', y:50 }, ]; ! var chart = d4.charts.column() .mixin({ 'name' : 'zeroLine', 'feature' : d4.features.referenceLine }) .using('zeroLine', function(zeroLine){ zeroLine .x1(0) .y1(function(){ return this.y(0); }) .x2(chart.outerWidth() - chart.marginLeft() - chart.margin().right) .y2(function(){ return this.y(0); }); }); d3.select('#example') .datum(data) .call(chart); });

#using()

You can control a feature’s attributes with the using() function.

Separation of concerns matters

Third Insight

Separation of Responsibilities

• Designers should not need to know JavaScript to style the chart and vice versa

• The chart does not own the data, and any transformations should be made on a copy of the data

• Don’t add needless abstractions, where possible leverage or proxy existing functionality in d3 or svg.

d4 and CSSd4 adds several generated CSS classes to various elements of the chart as it renders. !The ability to control the series colors changes the story your charts are telling, allowing you to choose the correct visual representation of your series data to support the point you are trying to make.

These is the same chart, using the same data. The only thing that has changed is which CSS classes are being accessed.

A Basic Chart'use strict'; !$(document).ready(function(){ var data = [ { year: '2010', unitsSold: 200, salesman : 'Bob' }, { year: '2011', unitsSold: 200, salesman : 'Bob' }, { year: '2012', unitsSold: 300, salesman : 'Bob' }, { year: '2013', unitsSold: -400, salesman : 'Bob' }, { year: '2014', unitsSold: -500, salesman : 'Bob' }, { year: '2010', unitsSold: 100, salesman : 'Gina' }, { year: '2011', unitsSold: 100, salesman : 'Gina' }, { year: '2012', unitsSold: 200, salesman : 'Gina' }, { year: '2013', unitsSold: -500, salesman : 'Gina' }, { year: '2014', unitsSold: -600, salesman : 'Gina' }, { year: '2010', unitsSold: 400, salesman : 'Average' }, { year: '2011', unitsSold: 100, salesman : 'Average' }, { year: '2012', unitsSold: 400, salesman : 'Average' }, { year: '2013', unitsSold: -400, salesman : 'Average' }, { year: '2014', unitsSold: -400, salesman : 'Average' } ]; ! var parsedData = d4.parsers.nestedStack() .x(function(){ return 'year'; }) .y(function(){ return 'salesman'; }) .value(function(){ return 'unitsSold'; })(data); ! var chart = d4.charts.stackedColumn() .outerWidth($('#example').width()) .x(function(x){ x.key('year'); }) .y(function(y){ y.key('unitsSold'); }) d3.select('#example') .datum(parsedData.data) .call(chart); });

using parsers

Notice: using the parser we define which values correspond to which dimension. Then you link those dimensions in the chart to the appropriate key.

A Basic Chart$(document).ready(function(){ var data = [ { year: '2010', unitsSold:-100, salesman : 'Bob' }, { year: '2011', unitsSold:200, salesman : 'Bob' }, { year: '2012', unitsSold:300, salesman : 'Bob' }, { year: '2013', unitsSold:400, salesman : 'Bob' }, { year: '2014', unitsSold:500, salesman : 'Bob' }, { year: '2010', unitsSold:100, salesman : 'Gina' }, { year: '2011', unitsSold:100, salesman : 'Gina' }, { year: '2012', unitsSold:-100, salesman : 'Gina' }, { year: '2013', unitsSold:500, salesman : 'Gina' }, { year: '2014', unitsSold:600, salesman : 'Gina' }, { year: '2010', unitsSold:400, salesman : 'Average' }, { year: '2011', unitsSold:0, salesman : 'Average' }, { year: '2012', unitsSold:400, salesman : 'Average' }, { year: '2013', unitsSold:400, salesman : 'Average' }, { year: '2014', unitsSold:400, salesman : 'Average' } ]; ! var parsedData = d4.parsers.nestedGroup() .x('year') .y('unitsSold') .value('unitsSold')(data); ! var chart = d4.charts.groupedColumn(); chart .outerWidth($('#example').width()) .x(function(x){ x.key('year'); ! // Just for fun lets reduce the outer padding of the column chart. // This also shows how d4 transparently passes the appropriate calls to d3. x.rangeRoundBands([0, chart.width()], 0.2, 0.1); }) .y(function(y){ y.key('unitsSold'); }) .groupsOf(parsedData.data[0].values.length); ! d3.select('#example') .datum(parsedData.data) .call(chart); });

Same data different parser

The data is the same as the previous example, and simply by using a different data parser you can easily restructure the relationship for d4.

Proxies in d4Maximize the work you are not doing

(function() { 'use strict'; d4.feature('lineSeries', function(name) { var line = d3.svg.line(); line.interpolate('basis'); return { accessors: { x: function(d) { return this.x(d[this.x.$key]); }, ! y: function(d) { return this.y(d[this.y.$key]); }, ! classes: function(d, n) { return 'line stroke series' + n; } }, proxies: [line], render: function(scope, data, selection) { selection.append('g').attr('class', name); line .x(d4.functor(scope.accessors.x).bind(this)) .y(d4.functor(scope.accessors.y).bind(this)); ! var group = this.svg.select('.' + name).selectAll('g') .data(data); group.exit().remove(); group.enter().append('g') .attr('data-key', function(d) { return d.key; }) .attr('class', d4.functor(scope.accessors.classes).bind(this)) .append('path') .attr('d', function(d) { return line(d.values); }); } }; }); }).call(this);

var chart = d4.charts.line() .outerWidth($('#example').width()) .x(function(x){ x.key('year'); }) .y(function(y){ y.key('unitsSold'); }) .using('lineSeries', function(line) { line.interpolate('cardinal'); }) .mixin({ 'name' : 'dotSeries', 'feature' : d4.features.circleSeries, 'index' : 2 }) .using('dotSeries', function(dot) { dot .r(function() { return 5; }) .cx(function(d) { return this.x(d[this.x.$key]); }) .cy(function(d) { return this.y(d[this.y.$key]); }); });

Defining a proxy in a feature Using a proxy attribute

The interpolate() function is a defined feature accessor. Instead the feature transparently proxies it to d3.

Thanks!Additional Resources

Examples & Documentation Site: http://www.visible.io Source code repository: https://github.com/heavysixer/d4

Mark Daggett 2014 @heavysixer

top related