Single column stacked bar chart with DC

274 views
Skip to first unread message

Basile Simon

unread,
Dec 18, 2014, 12:45:19 PM12/18/14
to dc-js-us...@googlegroups.com
Hello!

I've been working with dc.js and quite enjoyed the multiple time series, visualised as stacked charts.

However, I cannot figure out how to do a single-column stacked chart.

My dataset details air strikes. One row per strike, identifying a country, location, and all that stuff.

The idea is to have on a single column a stacks of different importance (area) depending on the number of strikes by each country. Such as in the image attached.

I created a jsfiddle with a placeholder for the interesting bit (excuse the big chunk of data at the top!).

Thanks so much for your help!

Screenshot from 2014-12-16 15:27:11.png

Gordon Woodhull

unread,
Dec 19, 2014, 10:44:56 AM12/19/14
to dc.js user group
Hi Basile,

It's an interesting problem, how to reduce a list of indirectly-specified fields.

Since my first SO answer here wasn't helpful, I felt obligated to give it another shot.  As I wrote in the second question, this isn't really a charting problem but a crossfilter reduction question, so I actually think the first question was better.

Here's what I came up with:

  var strikingCountriesGroup = xScaleDimension.group().reduce(
      function(p, v) { // add
          countryFields.forEach(function(c) {
              if(v[c]) p[v[c]] = (p[v[c]] || 0) + v.totalNumberOfStrikes;
          });
          return p;
      },
      function(p, v) { // remove
          countryFields.forEach(function(c) {
              if(v[c]) p[v[c]] = p[v[c]] - v.totalNumberOfStrikes;
          });
          return p;
      },
      function() { // initial
          return {};
      }
  );

Although that may look like a big tangle of brackets, the idea is that the fields v[c], where c is "Country 1", "Country 2"... in the original data set, indirectly specify the fields that you want to create in the reduction.

We are reducing into the map p from the value v.  We loop over the country fields, and for each c, if v has an entry for c, we add or subtract totalNumberOfStrikes from p[v[c]].  We have to be careful if the value doesn't already exist: the expression "|| 0" defaults a value to zero if it is undefined.

Here's a fiddle:

You ought to be able to add more charts using this model, without modifying the original data set.

Cheers,
Gordon

P.S. Ethan Jewett's reductio library is starting to do some multiple-value reductions, described here.  I haven't tried it out, but I wanted to point it out here for anyone who's interested.  I don't think it would handle this kind of indirection.. yet.


--
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/7ed1f119-863b-4c3c-8744-2582212b2632%40googlegroups.com.

Basile Simon

unread,
Dec 19, 2014, 11:15:28 AM12/19/14
to dc-js-us...@googlegroups.com
Wow, this looks great, Gordon. I would never have gotten that :p

I'm thinking of sorting (from bottom to top) by number of strikes - although it might be messing up with what is later in your code. I am missing something by trying this?

  strikingCountries
      .width(150).height(400)
      .dimension(xScaleDimension)
      .x(d3.scale.ordinal())
      .ordering(d3.ascending())
      .xUnits(dc.units.ordinal)
      .brushOn(false);

Cheers,

BS

Gordon Woodhull

unread,
Dec 19, 2014, 11:31:21 AM12/19/14
to dc.js user group
Hi Basile,

Glad that worked for you.  I'll post it as an answer to the original SO question.

Unfortunately, I don't know of a good way to sort stacks.  The option you are trying below is for sorting bins within a group.

I mean, you can change the .sort() in the part that extracts the country names from the reduction, to sort by value instead of name:

  var reducedCountries = strikingCountriesGroup.all()[0].value;
  var countries = d3.keys(reducedCountries).sort(function(a, b) {
      return reducedCountries[b] - reducedCountries[a];   
  });

But this is a one-time sort and won't hold up once you have other charts filtering the data.

Maybe someone else on this list has an idea how to keep the stacks sorted.

Cheers,
Gordon


Basile Simon

unread,
Dec 19, 2014, 12:04:55 PM12/19/14
to dc-js-us...@googlegroups.com
Hey Gordon,

Thanks again man, really appreciate your help.

Despite working fine in the JSFiddle, the solution doesn't work locally, which I will blame on the fact that the local version uses a CSV dataset, whereas I posted it as a JSON in the fiddle.

However, the data is identical, and I admit being a bit confused by the differences there would be. Maybe the leading space bit?

Here's the CSV structure, with 2 prop lines:
Date,Country 1,Country 2,Country 3,Country 4,Country 5,Country 6,Target country,Strikes in Iraq,Strikes in Syria,Location 1,Location 2,Aircraft type,Number of aircrafts,Ammunition,Target 1,No of target 1,Status of target 1,Target 2,No of target 2,Status of target 2,Target 3,No of target 3,Status of target 3,Target 4,No of target 4,Status of target 4,Target 5,No of target 5,Status of target 5,Target 6,No of target 6,Status of target 6,Target 7,No of target 7,Status of target 7,Target 8,No of target 8,Status of target 8,Target 9,No of target 9,Status of target 9,Target 10,No of target 10,Status of target 10,Casualties?,Casualties estimate
2014-08-08T00:00:00Z,USA,,,,,,Iraq,1,,Irbil,,Attack,2,500-pound laser-guided bombs,Mobile artillery piece,1,Unknown,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2014-08-08T00:00:00Z,USA,,,,,,Iraq,1,,Irbil,,Remotely piloted aircraft,1,,Mortar position,1,Unknown,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,Unknown


Cheers,

BS

Basile Simon

unread,
Dec 20, 2014, 1:13:59 PM12/20/14
to dc-js-us...@googlegroups.com
Christopher advised me that the question mark in the CSV might be causing problem - however, it still doesn't work if I remove it.

Am I missing something obvious here?

Cheers,

BS

Gordon Woodhull

unread,
Dec 20, 2014, 1:26:17 PM12/20/14
to dc.js user group
Hi Basile,

"It doesn't work" isn't much for us to go on.  You should look for errors in the browser console and/or the Network tab of the developer tools.

If you're running on your local file system: One common stumbling block is the fact that most browsers will not allow loading file:// URLs from files.


I think we've all been there.

Cheers,
Gordon


Basile Simon

unread,
Dec 20, 2014, 1:43:16 PM12/20/14
to dc-js-us...@googlegroups.com
Hey Gordon,

Didn't mean to be a moron, I just left out the specifics.

So, to be a bit more precise:
- CSV file containing the data is loading fine...
- as all the other resources required.
- left-hand side chart draws fine...
- but not the one you helped me with.
- running a simpleHTTPServer already
- zero errors in the console.

I've tried to comment out the three lines removing the leading trails, but no changes.
I'm enclined to think that it's the problematic bit, as I have many other charts on that page which use the same data (though in a different d3.csv() call and .js file), and everything is loading/displaying A-OK.

Do you think that's an issue between using JSON in the fiddle and CSV in my local version?

Cheers,

BS

Gordon Woodhull

unread,
Dec 20, 2014, 2:21:27 PM12/20/14
to dc-js-us...@googlegroups.com
Hi Basile,

Hmm. The leading whitespace was just creating twice as many countries, so it should be safe to remove those lines.

You'll want to get to know your debugger. 

There are a couple of starting tips here:

You may also want to put breakpoints inside the reduce functions to see if they are aggregating data as expected. 

Or if you're not comfortable with the debugger, adding console.log(group.all()) before rendering should have the same effect. (Same goes for the reduce functions, but you'll get a crazy amount of output.)

Cheers
Gordon

BS


BS


BS


BS

<Screenshot from 2014-12-16 15:27:11.png>


-- 
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/8E325BF5-68EF-443D-A3A3-9AC75D18ECB6%40woodhull.com.

-- 
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.

-- 
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/94B4E8ED-B87E-442D-9D6B-6FAAF04BFC74%40woodhull.com.

-- 
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/9688B120-7E20-4D4F-A216-2B6083B6796D%40woodhull.com.

--
You received this message because you are subscribed to the Google Groups "dc-js user group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dc-js-user-gro...@googlegroups.com.

Basile Simon

unread,
Dec 20, 2014, 2:41:48 PM12/20/14
to dc-js-us...@googlegroups.com
Thanks for the guidelines.

I attached a screenshot:

- reducedCountries seems not to add integers together, but to bind them as if they were strings. I am right to think this way?

Cheers,

BS

Screenshot from 2014-12-20 19:36:50.png

Gordon Woodhull

unread,
Dec 20, 2014, 3:21:08 PM12/20/14
to dc-js-us...@googlegroups.com
Ah right. I don't know why you didn't run into this before, but the d3 readers tend to read fields as strings. 

You'll want to coerce all your number fields to numbers. In your preprocess step, do

    d.number = +d.number;

for each numeric field.
<Screenshot from 2014-12-20 19:36:50.png>

Basile Simon

unread,
Dec 20, 2014, 3:50:02 PM12/20/14
to dc-js-us...@googlegroups.com

On 20 December 2014 at 20:21, Gordon Woodhull <gor...@woodhull.com> wrote:
d.number = +d.number;

Fiat Magicus. Thanks so much, Gordon!

BS
Screenshot from 2014-12-20 20:49:38.png
Reply all
Reply to author
Forward
0 new messages