How would I create my own color scale based on the id of my data in d3?

Viewed 142

I am trying to add a custom color scale based on the IDs of my dataset. I would like each continent to be it's own color. Currently the color scale is set by

var fill = d3.scale.category20(); 

and the code,

n.append("circle")
              .attr("r",  function(d) { return d.r; })
              .style("fill", function(d) { return fill(d.id); })

sets the colors based on the above color scale. I am trying to create my own color scale with my own hex codes based on the IDs of the data. How would I do this? I tried to change the IDs to categories but it messed with my animation. If you would like to play around with the code yourself, here's the link to the code on CodePen: (https://codepen.io/jaswar1/pen/QWdRVXp)

  var data = [
      //Americas
      {"id": "0", "name": "U.S.A", "r": 62.20, "value": 22.20 },
      {"id": "0", "name": "Brazil", "r": 42.06, "value": 2.06 },
      {"id": "0", "name": "Canada", "r": 41.83, "value" :1.83 },
      {"id": "0", "name": "Mexico", "r": 41.3, "value": 1.30 },
      {"id": "0", "name": "Argentina", "r": 40.52, "value": 0.52 },
      
      //Asia
      {"id": "1", "name": "China", "r": 55.47, "value": 15.47 },
      {"id": "1", "name": "Japan", "r": 45.50, "value": 5.50 },
      {"id": "1", "name": "India", "r": 43.26, "value": 3.26 },
      {"id": "1", "name": "South Korea", "r": 41.74, "value": 1.74 },
      {"id": "1", "name": "Russia", "r": 41.67, "value": 1.67 },
      {"id": "1", "name": "Indonesia", "r": 41.21, "value": 1.21},
      {"id": "1", "name": "Turkey", "r": 40.81, "value": 0.81 },
    
      //Europe
      {"id": "2", "name": "Germany", "r": 44.16, "value": 4.16 },
      {"id": "2", "name": "United Kingdom", "r": 42.93, "value": 2.93},
      {"id": "2", "name": "Italy", "r": 42.09, "value": 2.09 },
      {"id": "2", "name": "Spain", "r": 41.50, "value": 1.50 },
      {"id": "2", "name": "Netherlands", "r": 40.95, "value": 0.95 },
      {"id": "2", "name": "Switzerland", "r": 40.74, "value": 0.74 },
      
      //Oceania
      {"id": "3", "name": "Australia", "r": 41.48, "value": 1.48 },
      {"id": "3", "name": "New Zealand", "r": 40.207, "value": 0.207 },
      
      //Africa 
      {"id": "4", "name": "Saudi Arabia", "r": 40.79, "value": 0.79},
      {"id": "4", "name": "South Africa", "r": 40.351, "value": 0.351},
      
    ];
    
    var width = window.innerWidth,
        height = 550;
    
    var fill = d3.scale.category20();
            
    
    var nodes = [], labels = [],
        foci = [{x: -100, y: 150}, {x: 350, y: 150}, {x: 200, y: 150}, {x: 400, y: 150}, {x: 500, y: 150}];
    
    var svg = d3.select("body").append("svg")
        .attr("width", "100%")
        .attr("height", height)
    
    var force = d3.layout.force()
        .nodes(nodes)
        .links([])
        .charge(-400)
    
        .gravity(0.1)
        .friction(0.9)
        .size([width, height])
        .on("tick", tick);
    
    var node = svg.selectAll("g");
    
    var counter = 0;
    
    function tick(e) {
      var k = .1 * e.alpha;
    
      // Push nodes toward their designated focus.
      nodes.forEach(function(o, i) {
        o.y += (foci[o.id].y - o.y) * k;
        o.x += (foci[o.id].x - o.x) * k;
      });
    
      node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
    
    }
    
    
    var timer = setInterval(function(){
    
      if (nodes.length > data.length-1) { clearInterval(timer); return;}
    
      var item = data[counter];
      nodes.push({id: item.id, r: item.r, name: item.name});
      force.start();
    
      node = node.data(nodes);
    
      var n = node.enter().append("g")
          .attr("class", "node")
          .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
          .style('cursor', 'pointer')
          .on('mousedown', function() {
             var sel = d3.select(this);
             sel.moveToFront();
          })
          .call(force.drag);
    
      n.append("circle")
          .attr("r",  function(d) { return d.r; })
          .style("fill", function(d) { return fill(d.id); })
    
      n.append("text")
          .text(function(d){
              return d.name
              return d.value;
          })
          .style("font-size", function(d) {
              return Math.min(1 * d.r, (1 * d.r - 8) / this.getComputedTextLength() * 16) + "px"; 
           })
          .attr("dy", ".2em")
    
      counter++;
    }, 100);
    
    
    d3.selection.prototype.moveToFront = function() {
      return this.each(function(){
        this.parentNode.appendChild(this);
      });
    };
    
    function resize() {
      width = window.innerWidth;
      force.size([width, height]);
      force.start();
    }
    
    d3.select(window).on('resize', resize)
1 Answers

There are several ways to do this:

  1. Replace line 39 var fill = d3.scale.category20(); with:
var fill = new Map([
  [0, '#3182bd'],
  [1, '#9ecae1'],
  [2, '#fd8d3c'],
  [3, '#fdae6b'],
  [4, '#31a354']
]);

and lines 98 fill(d.id) with this (lines 96-98 shown):

  n.append("circle")
      .attr("r",  function(d) { return d.r; })
      .style("fill", function(d) { return fill.get(d.id); })

(you could also use an object literal var fill = {...} or an array var fill = [...] and access that with fill[d.id])

https://codepen.io/Alexander9111/pen/rNjEKEP

  1. You could write a new fill function:
var fill = (id) => {
  const arr = [
    '#3182bd',
    '#9ecae1',
    '#fd8d3c',
    '#fdae6b',
    '#31a354'
  ];
  const idx = parseInt(id) % arr.length;
  return arr[idx];
};

and your line 98 would stay the same (line(d.id))

https://codepen.io/Alexander9111/pen/mdRZjeW

  1. You could possibly use CSS classes: https://codepen.io/Alexander9111/pen/oNBrMez
  2. (My preferred approach, keeping everything data-driven) You could modify your data structure to have the color defined there: https://codepen.io/Alexander9111/pen/jOyjpGX
Related