My dataset looks like this:
var data = [{
"Date": "31/12/2019",
"Value": 23,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/01/2020",
"Value": 49,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "29/02/2020",
"Value": 23,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/03/2020",
"Value": 12,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/04/2020",
"Value": 33,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/05/2020",
"Value": 62,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/06/2020",
"Value": 65,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/07/2020",
"Value": 77,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/08/2020",
"Value": 65,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/09/2020",
"Value": 58,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/10/2020",
"Value": 87,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/11/2020",
"Value": 120,
"Type": "Actual",
"Plan": 100,
"Over": "Y"
},
{
"Date": "31/12/2020",
"Value": 103,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
},
{
"Date": "31/01/2021",
"Value": 110,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
},
{
"Date": "28/02/2021",
"Value": 117,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
}
]
I'm trying to plot a line chart that has the following formatting:
When d.Type === "Actual"
then show the line as SOLID and BLUE
When d.Type === "Fcst"
show the line as dashed (stroke-dasharray
) and GREEN
Essential this is what I'm trying to achieve:
...and this is what I have so far:
This is what I tried doing to add the conditional fomatting:
//add Trend Line
svg.append('path')
.datum(data)
.attr('class', 'data-line glowed')
.style('stroke', function(d, i) {
if (d.Type == "Fcst")
return "#00D4B2"
else {
return "#3087CD"
}
})
.style('stroke-width', 2)
.style("stroke-dasharray",function(d, i) {
if (d.Type == "Fcst")
return ("3, 1"); else {
return ("0, 0")
}
})
.style('fill', 'none')
.attr('d', line)
Here is my JSFIDDLE
CodePudding user response:
Because you use datum
you don't iterate over each point of the line - instead the entire path is generated by line
and is a single element and can only accept one set of styles.
The approach below draws 3 lines (one transparent) overlaid to one another. The shorter 'actual' line is on top of the 'forecast' line. You need to do it this way to get the nice curvature (I use d3.curveMonotoneX
).
The steps:
- Draw the line for 'actual' data (per filter on
Type
) but make it transparent - Calculate the length of this 'actual' line from step (1) with
getTotalLength()
- Draw the 'forecast' line for the full length of line (e.g. dashed green)
- Draw the 'actual' line again, this time in blue, and also set
stroke-dasharray
such that it has one dash (from start for length calculated in step (2)) and no repeating dash so the end of the original 'forecast' line shows through this single 'gap' of the dashed line.
See working example below and check the comments:
// get actual data points only
const actualData = data.filter(d => d.Type === "Actual");
// draw actual line but transparent
const actual = svg.append("g")
.append("path")
.datum(actualData)
.attr("d", line)
.style("fill", "none")
.style("opacity", 0)
.style("stroke-width", lineWidth);
// get length of actual curve
const actualLength = actual.node().getTotalLength();
// draw all data points in dashed green - this will be forecast
const allPoints = svg.append("g")
.append("path")
.datum(data)
.attr("d", line)
.style("fill", "none")
.style("stroke", "#00ee22")
.style("stroke-width", lineWidth)
.style("stroke-dasharray", "4, 2");
// draw full line in blue but only to actual end point with stroke-dasharray
const forecast = svg.append("g")
.append("path")
.datum(data)
.attr("d", line)
.style("fill", "none")
.style("stroke", "#2222ee")
.style("stroke-width", lineWidth)
.style("stroke-dasharray", `${actualLength}`);
// add points on line etc
const points = svg.selectAll(".label")
.data(data)
.enter()
.append("circle")
.attr("cx", d => xScale(parseTime(d.Date)))
.attr("cy", d => yScale(d.Value))
.attr("r", 4)
.attr("fill", "#2288ee")
.attr("stroke", "#22ccee")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script>
var data = [{
"Date": "31/12/2019",
"Value": 23,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/01/2020",
"Value": 49,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "29/02/2020",
"Value": 23,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/03/2020",
"Value": 12,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/04/2020",
"Value": 33,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/05/2020",
"Value": 62,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/06/2020",
"Value": 65,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/07/2020",
"Value": 77,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/08/2020",
"Value": 65,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/09/2020",
"Value": 58,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "31/10/2020",
"Value": 87,
"Type": "Actual",
"Plan": 100,
"Over": ""
},
{
"Date": "30/11/2020",
"Value": 120,
"Type": "Actual",
"Plan": 100,
"Over": "Y"
},
{
"Date": "31/12/2020",
"Value": 103,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
},
{
"Date": "31/01/2021",
"Value": 110,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
},
{
"Date": "28/02/2021",
"Value": 117,
"Type": "Fcst",
"Plan": 100,
"Over": "Y"
}
];
// basic line chart setup
const innerWidth = 520;
const innerHeight = 160;
const margin = {top: 5, bottom: 25, left: 15, right: 5}
const lineWidth = 3;
const svg = d3.select("body")
.append("svg")
.attr("width", innerWidth margin.left margin.right)
.attr("height", innerHeight margin.top margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const yScale = d3.scaleLinear()
.range([innerHeight, 0])
.domain([0, d3.max(data, d => d.Value) 20]);
const yAxis = svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale).ticks(5));
const parseTime = d3.timeParse("%d/%m/%Y")
const dates = data.map(d => parseTime(d.Date));
const xScale = d3.scaleTime()
.range([0, innerWidth])
.domain(d3.extent(dates))
.nice();
const xAxis = svg.append("g")
.attr("transform", `translate(${margin.left}, ${innerHeight})`)
.call(d3.axisBottom(xScale)
.tickFormat(d3.timeFormat('%b'))
);
const line = d3.line()
.x(d => xScale(parseTime(d.Date)))
.y(d => yScale(d.Value))
.curve(d3.curveMonotoneX);
</script>