How to Make a bar chart with rounded corners with

2020-03-30 03:26发布

问题:

I am trying to create a Bar chart using D3.js. The condition is that Bar should have fixed width and padding between the bars and it should be plot at center of grid lines.

Tooltip should appear on click with a vertical line

I am able to create the grid lines with plotted bar, somehow rx,ry is rounding from both sides. How can i acheive the same sort of result.

var rectCluster = svg.selectAll(".bar")
                    .data(data);

                rectCluster
                    .enter().append("rect")
                    .attr("class", function(d) {
                        return "bar";
                    })
                    .attr(attrs)
                    .attr({ry : (20), rx : 20 })
                    .attr("x", function(d) { return x(d.text); })
                    .attr("y", function(d) {
                        return height;
                    })
                    .style("fill", function(d) {
                        return color(d.text);
                    })
                    .attr("width", x.rangeBand())
                    .attr("height", 0)
                    .transition()
                    .duration(animationDelay)
                    .delay(function(d, i) {
                        return i * animationDelay;
                    })
                    .attr("y", function(d) { return y(d.score); })
                    .attr("height", function(d) { return height - y(d.score) });

                    var attrs = {
    width: function(d, i){
      return o.rangeBand();
    },
    height: function(d, i){
      return yScale(d);
    },
    fill: "#384252",
    x: function(d, i){
      return xScale(i);
    },
    y: function(d, i){
      return height - yScale(d) - margin.bottom;
    }
  };

Js Fiddle for the same

I am trying to achieve like this.

回答1:

One option is to use a clip path, but you can also create a simple path generator using the same information you use to build the rectangles: x,y,width,height, plus radius. The path is fairly simple:

  1. Move to bottom left corner
  2. Line to bottom of top left arc
  3. Arc to top of top left arc
  4. Line to the top of the top right arc
  5. Arc to the bottom of the top right arc.
  6. Line to the bottom right corner
  7. Close path.

Which is comes together something like:

  1. M x,y
  2. L x,y-height+radius
  3. A radius,radius,0,0,1,x+radius,y-height
  4. L x+width-r,y-height
  5. A radius,radius,0,0,1,x+width,y-height+radius
  6. L x+width,y
  7. Z

Which could look like this (a rather lazy implementation):

function bar(x,y,w,h,r,f) {
    // Flag for sweep:
    if(f == undefined) f = 1;
    // x coordinates of top of arcs
    var x0 = x+r;
    var x1 = x+w-r;
    // y coordinates of bottom of arcs
    var y0 = y-h+r;

    // assemble path:
    var parts = [
      "M",x,y,               // step 1
      "L",x,y0,              // step 2
      "A",r,r,0,0,f,x0,y-h,  // step 3
      "L",x1,y-h,            // step 4
      "A",r,r,0,0,f,x+w,y0,  // step 5
      "L",x+w,y,             // step 6
      "Z"                    // step 7
     ];
    return parts.join(" ");
}

I've included an optional sweep flag (f) - it'll invert the arcs if set to 0.

And applied something like so:

 .attr("d", function(d) { 
    return bar(x(d),y(0),x.bandwidth(),y(0)-y(d),15);  
  })

Put together you might get something like:

var width = 500;
var height = 200;
var svg = d3.select("body").append("svg").attr("width",width).attr("height",height);
var data = [ 10,20,30,40,20,50,60 ];

var x = d3.scaleBand().domain(d3.range(data.length)).range([10,width-10]).paddingInner(0.1);
var y = d3.scaleLinear().domain([0,60]).range([height-10,10]);

var bars = svg.selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("d", function(d,i) { 
	  return bar(x(i),y(0),x.bandwidth(),y(0)-y(d),10);  
  })
  
function bar(x,y,w,h,r,f) {
	// Flag for sweep:
	if(f == undefined) f = 1;
	
	// x coordinates of top of arcs
	var x0 = x+r;
	var x1 = x+w-r;
	// y coordinates of bottom of arcs
	var y0 = y-h+r;
	// just for convenience (slightly different than above):
	var l = "L", a = "A";

	var parts = ["M",x,y,l,x,y0,a,r,r,0,0,f,x0,y-h,l,x1,y-h,a,r,r,0,0,f,x+w,y0,l,x+w,y,"Z"];
	return parts.join(" ");
}

// Still transitionable:
bars.data(data.reverse())
 .transition()
 .attr("d", function(d,i) { 
  return bar(x(i),y(0),x.bandwidth(),y(0)-y(d),30);  
 })
 .duration(2000)
 .transition()
 .attr("d", function(d,i) { 
  return bar(x(i),y(0),x.bandwidth(),y(0)-y(d),0);  
 })
 .duration(2000)
 .transition()
 .attr("d", function(d,i) { 
  return bar(x(i),y(0),x.bandwidth(),y(0)-y(d),15);  
  })
 .duration(2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

If you radius exceeds half bar height or width you'll get some funkier results, a complete implementation of this would consider a check to make sure that the radius for bar isn't too large



回答2:

My first option would be to use clipPath (answer here: Rounded corner only on one side of svg <rect>)

Another option is to simply draw another rectangle at the bottom that has square corners:

rectCluster
                    .enter().append("rect")
                    .attr("class", function(d) {
                        return "bar";
                    })
                    .attr(attrs)
                    .attr({ry : (20), rx : 20 })
                    .attr("x", function(d) { return x(d.text); })
                    .attr("y", function(d) {
                        return height;
                    })
                    .style("fill", function(d) {
                        return color(d.text);
                    })
                    .attr("width", x.rangeBand())
                    .attr("height", 0)
                    .transition()
                    .duration(animationDelay)
                    .delay(function(d, i) {
                        return i * animationDelay;
                    })
                    .attr("y", function(d) { return y(d.score); })
                    .attr("height", function(d) { return height - y(d.score) });

                 // SQUARE CORNERS
                 rectCluster
                    .enter().append("rect")
                    .attr("class", function(d) {
                        return "bar";
                    })
                    .attr(attrs)
                    .attr({ry : (20), rx : 0 })
                    .attr("x", function(d) { return x(d.text); })
                    .attr("y", 0)
                    .style("fill", function(d) {
                        return color(d.text);
                    })
                    .attr("width", x.rangeBand())
                    .attr("height", 0)
                    .transition()
                    .duration(animationDelay)
                    .delay(function(d, i) {
                        return i * animationDelay;
                    })
                    .attr("y", height-20)
                    .attr("height", 20);

See Fiddle:

https://jsfiddle.net/6x2y35gn/40/