score:1

Accepted answer

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:

  1. Draw the line for 'actual' data (per filter on Type) but make it transparent
  2. Calculate the length of this 'actual' line from step (1) with getTotalLength()
  3. Draw the 'forecast' line for the full length of line (e.g. dashed green)
  4. 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>


Related Query