Skip to content

Instantly share code, notes, and snippets.

@guru-florida
Created October 22, 2019 17:56
Show Gist options
  • Save guru-florida/378076f154c10c6d171608c1e82b2e3c to your computer and use it in GitHub Desktop.
Save guru-florida/378076f154c10c6d171608c1e82b2e3c to your computer and use it in GitHub Desktop.
Smooth Timeline control in Vega
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 1500,
"height": 50,
"padding": 15,
"signals": [
{
"name": "rangeLimits",
"value": [30, 120]
},
{
"name": "scrollock",
"value": false,
"on": [{
"events": "window:keyup![!event.shiftKey && !event.ctrlKey && event.keyCode==76]",
"update": "!scrollock"
}]
},
{
"name": "tsvelocity", "value": 0,
"description": "current velocity of the timeline (ex. throwing the timeline)",
"on": [{
"events": {
"type": "timer",
"throttle": 100
},
"update": "abs(tsvelocity) > 60000 ? tsvelocity * tsacc : 0"
}, {
"events": "mousemove[event.buttons]",
"update": "scrollock ? 0 :-event.movementX * 400000"
}]
}, {
"name": "tsacc", "value": 0.75,
"description": "deaccelleration parameter controls how quickly the the timeline stops after a throw"
},
{
"name": "tsrange", "value": 120,
"description": "zoom setting, larger values means we are zooming out on the timeline",
"on": [{
"events": "wheel",
"update": "clamp(tsrange + (event.deltaY * 5), rangeLimits[0], rangeLimits[1])"
}]
},
{
"name": "tspick", "value": 0,
"on":[{
"events": "mousedown",
"update": "floor(invert('lin_timescale', clamp(x(), 0, width)))"
}, {
"events": "mouseup",
"update": "0"
}]
},
{
"name": "centerts", "value": 1568164000000,
"description": "timestamp that represents the center, the range is then extended to the left and right",
"on": [{
"events": "mousemove[event.buttons]",
"update": "(tspick===0 || scrollock) ? centerts : centerts + (tspick - invert('lin_timescale', clamp(x(), 0, width)))"
}, {
"events": {
"type": "timer",
"throttle": 33
},
"update": "(tspick > 0) ? centerts : floor(centerts + tsvelocity/33)"
}, {
"events": "window:keyup![!event.shiftKey && (event.keyCode>48) && (event.keyCode<59) ]",
"update": "warn('goto ', (isDefined(data('bookmarks')[event.keyCode - 49]) && data('bookmarks')[event.keyCode - 49].ts) ? data('bookmarks')[event.keyCode - 49].ts : centerts)"
}, {
"events": "window:keyup![event.shiftKey && event.keyCode==37]",
"update": "centerts - 86400000"
}, {
"events": "window:keyup![event.shiftKey && event.keyCode==39]",
"update": "centerts + 86400000"
}, {
"events": "window:keyup![!event.shiftKey && event.keyCode==37]",
"update": "centerts - 3600000"
}, {
"events": "window:keyup![!event.shiftKey && event.keyCode==39]",
"update": "centerts + 3600000"
},{
"events": "window:keyup![!event.shiftKey && event.keyCode==78]",
"update": "1569797913000"
},{
"events": "mousedown",
"update": "(datum && datum.shortcut && datum.ts) ? datum.ts : centerts"
}]
},
{
"name": "startts", "value": 1568152000000, "update": "centerts - (tsrange * 30000)"
},
{
"name": "endts", "value": 1568152000000, "update": "centerts + (tsrange * 30000)"
},
{
"name": "tickMinor", "update": "(tsrange < 60) ? 5 : (tsrange < 120) ? 10 : 15"
},
{
"name": "tickMajor", "update": "tickMinor * 4"
},
{
"name": "savebookmark",
"on": [{
"events": "window:keydown[event.shiftKey && (event.keyCode>48) && (event.keyCode<59) ]",
"update": "warn('save ', event.keyCode - 49, (isDefined(data('bookmarks')[event.keyCode - 49])) ? data('bookmarks')[event.keyCode - 49] : null)"
}, {
"events": "window:keyup[event.shiftKey && (event.keyCode>48) && (event.keyCode<59) ]",
"update": "null"
}]
},
{
"name": "bmspacing", "value": 30
}
],
"data": [{
"name": "window",
"values": [
{ "from": 1000, "to": 1567296000000, "fill": "#222", "radius": 0 },
{ "from": 1569801600000, "to": 9569801600000, "fill": "#5511117b", "radius": 0 }
]
}, {
"name": "bookmarks",
"on":[{
"trigger": "savebookmark",
"modify": "savebookmark",
"values": "{ ts: centerts }"
}],
"values": [
{ "shortcut": "1", "ts": 1568159000000 },
{ "shortcut": "2", "ts": 1568162000000 },
{ "shortcut": "3", "ts": 1568166000000 },
{ "shortcut": "4", "ts": 1569024000000 },
{ "shortcut": "5", "ts": 0 },
{ "shortcut": "6", "ts": 0 },
{ "shortcut": "7", "ts": 0 },
{ "shortcut": "8", "ts": 0 },
{ "shortcut": "9", "ts": 0 }
]
}, {
"name": "BMLT",
"source": "bookmarks",
"transform": [{
"type": "filter",
"expr": "datum.ts>0 && datum.ts < startts"
}]
}, {
"name": "BMV",
"source": "bookmarks",
"transform": [{
"type": "filter",
"expr": "datum.ts > startts && datum.ts < endts"
}]
}, {
"name": "BMGT",
"source": "bookmarks",
"transform": [{
"type": "filter",
"expr": "datum.ts > endts"
}]
}],
"scales": [
{
"name": "lin_timescale",
"type": "linear",
"domain": [{"signal": "startts"}, {"signal": "endts"}],
"nice": false,
"zero": false,
"range": "width"
}, {
"name": "timescale",
"type": "time",
"domain": { "signal": "domain('lin_timescale')" },
"nice": false,
"clamp": true,
"range": "width"
}, {
"name": "bands",
"type": "band",
"paddingInner": 0.04,
"domain": [0, 1, 2, 3, 4, 5],
"range": "height"
}
],
"axes": [
{
"orient": "top", "scale": "timescale", "domain": false,
"tickCount": { "interval": "minute", "step": 5 },
"labels": false,
"grid": true,
"gridWidth": 2,
"gridColor": "#888",
"zindex": -1,
"encode": {
"grid": {
"update": {
"y": { "scale": "bands", "value": 2 },
"y2": { "scale": "bands", "value": 3 }
}
}
}
},
{
"orient": "top", "scale": "timescale", "domain": false,
"tickCount": { "interval": "minute", "step": 15 },
"labelOverlap": "greedy",
"grid": true,
"gridWidth": 2,
"gridColor": "#888",
"zindex": -1,
"format": "%H:%M",
"encode": {
"grid": {
"update": {
"y": { "scale": "bands", "value": 1 },
"y2": { "scale": "bands", "value": 4 }
}
}
}
},
{
"orient": "bottom", "scale": "timescale", "domain": false,
"tickCount": { "interval": "hour", "step": 1 },
"labels": true,
"labelColor": "white",
"grid": true,
"gridWidth": 3,
"gridColor": "#888",
"zindex": -1,
"format": "%b%d %-I%p",
"encode": {
"labels": {
"update": {
"y": { "value": 9 },
"y2": { "scale": "bands", "value": 1 }
}
}
}
}
],
"marks": [
{
"name": "bars",
"from": {"data": "window"},
"type": "rect",
"encode": {
"enter": {
"y": {"scale": "bands", "value": 0, "offset": -4 },
"height": {"scale": "bands", "band": 6, "offset": 9 }
},
"update": {
"x": {"scale": "timescale", "field": "from"},
"x2": {"scale": "timescale", "field": "to"},
"cornerRadius": { "field": "radius" },
"fill": { "field": "fill"},
"stroke": { "field": "stroke"},
"strokeWidth": { "field": "strokeWidth"}
}
}
},
{
"name": "bookmarksGroup",
"type": "group",
"signals": [{
"name": "bmlt_width",
"update": "data('BMLT').length * bmspacing"
}, {
"name": "bmgt_width",
"update": "data('BMGT').length * bmspacing"
}],
"scales": [{
"name": "bmlt_scale",
"type": "point",
"align": 0,
"domain": {
"data": "BMLT",
"field": "shortcut",
"sort": {"op": "median", "field": "ts", "order": "ascending"}
},
"range": { "step": { "signal": "bmspacing" } }
}, {
"name": "bmgt_scale",
"type": "point",
"align": 0,
"domain": {
"data": "BMGT",
"field": "shortcut",
"sort": {"op": "median", "field": "ts", "order": "descending"}
},
"range": { "step": { "signal": "bmspacing" } }
}, {
"name": "timescale_bmv",
"type": "linear",
"domain": [
{ "signal": "invert('lin_timescale', bmlt_width)" },
{ "signal": "invert('lin_timescale', width - bmgt_width )" }
],
"nice": false,
"clamp": true,
"zero": false,
"range": [{ "signal": "bmlt_width" }, { "signal": "width - bmgt_width" }]
}],
"marks": [
{
"name": "BMPointers",
"from": {"data": "BMV"},
"type": "symbol",
"encode": {
"enter": {
"shape": {"value": "wedge"},
"y": {"scale": "bands", "value": 4, "offset": 7 },
"size": { "value": 500 },
"zindex": { "value": 5 },
"fill": { "value": "#00bfff"}
},
"update": {
"x": {"scale": "timescale", "field": "ts" }
}
}
},
{
"name": "BMLTSquares",
"from": {"data": "BMLT"},
"type": "rect",
"encode": {
"enter": {
"y": {"scale": "bands", "value": 5 },
"width": { "value": 20 },
"height": { "value": 20 },
"fill": { "value": "#00bfff"},
"zindex": { "value": 5 },
"cornerRadius": { "value": 4 }
},
"update": {
"x": { "scale": "bmlt_scale", "field": "shortcut", "offset": -10 }
}
}
}, {
"name": "BMLTLabels",
"from": {"data": "BMLT"},
"type": "text",
"encode": {
"enter": {
"text": {"field": "shortcut"},
"align": { "value": "center" },
"baseline": { "value": "top" },
"fontSize": { "value": 14 },
"y": {"scale": "bands", "value": 5, "offset": 4 },
"size": { "value": 200 },
"fill": { "value": "white"}
},
"update": {
"x": { "scale": "bmlt_scale", "field": "shortcut" }
}
}
},
{
"name": "BMCVSquares",
"from": {"data": "BMV"},
"type": "rect",
"encode": {
"enter": {
"y": {"scale": "bands", "value": 5 },
"width": { "value": 20 },
"height": { "value": 20 },
"fill": { "value": "#00bfff"},
"zindex": { "value": 5 },
"cornerRadius": { "value": 4 }
},
"update": {
"x": {"scale": "timescale_bmv", "field": "ts", "offset": -10 }
}
}
}, {
"name": "BMCVLabels",
"from": {"data": "BMV"},
"type": "text",
"encode": {
"enter": {
"text": {"field": "shortcut"},
"align": { "value": "center" },
"baseline": { "value": "top" },
"fontSize": { "value": 14 },
"y": {"scale": "bands", "value": 5, "offset": 4 },
"size": { "value": 200 },
"zindex": { "value": 5 },
"fill": { "value": "white"}
},
"update": {
"x": {"scale": "timescale_bmv", "field": "ts" }
}
}
},
{
"name": "BMGTSquares",
"from": {"data": "BMGT"},
"type": "rect",
"zindex": { "value": 50 },
"encode": {
"enter": {
"y": {"scale": "bands", "value": 5 },
"width": { "value": 20 },
"height": { "value": 20 },
"fill": { "value": "#00bfff"},
"zindex": { "value": 5 },
"cornerRadius": { "value": 4 }
},
"update": {
"x": { "signal": "width-10", "offset": { "scale": "bmgt_scale", "field": "shortcut", "mult": -1 } }
}
}
}, {
"name": "BMGTLabels",
"from": {"data": "BMGT"},
"type": "text",
"encode": {
"enter": {
"text": {"field": "shortcut"},
"align": { "value": "center" },
"baseline": { "value": "top" },
"fontSize": { "value": 14 },
"y": {"scale": "bands", "value": 5, "offset": 4 },
"size": { "value": 200 },
"zindex": { "value": 5 },
"fill": { "value": "white"}
},
"update": {
"x": { "signal": "width", "offset": { "scale": "bmgt_scale", "field": "shortcut", "mult": -1 } }
}
}
}
]
}]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment