How to implement D3 scales to have children inherit colour from parent with graduations?

亡梦爱人 提交于 2020-01-24 20:57:08

问题


I have a D3.js tree that have different colours applied to nodes and links.

The colouring is hardcoded:

nodeUpdate.select("circle")
      .attr("r", 10)
       .style("fill", function(d) { 
     if(d.name == "Top Level") {
      return d._children ? "blue" : "#fff"; 
      }
    if(d.name == "Second A") {
     return d._children ? "red" : "#fff"; 
    }
      if(d.name == "Second B") {
      return d._children ? "green" : "#fff"; 
      }
    if(d.name == "Second C") {
      return d._children ? "purple" : "#fff"; 
      }
      if(d.name == "Second D") {
      return d._children ? "gold" : "#fff"; 
      }
    })
     .style("stroke", function(d) { 
     if(d.name == "Top Level") {
      return "blue"; 
      }
    if(d.name == "Second A") {
     return "red"; 
    }
      if(d.name == "Second B") {
      return "green"; 
      }
    if(d.name == "Second C") {
      return "purple"; 
      }
      if(d.name == "Second D") {
      return "gold"; 
      }
    });

and

link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("stroke-width", function(d){
        return 1;
      })
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      })
    .style("stroke", function(d) {
      return linkColor(d.target.name);
    });

and

function linkColor(node_name) {
    switch (node_name)
    {
      case 'Second A': case 'Third A':  case 'Third B': 
        return 'red';
        break;
      case 'Second B': case 'Third C':  case 'Third D': 
        return 'green';
        break;
      case 'Second C': case 'Third E':  case 'Third F': 
        return 'purple';
        break;
      case 'Second D': case 'Third G':  case 'Third H': 
        return 'gold';
    }
}

See fiddle

Rather than hardcoding the colour of each node and link, I want to implement d3-scale to colour the nodes and lines. The solution would have the children 'inherit' their parent's colour along a graduation of that colour automatically given x number of children.

https://www.d3indepth.com/scales/

How can I achieve this?

As always, thanks for your help.


回答1:


The scale you need is an ordinal scale, like this:

var colourScale = d3.scale.ordinal()
    .domain(["Top Level","Second A", "Second B", "Second C", "Second D"])
    .range(["blue","red", "green", "purple", "gold"]);

Then, you can change this whole thing...

.style("fill", function(d) {
    if (d.name == "Top Level") {
        return d._children ? "blue" : "#fff";
    }
    if (d.name == "Second A") {
        return d._children ? "red" : "#fff";
    }
    if (d.name == "Second B") {
        return d._children ? "green" : "#fff";
    }
    if (d.name == "Second C") {
        return d._children ? "purple" : "#fff";
    }
    if (d.name == "Second D") {
        return d._children ? "gold" : "#fff";
    }
})

...into just:

.style("fill", function(d) {
    return colourScale(d.name)
})

And also get rid of that linkColor function.

Pay attention to the fact that, despite you have linked D3 v5 documentation, you're using D3 v3.

Here is your code with that change:

var treeData = [{
  "name": "Top Level",
  "children": [{
    "name": "Second A",
    "children": [{
      "name": "Third A"
    }, {
      "name": "Third B"
    }]
  }, {
    "name": "Second B",
    "children": [{
      "name": "Third C"
    }, {
      "name": "Third D"
    }]
  }, {
    "name": "Second C",
    "children": [{
      "name": "Third E"
    }, {
      "name": "Third F"
    }]
  }, {
    "name": "Second D",
    "children": [{
      "name": "Third G"
    }, {
      "name": "Third H"
    }, ]
  }, ]
}];

var colourScale = d3.scale.ordinal()
  .domain(["Top Level", "Second A", "Second B", "Second C", "Second D"])
  .range(["blue", "red", "green", "purple", "gold"]);


// ************** Generate the tree diagram	 *****************
var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 960 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

var i = 0,
  duration = 750,
  root;

var tree = d3.layout.tree()
  .size([height, width]);

var diagonal = d3.svg.diagonal()
  .projection(function(d) {
    return [d.y, d.x];
  });

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;

update(root);

d3.select(self.frameElement).style("height", "500px");


// Collapse after the second level
root.children.forEach(collapse);

update(root);

// Collapse the node and all it's children
function collapse(d) {
  if (d.children) {
    d._children = d.children
    d._children.forEach(collapse)
    d.children = null
  }
}

function update(source) {



  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
    links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 180;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  nodeEnter.append("circle")
    .attr("r", 1e-6)
    .style("fill", function(d) {
      return d._children ? "#C0C0C0" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x", function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  nodeUpdate.select("circle")
    .attr("r", 10)
    .style("fill", function(d) {
      return d.depth === 2 ? colourScale(d.parent.name) : colourScale(d.name);
    })
    .style("stroke", function(d) {
      return d.depth === 2 ? colourScale(d.parent.name) : colourScale(d.name);
    });

  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 1e-6);

  nodeExit.select("text")
    .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links, function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("stroke-width", function(d) {
      return 1;
    })
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .style("stroke", function(d) {
      return d.target.depth === 2 ? colourScale(d.target.parent.name) : colourScale(d.target.name);
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}


// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: #C0C0C0;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #C0C0C0;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>


来源:https://stackoverflow.com/questions/56310528/how-to-implement-d3-scales-to-have-children-inherit-colour-from-parent-with-grad

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!