Spaces:
Running
Running
| import plotly.graph_objects as go | |
| import plotly.io as pio | |
| import numpy as np | |
| import os | |
| import uuid | |
| """ | |
| Interactive line chart example (3 curves + live slider) | |
| The slider blends each curve from linear to exponential in real time (no mouseup required). | |
| This fragment is safe to insert multiple times on the page (unique IDs per instance). | |
| """ | |
| # Grid (x) and parameterization | |
| N = 240 | |
| x = np.linspace(0, 1, N) | |
| # Linear baselines (increasing) | |
| lin1 = 0.20 + 0.60 * x | |
| lin2 = 0.15 + 0.70 * x | |
| lin3 = 0.10 + 0.80 * x | |
| # Helper: normalized exponential on [0,1] | |
| def exp_norm(xv: np.ndarray, k: float) -> np.ndarray: | |
| return (np.exp(k * xv) - 1.0) / (np.exp(k) - 1.0) | |
| # Exponential counterparts (similar ranges) | |
| exp1 = 0.20 + 0.60 * exp_norm(x, 3.0) | |
| exp2 = 0.15 + 0.70 * exp_norm(x, 3.5) | |
| exp3 = 0.10 + 0.80 * exp_norm(x, 2.8) | |
| # Initial blend (alpha=0 ⇒ pure linear) | |
| alpha0 = 0.0 | |
| blend = lambda l, e, a: (1 - a) * l + a * e | |
| y1 = blend(lin1, exp1, alpha0) | |
| y2 = blend(lin2, exp2, alpha0) | |
| y3 = blend(lin3, exp3, alpha0) | |
| color_base = "#64748b" # slate-500 | |
| color_improved = "#2563eb" # blue-600 | |
| color_target = "#4b5563" # gray-600 (dash) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=y1, | |
| name="Baseline", | |
| mode="lines", | |
| line=dict(color=color_base, width=2, shape="spline", smoothing=0.6), | |
| hovertemplate="<b>%{fullData.name}</b><br>x=%{x:.2f}<br>y=%{y:.3f}<extra></extra>", | |
| showlegend=True, | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=y2, | |
| name="Improved", | |
| mode="lines", | |
| line=dict(color=color_improved, width=2, shape="spline", smoothing=0.6), | |
| hovertemplate="<b>%{fullData.name}</b><br>x=%{x:.2f}<br>y=%{y:.3f}<extra></extra>", | |
| showlegend=True, | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=y3, | |
| name="Target", | |
| mode="lines", | |
| line=dict(color=color_target, width=2, dash="dash"), | |
| hovertemplate="<b>%{fullData.name}</b><br>x=%{x:.2f}<br>y=%{y:.3f}<extra></extra>", | |
| showlegend=True, | |
| ) | |
| ) | |
| fig.update_layout( | |
| autosize=True, | |
| paper_bgcolor="rgba(0,0,0,0)", | |
| plot_bgcolor="rgba(0,0,0,0)", | |
| margin=dict(l=28, r=12, t=8, b=28), | |
| hovermode="x unified", | |
| hoverlabel=dict( | |
| bgcolor="white", | |
| font=dict(color="#111827", size=12), | |
| bordercolor="rgba(0,0,0,0.15)", | |
| align="left", | |
| namelength=-1, | |
| ), | |
| xaxis=dict( | |
| showgrid=False, | |
| zeroline=False, | |
| showline=True, | |
| linecolor="rgba(0,0,0,0.25)", | |
| linewidth=1, | |
| ticks="outside", | |
| ticklen=6, | |
| tickcolor="rgba(0,0,0,0.25)", | |
| tickfont=dict(size=12, color="rgba(0,0,0,0.55)"), | |
| title=None, | |
| automargin=True, | |
| fixedrange=True, | |
| ), | |
| yaxis=dict( | |
| showgrid=False, | |
| zeroline=False, | |
| showline=True, | |
| linecolor="rgba(0,0,0,0.25)", | |
| linewidth=1, | |
| ticks="outside", | |
| ticklen=6, | |
| tickcolor="rgba(0,0,0,0.25)", | |
| tickfont=dict(size=12, color="rgba(0,0,0,0.55)"), | |
| title=None, | |
| tickformat=".2f", | |
| rangemode="tozero", | |
| automargin=True, | |
| fixedrange=True, | |
| ), | |
| ) | |
| # Écrit le fragment de manière robuste à côté de ce fichier, dans src/fragments/line.html | |
| output_path = os.path.join(os.path.dirname(__file__), "fragments", "line.html") | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| # Injecte un petit script post-rendu pour arrondir les coins de la boîte de hover | |
| post_script = """ | |
| (function(){ | |
| function attach(gd){ | |
| function round(){ | |
| try { | |
| var root = gd && gd.parentNode ? gd.parentNode : document; | |
| var rects = root.querySelectorAll('.hoverlayer .hovertext rect'); | |
| rects.forEach(function(r){ r.setAttribute('rx', 8); r.setAttribute('ry', 8); }); | |
| } catch(e) {} | |
| } | |
| if (gd && gd.on) { | |
| gd.on('plotly_hover', round); | |
| gd.on('plotly_unhover', round); | |
| gd.on('plotly_relayout', round); | |
| } | |
| setTimeout(round, 0); | |
| } | |
| var plots = document.querySelectorAll('.js-plotly-plot'); | |
| plots.forEach(attach); | |
| })(); | |
| """ | |
| html_plot = pio.to_html( | |
| fig, | |
| include_plotlyjs=False, | |
| full_html=False, | |
| post_script=post_script, | |
| config={ | |
| "displayModeBar": False, | |
| "responsive": True, | |
| "scrollZoom": False, | |
| "doubleClick": False, | |
| "modeBarButtonsToRemove": [ | |
| "zoom2d", "pan2d", "select2d", "lasso2d", | |
| "zoomIn2d", "zoomOut2d", "autoScale2d", "resetScale2d", | |
| "toggleSpikelines" | |
| ], | |
| }, | |
| ) | |
| # Build a self-contained fragment with a live slider (no mouseup required) | |
| uid = uuid.uuid4().hex[:8] | |
| slider_id = f"line-ex-alpha-{uid}" | |
| container_id = f"line-ex-container-{uid}" | |
| slider_tpl = ''' | |
| <div id="__CID__"> | |
| __PLOT__ | |
| <div class="plotly_controls" style="margin-top:10px; display:flex; gap:14px; align-items:center;"> | |
| <label style="font-size:12px;color:rgba(0,0,0,.65); display:flex; align-items:center; gap:6px; white-space:nowrap;"> | |
| Dataset | |
| <select id="__DSID__" style="font-size:12px; padding:2px 6px;"> | |
| <option value="0">Dataset A</option> | |
| <option value="1">Dataset B</option> | |
| <option value="2">Dataset C</option> | |
| </select> | |
| </label> | |
| <label style="font-size:12px;color:rgba(0,0,0,.65);display:flex;align-items:center;gap:8px; flex:1;"> | |
| Nonlinearity | |
| <input id="__SID__" type="range" min="0" max="1" step="0.01" value="__A0__" style="flex:1;"> | |
| <span class="alpha-value">__A0__</span> | |
| </label> | |
| </div> | |
| </div> | |
| <script> | |
| (function(){ | |
| var container = document.getElementById('__CID__'); | |
| if(!container) return; | |
| var gd = container.querySelector('.js-plotly-plot'); | |
| var slider = document.getElementById('__SID__'); | |
| var dsSelect = document.getElementById('__DSID__'); | |
| var valueEl = container.querySelector('.alpha-value'); | |
| var N = __N__; | |
| var xs = Array.from({length: N}, function(_,i){ return i/(N-1); }); | |
| function expNorm(x,k){ return (Math.exp(k*x)-1)/(Math.exp(k)-1); } | |
| function blend(l,e,a){ return (1-a)*l + a*e; } | |
| var datasets = [ | |
| { curves: [ {o:0.20,s:0.60,k:3.0}, {o:0.15,s:0.70,k:3.5}, {o:0.10,s:0.80,k:2.8} ] }, | |
| { curves: [ {o:0.30,s:0.55,k:2.2}, {o:0.18,s:0.65,k:2.8}, {o:0.12,s:0.70,k:2.0} ] }, | |
| { curves: [ {o:0.10,s:0.85,k:3.8}, {o:0.12,s:0.80,k:3.2}, {o:0.08,s:0.90,k:3.0} ] } | |
| ]; | |
| var dsi = 0; | |
| function makeY(a){ | |
| var cs = datasets[dsi].curves; | |
| var y1 = xs.map(function(x){ return blend(cs[0].o + cs[0].s*x, cs[0].o + cs[0].s*expNorm(x,cs[0].k), a); }); | |
| var y2 = xs.map(function(x){ return blend(cs[1].o + cs[1].s*x, cs[1].o + cs[1].s*expNorm(x,cs[1].k), a); }); | |
| var y3 = xs.map(function(x){ return blend(cs[2].o + cs[2].s*x, cs[2].o + cs[2].s*expNorm(x,cs[2].k), a); }); | |
| return [y1,y2,y3]; | |
| } | |
| function apply(a){ | |
| var ys = makeY(a); | |
| Plotly.restyle(gd, {y:[ys[0]]}, [0]); | |
| Plotly.restyle(gd, {y:[ys[1]]}, [1]); | |
| Plotly.restyle(gd, {y:[ys[2]]}, [2]); | |
| if(valueEl) valueEl.textContent = a.toFixed(2); | |
| } | |
| var initA = parseFloat(slider.value)||0; | |
| slider.addEventListener('input', function(e){ apply(parseFloat(e.target.value)||0); }); | |
| dsSelect.addEventListener('change', function(e){ dsi = parseInt(e.target.value)||0; apply(parseFloat(slider.value)||0); }); | |
| setTimeout(function(){ apply(initA); }, 0); | |
| })(); | |
| </script> | |
| ''' | |
| slider_html = (slider_tpl | |
| .replace('__CID__', container_id) | |
| .replace('__SID__', slider_id) | |
| .replace('__A0__', f"{alpha0:.2f}") | |
| .replace('__N__', str(N)) | |
| .replace('__PLOT__', html_plot) | |
| ) | |
| fig.write_html("../app/src/fragments/line.html", | |
| include_plotlyjs=False, | |
| full_html=False, | |
| config={ | |
| 'displayModeBar': False, | |
| 'responsive': True, | |
| 'scrollZoom': False, | |
| }) | |