3
votes

I hope the title is not too confusing, but I am having a hard time to formulate this problem in a clear way. I'll give it a try anyway. Firstly, this is my pie chart, and this is my data:

DATA.CSV
voce;categoria;val2015
amministrazione;funzioni;404571081
sociale;funzioni;235251679
territorio e ambiente;funzioni;286164667
viabilità e trasporti;funzioni;144185664
istruzione;funzioni;168774925
cultura;funzioni;55868045
sport;funzioni;27219432
turismo;funzioni;9544845
sviluppo economico;funzioni;14790363
servizi produttivi;funzioni;4334
polizia locale;funzioni;99007202
giustizia;funzioni;12147068
anticipazioni di cassa;rimborso prestiti;304323808
finanziamenti a breve termine;rimborso prestiti;0
prestiti obbligazionari;rimborso prestiti;38842996
quota capitale di debiti pluriennali;rimborso prestiti;0
quota capitale di mutui e prestiti;rimborso prestiti;128508755
spese per conto terzi;altro;232661261
disavanzo di amministrazione;altro;0

This chart is supposed to show how the government allocates budget to different functions. Each data point is characterized by:

  1. "voce": the function to which a part of the budget is allocated
  2. "categoria": the macro-category of each expense. There are three of these: "funzioni"; "rimborso prestiti"; and "altro"
  3. "val2015": the absolute value of money allocated to that function for year 2015

In my visualization I created two pies on top of each other. The innerPie divides the budget over the three macro-categories. The outerPie divides it over the 19 different functions.

I would like to assign a color to each macro-category (say green, red, and blue respectively). Then I would like to have all the functions of category to be colored in gradual shades of their macro-category's color.

For example, if "funzioni" gets green, then I would like the 12 functions that belong to that category to be gradual shades going from light-green to dark-green.

First, I thought I would assign colors manually. However, I could not find a color brewer that would provide twelve shades of a given color.

Then I thought I would alternate colors. If macro-category1 has green, then function1 would be light-green, function2 dark-green, function3 light-green and so on.. But some functions have an absolute value of 0 (i.e. for that year no budget was allocated to that function). Since these do not appear in the pie, two functions of the same color may appear next to each other. Here is the code:

var color = d3.scale.ordinal()
            .domain(["amministrazione", "sociale", "territorio e ambiente", "viabilità e trasporti", "istruzione", "cultura", "sport", "turismo", "sviluppo economico", "servizi produttivi", "polizia locale", "giustizia", "anticipazioni di cassa", "finanziamenti a breve termine", "prestiti obbligazionari", "quota capitale di debiti pluriennali;", "quota capitale di mutui e prestiti", "spese per conto terzi", "disavanzo di amministrazione"])
            .range(["rgb(116,196,118)", "rgb(35,139,69)", "rgb(116,196,118)", "rgb(35,139,69)", "rgb(116,196,118)", "rgb(35,139,69)", "rgb(116,196,118)", "rgb(35,139,69)", "rgb(116,196,118)", "rgb(35,139,69)", "rgb(116,196,118)", "rgb(35,139,69)", "rgb(251,106,74)", "rgb(103,0,13)", "rgb(251,106,74)", "rgb(103,0,13)", "rgb(251,106,74)", "rgb(116,196,118)", "rgb(0,68,27)"]);

var colorInner = d3.scale.ordinal()
            .domain(["funzioni", "rimborso prestiti", "altro"])
            .range(["rgb(0,68,27)", "rgb(203,24,29)" , "rgb(33,113,181)"]); 

A solution would be to use three-shades for each macro-category. That would solve the problem, but I am looking for a more flexible solution.

Since I plan to add more years in my dataset, how can I make a function that:

  1. Takes the macro-category's color as input
  2. Counts the number of functions in that category with value bigger than 0
  3. Assign x shades of colors to the functions

-----EDIT-----

Now that I created a color scale, I need to assign the colors to each path based on its category (for the macro-color) and its index (for the shade of macro color). I tried two things but neither works.

var greenRange = ["rgb(199,233,192)", "rgb(0,68,27)"];
var redRange = ["rgb(252,187,161)", "rgb(103,0,13)"];
var blueRange = ["rgb(198,219,239)", "rgb(8,48,107)"];

OPTION 1

function draw () {

//(1) count the number of data points with value > in each category
var countFunzioni=0;
dataset.forEach (function (d) {if (d.categoria=="funzioni" && d.val2015>0) { countFunzioni += 1;}})

var countRimborso=0;
dataset.forEach (function (d) {if (d.categoria=="rimborso prestiti" && d.val2015>0) { countRimborso += 1;}})

var countAltro=0;
dataset.forEach (function (d) {if (d.categoria=="altro" && d.val2015>0) { countAltro += 1;}})

//(2) create a color method for each category based on a the count calculated above and the range I determined
var colorFunzioni = d3.scale.linear()
         .domain([0, countFunzioni])
         .range(redRange);

var colorRimborso = d3.scale.linear()
         .domain([0, countRimborso])
         .range(redRange); 

var colorAltro = d3.scale.linear()
         .domain([0, countAltro])
         .range(blueRange);

//draw the chart
chart = d3.select("#visualizationInner")
      .append("svg")
      .attr("id", "visualization")
      .attr("width", w)
      .attr("height", h)
      .append("g")
      .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");

//draw and color the paths
var path = chart.datum(dataset).selectAll("path")
            .data(pie)
            .enter()
            .append("path")
            //(3) return the appropriate color method depending on the datum's category
            .attr("fill", function(d, i) {
              if (d.categoria=="funzioni") {return colorFunzioni(i);}
              else if (d.categoria=="rimborso prestiti") {return colorRimborso(i);}
              else if (d.categoria=="altro") {return colorAltro(i);}
            })
            .style("fill-opacity", 0.75)
            .attr("d", arc);
}

OPTION 2

function draw () {

//(1) same as above

//(2) create a color method that adapts to each category's count and range
  var color = d3.scale.linear()
        .domain([0, function (d) {
          if (d.categoria=="funzioni") {return countFunzioni;}
          else if (d.categoria=="rimborso prestiti") {return countRimborso;}
          else if (d.categoria=="altro") {return countAltro;}
        }])
        .range(function (d) {
          if (d.categoria=="funzioni") {return greenRange;}
          else if (d.categoria=="rimborso prestiti") {return redRange;}
          else if (d.categoria=="altro") {return blueRange;}
        });

////(3) return the appropriate color method depending on the datum's category
        .attr("fill", function(d, i) {return color(i);}

}

1

1 Answers

1
votes

Pick a color and select a light shade and dark shade, this becomes your range. Then, your domain is [0, N] colors. For example, for green we could do:

var N = 12;
var greenRange = ['#cce0cc', '#001e00'];
      
var color = d3.scale.linear()
  .domain([0, N])
  .range(greenRange);
       
  d3.select('body')
    .append('div')
    .selectAll('.color')
    .data(d3.range(N))
    .enter()
    .append('div')
    .attr('class', 'color')
    .style('height', '50px')
    .style('width', '50px')
    .style('background-color', function(d,i){
      return color(i);
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Re-run this with a different N to get a feel for how it produces colors.

EDITS

I imagine option 1 isn't working because when you call return colorFunzioni(i); (or the others), i will be out of range. What you really need is an index for each color. Here's a quick refactor:

var countFunzioni = 0,
    countRimborso = 0,
    countAltro = 0;
dataset.forEach(function(d){
    if (d.categoria=="funzioni" && d.val2015>0){
        d.colorIdx = countFunzioni;            
        countFunzioni += 1;
    } else if (d.categoria=="rimborso prestiti" && d.val2015>0){
        d.colorIdx = countRimborso;
        countRimborso += 1;
    } else if (d.categoria=="altro" && d.val2015>0) {
        d.colorIdx = countAltro;
        countAltro += 1;
    }
});

var colors = {};
colors.funzioni = d3.scale.linear()
    .domain([0, countFunzioni])
    .range(redRange);

colors.rimborso = d3.scale.linear()
    .domain([0, countRimborso])
    .range(redRange); 

colors.altro = d3.scale.linear()
    .domain([0, countAltro])
    .range(blueRange);

Later:

...
.attr("fill", function(d, i) {
    return colors[d.categoria](d.colorIdx); 
});