-
Notifications
You must be signed in to change notification settings - Fork 630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add exponential moving average to vega-lite #9225
base: main
Are you sure you want to change the base?
Changes from all commits
83ab83e
1db302e
cf2ed1c
3b60571
8f56013
261728b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
{ | ||
"$schema": "https://vega.github.io/schema/vega/v5.json", | ||
"background": "white", | ||
"padding": 5, | ||
"width": 400, | ||
"height": 200, | ||
"style": "cell", | ||
"data": [ | ||
{ | ||
"name": "source_0", | ||
"values": [ | ||
{"price": 9.2, "year": 2020}, | ||
{"price": 10.76, "year": 2020}, | ||
{"price": 36.88, "year": 2021}, | ||
{"price": 3.44, "year": 2021}, | ||
{"price": 10.55, "year": 2022}, | ||
{"price": 9.65, "year": 2022}, | ||
{"price": 7.15, "year": 2023}, | ||
{"price": 15, "year": 2023}, | ||
{"price": 10.19, "year": 2024}, | ||
{"price": 8.86, "year": 2024} | ||
] | ||
}, | ||
{ | ||
"name": "data_0", | ||
"source": "source_0", | ||
"transform": [ | ||
{ | ||
"type": "aggregate", | ||
"groupby": ["year"], | ||
"ops": ["exponential", "mean"], | ||
"fields": ["price", "price"], | ||
"as": ["exponential_price", "mean_price"], | ||
"aggregate_params": [0.5, null] | ||
} | ||
] | ||
} | ||
], | ||
"marks": [ | ||
{ | ||
"name": "layer_0_marks", | ||
"type": "line", | ||
"style": ["line"], | ||
"sort": {"field": "datum[\"year\"]"}, | ||
"from": {"data": "data_0"}, | ||
"encode": { | ||
"update": { | ||
"stroke": {"value": "#4c78a8"}, | ||
"description": { | ||
"signal": "\"year: \" + (isValid(datum[\"year\"]) ? datum[\"year\"] : \"\"+datum[\"year\"]) + \"; Avg Price: \" + (format(datum[\"mean_price\"], \"\"))" | ||
}, | ||
"x": {"scale": "x", "field": "year"}, | ||
"y": {"scale": "y", "field": "mean_price"}, | ||
"defined": { | ||
"signal": "isValid(datum[\"mean_price\"]) && isFinite(+datum[\"mean_price\"])" | ||
} | ||
} | ||
} | ||
}, | ||
{ | ||
"name": "layer_1_marks", | ||
"type": "line", | ||
"style": ["line"], | ||
"sort": {"field": "datum[\"year\"]"}, | ||
"from": {"data": "data_0"}, | ||
"encode": { | ||
"update": { | ||
"opacity": {"value": 0.5}, | ||
"stroke": {"value": "#4c78a8"}, | ||
"description": { | ||
"signal": "\"year: \" + (isValid(datum[\"year\"]) ? datum[\"year\"] : \"\"+datum[\"year\"]) + \"; Exponential of price: \" + (format(datum[\"exponential_price\"], \"\"))" | ||
}, | ||
"x": {"scale": "x", "field": "year"}, | ||
"y": {"scale": "y", "field": "exponential_price"}, | ||
"defined": { | ||
"signal": "isValid(datum[\"exponential_price\"]) && isFinite(+datum[\"exponential_price\"])" | ||
} | ||
} | ||
} | ||
} | ||
], | ||
"scales": [ | ||
{ | ||
"name": "x", | ||
"type": "point", | ||
"domain": {"data": "data_0", "field": "year", "sort": true}, | ||
"range": [0, {"signal": "width"}], | ||
"padding": 0.5 | ||
}, | ||
{ | ||
"name": "y", | ||
"type": "linear", | ||
"domain": { | ||
"data": "data_0", | ||
"fields": ["mean_price", "exponential_price"] | ||
}, | ||
"range": [{"signal": "height"}, 0], | ||
"nice": true, | ||
"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": "year", | ||
"labelAlign": "right", | ||
"labelAngle": 270, | ||
"labelBaseline": "middle", | ||
"zindex": 0 | ||
}, | ||
{ | ||
"scale": "y", | ||
"orient": "left", | ||
"grid": false, | ||
"title": "Avg Price", | ||
"labelOverlap": true, | ||
"tickCount": {"signal": "ceil(height/40)"}, | ||
"zindex": 0 | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"$schema": "https://vega.github.io/schema/vega-lite/v5.json", | ||
"width": 400, | ||
"data": { | ||
"values": [ | ||
{"price": 9.2, "year": 2020}, | ||
{"price": 10.76, "year": 2020}, | ||
{"price": 36.88, "year": 2021}, | ||
{"price": 3.44, "year": 2021}, | ||
{"price": 10.55, "year": 2022}, | ||
{"price": 9.65, "year": 2022}, | ||
{"price": 7.15, "year": 2023}, | ||
{"price": 15.0, "year": 2023}, | ||
{"price": 10.19, "year": 2024}, | ||
{"price": 8.86, "year": 2024} | ||
] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally, I was planning on using some advanced data like Since the created spec in ![]() ![]() |
||
}, | ||
"layer": [ | ||
{ | ||
"mark": "line", | ||
"encoding": { | ||
"x": { | ||
"field": "year" | ||
}, | ||
"y": { | ||
"field": "price", | ||
"aggregate": "mean", | ||
"type": "quantitative", | ||
"title": "Avg Price" | ||
} | ||
} | ||
}, | ||
{ | ||
"mark": { | ||
"type": "line", | ||
"opacity": 0.5 | ||
}, | ||
"encoding": { | ||
"x": { | ||
"field": "year" | ||
}, | ||
"y": { | ||
"field": "price", | ||
"aggregate": { | ||
"exponential": 0.5 | ||
}, | ||
"type": "quantitative" | ||
} | ||
} | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import {Gradient, ScaleType, SignalRef, Text} from 'vega'; | ||
import {isArray, isBoolean, isNumber, isString} from 'vega-util'; | ||
import {Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, isCountingAggregateOp} from './aggregate'; | ||
import {Aggregate, isAggregateOp, isArgmaxDef, isArgminDef, isCountingAggregateOp, isExponentialDef} from './aggregate'; | ||
import {Axis} from './axis'; | ||
import {autoMaxBins, Bin, BinParams, binToString, isBinned, isBinning} from './bin'; | ||
import { | ||
|
@@ -805,7 +805,7 @@ export function vgField( | |
|
||
if (!opt.nofn) { | ||
if (isOpFieldDef(fieldDef)) { | ||
fn = fieldDef.op; | ||
fn = isExponentialDef(fieldDef.op) ? 'exponential' : fieldDef.op; | ||
} else { | ||
const {bin, aggregate, timeUnit} = fieldDef; | ||
if (isBinning(bin)) { | ||
|
@@ -819,7 +819,7 @@ export function vgField( | |
argAccessor = `["${field}"]`; | ||
field = `argmin_${aggregate.argmin}`; | ||
} else { | ||
fn = String(aggregate); | ||
fn = isExponentialDef(aggregate) ? 'exponential' : String(aggregate); | ||
} | ||
} else if (timeUnit && !isBinnedTimeUnit(timeUnit)) { | ||
fn = timeUnitToString(timeUnit); | ||
|
@@ -893,7 +893,8 @@ export function verbalTitleFormatter(fieldDef: FieldDefBase<string>, config: Con | |
} else if (isArgminDef(aggregate)) { | ||
return `${field} for min ${aggregate.argmin}`; | ||
} else { | ||
return `${titleCase(aggregate)} of ${field}`; | ||
const aggregateOp = isExponentialDef(aggregate) ? 'exponential' : aggregate; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ended up adding a mixture of ternary operators and if statements into the code to handle |
||
return `${titleCase(aggregateOp)} of ${field}`; | ||
} | ||
} | ||
return field; | ||
|
@@ -909,7 +910,9 @@ export function functionalTitleFormatter(fieldDef: FieldDefBase<string>) { | |
|
||
const timeUnitParams = timeUnit && !isBinnedTimeUnit(timeUnit) ? normalizeTimeUnit(timeUnit) : undefined; | ||
|
||
const fn = aggregate || timeUnitParams?.unit || (timeUnitParams?.maxbins && 'timeunit') || (isBinning(bin) && 'bin'); | ||
const aggregateOp = isExponentialDef(aggregate) ? 'exponential' : aggregate; | ||
const fn = | ||
aggregateOp || timeUnitParams?.unit || (timeUnitParams?.maxbins && 'timeunit') || (isBinning(bin) && 'bin'); | ||
if (fn) { | ||
return `${fn.toUpperCase()}(${field})`; | ||
} else { | ||
|
@@ -1136,7 +1139,14 @@ export function initFieldDef( | |
const fieldDef = {...fd}; | ||
|
||
// Drop invalid aggregate | ||
if (!compositeMark && aggregate && !isAggregateOp(aggregate) && !isArgmaxDef(aggregate) && !isArgminDef(aggregate)) { | ||
if ( | ||
!compositeMark && | ||
aggregate && | ||
!isAggregateOp(aggregate) && | ||
!isArgmaxDef(aggregate) && | ||
!isArgminDef(aggregate) && | ||
!isExponentialDef(aggregate) | ||
) { | ||
log.warn(log.message.invalidAggregate(aggregate)); | ||
delete fieldDef.aggregate; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test the docs as well? I see we use
AggregateOp
in https://github.com/julieg18/vega-lite/blob/261728bbeadb674041266dc3276ffd46a4c7510d/site/_data/link.yml#L61 so we need to make sure the links are still correct.