import plotly.graph_objects as go import pandas as pd import numpy as np def build_figure(dataframe: pd.DataFrame) -> go.Figure: fig = go.Figure() # Condition 1 fig.add_bar( y=dataframe["Condition 1"], x=dataframe["CASRN"], name="Condition 1", orientation="v", error_y=dict( type="data", symmetric=False, array=dataframe["Cond1_err_plus"], # +x direction arrayminus=dataframe["Cond1_err_minus"], # -x direction visible=True, ), marker=dict(color="red"), ) # Condition 2 fig.add_bar( y=dataframe["Condition 2"], x=dataframe["CASRN"], name="Condition 2", orientation="v", error_y=dict( type="data", symmetric=False, array=dataframe["Cond2_err_plus"], arrayminus=dataframe["Cond2_err_minus"], visible=True, ), marker=dict(color="blue"), ) #fig.add_hline( # y=0.9, # line_width=3, # line_color="green", # line_dash="longdash" #) # --- Compute data-driven x-range (include error bars) --- x1 = dataframe["Condition 1"].to_numpy(dtype=float) x2 = dataframe["Condition 2"].to_numpy(dtype=float) # --- Compute upper bounds including error bars --- x1_max = x1 - dataframe["Cond1_err_minus"].to_numpy(dtype=float) x2_max = x2 - dataframe["Cond2_err_minus"].to_numpy(dtype=float) # Piecewise maxima across the two conditions (array length = number of CASRN) piecewise_max = np.maximum(x1_max, x2_max) # Minimum of the piecewise maxima array (ignoring NaNs) min_of_piecewise_max = np.nanmin(piecewise_max) # Fallback if everything is NaN or non-finite if not np.isfinite(min_of_piecewise_max): min_of_piecewise_max = 1.0 # Minimum y = 3 orders of magnitude below that y_min = float(min_of_piecewise_max * 1e-1) # Ensure strictly positive for log scale y_min = float(np.maximum(y_min, np.nextafter(0.0, 1.0))) fig.update_layout( barmode="group", legend=dict( orientation="h", x=0.5, xanchor="center", y=1.08, font=dict(size=18) # ← legend text size ), yaxis=dict( title=dict( text=r"Fraction released", font=dict(size=18) # ← axis label size ), type="log", range=[np.log10(y_min), np.log10(1.)] ), xaxis=dict( title=dict( text=r"Representative boundary chemicals (CAS)", standoff=0, # adjust: try 0–10 depending on how close you want it font = dict(size=18) # ← axis label size ) ), margin=dict(l=50, r=50, t=20, b=20), autosize=False, height=600, width=800, ) fig.update_xaxes(tickfont=dict(size=14)) fig.update_yaxes(tickfont=dict(size=14)) return fig