d3 堆积条形图

背景:d3 画堆积条形图,每一个条形 显示多种类型所占的百分比

数据:


效果:


数据的 column 就是图上每个 x ..

每个条形就是显示的每列的值(竖着看)..

网上也有demo,但是我们的数据和人家的不太一样,我们是从列来看,然后数据已经是做好百分比的处理了..

重点:

columns 就是 样本 columns[0] 是 events

我是用 for 循环画了12 次条形,然后每个条形去堆叠每个 events 的百分比

拿第一个条形来说,

第一个蓝色 rect 它的 x 是第一个 sample,y 值是 yAxis(它的值),

height 值是 yAxis(0) - y (它的值)

从第二个开始,

色块的 y 值 就是 yAxis(当前色块的值+之前所有色块的值)

height 值 就是 yAxis(之前所有的色块值)- y(当前色块的值 + 之前所有色块的值)

代码如下:

let margin = {
        left: 50,
        right: 30,
        top: 30,
        bottom: 80
      }

      let width = 900
      let height = 600

      let svg = d3.select('#canvas')
        .append('svg')
        .attr('id', 'svg')
        .attr('width', 1000)
        .attr('height', height)
      let tooltip = d3.select('body')
      	.append('div')
      	.style('position', 'absolute')
        .style('z-index', '10')
        .style('visibility', 'hidden')
        .style('font-size', '14px')
      	.style('font-weight', 'bold')
      	.text('')
      var x = d3.scaleBand()
        .domain(this.samplesArr)
        .rangeRound([margin.left, width - margin.right])
        .padding(0.3)

      var y = d3.scaleLinear()
        .domain([0, 100])
        .rangeRound([height - margin.bottom, margin.top])

      var color = d3.scaleOrdinal(d3.schemeCategory20)

      var stack = d3.stack()
        .offset(d3.stackOffsetExpand);

      svg.append("g")
        .attr("transform", "translate(0," + y(0) + ")")
        .call(d3.axisBottom(x))
        .selectAll("text")
        .style("text-anchor", "start")
        .attr("transform", "rotate(45 -10 10)");

      svg.append("g")
        .attr("transform", "translate(" + margin.left + ",0)")
        .call(d3.axisLeft(y).tickFormat(d =>  d + "%" ))
      for (let i = 0;i < this.samplesArr.length;i++) {
        let sum = 0
        let sum2 = 0
        let index = i
        let sample = this.samplesArr[i]
        svg.selectAll(".rect")
          .data(this.data)
          .enter().append("rect")
          .attr("x", (d) => {
            return x(sample);
          })
          .attr("y", (d, i) => {
            sum = sum + d[index]
            return y(sum)
          })
          .attr("height", (d, i) => {
            sum2 = sum2 + d[index]
            if (i === 0) {
              return y(0) - y(d[index])
            } else {
              return y(sum2-d[index]) - y(sum2)
            }
          })
          .attr("width", x.bandwidth())
          .attr("fill", (d,i) => {
            return color(i)
          })
          .on('mouseover', (d, i) => {
            return tooltip.style('visibility', 'visible').text(this.eventsArr[i] + ' ' + d[index] + '%')
          })
          .on('mousemove', function (d, i) {
            return tooltip.style('top', (event.pageY-10)+'px').style('left',(event.pageX+10)+'px')
          })
          .on('mouseout', function (d, i) {
            return tooltip.style('visibility', 'hidden')
          })
      }
复制代码

2.这里再说一下图例,因为画出来的和图上色块的颜色顺序相反,所以把图例的数组 reverse 了一下~

let legend = svg.append("g")
      .attr("transform", "translate(" + (width - 30) + ","+ margin.top +")")
    legend.selectAll("rect")
      .data(this.eventsArrReverse)   // 为了和图的颜色从上到下一样
      .enter().append("rect")
      .attr("x",(d,i)=>{
        return 0
      })
      .attr("y",(d,i)=>{
        return 15 * i + 5 * i   // 每个 rect 间隔为5
      })
      .attr("width",15)
      .attr("height",15)
      .attr("fill",(d,i) => {
        return color(this.eventsArr.length - 1 - i)
      })
    legend.selectAll("text")
      .data(this.eventsArrReverse)
      .enter().append("text")
      .attr("x",(d,i)=>{
        return 20
      })
      .attr("y",(d,i)=>{
        return 15 * i + 5 * i + 12
      })
      .text((d) => {
        return d
      })
复制代码

参考链接

ps:提一下 数组的 reverse,会把原数组也修改了..

可以用下面方法做到 reverse 而不改变原数组

this.eventsArrReverse = Array.from(this.eventsArr).reverse()
复制代码