I am working on a dashboard using Shiny for Python and Plotly Express. I am trying to create a Gantt chart (using px.timeline) to visualize the operating periods of different boilers (ON/OFF states).
The Goal: I want to display a horizontal bar chart where the X-axis represents the timeline (Date and Time) and the bars represent the intervals where a boiler was active ("ON").
The Problem: The X-axis on the chart is not rendering the dates and times correctly. Even though I am passing datetime objects and setting the tickformat, the axis labels seem incorrect (or are not respecting the format).
My Data Transformation: I created a helper function create_gantt_data to transform my time-series data (minute-by-minute snapshots) into a DataFrame of intervals with fecha_inicio (start) and fecha_fin (end). This logic seems to work fine and returns the correct intervals:
def prepare_dataframe(df):
# Map original CSV column names to internal aliases for easier access
df.rename(columns={
'Bomba Calor - Temperatura de Aire (°C)': 'temp_aire',
'Bomba Calor - Temperatura Entrada (°C)': 'temp_entrada',
'Bomba Calor - Temperatura Salida (°C)' : 'temp_salida',
'Bomba Calor - Estado Caldera 2 (estado)':'estado_caldera2',
'Bomba Calor - Estado Caldera 1 (estado)': 'estado_caldera1',
'Bomba Calor - Estado Bomba de Calor (estado)': 'estado_bomba_calor'
}, inplace=True, errors='raise') # Using 'raise' to enforce strict checking (errors if columns are missing)
if ('timestamp' in df.columns):
df['timestamp'] = pd.to_datetime(df['timestamp'], format="%d-%m-%y %H:%M").astype("datetime64[s]")
# Set 'timestamp' as the index to enable efficient time-based filtering
df.set_index('timestamp', inplace=True)
df.sort_index(inplace=True) # Sort the index chronologically (required for .loc slicing)
elif ('Fecha' in df.columns and 'Hora' in df.columns):
# If Date and Time come in separate columns, combine them
df['timestamp'] = pd.to_datetime(df['Fecha'] + ' ' + df['Hora'], format="%d-%m-%y %H:%M").astype("datetime64[s]")
# Set 'timestamp' as the index to enable efficient time-based filtering
df.set_index('timestamp', inplace=True)
df.sort_index(inplace=True) # Sort the index chronologically (required for .loc slicing)
final_columns = [
'temp_aire', 'temp_entrada', 'temp_salida',
'estado_caldera2', 'estado_caldera1', 'estado_bomba_calor'
]
existing_columns = []
for col in final_columns:
if col in df.columns:
existing_columns.append(col)
return df[existing_columns]
def create_gantt_data(df_recorte):
gantt_rows = []
for col in ['estado_caldera1', 'estado_caldera2', 'estado_bomba_calor']:
serie = df_recorte[col]
# 1. Detect state changes
cambios = serie.diff()
# 2. Identify turn-on and turn-off timestamps based on diff
turn_on = serie[cambios == 1.0].index
turn_off = serie[cambios == -1.0].index
# 3. Handle Edge Cases
# If it starts ON (diff misses this because there is no previous value)
if serie.iloc[0] == 1.0:
# Prepend the first timestamp to turn_on
turn_on = serie.index[:1].union(turn_on)
# If it ends ON (it never turned off within the selected range)
if serie.iloc[-1] == 1.0:
# Append the last timestamp to turn_off
turn_off = turn_off.union(serie.index[-1:])
# 4. Create the intervals
# zip() pairs the first ON with the corresponding first OFF, etc.
for start, end in zip(turn_on, turn_off):
gantt_rows.append({
'caldera': col,
'fecha_inicio': start,
'fecha_fin': end
})
return pd.DataFrame(gantt_rows)
Renders a timeline chart (Gantt style) to visualize the ON intervals of each boiler.
with ui.card(style="margin-top: 20px;"):
ui.tags.h4("Gráfico de Encendido de Calderas")
@render_plotly
def show_calderas():
data = data_for_timeline()
fig = px.timeline(
data_frame=data,
x_start='fecha_inicio',
height=500,
x_end='fecha_fin',
template="simple_white",
y="caldera",
color="caldera",
title="Tiempos de Encendido por Caldera",
labels={
"fecha_inicio": "Inicio de Operación",
"fecha_fin": "Fin de Operación",
"caldera": "Calderas"
}
)
fig.update_layout(
xaxis_title="Fecha y Hora",
yaxis_title="Calderas",
xaxis=dict(tickformat="%d-%m-%Y %H:%M")
)
fig.update_xaxes(
tickangle=60,
nticks=20,
)
return fig
example of csv please note that I am dynamically converting to timestamps:
Fecha,Hora,Bomba Calor - Temperatura de Aire (°C),Bomba Calor - Temperatura Entrada (°C),Bomba Calor - Temperatura Salida (°C),Bomba Calor - Estado Caldera 2 (estado),Bomba Calor - Estado Caldera 1 (estado),Bomba Calor - Estado Bomba de Calor (estado)
04-10-25,00:01,22.2,63.4,63.4,0.0,1.0,0.0
04-10-25,00:11,21.9,61.8,61.7,0.0,1.0,1.0
04-10-25,00:21,21.7,60.3,60.3,1.0,1.0,0.0
04-10-25,00:30,22.2,63.4,63.4,1.0,0.0,0.0
04-10-25,00:41,21.9,61.8,61.7,1.0,0.0,1.0
04-10-25,00:51,21.7,60.3,60.3,0.0,1.0,0.0
04-10-25,00:55,22.2,63.4,63.4,0.0,1.0,1.0
04-10-25,01:03,21.9,61.8,61.7,0.0,0.0,1.0
04-10-25,01:10,21.7,60.3,60.3,0.0,0.0,1.0
