Vega-Lite: How to use a color field and have a legend? - colors

I would like to create a Vega-Lite / Deneb faceted bar chart with data where there is a need to a) have a specific color for each bar ie Brand and b) have the Brands in a specific order. Color and sort order are given as columns in the data: bColor & bSort. Is there a way to use the color field AND have a legend?
If I hard-code the colors as a range:
"scale": {"range": ["red", "orange", "green"]}
then I can see also the legend. So, is there a way to create a list of (unique) colors in the correct order to be used as the range? I have tried for example
"scale": {"range": {"op": "min", "field": "bColor"}},
but it gives me an error: Undefined data set name: "data_1"
Edit: This is how the end results should look like (this is done with hard-coded color range):
chart with legend

Changing resolve from independent to shared breaks the chart.
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"description": "dedicated colors and sort order",
"data": {
"values": [
{
"Brand": "Brand A (orange)",
"Statement": "S1",
"Score": 0.6,
"bSort": 2,
"bColor": "orange"
},
{
"Brand": "Brand B (green)",
"Statement": "S1",
"Score": 0.5,
"bSort": 3,
"bColor": "green"
},
{
"Brand": "Brand C (red)",
"Statement": "S1",
"Score": 0.7,
"bSort": 1,
"bColor": "red"
},
{
"Brand": "Brand A (orange)",
"Statement": "S2",
"Score": 0.6,
"bSort": 2,
"bColor": "orange"
},
{
"Brand": "Brand B (green)",
"Statement": "S2",
"Score": 0.5,
"bSort": 3,
"bColor": "green"
},
{
"Brand": "Brand C (red)",
"Statement": "S2",
"Score": 0.7,
"bSort": 1,
"bColor": "red"
},
{
"Brand": "Brand A (orange)",
"Statement": "S3",
"Score": 0.6,
"bSort": 2,
"bColor": "orange"
},
{
"Brand": "Brand B (green)",
"Statement": "S3",
"Score": 0.5,
"bSort": 3,
"bColor": "green"
},
{
"Brand": "Brand C (red)",
"Statement": "S3",
"Score": 0.7,
"bSort": 1,
"bColor": "red"
},
{
"Brand": "Brand A (orange)",
"Statement": "S4",
"Score": 0.6,
"bSort": 2,
"bColor": "orange"
},
{
"Brand": "Brand B (green)",
"Statement": "S4",
"Score": 0.5,
"bSort": 3,
"bColor": "green"
},
{
"Brand": "Brand C (red)",
"Statement": "S4",
"Score": 0.7,
"bSort": 1,
"bColor": "red"
}
]
},
"resolve": { "scale": {"color": "independent"}},
"facet": {"field": "Statement"},
"columns": 2,
"spec": {
"encoding": {
"x": {"field": "Score", "type": "quantitative"},
"y": {"field": "Brand"},
"color": {"field": "bColor", "scale": {"range": {"field": "bColor"}}}
},
"mark": {"type": "bar", "tooltip": true}
}
}

Related

How to vertically align Vega-Lite stacked bar chart data labels

How can I align data labels in a stacked bar chart to be centered vertically within each bar segment? In the following example, you can see that the text is positioned at the top of each bar segment. Where a bar segment is thin, the data label overlaps the one below. I realize that having multiple adjacent thin segments would result in overlap even if labels were centered vertically, but that case is unlikely with my data set.
{
"$schema": "https://vega.github.io/schema/vega-lite/v2.json",
"data": {
"values": [
{"Value": 0.321, "Date": "09/30/2021", "Measure": "Measure 4"},
{"Value": 0.031, "Date": "09/30/2021", "Measure": "Measure 3"},
{"Value": 0.123, "Date": "09/30/2021", "Measure": "Measure 2"},
{"Value": 0.475, "Date": "09/30/2021", "Measure": "Measure 1"}
]
},
"width": 500,
"height": 250,
"resolve": {"scale": {"color": "independent"}},
"layer": [
{
"mark": "bar",
"encoding": {
"y": {
"field": "Value",
"type": "quantitative",
"axis": {"format": ".1%"}
},
"x": {"field": "Date", "type": "nominal", "axis": {"labelAngle": -45}},
"color": {"field": "Measure", "type": "nominal"}
}
},
{
"mark": {"type": "text"},
"encoding": {
"y": {"field": "Value", "type": "quantitative", "stack": "zero"},
"x": {"field": "Date", "type": "nominal"},
"color": {
"field": "Measure",
"type": "nominal",
"scale": {"range": ["white"]},
"legend": null
},
"text": {
"aggregate": "sum",
"field": "Value",
"type": "quantitative",
"format": ".1%"
}
}
}
]
}
You can do this using a stack and calculate transform. The text layer would look like this:
{
...
"transform": [
{"stack": "Value", "groupby": ["Date"], "as": ["lower", "upper"]},
{"calculate": "(datum.lower + datum.upper) / 2", "as": "midpoint"}
],
"encoding": {
"y": {"field": "midpoint", "type": "quantitative"},
...
}
}
You can see the full spec in the vega editor:

How do I apply conditional formatting in .map() in nodejs?

I have the below data
[
{
"ID": "1",
"SIZE": 2.21,
"Metal": "Steel",
"Class": "Non magnetic",
"Density":3.9,
},
{
"ID": "2",
"SIZE": 1.25,
"Metal": "Iron",
"Class": "magnetic",
"Density":4.2,
},
{
"ID": "3",
"SIZE": 15.5,
"Metal": "Water",
"Class": "non magnetic",
"Density": 1.3,
},
{
"ID": "4",
"SIZE": 9.5,
"Metal": "Steel",
"Class": "non magnetic",
"Density": 1.2,
}
{
"ID": "5",
"SIZE": 3.2,
"Metal": "Water",
"Class": "non magnetic",
"Density": 1.0,
}
]
Goal is to create a new map and filter the data. If name is steel or iron, we need to get the size from the previous object . Likewise if metal is water, then we need to the skip the currect object and check in the next value in the next object. If the subsequent values are water too, then we need to return "Invalid". I tried the below piece of code but it is not returning the desired output
data.map((temp, i) =>{
if (temp.METAL.toUpperCase() != 'WATER')
{
res.push({
ID: temp.ID,
SIZE: temp.SIZE,
Priortosize: data[i + 1] ? data[i + 1].SIZE: "Invalid",
Metal: temp.METAL
Class: temp.CLASS,
Density: temp.DENSITY
})
}
})
It returns empty value if the last metal is water. Can you please help?
First, Javascript is a case-sensitive language.
data.Metal != data.METAL
Use data.Metal, just as it is in the data object.
I also saw that you forgot some commas in your example, both in the data object and in the push of your code.
data = [
{
"ID": "1",
"SIZE": 2.21,
"Metal": "Steel",
"Class": "Non magnetic",
"Density":3.9,
},
{
"ID": "2",
"SIZE": 1.25,
"Metal": "Iron",
"Class": "magnetic",
"Density":4.2,
},
{
"ID": "3",
"SIZE": 15.5,
"Metal": "Water",
"Class": "non magnetic",
"Density": 1.3,
},
{
"ID": "4",
"SIZE": 9.5,
"Metal": "Steel",
"Class": "non magnetic",
"Density": 1.2,
},
{
"ID": "5",
"SIZE": 3.2,
"Metal": "Water",
"Class": "non magnetic",
"Density": 1.0,
}
]
res = []
data.map((temp, i) =>{
if (temp.Metal.toUpperCase() != 'WATER')
{
res.push({
ID: temp.ID,
SIZE: temp.SIZE,
Priortosize: data[i + 1] ? data[i + 1].SIZE: "Invalid",
Metal: temp.Metal,
Class: temp.Class,
Density: temp.Density
})
}
})
console.log(JSON.stringify(res, null, 0))

timeUnit does not work after a flatten and flod transformation

Is it possible to use timeUnit after a flatten and flod transformation?
In the example below it doesnt work!
If I remove the timeUnit from the x axis it plots, but without the good things that come with the timeUnit.
Thanks
This is an example code that can be executed in the link below
https://vega.github.io/editor/#/edited
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "Sales in a Year.",
"width": 500,
"height": 200,
"data": {
"values": [
{"timestamp": ["2019-01-01","2019-02-01","2019-03-01","2019-04-01","2019-05-01","2019-06-01",
"2019-07-01","2019-08-01","2019-09-01","2019-10-01","2019-11-01","2019-12-01"],
"cars" : [55, 43, 91, 81, 53, 19, 87, 52, 52, 44, 52, 52],
"bikes" : [12, 6, 2, 0, 0, 0, 0, 0, 0, 3, 9, 15]}
]
},
"transform": [
{"flatten": ["timestamp", "cars", "bikes"]},
{"fold": ["cars", "bikes"]}
],
"mark": {"type":"bar", "tooltip": true, "cornerRadiusEnd": 4},
"encoding": {
"x": {"field": "timestamp",
"timeUnit": "month",
"type": "ordinal",
"title": "",
"axis": {"labelAngle": 0}},
"y": {"field": "value",
"type": "quantitative",
"title": "Soiling Loss"},
"color":{"field": "key",
"type": "nominal"}
}
}
For convenience, strings in input data with a simple temporal encoding are automatically parsed as dates, but such parsing is not applied to data that is the result of a transformation.
In this case, you can do the parsing manually with a calculate transform (view in editor):
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "Sales in a Year.",
"width": 500,
"height": 200,
"data": {
"values": [
{
"timestamp": [
"2019-01-01",
"2019-02-01",
"2019-03-01",
"2019-04-01",
"2019-05-01",
"2019-06-01",
"2019-07-01",
"2019-08-01",
"2019-09-01",
"2019-10-01",
"2019-11-01",
"2019-12-01"
],
"cars": [55, 43, 91, 81, 53, 19, 87, 52, 52, 44, 52, 52],
"bikes": [12, 6, 2, 0, 0, 0, 0, 0, 0, 3, 9, 15]
}
]
},
"transform": [
{"flatten": ["timestamp", "cars", "bikes"]},
{"fold": ["cars", "bikes"]},
{"calculate": "toDate(datum.timestamp)", "as": "timestamp"}
],
"mark": {"type": "bar", "tooltip": true, "cornerRadiusEnd": 4},
"encoding": {
"x": {
"field": "timestamp",
"timeUnit": "month",
"type": "ordinal",
"title": "",
"axis": {"labelAngle": 0}
},
"y": {"field": "value", "type": "quantitative", "title": "Soiling Loss"},
"color": {"field": "key", "type": "nominal"}
}
}

How to make a chart with SVG marker whose size increases in only one aspect or show a custom SVG marker for each data point in Vega/Vega-Lite?

I want to make a chart that has "spikes" as markers. And to denote the "severity" or the "quantitative" nature of the data, I want to scale the "spikes" longer but NOT wider. Currently, when I use the size encoding it increases the area of the "spike", which is undesirable. I used "aspect": false too but the results did not change -
Vega-Lite Spec
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "A simple bar chart with embedded data.",
"data": {
"values": [
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
]
},
"mark": {"type": "point", "shape":"M -1 0 L0 -10 L1 0", "fill": "red", "opacity": 0.5, "stroke": "black", "strokeOpacity": 1 },
"encoding": {
"x": {"field": "a", "type": "ordinal"},
"y": {"field": "b", "type": "quantitative"},
"size": {"field": "b", "type": "quantitative"}
}
}
Then I thought that maybe I can specify shape as an encoding and provide custom SVG that only changes in height, as PATH value to the data itself and pass that in the shape encoding. But of course that didn't work. Vega-Lite assigned its own shapes -
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "A simple bar chart with embedded data.",
"data": {
"values": [
{"a": "A", "b": 28, "c":"M -1 0 L0 -10 L1 0"}, {"a": "B", "b": 55, "c":"M -1 0 L0 -5 L1 0"}, {"a": "C", "b": 43, "c":"M -1 0 L0 -20 L1 0"},
{"a": "D", "b": 91, "c":"M -1 0 L0 -1 L1 0"}
]
},
"mark": {"type": "point", "fill": "red", "opacity": 0.5, "stroke": "black", "strokeOpacity": 1 },
"encoding": {
"x": {"field": "a", "type": "ordinal"},
"y": {"field": "b", "type": "quantitative"},
"shape": {"field": "c", "type": "quantitative"}
}
}
I also played around with url encoding in point mark as well as image mark, but they did not yield anything.
I saw Path Mark in Vega, which may be useful but I do not see it in Vega-Lite. If it can somehow be used then that is fine too.
Any idea how do I make this happen?
Main idea is to have the width of the marker same, but scale the height. I don't mind doing it via encoding channels or arguments/parameters or specifying an SVG PATH for each data point, either way is fine.
EDIT 1
After fiddling with vega, I got around to the following -
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A simple bar chart with embedded data.",
"background": "white",
"padding": 5,
"height": 700,
"style": "cell",
"data": [
{
"name": "source_0",
"values": [
{"a": "A", "b": 1.5, "c": 0},
{"a": "B", "b": 0.5, "c": 0},
{"a": "C", "b": 10, "c": 0},
{"a": "D", "b": 1, "c": 0}
]
},
{
"name": "data_0",
"source": "source_0",
"transform": [
{
"type": "filter",
"expr": "isValid(datum[\"b\"]) && isFinite(+datum[\"b\"])"
}
]
}
],
"signals": [
{"name": "x_step", "value": 20},
{
"name": "width",
"update": "bandspace(domain('x').length, 1, 0.5) * x_step"
}
],
"marks": [
{
"name": "marks",
"type": "symbol",
"style": ["path"],
"from": {"data": "data_0"},
"encode": {
"update": {
"opacity": {"value": 0.7},
"fill": {"value": "red"},
"stroke": {"value": "red"},
"strokeOpacity": {"value": 1},
"strokeWidth": {"value": 0.25},
"shape": {"value": "M -1 0 L0 -10 L1 0 Z"},
"ariaRoleDescription": {"value": "point"},
"description": {
"signal": "\"a\" + \": \" + (isValid(datum[\"a\"]) ? datum[\"a\"] : \"\"+datum[\"a\"]) + \"; \" + \"b\" + \": \" + (format(datum[\"b\"], \"\"))"
},
"x": {"scale": "x", "field": "a"},
"y": {"scale": "y", "field": "c"},
"scaleY": {"field": "b", "type": "quantitative"}
}
}
}
],
"scales": [
{
"name": "x",
"type": "point",
"domain": {"data": "data_0", "field": "a", "sort": true},
"range": {"step": {"signal": "x_step"}},
"padding": 0.5
},
{
"name": "y",
"type": "linear",
"domain": {"data": "data_0", "field": "b"},
"range": [{"signal": "height"}, 0],
"nice": true,
"zero": true
},
{
"name": "size",
"type": "linear",
"domain": {"data": "data_0", "field": "b"},
"range": [0, 361],
"zero": true
}
],
"axes": [
{
"scale": "y",
"orient": "left",
"gridScale": "x",
"grid": true,
"tickCount": {"signal": "ceil(height/40)"},
"domain": false,
"labels": false,
"aria": false,
"maxExtent": 0,
"minExtent": 0,
"ticks": false,
"zindex": 0
},
{
"scale": "x",
"orient": "bottom",
"grid": false,
"title": "a",
"labelAlign": "right",
"labelAngle": 270,
"labelBaseline": "middle",
"labelOverlap": true,
"zindex": 0
},
{
"scale": "y",
"orient": "left",
"grid": false,
"title": "b",
"labelOverlap": true,
"tickCount": {"signal": "ceil(height/40)"},
"zindex": 0
}
]
}
Which gives me -
I tried to convert this to Vega-Lite but it doesn't seem to work -
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "A simple bar chart with embedded data.",
"data": {
"values": [
{"a": "A", "b": 2}, {"a": "B", "b": 5}, {"a": "C", "b": 4},
{"a": "D", "b": 9}
]
},
"mark": {"type": "point", "shape":"M -1 0 L0 -10 L1 0", "fill": "red", "opacity": 0.5, "stroke": "black", "strokeOpacity": 1 },
"encoding": {
"x": {"field": "a", "type": "ordinal", "axis": {"labelAngle": 0}},
"y": {"field": "b", "type": "quantitative"},
"scaleY": {"field": "b", "type": "quantitative"}
}
}
Error
Property scaleY is not allowed.
Your second approach, of providing the SVG path in the shape encoding, will work if you set the scale to null (open in editor):
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "A simple bar chart with embedded data.",
"data": {
"values": [
{"a": "A", "b": 28, "c":"M -1 0 L0 -10 L1 0"},
{"a": "B", "b": 55, "c":"M -1 0 L0 -5 L1 0"},
{"a": "C", "b": 43, "c":"M -1 0 L0 -20 L1 0"},
{"a": "D", "b": 91, "c":"M -1 0 L0 -1 L1 0"}
]
},
"mark": {"type": "point", "fill": "red", "opacity": 0.5, "stroke": "black", "strokeOpacity": 1 },
"encoding": {
"x": {"field": "a", "type": "ordinal"},
"y": {"field": "b", "type": "quantitative"},
"shape": {"field": "c", "type": "quantitative", "scale": null}
}
}
Vega-Lite does not provide the equivalent of Vega's ScaleY encoding, so if you want that approach you will have to work in Vega directly.

Mongoose, update of child will affect all parents?

What is the query to update below data with mongoose. So 3 fields are going to be updated. Top Parent Points, Categories Points and Tag Points.
{
"_id": "561fba5e7fac41a4055fad45",
"fullName": "Test",
"points": 45,
"level": 1,
"categories": [
{
"name": "Computer Science",
"points": 15,
"level": 1,
"_id": "561fba5e7fac41a4055fad46",
"tags": [
{
"name": "C#",
"points": 10,
"level": 1,
"_id": "561fba5e7fac41a4055fad47"
},
{
"name": "Java",
"points": 5,
"level": 1,
"_id": "561fba5e7fac41a4055ert12"
}
]
},
{
"name": "History",
"points": 30,
"level": 2,
"_id": "562407d4e3edf2113f61ac37",
"tags": [
{
"name": "WW2",
"points": 30,
"level": 2,
"_id": "56240797e3edf2113f61ac36"
}
]
}
]
}
to this one. When user gets a point from a specific tag, it will effect all parents. Let's say, user gets 10 points from C# then i have to update mongodb to this.
{
"_id": "561fba5e7fac41a4055fad45",
"fullName": "Test",
**"points": 55,**
"level": 1,
"categories": [
{
"name": "Computer Science",
**"points": 25,**
"level": 1,
"_id": "561fba5e7fac41a4055fad46",
"tags": [
{
"name": "c#",
**"points": 20,**
"level": 1,
"_id": "561fba5e7fac41a4055fad47"
},
{
"name": "Java",
"points": 5,
"level": 1,
"_id": "561fba5e7fac41a4055ert12"
}
]
},
{
"name": "History",
"points": 30,
"level": 2,
"_id": "562407d4e3edf2113f61ac37",
"tags": [
{
"name": "WW2",
"points": 30,
"level": 2,
"_id": "56240797e3edf2113f61ac36"
}
]
}
]
}
you should use $elemMatch for querying your object
db.tests.update({_id: yourTestId, categories: {$elemMatch: {_id: categoryId}}}, {$set: {$inc: {"categories.$.points": 10, points: 10}}})
So you querying only needed array element and update it values with $ reference

Resources