2nzi commited on
Commit
1f7470c
·
verified ·
1 Parent(s): 70271c4
Files changed (6) hide show
  1. Pitch3D.py +369 -0
  2. app.py +266 -0
  3. config_location_player.py +77 -0
  4. functions.py +689 -0
  5. requirements.txt +7 -0
  6. stats_manager.py +154 -0
Pitch3D.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # import numpy as np
3
+ # import streamlit as st
4
+ # import plotly.express as px
5
+ # import plotly.graph_objects as go
6
+ # import pandas as pd
7
+
8
+ # # Soccer field dimensions (in meters)
9
+ # WIDTH = 80 # Width of the field
10
+ # LENGTH = 120 # Length of the field
11
+ # GOAL_HEIGHT = 2.44 # Standard goal height
12
+ # PENALTY_AREA_WIDTH = 40.3
13
+ # PENALTY_AREA_DEPTH = 16.5
14
+ # GOAL_AREA_WIDTH = 18.32
15
+ # GOAL_AREA_DEPTH = 5.5
16
+ # GOAL_WIDTH = 7.32 # Standard width of a soccer goal
17
+
18
+ # def create_field_df():
19
+ # """Create dataframes for different parts of the soccer field."""
20
+ # field_perimeter_bounds = [[0, 0, 0], [WIDTH, 0, 0], [WIDTH, LENGTH, 0], [0, LENGTH, 0], [0, 0, 0]]
21
+ # field_df = pd.DataFrame(field_perimeter_bounds, columns=['x', 'y', 'z'])
22
+ # field_df['line_group'] = 'field_perimeter'
23
+ # field_df['color'] = 'field'
24
+
25
+ # half_field_bounds = [[0, LENGTH / 2, 0], [WIDTH, LENGTH / 2, 0]]
26
+ # half_df = pd.DataFrame(half_field_bounds, columns=['x', 'y', 'z'])
27
+ # half_df['line_group'] = 'half_field'
28
+ # half_df['color'] = 'field'
29
+
30
+ # left_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, 0, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'left_penalty_area')
31
+ # right_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, LENGTH - PENALTY_AREA_DEPTH, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'right_penalty_area')
32
+ # left_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, 0, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'left_goal_area')
33
+ # right_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, LENGTH - GOAL_AREA_DEPTH, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'right_goal_area')
34
+
35
+ # return pd.concat([field_df, half_df, left_penalty_df, right_penalty_df, left_goal_df, right_goal_df])
36
+
37
+ # def create_rectangle_df(start_x, start_y, width, height, line_group):
38
+ # """Create a dataframe representing a rectangle on the field."""
39
+ # rectangle_bounds = [
40
+ # [start_x, start_y, 0],
41
+ # [start_x + width, start_y, 0],
42
+ # [start_x + width, start_y + height, 0],
43
+ # [start_x, start_y + height, 0],
44
+ # [start_x, start_y, 0]
45
+ # ]
46
+ # df = pd.DataFrame(rectangle_bounds, columns=['x', 'y', 'z'])
47
+ # df['line_group'] = line_group
48
+ # df['color'] = 'field'
49
+ # return df
50
+
51
+ # def create_center_circle():
52
+ # """Create a 3D line trace for the center circle."""
53
+ # theta = np.linspace(0, 2 * np.pi, 100)
54
+ # x = [(WIDTH / 2) + (9.15 * np.cos(t)) for t in theta]
55
+ # y = [(LENGTH / 2) + (9.15 * np.sin(t)) for t in theta]
56
+ # z = [0] * 100
57
+ # return go.Scatter3d(x=x, y=y, z=z, mode='lines', line=dict(color='white', width=2))
58
+
59
+ # def create_goalposts():
60
+ # """Create goalpost lines for both ends of the field."""
61
+ # goalposts = []
62
+
63
+ # goalposts.extend([
64
+ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
65
+ # go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
66
+ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4))
67
+ # ])
68
+
69
+ # goalposts.extend([
70
+ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
71
+ # go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
72
+ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4))
73
+ # ])
74
+
75
+ # return goalposts
76
+
77
+ # def generate_trajectory(start_point, end_point, peak_height=10, num_coords=100, trajectory_type='parabolic'):
78
+ # """Generate a trajectory (parabolic or linear) between start and end points."""
79
+ # shot_start_x, shot_start_y, start_z = start_point
80
+ # hoop_x, hoop_y, end_z = end_point
81
+
82
+ # if trajectory_type == 'parabolic':
83
+ # distance_x = hoop_x - shot_start_x
84
+ # a = -4 * peak_height / (distance_x ** 2)
85
+
86
+ # shot_path_coords = []
87
+ # for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)):
88
+ # z = a * (x - (shot_start_x + hoop_x) / 2) ** 2 + peak_height
89
+ # y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords)
90
+ # shot_path_coords.append([x, y, z])
91
+
92
+ # shot_path_coords[0][2] = start_z # Ensure start z is as specified
93
+ # shot_path_coords[-1][2] = end_z # Ensure end z is as specified
94
+
95
+ # elif trajectory_type == 'linear':
96
+ # shot_path_coords = []
97
+ # for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)):
98
+ # y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords)
99
+ # z = start_z + (end_z - start_z) * (index / num_coords)
100
+ # shot_path_coords.append([x, y, z])
101
+
102
+ # return pd.DataFrame(shot_path_coords, columns=['x', 'y', 'z'])
103
+
104
+ # def plot_trajectories(fig, start_points, end_points, trajectory_type='parabolic', peak_height=10, num_coords=100):
105
+ # """Plot multiple trajectories on the field."""
106
+ # for start_point, end_point in zip(start_points, end_points):
107
+ # trajectory_df = generate_trajectory(start_point, end_point, peak_height, num_coords, trajectory_type)
108
+ # fig.add_trace(go.Scatter3d(
109
+ # y=trajectory_df['x'],
110
+ # x=trajectory_df['y'],
111
+ # z=trajectory_df['z'],
112
+ # mode='lines',
113
+ # line=dict(color='red', width=4)
114
+ # ))
115
+ # fig.add_trace(go.Scatter3d(
116
+ # y=[trajectory_df['x'].iloc[0], trajectory_df['x'].iloc[-1]],
117
+ # x=[trajectory_df['y'].iloc[0], trajectory_df['y'].iloc[-1]],
118
+ # z=[trajectory_df['z'].iloc[0], trajectory_df['z'].iloc[-1]],
119
+ # mode='markers',
120
+ # marker=dict(size=3, color='red')
121
+ # ))
122
+
123
+ # def create_soccer_field_plot():
124
+ # """Create a 3D soccer field plot with trajectories."""
125
+ # field_df = create_field_df()
126
+
127
+ # fig = px.line_3d(
128
+ # data_frame=field_df, x='x', y='y', z='z', line_group='line_group', color='color',
129
+ # color_discrete_map={'field': '#FFFFFF'}
130
+ # )
131
+
132
+ # fig.add_trace(go.Mesh3d(
133
+ # x=[0, WIDTH, WIDTH, 0],
134
+ # y=[0, 0, LENGTH, LENGTH],
135
+ # z=[0, 0, 0, 0],
136
+ # color='rgb(0, 128, 0)',
137
+ # opacity=0.5
138
+ # ))
139
+
140
+ # fig.add_trace(create_center_circle())
141
+ # for goalpost in create_goalposts():
142
+ # fig.add_trace(goalpost)
143
+
144
+ # max_dimension = max(WIDTH, LENGTH, GOAL_HEIGHT)
145
+ # fig.update_layout(
146
+ # scene=dict(
147
+ # aspectmode="manual",
148
+ # aspectratio=dict(x=1, y=1, z=0.125),
149
+ # xaxis=dict(
150
+ # range=[-10, max_dimension + 10],
151
+ # visible=False
152
+ # ),
153
+ # yaxis=dict(
154
+ # range=[-10, max_dimension + 10],
155
+ # visible=False
156
+ # ),
157
+ # zaxis=dict(
158
+ # range=[0, 15],
159
+ # visible=False
160
+ # ),
161
+ # camera=dict(
162
+ # eye=dict(x=0.34, y=0, z=0.45)
163
+ # ),
164
+ # ),
165
+ # paper_bgcolor='rgba(0,0,0,0)',
166
+ # plot_bgcolor='rgba(0,0,0,0)',
167
+ # showlegend=False,
168
+ # )
169
+
170
+ # return fig
171
+
172
+ # def main_3D_pitch(start_points, end_points, trajectory_type='linear'):
173
+ # st.title("3D Soccer Field Trajectory Visualization")
174
+
175
+ # fig = create_soccer_field_plot()
176
+
177
+ # plot_trajectories(fig, start_points, end_points, trajectory_type=trajectory_type, peak_height=5, num_coords=100)
178
+
179
+ # st.plotly_chart(fig)
180
+ import numpy as np
181
+ import streamlit as st
182
+ import plotly.express as px
183
+ import plotly.graph_objects as go
184
+ import pandas as pd
185
+
186
+ # Soccer field dimensions (in meters)
187
+ WIDTH = 80 # Width of the field
188
+ LENGTH = 120 # Length of the field
189
+ GOAL_HEIGHT = 2.44 # Standard goal height
190
+ PENALTY_AREA_WIDTH = 40.3
191
+ PENALTY_AREA_DEPTH = 16.5
192
+ GOAL_AREA_WIDTH = 18.32
193
+ GOAL_AREA_DEPTH = 5.5
194
+ GOAL_WIDTH = 7.32 # Standard width of a soccer goal
195
+
196
+ def create_field_df():
197
+ """Create dataframes for different parts of the soccer field."""
198
+ field_perimeter_bounds = [[0, 0, 0], [WIDTH, 0, 0], [WIDTH, LENGTH, 0], [0, LENGTH, 0], [0, 0, 0]]
199
+ field_df = pd.DataFrame(field_perimeter_bounds, columns=['x', 'y', 'z'])
200
+ field_df['line_group'] = 'field_perimeter'
201
+ field_df['color'] = 'field'
202
+
203
+ half_field_bounds = [[0, LENGTH / 2, 0], [WIDTH, LENGTH / 2, 0]]
204
+ half_df = pd.DataFrame(half_field_bounds, columns=['x', 'y', 'z'])
205
+ half_df['line_group'] = 'half_field'
206
+ half_df['color'] = 'field'
207
+
208
+ left_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, 0, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'left_penalty_area')
209
+ right_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, LENGTH - PENALTY_AREA_DEPTH, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'right_penalty_area')
210
+ left_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, 0, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'left_goal_area')
211
+ right_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, LENGTH - GOAL_AREA_DEPTH, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'right_goal_area')
212
+
213
+ return pd.concat([field_df, half_df, left_penalty_df, right_penalty_df, left_goal_df, right_goal_df])
214
+
215
+ def create_rectangle_df(start_x, start_y, width, height, line_group):
216
+ """Create a dataframe representing a rectangle on the field."""
217
+ rectangle_bounds = [
218
+ [start_x, start_y, 0],
219
+ [start_x + width, start_y, 0],
220
+ [start_x + width, start_y + height, 0],
221
+ [start_x, start_y + height, 0],
222
+ [start_x, start_y, 0]
223
+ ]
224
+ df = pd.DataFrame(rectangle_bounds, columns=['x', 'y', 'z'])
225
+ df['line_group'] = line_group
226
+ df['color'] = 'field'
227
+ return df
228
+
229
+ def create_center_circle():
230
+ """Create a 3D line trace for the center circle."""
231
+ theta = np.linspace(0, 2 * np.pi, 100)
232
+ x = [(WIDTH / 2) + (9.15 * np.cos(t)) for t in theta]
233
+ y = [(LENGTH / 2) + (9.15 * np.sin(t)) for t in theta]
234
+ z = [0] * 100
235
+ return go.Scatter3d(x=x, y=y, z=z, mode='lines', line=dict(color='white', width=2))
236
+
237
+ def create_goalposts():
238
+ """Create goalpost lines for both ends of the field."""
239
+ goalposts = []
240
+
241
+ goalposts.extend([
242
+ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
243
+ go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
244
+ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4))
245
+ ])
246
+
247
+ goalposts.extend([
248
+ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
249
+ go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)),
250
+ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4))
251
+ ])
252
+
253
+ return goalposts
254
+
255
+ def create_goal_net(start_x, end_x, start_y, end_y, height, width):
256
+ """Create a 3D mesh for the goal net."""
257
+ x = [start_x, end_x, end_x, start_x, start_x]
258
+ y = [start_y, start_y, end_y, end_y, start_y]
259
+ z = [height, height, height, height, height]
260
+ return go.Mesh3d(x=x, y=y, z=z, opacity=0.1, color='blue')
261
+
262
+ def generate_trajectory(start_point, end_point, peak_height=10, num_coords=100, trajectory_type='parabolic'):
263
+ """Generate a trajectory (parabolic or linear) between start and end points."""
264
+ shot_start_x, shot_start_y, start_z = start_point
265
+ hoop_x, hoop_y, end_z = end_point
266
+
267
+ if trajectory_type == 'parabolic':
268
+ distance_x = hoop_x - shot_start_x
269
+ a = -4 * peak_height / (distance_x ** 2)
270
+
271
+ shot_path_coords = []
272
+ for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)):
273
+ z = a * (x - (shot_start_x + hoop_x) / 2) ** 2 + peak_height
274
+ y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords)
275
+ shot_path_coords.append([x, y, z])
276
+
277
+ shot_path_coords[0][2] = start_z # Ensure start z is as specified
278
+ shot_path_coords[-1][2] = end_z # Ensure end z is as specified
279
+
280
+ elif trajectory_type == 'linear':
281
+ shot_path_coords = []
282
+ for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)):
283
+ y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords)
284
+ z = start_z + (end_z - start_z) * (index / num_coords)
285
+ shot_path_coords.append([x, y, z])
286
+
287
+ return pd.DataFrame(shot_path_coords, columns=['x', 'y', 'z'])
288
+
289
+ def plot_trajectories(fig, start_points, end_points, trajectory_type='parabolic', peak_height=10, num_coords=100):
290
+ """Plot multiple trajectories on the field."""
291
+ for start_point, end_point in zip(start_points, end_points):
292
+ trajectory_df = generate_trajectory(start_point, end_point, peak_height, num_coords, trajectory_type)
293
+ fig.add_trace(go.Scatter3d(
294
+ y=trajectory_df['x'],
295
+ x=trajectory_df['y'],
296
+ z=trajectory_df['z'],
297
+ mode='lines',
298
+ line=dict(color='red', width=4)
299
+ ))
300
+ fig.add_trace(go.Scatter3d(
301
+ y=[trajectory_df['x'].iloc[0], trajectory_df['x'].iloc[-1]],
302
+ x=[trajectory_df['y'].iloc[0], trajectory_df['y'].iloc[-1]],
303
+ z=[trajectory_df['z'].iloc[0], trajectory_df['z'].iloc[-1]],
304
+ mode='markers',
305
+ marker=dict(size=3, color='red')
306
+ ))
307
+
308
+ def create_soccer_field_plot():
309
+ """Create a 3D soccer field plot with trajectories."""
310
+ field_df = create_field_df()
311
+
312
+ fig = px.line_3d(
313
+ data_frame=field_df, x='x', y='y', z='z', line_group='line_group', color='color',
314
+ color_discrete_map={'field': '#FFFFFF'}
315
+ )
316
+
317
+ fig.add_trace(go.Mesh3d(
318
+ x=[0, WIDTH, WIDTH, 0],
319
+ y=[0, 0, LENGTH, LENGTH],
320
+ z=[0, 0, 0, 0],
321
+ color='rgb(0, 128, 0)',
322
+ opacity=0.5
323
+ ))
324
+
325
+ fig.add_trace(create_center_circle())
326
+ for goalpost in create_goalposts():
327
+ fig.add_trace(goalpost)
328
+
329
+ # # Add goal nets
330
+ # fig.add_trace(create_goal_net((WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2), 0, -GOAL_HEIGHT, GOAL_HEIGHT, GOAL_WIDTH))
331
+ # fig.add_trace(create_goal_net((WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2), LENGTH, LENGTH + GOAL_HEIGHT, GOAL_HEIGHT, GOAL_WIDTH))
332
+
333
+ max_dimension = max(WIDTH, LENGTH, GOAL_HEIGHT)
334
+ fig.update_layout(
335
+ scene=dict(
336
+ aspectmode="manual",
337
+ aspectratio=dict(x=1, y=1, z=0.125),
338
+ xaxis=dict(
339
+ range=[-10, max_dimension + 10],
340
+ visible=False
341
+ ),
342
+ yaxis=dict(
343
+ range=[-10, max_dimension + 10],
344
+ visible=False
345
+ ),
346
+ zaxis=dict(
347
+ range=[0, 15],
348
+ visible=False
349
+ ),
350
+ camera=dict(
351
+ eye=dict(x=0.34, y=0, z=0.45)
352
+ ),
353
+ ),
354
+ paper_bgcolor='rgba(0,0,0,0)',
355
+ plot_bgcolor='rgba(0,0,0,0)',
356
+ showlegend=False,
357
+ )
358
+
359
+ return fig
360
+
361
+ def main_3D_pitch(start_points, end_points, trajectory_type='linear'):
362
+ st.title("3D Soccer Field Trajectory Visualization")
363
+
364
+ fig = create_soccer_field_plot()
365
+
366
+ plot_trajectories(fig, start_points, end_points, trajectory_type=trajectory_type, peak_height=5, num_coords=100)
367
+
368
+ st.plotly_chart(fig)
369
+
app.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from statsbombpy import sb
4
+ import config_location_player as clp
5
+ import functions
6
+ import Pitch3D
7
+ import json
8
+ from stats_manager import StatsManager
9
+
10
+ st.set_page_config(layout="wide")
11
+
12
+ st.markdown(
13
+ """
14
+ <style>
15
+ .centered-image {
16
+ display: block;
17
+ margin-left: auto;
18
+ margin-right: auto;
19
+ width: 20%; /* Adjust this percentage to resize the image */
20
+ }
21
+ .ban-image {
22
+ display: block;
23
+ margin-left: 0;
24
+ margin-right: 0;
25
+ width: 100%; /* Adjust this percentage to resize the image */
26
+ height: 50%; /* Adjust this percentage to resize the image */
27
+ }
28
+ .banner-image {
29
+ background-image: url('https://statsbomb.com/wp-content/uploads/2023/03/IconLockup_MediaPack-min.png');
30
+ background-size: cover;
31
+ background-position: center;
32
+ height: 300px; /* Adjust the height to your preference */
33
+ width: 100%;
34
+ margin: 0 auto;
35
+ margin-bottom: 100px;
36
+ }
37
+ </style>
38
+ """,
39
+ unsafe_allow_html=True
40
+ )
41
+
42
+ st.markdown('<div class="banner-image"></div>', unsafe_allow_html=True)
43
+
44
+
45
+ # st.markdown(
46
+ # '<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRsmWldT7_OE2kDhbehYcuTNHzItGFbeH5igw&s" class="centered-image"> <br> <br> <br>',
47
+ # unsafe_allow_html=True
48
+ # )
49
+
50
+
51
+
52
+ #PARTIE 1 : MATCH CHOOSEN
53
+ col1, col2 = st.columns([1, 3])
54
+ with col1:
55
+
56
+ competition = sb.competitions()
57
+
58
+ #Gender Choice
59
+ # on = st.toggle("Men" if st.session_state.get('competition_gender', 'women') == 'women' else "Women")
60
+ # competition_gender = 'men' if on else 'women'
61
+ # st.session_state.competition_gender = competition_gender
62
+ competition_gender = "male"
63
+ competition_gender_df = competition[competition['competition_gender']==competition_gender]
64
+
65
+
66
+
67
+ #Competition Choice
68
+ competitions = competition_gender_df['competition_name'].unique()
69
+ selected_competition = st.selectbox(
70
+ "Choose your competition",
71
+ competitions,
72
+ )
73
+ selected_competition_df = competition_gender_df[competition_gender_df['competition_name']==selected_competition]
74
+
75
+
76
+
77
+
78
+
79
+ #Season Choice
80
+ seasons = selected_competition_df['season_name'].unique()
81
+ selected_season = st.selectbox(
82
+ "Choose your season",
83
+ seasons,
84
+ )
85
+
86
+ competition_id = selected_competition_df[selected_competition_df['season_name']==selected_season]['competition_id'].iloc[0]
87
+ season_id = selected_competition_df[selected_competition_df['season_name']==selected_season]['season_id'].iloc[0]
88
+ matches = sb.matches(competition_id=competition_id, season_id=season_id)
89
+
90
+
91
+ #Season Choice
92
+ # teams = selected_competition_df[['home_team','away_team']].unique()
93
+ teams = pd.concat([matches['away_team'], matches['home_team']]).unique()
94
+ selected_team = st.selectbox(
95
+ "Choose your team",
96
+ teams,
97
+ )
98
+ one_team_matches = matches[(matches['home_team'] == selected_team) | (matches['away_team'] == selected_team)]
99
+ # matches = one_team_matches['match_id']
100
+ matches = one_team_matches['match_date']
101
+
102
+ selected_match = st.selectbox(
103
+ "Choose your match",
104
+ matches,
105
+ )
106
+ selected_one_team_matches = one_team_matches[one_team_matches['match_date']==selected_match]
107
+ match_id = selected_one_team_matches['match_id'].iloc[0]
108
+
109
+ home_team = selected_one_team_matches['home_team'].iloc[0]
110
+ home_score = selected_one_team_matches['home_score'].iloc[0]
111
+ home_color = "#0B8494"
112
+
113
+ away_team = selected_one_team_matches['away_team'].iloc[0]
114
+ away_score = selected_one_team_matches['away_score'].iloc[0]
115
+ away_color = "#F05A7E"
116
+
117
+ home_lineups = sb.lineups(match_id=match_id)[home_team]
118
+ away_lineups = sb.lineups(match_id=match_id)[away_team]
119
+
120
+ events = sb.events(match_id=match_id)
121
+ home_tactic_formation = events['tactics'].iloc[0]['formation']
122
+ away_tactic_formation = events['tactics'].iloc[1]['formation']
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ #PARTIE 2 : DASHBOARD
131
+ with col2:
132
+
133
+ # st.write(events.filter(regex='^bad_behaviour_card|^team$'))
134
+
135
+ # Créer un gestionnaire de statistiques
136
+ stats_manager = StatsManager(events)
137
+
138
+ # Appel des fonctions via le gestionnaire
139
+ home_possession, away_possession = stats_manager.get_possession()
140
+ home_xg, away_xg = stats_manager.get_total_xg()
141
+ home_shots, away_shots = stats_manager.get_total_shots()
142
+ home_off_target, away_off_target = stats_manager.get_total_shots_off_target()
143
+ home_on_target, away_on_target = stats_manager.get_total_shots_on_target()
144
+ home_passes, away_passes = stats_manager.get_total_passes()
145
+ home_successful_passes, away_successful_passes = stats_manager.get_successful_passes()
146
+ home_corners, away_corners = stats_manager.get_total_corners()
147
+ home_fouls, away_fouls = stats_manager.get_total_fouls()
148
+ home_yellow_cards, away_yellow_cards = stats_manager.get_total_yellow_cards()
149
+ home_red_cards, away_red_cards = stats_manager.get_total_red_cards()
150
+
151
+ # Créer la liste des scores, en excluant ceux qui ne peuvent pas être calculés
152
+ categories_scores = [
153
+ {"catégorie": "Possession de balle (%)", "Home Team": home_possession, "Away Team": away_possession},
154
+ {"catégorie": "xG (Buts attendus)", "Home Team": home_xg, "Away Team": away_xg},
155
+ {"catégorie": "Tirs", "Home Team": home_shots, "Away Team": away_shots},
156
+ {"catégorie": "Tirs cadrés", "Home Team": home_on_target, "Away Team": away_on_target},
157
+ {"catégorie": "Tirs non cadrés", "Home Team": home_off_target, "Away Team": away_off_target},
158
+ {"catégorie": "Passes", "Home Team": home_passes, "Away Team": away_passes},
159
+ {"catégorie": "Passes réussies", "Home Team": home_successful_passes, "Away Team": away_successful_passes},
160
+ {"catégorie": "Corners", "Home Team": home_corners, "Away Team": away_corners},
161
+ {"catégorie": "Fautes", "Home Team": home_fouls, "Away Team": away_fouls},
162
+ {"catégorie": "Cartons Jaunes", "Home Team": home_yellow_cards, "Away Team": away_yellow_cards},
163
+ {"catégorie": "Cartons Rouges", "Home Team": home_red_cards, "Away Team": away_red_cards},
164
+ ]
165
+
166
+ # Filtrer les catégories valides
167
+ categories_scores = [entry for entry in categories_scores if entry is not None]
168
+
169
+ # Extraire les catégories et scores
170
+ categories = [entry['catégorie'] for entry in categories_scores]
171
+ home_scores = [entry['Home Team'] for entry in categories_scores]
172
+ away_scores = [entry['Away Team'] for entry in categories_scores]
173
+
174
+ # Afficher le graphique
175
+ functions.display_normalized_scores(home_scores, away_scores, categories, home_color=home_color, away_color=away_color)
176
+
177
+
178
+
179
+ # st.image('img/logo_stade.png', width=200)
180
+ pitch_color='#d2d2d2',
181
+ st.markdown(
182
+ f"""
183
+ <div style='text-align: center;'>
184
+ <span style='color: {home_color}; margin-right: 30px;'>{home_team} {home_score}</span>
185
+ <span style='margin-right: 30px;'>-</span>
186
+ <span style='color: {away_color};'>{away_score} {away_team}</span>
187
+ </div>
188
+ <div style='text-align: center;'>
189
+ <span style='color: {pitch_color}; margin-right: 30px;'>{home_tactic_formation}</span>
190
+ <span style='margin-right: 30px;'> </span>
191
+ <span style='color: {pitch_color};'>{away_tactic_formation}</span>
192
+ </div>
193
+ """,
194
+ unsafe_allow_html=True
195
+ )
196
+
197
+
198
+ col21, col22 = st.columns([1,1])
199
+
200
+ position_id_to_coordinates_home = clp.initial_player_position_allpitch_home
201
+ position_id_to_coordinates_away = clp.initial_player_position_allpitch_away
202
+ # functions.display_player_names_and_positions_twoTeam(home_lineups, away_lineups, position_id_to_coordinates_home, position_id_to_coordinates_away)
203
+
204
+ with open('data/club.json', encoding='utf-8') as f:
205
+ images_data = json.load(f)
206
+
207
+ # Integrate club logo
208
+ home_team_image = functions.get_best_match_image(home_team, images_data)
209
+ away_team_image = functions.get_best_match_image(away_team, images_data)
210
+
211
+
212
+ with col21:
213
+
214
+ # if home_team_image:
215
+ # st.image(home_team_image)
216
+
217
+ functions.display_player_names_and_positions_oneTeam(home_lineups, position_id_to_coordinates_home, home_color)
218
+
219
+ st.markdown(
220
+ f"""
221
+ <div style='text-align: center;'>
222
+ <span style='color: {home_color}; margin-right: 30px;'>{selected_one_team_matches['home_managers'].iloc[0]}</span>
223
+ </div>
224
+ """,
225
+ unsafe_allow_html=True
226
+ )
227
+
228
+ with col22:
229
+
230
+ # if away_team_image:
231
+ # st.image(away_team_image)
232
+
233
+ functions.display_player_names_and_positions_oneTeam(away_lineups, position_id_to_coordinates_away,away_color)
234
+
235
+ st.markdown(
236
+ f"""
237
+ <div style='text-align: center;'>
238
+ <span style='color: {away_color}; margin-right: 30px;'>{selected_one_team_matches['away_managers'].iloc[0]}</span>
239
+ </div>
240
+ """,
241
+ unsafe_allow_html=True
242
+ )
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+ def ensure_3d_coordinates(coord):
251
+ if len(coord) == 2:
252
+ return coord + [0]
253
+ return coord
254
+
255
+ shot_events = events.filter(regex='^(shot|location|team)').dropna(how='all')
256
+ shot_events_location = shot_events[shot_events['shot_end_location'].notna()]
257
+
258
+ shot_events_location['location'] = shot_events_location['location'].apply(ensure_3d_coordinates)
259
+ shot_events_location['shot_end_location'] = shot_events_location['shot_end_location'].apply(ensure_3d_coordinates)
260
+
261
+ start_points = shot_events_location['location'].tolist()
262
+ end_points = shot_events_location['shot_end_location'].tolist()
263
+
264
+ st.write(shot_events_location)
265
+
266
+ Pitch3D.main_3D_pitch(start_points,end_points)
config_location_player.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ pitch_width = 120
3
+ pitch_height = 80
4
+ nb_line = 7
5
+ space_line = (pitch_width/2)/7
6
+ x_line1 = 5
7
+
8
+ x_lines = [x_line1 + i * space_line for i in range(7)]
9
+ initial_player_position_middlepitch_home = {
10
+ 1: (x_lines[0], 40),
11
+ 2: (x_lines[1], 70),
12
+ 3: (x_lines[1], 55),
13
+ 4: (x_lines[1], 40),
14
+ 5: (x_lines[1], 25),
15
+ 6: (x_lines[1], 10),
16
+ 7: (x_lines[2], 70),
17
+ 8: (x_lines[2], 10),
18
+ 9: (x_lines[2], 55),
19
+ 10: (x_lines[2], 40),
20
+ 11: (x_lines[2], 25),
21
+ 12: (x_lines[3], 70),
22
+ 13: (x_lines[3], 55),
23
+ 14: (x_lines[3], 40),
24
+ 15: (x_lines[3], 25),
25
+ 16: (x_lines[3], 10),
26
+ 17: (x_lines[4], 70),
27
+ 18: (x_lines[4], 55),
28
+ 19: (x_lines[4], 40),
29
+ 20: (x_lines[4], 25),
30
+ 21: (x_lines[4], 10),
31
+ 22: (x_lines[6], 55),
32
+ 23: (x_lines[6], 40),
33
+ 24: (x_lines[6], 25),
34
+ 25: (x_lines[5], 40)
35
+ }
36
+
37
+ initial_player_position_middlepitch_away = {
38
+ position_id: (pitch_width - x, y)
39
+ for position_id, (x, y) in initial_player_position_middlepitch_home.items()
40
+ }
41
+
42
+
43
+
44
+ space_line = (pitch_width*0.8)/7
45
+ x_lines = [x_line1 + i * space_line for i in range(7)]
46
+ initial_player_position_allpitch_home = {
47
+ 1: (x_lines[0], 40),
48
+ 2: (x_lines[1], 70),
49
+ 3: (x_lines[1], 55),
50
+ 4: (x_lines[1], 40),
51
+ 5: (x_lines[1], 25),
52
+ 6: (x_lines[1], 10),
53
+ 7: (x_lines[2], 70),
54
+ 8: (x_lines[2], 10),
55
+ 9: (x_lines[2], 55),
56
+ 10: (x_lines[2], 40),
57
+ 11: (x_lines[2], 25),
58
+ 12: (x_lines[3], 70),
59
+ 13: (x_lines[3], 55),
60
+ 14: (x_lines[3], 40),
61
+ 15: (x_lines[3], 25),
62
+ 16: (x_lines[3], 10),
63
+ 17: (x_lines[4], 70),
64
+ 18: (x_lines[4], 55),
65
+ 19: (x_lines[4], 40),
66
+ 20: (x_lines[4], 25),
67
+ 21: (x_lines[4], 10),
68
+ 22: (x_lines[6], 55),
69
+ 23: (x_lines[6], 40),
70
+ 24: (x_lines[6], 25),
71
+ 25: (x_lines[5], 40)
72
+ }
73
+
74
+ initial_player_position_allpitch_away = {
75
+ position_id: (pitch_width - x, y)
76
+ for position_id, (x, y) in initial_player_position_allpitch_home.items()
77
+ }
functions.py ADDED
@@ -0,0 +1,689 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mplsoccer import Pitch
2
+ import streamlit as st
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from fuzzywuzzy import fuzz, process
6
+ import plotly.express as px
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import streamlit as st
10
+
11
+ def display_player_names_and_positions_middlePitch(home_lineups, away_lineups, position_id_to_coordinates_home, position_id_to_coordinates_away):
12
+ # Dictionaries to store coordinates and player names for both home and away teams
13
+ coordinates_dict_home = {}
14
+ player_names_dict_home = {}
15
+ coordinates_dict_away = {}
16
+ player_names_dict_away = {}
17
+
18
+ # Process home team lineup
19
+ for index, row in home_lineups.iterrows():
20
+ player_name = row['player_name']
21
+ position_info = row['positions'][0] if row['positions'] else {}
22
+ position_id = position_info.get('position_id', 'Unknown')
23
+
24
+ if position_id in position_id_to_coordinates_home:
25
+ coordinates_dict_home[position_id] = position_id_to_coordinates_home[position_id]
26
+ player_names_dict_home[position_id] = player_name
27
+
28
+ # Process away team lineup
29
+ for index, row in away_lineups.iterrows():
30
+ player_name = row['player_name']
31
+ position_info = row['positions'][0] if row['positions'] else {}
32
+ position_id = position_info.get('position_id', 'Unknown')
33
+
34
+ if position_id in position_id_to_coordinates_away:
35
+ coordinates_dict_away[position_id] = position_id_to_coordinates_away[position_id]
36
+ player_names_dict_away[position_id] = player_name
37
+
38
+ # Plotting the pitch
39
+ pitch = Pitch(
40
+ # pitch_color='#1f77b4',
41
+ pitch_color='#d2d2d2',
42
+ line_color='white',
43
+ stripe=False,
44
+ pitch_type='statsbomb'
45
+ )
46
+
47
+ fig, ax = pitch.draw()
48
+
49
+ # Plotting home team positions
50
+ if coordinates_dict_home:
51
+ x_coords_home, y_coords_home = zip(*coordinates_dict_home.values())
52
+ ax.scatter(x_coords_home, y_coords_home, color='red', s=300, edgecolors='white', zorder=3)
53
+
54
+ for position_id, (x, y) in coordinates_dict_home.items():
55
+ name = player_names_dict_home.get(position_id, "Unknown")
56
+ ax.text(x, y - 4, f'{name.split()[-1]}', color='black', ha='center', va='center', fontsize=12, zorder=6)
57
+
58
+ # Plotting away team positions
59
+ if coordinates_dict_away:
60
+ x_coords_away, y_coords_away = zip(*coordinates_dict_away.values())
61
+ ax.scatter(x_coords_away, y_coords_away, color='blue', s=300, edgecolors='white', zorder=3)
62
+
63
+ for position_id, (x, y) in coordinates_dict_away.items():
64
+ name = player_names_dict_away.get(position_id, "Unknown")
65
+ ax.text(x, y - 4, f'{name.split()[-1]}', color='black', ha='center', va='center', fontsize=12, zorder=6)
66
+
67
+ # Display the plot in Streamlit
68
+ st.pyplot(fig)
69
+
70
+
71
+
72
+ def display_player_names_and_positions_twoTeam(home_lineups, away_lineups, position_id_to_coordinates_home, position_id_to_coordinates_away):
73
+ # Dictionaries to store coordinates and player names for both home and away teams
74
+ coordinates_dict_home = {}
75
+ player_names_dict_home = {}
76
+ coordinates_dict_away = {}
77
+ player_names_dict_away = {}
78
+
79
+ # Process home team lineup
80
+ for index, row in home_lineups.iterrows():
81
+ player_name = row['player_name']
82
+ position_info = row['positions'][0] if row['positions'] else {}
83
+ position_id = position_info.get('position_id', 'Unknown')
84
+
85
+ if position_id in position_id_to_coordinates_home:
86
+ coordinates_dict_home[position_id] = position_id_to_coordinates_home[position_id]
87
+ player_names_dict_home[position_id] = player_name
88
+
89
+ # Process away team lineup
90
+ for index, row in away_lineups.iterrows():
91
+ player_name = row['player_name']
92
+ position_info = row['positions'][0] if row['positions'] else {}
93
+ position_id = position_info.get('position_id', 'Unknown')
94
+
95
+ if position_id in position_id_to_coordinates_away:
96
+ coordinates_dict_away[position_id] = position_id_to_coordinates_away[position_id]
97
+ player_names_dict_away[position_id] = player_name
98
+
99
+ # Plotting the pitch
100
+ pitch = Pitch(
101
+ # pitch_color='#1f77b4',
102
+ pitch_color='#d2d2d2',
103
+ line_color='white',
104
+ stripe=False,
105
+ pitch_type='statsbomb'
106
+ )
107
+
108
+ fig, ax = pitch.draw()
109
+
110
+ # Plotting home team positions
111
+ if coordinates_dict_home:
112
+ x_coords_home, y_coords_home = zip(*coordinates_dict_home.values())
113
+ ax.scatter(x_coords_home, y_coords_home, color='red', s=300, edgecolors='white', zorder=3)
114
+
115
+ for position_id, (x, y) in coordinates_dict_home.items():
116
+ name = player_names_dict_home.get(position_id, "Unknown")
117
+ ax.text(x, y - 4, f'{name.split()[-1]}', color='black', ha='center', va='center', fontsize=12, zorder=6)
118
+
119
+ # Plotting away team positions
120
+ if coordinates_dict_away:
121
+ x_coords_away, y_coords_away = zip(*coordinates_dict_away.values())
122
+ ax.scatter(x_coords_away, y_coords_away, color='blue', s=300, edgecolors='white', zorder=3)
123
+
124
+ for position_id, (x, y) in coordinates_dict_away.items():
125
+ name = player_names_dict_away.get(position_id, "Unknown")
126
+ ax.text(x, y - 4, f'{name.split()[-1]}', color='black', ha='center', va='center', fontsize=12, zorder=6)
127
+
128
+ # Display the plot in Streamlit
129
+ st.pyplot(fig)
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+ def display_player_names_and_positions_oneTeam(home_lineups, position_id_to_coordinates_home,color):
138
+ # Dictionaries to store coordinates and player names for the home team
139
+ coordinates_dict_home = {}
140
+ player_names_dict_home = {}
141
+
142
+ # Process home team lineup
143
+ for index, row in home_lineups.iterrows():
144
+ player_name = row['player_name']
145
+ position_info = row['positions'][0] if row['positions'] else {}
146
+ position_id = position_info.get('position_id', 'Unknown')
147
+
148
+ if position_id in position_id_to_coordinates_home:
149
+ coordinates_dict_home[position_id] = position_id_to_coordinates_home[position_id]
150
+ player_names_dict_home[position_id] = player_name
151
+
152
+ # Plotting the pitch
153
+ pitch = Pitch(
154
+ pitch_color='#d2d2d2',
155
+ line_color='white',
156
+ stripe=False,
157
+ pitch_type='statsbomb'
158
+ )
159
+
160
+ fig, ax = pitch.draw()
161
+
162
+ # Plotting home team positions
163
+ if coordinates_dict_home:
164
+ x_coords_home, y_coords_home = zip(*coordinates_dict_home.values())
165
+ ax.scatter(x_coords_home, y_coords_home, color=color, s=300, edgecolors='white', zorder=3)
166
+
167
+ for position_id, (x, y) in coordinates_dict_home.items():
168
+ name = player_names_dict_home.get(position_id, "Unknown")
169
+ ax.text(x, y - 4, f'{name.split()[-1]}', color='black', ha='center', va='center', fontsize=12, zorder=6)
170
+
171
+ # Display the plot in Streamlit
172
+ st.pyplot(fig)
173
+
174
+
175
+
176
+
177
+ # Function to find the best image match based on the team name
178
+ def get_best_match_image(team_name, images_data):
179
+ image_names = [image['image_name'] for image in images_data]
180
+ best_match, match_score = process.extractOne(team_name, image_names, scorer=fuzz.token_sort_ratio)
181
+ if match_score > 70: # Adjust the threshold as needed
182
+ for image in images_data:
183
+ if image['image_name'] == best_match:
184
+ return image['image_link']
185
+ return None
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+ class StadiumImageFetcher:
200
+ def __init__(self):
201
+ self.headers = {
202
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
203
+ }
204
+
205
+ def get_stadium_image_url(self, stadium_name):
206
+ # Formulate the Google Image search URL
207
+ query = stadium_name.replace(' ', '+')
208
+ url = f"https://www.google.com/search?hl=en&tbm=isch&q={query}"
209
+
210
+ # Send the request
211
+ response = requests.get(url, headers=self.headers)
212
+
213
+ # Parse the HTML content using BeautifulSoup
214
+ soup = BeautifulSoup(response.text, 'lxml')
215
+
216
+ # Find the first image result
217
+ images = soup.find_all('img')
218
+
219
+ if images and len(images) > 1: # The first image might be the Google logo, so skip it
220
+ return images[1]['src']
221
+ else:
222
+ return None
223
+
224
+ def display_stadium(self):
225
+ st.title("Stadium Image Display")
226
+
227
+ # Input the stadium name
228
+ stadium_name = st.text_input("Enter the stadium name:")
229
+
230
+ if stadium_name:
231
+ # Get the image URL
232
+ image_url = self.get_stadium_image_url(stadium_name)
233
+
234
+ if image_url:
235
+ st.image(image_url, caption=f'{stadium_name} Stadium')
236
+ else:
237
+ st.write("Image not found.")
238
+
239
+ def main_stadium():
240
+ stadium = StadiumImageFetcher()
241
+ stadium.display_stadium()
242
+
243
+
244
+
245
+
246
+ def box_plot_pass(events,team):
247
+
248
+ event_filterTeam = events[events['team']==team]
249
+ event_pass = event_filterTeam.filter(regex='^(pass)', axis=1).dropna(how='all')
250
+ event_pass_length = event_pass['pass_length']
251
+
252
+ fig = px.box(event_pass_length, y=event_pass_length, labels={'y':'Pass Length'}, title=f'{team} Distribution of Pass Length')
253
+ fig.update_layout(width=300, height=600) # 200 pixels de largeur, 400 pixels de hauteur
254
+
255
+ # Afficher le graphique dans Streamlit
256
+ st.plotly_chart(fig)
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+ # def get_possession(events):
270
+ # """
271
+ # Extracts and normalizes the possession counts for the two teams from the events DataFrame.
272
+
273
+ # Parameters:
274
+ # - events (pd.DataFrame): DataFrame containing an events column 'possession_team'.
275
+
276
+ # Returns:
277
+ # - home_team_possession (float): Normalized possession percentage for the home team, rounded to one decimal place.
278
+ # - away_team_possession (float): Normalized possession percentage for the away team, rounded to one decimal place.
279
+ # """
280
+ # # Get possession counts
281
+ # possession_counts = events['possession_team'].value_counts()
282
+
283
+ # # Get the top two possession teams
284
+ # home_team_possession, away_team_possession = possession_counts.iloc[0], possession_counts.iloc[1]
285
+
286
+ # # Calculate total possession
287
+ # total_possession = home_team_possession + away_team_possession
288
+
289
+ # # Normalize possession and round to one decimal place
290
+ # home_team_possession = round((home_team_possession / total_possession) * 100, 1)
291
+ # away_team_possession = round((away_team_possession / total_possession) * 100, 1)
292
+
293
+ # return home_team_possession, away_team_possession
294
+
295
+
296
+
297
+ # def get_total_xg(events):
298
+ # """
299
+ # Calcule la somme totale des xG (expected goals) par équipe et retourne les valeurs arrondies à 2 décimales.
300
+
301
+ # Parameters:
302
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'shot_statsbomb_xg' et 'team'.
303
+
304
+ # Returns:
305
+ # - home_xg (float): Somme totale des xG pour l'équipe à domicile, arrondie à 2 décimales.
306
+ # - away_xg (float): Somme totale des xG pour l'équipe à l'extérieur, arrondie à 2 décimales.
307
+ # """
308
+ # # Filtrer les colonnes et supprimer les lignes où 'shot_statsbomb_xg' est NaN
309
+ # data = events.filter(regex='^shot_statsbomb_xg|^team$').dropna(subset=['shot_statsbomb_xg'])
310
+
311
+ # # Grouper par 'team' et calculer la somme des xG
312
+ # data = data.groupby('team')['shot_statsbomb_xg'].sum()
313
+
314
+ # # Arrondir les résultats à 2 décimales
315
+ # home_xg, away_xg = round(data.iloc[0], 2), round(data.iloc[1], 2)
316
+
317
+ # return home_xg, away_xg
318
+
319
+ # def get_total_shots(events):
320
+ # """
321
+ # Calcule le nombre total de tirs (shots) par équipe.
322
+
323
+ # Parameters:
324
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'shot_statsbomb_xg' et 'team'.
325
+
326
+ # Returns:
327
+ # - home_shots (int): Nombre total de tirs pour l'équipe à domicile.
328
+ # - away_shots (int): Nombre total de tirs pour l'équipe à l'extérieur.
329
+ # """
330
+ # # Filtrer les colonnes et supprimer les lignes où 'shot_statsbomb_xg' est NaN
331
+ # data = events.filter(regex='^shot_statsbomb_xg|^team$').dropna(subset=['shot_statsbomb_xg'])
332
+
333
+ # # Grouper par 'team' et compter le nombre de tirs
334
+ # shot_counts = data.groupby('team').count()['shot_statsbomb_xg']
335
+
336
+ # # Retourner les comptes pour les deux équipes
337
+ # home_shots, away_shots = shot_counts.iloc[0], shot_counts.iloc[1]
338
+
339
+ # return home_shots, away_shots
340
+
341
+
342
+ # def get_total_shots_off_target(events):
343
+ # """
344
+ # Calcule le nombre total de tirs non cadrés (off target) par équipe.
345
+
346
+ # Parameters:
347
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'shot_outcome' et 'team'.
348
+
349
+ # Returns:
350
+ # - home_off_target (int): Nombre total de tirs non cadrés pour l'équipe à domicile.
351
+ # - away_off_target (int): Nombre total de tirs non cadrés pour l'équipe à l'extérieur.
352
+ # """
353
+ # # Define outcomes that indicate a shot was off target
354
+ # off_target_outcomes = ['Off T', 'Blocked', 'Missed']
355
+
356
+ # # Filter the events DataFrame for shots off target
357
+ # data = events[events['shot_outcome'].isin(off_target_outcomes)]
358
+
359
+ # # Group by 'team' and count the number of off-target shots
360
+ # off_target_counts = data.groupby('team').size()
361
+
362
+ # # Return the counts for the two teams
363
+ # home_off_target, away_off_target = off_target_counts.iloc[0], off_target_counts.iloc[1]
364
+
365
+ # return home_off_target, away_off_target
366
+
367
+
368
+ # def get_total_shots_on_target(events):
369
+ # """
370
+ # Calcule le nombre total de tirs cadrés (on target) par équipe.
371
+
372
+ # Parameters:
373
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'shot_outcome' et 'team'.
374
+
375
+ # Returns:
376
+ # - home_on_target (int): Nombre total de tirs cadrés pour l'équipe à domicile.
377
+ # - away_on_target (int): Nombre total de tirs cadrés pour l'équipe à l'extérieur.
378
+ # """
379
+ # # Define outcomes that indicate a shot was on target
380
+ # on_target_outcomes = ['Goal', 'Saved', 'Saved To Post', 'Shot Saved Off Target']
381
+
382
+ # # Filter the events DataFrame for shots on target
383
+ # data = events[events['shot_outcome'].isin(on_target_outcomes)]
384
+
385
+ # # Group by 'team' and count the number of on-target shots
386
+ # on_target_counts = data.groupby('team').size()
387
+
388
+ # # Return the counts for the two teams
389
+ # home_on_target, away_on_target = on_target_counts.iloc[0], on_target_counts.iloc[1]
390
+
391
+ # return home_on_target, away_on_target
392
+
393
+
394
+
395
+ # def get_total_passes(events):
396
+ # """
397
+ # Calcule le nombre total de passes par équipe.
398
+
399
+ # Parameters:
400
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'pass_outcome' et 'team'.
401
+
402
+ # Returns:
403
+ # - home_passes (int): Nombre total de passes pour l'équipe à domicile.
404
+ # - away_passes (int): Nombre total de passes pour l'équipe à l'extérieur.
405
+ # """
406
+ # # Filtrer les colonnes 'pass_outcome' et 'team', puis grouper par équipe et compter le nombre de passes
407
+ # pass_counts = events.filter(regex='^pass_end_location|^team$').groupby('team').count()['pass_end_location']
408
+
409
+ # # Retourner les comptes pour les deux équipes
410
+ # home_passes, away_passes = pass_counts.iloc[0], pass_counts.iloc[1]
411
+
412
+ # return home_passes, away_passes
413
+
414
+ # def get_successful_passes(events):
415
+ # """
416
+ # Calculates the total number of successful passes for home and away teams.
417
+
418
+ # Parameters:
419
+ # - events (pd.DataFrame): DataFrame containing the columns for all passes and pass outcomes.
420
+
421
+ # Returns:
422
+ # - home_successful_passes (int): Successful passes for the home team.
423
+ # - away_successful_passes (int): Successful passes for the away team.
424
+ # """
425
+ # # Get the total passes using the get_total_passes function
426
+ # home_passes, away_passes = get_total_passes(events)
427
+
428
+ # # Identify unsuccessful passes based on 'type' or another column indicating unsuccessful outcomes
429
+ # unsuccessful_passes = events.filter(regex='^pass_outcome|^team$').groupby('team').count().reset_index()['pass_outcome']
430
+
431
+ # # Calculate successful passes by subtracting unsuccessful passes from total passes
432
+ # home_unsuccessful_passes = unsuccessful_passes.iloc[0]
433
+ # away_unsuccessful_passes = unsuccessful_passes.iloc[1]
434
+
435
+ # home_successful_passes = home_passes - home_unsuccessful_passes
436
+ # away_successful_passes = away_passes - away_unsuccessful_passes
437
+
438
+ # return home_successful_passes, away_successful_passes
439
+
440
+ # def get_total_corners(events):
441
+ # """
442
+ # Calcule le nombre total de corners par équipe.
443
+
444
+ # Parameters:
445
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'pass_type' et 'team'.
446
+
447
+ # Returns:
448
+ # - home_corners (int): Nombre total de corners pour l'équipe à domicile.
449
+ # - away_corners (int): Nombre total de corners pour l'équipe à l'extérieur.
450
+ # """
451
+ # # Filtrer les colonnes 'pass_type' et 'team', puis grouper par équipe et compter le nombre de corners
452
+ # corner_counts = events.filter(regex='^pass_type|^team$').query('pass_type == "Corner"').groupby('team').count()['pass_type']
453
+
454
+ # # Extract the team names from the events DataFrame
455
+ # teams = events['team'].unique()
456
+
457
+ # # Handle cases where a team might not have any corners
458
+ # home_corners = corner_counts.get(teams[0], 0)
459
+ # away_corners = corner_counts.get(teams[1], 0)
460
+
461
+ # return home_corners, away_corners
462
+
463
+
464
+
465
+ # def get_total_fouls(events):
466
+ # """
467
+ # Calcule le nombre total de fautes par équipe.
468
+
469
+ # Parameters:
470
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'team' et 'type'.
471
+
472
+ # Returns:
473
+ # - home_fouls (int): Nombre total de fautes pour l'équipe à domicile.
474
+ # - away_fouls (int): Nombre total de fautes pour l'équipe à l'extérieur.
475
+ # """
476
+ # fouls = events[events['type'] == 'Foul Committed']
477
+ # foul_counts = fouls.groupby('team').size()
478
+ # home_fouls, away_fouls = foul_counts.iloc[0], foul_counts.iloc[1]
479
+
480
+ # return home_fouls, away_fouls
481
+
482
+
483
+
484
+ # def get_total_yellow_cards(events):
485
+ # """
486
+ # Calcule le nombre total de cartons jaunes par équipe.
487
+
488
+ # Parameters:
489
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'bad_behaviour_card' et 'team'.
490
+
491
+ # Returns:
492
+ # - home_yellow_cards (int): Nombre total de cartons jaunes pour l'équipe à domicile.
493
+ # - away_yellow_cards (int): Nombre total de cartons jaunes pour l'équipe à l'extérieur.
494
+ # """
495
+ # # Filtrer les colonnes 'bad_behaviour_card' et 'team', puis grouper par équipe et compter le nombre de cartons jaunes
496
+ # yellow_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Yellow Card"').groupby('team').count()['bad_behaviour_card']
497
+
498
+ # # Extraire les noms des équipes de l'événement DataFrame
499
+ # teams = events['team'].unique()
500
+
501
+ # # Gérer les cas où une équipe pourrait ne pas avoir de cartons jaunes
502
+ # home_yellow_cards = yellow_card_counts.get(teams[0], 0)
503
+ # away_yellow_cards = yellow_card_counts.get(teams[1], 0)
504
+
505
+ # return home_yellow_cards, away_yellow_cards
506
+
507
+ # def get_total_red_cards(events):
508
+ # """
509
+ # Calcule le nombre total de cartons rouges par équipe.
510
+
511
+ # Parameters:
512
+ # - events (pd.DataFrame): DataFrame contenant les colonnes 'bad_behaviour_card' et 'team'.
513
+
514
+ # Returns:
515
+ # - home_red_cards (int): Nombre total de cartons rouges pour l'équipe à domicile.
516
+ # - away_red_cards (int): Nombre total de cartons rouges pour l'équipe à l'extérieur.
517
+ # """
518
+ # # Filtrer les colonnes 'bad_behaviour_card' et 'team', puis grouper par équipe et compter le nombre de cartons rouges
519
+ # red_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Red Card"').groupby('team').count()['bad_behaviour_card']
520
+
521
+ # # Extraire les noms des équipes de l'événement DataFrame
522
+ # teams = events['team'].unique()
523
+
524
+ # # Gérer les cas où une équipe pourrait ne pas avoir de cartons rouges
525
+ # home_red_cards = red_card_counts.get(teams[0], 0)
526
+ # away_red_cards = red_card_counts.get(teams[1], 0)
527
+
528
+ # return home_red_cards, away_red_cards
529
+
530
+
531
+
532
+
533
+ # def display_normalized_scores(home_scores, away_scores, categories, home_color='blue', away_color='green', background_color='lightgray', bar_height=0.8, spacing_factor=2.5):
534
+ # """
535
+ # Displays a horizontal bar chart with normalized scores for home and away teams.
536
+
537
+ # Parameters:
538
+ # - home_scores: List of scores for the home team.
539
+ # - away_scores: List of scores for the away team.
540
+ # - categories: List of category names for each score.
541
+ # - home_color: Color of the bars representing the home team.
542
+ # - away_color: Color of the bars representing the away team.
543
+ # - background_color: Color of the background rectangles.
544
+ # - bar_height: Height of the bars and background rectangles.
545
+ # - spacing_factor: Factor to adjust the spacing between bars.
546
+ # """
547
+
548
+ # # Internal container size variables
549
+ # container_width = '50%' # Adjust width as needed
550
+ # container_height = 'auto' # Adjust height as needed
551
+
552
+ # # Normalizing the scores
553
+ # home_normalized = []
554
+ # away_normalized = []
555
+ # for home, away in zip(home_scores, away_scores):
556
+ # total = home + away
557
+ # if total != 0:
558
+ # home_normalized.append((home / total) * 100)
559
+ # away_normalized.append((away / total) * 100)
560
+ # else:
561
+ # home_normalized.append(0)
562
+ # away_normalized.append(0)
563
+
564
+ # # Augmenting the spacing between the bars by multiplying y_pos by a factor
565
+ # y_pos = np.arange(len(categories)) * spacing_factor
566
+
567
+ # # Plot
568
+ # fig, ax = plt.subplots(figsize=(10, 8))
569
+
570
+ # # Adding light gray backgrounds for each category with the same height as the bars
571
+ # for i in range(len(categories)):
572
+ # ax.add_patch(plt.Rectangle((-100, y_pos[i] - bar_height / 2), 200, bar_height, color=background_color, alpha=0.3, linewidth=0))
573
+
574
+ # # Plotting normalized scores for both teams
575
+ # ax.barh(y_pos, home_normalized, height=bar_height, color=home_color, align='center', label='Home Team')
576
+ # ax.barh(y_pos, [-score for score in away_normalized], height=bar_height, color=away_color, align='center', label='Away Team')
577
+
578
+ # # Positioning the category names above the bars to avoid overlap
579
+ # for i in range(len(categories)):
580
+ # ax.text(0, y_pos[i] + bar_height / 2 + 0.1, categories[i], ha='center', va='bottom', fontsize=10)
581
+
582
+ # # Adding non-normalized values to the end of the bars
583
+ # for i in range(len(categories)):
584
+ # ax.text(home_normalized[i] / 2, y_pos[i], f'{home_scores[i]}', va='center', color='white', fontweight='bold')
585
+ # ax.text(-away_normalized[i] / 2, y_pos[i], f'{away_scores[i]}', va='center', color='white', fontweight='bold')
586
+
587
+ # # Adjusting the axis limits
588
+ # ax.set_xlim(-100, 100)
589
+ # ax.set_ylim(-1, max(y_pos) + spacing_factor)
590
+
591
+ # # Hiding the spines
592
+ # for spine in ax.spines.values():
593
+ # spine.set_visible(False)
594
+
595
+ # # Removing y-ticks and x-ticks
596
+ # ax.set_yticks([])
597
+ # ax.set_xticks([])
598
+
599
+ # # Custom HTML/CSS to control the size of the container
600
+ # st.markdown(
601
+ # f"""
602
+ # <style>
603
+ # .resizable-graph-container {{
604
+ # width: {container_width}; /* Adjust the width as needed */
605
+ # height: {container_height}; /* Adjust the height as needed */
606
+ # padding: 10px;
607
+ # overflow: auto; /* Handle overflow if the graph is larger than the container */
608
+ # }}
609
+ # </style>
610
+ # <div class="resizable-graph-container">
611
+ # """,
612
+ # unsafe_allow_html=True
613
+ # )
614
+
615
+ # # Displaying the plot in Streamlit
616
+ # st.pyplot(fig)
617
+
618
+ # # Closing the custom container
619
+ # st.markdown('</div>', unsafe_allow_html=True)
620
+
621
+
622
+ def display_normalized_scores(home_scores, away_scores, categories, home_color='blue', away_color='green', background_color='lightgray', bar_height=0.8, spacing_factor=2.5):
623
+ """
624
+ Displays a horizontal bar chart with normalized scores for home and away teams.
625
+
626
+ Parameters:
627
+ - home_scores: List of scores for the home team.
628
+ - away_scores: List of scores for the away team.
629
+ - categories: List of category names for each score.
630
+ - home_color: Color of the bars representing the home team.
631
+ - away_color: Color of the bars representing the away team.
632
+ - background_color: Color of the background rectangles.
633
+ - bar_height: Height of the bars and background rectangles.
634
+ - spacing_factor: Factor to adjust the spacing between bars.
635
+ """
636
+
637
+ # Normalizing the scores
638
+ home_normalized = []
639
+ away_normalized = []
640
+ for home, away in zip(home_scores, away_scores):
641
+ # Remplacer les None par 0 pour éviter les erreurs
642
+ home = 0 if home is None else home
643
+ away = 0 if away is None else away
644
+
645
+ total = home + away
646
+ if total != 0:
647
+ home_normalized.append((home / total) * 100)
648
+ away_normalized.append((away / total) * 100)
649
+ else:
650
+ home_normalized.append(0)
651
+ away_normalized.append(0)
652
+
653
+ # Augmenting the spacing between the bars by multiplying y_pos by a factor
654
+ y_pos = np.arange(len(categories)) * spacing_factor
655
+
656
+ # Plot
657
+ fig, ax = plt.subplots(figsize=(10, 8))
658
+
659
+ # Adding light gray backgrounds for each category with the same height as the bars
660
+ for i in range(len(categories)):
661
+ ax.add_patch(plt.Rectangle((-100, y_pos[i] - bar_height / 2), 200, bar_height, color=background_color, alpha=0.3, linewidth=0))
662
+
663
+ # Plotting normalized scores for both teams
664
+ ax.barh(y_pos, home_normalized, height=bar_height, color=home_color, align='center', label='Home Team')
665
+ ax.barh(y_pos, [-score for score in away_normalized], height=bar_height, color=away_color, align='center', label='Away Team')
666
+
667
+ # Positioning the category names above the bars to avoid overlap
668
+ for i in range(len(categories)):
669
+ ax.text(0, y_pos[i] + bar_height / 2 + 0.1, categories[i], ha='center', va='bottom', fontsize=10)
670
+
671
+ # Adding non-normalized values to the end of the bars
672
+ for i in range(len(categories)):
673
+ ax.text(home_normalized[i] / 2, y_pos[i], f'{home_scores[i]}', va='center', color='white', fontweight='bold')
674
+ ax.text(-away_normalized[i] / 2, y_pos[i], f'{away_scores[i]}', va='center', color='white', fontweight='bold')
675
+
676
+ # Adjusting the axis limits
677
+ ax.set_xlim(-100, 100)
678
+ ax.set_ylim(-1, max(y_pos) + spacing_factor)
679
+
680
+ # Hiding the spines
681
+ for spine in ax.spines.values():
682
+ spine.set_visible(False)
683
+
684
+ # Removing y-ticks and x-ticks
685
+ ax.set_yticks([])
686
+ ax.set_xticks([])
687
+
688
+ # Displaying the plot in Streamlit
689
+ st.pyplot(fig)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ pandas
3
+ plotly
4
+ requests
5
+ statsbombpy
6
+ mplsoccer
7
+ matplotlib
stats_manager.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+ def check_columns(df, required_columns):
4
+ """
5
+ Vérifie si les colonnes requises sont présentes dans le DataFrame.
6
+
7
+ Parameters:
8
+ - df (pd.DataFrame): Le DataFrame à vérifier.
9
+ - required_columns (list): Liste des noms de colonnes requis.
10
+
11
+ Returns:
12
+ - bool: True si toutes les colonnes sont présentes, sinon False.
13
+ """
14
+ return all(column in df.columns for column in required_columns)
15
+
16
+ class StatsManager:
17
+ def __init__(self, events):
18
+ self.events = events
19
+
20
+ def calculate_stat(self, required_columns, func):
21
+ """
22
+ Vérifie si les colonnes nécessaires sont présentes, puis applique la fonction de calcul si elles sont présentes.
23
+
24
+ Parameters:
25
+ - required_columns (list): Liste des colonnes requises.
26
+ - func (callable): Fonction à appliquer si les colonnes sont présentes.
27
+
28
+ Returns:
29
+ - tuple: Les résultats de la fonction si les colonnes sont présentes, sinon (None, None).
30
+ """
31
+ if check_columns(self.events, required_columns):
32
+ return func(self.events)
33
+ else:
34
+ return None, None
35
+
36
+ def get_possession(self):
37
+ return self.calculate_stat(['possession_team'], self._calculate_possession)
38
+
39
+ def _calculate_possession(self, events):
40
+ possession_counts = events['possession_team'].value_counts()
41
+ if len(possession_counts) < 2:
42
+ return None, None
43
+ total_possession = possession_counts.iloc[0] + possession_counts.iloc[1]
44
+ home_possession = round((possession_counts.iloc[0] / total_possession) * 100, 1)
45
+ away_possession = round((possession_counts.iloc[1] / total_possession) * 100, 1)
46
+ return home_possession, away_possession
47
+
48
+ def get_total_xg(self):
49
+ return self.calculate_stat(['shot_statsbomb_xg', 'team'], self._calculate_total_xg)
50
+
51
+ def _calculate_total_xg(self, events):
52
+ data = events[['shot_statsbomb_xg', 'team']].dropna(subset=['shot_statsbomb_xg'])
53
+ if data.empty:
54
+ return None, None
55
+ data = data.groupby('team')['shot_statsbomb_xg'].sum()
56
+ if len(data) < 2:
57
+ return None, None
58
+ return round(data.iloc[0], 2), round(data.iloc[1], 2)
59
+
60
+ def get_total_shots(self):
61
+ return self.calculate_stat(['shot_statsbomb_xg', 'team'], self._calculate_total_shots)
62
+
63
+ def _calculate_total_shots(self, events):
64
+ data = events[['shot_statsbomb_xg', 'team']].dropna(subset=['shot_statsbomb_xg'])
65
+ if data.empty:
66
+ return None, None
67
+ shot_counts = data.groupby('team').count()['shot_statsbomb_xg']
68
+ if len(shot_counts) < 2:
69
+ return None, None
70
+ return shot_counts.iloc[0], shot_counts.iloc[1]
71
+
72
+ def get_total_shots_off_target(self):
73
+ return self.calculate_stat(['shot_outcome', 'team'], self._calculate_total_shots_off_target)
74
+
75
+ def _calculate_total_shots_off_target(self, events):
76
+ off_target_outcomes = ['Off T', 'Blocked', 'Missed']
77
+ data = events[events['shot_outcome'].isin(off_target_outcomes)]
78
+ if data.empty:
79
+ return None, None
80
+ off_target_counts = data.groupby('team').size()
81
+ if len(off_target_counts) < 2:
82
+ return None, None
83
+ return off_target_counts.iloc[0], off_target_counts.iloc[1]
84
+
85
+ def get_total_shots_on_target(self):
86
+ return self.calculate_stat(['shot_outcome', 'team'], self._calculate_total_shots_on_target)
87
+
88
+ def _calculate_total_shots_on_target(self, events):
89
+ on_target_outcomes = ['Goal', 'Saved', 'Saved To Post', 'Shot Saved Off Target']
90
+ data = events[events['shot_outcome'].isin(on_target_outcomes)]
91
+ if data.empty:
92
+ return None, None
93
+ on_target_counts = data.groupby('team').size()
94
+ if len(on_target_counts) < 2:
95
+ return None, None
96
+ return on_target_counts.iloc[0], on_target_counts.iloc[1]
97
+
98
+ def get_total_passes(self):
99
+ return self.calculate_stat(['pass_end_location', 'team'], self._calculate_total_passes)
100
+
101
+ def _calculate_total_passes(self, events):
102
+ pass_counts = events.filter(regex='^pass_end_location|^team$').groupby('team').count()['pass_end_location']
103
+ if len(pass_counts) < 2:
104
+ return None, None
105
+ return pass_counts.iloc[0], pass_counts.iloc[1]
106
+
107
+ def get_successful_passes(self):
108
+ return self.calculate_stat(['pass_outcome', 'team'], self._calculate_successful_passes)
109
+
110
+ def _calculate_successful_passes(self, events):
111
+ home_passes, away_passes = self.get_total_passes()
112
+ unsuccessful_passes = events.filter(regex='^pass_outcome|^team$').groupby('team').count().reset_index()['pass_outcome']
113
+ if len(unsuccessful_passes) < 2:
114
+ return None, None
115
+ home_unsuccessful_passes = unsuccessful_passes.iloc[0]
116
+ away_unsuccessful_passes = unsuccessful_passes.iloc[1]
117
+ return home_passes - home_unsuccessful_passes, away_passes - away_unsuccessful_passes
118
+
119
+ def get_total_corners(self):
120
+ return self.calculate_stat(['pass_type', 'team'], self._calculate_total_corners)
121
+
122
+ def _calculate_total_corners(self, events):
123
+ corner_counts = events.filter(regex='^pass_type|^team$').query('pass_type == "Corner"').groupby('team').count()['pass_type']
124
+ if len(corner_counts) < 2:
125
+ return None, None
126
+ return corner_counts.iloc[0], corner_counts.iloc[1]
127
+
128
+ def get_total_fouls(self):
129
+ return self.calculate_stat(['type', 'team'], self._calculate_total_fouls)
130
+
131
+ def _calculate_total_fouls(self, events):
132
+ fouls = events[events['type'] == 'Foul Committed']
133
+ foul_counts = fouls.groupby('team').size()
134
+ if len(foul_counts) < 2:
135
+ return None, None
136
+ return foul_counts.iloc[0], foul_counts.iloc[1]
137
+
138
+ def get_total_yellow_cards(self):
139
+ return self.calculate_stat(['bad_behaviour_card', 'team'], self._calculate_total_yellow_cards)
140
+
141
+ def _calculate_total_yellow_cards(self, events):
142
+ yellow_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Yellow Card"').groupby('team').count()['bad_behaviour_card']
143
+ if len(yellow_card_counts) < 2:
144
+ return None, None
145
+ return yellow_card_counts.iloc[0], yellow_card_counts.iloc[1]
146
+
147
+ def get_total_red_cards(self):
148
+ return self.calculate_stat(['bad_behaviour_card', 'team'], self._calculate_total_red_cards)
149
+
150
+ def _calculate_total_red_cards(self, events):
151
+ red_card_counts = events.filter(regex='^bad_behaviour_card|^team$').query('bad_behaviour_card == "Red Card"').groupby('team').count()['bad_behaviour_card']
152
+ if len(red_card_counts) < 2:
153
+ return None, None
154
+ return red_card_counts.iloc[0], red_card_counts.iloc[1]