border for d3 stack bar chart on selection

2019-07-04 03:13发布

Trying to implement border for selected bar in d3 stack bar chart. Here the first bar's top border goes behind second bar a little bit. How to avoid this?

var svg, height, width, margin, parentWidth, parentHeight;

// container size
parentWidth = 700;
parentHeight = 500;
margin = {top: 50, right: 20, bottom: 35, left: 30};
width = parentWidth - margin.left - margin.right;
height = parentHeight - margin.top - margin.bottom;

var selectedSection = window.sessionStorage.getItem('selectedSection');

// data
var dataset = [{"label":"DEC","Set Up":{"count":12,"id":1,"label":"Set Up","year":"2016","graphType":"setup"},"Not Set Up":{"count":12,"id":0,"label":"Not Set Up","year":"2016","graphType":"setup"}},{"label":"JAN","Set Up":{"count":6,"id":1,"label":"Set Up","year":"2017","graphType":"setup"},"Not Set Up":{"count":21,"id":0,"label":"Not Set Up","year":"2017","graphType":"setup"}},{"label":"FEB","Set Up":{"count":1,"id":1,"label":"Set Up","year":"2017","graphType":"setup"},"Not Set Up":{"count":2,"id":0,"label":"Not Set Up","year":"2017","graphType":"setup"}},{"label":"MAR","Set Up":{"count":0,"id":1,"label":"Set Up","year":"2017","graphType":"setup"},"Not Set Up":{"count":0,"id":0,"label":"Not Set Up","year":"2017","graphType":"setup"}},{"label":"APR","Set Up":{"count":0,"id":1,"label":"Set Up","year":"2017","graphType":"setup"},"Not Set Up":{"count":0,"id":0,"label":"Not Set Up","year":"2017","graphType":"setup"}}];

// x cord
var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], 0.2);

// color helper
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
    .range(colorRange.range());

// x axis
var xAxis = d3.svg.axis()
    .scale(x)
    .orient('bottom');

var colors = ['#50BEE9', '#30738C'];

// Set SVG
svg = d3.select('#chart')
  .append('svg')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom )
  .attr('class', 'setup')
  .append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

color.domain(d3.keys(dataset[0]).filter(function(key) { return key !== 'label'; }));

dataset.forEach(function(d) {
  var y0 = 0;
  d.values = color.domain().map(function(name) { 
    return {
      name: name, 
      y0: y0, 
      y1: y0 += +d[name].count, 
      patientStatus:d[name].id,
      graphType:d[name].graphType,  
      fromDate:{
        month:d.label,
        year:d[name].year
      },
      toDate:{
        month:d.label,
        year:d[name].year
      }  
    }; 
  });
  d.total = d.values[d.values.length - 1].y1;
});

var y = d3.scale.linear()
    .domain([0, d3.max(dataset, function(d) {  
    return d.total;
  })])
    .range([height, 0]);

var ticks = y.ticks(),
    lastTick = ticks[ticks.length-1];    
var newLastTick = lastTick + (ticks[1] - ticks[0]);  
if (lastTick<y.domain()[1]){
  ticks.push(lastTick + (ticks[1] - ticks[0]));
}

// adjust domain for further value
y.domain([y.domain()[0], newLastTick]);

// y axis
var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left')
    .tickSize(-width, 0, 0) 
    .tickFormat(d3.format('d'))
    .tickValues(ticks);

x.domain(dataset.map(function(d) { return d.label; }));
y.domain([0, d3.max(dataset, function(d) { return d.total; })]);

svg.append('g')
  .attr('class', 'x axis')
  .attr('transform', 'translate(0,' + height + ')')
  .call(xAxis);

svg.append('g')
  .attr('class', 'y axis')
  .call(yAxis);

var bar = svg.selectAll('.label')
    .data(dataset)
    .enter().append('g')
    .attr('class', 'g')
    .attr('id', function(d, i) {
    return i;
  })
    .attr('transform', function(d) { return 'translate(' + x(d.label) + ',0)'; });

var barEnter = bar.selectAll('rect')
    .data(function(d) { return d.values; })
    .enter();

barEnter.append('rect')
  .attr('width', x.rangeBand())
  .attr('y', function(d) { 
    return y(d.y1); 
    })
  .attr('class', function(d, i){
    return 'bar';
    })
  .attr('height', function(d) { return y(d.y0) - y(d.y1); })
  .style('fill', function(d,i) { return colors[i]; })
  .on('click', function(d, i) {
    d3.selectAll('.bar').classed('selected', false);
    d3.select(this)
      .classed('bar selected', true);  
    });

barEnter.append('text')
  .text(function(d) { 
    var calcH = y(d.y0) - y(d.y1);
    var inText = (d.y1-d.y0);
    if(calcH >= 20) {
      return inText;
    } else {
      return '';
    }
})
.attr('class','inner-text')
.attr('y', function(d) { return y(d.y1)+(y(d.y0) - y(d.y1))/2 + 5; })
.attr('x', function(){
  return (x.rangeBand()/2) - 10;
}); 

svg
  .select('.y')
  .selectAll('.tick')
  .filter(function (d) { 
    return d % 1 !== 0;    
    })
  .style('display','none');

svg
  .select('.y')
  .selectAll('.tick')
  .filter(function (d) { 
    return d === 0;    
    })
  .select('text')
  .style('display','none');

JSFiddle

JSFiddle with d3 v4

2条回答
相关推荐>>
2楼-- · 2019-07-04 03:29

Tag:

  <div id='stacked-bar'></div>

Script:

    var initStackedBarChart = {
    draw: function(config) {
        me = this,
        domEle = config.element,
        stackKey = config.key,
        data = config.data,
        margin = {top: 20, right: 20, bottom: 30, left: 50},
        parseDate = d3.timeParse("%m/%Y"),
        width = 550 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom,
        xScale = d3.scaleBand().range([0, width]).padding(0.1),
        yScale = d3.scaleLinear().range([height, 0]),
        color = d3.scaleOrdinal(d3.schemeCategory20),
        xAxis = d3.axisBottom(xScale).tickFormat(d3.timeFormat("%b")),
        yAxis =  d3.axisLeft(yScale),
        svg = d3.select("#"+domEle).append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top+10 + margin.bottom+10)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        var stack = d3.stack()
            .keys(stackKey)
            .order(d3.stackOrderNone)
            .offset(d3.stackOffsetNone);

        var layers= stack(data);
            data.sort(function(a, b) { return b.total - a.total; });
            xScale.domain(data.map(function(d) { return parseDate(d.date); }));
            yScale.domain([0, d3.max(layers[layers.length - 1], function(d) { return d[0] + d[1]; }) ]).nice();

        var layer = svg.selectAll(".layer")
            .data(layers)
            .enter().append("g")
            .attr("class", "layer")
            .style("fill", function(d, i) { return color(i); });

          layer.selectAll("rect")
              .data(function(d) { return d; })
            .enter().append("rect")
        .attr('class', 'bar')
              .attr("x", function(d) { return xScale(parseDate(d.data.date)); })
              .attr("y", function(d) { return yScale(d[1]); })
              .attr("height", function(d) { return yScale(d[0]) - yScale(d[1]) -1; })
              .attr("width", xScale.bandwidth())
        .on('click', function(d, i) {
          d3.selectAll('.bar').classed('selected', false);
          d3.select(this).classed('selected', true);
        });

            svg.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", "translate(0," + (height+5) + ")")
            .call(xAxis);

            svg.append("g")
            .attr("class", "axis axis--y")
            .attr("transform", "translate(0,0)")
            .call(yAxis);                           
    }
}
var data = [
{"date":"4/1854","total":45,"disease":12,"wounds":14,"other":25},
{"date":"5/1854","total":23,"disease":12,"wounds":0,"other":9},
{"date":"6/1854","total":38,"disease":11,"wounds":0,"other":6},
{"date":"7/1854","total":26,"disease":11,"wounds":8,"other":7}
];
var key = ["wounds", "other", "disease"];
initStackedBarChart.draw({
    data: data,
    key: key,
    element: 'stacked-bar'
});

Css:

.axis text {
  font: 10px sans-serif;
}
.axis line,
.axis path {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.axis--x path {
  display: none;
}
.path-line {
  fill: none;
  stroke: yellow;
  stroke-width: 1.5px;
}
svg {
  background: #f0f0f0;
}
.selected{
  stroke:#333;
  stroke-width:2;

}
查看更多
一夜七次
3楼-- · 2019-07-04 03:32

In a SVG, just like a real painter putting ink to a white canvas, the element that is painted last stays on top.

Right now, the behaviour you're seeing is the expected one, because each stacked bar (rectangle) is in a different <g> element, and the groups, of course, have a given order in the SVG structure.

The solution involves just one line:

d3.select(this.parentNode).raise();

What this line does is selecting the group of the clicked rectangle and raising it (that is, moving it down in the DOM tree), so that group will be on top of all others. According to the API, raise():

Re-inserts each selected element, in order, as the last child of its parent. (emphasis mine)

"Moving down", "be on top" and "be the last child" may be a bit confusing and seem contradictory, but here is the explanation. Given this SVG structure:

<svg>
    <foo></foo>
    <bar></bar>
    <baz></baz>
</svg>

<baz>, being the last element, is the one painted last, and it is the element visually on the top in the SVG. So, raising an element means moving it down in the SVG tree structure, but moving it up visually speaking.

Here is your updated fiddle: https://jsfiddle.net/86Lgaupt/

PS: I increased the stroke-width just to make visibly clear that the clicked rectangle is now on top.

查看更多
登录 后发表回答