| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Otava Test Data Visualizer</title> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script> |
| <link rel="stylesheet" href="/static/css/style.css?v={{ version }}"> |
| </head> |
| <body> |
| <header> |
| <div class="header-content"> |
| <div class="header-left"> |
| <h1>Otava Test Data Visualizer</h1> |
| <p class="subtitle">Interactive visualization of change point detection with Apache Otava</p> |
| </div> |
| <div class="header-right"> |
| <button id="settings-btn" class="settings-btn" title="Settings"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="12" cy="12" r="3"></circle> |
| <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path> |
| </svg> |
| </button> |
| </div> |
| </div> |
| <p class="version">v{{ version }}</p> |
| </header> |
| |
| <!-- Settings Dialog --> |
| <div id="settings-dialog" class="settings-dialog hidden"> |
| <div class="settings-dialog-content"> |
| <div class="settings-dialog-header"> |
| <h3>Settings</h3> |
| <button id="settings-close-btn" class="settings-close-btn">×</button> |
| </div> |
| <div class="settings-dialog-body"> |
| <div class="settings-group"> |
| <label for="length-input">Data Length:</label> |
| <div class="slider-with-bounds"> |
| <input type="number" id="length-min" class="bound-input" value="50" min="10" max="5000" step="10" title="Min bound"> |
| <input type="range" id="length-slider" min="50" max="1000" value="{{ default_length }}" step="10"> |
| <input type="number" id="length-max" class="bound-input" value="1000" min="10" max="5000" step="10" title="Max bound"> |
| <input type="number" id="length-input" class="value-input" value="{{ default_length }}" min="50" max="1000" step="10"> |
| </div> |
| </div> |
| <div class="settings-group"> |
| <label for="seed-input">Random Seed:</label> |
| <input type="number" id="seed-input" class="seed-input" value="42" min="0" max="9999"> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <main> |
| <!-- Mode toggle stays visible in every mode --> |
| <section class="mode-toggle-section"> |
| <div class="section-header"> |
| <h3 class="section-title" id="mode-section-title">Select Pattern</h3> |
| <div class="mode-toggle"> |
| <button class="mode-btn active" data-mode="single" id="mode-single-btn">Single Pattern</button> |
| <button class="mode-btn" data-mode="mix" id="mode-mix-btn">Mix Patterns</button> |
| <button class="mode-btn" data-mode="dataset" id="mode-dataset-btn">Dataset</button> |
| </div> |
| </div> |
| </section> |
| |
| <!-- Generator Selection Grid --> |
| <section class="generator-grid-section"> |
| <div class="mix-info hidden" id="mix-info"> |
| <span class="mix-recipe" id="mix-recipe">Click patterns to add...</span> |
| <button class="clear-mix-btn" id="clear-mix-btn">Clear</button> |
| </div> |
| <div id="generator-grid" class="generator-grid"> |
| <!-- Generator tiles will be dynamically inserted here --> |
| </div> |
| </section> |
| |
| <!-- Dataset Mode: real-world preset or pasted series --> |
| <section class="dataset-section hidden" id="dataset-section"> |
| <div class="dataset-row"> |
| <label class="dataset-label"> |
| Source: |
| <select id="dataset-source"></select> |
| </label> |
| <p id="dataset-description" class="dataset-description"></p> |
| <label id="custom-input-wrapper" class="dataset-custom hidden"> |
| Custom series (numbers, comma- or whitespace-separated, or a JSON array): |
| <textarea id="custom-input" rows="4" placeholder="e.g. 100, 102, 99, 110, ..."></textarea> |
| </label> |
| </div> |
| <p class="dataset-hint"> |
| Pick which algorithms to overlay in the |
| <strong>Otava Analysis</strong> panel below. |
| </p> |
| <p class="dataset-status" id="dataset-status" hidden></p> |
| </section> |
| |
| <!-- Dynamic Parameters --> |
| <section class="controls top-controls hide-in-dataset" id="params-section"> |
| <div id="dynamic-params" class="dynamic-params"> |
| <!-- Dynamic parameters will be inserted here --> |
| </div> |
| </section> |
| |
| <!-- Stacked Charts Container --> |
| <section class="stacked-charts-container" id="stacked-charts-container"> |
| <!-- Charts will be dynamically inserted based on enabled analysis methods --> |
| </section> |
| |
| <!-- Analysis Controls Wrapper --> |
| <section class="analysis-wrapper"> |
| <h3 class="section-title">Analysis Controls</h3> |
| |
| <div class="analysis-toolbar"> |
| <div class="control-actions hide-in-dataset"> |
| <button id="generate-btn" class="btn-primary">Analyse</button> |
| <button id="show-all-btn" class="btn-secondary">Show All Graphs</button> |
| </div> |
| |
| <div class="control-group y-axis-group"> |
| <label for="y-min-input">Y-Axis Min:</label> |
| <div class="slider-with-bounds"> |
| <input type="number" id="y-min-bound-min" class="bound-input" value="0" min="-1000" max="1000" step="10" title="Min bound"> |
| <input type="range" id="y-min-slider" min="0" max="500" value="40" step="10"> |
| <input type="number" id="y-min-bound-max" class="bound-input" value="500" min="-1000" max="1000" step="10" title="Max bound"> |
| <input type="number" id="y-min-input" class="value-input" value="40" min="0" max="500" step="10"> |
| </div> |
| </div> |
| |
| <div class="control-group y-axis-group"> |
| <label for="y-max-input">Y-Axis Max:</label> |
| <div class="slider-with-bounds"> |
| <input type="number" id="y-max-bound-min" class="bound-input" value="0" min="-1000" max="1000" step="10" title="Min bound"> |
| <input type="range" id="y-max-slider" min="0" max="500" value="160" step="10"> |
| <input type="number" id="y-max-bound-max" class="bound-input" value="500" min="-1000" max="1000" step="10" title="Max bound"> |
| <input type="number" id="y-max-input" class="value-input" value="160" min="0" max="500" step="10"> |
| </div> |
| </div> |
| </div> |
| |
| <div class="analysis-panels"> |
| <!-- Otava Analysis Controls --> |
| <div class="controls otava-controls"> |
| <div class="panel-header"> |
| <span class="panel-title">Otava Analysis</span> |
| <button class="method-help-btn" data-method="otava" title="Learn about Otava">?</button> |
| </div> |
| |
| <div class="otava-algos" id="otava-algos"> |
| <div class="otava-algo" data-algo="split"> |
| <label class="otava-algo-row"> |
| <input type="checkbox" class="otava-algo-checkbox" |
| data-algo="split" id="otava-algo-split" checked> |
| split-edivisive <span class="algo-hint">(default)</span> |
| </label> |
| <div class="algo-params"> |
| <div class="control-group"> |
| <label for="window-len-split" class="param-label">Window Length</label> |
| <input type="number" id="window-len-split" data-algo-param="window_len" data-algo="split" value="{{ otava_defaults.window_len }}" min="5" max="100000" step="5"> |
| </div> |
| <div class="control-group"> |
| <div class="input-with-help"> |
| <label for="max-pvalue-split" class="param-label">Max P-Value</label> |
| <input type="number" id="max-pvalue-split" class="max-pvalue-input" data-algo-param="max_pvalue" data-algo="split" value="{{ otava_defaults.max_pvalue }}" min="0" max="1" step="0.001"> |
| <button type="button" class="param-help-btn" data-tooltip="Significance threshold for detecting changes. Lower values mean stricter detection (fewer false positives but may miss subtle changes). 0.05 is standard; use 0.00001 for very high confidence.">?</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="otava-algo" data-algo="orig"> |
| <label class="otava-algo-row"> |
| <input type="checkbox" class="otava-algo-checkbox" |
| data-algo="orig" id="otava-algo-orig"> |
| orig-edivisive <span class="algo-hint">(permutation test)</span> |
| </label> |
| <div class="algo-params"> |
| <div class="control-group"> |
| <div class="input-with-help"> |
| <label for="max-pvalue-orig" class="param-label">Max P-Value</label> |
| <input type="number" id="max-pvalue-orig" class="max-pvalue-input" data-algo-param="max_pvalue" data-algo="orig" value="{{ otava_defaults.max_pvalue }}" min="0" max="1" step="0.001"> |
| <button type="button" class="param-help-btn" data-tooltip="Significance threshold for detecting changes. Lower values mean stricter detection (fewer false positives but may miss subtle changes). 0.05 is standard; use 0.00001 for very high confidence.">?</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="otava-algo" data-algo="deterministic"> |
| <label class="otava-algo-row"> |
| <input type="checkbox" class="otava-algo-checkbox" |
| data-algo="deterministic" id="otava-algo-deterministic"> |
| deterministic-edivisive |
| <span class="algo-hint">(<a href="https://github.com/apache/otava/pull/154" target="_blank">PR #154</a>)</span> |
| </label> |
| <div class="algo-params"> |
| <div class="control-group"> |
| <div class="input-with-help"> |
| <label for="max-pvalue-deterministic" class="param-label">Max P-Value</label> |
| <input type="number" id="max-pvalue-deterministic" class="max-pvalue-input" data-algo-param="max_pvalue" data-algo="deterministic" value="{{ otava_defaults.max_pvalue }}" min="0" max="1" step="0.001"> |
| <button type="button" class="param-help-btn" data-tooltip="Significance threshold for detecting changes. Lower values mean stricter detection (fewer false positives but may miss subtle changes). 0.05 is standard; use 0.00001 for very high confidence.">?</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Method tutorial (hidden by default) --> |
| <div class="method-tutorial hidden" id="otava-tutorial"> |
| <p class="method-explanation"></p> |
| <div class="method-algorithm"> |
| <strong>Algorithm:</strong> |
| <pre></pre> |
| </div> |
| <div class="method-best-for"> |
| <strong>Best For:</strong> |
| <ul></ul> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Moving Average Analysis Controls --> |
| <div class="controls ma-controls hide-in-dataset"> |
| <div class="panel-header"> |
| <label for="run-ma-checkbox" class="panel-title">Moving Average Analysis</label> |
| <input type="checkbox" id="run-ma-checkbox"> |
| <button class="method-help-btn" data-method="moving_average" title="Learn about Moving Average">?</button> |
| </div> |
| <div class="panel-fields"> |
| <div class="control-group"> |
| <label for="ma-window-input" class="param-label">MA Window</label> |
| <input type="number" id="ma-window-input" value="10" min="3" max="50" step="1"> |
| </div> |
| <div class="control-group"> |
| <label for="ma-threshold-input" class="param-label">Threshold (σ)</label> |
| <div class="input-with-help"> |
| <input type="number" id="ma-threshold-input" value="2.0" min="0.5" max="5.0" step="0.1"> |
| <button type="button" class="param-help-btn" data-tooltip="How many standard deviations (σ) the mean difference must exceed to trigger detection. Higher values reduce false positives. Typical: 2.0-3.0.">?</button> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Method tutorial (hidden by default) --> |
| <div class="method-tutorial hidden" id="ma-tutorial"> |
| <p class="method-explanation"></p> |
| <div class="method-algorithm"> |
| <strong>Algorithm:</strong> |
| <pre></pre> |
| </div> |
| <div class="method-best-for"> |
| <strong>Best For:</strong> |
| <ul></ul> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Boundary Analysis Controls --> |
| <div class="controls boundary-controls hide-in-dataset"> |
| <div class="panel-header"> |
| <label for="run-boundary-checkbox" class="panel-title">Boundary Analysis</label> |
| <input type="checkbox" id="run-boundary-checkbox"> |
| <button class="method-help-btn" data-method="boundary" title="Learn about Boundary">?</button> |
| </div> |
| <div class="panel-fields"> |
| <div class="control-group"> |
| <label for="boundary-upper-input" class="param-label" data-tooltip="Values crossing above this threshold will be flagged. Set based on acceptable maximum for your metric (e.g., max response time SLA).">Upper Bound</label> |
| <input type="number" id="boundary-upper-input" value="115" min="0" max="500" step="5"> |
| </div> |
| <div class="control-group"> |
| <label for="boundary-lower-input" class="param-label" data-tooltip="Values crossing below this threshold will be flagged. Set based on acceptable minimum (e.g., minimum throughput requirement).">Lower Bound</label> |
| <input type="number" id="boundary-lower-input" value="85" min="0" max="500" step="5"> |
| </div> |
| </div> |
| |
| <!-- Method tutorial (hidden by default) --> |
| <div class="method-tutorial hidden" id="boundary-tutorial"> |
| <p class="method-explanation"></p> |
| <div class="method-algorithm"> |
| <strong>Algorithm:</strong> |
| <pre></pre> |
| </div> |
| <div class="method-best-for"> |
| <strong>Best For:</strong> |
| <ul></ul> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Threshold Based Alerts Controls --> |
| <div class="controls threshold-controls hide-in-dataset"> |
| <div class="panel-header"> |
| <label for="run-threshold-checkbox" class="panel-title">Threshold Alerts</label> |
| <input type="checkbox" id="run-threshold-checkbox"> |
| </div> |
| <div class="panel-fields"> |
| <div class="control-group"> |
| <label for="threshold-percent-input">Threshold (%)</label> |
| <input type="number" id="threshold-percent-input" value="5" min="0.1" max="100" step="0.5"> |
| </div> |
| <div class="control-group"> |
| <label for="threshold-offset-input">Compare Offset</label> |
| <input type="number" id="threshold-offset-input" value="1" min="1" max="50" step="1"> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Sliding Window Controls --> |
| <div class="controls sliding-window-controls hide-in-dataset"> |
| <div class="panel-header"> |
| <label for="run-sliding-window-checkbox" class="panel-title">Sliding Window</label> |
| <input type="checkbox" id="run-sliding-window-checkbox"> |
| </div> |
| <div class="panel-fields"> |
| <div class="control-group"> |
| <label for="sliding-window-size-input">Window Size (N)</label> |
| <input type="number" id="sliding-window-size-input" value="10" min="3" max="50" step="1"> |
| </div> |
| <div class="control-group"> |
| <label for="sliding-window-offset-input">Offset (M)</label> |
| <input type="number" id="sliding-window-offset-input" value="20" min="5" max="100" step="1"> |
| </div> |
| <div class="control-group"> |
| <label for="sliding-window-threshold-input">Threshold (%)</label> |
| <input type="number" id="sliding-window-threshold-input" value="5" min="0.1" max="100" step="0.5"> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Std Dev Controls --> |
| <div class="controls stddev-controls hide-in-dataset"> |
| <div class="panel-header"> |
| <label for="run-stddev-checkbox" class="panel-title">Std Dev</label> |
| <input type="checkbox" id="run-stddev-checkbox"> |
| </div> |
| <div class="panel-fields"> |
| <div class="control-group"> |
| <label for="stddev-window-input">Window (M)</label> |
| <input type="number" id="stddev-window-input" value="20" min="5" max="100" step="1"> |
| </div> |
| <div class="control-group"> |
| <label for="stddev-num-input">Std Devs (K)</label> |
| <input type="number" id="stddev-num-input" value="2" min="0.5" max="5" step="0.1"> |
| </div> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| <!-- Generator Info --> |
| <section class="generator-info hide-in-dataset" id="generator-info"> |
| <div class="generator-info-header"> |
| <div class="generator-info-main"> |
| <h3 id="generator-title">Constant</h3> |
| <p id="generator-description">Constant value: S = x, x, x, x...</p> |
| </div> |
| <div id="change-point-info" class="change-point-badge hidden"> |
| <span class="badge">Has Change Points</span> |
| </div> |
| <button id="toggle-tutorial-btn" class="generator-help-btn" title="Learn about this pattern">?</button> |
| </div> |
| |
| <!-- Generator Tutorial Panel --> |
| <div id="generator-tutorial-panel" class="tutorial-panel hidden"> |
| <div class="tutorial-section"> |
| <h4>Pattern Explanation</h4> |
| <p id="tutorial-explanation">-</p> |
| </div> |
| <div class="tutorial-section"> |
| <h4>Real-World Use Case</h4> |
| <p id="tutorial-use-case">-</p> |
| </div> |
| <div class="tutorial-section"> |
| <h4>Detection Notes</h4> |
| <p id="tutorial-detection-notes">-</p> |
| </div> |
| </div> |
| </section> |
| |
| <!-- Chart Legend --> |
| <section class="chart-legend hide-in-dataset"> |
| <div class="legend-item"> |
| <span class="legend-line ground-truth"></span> |
| <span>Ground Truth</span> |
| </div> |
| <div class="legend-item"> |
| <span class="legend-marker true-positive"></span> |
| <span>Exact Match (TP)</span> |
| </div> |
| <div class="legend-item"> |
| <span class="legend-marker close-match"></span> |
| <span>Close Match (CM)</span> |
| </div> |
| <div class="legend-item"> |
| <span class="legend-marker false-positive"></span> |
| <span>False Positive (FP)</span> |
| </div> |
| </section> |
| |
| <!-- Accuracy Metrics --> |
| <section class="accuracy-metrics hide-in-dataset" id="accuracy-metrics"> |
| <h3>Detection Accuracy</h3> |
| <table class="accuracy-table"> |
| <thead> |
| <tr> |
| <th>Method</th> |
| <th>TP</th> |
| <th>CM</th> |
| <th>FP</th> |
| <th>FN</th> |
| <th>Precision</th> |
| <th>Recall</th> |
| <th>F1</th> |
| </tr> |
| </thead> |
| <tbody id="accuracy-table-body"> |
| <!-- Rows will be dynamically inserted --> |
| </tbody> |
| </table> |
| </section> |
| |
| <!-- Basic Stats --> |
| <section class="stats" id="stats"> |
| <div class="stat-card"> |
| <h4>Data Points</h4> |
| <span id="stat-length">-</span> |
| </div> |
| <div class="stat-card"> |
| <h4>Mean</h4> |
| <span id="stat-mean">-</span> |
| </div> |
| <div class="stat-card"> |
| <h4>Std Dev</h4> |
| <span id="stat-std">-</span> |
| </div> |
| <div class="stat-card"> |
| <h4>Ground Truth CPs</h4> |
| <span id="stat-cp-truth">-</span> |
| </div> |
| <div class="stat-card"> |
| <h4>Otava Detected</h4> |
| <span id="stat-cp-detected">-</span> |
| </div> |
| </section> |
| |
| <!-- Change Points Comparison Table --> |
| <section class="change-points-detail hide-in-dataset" id="change-points-detail"> |
| <h3>Change Points Comparison</h3> |
| <div class="comparison-tables"> |
| <div class="table-wrapper"> |
| <h4>Ground Truth</h4> |
| <table id="truth-table"> |
| <thead> |
| <tr> |
| <th>Index</th> |
| <th>Type</th> |
| <th>Description</th> |
| <th>Matched?</th> |
| </tr> |
| </thead> |
| <tbody id="truth-table-body"> |
| </tbody> |
| </table> |
| </div> |
| <div class="table-wrapper"> |
| <h4>Otava Detected</h4> |
| <table id="detected-table"> |
| <thead> |
| <tr> |
| <th>Index</th> |
| <th>Mean Before</th> |
| <th>Mean After</th> |
| <th>P-Value</th> |
| <th>Status</th> |
| </tr> |
| </thead> |
| <tbody id="detected-table-body"> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </section> |
| |
| <!-- Dataset Mode: per-algorithm detected change-point table --> |
| <section class="dataset-results hidden" id="dataset-results"> |
| <h3>Detected change-point indices per algorithm</h3> |
| <table class="dataset-results-table"> |
| <thead> |
| <tr><th style="width:30%;">Algorithm</th><th style="width:8%;">Count</th><th>Indices</th></tr> |
| </thead> |
| <tbody id="dataset-results-body"></tbody> |
| </table> |
| </section> |
| |
| <!-- Multi-chart view for "Show All" --> |
| <section class="multi-chart-container hidden" id="multi-chart-container"> |
| <h2>All Test Patterns - Otava Comparison</h2> |
| <div class="summary-stats" id="summary-stats"> |
| <!-- Overall accuracy summary will be inserted here --> |
| </div> |
| <div class="chart-grid" id="chart-grid"> |
| <!-- Charts will be dynamically inserted --> |
| </div> |
| </section> |
| </main> |
| |
| <footer> |
| <p> |
| <a href="https://github.com/apache/otava" target="_blank">Apache Otava</a> | |
| Change point detection benchmarking and visualization |
| </p> |
| </footer> |
| |
| <script src="/static/js/app.js?v={{ version }}"></script> |
| </body> |
| </html> |