How to have 2 rect overlap in d3 to make progress bar?

Viewed 1107

I am trying to make a progress bar using d3, I am able to make the bar chart

But I want to have the range overlap under the bar like this

enter image description here

Here is my d3 code

var my_values = [
    { name: 'London', cost: 8674000},
    { name: 'New York', cost: 8406000},
    { name: 'Sydney', cost: 4293000},
    { name: 'Paris', cost: 2244000},
    { name: 'Beijing', cost: 11510000}
];

d3.selectAll('rect')
.data(my_values)
.attr('height', 5)
.attr('width', function(d) {
    var scaleFactor = 0.00004;
    return d.cost * scaleFactor;
})
.attr('y', function(d, i) {
  return i * 20;
})

Is there a way to make the range of the bar show under each rect so that the user can eye ball what is the relative percentage base on the position of the bar?

Edit:

I want to know how to create just the bar, here is the bar with text omitted

enter image description here

1 Answers

First of all: use a scale to create your bars based on your data. Using a proper scale the issue here is easy to solve:

Create the background bars using the maximum of the scale's domain:

.attr("width", function(d) {
    return scale(scale.domain()[1])
})

Then, over that elements, create the real bars, using the data:

.attr("width", function(d) {
    return scale(d.cost)
})

For creating the overlap give the real bars the same x, y and height attributes of the background bars. But they have to be painted after the background: in an SVG, whoever is painted later remains on top.

Here is a demo:

var my_values = [{
  name: 'London',
  cost: 8674000
}, {
  name: 'New York',
  cost: 8406000
}, {
  name: 'Sydney',
  cost: 4293000
}, {
  name: 'Paris',
  cost: 2244000
}, {
  name: 'Beijing',
  cost: 11510000
}];

var svg = d3.select("svg");

var scale = d3.scaleLinear()
  .domain([0, 12000000])
  .range([0, 480]);

var background = svg.selectAll(null)
  .data(my_values)
  .enter()
  .append("rect")
  .attr("x", 10)
  .attr("y", function(d, i) {
    return 10 + i * 20
  })
  .attr("height", 10)
  .attr("width", function(d) {
    return scale(scale.domain()[1])
  })
  .attr("rx", 5)
  .attr("ry", 5)
  .style("fill", "#ddd");

var bars = svg.selectAll(null)
  .data(my_values)
  .enter()
  .append("rect")
  .attr("x", 10)
  .attr("y", function(d, i) {
    return 10 + i * 20
  })
  .attr("height", 10)
  .attr("width", function(d) {
    return scale(d.cost)
  })
  .attr("rx", 5)
  .attr("ry", 5)
  .style("fill", "#666");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="200"></svg>

PS: If you want to remove the round corners in the right hand side of the bars you have some options: creating paths instead of bars or creating small bars on top of the real ones to cover the round corners, for instance. The problem is that, in an SVG rect, you cannot choose what corner will be round: rx and ry apply to all corners.

Related