Hierarchical Bar Chart - move bars to the bottom (not the top)

40 views
Skip to first unread message

ltow...@gmail.com

unread,
Mar 6, 2015, 9:06:26 PM3/6/15
to d3...@googlegroups.com
I am trying to rotate the axis on the hierarchical bar chart located here:


I am half way there, but I can't seem to move the bars to start from the bottom of the graph rather than the top..  Can anyone help? Would love to be able to post this complete demo as another sample somewhere...

<div class="sales-pipeline-chart"></div>

<!DOCTYPE html>
<meta charset="utf-8">
<style>

text {
  font: 10px sans-serif;
}

rect.background {
  fill: white;
}

.axis {
  shape-rendering: crispEdges;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var margin = {top: 30, right: 120, bottom: 30, left: 120},
    width = 600 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var y = d3.scale.linear()
    .range([0,height]);

var barWidth = 20;

var color = d3.scale.ordinal()
    .range(["steelblue", "#ccc"]);

var duration = 750,
    delay = 25;

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

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

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

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

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

svg.append("g")
    .attr("class", "x axis")
  .append("line")
    .attr("x1", "100%")
    .attr("transform", "translate(0," + height + ")");
    

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

function down(d, i) {
  if (!d.children || this.__transition__) return;
  var end = duration + d.children.length * delay;

  // 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", color(true));

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

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

  // 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(" + barWidth * i * 1.2 + ",0)"; });

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

  // Transition entering rects to the new x-scale.
  enterTransition.select("rect")
      .attr("height", function(d) { return y(d.value); })
      .style("fill", function(d) { return color(!!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("height", function(d) { return y(d.value); });

  // Rebind the current node to the background.
  svg.select(".background")
      .datum(d)
    .transition()
      .duration(end);

  d.index = i;
}

function up(d) {
  if (!d.parent || this.__transition__) return;
  var end = duration + d.children.length * delay;

  // 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(" + barWidth * i * 1.2 + ",0)"; })
      .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 color(!!d.children); })
    .filter(function(p) { return p === d; })
      .style("fill-opacity", 1e-6);

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

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

  // Transition entering bars to fade in over the full duration.
  var enterTransition = enter.transition()
      .duration(end)
      .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("height", function(d) { return y(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("height", function(d) { return y(d.value); })
      .style("fill", color(true));

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

  // Rebind the current parent to the background.
  svg.select(".background")
      .datum(d.parent)
    .transition()
      .duration(end);
}

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

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

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

  return bar;
}

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

</script>


Joe Keohan

unread,
Mar 7, 2015, 1:00:55 PM3/7/15
to d3...@googlegroups.com
Having a rect start at the bottom requires that the y scale range be reversed. This is because the svg coordinates by default are 0,0 starting at the top left.

var y = d3.scale.linear()
    .range([height, 0]); //reversed it...it was previously [0,height]

Also, I've always set the height attr for horizontal rect as follows:  
  bar.append("rect")
      .attr("height", function(d) { return height - y(d.value); })

Joe
Reply all
Reply to author
Forward
0 new messages