Py.Cafe

acabrera.citizens/

pixar_filmes

DocsPricing
  • app.py
  • pixar_dashboard.py
  • pixar_data.csv
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd
import random

# Cargar datos
df = pd.read_csv("pixar_data.csv", parse_dates=['release_date']).drop(['Unnamed: 0', 'number'], axis=1)

# Definir clusters y colores
CLUSTER_INFO = {
    "Box Office Titans": {
        "description": "High commercial success with balanced audience/critic reception.",
        "color": "#FF6B6B"  # Coral red
    },
    "Pixar Underdogs": {
        "description": "Films that performed more modestly commercially and critically.",
        "color": "#4ECDC4"  # Teal
    },
    "Critics/Users Favorites": {
        "description": "Strong audience/critic acclaim with moderate box office results.",
        "color": "#FFD166"  # Golden yellow
    }
}

CLUSTER_ORDER = ["Box Office Titans", "Pixar Underdogs", "Critics/Users Favorites"]

# Datos trivia
PIXAR_TRIVIA = [
    "Toy Story was the first feature-length film created entirely by computer animation.",
    "Finding Nemo won the Academy Award for Best Animated Feature in 2004.",
    "Luxo Jr. was the first 3D computer-animated film nominated for an Academy Award.",
    "Monsters, Inc. was almost called 'Monsters in the Closet' during development.",
    "Pixar's Up was the first animated film to open the Cannes Film Festival.",
    "The Pizza Planet truck has made a cameo in nearly every Pixar movie since Toy Story.",
    "The code 'A113' in Pixar movies refers to a classroom at the California Institute of the Arts.",
    "Pixar created a new algorithm to animate water for Finding Nemo, a landmark in animation technology.",
    "Ratatouille's animators worked in real kitchens to study chefs' movements and cooking styles.",
    "Wall-E's name is an homage to Walt Disney: 'Waste Allocation Load Lifter: Earth-Class.'"
]

# Inicializar app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SKETCHY, 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css'])
app.title = "Pixar Films"

# Función para estilizar gráficos
def style_figure(fig):
    fig.update_layout(
        plot_bgcolor='rgba(240, 240, 240, 0.5)',
        paper_bgcolor='white',
        font=dict(family='Nunito, sans-serif', size=14),
        margin=dict(l=40, r=40, t=40, b=60),
        legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5, font=dict(size=16)),
        xaxis=dict(showgrid=True, gridcolor='rgba(220, 220, 220, 0.8)', tickfont=dict(size=12), title_font=dict(size=16, color='#505050')),
        yaxis=dict(showgrid=True, gridcolor='rgba(220, 220, 220, 0.8)', tickfont=dict(size=12), title_font=dict(size=16, color='#505050')),
        hoverlabel=dict(bgcolor="white", font_size=14, font_family="Nunito, sans-serif", bordercolor="gray")
    )
    return fig

# Componentes de UI reutilizables
def create_header():
    return html.Div([
        html.Div([
            html.H2("Pixar Films Analytics", className="display-4 fw-bold"),
            html.P("Exploring the numbers behind the magic", className="lead")
        ], className="container py-4"),
        html.Div([
            html.I(className="fas fa-lightbulb me-2"),
            html.I(className="fa-solid fa-robot me-2"),
            html.I(className="fas fa-car me-2")
        ], className="d-flex justify-content-end align-items-center")
    ], className="bg-primary text-white mb-4 d-flex justify-content-between")

def create_sidebar():
    return html.Div([
        dbc.Card([
            dbc.CardHeader(html.H4("Data Visualizations", className="text-primary fw-bold"), id='data-visualizations'),
            dbc.CardBody([
                # Botones principales
                html.Div([
                    dbc.Button([html.I(className="fas fa-chart-pie me-2"), "Pixar Film Groupings"], 
                              id="btn-cluster-overview", color="primary", outline=True, className="mb-2 w-100 text-start shadow-sm"),
                    
                    dbc.Button([html.I(className="fas fa-chart-line me-2"), "Film Revenue Over Time"], 
                              id="btn-trend-metrics", color="primary", outline=True, className="mb-2 w-100 text-start shadow-sm"),
                    
                    # Opciones de tendencias (inicialmente ocultas)
                    html.Div([
                        html.Div(className="ps-3 mt-2 mb-3 border-start border-3 border-primary", children=[
                            dbc.Button("Worldwide Revenue", id="btn-worldwide", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal"),
                            dbc.Button("US/Canada Revenue", id="btn-us-canada", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal"),
                            dbc.Button("International Revenue", id="btn-international", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal")
                        ])
                    ], id="trend-options", style={"display": "none"}),
                    
                    dbc.Button([html.I(className="fas fa-chart-bar me-2"), "Performance Indicators"], 
                              id="btn-film-metrics", color="primary", outline=True, className="mb-2 w-100 text-start shadow-sm"),
                    
                    # Opciones de métricas (inicialmente ocultas)
                    html.Div([
                        html.Div(className="ps-3 mt-2 mb-3 border-start border-3 border-primary", children=[
                            dbc.Button("Revenue", id="btn-box-office", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal"),
                            dbc.Button("Return on Investment", id="btn-roi", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal"),
                            dbc.Button("Profit", id="btn-profit", color="link", className="text-decoration-none d-block text-start ps-2 py-2 fw-normal")
                        ])
                    ], id="metrics-options", style={"display": "none"}),
                    
                    dbc.Button([html.I(className="fas fa-clock me-2"), "Pixar Film Timeline"], 
                              id="btn-movie-timeline", color="primary", outline=True, className="mb-2 w-100 text-start shadow-sm"),
                ], className="mb-4"),
                
                html.Hr(),
                
                # Sección de trivia
                html.Div([
                    html.H5("Pixar Trivia", className="text-primary mb-3 fw-bold"),
                    dbc.Button("Random Fact", id='trivia-button', color="primary", className="w-100 shadow-sm"),
                    html.Div(id='trivia-section', className="p-3 mt-3 border rounded bg-light shadow-sm")
                ])
            ])
        ], className="sticky-top shadow-sm"),
        dbc.Tooltip("To see the charts, click on the information that interests you.",
                   target="data-visualizations", placement="right", className="fw-normal text-muted")
    ], style={'position': 'sticky', 'top': '0', 'height': '100vh', 'overflowY': 'auto'})

# Layout
app.layout = html.Div([
    create_header(),
    dbc.Container([
        dbc.Row([
            # Sidebar para controles
            dbc.Col(create_sidebar(), width=3, className="mb-4"),
            
            # Área principal de contenido
            dbc.Col([
                html.H3(id="graph-title", className="mb-4 text-primary fw-bold"),
                html.Div(id="content-area", className="mb-4 shadow-sm")
            ], width=9)
        ])
    ], fluid=True),
    
    # Footer
    html.Footer([
        html.Div("© 2025 Pixar Films Analysis Dashboard", className="text-center py-3")
    ], className="bg-light mt-4 border-top")
], style={'backgroundColor': '#F8F9FA', 'fontFamily': '"Nunito", sans-serif', 'boxShadow': '0 4px 6px rgba(0, 0, 0, 0.1)', 'transition': 'all 0.3s ease'})

# Funciones para generar gráficos
def generate_cluster_overview():
    # Crear tarjetas para cada cluster
    cluster_cards = []
    for cluster_name in CLUSTER_ORDER:
        cluster_df = df[df['model1_clusters'] == cluster_name]
        
        # Calcular métricas
        films_count = len(cluster_df['film'].unique())
        avg_boxoffice = cluster_df['box_office_worldwide'].mean() / 1e6
        avg_imdb = cluster_df['imdb_score'].mean()
        top_film = cluster_df.loc[cluster_df['box_office_worldwide'].idxmax()]['film'] if len(cluster_df) > 0 else "N/A"
        
        # Crear tarjeta
        card = dbc.Col(
            dbc.Card([
                dbc.CardHeader(
                    html.H5(cluster_name, className="fw-bold m-0"),
                    style={"backgroundColor": CLUSTER_INFO[cluster_name]["color"], "color": "#212529"}
                ),
                dbc.CardBody([
                    html.P(CLUSTER_INFO[cluster_name]["description"], className="mb-4"),
                    html.Div([
                        html.Div([
                            html.H3(f"{films_count}", className="fw-bold text-primary text-center mb-1"),
                            html.P("Films", className="text-muted text-center small")
                        ], className="col"),
                        html.Div([
                            html.H3(f"${avg_boxoffice:.1f}M", className="fw-bold text-primary text-center mb-1"),
                            html.P("Avg. Revenue", className="text-muted text-center small")
                        ], className="col"),
                        html.Div([
                            html.H3(f"{avg_imdb:.1f}/10", className="fw-bold text-primary text-center mb-1"),
                            html.P("Avg. IMDB", className="text-muted text-center small")
                        ], className="col")
                    ], className="row mb-3"),
                    html.Div(className="border-top pt-3 mt-2", children=[
                        html.P([html.Span("Top performer: ", className="text-muted"), 
                              html.Span(f"{top_film}", className="fw-bold")], className="text-center mb-0")
                    ])
                ])
            ], className="h-100 shadow"),
            width=4,
            className="mb-4"
        )
        cluster_cards.append(card)
    
    # Crear scatter plot
    scatter_fig = px.scatter(
        df,
        x='profit',
        y='imdb_score',
        color='model1_clusters',
        size='box_office_worldwide',
        size_max=30,
        category_orders={'model1_clusters': CLUSTER_ORDER},
        color_discrete_map={name: info["color"] for name, info in CLUSTER_INFO.items()},
        labels={
            'profit': 'Profit ($)',
            'imdb_score': 'IMDb Score',
            'model1_clusters': 'Cluster Group',
            'box_office_worldwide': 'World Wide Revenue ($)',
        },
        hover_name='film',
        hover_data={
            'film': False,
            'profit': ':.2s',
            'imdb_score': True,
            'box_office_worldwide': ':.3s',
            'model1_clusters': False
        }
    )
    
    scatter_fig.update_traces(
        marker=dict(opacity=0.85, line=dict(width=1, color='white'))
    )
    
    scatter_fig = style_figure(scatter_fig)
    scatter_fig.update_layout(legend_title_text='Cluster Groups', legend_title_font=dict(size=16))
    
    scatter_card = dbc.Card([
        dbc.CardBody([
            dcc.Graph(figure=scatter_fig, style={"height": "600px"})
        ])
    ], className="shadow")
    
    return html.Div([dbc.Row(cluster_cards, className="g-3 mb-4"), scatter_card])

def generate_trend_chart(metric_key):
    metric_labels = {
        'box_office_worldwide': 'Worldwide Revenue ($)',
        'box_office_us_canada': 'US/Canada Revenue ($)',
        'box_office_other': 'International Revenue ($)'
    }
    
    trend_df = df.drop_duplicates(subset='film').sort_values(by='release_year')
    
    fig = px.line(
        trend_df, 
        x='release_year', 
        y=metric_key,
        color='model1_clusters',
        category_orders={'model1_clusters': CLUSTER_ORDER},
        color_discrete_map={name: info["color"] for name, info in CLUSTER_INFO.items()},
        line_shape='spline',
        markers=True,
        labels={
            'release_year': 'Release Year', 
            metric_key: metric_labels[metric_key], 
            'model1_clusters': 'Cluster Group'
        },
        hover_name='film',
        hover_data={
            'film': False,
            'release_year': True,
            metric_key: ':.2s',
            'model1_clusters': False
        }
    )
    
    fig.update_traces(
        line=dict(width=4),
        marker=dict(size=12, line=dict(width=1, color='white'))
    )
    
    fig = style_figure(fig)
    fig.update_layout(legend_title_text='Cluster Groups', legend_title_font=dict(size=16))
    
    return dbc.Card(dbc.CardBody([
        dcc.Graph(figure=fig, style={"height": "600px"})
    ]), className="shadow")

def generate_performance_chart(metric_key):
    metric_labels = {
        'box_office_worldwide': 'Worldwide Revenue ($)',
        'roi': 'Return on Investment (%)',
        'profit': 'Profit ($)'
    }
    
    df_plot = df.drop_duplicates(subset='film').sort_values(by=metric_key, ascending=False)
    
    fig = px.bar(
        df_plot, 
        x='film', 
        y=metric_key,
        color='model1_clusters',
        category_orders={'model1_clusters': CLUSTER_ORDER},
        color_discrete_map={name: info["color"] for name, info in CLUSTER_INFO.items()},
        labels={
            'film': 'Movie',
            metric_key: metric_labels[metric_key], 
            'model1_clusters': 'Cluster Group',
            'release_year': 'Release Year',
            'imdb_score': 'Imdb Score'
        },
        hover_data={
            'release_year': True, 
            metric_key: ':.2s',
            'imdb_score': True,
            'model1_clusters': False,
            'film': False
        }
    )
    
    fig.update_traces(
        marker=dict(line=dict(width=1, color='white'), opacity=0.9),
        width=0.7
    )
    
    fig = style_figure(fig)
    fig.update_xaxes(tickangle=45, tickfont=dict(size=12))
    fig.update_layout(legend_title_text='Cluster Groups', legend_title_font=dict(size=16))
    
    return dbc.Card(dbc.CardBody([
        dcc.Graph(figure=fig, style={"height": "600px"})
    ]), className="shadow")

def generate_timeline():
    fig = px.scatter(
        df, 
        x='release_year', 
        y='imdb_score', 
        size='box_office_worldwide',
        size_max=35,
        color='model1_clusters',
        category_orders={'model1_clusters': CLUSTER_ORDER},
        color_discrete_map={name: info["color"] for name, info in CLUSTER_INFO.items()},
        labels={
            'release_year': 'Release Year', 
            'box_office_worldwide': 'World Wide Revenue ($)', 
            'imdb_score': 'IMDB Score', 
            'model1_clusters': 'Cluster Group'
        },
        hover_name='film',
        hover_data={
            'release_year': True,
            'box_office_worldwide': ':.3s',
            'imdb_score': True,
            'film': False,
            'model1_clusters': False
        }
    )
    
    fig.update_traces(marker=dict(opacity=0.9), line=dict(width=1.5, color='white'))
    fig = style_figure(fig)
    fig.update_layout(legend_title_text='Cluster Groups', legend_title_font=dict(size=16))
    
    return dbc.Card(dbc.CardBody([
        dcc.Graph(figure=fig, style={"height": "600px"})
    ]), className="shadow")

# Callbacks simplificados
@app.callback(
    [Output("trend-options", "style"), Output("metrics-options", "style"),
     Output("btn-cluster-overview", "color"), Output("btn-trend-metrics", "color"),
     Output("btn-film-metrics", "color"), Output("btn-movie-timeline", "color"),
     Output("btn-cluster-overview", "outline"), Output("btn-trend-metrics", "outline"),
     Output("btn-film-metrics", "outline"), Output("btn-movie-timeline", "outline")],
    [Input("btn-trend-metrics", "n_clicks"), Input("btn-film-metrics", "n_clicks"),
     Input("btn-cluster-overview", "n_clicks"), Input("btn-movie-timeline", "n_clicks")],
    prevent_initial_call=True
)
def toggle_options(n1, n2, n3, n4):
    ctx = dash.callback_context
    button_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
    
    # Valores por defecto
    trend_style = {"display": "none"}
    metrics_style = {"display": "none"}
    colors = ["primary"] * 4
    outlines = [True] * 4
    
    # Configurar según el botón presionado
    if button_id == "btn-trend-metrics":
        trend_style = {"display": "block"}
        colors[1] = "primary"
        outlines[1] = False
    elif button_id == "btn-film-metrics":
        metrics_style = {"display": "block"}
        colors[2] = "primary"
        outlines[2] = False
    elif button_id == "btn-cluster-overview":
        colors[0] = "primary"
        outlines[0] = False
    elif button_id == "btn-movie-timeline":
        colors[3] = "primary"
        outlines[3] = False
    
    return trend_style, metrics_style, *colors, *outlines

@app.callback(
    Output('graph-title', 'children'),
    [Input('btn-cluster-overview', 'n_clicks'), Input('btn-trend-metrics', 'n_clicks'),
     Input('btn-worldwide', 'n_clicks'), Input('btn-us-canada', 'n_clicks'),
     Input('btn-international', 'n_clicks'), Input('btn-film-metrics', 'n_clicks'),
     Input('btn-box-office', 'n_clicks'), Input('btn-roi', 'n_clicks'),
     Input('btn-profit', 'n_clicks'), Input('btn-movie-timeline', 'n_clicks')],
    prevent_initial_call=True
)
def update_graph_title(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10):
    ctx = dash.callback_context
    button_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
    
    titles = {
        'btn-cluster-overview': 'Pixar Film Grouping Overview',
        'btn-trend-metrics': 'Revenue Trends Over Time',
        'btn-worldwide': 'Worldwide Revenue Trends',
        'btn-us-canada': 'US/Canada Revenue Trends',
        'btn-international': 'International Box Revenue Trends',
        'btn-film-metrics': 'Film Performance Analysis',
        'btn-box-office': 'Revenue Performance',
        'btn-roi': 'Return on Investment Analysis',
        'btn-profit': 'Profit Analysis',
        'btn-movie-timeline': 'Pixar Movies Timeline'
    }
    
    return titles.get(button_id, 'Pixar Films Analytics')

@app.callback(
    Output('trivia-section', 'children'),
    Input('trivia-button', 'n_clicks')
)
def update_trivia(n_clicks):
    if n_clicks:
        return html.P(random.choice(PIXAR_TRIVIA), className="mb-0 fst-italic")
    return html.P("Click for a fun Pixar fact!", className="mb-0 text-muted")

@app.callback(
    Output('content-area', 'children'),
    [Input('btn-cluster-overview', 'n_clicks'), Input('btn-worldwide', 'n_clicks'),
     Input('btn-us-canada', 'n_clicks'), Input('btn-international', 'n_clicks'),
     Input('btn-box-office', 'n_clicks'), Input('btn-roi', 'n_clicks'),
     Input('btn-profit', 'n_clicks'), Input('btn-movie-timeline', 'n_clicks')],
    prevent_initial_call=True
)
def update_content(n1, n2, n3, n4, n5, n6, n7, n8):
    ctx = dash.callback_context
    button_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
    
    # Mapeo de botones a funciones y parámetros
    content_mapping = {
        'btn-cluster-overview': generate_cluster_overview,
        'btn-worldwide': lambda: generate_trend_chart('box_office_worldwide'),
        'btn-us-canada': lambda: generate_trend_chart('box_office_us_canada'),
        'btn-international': lambda: generate_trend_chart('box_office_other'),
        'btn-box-office': lambda: generate_performance_chart('box_office_worldwide'),
        'btn-roi': lambda: generate_performance_chart('roi'),
        'btn-profit': lambda: generate_performance_chart('profit'),
        'btn-movie-timeline': generate_timeline
    }
    
    if button_id in content_mapping:
        return content_mapping[button_id]()
    
    return html.Div("Select a visualization using the buttons", className="text-center text-muted my-5 py-5")