D3 如何将圆形顶部边框添加到堆积条形图

问题描述

我有一个项目需要创建堆积条形图。此条形图需要在顶部有一个圆形边框。我找到了一些关于如何将条形图的顶部边框变圆的文档。我遵循的示例可以在这里找到: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}
 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}
        h${this.xScale.bandwidth() - 2 * rx}
        a${rx},${ry}
        v${this.height - this.yScale(item.value) - ry}
        h${-this.xScale.bandwidth()}Z
        `
      ),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",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>

解决方法

rect 的绘图更改为 path。因此,.append("rect") 变为 .append("path"),您不再需要属性 xyheightwidth

要仅舍入顶部堆栈,您可以在 d3 绘制最后一个子组 (rx) 之前将 ryd[1] - d[0] == d.data[subgroups[subgroups.length-1]] 设置为 12,在本例中为“公共”,否则将它们设置为 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
        v${this.height - this.yScale(0)}
        h${-this.xScale.bandwidth()}Z
        `
      ),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}
          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
          h${this.xScale.bandwidth()}
          a0,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",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>

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...