Boundary for d3 stack bar chart when selected

Attempting to implement a border for the selected row in the d3 stack histogram. Here, the first upper border of the bar is slightly behind the second bar. 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

+3


source to share


2 answers


In SVG, just like a real artist applying ink to a white canvas, the element that gets painted last stays on top.

Currently, the behavior you see is as expected because each stack (rectangle) is in a different element <g>

, and the groups are of course ordered in the SVG structure.

The solution only includes one line:

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

      

What this line does is select the group of the clicked rectangle and raise it (i.e. move down in the DOM tree) so that the group is on top of everyone else. According to the API, raise () :

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



Moving down, being on top, and being the last child can be a little confusing and contradictory, but here's the explanation. Given this SVG structure:

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

      

<baz>

the last element is the one that was painted last , and this is the element visually on top in SVG. So raising an element means moving it down into the SVG tree structure, but visually rendering it up .

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

PS: I increased the stroke width just to clearly see that the clicked rectangle is now on top.

+7


source


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;

}

      

0


source







All Articles