I'm using Vega Lite in a crossfiltery app with an external data source.
I'd like the selection on a chart with a temporal scale to "snap" or "round" to a time interval which I'll supply in the spec.
I don't see anything about this in the selections documentation, so I guess I will probably need to generate a Vega spec from the Vega Lite spec, and then patch it. That's not something I've done yet, but I'm eager to learn.
However I am surprised not to find this question on SO, GitHub or Slack. I think I've searched for all combinations of {vega, vega-lite} x {round, snap}.
Closest I can find is an issue related to snapping selections for ordinal scales.
Am I using the wrong terminology?
It turns out the issue regarding snapping ordinal scales is more about snapping to time intervals, and this comment by Jeff Heer covers the topic thoroughly.
The resulting Vega expressions are lengthy, but the process is straightforward:
- Get the x-coordinate value.
- Run it through a scale inversion (
x.invert
) to map from pixel domain to data domain.- Perform "snapping" by rounding / truncating values in the data domain.
- Re-apply the x scale to get the snapped pixel coordinate.
We can change the update
fields of the signals
to implement this truncation.
Say the selection is named selection1
. Vega-Lite will generate a Vega spec including the signal selection1_x
(redacted for brevity):
{
"name": "selection1_x",
"value": [],
"on": [
{
"events": {
"source": "scope",
"type": "mousedown",
"filter": // ...
},
"update": "[x(unit), x(unit)]"
},
{
"events": {
"source": "scope",
"type": "mousemove",
"between": // ...
},
"update": "[selection1_x[0], clamp(x(unit), 0, width)]"
},
// ...
We can replace the x(unit)
expressions in the first on
item, and the clamp(x(unit), 0, width)
expression in the second on
item, using the steps above:
{
"name": "selection1_x",
"value": [],
"on": [
{
"events": {
"source": "scope",
"type": "mousedown",
"filter": // ...
},
"update": "[scale(\"x\",datetime(year(invert(\"x\",x(unit))),0)),scale(\"x\",datetime(year(invert(\"x\",x(unit))),0))]"
},
{
"events": {
"source": "scope",
"type": "mousemove",
"between": // ...
},
"update": "[selection1_x[0],scale(\"x\",datetime(year(invert(\"x\",clamp(x(unit),0,width))),0))]"
},
// ...
The lines are getting long, and this is without considering intervals smaller than a year. You probably don't want to write this stuff by hand!
This method supports truncating to any of the parameters of the Vega datetime expression:
datetime(year, month[, day, hour, min, sec, millisec])
Returns a newDate
instance. The month is 0-based, such that1
represents February.
You have to specify at least year and month, so above we had zeros for month.
To snap to week, you can subtract the day of the week from the date of the month:
[selection1_x[0], scale("x", datetime(
year(invert("x", clamp(x(unit),0,width))),
month(invert("x", clamp(x(unit),0,width))),
date(invert("x", clamp(x(unit),0,width)))
- day(invert("x", clamp(x(unit),0,width)))))]
I haven't learned Vega well enough to know if I can simplify these expressions.
Also vega-time is in the pipeline, providing the functionality of d3-time intervals, so this should get a lot simpler in the near future!