Skip to content

Instantly share code, notes, and snippets.

@Giammaria
Last active March 10, 2025 17:10
Show Gist options
  • Save Giammaria/830c975be22fc3871902ef653cc37233 to your computer and use it in GitHub Desktop.
Save Giammaria/830c975be22fc3871902ef653cc37233 to your computer and use it in GitHub Desktop.
20250308_numeric_bar_kpi_v_v1
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 300,
"autosize": {"type": "none"},
"signals": [
{"name": "title", "value": "Metric Name"},
{"name": "padding", "value": 0},
{"name": "height", "update": "width"},
{
"name": "darkMode",
"init": "true",
"bind": {"name": "Dark Mode", "input": "checkbox"}
},
{"name": "background", "update": "darkMode ? '#202020' : '#fff'"},
{"name": "value", "init": "200000000"},
{"name": "goal", "init": "250000000"},
{
"name": "initializedTime",
"init": "now()",
"on": [{"events": {"signal": "darkMode"}, "update": "now()"}]
},
{"name": "tickCount", "value": 20},
{
"name": "transitionDuration",
"init": "clamp((value/goal)*1500+250, 250, 3000)"
},
{
"name": "t",
"init": "0",
"on": [
{
"events": {"type": "timer", "throttle": 10},
"update": "now() >= (initializedTime + transitionDuration) ? 1 : (now()-initializedTime)/(transitionDuration)"
}
]
},
{
"name": "valueOpacity",
"init": "0",
"on": [
{
"events": {"type": "timer", "throttle": 10},
"update": "clamp(now() < (initializedTime + transitionDuration) ? 0 : (now()-75-(initializedTime + transitionDuration))/(250), 0, 1)"
}
]
},
{
"name": "valueInterpolated",
"update": "+format((t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1)*value, '.2f')"
},
{"name": "radius", "update": "min(width, height)/2.1"},
{"name": "mainColor", "update": "darkMode ? '#8C9EFF' : '#2943D1'"},
{"name": "secondaryColor", "update": "darkMode ? '#ccc' : '#888'"},
{"name": "tiertiaryColor", "update": "'#FAFAFA'"},
{"name": "font", "value": "sans-serif"},
{"name": "due", "update": "now()+(2.628e+9)*(random()*10)"},
{"name": "remainingDays", "update": "round((due-now())/(8.64e+7))"},
{
"name": "dueText",
"update": "['Due ' + utcFormat(due, '%d-%b-%y'), '━━━', remainingDays + ' day' + (remainingDays > 1 ? 's' : '') + ' remain']"
},
{"name": "xDomain", "update": "[0, goal*1.2]"}
],
"scales": [
{
"name": "xBandScale",
"type": "band",
"domain": {"data": "trackDataset", "field": "index", "sort": true},
"range": {"signal": "[0.05*width,0.95*width]"},
"paddingInner": 0.3
},
{
"name": "xScale",
"type": "linear",
"zero": true,
"clamp": true,
"domain": {"signal": "xDomain"},
"range": {"signal": "range('xBandScale')"}
}
],
"marks": [
{
"name": "metric-title-text",
"type": "text",
"encode": {
"update": {
"text": {"signal": "title"},
"font": {"signal": "font"},
"fontWeight": {"value": 500},
"fontSize": {"signal": "min(width,height)/14"},
"x": {"signal": "width/2"},
"y": {"signal": "height/20"},
"fill": {"signal": "secondaryColor"},
"baseline": {"value": "top"},
"align": {"value": "center"}
}
}
},
{
"name": "track-group",
"from": {"data": "trackDataset"},
"type": "group",
"encode": {
"update": {
"x": {"scale": "xBandScale", "field": "index"},
"width": {"signal": "bandwidth('xBandScale')"},
"y": {"signal": "data('metric-title-text')[0].bounds.y2+height/12"},
"height": {"signal": "height/10"},
"fill": {"value": "transparent"},
"cornerRadius": {"signal": "0.005*min(width, height)"},
"stroke": {"signal": "mainColor"},
"strokeWidth": {"value": 1.5},
"strokeOpacity": {"field": "opacity"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "value-rect",
"type": "rect",
"encode": {
"update": {
"width": {
"signal": "bandwidth('xBandScale')*parent.bandPercentage"
},
"height": {"signal": "height"},
"fill": {"signal": "mainColor"},
"fillOpacity": {"signal": "parent.opacity"}
}
}
}
]
},
{
"name": "goal-indicator-top-text",
"type": "text",
"encode": {
"update": {
"x": {"scale": "xScale", "signal": "goal"},
"y": {"signal": "data('track-group')[0].bounds.y1"},
"align": {"value": "center"},
"baseline": {"value": "bottom"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize*0.5"},
"fill": {"signal": "secondaryColor"},
"font": {"value": "Zapf Dingbats"},
"text": {"value": "⮟"}
}
}
},
{
"name": "goal-indicator-bottom-text",
"type": "text",
"encode": {
"update": {
"x": {"scale": "xScale", "signal": "goal"},
"y": {"signal": "data('track-group')[0].bounds.y2"},
"align": {"value": "center"},
"baseline": {"value": "top"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize*0.5"},
"fill": {"signal": "secondaryColor"},
"font": {"value": "Zapf Dingbats"},
"text": {"value": "⮝"}
}
}
},
{
"name": "value-text-label",
"type": "text",
"encode": {
"update": {
"x": {"signal": "range('xScale')[0]"},
"y": {"signal": "data('track-group')[0].bounds.y2"},
"dy": {"signal": "height/10"},
"text": {"signal": "'Total:'"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize*0.5"},
"fontWeight": {"signal": "200"},
"fill": {"signal": "secondaryColor"},
"align": {"signal": "'left'"},
"baseline": {"value": "top"}
}
}
},
{
"name": "value-text",
"from": {"data": "value-text-label"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "width*0.565"},
"y": {"signal": "datum.bounds.y2"},
"dy": {"signal": "height/50"},
"text": {"signal": "data('trackDataset')[0].valueFormatted"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize"},
"fontWeight": {"signal": "700"},
"fill": {"signal": "mainColor"},
"align": {"signal": "'right'"},
"baseline": {"value": "bottom"}
}
}
},
{
"name": "goal-text-label",
"from": {"data": "value-text-label"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "range('xScale')[0]"},
"y": {"signal": "datum.bounds.y2"},
"dy": {"signal": "height/15"},
"text": {"signal": "'Goal:'"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize*0.5"},
"fontWeight": {"signal": "200"},
"fill": {"signal": "secondaryColor"},
"align": {"signal": "'left'"},
"baseline": {"value": "top"}
}
}
},
{
"name": "goal-text",
"from": {"data": "goal-text-label"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "data('value-text')[0].bounds.x2"},
"y": {"signal": "datum.bounds.y2"},
"dy": {"signal": "height/100"},
"text": {"signal": "data('trackDataset')[0].goalFormatted"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize*0.75"},
"fontWeight": {"signal": "600"},
"fill": {"signal": "secondaryColor"},
"align": {"signal": "'right'"},
"baseline": {"value": "bottom"}
}
}
},
{
"name": "percentage-text",
"from": {"data": "value-text-label"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "range('xScale')[1]"},
"y": {"signal": "datum.bounds.y2"},
"dy": {
"signal": "-(datum.bounds.y2 - data('goal-text')[0].bounds.y1)/2"
},
"text": {"signal": "data('trackDataset')[0].percentageFormatted"},
"fontSize": {"signal": "data('trackDataset')[0].fontSize"},
"fontWeight": {"signal": "700"},
"fill": {"signal": "mainColor"},
"align": {"signal": "'right'"},
"baseline": {"value": "middle"}
}
}
},
{
"name": "link-group",
"type": "group",
"data": [
{
"name": "edges",
"values": [{"edge": 1}, {"edge": 2}],
"transform": [
{
"type": "formula",
"expr": "length(format(value/goal, '.0%'))",
"as": "percentageLength"
},
{
"type": "formula",
"expr": "(datum.edge === 1 ? data('value-text')[0].bounds.x2 : data('goal-text')[0].bounds.x2)+width/50",
"as": "sourceX"
},
{
"type": "formula",
"expr": "datum.edge === 1 ? (data('value-text')[0].bounds.y1 + (data('value-text')[0].bounds.y2-data('value-text')[0].bounds.y1)/2) : (data('goal-text')[0].bounds.y1 + (data('goal-text')[0].bounds.y2-data('goal-text')[0].bounds.y1)/2)",
"as": "sourceY"
},
{
"type": "formula",
"expr": "data('percentage-text')[0].bounds.x1-datum.percentageLength*data('trackDataset')[0].fontSize/3.5",
"as": "goalX"
},
{
"type": "formula",
"expr": "data('percentage-text')[0].bounds.y1+(data('percentage-text')[0].bounds.y2-data('percentage-text')[0].bounds.y1)/2",
"as": "goalY"
},
{
"type": "linkpath",
"sourceX": "sourceX",
"sourceY": "sourceY",
"targetX": "goalX",
"targetY": "goalY",
"orient": "horizontal",
"shape": "diagonal"
}
]
}
],
"marks": [
{
"type": "path",
"from": {"data": "edges"},
"encode": {
"update": {
"stroke": {"signal": "secondaryColor"},
"strokeWidth": {"value": 2}
},
"enter": {"path": {"field": "path"}}
}
}
]
},
{
"name": "metric-date-due",
"from": {"data": "goal-text"},
"type": "text",
"encode": {
"update": {
"text": {"signal": "dueText"},
"font": {"signal": "font"},
"fontWeight": {"value": 400},
"fontSize": {"signal": "height/20"},
"x": {"signal": "width/2"},
"y": {"signal": "datum.bounds.y2", "offset": {"signal": "height/20"}},
"fill": {"signal": "secondaryColor"},
"angle": {"field": "angle"},
"baseline": {"value": "top"},
"align": {"value": "center"}
}
}
}
],
"data": [
{
"name": "trackDataset",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "sequence((xDomain[1]+1)/tickCount, xDomain[1]+1, (xDomain[1]+1)/tickCount)",
"as": "endDomain"
},
{"type": "flatten", "fields": ["endDomain"]},
{
"type": "formula",
"expr": "round(datum.endDomain)",
"as": "endDomain"
},
{
"type": "window",
"ops": ["lag"],
"fields": ["endDomain"],
"as": ["startDomain"]
},
{
"type": "formula",
"expr": "datum.startDomain || 0",
"as": "startDomain"
},
{
"type": "formula",
"expr": "valueInterpolated >= datum.startDomain ? clamp((valueInterpolated-datum.startDomain)/(datum.endDomain-datum.startDomain), 0, 1) : 0",
"as": "bandPercentage"
},
{"type": "formula", "expr": "valueInterpolated", "as": "value"},
{
"type": "formula",
"expr": "format(datum.value, '$.2s')",
"as": "valueFormatted"
},
{"type": "formula", "expr": "goal", "as": "goal"},
{
"type": "formula",
"expr": "format(datum.goal, '$.2s')",
"as": "goalFormatted"
},
{
"type": "formula",
"expr": "format(datum.value/datum.goal, '.0%')",
"as": "percentageFormatted"
},
{"type": "formula", "expr": "(min(width,height))/10", "as": "fontSize"},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDomain", "order": "ascending"},
"as": ["index"]
},
{
"type": "formula",
"expr": "inrange(datum.goal, [datum.startDomain, datum.endDomain]) ? datum.index : 0",
"as": "opacity"
},
{
"type": "joinaggregate",
"ops": ["max"],
"fields": ["opacity"],
"as": ["opacity"]
},
{
"type": "formula",
"expr": "clamp(0.1+(datum.index/datum.opacity)*0.9, 0, 1)",
"as": "opacity"
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment