Vega legend and color per mark - legend

As you can see in the example, I want to create a chart with multiple marks. Each of these marks needs a specific color, a corresponding label and legend. I get that I define the color by "domain": {"data": "table", "field": "District"}. But I need a specific color for my 'subtotal' and 'totals' line. And the legend should read Neustadt - Totals and Neustadt - Subtotals.
I have played around with what I can find in the scales documentation. But I just cant seem to make color and legend items by referenced marks field.
"data": [
{
"name": "table",
"values": [
{"District": "Neustadt", "total": "86", "id": 12, "subtotal": "600", "Year": "2017"},
{"District": "Neustadt", "total": "398", "id": 13, "subtotal": "100", "Year": "2018"},
{"District": "Neustadt", "total": "155", "id": 14, "subtotal": "10", "Year": "2019"}
],
"transform": [
{
"type": "collect",
"sort": {
"field": "Year"
}
}
]
}
],
"scales": [
{
"name": "Year",
"type": "point",
"range": "width",
"domain": {"data": "table", "field": "Year", "sort": true}
},
{
"name": "subtotal",
"type": "linear",
"range": "height",
"nice": true,
"zero": true,
"domain": {"data": "table", "field": "subtotal"}
},
{
"name": "color",
"type": "ordinal",
"range": "category",
"domain": {"data": "table", "field": "District"}
}
]
I can only get one legend item or of course multiple items if I would have another district in the data. Is grouping marks an option?

Related

How can I get my Vega marks to update when a signal changes?

I am using Vega to plot a bar chart. I also have a drop down list 'signal' that is used to specify the sort order of the data:
...
"signals": [
{
"name": "sortby",
"value": "name",
"bind": {"input": "select", "options": ["name", "net_worth"]}
}
],
"data": [
{
"name": "people",
"values": [
{"name": "Grimes", "net_worth": 1.2},
{"name": "Johnny", "net_worth": 6},
{"name": "Elon", "net_worth": 100},
{"name": "Amber", "net_worth": 0.1}
],
"transform": [
{
"type": "collect",
"sort": {"field": {"signal": "sortby"}, "order": "ascending"}
}
]
}
],
...
The sort transform is working correctly, when the chart is initially rendered, the data is sorted according to the default sort value of 'name'.
When I change the selected sortby value I see that the 'people' data is correctly sorted (I can see this by inspecting the Data Viewer tab in the Vega editor), however the marks do not update.
I've made sure that the encode.update definition for the marks holds all the positional data. I'm a bit stumped as to why the chart doesn't update, it feels like it's probably just due to a simple oversight by me and I'm hoping that someone here can spot what the issue is!
The full code is pasted below and is also available in the editor: Open the Chart in the Vega Editor
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 400,
"padding": 5,
"signals": [
{
"name": "sortby",
"value": "name",
"bind": {"input": "select", "options": ["name", "net_worth"]}
}
],
"data": [
{
"name": "people",
"values": [
{"name": "Grimes", "net_worth": 1.2},
{"name": "Johnny", "net_worth": 6},
{"name": "Elon", "net_worth": 100},
{"name": "Amber", "net_worth": 0.1}
],
"transform": [
{
"type": "collect",
"sort": {"field": {"signal": "sortby"}, "order": "ascending"}
}
]
}
],
"scales": [
{
"name": "yscale",
"type": "band",
"domain": {"data": "people", "field": "name"},
"range": "height",
"padding": 0.1
},
{
"name": "xscale",
"domain": {"data": "people", "field": "net_worth"},
"nice": true,
"range": "width"
}
],
"axes": [
{"orient": "top", "scale": "xscale"},
{"orient": "bottom", "scale": "xscale"},
{"orient": "left", "scale": "yscale"}
],
"marks": [
{
"type": "rect",
"from": {"data": "people"},
"encode": {
"enter": {"fill": {"value": "steelblue"}},
"update": {
"height": {"scale": "yscale", "band": 1},
"width": {"scale": "xscale", "field": "net_worth"},
"x": {"scale": "xscale", "value": 0},
"y": {"scale": "yscale", "field": "name"}
},
"exit": {"width": {"scale": "xscale", "value": 0}}
}
}
]
}
This is currently an open bug as described here: https://github.com/vega/vega/issues/1417. You can register your interest on that page to see when it will be fixed. In the meantime, I have used the workaround described to get the functionality you're after.
Editor.
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 400,
"padding": 5,
"signals": [
{
"name": "sortby",
"value": "name",
"bind": {"input": "select", "options": ["name", "net_worth"]}
}
],
"data": [
{
"name": "people",
"values": [
{"name": "Grimes", "net_worth": 1.2},
{"name": "Johnny", "net_worth": 6},
{"name": "Elon", "net_worth": 100},
{"name": "Amber", "net_worth": 0.1}
],
"transform": [
{
"type": "window",
"sort": {"field": {"signal": "sortby"}},
"ops": ["row_number"],
"fields": [null],
"as": ["rank"]
}
]
}
],
"scales": [
{
"name": "yscale",
"type": "band",
"domain": {
"data": "people",
"field": "name",
"sort": {"field": "rank", "op": "min"}
},
"range": "height",
"padding": 0.1
},
{
"name": "xscale",
"domain": {"data": "people", "field": "net_worth"},
"nice": true,
"range": "width"
}
],
"axes": [
{"orient": "top", "scale": "xscale"},
{"orient": "bottom", "scale": "xscale"},
{"orient": "left", "scale": "yscale"}
],
"marks": [
{
"type": "rect",
"from": {"data": "people"},
"encode": {
"enter": {"fill": {"value": "steelblue"}},
"update": {
"height": {"scale": "yscale", "band": 1},
"width": {"scale": "xscale", "field": "net_worth"},
"x": {"scale": "xscale", "value": 0},
"y": {"scale": "yscale", "field": "name"}
},
"exit": {"width": {"scale": "xscale", "value": 0}}
}
}
]
}

Vega How to SUM all descendants value of every node on a treemap

I am trying to access the sum of values for a node in VEGA. In other words, I want to display sum of "percentage" values of all leaves for each parent node.
Got the following Vega specs (https://gist.github.com/omerakko/655674f9f37e9361fe5378b6d440e411)
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "An example of treemap layout for hierarchical data.",
"width": 960,
"height": 500,
"padding": 2.5,
"autosize": "none",
"data": [
{
"name": "tree",
"url": "https://raw.githubusercontent.com/omerakko/VEGA/main/vegaTreemapData.json",
"transform": [
{
"type": "stratify",
"key": "id",
"parentKey": "parent"
},
{
"type": "treemap",
"field": "percentage",
"sort": {"field": "value", "order":"descending"},
"round": true,
"method": "resquarify",
"ratio": 1,
"size": [{"signal": "width"}, {"signal": "height"}],
"paddingOuter": 2,
"paddingInner":2
}
]
},
{
"name": "nodes",
"source": "tree",
"transform": [{ "type": "filter", "expr": "datum.children" }]
},
{
"name": "leaves",
"source": "tree",
"transform": [{ "type": "filter", "expr": "!datum.children" },
{"type": "filter", "expr": "datum.percentage > 0"}]
}
],
"scales": [
{
"name": "color",
"type": "ordinal",
"domain": {"data": "nodes", "field": "name"},
"range": [
"transparent", "#dd96ba", "#dea84e", "#c83836", "#dfde9b",
"#5eafb9", "#adc35d"]
},
{
"name": "size",
"type": "ordinal",
"domain": [0, 1, 2, 3],
"range": [256, 28, 20, 14]
},
{
"name": "opacity",
"type": "ordinal",
"domain": [0, 1, 2, 3],
"range": [0.15, 0.5, 0.8, 1.0]
}
],
"marks": [
{
"type": "rect",
"from": {"data": "nodes"},
"interactive": false,
"encode": {
"enter": {
"fill": {"value":"#333238"},
"stroke": {"scale": "color", "field": "name"},
"strokeWidth":{"value": 5}
},
"update": {
"x": {"field": "x0"},
"y": {"field": "y0"},
"x2": {"field": "x1"},
"y2": {"field": "y1"},
"stroke": {"scale": "color", "field": "name"}
}
}
},
{
"type": "rect",
"from": {"data": "leaves"},
"encode": {
"enter": {
"stroke": {"value": "#fff"}
},
"update": {
"x": {"field": "x0"},
"y": {"field": "y0"},
"x2": {"field": "x1"},
"y2": {"field": "y1"},
"fill": {"value": "transparent"}
},
"hover": {
"fill": {"value": "red"}
}
}
},
{
"type": "text",
"from": {"data": "nodes"},
"interactive": false,
"encode": {
"enter": {
"font": {"value": "Helvetica Neue, Arial"},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"fill": {"scale": "color", "field": "name"},
"text": {"field": "name"},
"fontSize": {"scale": "size", "field": "depth"}
},
"update": {
"x": {"signal": "0.5 * (datum.x0 + datum.x1)"},
"y": {"signal": "0.5 * (datum.y0 + datum.y1)"}
}
}
}
]
}
There is doc available https://vega.github.io/vega/docs/transforms/treemap/ here saying that I can access to what I want, but I couldnt manage to apply it to the specs.
It seems like it's not currently possible in Vega. There's this PR that hasn't been merged yet.
But you can work around that by manually aggregating the values and then looking them up. Here a gist in which there's now a total field for each node, the relevant part being:
{
"name": "leaves",
"source": "tree",
"transform": [
{"type": "filter", "expr": "!datum.children"},
{"type": "filter", "expr": "datum.percentage > 0.3"}
]
},
{
"name": "totals",
"source": "leaves",
"transform": [
{
"type": "aggregate",
"groupby": ["parent"],
"fields": ["percentage"],
"as": ["total"],
"ops": ["sum"]
}
]
},
{
"name": "nodes",
"source": "tree",
"transform": [
{"type": "filter", "expr": "datum.children"},
{
"type": "lookup",
"from": "totals",
"key": "parent",
"fields": ["id"],
"values": ["total"],
"as": ["total"]
}
]
},
Basically you just get sums of all the leaves by parent in totals and then, after constructing the base nodes dataset, look the total up in totals.
Note that this will only work for this particular example, where there are exactly two levels in the hierarchy.

Clamping y-axis when layering aggregated charts in vega-lite

This is a follow up from a previous question for which I built a test case in a (hopefully now public) notebook and noticed the following behavior:
At the end of the notebook, in the section bugs you will notice that y-axis of the max_precipitation of the layered chart using is clamped to 10.
I tried changing the domain but the bars do not go above 10.
Here the code example in vega-lite's editor reproduced below:
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"title": "Top Months by Mean Precipitation",
"data": {"url": "data/seattle-weather.csv"},
"transform": [
{"timeUnit": "month", "field": "date", "as": "month_date"},
{
"aggregate": [
{"op": "mean", "field": "precipitation", "as": "mean_precipitation"},
{"op": "max", "field": "precipitation", "as": "max_precipitation"}
],
"groupby": ["month_date"]
},
{
"window": [{"op": "row_number", "as": "rank"}],
"sort": [{"field": "mean_precipitation", "order": "descending"}]
}
],
"encoding": {
"x": {
"field": "month_date",
"type": "ordinal",
"timeUnit": "month",
"title": "month (descending by max precip)",
"sort": {
"field": "max_precipitation",
"op": "average",
"order": "descending"
}
}
},
"layer": [
{
"mark": {"type": "bar"},
"encoding": {
"y": {
"field": "max_precipitation",
"type": "quantitative",
"title": "precipitation (mean & max)"
}
}
},
{
"mark": "tick",
"encoding": {
"y": {"field": "mean_precipitation", "type": "quantitative"},
"color": {"value": "red"},
"size": {"value": 15}
}
}
]
}
Please help me understand what I am doing wrong?
It appears that the precipitation column is being parsed as strings rather than as numbers. You can specify the parsing format for the column using :
"data": {
"url": "data/seattle-weather.csv",
"format": {"parse": {"precipitation": "number"}}
},
The result is here:

Render different fill/stroke colours depending on positive/negative x-axis value

I'm trying to code a little chart for use in risk/opportunity analysis. You feed in 4 values:
PreConsequence & PostConsequence (int's ranging -4 to +4)
PreLikelihood & PostLikelihood (int's ranging 0 to 4)
And it visualises those with charts as follows:
The shaded fill renders the Pre values, whilst the stroke renders the Post values.
Geometry in the left quadrant represents risk and should be red, whilst geometry in the right quadrant represents opportunity and should be green.
I'm struggling to find out how to check for negative consequence values and assign colour accordingly. I think it'll be done in the last two lines of my code below:
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 500,
"height": 250,
"data": [
{
"name": "table",
"values": [
{"C": -4, "L": 0, "f":"Pre"}, {"C": 0, "L": 4, "f":"Pre"},
{"C": 0, "L": 2, "f":"Post"}, {"C": -1, "L": 0, "f":"Post"}
]
}
],
"scales": [
{
"name": "xscale",
"type": "linear",
"range": "width",
"nice": true,
"zero": true,
"domain": {"data": "table", "field": "C"},
"domainMax": 4,
"domainMin": -4
},
{
"name": "yscale",
"type": "linear",
"range": "height",
"domain": {"data": "table", "field": "L"},
"domainMax": 4,
"domainMin": 0
},
{
"name": "color",
"type": "ordinal",
"range":"ordinal",
"domain": {"data": "table", "field": "f"}
}
],
"axes": [
{"orient": "bottom", "scale": "xscale", "tickCount": 10 },
{"orient": "left", "scale": "yscale", "tickCount": 5, "offset": -250 }
],
"marks": [
{
"type": "group",
"from": {
"facet": {
"name": "series",
"data": "table",
"groupby": "f"
}
},
"marks": [
{
"type": "area",
"from": {"data": "series"},
"encode": {
"enter": {
"x": {"scale": "xscale", "field": "C" },
"y": {"scale": "yscale", "field": "L"},
"y2": {"scale": "yscale", "value": 0 },
"fillOpacity": [{ "test": "indata('series', 'f', 'Pre')", "value": 0.3 }, {"value": 0}],
"strokeWidth": [{ "test": "indata('series', 'f', 'Pre')", "value": 0 }, {"value": 2}],
"fill": [{ "test": "indata('series', 'f', 'Pre')", "value": "red" }, {"value": "red"}],
"stroke": [{ "test": "indata('series', 'f', 'Pre')", "value": "red" }, {"value": "red"}]
}
}
}
]
}
]
}
Can anyone give me some pointers as to how to test the data values & set fill & stroke colours accordingly?
Thanks
Vega signals can be used to conditionally paint colors based on data values.
In the given vega spec, changing the fill property like this should do what you are expecting if data has Positive quadrant C values also.
"fill": { "signal": "datum.C > 0 ? 'green': 'red'"},

vega-lite line plot - color not getting applied in transform filter

Vega Editor link here
I've an overlay color change based on filter condition in a multi line chart. Got it working with single line here but 'red' overlay line(along with red dot) doesn't come up with this above multi-line example. Could anyone help me out?
Short answer: your chart is working, except the filtered values are not colored red.
The core issue is that encodings always supersede mark properties, as you can see in this simpler example: editor link
{
"$schema": "https://vega.github.io/schema/vega-lite/v3.json",
"description": "A scatterplot showing horsepower and miles per gallons.",
"data": {"url": "data/cars.json"},
"mark": {"type": "point", "color": "red"},
"encoding": {
"x": {"field": "Horsepower", "type": "quantitative"},
"y": {"field": "Miles_per_Gallon", "type": "quantitative"},
"color": {"field": "Origin", "type": "nominal"},
"shape": {"field": "Origin", "type": "nominal"}
}
}
Notice that although we specify that the mark should have color red, this is overridden by the color encoding. This is by design within Vega-Lite, because encodings are more specific than properties.
Back to your chart: because you specify the color encoding in the parent chart, each individual layer inherits that color encoding, and those colors override the "color": "red" that you specify in the individual layers.
To make it do what you want, you can move the color encoding into the individual layers (and use a detail encoding to ensure the data are still grouped by that field). For example (editor link):
{
"$schema": "https://vega.github.io/schema/vega-lite/v3.json",
"data": {...},
"width": 1000,
"height": 200,
"autosize": {"type": "pad", "resize": true},
"transform": [
{
"window": [{"op": "rank", "as": "rank"}],
"sort": [{"field": "dateTime", "order": "descending"}]
},
{"filter": "datum.rank <= 100"}
],
"layer": [
{
"mark": {"type": "line"},
"encoding": {
"color": {
"field": "name",
"type": "nominal",
"legend": {"title": "Type"}
}
}
},
{
"mark": {"type": "line", "color": "red"},
"transform": [
{
"as": "count",
"calculate": "if(datum.anomaly == true, datum.count, null)"
},
{"calculate": "true", "as": "baseline"}
]
},
{
"mark": {"type": "circle", "color": "red"},
"transform": [
{"filter": "datum.anomaly == true"},
{"calculate": "true", "as": "baseline"}
]
}
],
"encoding": {
"x": {
"field": "dateTime",
"type": "temporal",
"timeUnit": "hoursminutesseconds",
"sort": {"field": "dateTime", "op": "count", "order": "descending"},
"axis": {"title": "Time", "grid": false}
},
"y": {
"field": "count",
"type": "quantitative",
"axis": {"title": "Count", "grid": false}
},
"detail": {"field": "name", "type": "nominal"}
}
}