cities = pd.read_csv('data/cities.csv')
links = pd.read_csv('data/links.csv')
# aggregate identical directed pairs to get a simple frequency for each OD link
edges = links.groupby(['source', 'target']).size().reset_index(name='count')
# bring in coordinates and labels for source and target endpoints
src = cities[['id', 'lng', 'lat', 'name']].rename(columns={
'id': 'src_id', 'lng': 'src_lng', 'lat': 'src_lat', 'name': 'src_name'
})
tgt = cities[['id', 'lng', 'lat', 'name']].rename(columns={
'id': 'tgt_id', 'lng': 'tgt_lng', 'lat': 'tgt_lat', 'name': 'tgt_name'
})
edges = edges.merge(src, left_on='source', right_on='src_id', how='left')
edges = edges.merge(tgt, left_on='target', right_on='tgt_id', how='left')
# drop any pairs that lack coordinate information
edges = edges.dropna(subset=['src_lng', 'src_lat', 'tgt_lng', 'tgt_lat'])
# pick top-N links to keep the prototype readable
top_n = min(50, len(edges))
top = edges.nlargest(top_n, 'count').reset_index(drop=True)
fig = go.Figure()
# draw each top flow as a directed line (simple straight segment) with thickness by frequency
for i, row in top.iterrows():
color = PAL[i % len(PAL)]
width = 1.0 + np.log1p(row['count']) * 2.0
fig.add_trace(
go.Scatter(
x=[row['src_lng'], row['tgt_lng']],
y=[row['src_lat'], row['tgt_lat']],
mode='lines',
line=dict(color=color, width=width),
hoverinfo='text',
text=(f"{row['src_name']} → {row['tgt_name']}<br>count: {int(row['count'])}"),
showlegend=False
)
)
# add small endpoint markers so viewers can orient to the cities involved
src_nodes = top[['src_lng', 'src_lat', 'src_name']].rename(columns={'src_lng': 'lng', 'src_lat': 'lat', 'src_name': 'name'})
tgt_nodes = top[['tgt_lng', 'tgt_lat', 'tgt_name']].rename(columns={'tgt_lng': 'lng', 'tgt_lat': 'lat', 'tgt_name': 'name'})
nodes = pd.concat([src_nodes, tgt_nodes], ignore_index=True).drop_duplicates().dropna(subset=['lng', 'lat'])
fig.add_trace(
go.Scatter(
x=nodes['lng'],
y=nodes['lat'],
mode='markers',
marker=dict(size=6, color=PAL[1], opacity=0.85),
text=nodes['name'],
hoverinfo='text',
showlegend=False
)
)
fig.update_layout(
**THEME,
xaxis=dict(title='Longitude', tickformat=",.2f"),
yaxis=dict(title='Latitude', tickformat=",.2f"),
)
apply_mm_layout(
fig,
"Flow-first OD prototype — city-to-city links",
subtitle=(
"Top 50 directed links from the sample OD graph (5,470 nodes / 10,596 links). "
"Line thickness encodes frequency; hover for details."
),
legend_position='right',
n_legend_items=0,
)
add_source(fig)
assert_no_title_overlap(fig)
fig.show()
try:
fig.write_image('chart-1.png', width=1200, height=520, scale=2)
except Exception:
pass