| 107 | |
| 108 | |
| 109 | def make_plotly(df, title, html_path, xcol, ycol, xlabel, ylabel, overlay): |
| 110 | import plotly.graph_objects as go |
| 111 | from plotly.subplots import make_subplots |
| 112 | |
| 113 | hover = ( |
| 114 | "T = %{customdata[0]:.3f} K<br>" |
| 115 | "p = %{customdata[1]:.4g} Pa<br>" |
| 116 | "rho = %{customdata[2]:.4g} mol/dm^3<br>" |
| 117 | "h = %{customdata[6]:.4g} J/mol<br>" |
| 118 | "s = %{customdata[7]:.4g} J/mol/K<br>" |
| 119 | "evals = %{customdata[3]:d}<br>" |
| 120 | "time = %{customdata[4]:.2f} us<br>" |
| 121 | "leg = %{customdata[5]}<extra></extra>" |
| 122 | ) |
| 123 | |
| 124 | def cd(d): |
| 125 | return np.stack([d["T_K"], d["p_Pa"], d["rho_moldm3"], d["evals"], d["microseconds"], d["method_label"], |
| 126 | d["h_Jmol"], d["s_JmolK"]], axis=-1) |
| 127 | |
| 128 | fig = make_subplots(rows=1, cols=3, shared_yaxes=True, |
| 129 | subplot_titles=("EOS evaluations (log)", "Solve time [us] (log)", "Cascade leg"), horizontal_spacing=0.06) |
| 130 | ev_c, ev_tv, ev_tt = log_color(df["evals"]) |
| 131 | us_c, us_tv, us_tt = log_color(df["microseconds"]) |
| 132 | fig.add_trace(go.Scattergl(x=df[xcol], y=df[ycol], mode="markers", |
| 133 | marker=dict(size=4, color=ev_c, colorscale="Viridis", |
| 134 | colorbar=dict(title="evals", x=0.28, len=0.9, tickvals=ev_tv, ticktext=ev_tt)), |
| 135 | customdata=cd(df), hovertemplate=hover, name="evals"), row=1, col=1) |
| 136 | fig.add_trace(go.Scattergl(x=df[xcol], y=df[ycol], mode="markers", |
| 137 | marker=dict(size=4, color=us_c, colorscale="Inferno", |
| 138 | colorbar=dict(title="us", x=0.63, len=0.9, tickvals=us_tv, ticktext=us_tt)), |
| 139 | customdata=cd(df), hovertemplate=hover, name="time"), row=1, col=2) |
| 140 | for m, label in METHOD_LABEL.items(): |
| 141 | sub = df[df["method"] == m] |
| 142 | if len(sub): |
| 143 | fig.add_trace(go.Scattergl(x=sub[xcol], y=sub[ycol], mode="markers", marker=dict(size=4, color=METHOD_COLOR[m]), |
| 144 | customdata=cd(sub), hovertemplate=hover, name=label), row=1, col=3) |
| 145 | |
| 146 | # Phase boundary overlaid on every panel. Scattergl (same WebGL layer as the |
| 147 | # markers, added last -> on top) drawn as a black halo under a yellow line so |
| 148 | # it stays visible over Viridis, Inferno, AND the white categorical panel. |
| 149 | if overlay: |
| 150 | for c in (1, 2, 3): |
| 151 | for (lx, ly, lname) in overlay["lines"]: |
| 152 | fig.add_trace(go.Scattergl(x=lx, y=ly, mode="lines", line=dict(color="black", width=6), |
| 153 | legendgroup="bound", showlegend=False, hoverinfo="skip"), row=1, col=c) |
| 154 | fig.add_trace(go.Scattergl(x=lx, y=ly, mode="lines", line=dict(color="yellow", width=2.5), |
| 155 | name=lname, legendgroup="bound", showlegend=(c == 1), |
| 156 | hovertemplate=lname + ": x=%{x:.4g}, y=%{y:.4g}<extra></extra>"), row=1, col=c) |
| 157 | cx, cy = overlay["crit"] |
| 158 | fig.add_trace(go.Scattergl(x=[cx], y=[cy], mode="markers", |
| 159 | marker=dict(color="yellow", size=11, symbol="x", line=dict(color="black", width=1.5)), |
| 160 | name="critical point", legendgroup="cp", showlegend=(c == 1), |
| 161 | hovertemplate="critical point<extra></extra>"), row=1, col=c) |
| 162 | |
| 163 | for c in (1, 2, 3): |
| 164 | fig.update_xaxes(title_text=xlabel, row=1, col=c) |
| 165 | fig.update_yaxes(title_text=ylabel, row=1, col=1) |
| 166 | fig.update_layout(title=title, width=1500, height=620, legend=dict(x=1.0, y=1.0)) |