Saturday, 3 April 2021

D3 How to add rounded top border to a stacked bar chart

I have a project where I need to create a stacked bar chart. This bar chart needs to have a rounded border at the top. I have found some documentation on how to round the top border of bar chart. The example I followed can be found here: Rounded top corners for bar chart.

It says to add a attribute with the following:

`
 M${x(item.name)},${y(item.value) + ry}
 a${rx},${ry} 0 0 1 ${rx},${-ry}
 h${x.bandwidth() - 2 * rx}
 a${rx},${ry} 0 0 1 ${rx},${ry}
 v${height - y(item.value) - ry}
 h${-x.bandwidth()}Z
`

You also need to declare two variables rx and ry that define the sharpness of the corner.

The problem I am facing is that I cannot get it to work with my stacked bar chart. The top stack needs to be rounded at the top. Also when the top stack is 0 zero the next stack needs to be rounded. So that the top of the bar is always rounded no matter what data is included.

I have added a trimmed down snippet. It includes a button to transition where I remove(set to zero) the top stacks. This snippet of course also includes the attribute needed to round the corner. But it doesn't get rounded.

this.width = 400;
this.height = 200;
var margin = {
  top: 20,
  right: 20,
  bottom: 30,
  left: 40
}

this.index = 0;

this.svg = d3
  .select(".canvas")
  .classed("svg-container", true)
  .append("svg")
  .attr("class", "chart")
  .attr(
    "viewBox",
    `0 0 ${this.width} ${this.height}`
  )
  .attr("preserveAspectRatio", "xMinYMin meet")
  .classed("svg-content-responsive", true)
  .append("g");

const scale = [0, 1200];

// set the scales
this.xScale = d3
  .scaleBand()
  .range([0, width])
  .padding(0.3);

this.yScale = d3.scaleLinear().range([this.height, 0]);

var bars = this.svg.append("g").attr("class", "bars");

const update = data => {
  const scale = [0, 1200];

  // Update scales.
  this.xScale.domain(data.map(d => d.key));
  this.yScale.domain([scale[0], scale[1]]);

  const subgroups = ["home", "work", "public"];

  var color = d3
    .scaleOrdinal()
    .domain(subgroups)
    .range(["#206BF3", "#171D2C", "#8B0000"]);

  var stackData = d3.stack().keys(subgroups)(data);

  const rx = 12;
  const ry = 12;

  // Set up transition.
  const dur = 1000;
  const t = d3.transition().duration(dur);

  bars
    .selectAll("g")
    .data(stackData)
    .join(
      enter => enter
      .append("g")
      .attr("fill", d => color(d.key)),

      null, // no update function

      exit => {
        exit
          .transition()
          .duration(dur / 2)
          .style("fill-opacity", 0)
          .remove();
      }
    ).selectAll("rect")
    .data(d => d, d => d.data.key)
    .join(
      enter => enter
      .append("rect")
      .attr("class", "bar")
      .attr("x", d => {
        return this.xScale(d.data.key);
      })
      .attr("y", () => {
        return this.yScale(0);
      })
      .attr("height", () => {
        return this.height - this.yScale(0);
      })
      .attr("width", this.xScale.bandwidth())

      .attr(
        'd',
        item =>
        `M${this.xScale(item.name)},${this.yScale(item.value) + ry}
        a${rx},${ry} 0 0 1 ${rx},${-ry}
        h${this.xScale.bandwidth() - 2 * rx}
        a${rx},${ry} 0 0 1 ${rx},${ry}
        v${this.height - this.yScale(item.value) - ry}
        h${-this.xScale.bandwidth()}Z
        `
      ),
      null,
      exit => {
        exit
          .transition()
          .duration(dur / 2)
          .style("fill-opacity", 0)
          .remove();
      }
    )
    .transition(t)
    .delay((d, i) => i * 20)
    .attr("x", d => this.xScale(d.data.key))
    .attr("y", d => {
      return this.yScale(d[1]);
    })
    .attr("width", this.xScale.bandwidth())
    .attr("height", d => {
      return this.yScale(d[0]) - this.yScale(d[1]);
    });
};

const data = [
  [{
      key: "1",
      home: 282,
      work: 363,
      public: 379
    },
    {
      key: "2",
      home: 232,
      work: 432,
      public: 0
    }
  ],
  [{
      key: "1",
      home: 282,
      work: 363,
      public: 379
    },
    {
      key: "2",
      home: 232,
      work: 0,
      public: 0
    }
  ]
];

update(data[this.index]);

const swap = document.querySelector(".swap");
swap.addEventListener("click", () => {
  if (this.index < 1) this.index += 1;
  else this.index = 0;
  update(data[this.index]);
});
<button class="swap">swap</button>
<div class="canvas"></div>
<script src="https://d3js.org/d3.v6.js"></script>


from D3 How to add rounded top border to a stacked bar chart

No comments:

Post a Comment