d3.js如何计算悬停时钢筋的百分比

时间:2019-07-09 17:34:41

标签: javascript d3.js

我有一个d3图表,我想在悬停上显示一个工具提示,该提示显示条形部分的标签以及该条形占总条形的百分比。我最初的想法是计算要悬停的部分的高度,并找出总条形占该部分的百分比。我是通过将其添加到我的mouseover事件(您悬停时正在记录的事件)中来做到这一点的:

d3.select(this).style("height")

这正常工作,但我不确定从这里去哪里。我似乎无法找到一种方法来获得整个条形的高度(考虑到所有部分),以便创建方程式。

该如何解决?还是有更好的方法来获取百分比?

<!DOCTYPE html>
<meta charset="utf-8">
<svg id="chart" width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var data = [
  {month: "January", toys: 16591, games: 1047, books: 0, crafts: 5757},
  {month: "February", toys: 42337, games: 129, books: 835, crafts: 0},
  {month: "March", toys:  3385, games: 1053, books: 6260, crafts: 10},
  {month: "April", toys:  353, games: 3724, books: 4038, crafts: 0}
];

var series = d3.stack()
    .keys(["toys", "games", "books", "crafts"])
    .offset(d3.stackOffsetDiverging)
    (data);

var svg = d3.select("svg"),
    margin = {top: 20, right: 30, bottom: 30, left: 60},
    width = +svg.attr("width"),
    height = +svg.attr("height");

var x = d3.scaleBand()
    .domain(data.map(function(d) { return d.month; }))
    .rangeRound([margin.left, width - margin.right])
    .padding(0.1);

var y = d3.scaleLinear()
    .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
    .rangeRound([height - margin.bottom, margin.top]);

var z = d3.scaleOrdinal(d3.schemeCategory10);

var colors = ['#000','#000','#000','#000'];

svg.append("g")
  .selectAll("g")
  .data(series)
  .enter().append("g")
    .attr("fill", function(d) { return z(d.key); })
  .selectAll("rect")
  .data(function(d) { return d; })
  .enter().append("rect")
    .attr("width", x.bandwidth)
    .attr("x", function(d) { return x(d.data.month); })
    .attr("y", function(d) { return y(d[1]); })
    .attr("height", function(d) { return y(d[0]) - y(d[1]); })
    .on("mouseover", function(d){
        console.log(d3.select(this).style("height"));
        var xPos = parseFloat(d3.select(this).attr("x"));
        var yPos = parseFloat(d3.select(this).attr("y"));
        var height = parseFloat(d3.select(this).attr("height"))
                
        d3.select(this).attr("stroke","blue").attr("stroke-width",0.8);
        svg.append("text")
          .attr("x",xPos)
          .attr("y",yPos +height/2)
          .attr("class","tooltip")
          //.text(Math.floor(d.y_pct.toFixed(2)*100) + "% of " + d.type );
    })

svg.append("g")
    .attr("transform", "translate(0," + y(0) + ")")
    .call(d3.axisBottom(x));

svg.append("g")
    .attr("transform", "translate(" + margin.left + ",0)")
    .call(d3.axisLeft(y));

function stackMin(serie) {
  return d3.min(serie, function(d) { return d[0]; });
}

function stackMax(serie) {
  return d3.max(serie, function(d) { return d[1]; });
}

</script>

1 个答案:

答案 0 :(得分:1)

每个矩形的基准点都包含data属性中该列中的所有条形:

enter image description here

基准面还包括矩形本身的顶部和底部(开始和结束)值:d[0]d[1]

使用这个我们可以算出一个百分比:

.on("mouseover", function(d) { 
   var rectHeight = d[1] - d[0]; // The top and bottom vales of the rectangle.
   var columnHeight = d3.sum(d3.keys(d.data),function(k) {
      return +d.data[k]; // The sum of all bar rectangle heights in that column.
  });
  var percentHeight = rectHeight/columnHeight*100;

上面的rect height和column height的单位不是SVG单位,而是在数据集的单位中,因为我们无需缩放即可直接访问数据。

d3.sum将忽略NaN或未定义的值,一元加号运算符会将字符串强制转换为数字,如果不可能,则将返回NaN。当然,如果您有一个month或x属性是一个数字,则需要将其从总和中删除

有了这个我们得到类似的东西:

<!DOCTYPE html>
<meta charset="utf-8">
<svg id="chart" width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var data = [
  {month: "January", toys: 16591, games: 1047, books: 0, crafts: 5757},
  {month: "February", toys: 42337, games: 129, books: 835, crafts: 0},
  {month: "March", toys:  3385, games: 1053, books: 6260, crafts: 10},
  {month: "April", toys:  353, games: 3724, books: 4038, crafts: 0}
];

var series = d3.stack()
    .keys(["toys", "games", "books", "crafts"])
    .offset(d3.stackOffsetDiverging)
    (data);

var svg = d3.select("svg"),
    margin = {top: 20, right: 30, bottom: 30, left: 60},
    width = +svg.attr("width"),
    height = +svg.attr("height");

var x = d3.scaleBand()
    .domain(data.map(function(d) { return d.month; }))
    .rangeRound([margin.left, width - margin.right])
    .padding(0.1);

var y = d3.scaleLinear()
    .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
    .rangeRound([height - margin.bottom, margin.top]);

var z = d3.scaleOrdinal(d3.schemeCategory10);

var colors = ['#000','#000','#000','#000'];

svg.append("g")
  .selectAll("g")
  .data(series)
  .enter().append("g")
    .attr("fill", function(d) { return z(d.key); })
  .selectAll("rect")
  .data(function(d) { return d; })
  .enter().append("rect")
    .attr("width", x.bandwidth)
    .attr("x", function(d) { return x(d.data.month); })
    .attr("y", function(d) { return y(d[1]); })
    .attr("height", function(d) { return y(d[0]) - y(d[1]); })
    .on("mouseover", function(d){
        var rectHeight = d[1] - d[0];
        var columnHeight = d3.sum(d3.keys(d.data),function(k) {
           return +d.data[k];
        });
        console.log(Math.round(rectHeight/columnHeight*100)+"%");
    })

svg.append("g")
    .attr("transform", "translate(0," + y(0) + ")")
    .call(d3.axisBottom(x));

svg.append("g")
    .attr("transform", "translate(" + margin.left + ",0)")
    .call(d3.axisLeft(y));

function stackMin(serie) {
  return d3.min(serie, function(d) { return d[0]; });
}

function stackMax(serie) {
  return d3.max(serie, function(d) { return d[1]; });
}

</script>