0

I'm using the following script to generate a bar chart with drill down capability. (source: http://mbostock.github.io/d3/talk/20111116/bar-hierarchy.html).

What I am trying to do is - I want the bars to be different shades of a color depending on the data (pretty much what this question asks - D3.js: Changing the color of the bar depending on the value). Except, in my case... the graph is horizontal and not static so the answer may be different.

So Ideally, at the parent node and all sub nodes except the child node, it will display lets say different shades of blue based on the data, and once it reaches the end after drilling down, the remaining bars will be grey.

I've recently started using d3 and am kinda lost as to where to start. I tried adding different colors to the color range z but that did not work.

Any help will be appreciated! Thanks.

NOTE: in my case, I am assuming.. that after a transition, either all nodes will lead to subnodes OR no node will lead to subnodes. Basically, at no point in the graph will there be bars, where some will drill down further while some won't. This assumption is based on the type of data I want to show with my graph.

<script>

    var m = [80, 160, 0, 160], // top right bottom left
        w = 1280 - m[1] - m[3], // width
        h = 800 - m[0] - m[2], // height
        x = d3.scale.linear().range([0, w]),
        y = 25, // bar height
        z = d3.scale.ordinal().range(["steelblue", "#aaa"]); // bar color

    var hierarchy = d3.layout.partition()
        .value(function(d) { return d.size; });

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("top");

    var svg = d3.select("body").append("svg:svg")
        .attr("width", w + m[1] + m[3])
        .attr("height", h + m[0] + m[2])
      .append("svg:g")
        .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

    svg.append("svg:rect")
        .attr("class", "background")
        .attr("width", w)
        .attr("height", h)
        .on("click", up);

    svg.append("svg:g")
        .attr("class", "x axis");

    svg.append("svg:g")
        .attr("class", "y axis")
      .append("svg:line")
        .attr("y1", "100%");

    d3.json("flare.json", function(root) {
      hierarchy.nodes(root);
      x.domain([0, root.value]).nice();
      down(root, 0);
    });

    function down(d, i) {
      if (!d.children || this.__transition__) return;
      var duration = d3.event && d3.event.altKey ? 7500 : 750,
          delay = duration / d.children.length;

      // Mark any currently-displayed bars as exiting.
      var exit = svg.selectAll(".enter").attr("class", "exit");

      // Entering nodes immediately obscure the clicked-on bar, so hide it.
      exit.selectAll("rect").filter(function(p) { return p === d; })
          .style("fill-opacity", 1e-6);

      // Enter the new bars for the clicked-on data.
      // Per above, entering bars are immediately visible.
      var enter = bar(d)
          .attr("transform", stack(i))
          .style("opacity", 1);

      // Have the text fade-in, even though the bars are visible.
      // Color the bars as parents; they will fade to children if appropriate.
      enter.select("text").style("fill-opacity", 1e-6);
      enter.select("rect").style("fill", z(true));

      // Update the x-scale domain.
      x.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice();

      // Update the x-axis.
      svg.selectAll(".x.axis").transition()
          .duration(duration)
          .call(xAxis);

      // Transition entering bars to their new position.
      var enterTransition = enter.transition()
          .duration(duration)
          .delay(function(d, i) { return i * delay; })
          .attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; });

      // Transition entering text.
      enterTransition.select("text").style("fill-opacity", 1);

      // Transition entering rects to the new x-scale.
      enterTransition.select("rect")
          .attr("width", function(d) { return x(d.value); })
          .style("fill", function(d) { return z(!!d.children); });

      // Transition exiting bars to fade out.
      var exitTransition = exit.transition()
          .duration(duration)
          .style("opacity", 1e-6)
          .remove();

      // Transition exiting bars to the new x-scale.
      exitTransition.selectAll("rect").attr("width", function(d) { return x(d.value); });

      // Rebind the current node to the background.
      svg.select(".background").data([d]).transition().duration(duration * 2); d.index = i;
    }

    function up(d) {
      if (!d.parent || this.__transition__) return;
      var duration = d3.event && d3.event.altKey ? 7500 : 750,
          delay = duration / d.children.length;

      // Mark any currently-displayed bars as exiting.
      var exit = svg.selectAll(".enter").attr("class", "exit");

      // Enter the new bars for the clicked-on data's parent.
      var enter = bar(d.parent)
          .attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; })
          .style("opacity", 1e-6);

      // Color the bars as appropriate.
      // Exiting nodes will obscure the parent bar, so hide it.
      enter.select("rect")
          .style("fill", function(d) { return z(!!d.children); })
        .filter(function(p) { return p === d; })
          .style("fill-opacity", 1e-6);

      // Update the x-scale domain.
      x.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice();

      // Update the x-axis.
      svg.selectAll(".x.axis").transition()
          .duration(duration * 2)
          .call(xAxis);

      // Transition entering bars to fade in over the full duration.
      var enterTransition = enter.transition()
          .duration(duration * 2)
          .style("opacity", 1);

      // Transition entering rects to the new x-scale.
      // When the entering parent rect is done, make it visible!
      enterTransition.select("rect")
          .attr("width", function(d) { return x(d.value); })
          .each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); });

      // Transition exiting bars to the parent's position.
      var exitTransition = exit.selectAll("g").transition()
          .duration(duration)
          .delay(function(d, i) { return i * delay; })
          .attr("transform", stack(d.index));

      // Transition exiting text to fade out.
      exitTransition.select("text")
          .style("fill-opacity", 1e-6);

      // Transition exiting rects to the new scale and fade to parent color.
      exitTransition.select("rect")
          .attr("width", function(d) { return x(d.value); })
          .style("fill", z(true));

      // Remove exiting nodes when the last child has finished transitioning.
      exit.transition().duration(duration * 2).remove();

      // Rebind the current parent to the background.
      svg.select(".background").data([d.parent]).transition().duration(duration * 2);
    }

    // Creates a set of bars for the given data node, at the specified index.
    function bar(d) {
      var bar = svg.insert("svg:g", ".y.axis")
          .attr("class", "enter")
          .attr("transform", "translate(0,5)")
        .selectAll("g")
          .data(d.children)
        .enter().append("svg:g")
          .style("cursor", function(d) { return !d.children ? null : "pointer"; })
          .on("click", down);

      bar.append("svg:text")
          .attr("x", -6)
          .attr("y", y / 2)
          .attr("dy", ".35em")
          .attr("text-anchor", "end")
          .text(function(d) { return d.name; });

      bar.append("svg:rect")
          .attr("width", function(d) { return x(d.value); })
          .attr("height", y);

      return bar;
    }

    // A stateful closure for stacking bars horizontally.
    function stack(i) {
      var x0 = 0;
      return function(d) {
        var tx = "translate(" + x0 + "," + y * i * 1.2 + ")";
        x0 += x(d.value);
        return tx;
      };
    }

</script>
Community
  • 1
  • 1
sparta93
  • 3,684
  • 5
  • 32
  • 63
  • can you make a running example we can look at, such as a jsfiddle? Also, i just want to clarify that you are looking for different shades of 1 color based entirely on the data? – Fallenreaper Oct 10 '16 at 16:30
  • @Fallenreaper here is a fiddle (https://jsfiddle.net/af9gqqth/) and yes I do want different shades of 1 color (based on data) until I've drilled down all the way into the graph.. in which case 1 color is fine to show the user that they are done drilling down. – sparta93 Oct 10 '16 at 17:48
  • @Fallenreaper you can look at this color scheme for example, where they use blue - http://www.ibm.com/developerworks/library/ba-pp-infrastructure-cognos_specific_page702/page702_figure44.jpg – sparta93 Oct 10 '16 at 17:48
  • what kind of color breaks do you want, are you looking for a continuous scale or discrete one like in the first answer of the link in your answer. – NicE Oct 11 '16 at 16:19
  • @NicE I want a continuous one like this one for example - http://www.ibm.com/developerworks/library/ba-pp-infrastructure-cognos_specific_page702/page702_figure44.jpg – sparta93 Oct 11 '16 at 17:41
  • well that one is discrete, it has five levels and each one has a color. On your data, do you want something like if the value is more 100 000 then dark blue, if between 50 000 and 100 000 then light blue etc? – NicE Oct 11 '16 at 17:42
  • @NicE Yes, I think that can work as well.. as long as they are different shades of blue – sparta93 Oct 11 '16 at 17:44

2 Answers2

1

you can create a new scale to handle the "shades" of your colors,

var shades = d3.scale.sqrt()
.domain([your domain])
.clamp(true)
.range([your range]);

and create a variable to control the "depth" of your drill-down, so when you are going to color your bars, you simply set the level of the color "shade" with d3.lab (Doc), like this:

   function fill(d) {
      var c = d3.lab(colorScale(d.barAttr));
      c.l = shades(d.depth);
      return c; 
   }
Douglas Cardona
  • 151
  • 1
  • 6
  • Can you show this in the jfiddle? jsfiddle.net/af9gqqth – sparta93 Oct 11 '16 at 15:41
  • Hi there, i'm not very fond of jsfiddle but here it is, [link](http://jsfiddle.net/af9gqqth/3/), i choose to randomize the color shades level, for simplicity (it makes it very easy to see), i hope this will help you... – Douglas Cardona Oct 13 '16 at 03:28
  • Hi, this helped a lot... but I have one question... when I try to move up to a parent node in the graph, while transitioning, some of the bars are black at first and then change to a shade of blue. Is there anyway we can hide the black color in the transition/animation? – sparta93 Oct 17 '16 at 20:46
  • sure, i forgot to update the fill in the up function (i leave there a console.log, i'm sorry), [jfiddle] (http://jsfiddle.net/af9gqqth/4/), well everything is still randomize, so i leave you the part of choosing the colors... the change is made in the line 188 of the js, if you want to check. – Douglas Cardona Oct 18 '16 at 15:03
0

Using the same code as the second in the link you posted you could add (the ellipsis indicates nothing is changed compared to the code in the fiddle):

//initialize the scale
var colors = ["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"];

var colorScale = d3.scale.quantile() 

Then when d3 reads the data, you need to add a domain and a range. This assumes that all the biggest value any bar would have is in the children of the root node (ie in the bars that are initially displayed).

d3.json(..., function(root) {
  ...
  colorScale.domain([0, colors.length - 1,d3.max(root.children, function(d) {
    return d.value;
  })]).range(colors);
 ...
});

You can then use the colorScale to color the bars according the value during the transitions. Here are the lines I modified:

enter.select("rect").style("fill", colorScale(d.value));
    ...
    enterTransition.select("rect")
        .attr("width", function(d) {
          return x(d.value);
        })
        .style("fill", function(d) {
            if(!d.children) return "#aaa";
          return colorScale(d.value);
        });
    ...
    enter.select("rect")
        .style("fill", function(d) {
          return colorScale(d.value);
        })
        .filter(function(p) {
          return p === d;
        })
        .style("fill-opacity", 1e-6);
    ...
    exitTransition.select("rect")
        .attr("width", function(d) {
          return x(d.value);
        })
        .style("fill", colorScale(d.value));

Here's a working fiddle: https://jsfiddle.net/f640v0yj/2/

NicE
  • 21,165
  • 3
  • 51
  • 68
  • At the root level, all bars are of the same color in the fiddle.... They should be different shades based on data. – sparta93 Oct 17 '16 at 20:43
  • well I asked in a comment in your question and you said that's what you wanted (above 100 000 darkblue, between 50 000 and 100 000 lightblue etc)... – NicE Oct 17 '16 at 20:45