Xavier
Xavier

Reputation: 323

conditionally fill area between two lines in d3

for those working with D3... I have a multi-line chart and managed to fill the area between two series. The second part I've been trying to figure out is if it's possible to pass conditional statements so that the filled area changes color when the data point value of one set is greater than that of the other.

var w = 200,
    h = 200,
    p = 50;

var ddd = [[1,.22,.12],[2,.45,.09],[3,.54,.14],[4,.32,.18],[5,.89,.19],[6,.76,.22],[7,.72,.29],[8,.68,.34],[9,.73,.22],[10,.88,.37],[11,.92,.44],[12,.95,.89],[13,.97,.97],[14,.99,1.21],[15,1.11,1.99],[16,1.01,2.38],[17,1.13,2.11],[18,1.18,2.11],[19,1.22,2.18],			[20,1.29,2.03]],
    
    xmin = d3.min(ddd,function(d){return d[0];}),
    xmax = d3.max(ddd,function(d){return d[0];}),
    ymin = 0,
    ymax = 2.25;

var xscale = d3.scale.linear()
	.domain([xmin,xmax])
	.range([p,w-p]);
var yscale = d3.scale.linear()
	.domain([ymin,ymax])
	.range([h-p,p]);
var xaxis = d3.svg.axis()
	.scale(xscale)
	.orient("bottom");
var yaxis = d3.svg.axis()
	.scale(yscale)
	.orient("left");

var central = d3.select("div#z")
	.append("svg")
	.attr("width",w)
	.attr("height",h);

var line1 = d3.svg.line()
	.x(function(d) {return xscale(d[0])})
	.y(function(d) {return yscale(d[1])});
var line2 = d3.svg.line()
	.x(function(d) {return xscale(d[0])})
	.y(function(d) {return yscale(d[2])});

var x = d3.scale.linear().range([p,w-p]),
	y = d3.scale.linear().range([h-p,p]);

var area = d3.svg.area()
	.x(function(d){return xscale(d[0])})
	//.y0(h-p)
	.y0(function(d){return yscale(d[2])})
	.y1(function(d){return yscale(d[1])});

central.append("g")
	.attr("class","axis")
	.attr("transform","translate(0," + (h-p) + ")")
	.call(xaxis);
central.append("g")
	.attr("class","axis")
	.attr("transform","translate(" + (p) + ",0)")
	.call(yaxis);

central.append("svg:path")
	.attr("d",line1(ddd))	
    .attr("stroke","red")
	.attr("stroke-width",2)
	.attr('fill','none');
central.append("svg:path")
	.attr("d",line2(ddd))
	.attr("stroke","blue")
	.attr("stroke-width",2)
	.attr("fill","none");

central.append("path")
	.datum(ddd)
	.attr("class", "area")
//function(d){if (d[1] > d[2]) {return area} else {return area51};})
	.attr("d",area);
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}
.axis text {
    font-family: sans-serif;
    font-size: 9px;
}
.area {
    fill: lightsteelblue;
    stroke-width: 0;
}
.area51 {
    fill: lightsteelblue;
    stroke-width: 0;
}
.area:hover {
    fill: grey;
    stroke-width: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="z"></div>

So based on the chart, how can I get the right side of the chart filled in with a different color?

Upvotes: 2

Views: 932

Answers (1)

Cyril Cherian
Cyril Cherian

Reputation: 32327

I am using library....an awesome library to detect path intersection (courtesy: @thelonious)

   //the red line path
   var rl = central.append("svg:path")
    .attr("d",line1(ddd))   
     .attr("id", "linered")
    .attr("stroke","red")
    .attr("stroke-width",2)
    .attr('fill','none');

//the blue line path    
   var bl = central.append("svg:path")
    .attr("d",line2(ddd))
    .attr("id", "lineblue")
    .attr("stroke","blue")
    .attr("stroke-width",2)
    .attr("fill","none");

To get point of intersection do

var shape1 = new Path(rl.node());
var shape2 = new Path(bl.node());

var overlays = Intersection.intersectShapes(shape1, shape2);

Make left area and right area chart separately as below and add different classes

if (overlays.points.length > 0){
  var d = overlays.points[0]
  var midx = xscale.invert(d.x);
  var midy = yscale.invert(d.y);

  //assuming that the curve will have single y values for an x
  var left = ddd.filter(function(k){ return (k[0] <= midx);});
  var right = ddd.filter(function(k){ return (k[0] > midx);});
  //inserting the new middle point
  right.unshift([midx,midy,midy]);
  //left area
  central.append("path")
    .datum(left)
    .attr("class", "area")
    .attr("d",area);
    //right area  
    central.append("path")
    .datum(right)
    .attr("class", "area51")
    .attr("d",area);
}

Working code here

Hope this helps!

Upvotes: 1

Related Questions