I am trying to create an animated basketball shot chart. This is what the court looks like originally.
I am trying to add frames to animate a shot path going into the hoop.
When I press play to view the animation, the court turns into this.
Is there a way to fix this so the court doesnβt lose the traces it originally had? It seems like the bigger the batch is, the more traces are removed until it just becomes a plain court with nothing on it.
This is the code for the fig and the frames. I would appreciate any help. Thanks
#code for creating the court
court = CourtCoordinates(season)
court_lines_df = court.get_coordinates()
# st.write(court_lines_df)
fig = px.line_3d(
data_frame=court_lines_df,
x='x',
y='y',
z='z',
line_group='line_group_id',
color='line_group_id',
color_discrete_map={
'court': 'black',
'hoop': '#e47041',
'net': '#D3D3D3',
'backboard': 'gray',
'free_throw_line': 'black',
'hoop2':'#D3D3D3',
'free_throw_line2': 'black',
'free_throw_line3': 'black',
'free_throw_line4': 'black',
'free_throw_line5': 'black',
}
)
fig.update_traces(hovertemplate=None, hoverinfo='skip', showlegend=False)
fig.update_traces(line=dict(width=6))
fig.update_layout(
margin=dict(l=20, r=20, t=20, b=20),
scene_aspectmode="data",
height=600,
scene_camera=dict(
eye=dict(x=1.3, y=0, z=0.7)
),
scene=dict(
xaxis=dict(title='', showticklabels=False, showgrid=False),
yaxis=dict(title='', showticklabels=False, showgrid=False),
zaxis=dict(title='', showticklabels=False, showgrid=False, showbackground=False, backgroundcolor='#d2a679'),
),
showlegend=False,
legend=dict(
yanchor='top',
y=0.05,
x=0.2,
xanchor='left',
orientation='h',
font=dict(size=15, color='gray'),
bgcolor='rgba(0, 0, 0, 0)',
title='',
itemsizing='constant'
)
)
court_perimeter_bounds = np.array([[-250, 0, 0], [250, 0, 0], [250, 450, 0], [-250, 450, 0], [-250, 0, 0]])
# Extract x, y, and z values for the mesh
court_x = court_perimeter_bounds[:, 0]
court_y = court_perimeter_bounds[:, 1]
court_z = court_perimeter_bounds[:, 2]
# Add a square mesh to represent the court floor at z=0
fig.add_trace(go.Mesh3d(
x=court_x,
y=court_y,
z=court_z,
color='lightgray',
opacity=0.5,
name='Court Floor',
hoverinfo='none',
showscale=False
))
#code for adding frames
frames = []
num_points = 100
# Function to process shots in batches
def process_shots_in_batches(shotdf, batch_size=3):
# Split shotdf into batches of size batch_size
for batch_start in range(0, len(shotdf), batch_size):
batch_end = min(batch_start + batch_size, len(shotdf))
batch = shotdf[batch_start:batch_end]
yield batch
# Generate frames for each batch
for batch in process_shots_in_batches(shotdf, batch_size=2):
# For each batch, generate the frames with growing arcs for all shots in the batch
for t in np.linspace(0, 1, 10): # Adjust number of frames for smoothness
frame_data = []
# For each shot in the batch, generate its arc and add to the frame data
for i, row in batch.iterrows():
x1 = int(row['x'])
y1 = int(row['y'])
x2 = court.hoop_loc_x
y2 = court.hoop_loc_y
p2 = np.array([x1, y1, 0])
p1 = np.array([x2, y2, 100])
# Calculate the arc height based on shot distance
if row['shotDist'] > 3:
distance = calculate_distance(x1, y1, x2, y2)
if row['shotDist'] > 50:
h = np.random.randint(275, 325)
elif row['shotDist'] > 30:
h = np.random.randint(250, 300)
elif row['shotDist'] > 25:
h = np.random.randint(200, 250)
elif row['shotDist'] > 15:
h = np.random.randint(200, 250)
else:
h = np.random.randint(150, 200)
apex = np.array([0.5 * (x1 + x2), 0.5 * (y1 + y2), h])
x, y, z = generate_arc_points(p2, p1, apex, num_points)
# Calculate the portion of the arc to show for the current frame
frame_x = x[:int(len(x)*t)]
frame_y = y[:int(len(y)*t)]
frame_z = z[:int(len(z)*t)]
# Add the arc to the frame data
frame_data.append(go.Scatter3d(
x=frame_x, y=frame_y, z=frame_z,
mode='lines', line=dict(width=6, color='green')
))
# Add the frame with all 5 shots to the frames list
frames.append(go.Frame(
data=frame_data,
# name=f'Frame_batch_{batch[0]["x"]}_{int(t*100)}'
))
# Add an initial empty trace (just for layout)
fig.add_trace(go.Scatter3d(x=[], y=[], z=[]))
# Add frames to the figure
fig.frames = frames
fig.update_layout(
updatemenus=[
dict(type="buttons",
showactive=False,
buttons=[
dict(label="Play",
method="animate",
args=[None, {"frame": {"duration": 100, "redraw": True}, "fromcurrent": True}]),
dict(label="Pause",
method="animate",
args=[[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate"}])
])
],
# scene=dict(
# xaxis_title="X",
# yaxis_title="Y",
# zaxis_title="Z",
# aspectratio=dict(x=1, y=1, z=0.5)
# ),
# title="3D Shot Animation with Growing Lines",
)