📊

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.

Vega-Lite bump avanzado ✓ Cross-filter ✓ Tooltip ✓ Context menu ✓ Selección

Vista previa en vivo

Render con datos de muestra · Vega/Vega-Lite

El 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.

Cargando vista previa…

Vista previa renderizada con el runtime de Deneb · MIT © Daniel Marsh-Patrick

🔽 Descargar .pbix 🔽 Descargar spec .json

🧩 Campos que espera

  1. __0__ SubCategoryName texto
  2. __1__ YearMonth texto
  3. __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

  1. Agrega el visual Deneb a tu reporte e ingresa a su editor.
  2. Arrastra tus campos a Values en este orden: SubCategoryName , YearMonth , Total Sales .
  3. Pega el spec (botón «Copiar spec» arriba) en el editor JSON de Deneb.
  4. Mapea tus columnas y ajusta a tu gusto. La interactividad nativa ya viene configurada.
Ver el spec Vega-Lite completo
spec.vega-lite.json
{
  "$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"
        }
      }
    }
  ]
}
¡Contáctame!