Search code examples
javascriptd3.jschartsline

Assign different colours to vertical lines in a line chart based on property value


I would like to create a line chart with two vertical lines that mark the start (in red) and end (in green) of a streak of high values (10). The colours of these lines should be appointed based on the start_end property in the output variable, like so:

enter image description here

Any ideas on how to achieve this? This is my code so far (i'm using d3 v6)

{
  const svg = d3.select(DOM.svg(width, height))
  
  svg.append('g').call(xAxis)
  svg.append('g').call(yAxis)
 
  svg.append("line")
        .datum(output)
        .attr("d", line)
        .attr("fill", "none")
        .style("stroke", "steelblue")
        .style("stroke-width", 4)
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("x1", xScale(output.timestamp))
        .attr("y1", 0)
        .attr("x2", xScale(output.timestamp))
        .attr("y2", 10)
        
  svg.append('path')
      .datum(segment)
      .attr('d', line)
      .style('fill', 'none')
      .style('stroke', 'black')  
  
  return svg.node()
}

output = [
  {timestamp: "2020-09-25T04:00:54.857Z", jam_factor: 10, start_end: "start"}
  {timestamp: "2020-09-29T18:02:23.282Z", jam_factor: 8.39212, start_end: "end"}
]

Solution

  • I generated some data and show below how to add start and stop lines in a generic way.

    const data = [0, 3, 5, 7, 5, 4, 6, 7, 10, 10, 10, 10, 7, 5, 7, 6, 8, 9, 9, 10, 10, 10, 10, 10, 10, 9, 5, 6, 4, 6, 8, 10, 10, 9].map((v, i) => {
      const now = new Date();
      now.setDate(i);
      return {
        timestamp: now,
        value: v,
      };
    });
    
    // Find all places where we first hit or stop hitting 10
    let active = false;
    data.forEach((d, i) => {
      if (d.value === 10) {
        if (!active) {
          d.start = true;
          active = true;
        }
      } else {
        // The previous was the last active one
        if (active) {
          data[i - 1].end = true;
          active = false;
        }
      }
    });
    
    const width = 600,
      height = 300;
    const x = d3.scaleTime()
      .domain(d3.extent(data, d => d.timestamp))
      .range([0, width]);
    const y = d3.scaleLinear()
      .domain([0, 10])
      .range([height - 10, 10]);
    const line = d3.line()
      .x(d => x(d.timestamp))
      .y(d => y(d.value))
    
    const svg = d3.select('svg')
      .attr('width', width)
      .attr('height', height)
    
    svg.append("path")
      .datum(data)
      .attr("d", line)
      .style("stroke", "steelblue")
      .style("stroke-width", 2)
      .style("fill", "none");
    
    svg.selectAll("line")
      .data(data.filter(d => d.start || d.end))
      .enter()
      .append("line")
      .attr("x1", d => x(d.timestamp))
      .attr("x2", d => x(d.timestamp))
      .attr("y1", y.range()[0])
      .attr("y2", y.range()[1])
      .attr("stroke", d => d.start ? "green" : "red")
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
    <svg></svg>