Ranking en el tiempo (bump chart)
Cada línea es una categoría y su altura es el puesto que ocupa en cada periodo: 1º arriba. El puesto NO viene del modelo — se calcula dentro del spec con un window rank por periodo, así que cambiar la medida que ordena no exige tocar el modelo semántico. Pasa el cursor sobre una línea y el resto se atenúa para seguir su recorrido.
💡 Cuándo usarlo: Cuando importa el ORDEN y no la magnitud: quién adelanta a quién periodo a periodo. Trae un Top 6 dentro del spec (`datum.puestoSerie <= 6`), así que puedes enlazarle una dimensión de 14 o 50 valores sin que se convierta en espaguetis — cambia ese 6 y ya. Si lo que importa es cuánto y no en qué puesto, usa un gráfico de líneas normal.
Vista previa en vivo
Render con datos de muestra · Vega/Vega-LiteEl resaltado al pasar el cursor (una serie se realza y el resto se atenúa) corre EN VIVO aquí en la web: es un `point` select de Vega-Lite sobre `pointerover`. El atenuado por `__selected__` es la otra mitad — esa solo la enciende Power BI cuando otro visual filtra este, aunque la galería simula el clic en la propia vista.
Vista previa renderizada con el runtime de Deneb · MIT © Daniel Marsh-Patrick
🧩 Campos que espera
-
__0__SubCategoryName texto -
__1__YearMonth texto -
__2__Total Sales número medida
Arrastra tus campos a Values en este orden.
🎯 Técnica
- Motor
- Vega-Lite 5.16.3
- Marks
- line, point, text
Cómo usarlo en Power BI
- Agrega el visual Deneb a tu reporte e ingresa a su editor.
- Arrastra tus campos a Values en este orden: SubCategoryName , YearMonth , Total Sales .
- Pega el spec (botón «Copiar spec» arriba) en el editor JSON de Deneb.
- Mapea tus columnas y ajusta a tu gusto. La interactividad nativa ya viene configurada.
Ver el spec Vega-Lite completo
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"usermeta": {
"deneb": {
"build": "1.6.2.1",
"metaVersion": 1,
"provider": "vegaLite",
"providerVersion": "5.16.3"
},
"interactivity": {
"tooltip": true,
"contextMenu": true,
"selection": true,
"highlight": true,
"dataPointLimit": 200
},
"information": {
"name": "Ranking en el tiempo (bump chart)",
"description": "Bump chart de ranking: el puesto de cada categoría se recalcula dentro del propio spec con un window rank por periodo. Autor: Cristobal Salcedo Beltrán."
},
"dataset": [
{
"key": "__0__",
"name": "SubCategoryName",
"description": "Serie que compite en el ranking (una línea por valor)",
"type": "text",
"kind": "column"
},
{
"key": "__1__",
"name": "YearMonth",
"description": "Eje temporal del ranking, ya al grano que quieres ver",
"type": "text",
"kind": "column"
},
{
"key": "__2__",
"name": "Total Sales",
"description": "Medida que ordena el ranking dentro de cada periodo",
"type": "numeric",
"kind": "measure"
}
]
},
"config": {
"background": "transparent",
"font": "Segoe UI",
"view": {
"stroke": "transparent"
},
"axis": {
"grid": false,
"domain": false,
"ticks": false,
"labelColor": "#605E5C",
"labelFontSize": 12.5,
"titleColor": "#252423",
"titleFontWeight": "normal"
},
"text": {
"font": "Segoe UI"
}
},
"title": {
"text": "Ranking de subcategorías por ventas · 2023–2024",
"subtitle": [
"Top 6 por ventas totales · 1º arriba",
"Contoso Retail · pasa el cursor por una línea para seguirla"
],
"anchor": "middle",
"font": "Segoe UI",
"fontSize": 21,
"fontWeight": 600,
"color": "#1B1A19",
"subtitleFont": "Segoe UI",
"subtitleFontSize": 12,
"subtitleFontWeight": "normal",
"subtitleColor": "#8A8886",
"subtitleLineHeight": 17,
"subtitlePadding": 10,
"offset": 24
},
"description": "Bump chart de ranking: cada línea es una categoría y su altura es el puesto que ocupa en cada periodo (1 = arriba). El puesto se calcula dentro del spec con un window rank por periodo.",
"height": 340,
"padding": {
"left": 4,
"top": 4,
"bottom": 4,
"right": 12
},
"data": {
"name": "dataset"
},
"transform": [
{
"joinaggregate": [
{
"op": "sum",
"field": "__2__",
"as": "totalSerie"
}
],
"groupby": [
"__0__"
]
},
{
"window": [
{
"op": "dense_rank",
"as": "puestoSerie"
}
],
"sort": [
{
"field": "totalSerie",
"order": "descending"
}
]
},
{
"filter": "datum.puestoSerie <= 6"
},
{
"window": [
{
"op": "rank",
"as": "ranking"
}
],
"groupby": [
"__1__"
],
"sort": [
{
"field": "__2__",
"order": "descending"
}
]
},
{
"window": [
{
"op": "row_number",
"as": "pasoFinal"
}
],
"groupby": [
"__0__"
],
"sort": [
{
"field": "__1__",
"order": "descending"
}
]
}
],
"encoding": {
"x": {
"field": "__1__",
"type": "temporal",
"title": null,
"scale": {
"type": "utc",
"padding": 18
},
"axis": {
"format": "%b %y",
"tickCount": {
"interval": "month",
"step": 3
},
"labelAngle": 0,
"labelPadding": 14,
"grid": true,
"gridColor": "#F0EEEC",
"gridWidth": 1
}
},
"y": {
"field": "ranking",
"type": "quantitative",
"title": null,
"scale": {
"reverse": true,
"nice": false,
"zero": false,
"padding": 26
},
"axis": {
"tickMinStep": 1,
"labelExpr": "datum.value + 'º'",
"labelPadding": 14,
"labelFontWeight": 600,
"labelColor": "#3B3A39"
}
},
"color": {
"field": "__0__",
"type": "nominal",
"legend": null,
"scale": {
"range": [
"#2E5E7E",
"#C9724A",
"#4C8577",
"#A8456B",
"#7E6BB0",
"#B08A1E"
]
}
},
"opacity": {
"condition": [
{
"test": "datum.__selected__ === 'off'",
"value": 0.12
},
{
"param": "serieHover",
"value": 1
}
],
"value": 0.16
}
},
"layer": [
{
"description": "Cinta invisible y ancha: es la que recibe el cursor (una línea de 3 px no se atrapa). Anula la opacidad heredada para no pintarse nunca.",
"mark": {
"type": "line",
"interpolate": "monotone",
"strokeWidth": 22
},
"params": [
{
"name": "serieHover",
"select": {
"type": "point",
"fields": [
"__0__"
],
"on": "pointerover",
"clear": "pointerout"
}
}
],
"encoding": {
"opacity": {
"value": 0
}
}
},
{
"description": "La línea visible.",
"mark": {
"type": "line",
"interpolate": "monotone",
"strokeCap": "round",
"strokeJoin": "round"
},
"encoding": {
"strokeWidth": {
"condition": {
"param": "serieHover",
"empty": false,
"value": 4.5
},
"value": 3
}
}
},
{
"description": "Los nodos: un punto por periodo, con halo blanco para que el cruce se lea.",
"mark": {
"type": "point",
"filled": true,
"stroke": "#FFFFFF",
"strokeWidth": 2.5
},
"encoding": {
"size": {
"condition": {
"param": "serieHover",
"empty": false,
"value": 170
},
"value": 110
},
"tooltip": [
{
"field": "__0__",
"type": "nominal",
"title": "Subcategoría"
},
{
"field": "__1__",
"type": "temporal",
"title": "Periodo",
"format": "%b %Y"
},
{
"field": "ranking",
"type": "quantitative",
"title": "Puesto"
},
{
"field": "__2__",
"type": "quantitative",
"title": "Ventas",
"format": ",.0f"
}
]
}
},
{
"description": "Etiqueta al final de cada línea: sustituye a la leyenda, sin saltos de vista.",
"transform": [
{
"filter": "datum.pasoFinal === 1"
}
],
"mark": {
"type": "text",
"align": "left",
"baseline": "middle",
"dx": 14,
"fontSize": 12.5,
"fontWeight": 600
},
"encoding": {
"text": {
"field": "__0__",
"type": "nominal"
}
}
}
]
}