Search code examples
chartsvega-litearea

Intersection Color Not Showing Correctly


Seeking help to troubleshoot Vega Lite area chart.I need to get the intersection area between "activity" and "stop" line correctly colored.

I did use transfrom to create two parts of the area to be calculated, with some logic to for choosing which line will be up or down. However, in the area chart, the interpolation of the area doesn't reflect the desired area to be colored.

Current State

Below is the vega lite code so far


    {
      "data": 
      {
        "values": [{"Date":"2024-04-01","Activity":3,"Stops":10},{"Date":"2024-04-02","Activity":6,"Stops":13},{"Date":"2024-04-03","Activity":13,"Stops":11},{"Date":"2024-04-04","Activity":13,"Stops":14},{"Date":"2024-04-05","Activity":12,"Stops":16},{"Date":"2024-04-06","Activity":6,"Stops":11},{"Date":"2024-04-07","Activity":9,"Stops":8},{"Date":"2024-04-08","Activity":10,"Stops":9},{"Date":"2024-04-09","Activity":11,"Stops":5},{"Date":"2024-04-10","Activity":20,"Stops":10},{"Date":"2024-04-11","Activity":9,"Stops":6},{"Date":"2024-04-12","Activity":15,"Stops":8},{"Date":"2024-04-13","Activity":17,"Stops":4},{"Date":"2024-04-14","Activity":7,"Stops":5},{"Date":"2024-04-15","Activity":5,"Stops":8},{"Date":"2024-04-16","Activity":14,"Stops":4},{"Date":"2024-04-17","Activity":18,"Stops":3},{"Date":"2024-04-18","Activity":13,"Stops":13},{"Date":"2024-04-19","Activity":13,"Stops":5},{"Date":"2024-04-20","Activity":14,"Stops":3},{"Date":"2024-04-21","Activity":10,"Stops":8},{"Date":"2024-04-22","Activity":15,"Stops":12},{"Date":"2024-04-23","Activity":15,"Stops":6},{"Date":"2024-04-24","Activity":19,"Stops":9},{"Date":"2024-04-25","Activity":6,"Stops":9},{"Date":"2024-04-26","Activity":16,"Stops":6},{"Date":"2024-04-27","Activity":11,"Stops":3},{"Date":"2024-04-28","Activity":9,"Stops":10},{"Date":"2024-04-29","Activity":14,"Stops":6},{"Date":"2024-04-30","Activity":19,"Stops":9}]
        },
        "width":500,
        "height":200,
      "layer": [
        
              {
                "mark": {
                  "type": "line",
                  "point":{
                    "filled":true,
                    "fill":"#20419A"
                  },
                  "color":"#20419A",
                  "opacity": 1,
                  "tooltip": true
                },
                "encoding": {
                  "y": {
                    "field": "Activity"
                  }
                }
              },
              {
                "mark": {
                  "type": "line",
                  "point":{
                    "filled":true,
                    "fill":"#00A19C"
                  },
                  "color":"#00A19C",
                  "opacity": 1,
                  "tooltip": true
                },
                "encoding": {
                  "y": {
                    "field": "Stops"
                  }
                }
              },
              {
                "transform":[
                  {
                    "calculate":"datum['Stops']<=datum['Activity']?datum['Stops']:datum['Activity']", "as":"MinY"
                  }
                ],
                "mark": {
                  "type": "area",
                  "line":true,
                  "point":true,
                    "color":"red",      "opacity": 1,
                    "tooltip":true
                },
                "encoding": {
                
                  "y": {
                    "field": "Activity"
                  },
                  "y2": {
                    "field": "MinY"
                  }
                }
              },
              {
                "transform":[
                  {
                    "calculate":"datum['Activity']<=datum['Stops']?datum['Activity']:datum['Stops']", "as":"MinY1"
                  }
                ],
                "mark": {
                  "type": "area",
                  "line":true,
                  "point":true,
                  "interpolate":"linear",
                    "color":"#00A19C",      "opacity": 1,
                    "tooltip":true
                },
                "encoding": {
                
                  "y": {
                    "field": "Stops"
                  },
                  "y2": {
                    "field": "MinY1"
                  }
                }
              }
      ],
       "encoding": {
              "x": {
                "field": "Date",
                "type": "temporal",
                "axis": {
                  "labelFontSize": 8,
                  "labelFont": "sans-serif",
                  "labelFontWeight": "bold",
                  "labelAngle": 0,
                  "title":"Date",
                  "format":"%_d %b"
                }
              },
              "y": {
                "type": "quantitative",
                 "axis": {
                  "labelFontSize": 12,
                  "labelFont": "sans-serif",
                  "labelFontWeight": "bold",
                  "title": null,
                  "domain":false,
                  "grid":false,
                  "ticks":false,
                  "labels":false
                }
              }
            },
        "title": {
        "text": [
          "XXXXX"
        ],
        "subtitle": "Operations",
        "font": "sans-serif",
        "align": "left",
        "anchor": "start",
        "dx": 0,
        "fontSize": 18,
        "subtitleFontSize": 12
      }
    }

[text]

This is the code in vega lite with the transform calculation to create red and green area Link to editor Note that, the colored areas not matching as the expected "intersection" as in the following area chart (without transfrom calculation used). Link to editor

Area between "blue" and "green" line will be "red". Area between "green" and "blue" line will be green Desired Outcome

Update. Solved following material from @APB Report. The following is the final code:

{
      "config": {
      "view": {"stroke": "transparent"},
      "font": "Segoe UI",
      "axis": {
        "title": null,
        "grid": false,
        "ticks": false,
        "labelPadding": 10,
        "labelFontSize": 12
      },
      "area": {
    "opacity": 1.0 },
      "axisY": {"domain": false},
      "style": {
        "delta_negative": {
          "color": "#FF0000"
        },
        "delta_positive": {
          "color": "#00A19C"
        },
        "mask_foreground": {
          "color": "#ffffff",
          "stroke": "#ffffff"
        },
        "Activity_line": {
          "color": "#763F98",
          "strokeWidth": 2
        },
        "stops_line": {
          "color": "#00A19C",
          "strokeWidth": 3
        }
      }
    },
    "width":500,
    "height":250,
    "data": {
      "values": [
      {"Date": "2024-04-01", "Activity": 3, "Stops": 10},
      {"Date": "2024-04-02", "Activity": 6, "Stops": 13},
      {"Date": "2024-04-03", "Activity": 13, "Stops": 11},
      {"Date": "2024-04-04", "Activity": 13, "Stops": 14},
      {"Date": "2024-04-05", "Activity": 12, "Stops": 16},
      {"Date": "2024-04-06", "Activity": 6, "Stops": 11},
      {"Date": "2024-04-07", "Activity": 9, "Stops": 8},
      {"Date": "2024-04-08", "Activity": 10, "Stops": 9},
      {"Date": "2024-04-09", "Activity": 11, "Stops": 5},
      {"Date": "2024-04-10", "Activity": 20, "Stops": 10},
      {"Date": "2024-04-11", "Activity": 9, "Stops": 6},
      {"Date": "2024-04-12", "Activity": 15, "Stops": 8},
      {"Date": "2024-04-13", "Activity": 17, "Stops": 4},
      {"Date": "2024-04-14", "Activity": 7, "Stops": 5},
      {"Date": "2024-04-15", "Activity": 5, "Stops": 8},
      {"Date": "2024-04-16", "Activity": 14, "Stops": 4},
      {"Date": "2024-04-17", "Activity": 18, "Stops": 3},
      {"Date": "2024-04-18", "Activity": 13, "Stops": 13},
      {"Date": "2024-04-19", "Activity": 13, "Stops": 5},
      {"Date": "2024-04-20", "Activity": 14, "Stops": 3},
      {"Date": "2024-04-21", "Activity": 10, "Stops": 8},
      {"Date": "2024-04-22", "Activity": 15, "Stops": 12},
      {"Date": "2024-04-23", "Activity": 15, "Stops": 6},
      {"Date": "2024-04-24", "Activity": 19, "Stops": 9},
      {"Date": "2024-04-25", "Activity": 6, "Stops": 9},
      {"Date": "2024-04-26", "Activity": 16, "Stops": 6},
      {"Date": "2024-04-27", "Activity": 11, "Stops": 3},
      {"Date": "2024-04-28", "Activity": 9, "Stops": 10},
      {"Date": "2024-04-29", "Activity": 14, "Stops": 6},
      {"Date": "2024-04-30", "Activity": 19, "Stops": 9}
    ]
    },
    "encoding": {
      "x": {
        "field": "Date",
        "type": "temporal",
        "axis": {
          "zindex": 1,
          "format": "%_d-%b"
        }
      },
      "y": {
        "type": "quantitative",
        "axis": {"tickCount": 5}
      }
    },
    "layer": [
      {
        "description": "Target area - background",
        "mark": {
          "type": "area",
          "style": "delta_negative",
          "tooltip":true
        },
        "encoding": {
          "y": {"field": "Activity"}
        }
      },
      {
        "description": "Actual area - masks out target where necessary",
        "mark": {
          "type": "area",
          "style": "delta_positive",
          "tooltip":true
        },
        "encoding": {
          "y": {"field": "Stops"}
        }
      },
      {
        "description": "Masking layer (with interpolated points)",
        "transform": [
          {
            "calculate": "min(datum['Activity'], datum['Stops'])",
            "as": "lowest_value"
          },
          {
            "window": [
              {
                "op": "lead",
                "field": "Date",
                "as": "date_following"
              },
              {
                "op": "lead",
                "field": "Stops",
                "as": "stops_following"
              },
              {
                "op": "lead",
                "field": "Activity",
                "as": "activity_following"
              }
            ]
          },
          {
            "calculate": "(datum['stops_following'] - datum['Stops']) / (datum['date_following'] - datum['Date'])",
            "as": "stops_slope"
          },
          {
            "calculate": "(datum['activity_following'] - datum['Activity']) / (datum['date_following'] - datum['Date'])",
            "as": "activity_slope"
          },
          {
            "calculate": "datum['Stops'] - (datum['stops_slope'] * datum['Date'])",
            "as": "stops_y_intercept"
          },
          {
            "calculate": "datum['Activity'] - (datum['activity_slope'] * datum['Date'])",
            "as": "activity_y_intercept"
          },
          {
            "calculate": "(datum['activity_y_intercept'] - datum['stops_y_intercept']) / (datum['stops_slope'] - datum['activity_slope'])",
            "as": "intersect_base"
          },
          {
            "calculate": "datetime(datum['intersect_base']) > datum['Date'] && datum['intersect_base'] < datum['date_following']",
            "as": "intersect_before_following"
          },
          {
            "calculate": "datum['intersect_before_following'] ? datetime(datum['intersect_base']) : null",
            "as": "intersect_x"
          },
          {
            "calculate": "datum['intersect_before_following'] ? (datum['stops_slope'] * datum['intersect_base']) + datum['stops_y_intercept'] : null",
            "as": "intersect_y"
          },
          {
            "fold": [
              "Date",
              "intersect_x"
            ]
          },
          {
            "filter": "datum['value'] !== null"
          },
          {
            "calculate": "datum['key'] === 'Date' ? datum['Date'] : datum['intersect_x']",
            "as": "x"
          },
          {
            "calculate": "datum['key'] === 'Date' ? datum['lowest_value'] : datum['intersect_y']",
            "as": "y"
          }
        ],
        "mark": {
          "type": "area",
          "style": "mask_foreground"
        },
        "encoding": {
          "x": {"field": "x"},
          "y": {"field": "y"}
        }
      },
      {
        "description": "Activity line",
        "mark": {
          "type": "line",
          "style": "Activity_line",
          "tooltip":true
        },
        "encoding": {
          "y": {"field": "Activity"}
        }
      },
      {
        "description": "Stops line",
        "mark": {
          "type": "line",
          "style": "stops_line",
          "tooltip":true
        },
        "encoding": {
          "y": {"field": "Stops"}
        }
      }
    ],
      "title": {
    "text": [
      "XXXXX"
    ],
    "subtitle": "Operations",
    "font": "sans-serif",
    "align": "left",
    "anchor": "start",
    "dx": 0,
    "fontSize": 18,
    "subtitleFontSize": 12
  }
  }

Link to editor


Solution

  • I recommend you have a read of Daniel Marsh-Patrick's very insightful analysis here:

    A Study on Hills and Valleys in Power BI with Deneb:

    https://coacervo.co/deneb_hill_valley

    You need to calculate where the lines intersect in a better way. I have provided a similar answer a while back how to do this with Javascript.

    Vega-Lite gradient for line chart