Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dc.js Series Chart multi line

Tags:

d3.js

dc.js

I am trying to create 2 charts one bar and one series which the bar will show total earnings per store and series will show multi line earnings per year.

Here is the jsfiddle https://jsfiddle.net/xc4bwgLj/

So when I click on Bar chart Store 1, I want in series chart to see for this store earning for 2017 and 2016 each on a new line. Currently the series chart show the total earnings for each store like bar chart.

Any idea how can I change series chart to show 2016 and 2017 earnings per store?

JsFiddle code:

// generate data
var data = [];
var n = 1000.;

for (var i = 0; i < n; i++) {
console.log(Math.floor(Math.random() * (1 - 0 + 1)) + 0);
  data.push({
    id: (Math.floor(Math.random() * (1 - 0 + 1)) + 0),
    "i": i,
    x: Math.random(),
    "store_name": "Store"+(Math.floor(Math.random() * (1 - 0 + 1)) + 0),
    "2017_earnings": Math.random()*110,
    "2016_earnings": Math.random()*80
  });
}

// do some crossfilter stuff
var cf = crossfilter(data),
  series = cf.dimension(function(d) {
    return [d.store_name, d.i];
  }),
  series_grouped = series
  .group(function(d) {
  console.log(d)
    return [d[0], Math.floor(d[1] / 100.) * 100.];
  })
  .reduceSum(function(d) {
    return d.x;
  }),
  id = cf.dimension(function(d) {
    return d.store_name;
  }),
  id_grouped = id.group().reduceSum(function(d) {
    return d.x;
  });

// generate charts
var chart_width = 960,
  chart_height = 200;
console.log(dc);
dc.seriesChart("#chart_a").height(chart_height).width(.74 * chart_width)
  .chart(function(c) {
    return dc.lineChart(c).renderArea(true)
        .filterHandler(function(dimension, filter) {
        if(filter[0]) {
            dimension.filterFunction(function(d) {
            return d[1] > filter[0][0] && d[1] < filter[0][1];
          });
        } else {
            dimension.filterAll();
        }
        setTimeout(dc.redrawAll,0);
        return filter;
        });
  })
  .x(d3.scale.linear().domain([0, n]))
  .dimension(series)
  .group(series_grouped)
  .seriesAccessor(function(d) {
    return d.key[0];
  })
  .keyAccessor(function(d) {
    return d.key[1];
  })
  .valueAccessor(function(d) {
    return d.value;
  }).legend(dc.legend().x(350).y(350).itemHeight(13).gap(5).horizontal(1).legendWidth(140).itemWidth(70));
dc.barChart("#chart_b").height(chart_height).width(.24 * chart_width)
  .dimension(id)
  .group(id_grouped)
  .x(d3.scale.ordinal())
  .xUnits(dc.units.ordinal)
  .xAxis();

dc.renderAll();
like image 247
Nick Doulgeridis Avatar asked May 03 '17 16:05

Nick Doulgeridis


1 Answers

Here's a different approach that keeps the shape of the source the same, but split the group for the use of the series chart. In my other answer I change the source data shape.

Using a fake group

Whenever we need to preprocess data, e.g. to change the shape that crossfilter returns, we can use a fake group

We'll reduce both columns separately using an ordinary reduction of multiple fields:

  series = cf.dimension(function(d) {
    return d.i;
  }),
  series_grouped = series.group(function(k) {
    return Math.floor(k / 100.) * 100.;
  })
  .reduce(
    function(p, d) { // add
      p[2016] += d['2016_earnings'];
      p[2017] += d['2017_earnings'];
      return p;
    },
    function(p, d) { // remove
      p[2016] -= d['2016_earnings'];
      p[2017] -= d['2017_earnings'];
      return p;
    },
    function() {
     return {2016: 0, 2017: 0};
    }),

Then this split group will take the names of two fields, and will split each bin into two, using the field name as the first part of the multikey:

function split_group(group, field1, field2) {
  return {
    all: function() {
      var ret = [];
      group.all().forEach(function(kv) {
        ret.push({
          key: [field1, kv.key],
          value: kv.value[field1]
        });
        ret.push({
          key: [field2, kv.key],
          value: kv.value[field2]
        });
      });
      return ret;
    }
  }
}

Use it like this:

  series_split = split_group(series_grouped, 2016, 2017)
  // ...
  chart
    .group(series_split)

Hard to tell with the random number generation, but I think the result is identical to the other answer. Just a different approach.

like image 60
Gordon Avatar answered Oct 19 '22 21:10

Gordon