|
1 | 1 | import time |
2 | 2 |
|
3 | | -import matplotlib.pyplot as plt |
4 | 3 | import pandas as pd |
| 4 | +import plotly.express as px |
| 5 | +import plotly.graph_objects as go |
5 | 6 | import streamlit as st |
6 | 7 | from google.cloud import bigquery |
7 | 8 | from google.oauth2 import service_account |
|
13 | 14 | st.set_page_config(page_title="Weekly U.S. Petroleum Supply", layout="wide") |
14 | 15 |
|
15 | 16 | # ========================= |
16 | | -# Sidebar title |
| 17 | +# Sidebar title (above nav via CSS) |
17 | 18 | # ========================= |
18 | | -st.sidebar.markdown( |
| 19 | +st.markdown( |
19 | 20 | """ |
20 | | - <h1 style="font-size: 1.5rem; line-height: 1.2; margin-bottom: 0.2rem;"> |
21 | | - U.S. Petroleum & WTI Weekly Monitor |
22 | | - </h1> |
| 21 | + <style> |
| 22 | + [data-testid="stSidebar"] { |
| 23 | + background-color: #0D2B5E !important; |
| 24 | + } |
| 25 | + [data-testid="stSidebar"] * { |
| 26 | + color: white !important; |
| 27 | + } |
| 28 | + [data-testid="stSidebarNav"] a { |
| 29 | + color: rgba(255,255,255,0.8) !important; |
| 30 | + } |
| 31 | + [data-testid="stSidebarNav"] a:hover { |
| 32 | + color: white !important; |
| 33 | + background-color: rgba(255,255,255,0.1) !important; |
| 34 | + } |
| 35 | + [data-testid="stSidebarNav"] { |
| 36 | + padding-top: 3.5rem; |
| 37 | + } |
| 38 | + [data-testid="stSidebarNav"]::before { |
| 39 | + content: "U.S. Petroleum & WTI Weekly Monitor"; |
| 40 | + display: block; |
| 41 | + position: absolute; |
| 42 | + top: 0; |
| 43 | + left: 0; |
| 44 | + right: 0; |
| 45 | + padding: 1rem 1.2rem 0.2rem 1.2rem; |
| 46 | + font-size: 1.05rem; |
| 47 | + font-weight: 600; |
| 48 | + line-height: 1.3; |
| 49 | + color: white !important; |
| 50 | + } |
| 51 | + </style> |
23 | 52 | """, |
24 | 53 | unsafe_allow_html=True, |
25 | 54 | ) |
26 | | -st.sidebar.caption("Source: EIA") |
27 | | -st.sidebar.divider() |
28 | 55 |
|
29 | 56 | # ========================= |
30 | 57 | # Main page header |
31 | 58 | # ========================= |
32 | 59 | st.title("Weekly U.S. Petroleum Supply") |
33 | | -st.subheader("Team Members: Irina, Indra") |
34 | | -st.caption("Source: U.S. Energy Information Administration (EIA)") |
| 60 | +st.caption("Team Members: Irina, Indra · Source: U.S. Energy Information Administration (EIA)") |
35 | 61 |
|
36 | 62 | # ========================= |
37 | 63 | # Project Proposal |
@@ -332,50 +358,71 @@ def compute_product_price_sensitivity( |
332 | 358 | st.divider() |
333 | 359 |
|
334 | 360 | # ========================= |
335 | | -# Two side-by-side charts |
| 361 | +# Stacked charts |
336 | 362 | # ========================= |
337 | | -left_col, right_col = st.columns(TWO_COLUMN_LAYOUT) |
338 | | - |
339 | | -with left_col: |
340 | | - st.subheader("Total Product Supplied") |
341 | | - |
342 | | - fig, ax = plt.subplots(figsize=(7, 4)) |
343 | | - ax.plot(filtered_total["week"], filtered_total["total_supply"]) |
344 | | - ax.set_xlabel("Week") |
345 | | - ax.set_ylabel("Total Product Supplied") |
346 | | - st.pyplot(fig) |
347 | | - |
348 | | - with st.expander("Show total supply data table"): |
349 | | - total_display = filtered_total.sort_values("week", ascending=False).copy() |
350 | | - total_display["week"] = total_display["week"].dt.strftime("%Y-%m-%d") |
351 | | - st.dataframe(total_display, width="stretch") |
352 | | - |
353 | | -with right_col: |
354 | | - st.subheader("Product-Level Weekly Supply") |
355 | | - |
356 | | - if not selected_products: |
357 | | - st.warning("Please select at least one product from the sidebar.") |
358 | | - else: |
359 | | - product_plot_df = filtered_product[ |
360 | | - filtered_product["product_name"].isin(selected_products) |
361 | | - ].copy() |
362 | | - |
363 | | - fig2, ax2 = plt.subplots(figsize=(7, 4)) |
364 | | - for product_name in selected_products: |
365 | | - temp = product_plot_df[product_plot_df["product_name"] == product_name] |
366 | | - ax2.plot(temp["week"], temp["product_supplied"], label=product_name) |
367 | | - |
368 | | - ax2.set_xlabel("Week") |
369 | | - ax2.set_ylabel("Product Supplied") |
370 | | - ax2.legend() |
371 | | - st.pyplot(fig2) |
372 | | - |
373 | | - with st.expander("Show product-level data table"): |
374 | | - product_display = product_plot_df.sort_values( |
375 | | - ["product_name", "week"], ascending=[True, False] |
376 | | - ).copy() |
377 | | - product_display["week"] = product_display["week"].dt.strftime("%Y-%m-%d") |
378 | | - st.dataframe(product_display, width="stretch") |
| 363 | +st.subheader("Total Product Supplied") |
| 364 | + |
| 365 | +fig = px.line( |
| 366 | + filtered_total, |
| 367 | + x="week", |
| 368 | + y="total_supply", |
| 369 | + labels={"week": "Week", "total_supply": "Total Product Supplied"}, |
| 370 | +) |
| 371 | +fig.update_layout( |
| 372 | + hovermode="x unified", |
| 373 | + xaxis=dict(gridcolor="rgba(13,43,94,0.2)", linecolor="#0D2B5E"), |
| 374 | + yaxis=dict(gridcolor="rgba(13,43,94,0.2)", linecolor="#0D2B5E"), |
| 375 | + plot_bgcolor="rgba(0,0,0,0)", |
| 376 | + paper_bgcolor="rgba(0,0,0,0)", |
| 377 | +) |
| 378 | +fig.update_traces(hovertemplate="<b>%{x|%b %d, %Y}</b><br>Total Supply: %{y:,.0f}<extra></extra>") |
| 379 | +st.plotly_chart(fig, use_container_width=True) |
| 380 | + |
| 381 | +with st.expander("Show total supply data table"): |
| 382 | + total_display = filtered_total.sort_values("week", ascending=False).copy() |
| 383 | + total_display["week"] = total_display["week"].dt.strftime("%Y-%m-%d") |
| 384 | + st.dataframe(total_display, width="stretch") |
| 385 | + |
| 386 | +st.divider() |
| 387 | + |
| 388 | +st.subheader("Product-Level Weekly Supply") |
| 389 | + |
| 390 | +if not selected_products: |
| 391 | + st.warning("Please select at least one product from the sidebar.") |
| 392 | +else: |
| 393 | + product_plot_df = filtered_product[ |
| 394 | + filtered_product["product_name"].isin(selected_products) |
| 395 | + ].copy() |
| 396 | + |
| 397 | + fig2 = px.line( |
| 398 | + product_plot_df, |
| 399 | + x="week", |
| 400 | + y="product_supplied", |
| 401 | + color="product_name", |
| 402 | + labels={ |
| 403 | + "week": "Week", |
| 404 | + "product_supplied": "Product Supplied", |
| 405 | + "product_name": "Product", |
| 406 | + }, |
| 407 | + ) |
| 408 | + fig2.update_layout( |
| 409 | + hovermode="x unified", |
| 410 | + xaxis=dict(gridcolor="rgba(13,43,94,0.2)", linecolor="#0D2B5E"), |
| 411 | + yaxis=dict(gridcolor="rgba(13,43,94,0.2)", linecolor="#0D2B5E"), |
| 412 | + plot_bgcolor="rgba(0,0,0,0)", |
| 413 | + paper_bgcolor="rgba(0,0,0,0)", |
| 414 | + ) |
| 415 | + fig2.update_traces( |
| 416 | + hovertemplate="<b>%{fullData.name}</b><br>%{x|%b %d, %Y}: %{y:,.0f}<extra></extra>" |
| 417 | + ) |
| 418 | + st.plotly_chart(fig2, use_container_width=True) |
| 419 | + |
| 420 | + with st.expander("Show product-level data table"): |
| 421 | + product_display = product_plot_df.sort_values( |
| 422 | + ["product_name", "week"], ascending=[True, False] |
| 423 | + ).copy() |
| 424 | + product_display["week"] = product_display["week"].dt.strftime("%Y-%m-%d") |
| 425 | + st.dataframe(product_display, width="stretch") |
379 | 426 |
|
380 | 427 | st.divider() |
381 | 428 |
|
@@ -409,15 +456,20 @@ def compute_product_price_sensitivity( |
409 | 456 | "abs_correlation", ascending=True |
410 | 457 | ) |
411 | 458 |
|
412 | | - fig3, ax3 = plt.subplots(figsize=(6, 3.5)) |
413 | | - ax3.barh( |
414 | | - chart_df["product_name"], |
415 | | - chart_df["abs_correlation"], |
416 | | - color="darkorange", |
| 459 | + fig3 = px.bar( |
| 460 | + chart_df, |
| 461 | + x="abs_correlation", |
| 462 | + y="product_name", |
| 463 | + orientation="h", |
| 464 | + labels={ |
| 465 | + "abs_correlation": "Absolute correlation with WTI price", |
| 466 | + "product_name": "Product", |
| 467 | + }, |
| 468 | + color_discrete_sequence=["darkorange"], |
417 | 469 | ) |
418 | | - ax3.set_xlabel("Absolute correlation with WTI price") |
419 | | - ax3.set_ylabel("Product") |
420 | | - st.pyplot(fig3) |
| 470 | + fig3.update_layout(showlegend=False) |
| 471 | + fig3.update_traces(hovertemplate="<b>%{y}</b><br>Correlation: %{x:.4f}<extra></extra>") |
| 472 | + st.plotly_chart(fig3, use_container_width=True) |
421 | 473 |
|
422 | 474 | with st.expander("Show product sensitivity table"): |
423 | 475 | st.dataframe( |
|
0 commit comments