Mark Unsworth
Mark Unsworth

Reputation: 3177

Consistent gradient on vega-lite bar chart

Is it possible in vega or vega lite to create bar chart that has a fixed gradient across the entire chart, rather than a seperate gradient for each bar?

heatmap gradient bar chart

I have tried to use a gradient color/fill on the mark but it creates a new gradient for each bar:

enter image description here

Here's the code i've tried in the Vega Editor

Upvotes: 2

Views: 103

Answers (2)

APB Reports
APB Reports

Reputation: 2441

Try this:

I have added support for a 5 color gradient here but this could easily be modified to 3 or even 10 for example. enter image description here

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": 350,
  "height": 250,
  "data": {
    "values": [
      {"date": "2024-08-25", "views": 30},
      {"date": "2024-08-26", "views": 7},
      {"date": "2024-08-27", "views": 12},
      {"date": "2024-08-28", "views": 15},
      {"date": "2024-08-29", "views": 8},
      {"date": "2024-08-30", "views": 2},
      {"date": "2024-08-31", "views": 28},
      {"date": "2024-09-01", "views": 6},
      {"date": "2024-09-02", "views": 16}
    ]
  },
  "transform": [
    {"joinaggregate": [{"op": "max", "field": "views", "as": "maxVal"}]},
    {"calculate": "(datum.maxVal/5)*1", "as": "gradEnd1"},
    {"calculate": "(datum.maxVal/5)*2", "as": "gradEnd2"},
    {"calculate": "(datum.maxVal/5)*3", "as": "gradEnd3"},
    {"calculate": "(datum.maxVal/5)*4", "as": "gradEnd4"},
    {"calculate": "(datum.maxVal/5)*5", "as": "gradEnd5"},
    {
      "joinaggregate": [{"op": "max", "field": "views", "as": "maxValDate"}],
      "groupby": ["date"]
    },
    {
      "joinaggregate": [{"op": "min", "field": "views", "as": "minValDate"}],
      "groupby": ["date"]
    }
  ],
  "encoding": {
    "x": {
      "type": "ordinal",
      "field": "date",
      "timeUnit": "yearmonthdate",
      "axis": {"format": "%m/%d", "title": null, "labelBound": true}
    },
    "y": {
      "field": "views",
      "aggregate": "sum",
      "type": "quantitative",
      "axis": {"title": null}
    }
  },
  "layer": [
    {
      "transform": [
        {"calculate": "0", "as": "gradStart"},
        {"calculate": "datum.gradEnd1", "as": "gradEndTemp"},
        {
          "calculate": "datum.minValDate <= datum.gradEndTemp ? datum.minValDate: datum.gradEndTemp",
          "as": "gradEnd"
        },
        {"filter": "datum.minValDate >= 0"}
      ],
      "mark": {
        "type": "bar",
        "tooltip": true,
        "clip": true,
        "fill": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0.3,
          "gradient": "linear",
          "stops": [
            {"offset": 0, "color": "#2c7bb6"},
            {"offset": 1, "color": "#abd9e9"}
          ]
        }
      },
      "encoding": {
        "y": {"type": "quantitative", "field": "gradEnd"},
        "y2": {"field": "gradStart"}
      }
    },
    {
      "transform": [
        {"calculate": "datum.gradEnd1*.90", "as": "gradStart"},
        {"calculate": "datum.gradEnd2", "as": "gradEndTemp"},
        {
          "calculate": "datum.minValDate <= datum.gradEndTemp ? datum.minValDate: datum.gradEndTemp",
          "as": "gradEnd"
        },
        {"filter": "datum.minValDate >= datum.gradEnd1"}
      ],
      "mark": {
        "type": "bar",
        "tooltip": true,
        "clip": true,
        "fill": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0.3,
          "gradient": "linear",
          "stops": [
            {"offset": 0, "color": "#abd9e9"},
            {"offset": 1, "color": "#ffd01b"}
          ]
        }
      },
      "encoding": {
        "y": {"type": "quantitative", "field": "gradEnd"},
        "y2": {"field": "gradStart"}
      }
    },
    {
      "transform": [
        {"calculate": "datum.gradEnd2*.90", "as": "gradStart"},
        {"calculate": "datum.gradEnd3", "as": "gradEndTemp"},
        {
          "calculate": "datum.minValDate <= datum.gradEndTemp ? datum.minValDate: datum.gradEndTemp",
          "as": "gradEnd"
        },
        {"filter": "datum.minValDate >= datum.gradEnd2"}
      ],
      "mark": {
        "type": "bar",
        "tooltip": true,
        "clip": true,
        "fill": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0.3,
          "gradient": "linear",
          "stops": [
            {"offset": 0, "color": "#ffd01b"},
            {"offset": 1, "color": "#ff891b"}
          ]
        }
      },
      "encoding": {
        "y": {"type": "quantitative", "field": "gradEnd"},
        "y2": {"field": "gradStart"}
      }
    },
    {
      "transform": [
        {"calculate": "datum.gradEnd3*.90", "as": "gradStart"},
        {"calculate": "datum.gradEnd4", "as": "gradEndTemp"},
        {
          "calculate": "datum.minValDate <= datum.gradEndTemp ? datum.minValDate: datum.gradEndTemp",
          "as": "gradEnd"
        },
        {"filter": "datum.minValDate >= datum.gradEnd3"}
      ],
      "mark": {
        "type": "bar",
        "tooltip": true,
        "clip": true,
        "fill": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0.3,
          "gradient": "linear",
          "stops": [
            {"offset": 0, "color": "#ff891b"},
            {"offset": 1, "color": "#d7191c"}
          ]
        }
      },
      "encoding": {
        "y": {"type": "quantitative", "field": "gradEnd"},
        "y2": {"field": "gradStart"}
      }
    },
    {
      "transform": [
        {"calculate": "datum.gradEnd4*.90", "as": "gradStart"},
        {"calculate": "datum.gradEnd5", "as": "gradEndTemp"},
        {
          "calculate": "datum.minValDate <= datum.gradEndTemp ? datum.minValDate: datum.gradEndTemp",
          "as": "gradEnd"
        },
        {"filter": "datum.minValDate >= datum.gradEnd4"}
      ],
      "mark": {
        "type": "bar",
        "tooltip": true,
        "clip": true,
        "fill": {
          "x1": 1,
          "y1": 1,
          "x2": 1,
          "y2": 0.3,
          "gradient": "linear",
          "stops": [
            {"offset": 0, "color": "#d7191c"},
            {"offset": 1, "color": "#470809"}
          ]
        }
      },
      "encoding": {
        "y": {"type": "quantitative", "field": "gradEnd"},
        "y2": {"field": "gradStart"}
      }
    }
  ]
}

Upvotes: 1

davidebacci
davidebacci

Reputation: 30174

I think this is quite difficult given how gradients are created. Here is a hacky way of drawing full bars and then white bars on top to achieve the desired outcome. Grid lines won't be the same due to zindex issue but it might be a good enough solution for you.

enter image description here

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "background": "white",
  "padding": 5,
  "height": 200,
  "style": "cell",
  "data": [
    {
      "name": "source_0",
      "values": [
        {"date": "2024-08-25", "views": 30},
        {"date": "2024-08-26", "views": 7},
        {"date": "2024-08-27", "views": 12},
        {"date": "2024-08-28", "views": 15},
        {"date": "2024-08-29", "views": 8},
        {"date": "2024-08-30", "views": 2},
        {"date": "2024-08-31", "views": 28},
        {"date": "2024-09-01", "views": 6},
        {"date": "2024-09-02", "views": 16}
      ]
    },
    {
      "name": "data_0",
      "source": "source_0",
      "transform": [
        {"type": "formula", "expr": "toDate(datum[\"date\"])", "as": "date"},
        {
          "field": "date",
          "type": "timeunit",
          "units": ["year", "month", "date"],
          "as": ["yearmonthdate_date", "yearmonthdate_date_end"]
        },
        {
          "type": "stack",
          "groupby": ["yearmonthdate_date"],
          "field": "views",
          "sort": {"field": [], "order": []},
          "as": ["views_start", "views_end"],
          "offset": "zero"
        },
        {
          "type": "filter",
          "expr": "isValid(datum[\"views\"]) && isFinite(+datum[\"views\"])"
        },
        {"type": "extent", "field": "views", "signal": "extent"}
      ]
    }
  ],
  "signals": [
    {"name": "x_step", "value": 20},
    {
      "name": "width",
      "update": "bandspace(domain('x').length, 0.1, 0.05) * x_step"
    }
  ],
  "marks": [
    {
      "name": "marks", "zindex":0,
      "type": "rect",
      "style": ["bar"],
      "from": {"data": "data_0"},
      "encode": {
        "update": {
          "fill": {"signal": "gradient('color', [0,0], [0,1])"},
          "x": {"scale": "x", "field": "yearmonthdate_date"},
          "width": {"signal": "max(0.25, bandwidth('x'))"},
          "y": {"signal": "scale('y', extent[1])"},
          "y2": {"scale": "y", "field": "views_start"},
          
        }
      }
    },
    {
      "name": "marks2","zindex":1,
      "type": "rect",
      "style": ["bar"],
      "from": {"data": "data_0"},
      "encode": {
        "update": {
          "fill": {"signal": "'white'"},
          "x": {"scale": "x", "field": "yearmonthdate_date"},
          "width": {"signal": "max(0.25, bandwidth('x'))"},
          "y": {"signal": "scale('y', datum.views)"},
          "y2": {"signal": "scale('y', extent[1])"},
        
        }
      }
    }
  ],
  "scales": [
    {
      "name": "x",
      "type": "band",
      "domain": {"data": "data_0", "field": "yearmonthdate_date", "sort": true},
      "range": {"step": {"signal": "x_step"}},
      "paddingInner": 0.1,
      "paddingOuter": 0.05
    },
    {
      "name": "y",
      "type": "linear",
      "domain": {"data": "data_0", "fields": ["views_start", "views_end"]},
      "range": [{"signal": "height"}, 0],
      "nice": true,
      "zero": true
    },
    {
      "name": "color",
      "type": "sequential",
      "range": [
        "#002C41",
        "#35406F",
        "#834886",
        "#CE4D7C",
        "#FC6B55",
        "#FFA516"
      ]
    }
  ],
  "axes": [
    {
      "scale": "y",
      "orient": "left",
      "gridScale": "x",
      "grid": false,
      "tickCount": {"signal": "ceil(height/40)"},
      "domain": false,
      "labels": false,
      "aria": false,
      "maxExtent": 0,
      "minExtent": 0,
      "ticks": false
    },
    {
      "scale": "x",
      "orient": "bottom",
      "grid": false,
      "format": "%m/%d",
      "labelBound": true,
      "formatType": "time",
      "labelOverlap": true,
      "tickMinStep": {
        "signal": "datetime(2001, 0, 2, 0, 0, 0, 0) - datetime(2001, 0, 1, 0, 0, 0, 0)"
      },
      "zindex": 0
    },
    {
      "scale": "y",
      "orient": "left",
      "grid": false,
      "labelOverlap": true,
      "tickCount": {"signal": "ceil(height/40)"},
      "zindex": 0
    }
  ]
}

Upvotes: 1

Related Questions