D3如何为堆叠条形图添加圆形顶部边框

gmxoilav  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(137)

我有一个项目,我需要创建一个堆叠条形图。此条形图的顶部需要有圆形边框。我发现了一些关于如何圆条形图的顶部边界的文档。我的例子可以在这里找到:Rounded top corners for bar chart.
它说要添加一个属性,如下所示:

`
 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
`

您还需要声明两个变量rxry来定义角的锐度。
我面临的问题是,我不能让它与我的堆叠条形图工作。顶部堆栈需要在顶部圆角。同样,当顶部堆栈为0时,下一个堆栈需要舍入。因此,无论包含什么数据,条形图的顶部始终是四舍五入的。
我添加了一个修剪下来的片段。它包括一个按钮,以过渡到我删除(设置为零)顶部堆栈。这个片段当然也包括圆角所需的属性。但它并不圆。

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>
pinkon5k

pinkon5k1#

rect的绘图更改为path。所以.append("rect")变成了.append("path"),你不再需要属性xyheightwidth
为了只舍入顶部堆栈,可以在d3绘制最后一个子组(d[1] - d[0] == d.data[subgroups[subgroups.length-1]])之前将rxry设置为12,在本例中,这是“public”,否则将它们设置为0。
最后,关于你的最后一个问题:
当栈顶为0时,下一个栈需要被舍入
堆栈按顺序绘制。在绘制每个堆栈之前,找出下一个堆栈/子组是否为零,如果是,则设置rxry = 12。要找出这一点,您需要获得当前正在绘制的子组,以便您可以确定下一个子组将是什么,并获得该子组的值。

const current_subgroup = Object.keys(d.data).find(key => d.data[key] === d[1] - d[0]);
const next_subgroup_index = Math.min(subgroups.length - 1, subgroups.indexOf(current_subgroup)+1);
const next_subgroup_data = stackData[next_subgroup_index][i];
            if (next_subgroup_data[1] - next_subgroup_data[0] == 0) { rx = 12; ry = 12; }
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);

  let rx = 12;
  let 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("path")
    .data(d => d, d => d.data.key)
    .join(
      enter => enter
      .append("path")
      .attr("class", "bar")
      .attr(
        'd',
        d =>
        `M${this.xScale(d.data.key)},${this.yScale(0)}
        a0,0 0 0 1 0,0
        h${this.xScale.bandwidth()}
        a0,0 0 0 1 0,0
        v${this.height - this.yScale(0)}
        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(
      'd',
      (d, i) => {
        //if last subgroup, round the corners of the stack
        if (d[1] - d[0] == d.data[subgroups[subgroups.length-1]]) { rx = 12; ry = 12; }
        else { rx = 0; ry = 0; }
        
        //if next subgroup is zero, round the corners of the stack
        const current_subgroup = Object.keys(d.data).find(key => d.data[key] === d[1] - d[0]);
        const next_subgroup_index = Math.min(subgroups.length - 1, subgroups.indexOf(current_subgroup)+1);
        const next_subgroup_data = stackData[next_subgroup_index][i];
        if (next_subgroup_data[1] - next_subgroup_data[0] == 0) { rx = 12; ry = 12; }
        
        //draw the stack
        if (d[1] - d[0] > 0) {
          return `M${this.xScale(d.data.key)},${this.yScale(d[1]) + 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.yScale(d[0]) - this.yScale(d[1]) - ry}
          h${-this.xScale.bandwidth()}Z
          `
        } else {
          return `M${this.xScale(d.data.key)},${this.yScale(d[1])}
          a0,0 0 0 1 0,0
          h${this.xScale.bandwidth()}
          a0,0 0 0 1 0,0
          v${this.yScale(d[0]) - this.yScale(d[1]) }
          h${-this.xScale.bandwidth()}Z
          `
        }
      }
    );
};
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>

相关问题