Creating a Dashboard with dc.js



Fernando Blat // @ferblape

## Developer ![Logo Populate](https://dl.dropboxusercontent.com/u/659167/Logo_Populate_Simple.png)
## What is dc.js? > Dimensional Charting Javascript Library - On top of [D3.js](http://d3js.org/) and [Crossfilter](http://square.github.io/crossfilter/) - Create visualizations for filtering and aggregating data - Links: - http://dc-js.github.io/dc.js/ - [Source code](https://github.com/dc-js/dc.js) - [Annotated source code of the example page](http://dc-js.github.io/dc.js/docs/stock.html) - [API documentation](https://github.com/dc-js/dc.js/blob/master/web/docs/api-latest.md)
## Our example dataset: Spanish municipalities budgets ![](images/dataset1.png)
## The dashboard we are going to create http://ferblape.github.io/d3mad-dashboards-with-dc.js/example1/
## Super-quick introduction to crossfilter.js
## crossfilter.js - http://square.github.io/crossfilter/ - https://github.com/square/crossfilter/wiki/API-Reference
## crossfilter.js: concepts - Dimensions - Groups
### Dimensions - Dimensions are just the columns we want to filter by - Example: years, provinces, autonomous regions....
### Groups - Groups are the result of aggregating the variables filtered by the dimensions - Example: budget per year, mean budget per province
## Example Declare some dimensions: years and provinces.
## Our example dataset: Spanish municipalities budgets ![](images/dataset1.png)

Load the CSV dataset


d3.csv('/datasets/presupuestos-municipales.csv', function(error, data){
  // Format the data
  data.forEach(function(d){
    d.budget = +d.budget;
    d.population = +d.population;
    d.year = +d.year;
    d.date = new Date(d.year, 0, 1);
    d.budgetPerInhabitant = (d.budget / d.population);
  });

  // Construct a new crossfilter
  var budgets = crossfilter(data);

  // Insert your code here
  // ...
});
          

data[0]


{year: 2010, city: "Albacete",
budget: 173159214.03, population: 171390,
province: "Albacete",...}

Declaring our dimensions


d3.csv('/datasets/presupuestos-municipales.csv', function(error, data){
  // Format the data
  data.forEach(function(d){
    d.budget = +d.budget;
    ...
  });

  // Construct a new crossfilter
  var budgets = crossfilter(data);

  var yearsDim = budgets.dimension(function(d){ return d.date; });

  var provinceDim = budgets.dimension(function(d){ return d.province; });
          
## Dimensions - Are declared with an accessor function - They create an internal collection of elements sorted by the attribute returned in the function
## Groups - Groups create collections of data, grouping a given column by a given filter - Example: budget per year - Groups are declared from a **dimension** or from the whole data, providing a **reduce** function

Budget per year


var yearsDim = budgets.dimension(function(d){ return d.date; });

var budgetPerYearGroup = yearsDim
                          .group()
                          .reduceSum(function(d){
                            return d.budget;
                          });
          

> budgetPerYearGroup.top(10).map(function(d){console.log(d);});

[Log] {key: Fri Jan 01 2010 00:00:00, value: 19953790555.04}
[Log] {key: Sat Jan 01 2011 00:00:00, value: 18620250780.48}
[Log] {key: Sun Jan 01 2012 00:00:00, value: 18106387923.29}
[Log] {key: Thu Jan 01 2015 00:00:00, value: 17121299191.05}
[Log] {key: Wed Jan 01 2014 00:00:00, value: 16805983230.21}
[Log] {key: Tue Jan 01 2013 00:00:00, value: 16069487367.37}
          

Mean budget per year


var yearsDim = budgets.dimension(function(d){ return d.date; });

var meanBudgetPerYearGroup = yearsDim
                              .group()
                              .reduce(addElement, removeElement,
                                       initialize);
          

Mean budget per year


var initialize = function(){
  return {
    count: 0,
    totalBudget: 0,
    totalBudgetPerInhabitant: 0
  };
}
          

Mean budget per year


var addElement = function(p, v){
  p.count++;
  p.totalBudget += v.budget;
  p.totalBudgetPerInhabitant += v.budgetPerInhabitant;

  p.meanBudget = p.totalBudget / p.count;
  p.meanBudgetPerInhabitant = p.totalBudgetPerInhabitant / p.count;
  return p;
}
          

Mean budget per year


var removeElement = function(p, v){
  p.count--;
  p.totalBudget -= v.budget;
  p.totalBudgetPerInhabitant -= v.budgetPerInhabitant;

  p.meanBudget = p.totalBudget / p.count;
  p.meanBudgetPerInhabitant = p.totalBudgetPerInhabitant / p.count;
  return p;
}
          

Mean budget per year


> meanBudgetPerYearGroup.top(2).map(function(d){console.log(d);});

[Log] {key: Tue Jan 01 2011 00:00:00, {count: 50,
  totalBudget: 18620250780.48, meanBudget: 372405015.60}}
[Log] {key: Tue Jan 01 2012 00:00:00, {count: 50,
  totalBudget: 17232302324.38, meanBudget: 632403010.82}}
          
## dc.js
## dc.js - dc.js charts use crossfilter dimensions and groups - charts are defined to filter by default - http://ferblape.github.io/d3mad-dashboards-with-dc.js/example1/

Pie Chart


d3.csv('/datasets/presupuestos-municipales.csv', function(error, data){
  // Format the data
  data.forEach(function(d){
    d.budget = +d.budget;
    ...
  });

  // Construct a new crossfilter
  var budgets = crossfilter(data);

  var yearsDim = budgets.dimension(function(d){ return d.date; });
  var budgetPerYearGroup = yearsDim.group().reduceSum(function(d){ return d.budget; });

  yearsChart = dc.pieChart("#years");
          

Pie Chart


...
yearsChart = dc.pieChart("#years");

yearsChart
  .dimension(yearsDim)
  .group(budgetPerYearGroup)
  .title(function(d){
    return accounting.formatNumber(d.value, {precision: 0}) + '€';
  })
  .label(function(d){
    return d.key.getFullYear();
  });
          

## Pie Chart - By default charts have a nice configuration - Custom configuration: https://github.com/dc-js/dc.js/blob/master/web/docs/api-latest.md#dc.pieChart - `radius`: set radius - `innerRadius`: set the inner radius (donut!) - `cx` and `cy`: set the center - ...
## dc Charts - All charts inherit from `dc.baseMixin` - https://github.com/dc-js/dc.js/blob/master/web/docs/api-latest.md#dc.baseMixin - `height`, `width` - `label`, `title` - `colors` - `group`, `dimension` - `on` (eventos) - ...

Bars Chart


...
autonomousRegionsChart = dc.rowChart("#autonomous-regions");

autonomousRegionsChart
  .dimension(autonomousRegionsDim)
  .group(budgetPerAutonomousRegionGroup)
  .title(function(d) {
    return d.key + " " + accounting.formatNumber(d.value);
  })
  .elasticX(true)
  .height(400)
  .xAxis().ticks(2);
          

Line Chart


evolutionChart = dc.lineChart("#evolution");

evolutionChart
  .dimension(yearsDim)
  .group(meanBudgetPerYearGroup)
  .margins({top: 50, right: 50, bottom: 25, left: 90})
  .x(d3.time.scale().domain([new Date(2010, 0, 1), new Date(2015, 0, 1)]))
  .valueAccessor(function(d) {
    return d.value.meanBudgetPerInhabitant;
  })
  .yAxisPadding(50)
  .title(function(d) {
    return d.key + " " + accounting.formatNumber(d.value);
  })
  .height(250)
  .elasticY(true)
  .yAxis()
  .tickFormat(function(v){return accounting.formatNumber(v, {precision: 0}) + '€';});
          

GeoChoropleth Chart


mapChart = dc.geoChoroplethChart("#map");

d3.json("provinces_carto.geojson", function(error, json){
    var colors = ["#d75231", "#ec8b66", "#fcdbc8", "#eff3ff", "#a0cae0", "#4893c4", "#022977"];

    mapChart
      .height(450)
      .colors(colors)
      .dimension(provinceDim)
      .group(budgetPerProvinceGroup)
      .valueAccessor(function(d){
        return d.value.meanBudgetPerInhabitant;
      })
      .overlayGeoJson(json.features, "provinces", function(p){
        return p.properties.nombre99;
      })
      .on('preRender', function(chart, filter){
      })
      .on('preRedraw', function(chart, filter){
      })
      .on('renderlet', function(){
      });
});
          

GeoChoropleth Chart


.on('preRender', function(chart, filter){
  var mapColors = d3.scale
    .quantize()
    .domain(d3.extent(chart.group().all().map(function(d){
      return d.value.meanBudgetPerInhabitant;
    })))
    .range(colors);

  chart
    .colors(mapColors)
    .colorCalculator(function(d){
      return (d !== undefined) ? mapColors(d) : '#ccc';
    });
})
.on('preRedraw', function(chart, filter){
  // Same code
})
          

GeoChoropleth Chart


.on('renderlet', function(){
  if(d3.select("#map-legend svg")[0][0] !== null) {
    var e = document.getElementById('map-legend');
    e.innerHTML = '';
  }

  var mapColors = d3.scale
    .quantize()
    .domain(d3.extent(mapChart.group().all().map(function(d){
      return d.value.meanBudgetPerInhabitant;
    })))
    .range(colors);

  var svg = d3.select("#map-legend")
    .append("svg")
    .attr('width',500);

  svg.append("g")
    .attr("class", "legendLinear");

  var legendLinear = d3.legend.color()
    .shapeWidth(40)
    .cells(mapColors.domain())
    .orient('vertical')
    .labelFormat(function(v){
      return accounting.formatNumber(v, {precision: 0});
    })
    .scale(mapColors);

  svg.select(".legendLinear")
    .call(legendLinear);
})
          
http://ferblape.github.io/d3mad-dashboards-with-dc.js/example1/
## Summary - dc.js is a library on top of D3.js and crossfilter.js - dc.js is a good choice when working with filterd and aggregated data - crossfilter.js helps us to create the aggregated datasets - dc.js pros: - simple - extensible - good defaults - dc.js cons: - you'll have to read the code
## Extras - d3.legend: http://d3-legend.susielu.com/ - accounting.js: http://openexchangerates.github.io/accounting.js/

Thank you!

ferblape@gmail.com

Questions?



http://ferblape.github.io/d3mad-dashboards-with-dc.js/