block by renecnielsen 70e2ae770cd166731b3eb956f90aac87

Multi-chart interactions using d3.dispatch and crossfilter

Full Screen

This shows how separate charts can be enclosed in d3.dispatch events, and based on generic update events, we can trigger each chart to update accordingly. Crossfilter .filter() takes care of updating the dimensions groups so we simply need to re-trigger part of the chart’s code that updates the chart with new data.

forked from lmatteis‘s block: Multi-chart interactions using d3.dispatch and crossfilter

index.html

<!DOCTYPE html>
<meta charset='utf-8'>
<style>
.bar--positive {
  fill: #9BCCF5;
}

.bar--negative {
  fill: pink;
}

text {
  font: 10px sans-serif;
  text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.title {
  font-size: 13px;
  font-weight:bold;
}
.reset { cursor: pointer;}
</style>
<body>
<br />
<span id='bubble-chart'></span>
<span id='bar-chart'></span>
<script src='//crossfilter.github.io/crossfilter/crossfilter.v1.min.js'></script>
<script src='https://d3js.org/d3.v3.min.js'></script>
<script>
var data = [{"topic":"BMW","age":"18-24","gender":"male","interactions":21600,"key":"BMW","positiveInteractions":21600},{"topic":"BMW","age":"18-24","gender":"female","interactions":-5500,"key":"BMW","positiveInteractions":5500},{"topic":"BMW","age":"25-34","gender":"male","interactions":19500,"key":"BMW","positiveInteractions":19500},{"topic":"BMW","age":"25-34","gender":"female","interactions":-5000,"key":"BMW","positiveInteractions":5000},{"topic":"BMW","age":"35-44","gender":"male","interactions":10700,"key":"BMW","positiveInteractions":10700},{"topic":"BMW","age":"35-44","gender":"female","interactions":-3500,"key":"BMW","positiveInteractions":3500},{"topic":"BMW","age":"45-54","gender":"male","interactions":5700,"key":"BMW","positiveInteractions":5700},{"topic":"BMW","age":"45-54","gender":"female","interactions":-2400,"key":"BMW","positiveInteractions":2400},{"topic":"BMW","age":"55-64","gender":"male","interactions":2500,"key":"BMW","positiveInteractions":2500},{"topic":"BMW","age":"55-64","gender":"female","interactions":-1100,"key":"BMW","positiveInteractions":1100},{"topic":"BMW","age":"65+","gender":"male","interactions":1600,"key":"BMW","positiveInteractions":1600},{"topic":"BMW","age":"65+","gender":"female","interactions":-600,"key":"BMW","positiveInteractions":600},{"topic":"Ford Mustang","age":"18-24","gender":"male","interactions":7600,"key":"Ford Mustang","positiveInteractions":7600},{"topic":"Ford Mustang","age":"18-24","gender":"female","interactions":-800,"key":"Ford Mustang","positiveInteractions":800},{"topic":"Ford Mustang","age":"25-34","gender":"male","interactions":7100,"key":"Ford Mustang","positiveInteractions":7100},{"topic":"Ford Mustang","age":"25-34","gender":"female","interactions":-900,"key":"Ford Mustang","positiveInteractions":900},{"topic":"Ford Mustang","age":"35-44","gender":"male","interactions":5100,"key":"Ford Mustang","positiveInteractions":5100},{"topic":"Ford Mustang","age":"35-44","gender":"female","interactions":-800,"key":"Ford Mustang","positiveInteractions":800},{"topic":"Ford Mustang","age":"45-54","gender":"male","interactions":3500,"key":"Ford Mustang","positiveInteractions":3500},{"topic":"Ford Mustang","age":"45-54","gender":"female","interactions":-600,"key":"Ford Mustang","positiveInteractions":600},{"topic":"Ford Mustang","age":"55-64","gender":"male","interactions":1400,"key":"Ford Mustang","positiveInteractions":1400},{"topic":"Ford Mustang","age":"55-64","gender":"female","interactions":-300,"key":"Ford Mustang","positiveInteractions":300},{"topic":"Ford Mustang","age":"65+","gender":"male","interactions":600,"key":"Ford Mustang","positiveInteractions":600},{"topic":"Ford Mustang","age":"65+","gender":"female","interactions":-200,"key":"Ford Mustang","positiveInteractions":200},{"topic":"Ford Motor Company","age":"25-34","gender":"male","interactions":4300,"key":"Ford Motor Company","positiveInteractions":4300},{"topic":"Ford Motor Company","age":"25-34","gender":"female","interactions":-800,"key":"Ford Motor Company","positiveInteractions":800},{"topic":"Ford Motor Company","age":"18-24","gender":"male","interactions":4200,"key":"Ford Motor Company","positiveInteractions":4200},{"topic":"Ford Motor Company","age":"18-24","gender":"female","interactions":-700,"key":"Ford Motor Company","positiveInteractions":700},{"topic":"Ford Motor Company","age":"35-44","gender":"male","interactions":3000,"key":"Ford Motor Company","positiveInteractions":3000},{"topic":"Ford Motor Company","age":"35-44","gender":"female","interactions":-600,"key":"Ford Motor Company","positiveInteractions":600},{"topic":"Ford Motor Company","age":"45-54","gender":"male","interactions":1800,"key":"Ford Motor Company","positiveInteractions":1800},{"topic":"Ford Motor Company","age":"45-54","gender":"female","interactions":-500,"key":"Ford Motor Company","positiveInteractions":500},{"topic":"Ford Motor Company","age":"55-64","gender":"male","interactions":800,"key":"Ford Motor Company","positiveInteractions":800},{"topic":"Ford Motor Company","age":"55-64","gender":"female","interactions":-300,"key":"Ford Motor Company","positiveInteractions":300},{"topic":"Ford Motor Company","age":"65+","gender":"male","interactions":500,"key":"Ford Motor Company","positiveInteractions":500},{"topic":"Ford Motor Company","age":"65+","gender":"female","interactions":-200,"key":"Ford Motor Company","positiveInteractions":200}];

var dispatch = d3.dispatch('loadBar', 'loadBubble', 'update');

dispatch.on('loadBar', function (dimension, group) {
  var margin = {top: 0, right: 0, bottom: 40, left: 50},
    width = 480 - margin.left - margin.right,
    height = 480 - margin.top - margin.bottom;

  var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
  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);

  var t = d3.transition()
        .duration(750);

  var click;

  var svg = d3.select('#bar-chart'),
        g = svg.select('g');

  if (g.empty()) {
    g = svg.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 + ')');

    g.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

    g.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('Interactions');

    var reset = g.append('text')
      .attr('class', 'reset')
      .attr('y', 10)
      .attr('x', 20)
      .style('display', 'none')
      .text('reset')
      .on('click', function () { dimension.filter(null); dispatch.update(); reset.style('display', 'none') })
  }

  dispatch.on('update.bar', function () {
    x.domain(group.all().map(function(d) { return d.key; }));
    y.domain([0, d3.max(group.all(), function(d) { return d.value; })]);

    g.select('.y.axis')
      .transition(t)
       .call(yAxis)

    g.select('.x.axis')
     .transition(t)
      .call(xAxis)

    var rects = g.selectAll('rect')
        .data(group.all());

    rects.enter().append('rect');

    rects
        .on('click', function (d) { dimension.filter(d.key); dispatch.update(); reset.style('display', 'block') })
      .transition(t)
        .attr('class', function(d) { return 'bar bar--' + (d.key == 'female' ? 'negative' : 'positive'); })
        .attr('x', function(d) { return x(d.key); })
        .attr('width', x.rangeBand())
        .attr('y', function(d) { return y(d.value); })
        .attr('height', function(d) { return height - y(d.value); })

    var texts = g.selectAll('.label')
        .data(group.all())

    texts.enter().append('text').attr('class', 'label')

    texts
      .transition(t)
        .attr('text-anchor', 'middle')
        .attr('x', function(d,i) {
            return x(d.key) + (x.rangeBand() / 2);
        })
        .attr('y', function(d,i) {
            return y(d.value) + ((height - y(d.value)) / 2);
        })
        .attr('dy', '.35em')
        .text(function (d) { return d.value })
  })

})

dispatch.on('loadBubble', function (dimension, group) {
  var margin = {top: 0, right: 0, bottom: 0, left: 0},
    diameter = 460 - margin.left - margin.right;

  var onClick;

  var color = d3.scale.category10();

  var bubble = d3.layout.pack()
    .sort(null)
    .size([diameter, diameter])
    .padding(1.5);

  var t = d3.transition()
      .duration(750);

  var svg = d3.select('#bubble-chart'),
        g = svg.select('g');

  if (!svg.empty()) {
    svg.select('svg').remove()
  }

  g = svg.append('svg')
      .attr('width', diameter + margin.left + margin.right)
      .attr('height', 480 + margin.top + margin.bottom)
      .attr('class', 'bubble')
    .append('g')
      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

  var reset = g.append('text')
    .attr('class', 'reset')
    .style('display', 'none')
    .attr('y', 10)
    .attr('x', 20)
    .text('reset')
    .on('click', function () { dimension.filter(null); dispatch.update(); reset.style('display', 'none') })

  var node = g.selectAll('.node')
    .data(bubble.nodes({ children: group.all() }).filter(function(d) { return !d.children; }))

  node.enter().append('g')
    .attr('class', 'node')
    .attr('transform', function(d) {return 'translate(' + d.x + ',' + d.y + ')'; });

  node.append('title')
    .text(function(d) { return d.key; });

  node.append('circle')
    .attr('r', function(d) { return d.r; })
    .style('fill', function(d) { return color(d.key); })

  node.append('text')
    .attr('dy', '.3em')
    .style('text-anchor', 'middle')
    .text(function(d) { return d.key + ' - ' + d.value; });

  dispatch.on('update.bubble', function () {
    var reset = g.selectAll('.reset')

    node = g.selectAll('.node')
      .data(bubble.nodes({ children: group.all() }).filter(function(d) { return !d.children; }))

    node
      .attr('class', 'node')
      .transition(t)
      .attr('transform', function(d) {return 'translate(' + d.x + ',' + d.y + ')'; });

    node.select('circle')
      .on('click', function (d) { dimension.filter(d.key); dispatch.update(); reset.style('display', 'block') })
      .transition(t)
      .attr('r', function(d) { return d.r; })
      .style('fill', function(d) { return color(d.key); })

    node.select('text')
      .attr('dy', '.3em')
      .style('text-anchor', 'middle')
      .text(function(d) { if (d.value) return d.key + ' - ' + d.value; });

  })
})

var xf = crossfilter(data)

var gender = xf.dimension(function (d) { return d.gender; }),
    genders = gender.group().reduceSum(function (d) { return d.positiveInteractions; }),
    topic = xf.dimension(function (d) { return d.topic }),
    topics = topic.group().reduceSum(function (d) { return d.positiveInteractions; })

dispatch.loadBar(gender, genders);
dispatch.loadBubble(topic, topics);
dispatch.update();

</script>
</body>