curtisp
curtisp

Reputation: 2315

DC.js compositeChart with two lineChart both representing average value by date

I want to create DC.js chart with two lines each representing average value by date.

I believe the way to do this is to use DC.js compositeChart and create two lineCharts to put into it, and then use reducio() group.reduce(add, remove, initial) to get the average amounts.

My question is where does the conditional logic go? How/where do I conditionally use this:

var group = dimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);  

to calculate average for records with d.PPCC === "PPCC" and another average for records with d.PPCC === "Other"?

I have tried two ways neither of which is working for me.

This is first try:

var bounce30DaysPPCC = function(d) { if (d.PPCC === "PPCC") 
{ dateDim.group().reduce(reduceAdd, reduceRemove, reduceInitial); 
} else { dateDim.group().reduce(0); } };

var bounce30DaysOther = function(d) { if (d.PPCC === "Other") 
{ dateDim.group().reduce(reduceAdd, reduceRemove, reduceInitial); 
} else { dateDim.group().reduce(0); } };

which produces this error:

dc.js:4017 Uncaught TypeError: layer.group.all is not a function

This is second try:

var bounce30DaysPPCC = dateDim.group().reduce( function(d) { if (d.PPCC === "PPCC") 
{ return reduceAdd, reduceRemove, reduceInitial; } else { return 0; }});

var bounce30DaysOther = dateDim.group().reduceSum( function(d) { if (d.PPCC === "Other") 
{ return reduceAdd, reduceRemove, reduceInitial; } else { return 0; } });

which also produces an error:

Uncaught TypeError: reduceInitial is not a function 

Here is my full javascript code.

<script type="text/javascript">

    // get csv data 
    d3.csv("merged.csv", function(data) {

    // get csv data and format date
    var parseDate = d3.time.format("%Y-%m-%d").parse;

    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.sessions = +d.sessions;
        d.ad_requests = +d.ad_requests;
        d.bounceRate = +d.bounceRate;
        d.clicks = +d.clicks;
        d.earnings = +d.earnings;
        d.newUsers = +d.newUsers;
        d.sessionDuration = +d.sessionDuration;
        d.sessionsPerUser = +d.sessionsPerUser;
        d.twitterSessions = +d.twitterSessions;
        d.users = +d.users;
    });

    // setup crossfilter
    var ndx  = crossfilter(data);

    // create dimension
    var dateDim = ndx.dimension(function(d) { return d["date"]; });

    // create data groups
    var bounce30DaysPPCC = function(d) { if (d.PPCC === "PPCC") 
    { dateDim.group().reduce(reduceAdd, reduceRemove, reduceInitial); 
    } else { dateDim.group().reduce(0); } };

    var bounce30DaysOther = function(d) { if (d.PPCC === "Other") 
    { dateDim.group().reduce(reduceAdd, reduceRemove, reduceInitial); 
    } else { dateDim.group().reduce(0); } };

    // create min and max dates to limit chart x axis values
    var minDateTime = new Date(new Date().setDate(new Date().getDate()-30));

    var maxDateTime = new Date(new Date().setDate(new Date().getDate()+1));

    // create chart     
    BounceChart30Day = dc.compositeChart("#bounce-chart-30day");

    // create two line charts to put into composite chart
    var ppccLine = dc.lineChart(BounceChart30Day)
    .group(bounce30DaysPPCC)    

    var otherLine = dc.lineChart(BounceChart30Day)
    .group(bounce30DaysOther)

    // create composite chart
    BounceChart30Day
    .height(50)
    .width(450)
    .margins({top: 10, right: 10, bottom: 5, left: 35})
    .dimension(dateDim)
    .transitionDuration(500)
    .brushOn(false)
    .elasticY(true)
    .compose([ppccLine, otherLine])
    .x(d3.time.scale().domain([minDateTime, maxDateTime]))
    .yAxis().ticks(3);

    // create value accessor to calculate averages
    BounceChart30Day.valueAccessor(function(p) { 
    return p.value.count > 0 ? p.value.total / p.value.count : 0; });

    // create add, remove, initial reduce functions
    function reduceAdd(p, v) {
      ++p.count;
      p.total += v.bounceRate;
      return p;
    }

    function reduceRemove(p, v) {
      --p.count;
      p.total -= v.bounceRate;
      return p;
    }

    function reduceInitial() {
      return {count: 0, total: 0};
    }

    // render chart
    dc.renderAll();

</script>

Upvotes: 1

Views: 664

Answers (1)

Ethan Jewett
Ethan Jewett

Reputation: 6010

You need to evaluate the data within your reduce functions. So make your reduce functions look something like this:

// create add, remove, initial reduce functions
function reduceAdd(p, v) {
  if (v.PPCC === "PPCC") {
    ++p.PPCCcount;
    p.PPCCtotal += v.bounceRate;
  } else if(v.PPCC === "Other") {
    ++p.Othercount;
    p.Othertotal += v.bounceRate;
  }
  return p;
}

function reduceRemove(p, v) {
  if (v.PPCC === "PPCC") {
    --p.PPCCcount;
    p.PPCCtotal -= v.bounceRate;
  } else if(v.PPCC === "Other") {
    --p.Othercount;
    p.Othertotal -= v.bounceRate;
  }
  return p;
}

function reduceInitial() {
  return { PPCCcount: 0, PPCCtotal: 0, Othercount: 0, Othertotal: 0};
}

Then just create your groups as normal:

var bounce30DaysPPCC = dateDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);
var bounce30DaysOther = dateDim.group().reduceSum(reduceAdd, reduceRemove, reduceInitial);

Everything else looks pretty much OK at first glance. You'll just need to update your valueAccessor functions to get at the new property names on the groups.

Upvotes: 2

Related Questions