.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "_examples\ukvi-trips\plot_main04.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr__examples_ukvi-trips_plot_main04.py: UKVI Travel History Visualizer (plotly) ======================================= Improving the visualisation of previous example using plotly. .. GENERATED FROM PYTHON SOURCE LINES 8-200 .. raw:: html :file: images\sphx_glr_plot_main04_001.html .. rst-class:: sphx-glr-script-out Out: .. code-block:: none Roundtrips: Outbound Date Inbound Date Outbound Ports Inbound Ports ... Voyage Code Departure Airport y_axis_str Voyage Label 0 2019-12-04 2020-01-13 STN-MAD SGN-LHR ... FR5998 STN 00 STN-MAD (04 Dec) → SGN-LHR (13 Jan) | 40 Days 1 2020-09-15 2021-02-08 STN-RMI MAD-LHR ... FR6035 STN 01 STN-RMI (15 Sep) → MAD-LHR (08 Feb) | 146 Days 2 2021-06-17 2021-10-05 STN-MAD MAD-STN ... FR5994 STN 02 STN-MAD (17 Jun) → MAD-STN (05 Oct) | 110 Days 3 2021-11-19 2021-12-02 STN-BLO RMU-LGW ... FRO194 STN 03 STN-BLO (19 Nov) → RMU-LGW (02 Dec) | 13 Days 4 2022-01-20 2022-02-15 LGW-MAD MAD-STN ... UX1016 LGW 04 LGW-MAD (20 Jan) → MAD-STN (15 Feb) | 26 Days 5 2022-03-24 2022-03-30 STN-TFS TFS-LGW ... LS1663 STN 05 STN-TFS (24 Mar) → TFS-LGW (30 Mar) | 6 Days 6 2022-04-22 2022-04-27 STN-LIS LIS-STN ... FR1886 STN 06 STN-LIS (22 Apr) → LIS-STN (27 Apr) | 5 Days 7 2022-06-03 2022-06-14 STN-MAD MAD-STN ... FR5996 STN 07 STN-MAD (03 Jun) → MAD-STN (14 Jun) | 11 Days 8 2022-08-06 2022-09-21 LTN-CDT MAD-STN ... W94495 LTN 08 LTN-CDT (06 Aug) → MAD-STN (21 Sep) | 46 Days 9 2022-10-26 2022-11-15 STN-MAD MAD-LHR ... FR5996 STN 09 STN-MAD (26 Oct) → MAD-LHR (15 Nov) | 20 Days 10 2022-11-16 2022-12-26 LHR-KUL KUL-LHR ... MHOOO1 LHR 10 LHR-KUL (16 Nov) → KUL-LHR (26 Dec) | 40 Days 11 2023-02-04 2023-02-06 LTN-ATH ATH-LGW ... W94467 LTN 11 LTN-ATH (04 Feb) → ATH-LGW (06 Feb) | 2 Days 12 2023-02-21 2023-03-01 LTN-BLQ PEG-STN ... FR3406 LTN 12 LTN-BLQ (21 Feb) → PEG-STN (01 Mar) | 8 Days 13 2023-04-28 2023-05-09 STN-LPA LPA-STN ... FR2842 STN 13 STN-LPA (28 Apr) → LPA-STN (09 May) | 11 Days 14 2023-09-04 2023-09-11 LGW-MXP MXP-LGW ... W45785 LGW 14 LGW-MXP (04 Sep) → MXP-LGW (11 Sep) | 7 Days 15 2023-10-04 2023-10-17 STN-MAD MAD-STN ... FR5996 STN 15 STN-MAD (04 Oct) → MAD-STN (17 Oct) | 13 Days 16 2023-12-15 2024-01-10 STN-PEG MAD-STN ... FR2497 STN 16 STN-PEG (15 Dec) → MAD-STN (10 Jan) | 26 Days 17 2024-04-24 2024-05-02 LHR-MAD MAD-LHR ... BA0464 LHR 17 LHR-MAD (24 Apr) → MAD-LHR (02 May) | 8 Days 18 2024-06-26 2024-07-01 LHR-MAD MAD-LGW ... IB3177 LHR 18 LHR-MAD (26 Jun) → MAD-LGW (01 Jul) | 5 Days [19 rows x 9 columns] | .. code-block:: default :lineno-start: 9 import pandas as pd import plotly.express as px from pathlib import Path try: __file__ TERMINAL = True except: TERMINAL = False def display_flights_plotly_final(df): """ Generates an interactive flight schedule plot ensuring each trip is on its own unique row. Includes custom text labels, month lines, and alternating background shading. """ # --- 1. Data Pre-processing --- df = df.copy() # Force columns to datetime, turning any errors into NaT (Not a Time) df['Outbound Date'] = pd.to_datetime(df['Outbound Date'], errors='coerce') df['Inbound Date'] = pd.to_datetime(df['Inbound Date'], errors='coerce') # Strip the time component to prevent rendering glitches df['Outbound Date'] = df['Outbound Date'].dt.normalize() df['Inbound Date'] = df['Inbound Date'].dt.normalize() # Compute extra information df['Days Difference'] = (df['Inbound Date'] - df['Outbound Date']).dt.days df['Departure Airport'] = df['Outbound Ports'].apply(lambda x: x.split('-')[0]) # Sort by date and reset the index. This index (0, 1, 2...) will be the # unique y-axis position for each trip, guaranteeing one row per trip. df = df.sort_values('Outbound Date', ascending=True).reset_index(drop=True) # Hack: create y_axes_str to control order. # Calculate the required width (e.g., 2 for up to 99 items, 3 for up to 999) pad_width = len(str(len(df))) df['y_axis_str'] = df.index.astype(str).str.zfill(pad_width) # Create the label for annotations and hovering df['Voyage Label'] = df.apply( lambda row: f"{row['Outbound Ports']} ({row['Outbound Date'].strftime('%d %b')}) → " f"{row['Inbound Ports']} ({row['Inbound Date'].strftime('%d %b')}) | " f"{row['Days Difference']} Days", axis=1 ) # Show DataFrame to plot print("\nRoundtrips:") print(df) # --- 2. Generate Background Shapes --- shapes = [] min_date = df['Outbound Date'].min().normalize() max_date = df['Inbound Date'].max().normalize() + pd.DateOffset(months=1) month_starts = pd.date_range(start=min_date, end=max_date, freq='MS') for i, month_start in enumerate(month_starts): shapes.append({ 'type': 'line', 'xref': 'x', 'yref': 'paper', 'x0': month_start, 'y0': 0, 'x1': month_start, 'y1': 1, 'line': {'color': 'Gainsboro', 'width': 1, 'dash': 'dot'}, 'layer': 'below' }) if i % 2 == 0: shapes.append({ 'type': 'rect', 'xref': 'x', 'yref': 'paper', 'x0': month_start, 'y0': 0, 'x1': month_start + pd.DateOffset(months=1), 'y1': 1, 'fillcolor': 'LightGray', 'opacity': 0.1, 'line': {'width': 0}, 'layer': 'below' }) # --- 3. Generate Annotations for Text Labels --- annotations = [] for index, row in df.iterrows(): annotations.append( dict( x=row['Inbound Date'], y=row['y_axis_str'], #index, # Use the unique numerical index for the y-position text=f" {row['Voyage Label']}", showarrow=False, xanchor='left', yanchor='middle', align='left' ) ) # --- 4. Core Plotting --- fig = px.timeline( df, x_start="Outbound Date", x_end="Inbound Date", y='y_axis_str', #df.index, # KEY FIX: Use the unique index for the y-axis color="Departure Airport", hover_name="Voyage Label", hover_data={'Days Difference': True}, title=f"Voyage Durations (Total Abroad: {df['Days Difference'].sum()} days)", opacity=1.0, category_orders={"y_axis_str": df.y_axis_str.tolist()[::-1]}, color_discrete_sequence=px.colors.qualitative.Pastel ) # --- 5. Final Layout Updates --- fig.update_layout( plot_bgcolor='white', xaxis_title="Date", legend_title="Departure Airport", shapes=shapes, annotations=annotations, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ) ) # Hide the meaningless y-axis numbers and title fig.update_yaxes( autorange="reversed", showticklabels=False, title_text="" ) if not TERMINAL: fig.update_yaxes(automargin=False) fig.update_layout( title=dict( x=0.5, # Center the title y=0.95, # Move it up slightly xanchor='center', yanchor='top' ), margin=dict(l=0, r=20, t=110, b=20) ) # We could adjust x-axis to get use more space. fig.show() # Show from plotly.io import show show(fig) # ------------------------------------------------------- # Main # ------------------------------------------------------- # Libraries from pathlib import Path # Configuration id = '1085721' out_path = Path(f'./outputs/{id}') try: flight_df = pd.read_json(out_path / 'roundtrips.json') flight_df['Outbound Date'] = pd.to_datetime(flight_df['Outbound Date'], unit='ms') flight_df['Inbound Date'] = pd.to_datetime(flight_df['Inbound Date'], unit='ms') except FileNotFoundError: print(f"Error: File 'roundtrips.json' not found. Displaying sample data.") sample_data = [ {"Outbound Date": "2024-01-15", "Inbound Date": "2024-02-23", "Outbound Ports": "LHR-JFK", "Inbound Ports": "JFK-LHR", "Voyage Code": "VS003"}, {"Outbound Date": "2024-03-05", "Inbound Date": "2024-03-20", "Outbound Ports": "LGW-BCN", "Inbound Ports": "BCN-LGW", "Voyage Code": "BA2712"}, {"Outbound Date": "2024-04-20", "Inbound Date": "2024-05-15", "Outbound Ports": "STN-FCO", "Inbound Ports": "FCO-STN", "Voyage Code": "FR123"}, {"Outbound Date": "2024-06-10", "Inbound Date": "2024-06-25", "Outbound Ports": "LHR-BOS", "Inbound Ports": "BOS-LHR", "Voyage Code": "VS011"} ] flight_df = pd.DataFrame(sample_data) flight_df['Outbound Date'] = pd.to_datetime(flight_df['Outbound Date']) flight_df['Inbound Date'] = pd.to_datetime(flight_df['Inbound Date']) display_flights_plotly_final(flight_df) .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 6.257 seconds) .. _sphx_glr_download__examples_ukvi-trips_plot_main04.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_main04.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_main04.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_