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.
Usá funnel cuando el proceso es una cascada ordenada única sin ramificación. Usá bar para una comparación categórica simple, o heatmap para una grilla de intensidad bidimensional.
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 declarás. Los pares (source, target) duplicados se suman, los self-loops (source == target) se descartan, y los valores no positivos se ignoran.
Un sankey normalmente abarca varias etapas, pero la entrada es una edge list plana. Armala en el modelo: producí 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 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. Si lo activás, tenés que setear también chart.cross_filter_emit.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).
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 — rastreá el loop en el query (normalmente una etapa cuyos valores reaparecen como nombres de nodo de una etapa anterior) y rompelo.
Sankey participa del cross-filter en el click sobre nodos; los clicks sobre los links entre nodos se ignoran. Dentro de un dashboard:
chart.cross_filter_emit (source o target) con el nombre del nodo clickeado, siempre que ese campo esté declarado como parámetro en al menos un modelo del dashboard.chart.cross_filter: false para suprimir la emisión de clicks pero seguir consumiendo pills.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
value no positivos, nombres de source/target en blanco, o self-loops, que se saltean.chart.cross_filter está activo con un cross_filter_emit, y solo si ese campo está declarado como parámetro en un modelo del dashboard. Los clicks sobre links nunca emiten.chart.show_value_labels en off, o aumentá chart.height / chart.node_gap.