Connor
Connor

Reputation: 4844

Align area and line marks to same domain in Vega-Lite

I'm trying to build a line chart with error area in vega lite.

enter image description here

  {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {"url": "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv"},
  "transform": [
    {"calculate": "toNumber(datum.x)", "as": "x2"},
    {"calculate": "toNumber(datum.y)", "as": "y2"},
    {"calculate": "toNumber(datum.CI_left)", "as": "l"},
    {"calculate": "toNumber(datum.CI_right)", "as": "r"}
  ],
  "params": [
    { "name": "scaleDomain", "expr": "[0, 10]"}
  ],
  "encoding": {
    "y": {
      "field": "x2",
      "type": "ordinal",
      "sort": "descending"
    }
  },
  "layer": [
    {
      "mark": {"type": "line", "interpolate": "cardinal"},
      "encoding": {
        "x": {
          "field": "y",
          "type": "quantitative",
          "title": "Mean of Miles per Gallon (95% CIs)",
          "scale": {"type": "linear", "domain": {"expr": "scaleDomain"}},
          "axis": {
            "orient": "top"
          }
        }
      }
    },
    {
      "mark": {"type": "area", "interpolate": "cardinal"},
      "encoding": {
        "x": {
          "field": "l",
          "scale": {"type": "linear", "domain": {"expr": "scaleDomain"}},
          "axis": {
            "orient": "top"
          }
        },
        "x2": {
          "field": "r"
        },
        "opacity": { "value": 0.3 }
      }
    }
  ]
}

So far, it's nice looking. But there's a problem: to get this to work I have had to manually constrain the scale domain for the two marks by setting a param called scaleDomain. This is a problem, because if ever the data changes I need to manually update the domain :/

However, look what would happen if I didn't manually set the scale to the same domain for the area plot and a line plot: enter image description here

  {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {"url": "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv"},
  "transform": [
    {"calculate": "toNumber(datum.x)", "as": "x2"},
    {"calculate": "toNumber(datum.y)", "as": "y2"},
    {"calculate": "toNumber(datum.CI_left)", "as": "l"},
    {"calculate": "toNumber(datum.CI_right)", "as": "r"}
  ],
  "params": [
    { "name": "scaleDomain", "expr": "[0, 10]"}
  ],
  "encoding": {
    "y": {
      "field": "x2",
      "type": "ordinal",
      "sort": "descending"
    }
  },
  "layer": [
    {
      "mark": {"type": "line", "interpolate": "cardinal"},
      "encoding": {
        "x": {
          "field": "y",
          "type": "quantitative",
          "title": "Mean of Miles per Gallon (95% CIs)",
          // "scale": {"type": "linear", "domain": {"expr": "scaleDomain"}},
          "axis": {
            "orient": "top"
          }
        }
      }
    },
    {
      "mark": {"type": "area", "interpolate": "cardinal"},
      "encoding": {
        "x": {
          "field": "l",
          // "scale": {"type": "linear", "domain": {"expr": "scaleDomain"}},
          "axis": {
            "orient": "top"
          }
        },
        "x2": {
          "field": "r"
        },
        "opacity": { "value": 0.3 }
      }
    }
  ]
}

Yikes! The area plot gets a bit lost and doesn't track the line.

I can see one of two solutions to this problem:

  1. Shared Scale: Coax the two mark layers to share the same scale
  2. Manually Calculate Scale Domain: Use a parameter or a signal to store the desired domain.

I don't know how to do #1, but it seems like the correct approach. One imagined solution is something like:

"scale": {"align": "shared"},

I tried adding an aggregation to transform, but that of course results in summarizing the whole data set.

  "transform": [
    {"calculate": "toNumber(datum.x)", "as": "x2"},
    {"calculate": "toNumber(datum.y)", "as": "y2"},
    {"calculate": "toNumber(datum.CI_left)", "as": "l"},
    {"calculate": "toNumber(datum.CI_right)", "as": "r"},
    { "aggregate": [
      {
        "field": "l",
        "op": "min",
        "as": "min"
      },
      {
        "field": "r",
        "op": "max",
        "as": "max"
      }
    ]}
  ],

enter image description here

It seems like I'd want to somehow put the transform directly into the layer or the params, but it's not clear how to do that.

I have seen these answers (finding max and min from dataset in vega and Post aggregation calculation & filter ##) but I don't know how to use them to achieve this.

Upvotes: 1

Views: 204

Answers (1)

davidebacci
davidebacci

Reputation: 30219

You don't need any transforms and scales are automatically shared. Try this:

enter image description here

    {
      "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
      "width":500,
      "height":500,
      "data": {
        "url": "https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv"
      },
    
      
      "encoding": {"y": {"field": "x", "type": "quantitative", "sort": "ascending"}},
      "layer": [
        {
          "mark": {"type": "line", "interpolate": "cardinal"},
          "encoding": {
            "x": {
              "field": "y",
              "sort": null,
              "type": "quantitative",
              "title": "Mean of Miles per Gallon (95% CIs)",
              "axis": {"orient": "top"}
            }
          }
        },
        {
          "mark": {"type": "area", "interpolate": "cardinal"},
          "encoding": {
            "x": {"field": "CI_left", "type": "quantitative"},
            "x2": {"field": "CI_right"},
            "opacity": {"value": 0.3}
          }
        }
      ]
    }

Upvotes: 1

Related Questions