1

I'm building a data dashboard using DC.js and was wondering if it was possible to change the color of the slices in a pie chart dynamically based on the value in the field it is referring to.

Basically I've built a pie chart aggregating the costume colors of different superheroes and I'd love to be able to color each slice with the color it is referring to - so the slice for 'Black' is colored black, the slice for 'Green' is colored green and so forth.

I'm fairly new to DC.js so accept that it may not be possible, but wanted to throw it out there and see if it could be done!

I tried including an array within .ordinalColors but couldn't figure out if there was a way to pull in the data from the field dynamically. I'm assuming that I'd have to change the data in the .csv file to a string that could be recognised as a color reference, but not sure how to go about doing that.

function show_costume_color(ndx) {
   var costume_color_dim = ndx.dimension(dc.pluck('Costume Colour'));
   var costume_color = costume_color_dim.group();

dc.pieChart('#costume-color')
    .width(500)
    .height(500)
    .radius(500)
    .innerRadius(100)
    .slicesCap([7])
    .transitionDuration(1500)
    .dimension(costume_color_dim)
    .group(costume_color);
}

CSV data comes in the below format

ID,name,Gender,Eye color,Race,Hair color,Publisher,Alignment,Superpower,Superpower Strength Level,Costume 
Colour
0,A-Bomb,Male,Yellow,Human,No Hair,Marvel Comics,Good,Superhuman 
Strength,10,None
1,Abin Sur,Male,Blue,Ungaran,No Hair,DC Comics,Good,Cosmic Power,40,Green
Gordon
  • 19,811
  • 4
  • 36
  • 74
  • Welcome to SO! Generally you should use only the most specific tags when asking a question, because otherwise it will attract attention from people who don't know what you're talking about. And sometimes they react negatively. This question is strictly about dc.js so I've removed the other tags. It is clearly on topic about how to program this library, so I can't interpret the close vote any other way. – Gordon Jan 07 '19 at 14:45

2 Answers2

0

Yes, of course. Everything is specified dynamically in dc.js.

Assuming you are using dc.js v3 (and d3 v4+) the way I would suggest doing this is by creating another CSV file with the color assignments you want, something like

Name, RGB
Red, #ff1122
Blue, #1133ff
...

Then you can load the second file in parallel with your data using Promise.all(),

Promise.all([d3.csv('data.csv'), d3.csv('colors.csv')])
    .then(function(data, colors) {
  // rest of code will go here
});

ordinalColors is a nice convenience method, but if you want complete control, and to understand exactly what's going on, it's better to supply your own color scale. In this case, we want an ordinal scale, which maps specific discrete values to specific colors.

Under the covers, dc.js always deals with colors by using the colorAccessor to fetch a value for the the item, and then mapping this value using a color scale. You can think of the value that the accessor returns as a "color name", which is pretty convenient because it's exactly what you want here.

So you can populate a d3.scaleOrdinal with the domain of color names and the range of RGB colors:

var colorScale = d3.scaleOrdinal()
    .domain(colors.map(row => row.Name))
    .range(colors.map(row => row.RGB));

Now supply it to your chart using .colors():

chart.colors(colorScale);

What's really handy about this approach is that you can supply the same color scale for multiple charts, in order to make sure they are consistent. This is something that you don't get automatically in dc.js, because charts don't know very much about each other.

Gordon
  • 19,811
  • 4
  • 36
  • 74
  • Hi Gordon, thanks so much for your response, it's really helpful. I've given it a go in my dashboard and I'm getting an 'Uncaught TypeError: d3.scaleOrdinal is not a function' when I try and run the code. I Googled it and there was a suggestion that it might be to do with running an old version of d3.js but I've made sure I'm running an up to date version so not really sure what the issue is - any suggestions? – TwelvePercentHero Jan 07 '19 at 20:07
  • Gee that really does sound like you're still on d3 v3 (and thus dc.js v2), unless it's a typo or something. Try `head d3.js` to be sure? I can't tell from the code you pasted. All of this will work with the old version, the main difference being the Promise stuff. That said it's best to move on.. – Gordon Jan 07 '19 at 20:24
  • I've just double checked my d3 and dc versions and I'm definitely running d3 v5.7.0 and dc v3.0.9. Does it make any difference that I'm using queue.js to load the files so haven't actually done any of the Promise stuff outlined above? Apologies if these are stupid questions, as I said in the OP I'm pretty new to all of this! – TwelvePercentHero Jan 07 '19 at 20:35
  • Weird. Using queue.js shouldn't matter - I didn't know that worked with d3v5. Hard for me to guess what might be going wrong without seeing running code. I guess I would stick a breakpoint on the offending line and check `d3.version` and `d3.scaleOrdinal` from the browser debug console. Are you using a module loader or bundles or just vanilla JS? – Gordon Jan 07 '19 at 20:43
  • Editor is here - [link](https://ide.c9.io/twelvepercenthero/milestone-project-2) if you have a chance to look at the code, the offending section starts on line 42. It's entirely possible I'm missing something really obvious, so if you can take a look it would be really helpful. – TwelvePercentHero Jan 07 '19 at 20:52
  • Sorry, I don't have a Cloud9 account. Could you post a jsfiddle or something? – Gordon Jan 07 '19 at 20:59
  • Hi Gordon, sorry for the delay in response but I've just put the code into a jsfiddle [here](https://jsfiddle.net/TwelvePercentHero/g1oq6jhs/1/) – TwelvePercentHero Jan 09 '19 at 19:23
  • I don't understand. This is clearly d3v3/dc v2 code, due to all the `d3.scale.ordinal`s in there. Is it working without the code from my answer? You just need to decide between one version or the other. If you are using d3v3, then use `d3.scale.ordinal`. If you are using d3v4+, use `d3.scaleOrdinal`. I would need a complete example in order to help debug this further, i.e. including the libraries you are using and the data. That's kind of a PITA I know... I could also fix your fiddle but that's a bit of work and I don't have time right now. – Gordon Jan 13 '19 at 17:00
  • Also, if you want to use d3v5 you'll need to switch from d3.queue to promises. Yes it's tedious but that's progress! An exhaustive list of changes is found in d3's [CHANGES.md](https://github.com/d3/d3/blob/master/CHANGES.md) – Gordon Jan 13 '19 at 17:04
  • Hi again Gordon, apologies for the delay in getting back to you but I've just been trying to get my head around everything. I have made some changes to the basis of my dashboard to use promises rather than d3.queue - jsfiddle is updated and can be found [here](https://jsfiddle.net/TwelvePercentHero/g1oq6jhs/2/#&togetherjs=we125gM1qw). The test graph I've created isn't displaying so I'm sure I've missed something, but I think I'm getting there... – TwelvePercentHero Jan 23 '19 at 20:52
  • Yeah, you have to include your data & library resources differently when using jsfiddle. Currently your fiddle doesn't have either, because they are still pointing to relative paths which don't exist. If you're not familiar with jsfiddle it may be easier to use bl.ocks.org or blockbuilder.org. Otherwise, I started adding the library dependencies in [this fiddle](https://jsfiddle.net/gordonwoodhull/udaL15o3/1/) and you could look at [this answer](https://stackoverflow.com/questions/22890836/loading-external-csv-file-in-jsfiddle) for a good way to add data to a fiddle. – Gordon Jan 28 '19 at 15:14
0

So, I managed to figure it out through an extensive period of trial and error and now I'm off and away with my dashboard. Thanks for your help, Gordon - it really made the difference! It needs a bit of tidying up but my working test code is below.

// Bring in data from both csv files

Promise.all([d3.csv("../data/heroes_information.csv"), 
d3.csv("../data/costume_colors.csv")])
    .then(function(data) {

        // Tidy up data before use

        data.forEach(function(d) {
            d.Height = +d.Height;
            d.Weight = +d.Weight;
            d.Strength = +d.Strength;
        });

        // Bring in colorScale to dynamically color pie chart slices

        var ndxcol = crossfilter(data[1]);

        var colorScale = d3.scaleOrdinal()
            .domain(data[1].map(row => row.Name))
            .range(data[1].map(row => row.RGB));

        // Bring in superhero data

        var ndx = crossfilter(data[0]);

        // Define chart types

        var publisherSelector = dc.selectMenu('#publisher-selector')
        var genderChart = dc.rowChart('#gender-balance');

        // Define chart dimensions

        var publisherChoice = ndx.dimension(dc.pluck('Publisher'));
        var genderBalance = ndx.dimension(dc.pluck('Gender'));

        // Define chart groups

        var genderNumber = genderBalance.group();
        var publisherNumber = publisherChoice.group();

        // Draw charts

        publisherSelector
            .dimension(publisherChoice)
            .group(publisherNumber);

        genderChart
            .width(500)
            .height(200)
            .margins({ top: 30, right: 30, bottom: 30, left: 30 })
            .dimension(genderBalance)
            .group(genderNumber)
            .gap(6)
            .colors(colorScale)
            .transitionDuration(500)
            .x(d3.scaleOrdinal())
            .elasticX(true);

        dc.renderAll();

    });