javascriptd3.jsdata-visualization

d3 x scale and labels at the bottom


I'm trying to build a vertical drill down bar graph. I'm referring to this link https://observablehq.com/@d3/hierarchical-bar-chart. The graph in the link is a horizontal bar chart but I'm building a vertical one. I have managed to build the graph but there is an issue with the xAxis positioning. I'm trying to position it down like how we do in the normal bar chart but it seems to be not working.My code and image are given below.

enter image description here

As you can see from the image the xAxis and the labels are on the top. I want it in the bottom. I have used d3.axisBottom but it doesnt seem to be working.

code

var margin = { top: 30, right: 50, bottom: 30, left: 50 },
  width = 400 - margin.left - margin.right,
  height = 250 - margin.top - margin.bottom;

var y:any = d3.scaleLinear()
  .range([height, 0]);

var x = d3.scaleBand()
  .range([0, width])
//   .domain(flare.map(function(d) { return d.name; }))
  // .padding(0.2);

var barWidth = 25;

var color:any = d3.scaleOrdinal()
  .range(["steelblue"]);

var duration = 750,
  delay = 25;

var yAxis:any = d3.axisLeft(y);

var xAxis:any = d3.axisBottom(x);


var svg: any = d3.select("#Area")
  .append("svg")
  .attr('preserveAspectRatio', 'xMinYMin meet')
  .attr(
    'viewBox',
    '0 0 ' +
    (width + margin.left + margin.right) +
    ' ' +
    (height + margin.top + margin.bottom)
  )
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")")

svg.append("rect")
  .attr("class", "background")
  .attr("width", width + margin.left + margin.right)
  .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%")
  .call(d3.axisBottom(x));

  


var root = d3.hierarchy(flare)
  .sum(d => d['size']);
y.domain([0, root.value]).nice();
down(root, 0);

function down(d, i) {
  if (!d.children) 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(p => 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 y-scale domain.
  y.domain([0, d3.max(d.children, d => d['value'])]).nice();

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

  // 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((d, i) => i * delay)
    .attr("transform", (d, i) => `translate(${barWidth * i * 1.2 + 5}, 0)`);

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

  // Transition entering rects to the new y-scale.
  enterTransition.select("rect")
    .attr("height", d => height - y(d['value']))
    .attr("width", barWidth)
    .attr("y", d => y(d['value']))
    .attr("x", d => x(d['data'].name))
    .attr("width", barWidth)
    .style("fill", d => 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 y-scale.
  exitTransition.selectAll("rect")
    .attr("height", d => height - y(d['value']))
    .attr("width", barWidth)
    .attr("y", d => y(d['value']))
    .attr("x", d => x(d['data'].name))
    .attr("width", barWidth)

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

  d.index = i;
}

function up(d) {
  if (!d.parent) 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", (d, i) => `translate(${barWidth * i * 1.2 + 5}, 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", d => color(!!d['children']))
    .filter(p => p === d)
    .style("fill-opacity", 1e-6);

  // Update the y-scale domain.
  y.domain([0, d3.max(d.parent.children, d => d['value'])]).nice();

  // Update the y-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 y-scale.
  // When the entering parent rect is done, make it visible!
  enterTransition.select("rect")
    .attr("height", d => height - y(d['value']))
    .attr("width", barWidth)
    .attr("y", d => y(d['value']))
    .attr("x", d => x(d['data'].name))
    .attr("width", barWidth)
    .on("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((d, i) => 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", d => height - y(d['value']))
    .attr("width", barWidth)
    .attr("y", d => y(d['value']))
    .attr("x", d => x(d['data'].name))
    .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(0,0)")
    .selectAll("g")
    .data(d.children)
    .enter().append("g")
    .style("cursor", d => !d['children'] ? null : "pointer")
    .on("click", down);
  bar.append("rect")
    .attr("height", d => height - y(d['value']))
    .attr("width", barWidth)
    .attr("y", d =>  y(d['value']))
    .attr("x", d => x(d['data'].name))
  bar.append("text")
    .attr("x", -15)
    .attr("y", -barWidth / 2)
    .attr("dx", ".35em")
    .attr("transform", "rotate(90)")
    .style("text-anchor", "end")
    .text(d => d['data'].name);
  return bar;
}

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

Solution

  • axisBottom just dictates the direction of the ticks and labels. You still need to move it in place.

    Change this:

    svg.append("g")
      .attr("class", "x axis")
      .append("line")
      .attr("x1", "100%")
      .call(d3.axisBottom(x));
    

    To:

    svg.append('g')
        .attr('class','x axis')
        .attr('transform','translate(0,' + height + ')') // Move the axis to the bottom
        .call(d3.axisBottom(x))
        .append("line")
        .attr("x1", "100%")