Don't know if there's a ready-made site for stuff like that, but it's not hard to do.
Here's a quick and dirty AI generated piece of trash code as a proof of concept:
# sankey_hiring_funnel_direct.py
# Requires: plotly
# Install: pip install plotly
import plotly.graph_objects as go
# Node labels (unique)
labels = [
"Network", # 0
"Hiring.cafe", # 1
"Abandoned Lead", # 2
"Applied", # 3
"Rejected", # 4
"No Response", # 5
"Screener", # 6
"Rejected by Screen", # 7
"Full Round", # 8
"Rejected by Panel", # 9
"Offer", #10
"Accepted", #11
"Declined" #12
]
# Colors for the two source groups (consistent)
network_color = "rgba(31,119,180,0.8)" # blue-ish
hiring_color = "rgba(255,127,14,0.8)" # orange-ish
sources = []
targets = []
values = []
link_colors = []
def add_link(src_idx, tgt_idx, val, color):
sources.append(src_idx)
targets.append(tgt_idx)
values.append(val)
link_colors.append(color)
# Direct flows from Network and Hiring.cafe into Abandoned Lead and Applied
add_link(0, 2, 1, network_color) # Network -> Abandoned Lead (1)
add_link(1, 2, 58, hiring_color) # Hiring.cafe -> Abandoned Lead (58)
add_link(0, 3, 11, network_color) # Network -> Applied (11)
add_link(1, 3, 70, hiring_color) # Hiring.cafe -> Applied (70)
# Applied -> Rejected, No Response, Screener (split by original group)
add_link(3, 4, 5, network_color) # Applied -> Rejected (network 5)
add_link(3, 4, 40, hiring_color) # Applied -> Rejected (hiring 40)
add_link(3, 5, 3, network_color) # Applied -> No Response (network 3)
add_link(3, 5, 15, hiring_color) # Applied -> No Response (hiring 15)
add_link(3, 6, 4, network_color) # Applied -> Screener (network 4)
add_link(3, 6, 15, hiring_color) # Applied -> Screener (hiring 15)
# Screener -> Rejected by Screen, Full Round
add_link(6, 7, 1, network_color) # Screener -> Rejected by Screen (network 1)
add_link(6, 7, 5, hiring_color) # Screener -> Rejected by Screen (hiring 5)
add_link(6, 8, 3, network_color) # Screener -> Full Round (network 3)
add_link(6, 8, 10, hiring_color) # Screener -> Full Round (hiring 10)
# Full Round -> Rejected by Panel, Offer
add_link(8, 9, 1, network_color) # Full Round -> Rejected by Panel (network 1)
add_link(8, 9, 7, hiring_color) # Full Round -> Rejected by Panel (hiring 7)
add_link(8, 10, 2, network_color) # Full Round -> Offer (network 2)
add_link(8, 10, 3, hiring_color) # Full Round -> Offer (hiring 3)
# Offer -> Accepted, Declined
add_link(10, 11, 1, network_color) # Offer -> Accepted (network 1)
add_link(10, 12, 1, network_color) # Offer -> Declined (network 1)
add_link(10, 12, 3, hiring_color) # Offer -> Declined (hiring 3)
# Sanity check
assert len(sources) == len(targets) == len(values) == len(link_colors)
# Node colors (visual guidance)
node_colors = [
"rgba(31,119,180,0.9)", # Network
"rgba(255,127,14,0.9)", # Hiring.cafe
"rgba(220,220,220,0.9)", # Abandoned Lead
"rgba(200,200,200,0.9)", # Applied
"rgba(220,180,180,0.9)", # Rejected
"rgba(200,200,220,0.9)", # No Response
"rgba(200,220,200,0.9)", # Screener
"rgba(255,200,200,0.9)", # Rejected by Screen
"rgba(210,210,255,0.9)", # Full Round
"rgba(240,200,220,0.9)", # Rejected by Panel
"rgba(200,255,200,0.9)", # Offer
"rgba(140,255,140,0.9)", # Accepted
"rgba(255,140,140,0.9)" # Declined
]
fig = go.Figure(data=[go.Sankey(
node=dict(
pad=18,
thickness=18,
line=dict(color="black", width=0.5),
label=labels,
color=node_colors
),
link=dict(
source=sources,
target=targets,
value=values,
color=link_colors,
hovertemplate='%{source.label} → %{target.label}: %{value}<extra></extra>'
)
)])
fig.update_layout(
title_text="Hiring funnel Sankey — direct source flows (no Leads node)",
font_size=12,
height=700,
margin=dict(l=20, r=20, t=60, b=20)
)
fig.show()
# To save as interactive HTML:
# fig.write_html("sankey_hiring_funnel_direct.html", include_plotlyjs='cdn')
Couldn't be bothered to write this by hand for just an online comment. There's enough that can be improved with this, but I think it's ok to show how it can be done quite easily.