How to stack rects respective of previous rect's height?

时间:2019-04-17 02:11:44

标签: javascript d3.js svg

I am try to make a simple column visual where the rects have a height that is determined by a scale:

var heightScale = d3.scaleLinear()
    .domain([150,2500])
    .range([10,80]);

My code is like this

var margins = {top:100, left:80, bottom:40, right:20};
var width = 950;
var height = 600;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;

var svg = d3.select('body')
    .append('svg')
    .attr('width', totalWidth)
    .attr('height', totalHeight);

var graphGroup = svg.append("g")
    .attr("transform", "translate(" + margins.left + "," + margins.top + ")");

var q12018Data = [{'fmc': 'UBS', 'value': 2308.78},
 {'fmc': 'Fidelity', 'value': 976.76},
 {'fmc': 'JP Morgan', 'value': 837.19},
 {'fmc': 'Value Partners', 'value': 787.39},
 {'fmc': 'BlackRock', 'value': 664.42},
 {'fmc': 'Krane', 'value': 445.13},
 {'fmc': 'Investec', 'value': 407.40},
 {'fmc': 'Nikko', 'value': 356.07},
 {'fmc': 'Yuanta', 'value': 202.71},
 {'fmc': 'Cathay Securities Invest', 'value': 174.46}];

var q22018Data = [{'fmc': 'UBS', 'value': 2193.05},
 {'fmc': 'BlackRock', 'value': 817.24},
 {'fmc': 'Yuanta', 'value': 676.87},
 {'fmc': 'Fubon', 'value': 660.11},
 {'fmc': 'JP Morgan', 'value': 577.26},
 {'fmc': 'Investec', 'value': 384.65},
 {'fmc': 'Hang Seng', 'value': 289.92},
 {'fmc': 'Cathay Securities Invest', 'value': 289.43},
 {'fmc': 'Pictet', 'value': 232.74},
 {'fmc': 'Nikko', 'value': 224.73}];

var q32018Data = [{'fmc': 'ChinaAMC', 'value': 1074},
 {'fmc': 'Fubon', 'value': 466.32},
 {'fmc': 'Heungkuk', 'value': 458.23},
 {'fmc': 'BlackRock', 'value': 361.34},
 {'fmc': 'UBS', 'value': 350.01},
 {'fmc': 'CSOP', 'value': 263.60},
 {'fmc': 'Legal & General', 'value': 218.79},
 {'fmc': 'SSgA', 'value': 188.35},
 {'fmc': 'Fidelity', 'value': 134.06},
 {'fmc': 'Morgan Stanley', 'value': 112.70}];

var q42018Data = [{'fmc': 'ChinaAMC', 'value': 1994.02},
 {'fmc': 'Fubon', 'value': 808.34},
 {'fmc': 'Heungkuk', 'value': 676.26},
 {'fmc': 'BlackRock', 'value': 668.64},
 {'fmc': 'UBS', 'value': 595.27},
 {'fmc': 'CSOP', 'value': 424.83},
 {'fmc': 'Legal & General', 'value': 380.30},
 {'fmc': 'SSgA', 'value': 366.85},
 {'fmc': 'Fidelity', 'value': 285.09},
 {'fmc': 'Morgan Stanley', 'value': 273.55}];

var q12019Data = [{'fmc': 'UBS', 'value': 938.23},
 {'fmc': 'BlackRock', 'value': 474.45},
 {'fmc': 'Yuanta', 'value': 385.32},
 {'fmc': 'Fubon', 'value': 349.73},
 {'fmc': 'JP Morgan', 'value': 246.86},
 {'fmc': 'Investec', 'value': 235.12},
 {'fmc': 'Hang Seng', 'value': 230.23},
 {'fmc': 'Cathay Securities Invest', 'value': 220.02},
 {'fmc': 'Pictet', 'value': 213.76},
 {'fmc': 'Nikko', 'value': 190.73}];


var heightScale = d3.scaleLinear()
    .domain([150,2500])
    .range([10,80]);

    var column = graphGroup.selectAll("g")
        .data(q12018Data)
      .enter().append("g");

    column.append("rect")
        .attr("width", 150)
        .attr("height", function(d) {return heightScale(d.value)})
        .attr('y', function(d,i) {
          if (i!=0) {
            var prevData = column.data()[i-1];
            var prevHeight = heightScale(prevData.value);
            var currentHeight = heightScale(d.value);
            return prevHeight;
          } else {
            return 0;
          }

        })
        .style('fill','gray');

    column.append("text")
        .attr("x", 75)
        .attr('y', function(d,i) {
          if (i!=0) {
            var prevData = column.data()[i-1];
            var prevHeight = heightScale(prevData.value);
            var currentHeight = heightScale(d.value);
            return prevHeight;
          } else {
            return 0;
          }

        })
        .attr('text-anchor','middle')
        .text(function(d) { return d.fmc; });
<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>

Question

Why is my rect height of the previous rect not being calculated correctly? The result should be similar to a stacked bar in appearance. There is no y scale though, I just want to append the subsequent rect so that there is just a few px of padding, say 2px.

1 个答案:

答案 0 :(得分:1)

You are not summing the values! You're simply passing the individual value for each object... you have to accumulate them.

We can do this by simply declaring two counters, with the initial value of zero...

let counterRect = 0,
    counterText = 0;

...which we later increase, using:

.attr('y', function(d, i) {
    let previous = counterRect;
    return (counterRect += heightScale(d.value), previous)
})

Here is your code with that change (and white borders for the rectangles):

var margins = {
  top: 100,
  left: 80,
  bottom: 40,
  right: 20
};
var width = 950;
var height = 600;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append("g")
  .attr("transform", "translate(" + margins.left + "," + margins.top + ")");

var q12018Data = [{
    'fmc': 'UBS',
    'value': 2308.78
  },
  {
    'fmc': 'Fidelity',
    'value': 976.76
  },
  {
    'fmc': 'JP Morgan',
    'value': 837.19
  },
  {
    'fmc': 'Value Partners',
    'value': 787.39
  },
  {
    'fmc': 'BlackRock',
    'value': 664.42
  },
  {
    'fmc': 'Krane',
    'value': 445.13
  },
  {
    'fmc': 'Investec',
    'value': 407.40
  },
  {
    'fmc': 'Nikko',
    'value': 356.07
  },
  {
    'fmc': 'Yuanta',
    'value': 202.71
  },
  {
    'fmc': 'Cathay Securities Invest',
    'value': 174.46
  }
];

var q22018Data = [{
    'fmc': 'UBS',
    'value': 2193.05
  },
  {
    'fmc': 'BlackRock',
    'value': 817.24
  },
  {
    'fmc': 'Yuanta',
    'value': 676.87
  },
  {
    'fmc': 'Fubon',
    'value': 660.11
  },
  {
    'fmc': 'JP Morgan',
    'value': 577.26
  },
  {
    'fmc': 'Investec',
    'value': 384.65
  },
  {
    'fmc': 'Hang Seng',
    'value': 289.92
  },
  {
    'fmc': 'Cathay Securities Invest',
    'value': 289.43
  },
  {
    'fmc': 'Pictet',
    'value': 232.74
  },
  {
    'fmc': 'Nikko',
    'value': 224.73
  }
];

var q32018Data = [{
    'fmc': 'ChinaAMC',
    'value': 1074
  },
  {
    'fmc': 'Fubon',
    'value': 466.32
  },
  {
    'fmc': 'Heungkuk',
    'value': 458.23
  },
  {
    'fmc': 'BlackRock',
    'value': 361.34
  },
  {
    'fmc': 'UBS',
    'value': 350.01
  },
  {
    'fmc': 'CSOP',
    'value': 263.60
  },
  {
    'fmc': 'Legal & General',
    'value': 218.79
  },
  {
    'fmc': 'SSgA',
    'value': 188.35
  },
  {
    'fmc': 'Fidelity',
    'value': 134.06
  },
  {
    'fmc': 'Morgan Stanley',
    'value': 112.70
  }
];

var q42018Data = [{
    'fmc': 'ChinaAMC',
    'value': 1994.02
  },
  {
    'fmc': 'Fubon',
    'value': 808.34
  },
  {
    'fmc': 'Heungkuk',
    'value': 676.26
  },
  {
    'fmc': 'BlackRock',
    'value': 668.64
  },
  {
    'fmc': 'UBS',
    'value': 595.27
  },
  {
    'fmc': 'CSOP',
    'value': 424.83
  },
  {
    'fmc': 'Legal & General',
    'value': 380.30
  },
  {
    'fmc': 'SSgA',
    'value': 366.85
  },
  {
    'fmc': 'Fidelity',
    'value': 285.09
  },
  {
    'fmc': 'Morgan Stanley',
    'value': 273.55
  }
];

var q12019Data = [{
    'fmc': 'UBS',
    'value': 938.23
  },
  {
    'fmc': 'BlackRock',
    'value': 474.45
  },
  {
    'fmc': 'Yuanta',
    'value': 385.32
  },
  {
    'fmc': 'Fubon',
    'value': 349.73
  },
  {
    'fmc': 'JP Morgan',
    'value': 246.86
  },
  {
    'fmc': 'Investec',
    'value': 235.12
  },
  {
    'fmc': 'Hang Seng',
    'value': 230.23
  },
  {
    'fmc': 'Cathay Securities Invest',
    'value': 220.02
  },
  {
    'fmc': 'Pictet',
    'value': 213.76
  },
  {
    'fmc': 'Nikko',
    'value': 190.73
  }
];

let counterRect = 0,
  counterText = 0;

var heightScale = d3.scaleLinear()
  .domain([150, 2500])
  .range([10, 80]);

var column = graphGroup.selectAll("g")
  .data(q12018Data)
  .enter().append("g");

column.append("rect")
  .attr("width", 150)
  .attr("height", function(d) {
    return heightScale(d.value)
  })
  .attr('y', function(d, i) {
    let previous = counterRect;
    return (counterRect += heightScale(d.value), previous)
  })
  .style('fill', 'gray')
  .style("stroke", "white");

column.append("text")
  .attr("x", 75)
  .attr('y', function(d, i) {
    let previous = counterText;
    return (counterText += heightScale(d.value), previous + (heightScale(d.value)/2))
  })
  .attr("dominant-baseline", "central")
  .attr('text-anchor', 'middle')
  .text(function(d) {
    return d.fmc;
  });
<script src="https://d3js.org/d3.v5.min.js"></script>