SkyStar
SkyStar

Reputation: 169

How to create a line chart with vertical line and different backgrounds?

I Need to create a line chart with a vertical line that shows the border. The left side should be painted in red, the right in green, respectively:

Linear chart which I want to build

I started looking for examples in libraries, but there are no examples ... I made a similarity on D3. But I was able to draw only the graph and the vertical line, and I can’t fill parts and display the legend. Tell me please, how to create this type of schedule? Are there any existing implementation solutions? My code in which areas are not painted over and there is no legend.

// https://plnkr.co/edit/9tnP6l15dqmCaOAK8Ojm?p=preview

function drawChart(data) {

var svgWidth = 600, 
    svgHeight = 400;

var margin = { top: 20, right: 20, bottom: 30, left: 50 };

var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;

var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

// Grouping element with margins
var g = svg.append("g")
    .attr("transform", 
    "translate(" + margin.left + "," + margin.top + ")"
);

var x = d3.scaleLinear().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);

var line = d3.line()
    .x(function(d) { return x(d.date)})
    .y(function(d) { return y(d.value)})
    x.domain(d3.extent(data, function(d) { return d.date }));
    y.domain(d3.extent(data, function(d) { return d.value }));

g.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x))
    .append("text")
    .attr("fill", "#000")
    .attr("transform", "rotate(0)")
    .attr("y", 0)
    .attr("dx", "50em")
    .attr("dy", "-0.5em")
    .attr("text-anchor", "start")
    .text("Title ($)");

// Text for Y axes
g.append("g")
    .call(d3.axisLeft(y))
    .append("text")
    .attr("fill", "#000")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", "0.71em")
    .attr("text-anchor", "end")
    .text("Price ($)");

g.append("path")
    .datum(data)
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("stroke-width", 1)
    .attr("d", line);

g.append("line")
    .attr("x1", 300) 
    .attr("y1", 0)
    .attr("x2", 300)
    .attr("y2", 350)
    .style("stroke-width", 2)
    .style("stroke", "red")
    .style("fill", "black");
}

document.addEventListener("DOMContentLoaded", function(event) {
var parsedData = [{
    date: 0,
    value: 10
 }, {
    date: 0,
    value: 1000
 }, {
    date: 200,
    value: 2400
 }, {
    date: 300,
    value: 4600
 }, {
    date: 400,
    value: 5600
 }, {
    date: 500,
    value: 7777
 }];

drawChart(parsedData);
});

Upvotes: 2

Views: 971

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102218

There are different ways to create this, like drawing two paths with the same data and using a <clipPath> for instance.

But I reckon that the easiest way to do this is just using different data for each path. Following that approach, we just need to set the area generator...

var area = d3.area()
    .x(function(d) { return x(d.date)})
    .y1(function(d) { return y(d.value)})
    .y0(y(0));

... and append the paths, filtering the data according to the boundary (here named border):

g.append("path")
    .datum(data.filter(function(d) {
        return d.date <= border;
    }))
    .attr("fill", "red")
    .attr("d", area);

g.append("path")
    .datum(data.filter(function(d) {
        return d.date >= border;
    }))
    .attr("fill", "green")
    .attr("d", area);

Here is the demo using your code:

var data = [{
  date: 0,
  value: 1000
}, {
  date: 200,
  value: 2400
}, {
  date: 300,
  value: 4600
}, {
  date: 400,
  value: 5600
}, {
  date: 500,
  value: 7777
}];

var svgWidth = 600,
  svgHeight = 400;

var margin = {
  top: 20,
  right: 20,
  bottom: 30,
  left: 50
};

var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;

var svg = d3.select('svg')
  .attr("width", svgWidth)
  .attr("height", svgHeight);

// Grouping element with margins
var g = svg.append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")"
  );

var x = d3.scaleLinear().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);

var border = 300;

var line = d3.line()
  .x(function(d) {
    return x(d.date)
  })
  .y(function(d) {
    return y(d.value)
  });

var area = d3.area()
  .x(function(d) {
    return x(d.date)
  })
  .y1(function(d) {
    return y(d.value)
  })
  .y0(y(0));

x.domain(d3.extent(data, function(d) {
  return d.date
}));
y.domain([0, d3.max(data, function(d) {
  return d.value
})]);

g.append("path")
  .datum(data.filter(function(d) {
    return d.date <= border;
  }))
  .attr("fill", "red")
  .attr("d", area);

g.append("path")
  .datum(data.filter(function(d) {
    return d.date >= border;
  }))
  .attr("fill", "green")
  .attr("d", area);

g.append("path")
  .datum(data)
  .attr("fill", "none")
  .attr("stroke", "steelblue")
  .attr("stroke-linejoin", "round")
  .attr("stroke-linecap", "round")
  .attr("stroke-width", 2)
  .attr("d", line);

g.append("line")
  .attr("x1", x(border))
  .attr("y1", 0)
  .attr("x2", x(border))
  .attr("y2", 350)
  .style("stroke-width", 2)
  .style("stroke", "blue");

g.append("g")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x))
  .append("text")
  .attr("fill", "#000")
  .attr("transform", "rotate(0)")
  .attr("y", 0)
  .attr("dx", "50em")
  .attr("dy", "-0.5em")
  .attr("text-anchor", "start")
  .text("Title ($)");

// Text for Y axes
g.append("g")
  .call(d3.axisLeft(y))
  .append("text")
  .attr("fill", "#000")
  .attr("transform", "rotate(-90)")
  .attr("y", 6)
  .attr("dy", "0.71em")
  .attr("text-anchor", "end")
  .text("Price ($)");
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

Have in mind that this simple code works because, in your example, the limit (which is 300) is actually a data point. If that's not the case (for instance, the limit is 280 but the two neighbours data points are 250 and 300) you'll need a more complex algorithm for creating the two individual data sets.

Upvotes: 0

Related Questions