|
1 | | -import { CONTOUR, HMC_SAMPLER } from './plotConfig.json'; |
| 1 | +import { CONTOUR, HMC_SAMPLER, TRACE_PLOT } from './plotConfig.json'; |
2 | 2 |
|
3 | 3 | /** |
4 | 4 | * Creates a Plotly contour trace configuration |
@@ -142,3 +142,94 @@ export function createSamplesTrace( |
142 | 142 | hovertemplate: 'Sample<br>x: %{x:.2f}<br>y: %{y:.2f}<extra></extra>', |
143 | 143 | }; |
144 | 144 | } |
| 145 | + |
| 146 | +/** |
| 147 | + * Converts a hex color to rgba string |
| 148 | + * @param {string} hex - Hex color string (e.g., "#ff0000") |
| 149 | + * @param {number} alpha - Alpha value (0-1) |
| 150 | + * @returns {string} Rgba color string |
| 151 | + */ |
| 152 | +function hexToRgba(hex, alpha) { |
| 153 | + const r = parseInt(hex.slice(1, 3), 16); |
| 154 | + const g = parseInt(hex.slice(3, 5), 16); |
| 155 | + const b = parseInt(hex.slice(5, 7), 16); |
| 156 | + return `rgba(${r}, ${g}, ${b}, ${alpha})`; |
| 157 | +} |
| 158 | + |
| 159 | +/** |
| 160 | + * Creates Plotly traces for trace plots (iteration vs value) |
| 161 | + * @param {Array<{x: number, y: number}>} samples - Array of accepted sample points |
| 162 | + * @param {string} axis - 'x' or 'y' to plot |
| 163 | + * @param {number} burnIn - Number of samples to treat as burn-in |
| 164 | + * @param {string} [color] - Color for the valid samples |
| 165 | + * @param {string} [name] - Name for the valid samples trace |
| 166 | + * @returns {object[]} Array of Plotly trace objects (burn-in and valid) |
| 167 | + */ |
| 168 | +export function createTracePlotTrace( |
| 169 | + samples, |
| 170 | + axis, |
| 171 | + burnIn = 0, |
| 172 | + color = HMC_SAMPLER.styles.primaryColor, |
| 173 | + name = 'Trace' |
| 174 | +) { |
| 175 | + if (!samples || !Array.isArray(samples) || samples.length === 0) { |
| 176 | + return []; |
| 177 | + } |
| 178 | + |
| 179 | + const traces = []; |
| 180 | + const validOpacity = 1.0; |
| 181 | + const burnInOpacity = TRACE_PLOT.styles.burnInOpacity; |
| 182 | + const lineWidth = TRACE_PLOT.styles.lineWidth; |
| 183 | + |
| 184 | + // Split samples into burn-in and valid |
| 185 | + let burnInSamples = []; |
| 186 | + let validSamples = []; |
| 187 | + |
| 188 | + if (burnIn > 0) { |
| 189 | + // If we have valid samples after burn-in, include the first one in burn-in set to connect lines |
| 190 | + const endIndex = samples.length > burnIn ? burnIn + 1 : burnIn; |
| 191 | + burnInSamples = samples.slice(0, endIndex); |
| 192 | + validSamples = samples.slice(burnIn); |
| 193 | + } else { |
| 194 | + validSamples = samples; |
| 195 | + } |
| 196 | + |
| 197 | + // Helper to create a single trace part |
| 198 | + const createSubTrace = (data, startIndex, opacity, traceName, showLegend) => { |
| 199 | + const iterations = data.map((_, i) => i + startIndex); |
| 200 | + const values = data.map((p) => p[axis]); |
| 201 | + |
| 202 | + // Use RGBA for color to ensure opacity works reliably on lines |
| 203 | + const traceColor = opacity < 1 ? hexToRgba(color, opacity) : color; |
| 204 | + |
| 205 | + return { |
| 206 | + type: 'scatter', |
| 207 | + mode: 'lines', |
| 208 | + x: iterations, |
| 209 | + y: values, |
| 210 | + line: { |
| 211 | + color: traceColor, |
| 212 | + width: lineWidth, |
| 213 | + }, |
| 214 | + // Remove top-level opacity to rely on rgba color |
| 215 | + // opacity: opacity, |
| 216 | + name: traceName, |
| 217 | + showlegend: showLegend, |
| 218 | + hovertemplate: `Iter: %{x}<br>${axis}: %{y:.2f}<extra></extra>`, |
| 219 | + }; |
| 220 | + }; |
| 221 | + |
| 222 | + // Add burn-in trace |
| 223 | + if (burnInSamples.length > 0) { |
| 224 | + traces.push( |
| 225 | + createSubTrace(burnInSamples, 0, burnInOpacity, `${name} (Burn-in)`, true) |
| 226 | + ); |
| 227 | + } |
| 228 | + |
| 229 | + // Add valid samples trace |
| 230 | + if (validSamples.length > 0) { |
| 231 | + traces.push(createSubTrace(validSamples, burnIn, validOpacity, name, true)); |
| 232 | + } |
| 233 | + |
| 234 | + return traces; |
| 235 | +} |
0 commit comments