This would be exactly what we would need! How did you find this so quickly? haha
So I assume this chart is not natively supported then, but yes, that would be amazing to have in the library!
I spend too much time on the computer … but you can make a bump chart in plotly just isn’t as elegant as the prior components in those links i included. Typed up a quick bump chart example using plotly express:
import plotly.express as px
import pandas as pd
import numpy as np
# Create sample data
np.random.seed(42)
dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='M')
categories = ['A', 'B', 'C', 'D', 'E']
data = []
for category in categories:
values = np.random.randint(1, 101, size=len(dates))
for date, value in zip(dates, values):
data.append({'Date': date, 'Category': category, 'Value': value})
df = pd.DataFrame(data)
# Calculate ranks
df['Rank'] = df.groupby('Date')['Value'].rank(method='dense', ascending=False)
# Create the bump chart
fig = px.line(df, x='Date', y='Rank', color='Category', line_shape='linear',
markers=True, text='Rank', title='Plotly Bump Chart')
# Customize the layout
fig.update_traces(textposition='top center')
fig.update_yaxes(autorange="reversed", title='Rank', tickmode='linear', tick0=1, dtick=1)
fig.update_xaxes(title='Date')
fig.update_layout(
height=600,
width=1000,
legend_title='Category',
hovermode='x unified',
)
# Show the plot
fig.show()
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from collections import defaultdict
# Create sample data with smoother transitions
np.random.seed(42)
dates = pd.date_range(start='2000-01-01', end='2015-01-01', freq='YS')
categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
# Initialize ranks
initial_ranks = np.random.permutation(len(categories)) + 1
rank_dict = defaultdict(list)
for i, category in enumerate(categories):
rank_dict[category].append(initial_ranks[i])
# Generate ranks for subsequent years
for _ in range(1, len(dates)):
current_ranks = [rank_dict[cat][-1] for cat in categories]
new_ranks = []
for rank in current_ranks:
# Possible moves: -1 (up), 0 (stay), 1 (down)
move = np.random.choice([-1, 0, 1])
new_rank = max(1, min(len(categories), rank + move))
while new_rank in new_ranks:
# Ensure no duplicate ranks
new_rank = max(1, min(len(categories), new_rank + np.random.choice([-1, 1])))
new_ranks.append(new_rank)
# Assign new ranks to categories
for category, new_rank in zip(categories, new_ranks):
rank_dict[category].append(new_rank)
# Create DataFrame
data = []
for category in categories:
for date, rank in zip(dates, rank_dict[category]):
data.append({'Date': date, 'Category': category, 'Rank': rank})
df = pd.DataFrame(data)
# Create the bump chart
fig = go.Figure()
colors = ['#FFFFFF', '#D2B48C', '#DDA0DD', '#FFFF00', '#87CEEB',
'#FF6347', '#C0C0C0', '#98FB98', '#FFA07A', '#00CED1']
for category, color in zip(categories, colors):
df_cat = df[df['Category'] == category]
fig.add_trace(go.Scatter(
x=df_cat['Date'],
y=df_cat['Rank'],
mode='lines+markers+text',
name=category,
line=dict(color=color, width=4),
marker=dict(color=color, size=20),
text=df_cat['Rank'],
textposition='middle right',
textfont=dict(color=color, size=14),
))
# Customize the layout
fig.update_layout(
title={
'text': 'World GDP<br>2000 - 2015',
'font': {'size': 24, 'color': 'white'},
'x': 0.5,
'y': 0.95,
},
plot_bgcolor='#333333',
paper_bgcolor='#333333',
height=800,
width=1200,
showlegend=False,
xaxis=dict(
showgrid=True,
gridcolor='#555555',
tickmode='array',
tickvals=dates,
ticktext=[d.year for d in dates],
tickfont=dict(color='white', size=12),
tickangle=0,
),
yaxis=dict(
showgrid=True,
gridcolor='#555555',
range=[0.5, 10.5],
dtick=1,
tickfont=dict(color='white', size=12),
),
font=dict(color='white'),
)
# Show the plot
fig.show()
this one is a closer example to the articles picture.
I guess this makes two of us
Wow!!! Honestly, I think it looks pretty good. Sure, the curved lines in the other examples are fancy, but your line chart already gets us really far. If we could make the dots a bit bigger, it would look even nicer. But even as it is, it already works! I just bookmarked it. Will try to use it soon!
Can you use the Sankey chart for this?
Oh wow, this is amazing!! I’m so impressed with how quickly you went from 0 to 100!
You could make the grid lines a bit more subtle by adding some opacity, so the chart stands out more. But that’s just a small detail—the rest is perfect!
Yes you can:
and it looks pretty

import plotly.graph_objects as go
import pandas as pd
import numpy as np
from collections import defaultdict
# Create sample data with smoother transitions
np.random.seed(42)
years = list(range(2000, 2016))
categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
# Initialize ranks
initial_ranks = np.random.permutation(len(categories)) + 1
rank_dict = defaultdict(list)
for i, category in enumerate(categories):
rank_dict[category].append(initial_ranks[i])
# Generate ranks for subsequent years
for _ in range(1, len(years)):
current_ranks = [rank_dict[cat][-1] for cat in categories]
new_ranks = []
for rank in current_ranks:
move = np.random.choice([-1, 0, 1])
new_rank = max(1, min(len(categories), rank + move))
while new_rank in new_ranks:
new_rank = max(1, min(len(categories), new_rank + np.random.choice([-1, 1])))
new_ranks.append(new_rank)
for category, new_rank in zip(categories, new_ranks):
rank_dict[category].append(new_rank)
# Prepare data for Sankey diagram
source = []
target = []
value = []
label = []
color_index = []
for year_idx in range(len(years) - 1):
for cat_idx, category in enumerate(categories):
source.append(year_idx * len(categories) + rank_dict[category][year_idx] - 1)
target.append((year_idx + 1) * len(categories) + rank_dict[category][year_idx + 1] - 1)
value.append(1)
color_index.append(cat_idx)
for year in years:
label.extend([f"{year} - {rank}" for rank in range(1, len(categories) + 1)])
# Create color scale
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98FB98',
'#DDA0DD', '#F0E68C', '#87CEFA', '#CD853F', '#B0C4DE']
# Create the Sankey diagram
fig = go.Figure(data=[go.Sankey(
node=dict(
pad=15,
thickness=20,
line=dict(color="black", width=0.5),
label=label,
color="lightgray"
),
link=dict(
source=source,
target=target,
value=value,
color=[colors[i] for i in color_index]
))])
# Update the layout
fig.update_layout(
title_text="World GDP Rankings 2000-2015",
font_size=10,
plot_bgcolor='#333333',
paper_bgcolor='#333333',
font_color='white',
height=800,
width=1200
)
fig.show()
Hi,
My iteration is an interactive dash app with stats over seasons and matches analysis.
Charts:
- Stats over season bar chart
- Stats over matches scatter plot
Filters:
- Select box for season
- Tabs for stat (points, wins, draws, losses, goals for, goals against, goal difference)
Deployment is pending
Thank you for the kind word @li.nguyen. I decided to use different background colors to show where the league changed the schedule from summer only to Fall/Spring. I half-expected the schedule change to have had a noticeable effect on attendance. Turns out, it did not, with the biggest attendance boost 5 years later after covid. In a sense the fact that the schedule change had no impact on attendance is a point worth noting, as it defies conventional wisdom.
One thing I really like about Figure-Friday is it keeps these skills sharp and helps me in my real job. I work as a semiconductor test & product engineering, and often have to annotate plots like this for non-technical worker and management.
Great question from @li.nguyen , would be great to share html plots and a screen shots of our examples. I will check out py.cafe.
This plot shows cumulative goal differential for 2023 of every team. The code I will post makes a plot like this every season. Goal differential is interesting because each team gets one point added for every goal scored, and one point subtracted for every goal given up. When tracking wins or points, teams get the same value for a close win as they do for a blow-out win. Here is the 2023 season. The annotated values after each team name are the season rank and final goal differential of that team. Most of the positions are as expected, with some exceptions.
Can someone please tell me the best way to post python code? When I do, it loses the formatting and indentation.
Wrap in 3 ` on top and 3 on bottom of code
hi @Mike_Purtell
Just like @PipInstallPython said, you should use backticks. You can insert them with the preformatted text button (CTRL+e) or you can do it manually:
@```
Code goes here. Just remove the @
signs.
@```
Thanks for submitting the Dash app, @Alfredo49 .
I’m eager to play around with the other tabs once it’s deployed.
My recommendation is to modify the colors used. In the bottom graph, it’s very hard to tell the difference between Southampton, Sunderland, Reading, and Sheffield.
I don’t think that the teams in the top graph need to be assigned colors. Leaving them all the same color would make reading the bar chart even easier, in my opinion.
This is beautiful, @PipInstallPython . I personally bookmarked this for future use.
I think this would be a great post on it’s own. Do you mind copy-pasting this post into a new topic called How to create a bump chart with Plotly
. That would allow it to be more searchable by search engines when people inquire about bump charts.
(I can technically separate this post into a new topic, but I’d like it to be here as well )
Hi all
So many great examples to learn from so far!
When I saw that this week’s dataset was football-related I got curious about the possibility of using club crests for markers. Thankfully, @RenaudLN showed it could be easily done in this example Link
The graph shows the correlation between a team’s goal difference and the total number of points gained at the end of the season. While the 2 variables will always be strongly positively correlated, it might be useful to explore how much this varies from season to season.
One useful feature of goal difference in Football is that it’s used as a tie-breaker when 2 teams end the season with the same point tally. Football fans would know that this is quite an uncommon occurrence. To my pleasant surprise, it turns out that this is exactly how last season’s winner was decided when Chelsea won the league on goal difference after ending the season with the same number of points as Manchester City
Sources
- Code available at: Link
- Most of the clubs crests were readily available to me thanks to Luuk Hopman’s Repo
Nicely done, @mo.elauzei .
I also spotted that tie breaker for first place by using goal difference. I find it more interesting to use games-played-against-each-other as the tie breaker.
For example, if Manchester and Arsnel were tied for first place, we could look at who did better when they played each other twice during that season.
I believe if GD is the same, then the tie break would go to who won. But in a two game, if they split the games, you’d be back to who won the last game I think.
You’re right the pallete is kind of monocromatic, but i fix it including a more contrasting palette.
Also I deployed on render.
https://womenfootballdashboard.onrender.com