Created
March 27, 2025 19:12
-
-
Save PBI-DataVizzle/9b21c0f74fbe0b97e71788b17d37e6ba to your computer and use it in GitHub Desktop.
easing formulas by Madison Giammaria
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"$schema": "https://vega.github.io/schema/vega-lite/v5.json", | |
"usermeta": { | |
"version": "01.01", | |
"developedBy": "Madison Giammaria", | |
"gitHub": "https://github.com/Giammaria", | |
"linkedIn": "https://www.linkedin.com/in/madison-giammaria-58463b33", | |
"email": "[email protected]", | |
"visualName": "Easing Formulas", | |
"visualDescription": "Here you can compare different easing functions and see their respective formulas. This isn't a comprehensive list of easing functions, but these are definitely some popular ones. Special thanks to Pavithra Kodmad for writing this excellent article on understanding easing! https://css-tricks.com/ease-y-breezy-a-primer-on-easing-functions/ A few months prior to making this, I would not have thought that this would be feasible in Vega-Lite." | |
}, | |
"padding": {"top": 0, "right": 10, "bottom": 20, "left": 0}, | |
"title": { | |
"text": {"signal": "['Easing Formulas']"}, | |
"subtitle": { | |
"signal": "['Using Timers in Vega-Lite','', 't = '+format(t, '0.3f'), 'd = ' + s+'s']" | |
}, | |
"subtitleFontStyle": "italic", | |
"subtitleFontSize": 16, | |
"subtitleFontWeight": 200, | |
"subtitlePadding": 5, | |
"fontSize": 20, | |
"anchor": "start", | |
"offset": 20 | |
}, | |
"params": [ | |
{"name": "desiredWidth", "init": 1000}, | |
{"name": "desiredHeight", "init": 500}, | |
{ | |
"name": "duration", | |
"description": "time in seconds to complete one to go from t[0] to t[1]", | |
"value": 3, | |
"bind": { | |
"input": "range", | |
"min": 1, | |
"max": 25, | |
"step": 1, | |
"name": "Duration (s)" | |
} | |
}, | |
{ | |
"name": "colorDomain", | |
"description": "ease names", | |
"init": "['Linear','EaseInQuad','EaseOutQuad','EaseInOutQuad','EaseInCubic','EaseOutCubic','EaseInOutCubic']" | |
}, | |
{ | |
"name": "colorRange", | |
"description": "ease colors", | |
"init": "['#003f5c','#ffa600','#374c80','#ff764a','#7a5195','#ef5675','#bc5090']" | |
}, | |
{ | |
"name": "oscillationChartWidth", | |
"description": "width of the oscillation chart area", | |
"init": "desiredWidth*0.45" | |
}, | |
{ | |
"name": "chartsInnerPadding", | |
"description": "padding between charts", | |
"init": "0.1*width" | |
}, | |
{ | |
"name": "lineChartWidth", | |
"description": "width of the line chart area", | |
"update": "desiredWidth-oscillationChartWidth-chartsInnerPadding" | |
}, | |
{ | |
"name": "toggleOscillationSecondsVisibility", | |
"description": "show seconds in oscillation chart", | |
"value": true, | |
"bind": {"input": "checkbox", "name": "Seconds"} | |
}, | |
{ | |
"name": "toggleFormulasVisibility", | |
"description": "show formulas in the oscillation chart", | |
"value": true, | |
"bind": {"input": "checkbox", "name": "Formulas"} | |
}, | |
{ | |
"name": "toggleAllEasingsVisibility", | |
"description": "show/hide all easings", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "Select/Clear All"} | |
}, | |
{ | |
"name": "showLinear", | |
"description": "show linear easing", | |
"value": true, | |
"bind": {"input": "checkbox", "name": "Linear"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseInQuad", | |
"description": "show easeInQuad easing", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "EaseInQuad"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseOutQuad", | |
"description": "show eaeOutQuad easing", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "EaseOutQuad"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseInOutQuad", | |
"description": "show easeInOutQuad easing", | |
"value": true, | |
"bind": {"input": "checkbox", "name": "EaseInOutQuad"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseInCubic", | |
"description": "show easeInCubic easing", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "EaseInCubic"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseOutCubic", | |
"description": "show easeOutCubic easing", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "EaseOutCubic"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "showEaseInOutCubic", | |
"description": "show easeInOutCubic easing", | |
"value": false, | |
"bind": {"input": "checkbox", "name": "EaseInOutCubic"}, | |
"on": [ | |
{ | |
"events": {"signal": "toggleAllEasingsVisibility"}, | |
"update": "toggleAllEasingsVisibility" | |
} | |
] | |
}, | |
{ | |
"name": "xOscillationDomain", | |
"description": "input range from 0 to duration", | |
"init": "[0, duration]", | |
"on": [{"events": {"signal": "duration"}, "update": "[0, duration]"}] | |
}, | |
{ | |
"name": "timer", | |
"description": "timer that updates every milisecond", | |
"init": "now()", | |
"on": [{"events": {"type": "timer"}, "update": "now()"}] | |
}, | |
{ | |
"name": "start", | |
"description": "time in miliseconds at t[0]", | |
"init": "timer", | |
"on": [ | |
{ | |
"events": {"signal": "timer"}, | |
"update": "timer > start+duration*1000 ? timer+duration*1000 : start" | |
}, | |
{"events": {"signal": "duration"}, "update": "timer+duration*1000"} | |
] | |
}, | |
{ | |
"name": "end", | |
"description": "time in miliseconds at t[1]", | |
"init": "start+duration*1000", | |
"on": [{"events": {"signal": "start"}, "update": "start+duration*1000"}] | |
}, | |
{ | |
"name": "direction", | |
"description": "direction in the oscillation chart. When t is increasing 1 else -1", | |
"update": "(xOscillationDomain[1]-xOscillationDomain[0])*(timer-start)/(end-start) >= 0 ? 1 : -1" | |
}, | |
{ | |
"name": "linearOscillationX", | |
"init": "xOscillationDomain[0]", | |
"on": [ | |
{ | |
"events": {"signal": "timer"}, | |
"update": "abs(lerp(xOscillationDomain, (timer-start)/(end-start)))" | |
}, | |
{"events": {"signal": "duration"}, "update": "xOscillationDomain[0]"} | |
] | |
}, | |
{ | |
"name": "t", | |
"description": "current linear t ", | |
"update": "direction === 1 ? linearOscillationX/xOscillationDomain[1] : 1-linearOscillationX/xOscillationDomain[1]" | |
}, | |
{ | |
"name": "s", | |
"description": "current second", | |
"expr": "direction === 1 ? round(linearOscillationX+0.5) : duration - round(linearOscillationX-0.5)" | |
}, | |
{"name": "concat_0_width", "update": "350"}, | |
{"name": "concat_1_width", "update": "350"} | |
], | |
"spacing": 250, | |
"bounds": "flush", | |
"hconcat": [ | |
{ | |
"height": 400, | |
"view": {"stroke": "transparent"}, | |
"title": {"anchor": "start", "text": "Oscillating Easing"}, | |
"encoding": { | |
"x": { | |
"field": "e", | |
"type": "quantitative", | |
"scale": { | |
"domain": [0, 1], | |
"rangeMin": {"expr": "direction >= 0 ? 0 : concat_0_width"}, | |
"rangeMax": {"expr": "direction >= 0 ? concat_0_width : 0"} | |
}, | |
"axis": { | |
"ticks": false, | |
"labels": false, | |
"grid": false, | |
"domain": false, | |
"title": null | |
} | |
}, | |
"y": { | |
"field": "sort", | |
"type": "quantitative", | |
"axis": { | |
"title": null, | |
"ticks": false, | |
"domain": false, | |
"labelPadding": 20, | |
"labelFontSize": 13, | |
"gridOpacity": {"expr": "datum.label%1===0 ? 1 : 0"}, | |
"tickOpacity": {"expr": "datum.label%1===0 ? 1 : 0"}, | |
"labelExpr": "datum.label%1===0 ? data('data_0')[datum.value-1].name : null" | |
}, | |
"scale": {"zero": false, "reverse": true} | |
} | |
}, | |
"layer": [ | |
{ | |
"mark": { | |
"type": "circle", | |
"size": 700, | |
"fillOpacity": 1, | |
"align": "left", | |
"fill": {"expr": "colorRange[datum.id-1]"} | |
} | |
}, | |
{ | |
"mark": { | |
"type": "text", | |
"align": "center", | |
"baseline": "middle", | |
"fill": "#fff", | |
"text": {"expr": "toggleOscillationSecondsVisibility ? datum['seconds'] + 's' : null"}, | |
"fontSize": 12, | |
"fontWeight": 600 | |
} | |
}, | |
{ | |
"mark": { | |
"type": "text", | |
"dx": { | |
"expr": "(direction < 0 ? -scale('concat_0_x', datum.e) : -scale('concat_0_x', datum.e))" | |
}, | |
"dy": 17.5, | |
"align": "left", | |
"baseline": "top", | |
"text": {"expr": "toggleFormulasVisibility ? datum['formula'] : null"}, | |
"fontSize": 12, | |
"fill": "#777" | |
} | |
} | |
] | |
}, | |
{ | |
"height": 400, | |
"view": {"stroke": "transparent"}, | |
"title": {"anchor": "start", "text": "Ease Over t"}, | |
"encoding": { | |
"color": { | |
"field": "name", | |
"type": "ordinal", | |
"scale": { | |
"domain": {"expr": "colorDomain"}, | |
"range": [ | |
"#003f5c", | |
"#ffa600", | |
"#374c80", | |
"#ff764a", | |
"#7a5195", | |
"#ef5675", | |
"#bc5090" | |
] | |
}, | |
"legend": null | |
} | |
}, | |
"layer": [ | |
{ | |
"transform": [ | |
{ | |
"aggregate": [{"op": "count", "as": "t"}], | |
"groupby": ["id", "name", "formula"] | |
}, | |
{"calculate": "sequence(0, 1, 0.01)", "as": "t"}, | |
{"flatten": ["t"], "as": ["t"]}, | |
{ | |
"calculate": "datum['name'] === 'Linear' ? datum['t'] : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInQuad' ? (datum['t']*datum['t']) : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseOutQuad' ? datum['t']*(2-datum['t']) : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInOutQuad' ? datum['t'] < 0.5 ? 2 * datum['t'] * datum['t'] : -1 + (4 -2 * datum['t']) * datum['t'] : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInCubic' ? datum['t'] * datum['t'] * datum['t'] : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseOutCubic' ? 1 - pow(1 - datum['t'], 3) : datum['e']", | |
"as": "e" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInOutCubic' ? datum['t'] < 0.5 ? 4 * datum['t'] * datum['t'] * datum['t'] : (datum['t'] - 1) * (2 * datum['t'] - 2) * (2 * datum['t'] - 2) + 1: datum['e']", | |
"as": "e" | |
} | |
], | |
"mark": { | |
"type": "line", | |
"stroke": {"expr": "colorRange[datum.id-1]"}, | |
"strokeWidth": 2 | |
}, | |
"encoding": { | |
"x": { | |
"field": "t", | |
"type": "quantitative", | |
"scale": { | |
"rangeMin": {"expr": "0"}, | |
"rangeMax": { | |
"expr": "length(data('data_1')) === 0 ? 0 : concat_1_width" | |
} | |
}, | |
"axis": { | |
"tickCount": 10, | |
"title": "t (original)", | |
"titleFontSize": 14 | |
} | |
}, | |
"y": { | |
"field": "e", | |
"type": "quantitative", | |
"axis": { | |
"tickCount": {"expr": "10"}, | |
"title": "t (eased)", | |
"titleFontSize": 14 | |
} | |
} | |
} | |
}, | |
{ | |
"mark": {"type": "circle", "size": 75}, | |
"encoding": { | |
"x": {"field": "t", "type": "quantitative"}, | |
"y": {"field": "e", "type": "quantitative"} | |
} | |
} | |
] | |
} | |
], | |
"data": { | |
"name": "ease", | |
"values": [ | |
{"id": 1, "name": "Linear", "formula": "t => t"}, | |
{"id": 2, "name": "EaseInQuad", "formula": "t => t * t"}, | |
{"id": 3, "name": "EaseOutQuad", "formula": "t => t * (2 - t)"}, | |
{ | |
"id": 4, | |
"name": "EaseInOutQuad", | |
"formula": "t => t < 0.5 ? 2 * t * t : - 1 + (4 - 2 * t ) * t" | |
}, | |
{"id": 5, "name": "EaseInCubic", "formula": "t => t * t * t"}, | |
{"id": 6, "name": "EaseOutCubic", "formula": "t => 1 - pow(1 - t, 3)"}, | |
{ | |
"id": 7, | |
"name": "EaseInOutCubic", | |
"formula": "t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1" | |
} | |
] | |
}, | |
"transform": [ | |
{"calculate": "null", "as": "value"}, | |
{ | |
"calculate": "datum['name'] === 'Linear' ? t : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInQuad' ? (t*t) : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseOutQuad' ? t*(2-t) : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInOutQuad' ? t < 0.5 ? 2 * t * t : -1 + (4 -2 * t) * t : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInCubic' ? t * t * t : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseOutCubic' ? 1 - pow(1 - t, 3) : datum['value']", | |
"as": "value" | |
}, | |
{ | |
"calculate": "datum['name'] === 'EaseInOutCubic' ? t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1: datum['value']", | |
"as": "value" | |
}, | |
{"filter": "datum['name'] === 'Linear' && !showLinear ? false : true"}, | |
{ | |
"filter": "datum['name'] === 'EaseInQuad' && !showEaseInQuad ? false : true" | |
}, | |
{ | |
"filter": "datum['name'] === 'EaseOutQuad' && !showEaseOutQuad ? false : true" | |
}, | |
{ | |
"filter": "datum['name'] === 'EaseInOutQuad' && !showEaseInOutQuad ? false : true" | |
}, | |
{ | |
"filter": "datum['name'] === 'EaseInCubic' && !showEaseInCubic ? false : true" | |
}, | |
{ | |
"filter": "datum['name'] === 'EaseOutCubic' && !showEaseOutCubic ? false : true" | |
}, | |
{ | |
"filter": "datum['name'] === 'EaseInOutCubic' && !showEaseInOutCubic ? false : true" | |
}, | |
{"calculate": "datum['value']", "as": "e"}, | |
{ | |
"calculate": "xOscillationDomain[1]*(direction > 0 ? datum['value'] : 1-datum['value'])", | |
"as": "value" | |
}, | |
{"calculate": "t", "as": "t"}, | |
{"calculate": "s", "as": "seconds"}, | |
{"calculate": "datum['formula']", "as": "y2Label"}, | |
{"window": [{"op": "row_number", "as": "sort"}]} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment