Reputation: 169
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:
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
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