blob: a44e724ac0b3dd0c6cf02b6d24876791c41fbd56 [file]
<!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">&times;</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>