Bump Chart
A bump chart tracks how categories rank against each other over time, highlighting overtakes and trend reversals. Use it for questions like "which products have been our top sellers each month?" where the rank matters more than the raw value. This template uses full Vega (@vg) so hovering a point or legend entry highlights that category's line and fades the others.
CustomChartDef bump_chart {
label: 'Bump Chart'
description: "A bump chart that tracks category rankings over time, highlighting the hovered category and fading the others."
fields {
field period {
type: 'dimension'
label: 'Period'
}
field category {
type: 'dimension'
label: 'Category'
}
field value {
type: 'measure'
label: 'Value (used for ranking)'
}
}
options {
option color_scheme {
type: 'select'
label: 'Color scheme'
options: ['tableau10', 'category10', 'accent', 'dark2', 'paired', 'pastel1', 'pastel2', 'set1', 'set2', 'set3']
default_value: 'tableau10'
}
option line_interpolate {
type: 'select'
label: 'Line style'
options: ['linear', 'monotone']
default_value: 'monotone'
}
option point_size {
type: 'select'
label: 'Point size'
options: [60, 100, 140, 180]
default_value: 100
}
}
template: @vg {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "Bump chart with hover-to-highlight interaction and legend.",
"autosize": {"type": "fit", "contains": "padding"},
"padding": {"left": 5, "right": 5, "top": 5, "bottom": 5},
"signals": [
{
"name": "width",
"init": "containerSize()[0] - 10",
"on": [{ "events": "window:resize", "update": "containerSize()[0] - 10" }]
},
{
"name": "height",
"init": "containerSize()[1] - 10",
"on": [{ "events": "window:resize", "update": "containerSize()[1] - 10" }]
},
{
"name": "hovered",
"value": null,
"on": [
{"events": "symbol:mouseover", "update": "datum.category"},
{"events": "symbol:mouseout", "update": "null"},
{"events": "@legendSymbol:mouseover, @legendLabel:mouseover", "update": "datum.value"},
{"events": "@legendSymbol:mouseout, @legendLabel:mouseout", "update": "null"}
]
}
],
"data": [
{
"name": "table",
"values": @{values},
"transform": [
{"type": "formula", "expr": "datum['@{fields.period.name}']", "as": "period"},
{"type": "formula", "expr": "datum['@{fields.category.name}']", "as": "category"},
{"type": "formula", "expr": "datum['@{fields.value.name}']", "as": "amount"},
{
"type": "window",
"sort": {"field": "amount", "order": "descending"},
"ops": ["rank"],
"as": ["rank"],
"groupby": ["period"]
},
{
"type": "collect",
"sort": {"field": "period"}
}
]
}
],
"scales": [
{
"name": "x",
"type": "point",
"domain": {"data": "table", "field": "period", "sort": true},
"range": "width"
},
{
"name": "y",
"type": "point",
"domain": {"data": "table", "field": "rank", "sort": true},
"range": "height"
},
{
"name": "color",
"type": "ordinal",
"domain": {"data": "table", "field": "category"},
"range": {"scheme": @{options.color_scheme.value}}
}
],
"axes": [
{
"orient": "bottom",
"scale": "x",
"labelAngle": 0,
"domain": false,
"ticks": false,
"labelPadding": 8,
"labelFontSize": 12
},
{
"orient": "left",
"scale": "y",
"title": "Rank",
"domain": false,
"ticks": false,
"labelPadding": 8,
"labelFontSize": 12,
"titleFontSize": 12,
"grid": true,
"gridColor": "#F3F4F6"
}
],
"legends": [
{
"fill": "color",
"orient": "top",
"direction": "horizontal",
"title": null,
"symbolType": "circle",
"encode": {
"symbols": {
"name": "legendSymbol",
"interactive": true,
"update": {
"opacity": {"signal": "hovered === null || hovered === datum.value ? 1 : 0.15"}
}
},
"labels": {
"name": "legendLabel",
"interactive": true,
"update": {
"opacity": {"signal": "hovered === null || hovered === datum.value ? 1 : 0.3"}
}
}
}
}
],
"marks": [
{
"type": "group",
"from": {"facet": {"name": "series", "data": "table", "groupby": "category"}},
"marks": [
{
"type": "line",
"from": {"data": "series"},
"encode": {
"update": {
"x": {"scale": "x", "field": "period"},
"y": {"scale": "y", "field": "rank"},
"stroke": {"scale": "color", "field": "category"},
"interpolate": {"value": @{options.line_interpolate.value}},
"strokeWidth": {"signal": "hovered === datum.category ? 4 : 2"},
"opacity": {"signal": "hovered === null || hovered === datum.category ? 1 : 0.15"}
}
}
},
{
"type": "symbol",
"from": {"data": "series"},
"encode": {
"update": {
"x": {"scale": "x", "field": "period"},
"y": {"scale": "y", "field": "rank"},
"fill": {"scale": "color", "field": "category"},
"size": {"value": @{options.point_size.value}},
"opacity": {"signal": "hovered === null || hovered === datum.category ? 1 : 0.15"},
"tooltip": {
"signal": "datum.category + ' - ' + datum.period + ': rank ' + datum.rank + ' (' + format(datum.amount, ',') + ')'"
}
}
}
}
]
}
]
}
;;
}