Docs / Build Workflow

Visualization — sankey

Cuándo usar sankey

Sankey es la opción correcta para preguntas de flujo y atribución — cómo se mueve el volumen de un conjunto de categorías a otro y dónde se ramifica o converge: traffic source → categoría de producto → estado de orden, presupuesto → equipo → resultado, movimiento etapa a etapa. Cada fila del query es un link: un nodo de origen, un nodo de destino y la magnitud que fluye entre ellos.

Usa funnel cuando el proceso es una cascada ordenada única sin ramificación. Usa bar para una comparación categórica simple, o heatmap para una grilla de intensidad bidimensional.

Mapping

Sankey toma una lista de aristas (edge list) — una fila por link.

  • mapping.source — requerido. Campo con el nombre del nodo de origen del link.
  • mapping.target — requerido. Campo con el nombre del nodo de destino del link.
  • mapping.value — requerido. Campo numérico con la magnitud del link (el ancho de la cinta).
mapping:
  source: source
  target: target
  value: order_count

Los nodos se infieren de la unión de los valores de source y target — no los declaras. Los pares (source, target) duplicados se suman, los self-loops (source == target) se descartan, y los valores no positivos se ignoran.

Cómo darle forma al dato: una fila = un link

Un sankey normalmente abarca varias etapas, pero la entrada es una edge list plana. Armala en el modelo: produce un query por cada par de etapas adyacentes y componelos con UNION ALL — el pivot va en el modelo, donde es revisable, no en el chart. Para un source construido desde SQL crudo:

WITH base AS (
  SELECT
    u.traffic_source AS traffic_source,
    p.category       AS category,
    oi.status        AS order_status
  FROM `bigquery-public-data.thelook_ecommerce.order_items` AS oi
  JOIN `bigquery-public-data.thelook_ecommerce.users`    AS u ON oi.user_id = u.id
  JOIN `bigquery-public-data.thelook_ecommerce.products` AS p ON oi.product_id = p.id
)
SELECT traffic_source AS source, category AS target, COUNT(*) AS order_count
FROM base GROUP BY 1, 2
UNION ALL
SELECT category AS source, order_status AS target, COUNT(*) AS order_count
FROM base GROUP BY 1, 2

Cada etapa adicional agrega un bloque SELECT … UNION ALL más. El resultado tiene exactamente las tres columnas que espera el mapping: source, target, order_count.

El SQL vive dentro del modelo Malloy que referencia el query del viz — el archivo completo lo envuelve en un source de SQL crudo:

# models/traffic_attribution.malloy
##! experimental.parameters

source: traffic_attribution() is ecommerce.sql("""
  -- the edge-list SQL above, verbatim
""") extend {
  view: edges is { select: * }
}

Atajos de chart

El bloque chart es tipado y cerrado.

  • chart.orientation"horizontal" (default) o "vertical". Dirección en que viaja el flujo.
  • chart.node_align"justify" (default), "left" o "right". Cómo se alinean los nodos en el diagrama.
  • chart.node_width — ancho en píxeles de cada barra de nodo.
  • chart.node_gap — espacio en píxeles entre nodos de la misma columna.
  • chart.layout_iterations — número de iteraciones de posicionamiento (más = layout más asentado).
  • chart.draggable — booleano; permite al lector arrastrar nodos para reordenar.
  • chart.show_value_labels — booleano, default off. Prende los labels de nodo. Un sankey etiqueta todos los nodos, así que dejalo off salvo que el diagrama sea chico. Estilo con chart.label.
  • chart.height — alto en píxeles del contenedor del viz.
  • chart.cross_filter — booleano, default true. Siempre que no sea explícitamente false, el schema exige chart.cross_filter_emit junto a cualquier bloque chart.
  • chart.cross_filter_emit"source" o "target". Qué rol de nodo emite el click como filtro.

Bloques de estilo pass-through: chart.label (estilo del label de nodo), chart.line_style (color, opacity, curveness del link) y chart.tooltip.

Dos cosas que sankey deliberadamente no tiene: no hay legend (una única serie de flujo no tiene nada que alternar) ni bloques de eje (un sankey no tiene ejes).

Los links necesitan dirección: nada de flujo circular

Un sankey tiene que ser un grafo dirigido acíclico — el flujo avanza por las etapas y nunca vuelve atrás. Si el dato contiene un ciclo (por ejemplo A → B, B → C, C → A), el diagrama no se puede posicionar y el viz muestra un mensaje claro: "Sankey data contains a circular flow." Es una señal sobre el dato, no un resultado vacío — rastrea el loop en el query (normalmente una etapa cuyos valores reaparecen como nombres de nodo de una etapa anterior) y rompelo.

Comportamiento de cross-filter

Sankey participa del cross-filter en el click sobre nodos; los clicks sobre los links entre nodos se ignoran. Dentro de un dashboard:

  • Emite — al hacer click en un nodo agrega un pill para el campo nombrado por chart.cross_filter_emit (source o target) con el nombre del nodo clickeado, siempre que ese campo esté declarado como parámetro en una firma de source del archivo de model propio de esta viz.
  • Consume — los pills y filtros de dashboard seteados en otro lado se vuelven parámetros en la siguiente corrida; el query del sankey se re-ejecuta y los flujos se recalculan.
  • Opt-out — pon chart.cross_filter: false para suprimir la emisión de clicks pero seguir consumiendo pills.

Ejemplo

Atribución de tráfico en dos etapas, anclada en el dataset público thelook_ecommerce:

id: traffic_attribution_sankey
title: Traffic Attribution Flow
query: "models/traffic_attribution.malloy::edges"
type: sankey
mapping:
  source: source
  target: target
  value: order_count
chart:
  height: 420
  orientation: horizontal
  node_align: justify
  show_value_labels: true
  cross_filter: true
  cross_filter_emit: target
format:
  order_count: "#,##0"
published: true

Un diagrama de solo lectura (sin cross-filter), en orientación vertical:

chart:
  height: 320
  orientation: vertical
  cross_filter: false

Notas de diseño

  • Los flujos forman un grafo dirigido acíclico — arma la query de forma que ningún link apunte de vuelta a un stage anterior.
  • Un link renderiza cuando su value es positivo, ambos nombres de nodo son no-vacíos, y source ≠ target.
  • Los nombres de nodo se mergean entre stages: el mismo label apareciendo como categoría y como status se vuelve un solo nodo. Dale a los stages nombres distintos en la query salvo que el merge sea intencional.
  • En diagramas densos, deja chart.show_value_labels apagado, o aumenta chart.height / chart.node_gap.