660 lines
21 KiB
PHP
660 lines
21 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('display_errors', '1');
|
|
error_reporting(E_ALL);
|
|
session_start();
|
|
|
|
$config = require __DIR__ . '/../config/database.php';
|
|
|
|
$dsn = sprintf(
|
|
'mysql:host=%s;dbname=%s;charset=%s',
|
|
$config['host'],
|
|
$config['dbname'],
|
|
$config['charset'] ?? 'utf8mb4'
|
|
);
|
|
|
|
$pdo = new PDO(
|
|
$dsn,
|
|
$config['user'],
|
|
$config['pass'],
|
|
[
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
]
|
|
);
|
|
|
|
$loggedIn = isset($_SESSION['user_id']);
|
|
$pageTitle = 'T CrB Lichtkurve';
|
|
$headerIntroPre = 'Variabler Stern';
|
|
$headerIntroTitle = 'T CrB Lichtkurve';
|
|
$headerIntroSub = 'AAVSO-WebObs-Daten mit waehlbaren Filtern in einem gemeinsamen Plotly-Diagramm.';
|
|
$errors = [];
|
|
$eruptionIntervalDays = 227.5687;
|
|
$lastEruptionUtc = new DateTimeImmutable('1946-02-09 00:00:00', new DateTimeZone('UTC'));
|
|
$todayUtc = new DateTimeImmutable('now', new DateTimeZone('UTC'));
|
|
$secondsPerDay = 86400;
|
|
$eruptionIntervalSeconds = (int) round($eruptionIntervalDays * $secondsPerDay);
|
|
$elapsedSecondsSinceLastEruption = max(0, $todayUtc->getTimestamp() - $lastEruptionUtc->getTimestamp());
|
|
$elapsedIntervals = (int) floor($elapsedSecondsSinceLastEruption / $eruptionIntervalSeconds);
|
|
$nextPossibleEruptionUtc = $lastEruptionUtc->modify('+' . (($elapsedIntervals + 1) * $eruptionIntervalSeconds) . ' seconds');
|
|
$daysUntilNextEruption = ($nextPossibleEruptionUtc->getTimestamp() - $todayUtc->getTimestamp()) / $secondsPerDay;
|
|
|
|
function h(?string $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function formatGermanDate(?string $value, bool $includeTime = true): string
|
|
{
|
|
if ($value === null || trim($value) === '') {
|
|
return '—';
|
|
}
|
|
|
|
try {
|
|
$date = new DateTimeImmutable($value, new DateTimeZone('UTC'));
|
|
} catch (Exception) {
|
|
return $value;
|
|
}
|
|
|
|
return $date->format($includeTime ? 'd.m.Y H:i:s' : 'd.m.Y');
|
|
}
|
|
|
|
$filterRows = $pdo->query("
|
|
SELECT `filter_code`, COUNT(*) AS `entry_count`
|
|
FROM `aavso_tcrb_observations`
|
|
WHERE `filter_code` IS NOT NULL
|
|
AND `filter_code` <> ''
|
|
GROUP BY `filter_code`
|
|
ORDER BY `filter_code` ASC
|
|
")->fetchAll();
|
|
|
|
$availableFilters = [];
|
|
foreach ($filterRows as $filterRow) {
|
|
$filterCode = trim((string) ($filterRow['filter_code'] ?? ''));
|
|
if ($filterCode === '') {
|
|
continue;
|
|
}
|
|
|
|
$availableFilters[] = [
|
|
'code' => $filterCode,
|
|
'count' => (int) ($filterRow['entry_count'] ?? 0),
|
|
];
|
|
}
|
|
|
|
$selectedFilters = array_values(array_unique(array_filter(array_map(
|
|
static fn ($value): string => trim((string) $value),
|
|
is_array($_GET['filters'] ?? null) ? $_GET['filters'] : []
|
|
), static fn (string $value): bool => $value !== '')));
|
|
|
|
if (empty($selectedFilters)) {
|
|
$selectedFilters = in_array('V', array_column($availableFilters, 'code'), true)
|
|
? ['V']
|
|
: array_column($availableFilters, 'code');
|
|
}
|
|
|
|
$selectedFilters = array_values(array_intersect($selectedFilters, array_column($availableFilters, 'code')));
|
|
|
|
if (empty($availableFilters)) {
|
|
$errors[] = 'In der Tabelle aavso_tcrb_observations sind noch keine T-CrB-Daten vorhanden.';
|
|
}
|
|
|
|
$observations = [];
|
|
$summary = [
|
|
'count' => 0,
|
|
'min_utc' => null,
|
|
'max_utc' => null,
|
|
'min_mag' => null,
|
|
'max_mag' => null,
|
|
];
|
|
|
|
if (empty($errors) && !empty($availableFilters)) {
|
|
$queryFilters = array_column($availableFilters, 'code');
|
|
$placeholders = implode(',', array_fill(0, count($queryFilters), '?'));
|
|
$stmt = $pdo->prepare("
|
|
SELECT
|
|
`observed_at_utc`,
|
|
`jd`,
|
|
`magnitude`,
|
|
`magnitude_error`,
|
|
`filter_code`,
|
|
`observer_code`,
|
|
`notes_raw`
|
|
FROM `aavso_tcrb_observations`
|
|
WHERE `filter_code` IN ({$placeholders})
|
|
ORDER BY `observed_at_utc` ASC, `jd` ASC
|
|
");
|
|
$stmt->execute($queryFilters);
|
|
$rows = $stmt->fetchAll();
|
|
|
|
foreach ($rows as $row) {
|
|
$observedAtUtc = (string) ($row['observed_at_utc'] ?? '');
|
|
$magnitude = isset($row['magnitude']) ? (float) $row['magnitude'] : null;
|
|
if ($observedAtUtc === '' || $magnitude === null) {
|
|
continue;
|
|
}
|
|
|
|
$observations[] = [
|
|
'observed_at_utc' => $observedAtUtc,
|
|
'jd' => isset($row['jd']) ? (float) $row['jd'] : null,
|
|
'magnitude' => $magnitude,
|
|
'magnitude_error' => $row['magnitude_error'] !== null ? (float) $row['magnitude_error'] : null,
|
|
'filter_code' => (string) ($row['filter_code'] ?? ''),
|
|
'observer_code' => (string) ($row['observer_code'] ?? ''),
|
|
'notes_raw' => (string) ($row['notes_raw'] ?? ''),
|
|
];
|
|
|
|
$summary['count']++;
|
|
$summary['min_utc'] = $summary['min_utc'] === null || $observedAtUtc < $summary['min_utc'] ? $observedAtUtc : $summary['min_utc'];
|
|
$summary['max_utc'] = $summary['max_utc'] === null || $observedAtUtc > $summary['max_utc'] ? $observedAtUtc : $summary['max_utc'];
|
|
$summary['min_mag'] = $summary['min_mag'] === null || $magnitude < $summary['min_mag'] ? $magnitude : $summary['min_mag'];
|
|
$summary['max_mag'] = $summary['max_mag'] === null || $magnitude > $summary['max_mag'] ? $magnitude : $summary['max_mag'];
|
|
}
|
|
}
|
|
|
|
if (empty($errors) && empty($observations)) {
|
|
$errors[] = 'Fuer die ausgewaehlten Filter wurden keine Daten gefunden.';
|
|
}
|
|
|
|
$filterMetadata = [];
|
|
$filterMetadataPath = __DIR__ . '/../config/filter_colors.json';
|
|
if (is_file($filterMetadataPath)) {
|
|
$filterMetadataRaw = file_get_contents($filterMetadataPath);
|
|
$filterMetadataDecoded = is_string($filterMetadataRaw) ? json_decode($filterMetadataRaw, true) : null;
|
|
if (is_array($filterMetadataDecoded)) {
|
|
$filterMetadata = $filterMetadataDecoded;
|
|
}
|
|
}
|
|
|
|
$filterPalette = [];
|
|
$filterDescriptions = [];
|
|
foreach ($filterMetadata as $filterCode => $filterConfig) {
|
|
if (!is_array($filterConfig)) {
|
|
continue;
|
|
}
|
|
|
|
$filterPalette[$filterCode] = (string) ($filterConfig['color'] ?? '');
|
|
$filterDescriptions[$filterCode] = (string) ($filterConfig['description'] ?? $filterCode);
|
|
}
|
|
|
|
$chartDatasets = [];
|
|
foreach ($availableFilters as $availableFilter) {
|
|
$filterCode = $availableFilter['code'];
|
|
$chartDatasets[$filterCode] = [
|
|
'filter' => $filterCode,
|
|
'color' => $filterPalette[$filterCode] ?? sprintf('#%06X', crc32($filterCode) & 0xFFFFFF),
|
|
'points' => [],
|
|
];
|
|
}
|
|
|
|
foreach ($observations as $observation) {
|
|
$filterCode = $observation['filter_code'];
|
|
if (!isset($chartDatasets[$filterCode])) {
|
|
continue;
|
|
}
|
|
|
|
$chartDatasets[$filterCode]['points'][] = $observation;
|
|
}
|
|
|
|
$chartDatasets = array_values(array_filter($chartDatasets, static fn (array $dataset): bool => !empty($dataset['points'])));
|
|
|
|
$plotlyTraces = [];
|
|
foreach ($chartDatasets as $dataset) {
|
|
$xValues = [];
|
|
$yValues = [];
|
|
$errorValues = [];
|
|
$customData = [];
|
|
$hasErrors = false;
|
|
|
|
foreach ($dataset['points'] as $point) {
|
|
$xValues[] = (string) $point['observed_at_utc'];
|
|
$yValues[] = (float) $point['magnitude'];
|
|
$errorValue = $point['magnitude_error'] !== null ? (float) $point['magnitude_error'] : 0.0;
|
|
$errorValues[] = $errorValue;
|
|
if ($errorValue > 0) {
|
|
$hasErrors = true;
|
|
}
|
|
|
|
$customData[] = [
|
|
$point['jd'],
|
|
$point['observer_code'] !== '' ? $point['observer_code'] : '—',
|
|
$point['magnitude_error'] !== null ? number_format((float) $point['magnitude_error'], 3, '.', '') : '—',
|
|
];
|
|
}
|
|
|
|
$plotlyTraces[] = [
|
|
'type' => 'scatter',
|
|
'mode' => 'lines+markers',
|
|
'name' => $dataset['filter'],
|
|
'x' => $xValues,
|
|
'y' => $yValues,
|
|
'line' => [
|
|
'color' => $dataset['color'],
|
|
'width' => 2,
|
|
],
|
|
'marker' => [
|
|
'color' => $dataset['color'],
|
|
'size' => 7,
|
|
'line' => [
|
|
'color' => '#0f172a',
|
|
'width' => 1,
|
|
],
|
|
],
|
|
'error_y' => [
|
|
'type' => 'data',
|
|
'array' => $errorValues,
|
|
'visible' => $hasErrors,
|
|
'thickness' => 1.1,
|
|
'width' => 3,
|
|
'color' => $dataset['color'],
|
|
],
|
|
'customdata' => $customData,
|
|
'hovertemplate' => 'Filter %{fullData.name}<br>'
|
|
. 'Magnitude %{y:.3f}<br>'
|
|
. 'Fehler %{customdata[2]}<br>'
|
|
. 'UTC %{x}<br>'
|
|
. 'JD %{customdata[0]}<br>'
|
|
. 'Observer %{customdata[1]}<extra></extra>',
|
|
];
|
|
}
|
|
|
|
$plotlyPayload = [
|
|
'traces' => $plotlyTraces,
|
|
'datasets' => $chartDatasets,
|
|
'selectedFilters' => $selectedFilters,
|
|
'summary' => $summary,
|
|
];
|
|
?>
|
|
<?php require __DIR__ . '/header.php'; ?>
|
|
<script src="https://cdn.plot.ly/plotly-3.2.0.min.js" charset="utf-8"></script>
|
|
|
|
<?php if (!empty($errors)): ?>
|
|
<ul class="msg-error">
|
|
<?php foreach ($errors as $error): ?>
|
|
<li><?= h($error) ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
|
|
<style>
|
|
.tcrb-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
|
|
gap: 1.2rem;
|
|
align-items: start;
|
|
}
|
|
|
|
.tcrb-filter-list {
|
|
display: grid;
|
|
gap: 0.55rem;
|
|
}
|
|
|
|
.tcrb-filter-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.6rem;
|
|
margin-bottom: 0.9rem;
|
|
}
|
|
|
|
.tcrb-filter-actions .btn {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
.tcrb-filter-option {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.8rem;
|
|
padding: 0.55rem 0.7rem;
|
|
border: 1px solid rgba(255,255,255,0.12);
|
|
border-radius: 0.7rem;
|
|
background: rgba(255,255,255,0.03);
|
|
}
|
|
|
|
.tcrb-filter-option label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.55rem;
|
|
margin: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tcrb-filter-swatch {
|
|
width: 0.9rem;
|
|
height: 0.9rem;
|
|
border-radius: 999px;
|
|
border: 1px solid rgba(255,255,255,0.4);
|
|
}
|
|
|
|
.tcrb-filter-count {
|
|
color: var(--text-dim);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.tcrb-chart-card {
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tcrb-chart-wrap {
|
|
position: relative;
|
|
min-height: 32rem;
|
|
border-radius: 1rem;
|
|
background:
|
|
radial-gradient(circle at top, rgba(200, 168, 75, 0.12), transparent 45%),
|
|
linear-gradient(180deg, rgba(17, 24, 39, 0.96), rgba(7, 10, 18, 0.98));
|
|
border: 1px solid rgba(200, 168, 75, 0.2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tcrb-plot {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.tcrb-chart-legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.7rem 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.tcrb-chart-legend-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.45rem;
|
|
color: #dfe7ff;
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
.tcrb-chart-legend-item span {
|
|
width: 0.85rem;
|
|
height: 0.85rem;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.tcrb-summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 0.8rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.tcrb-summary-box {
|
|
padding: 0.8rem 0.9rem;
|
|
border-radius: 0.9rem;
|
|
background: rgba(255,255,255,0.04);
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
.tcrb-summary-box strong {
|
|
display: block;
|
|
font-size: 1.05rem;
|
|
color: #f9fafb;
|
|
}
|
|
|
|
.tcrb-summary-box span {
|
|
display: block;
|
|
color: var(--text-dim);
|
|
font-size: 0.88rem;
|
|
margin-top: 0.2rem;
|
|
}
|
|
|
|
.tcrb-chart-empty {
|
|
padding: 2rem 1.2rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
@media (max-width: 960px) {
|
|
.tcrb-layout {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.tcrb-summary-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="tcrb-layout">
|
|
<div class="card">
|
|
<h2>Filter</h2>
|
|
<form method="get" action="" class="form-stack" id="tcrbFilterForm">
|
|
<div class="tcrb-filter-actions">
|
|
<button type="button" class="btn btn-secondary" id="tcrbSelectAll">Alle an</button>
|
|
<button type="button" class="btn btn-secondary" id="tcrbSelectNone">Alle aus</button>
|
|
</div>
|
|
<div class="tcrb-filter-list">
|
|
<?php foreach ($availableFilters as $filter): ?>
|
|
<?php
|
|
$filterCode = $filter['code'];
|
|
$filterColor = $filterPalette[$filterCode] ?? sprintf('#%06X', crc32($filterCode) & 0xFFFFFF);
|
|
$filterDescription = $filterDescriptions[$filterCode] ?? $filterCode;
|
|
?>
|
|
<div class="tcrb-filter-option" title="<?= h($filterDescription) ?>">
|
|
<label title="<?= h($filterDescription) ?>">
|
|
<input
|
|
type="checkbox"
|
|
name="filters[]"
|
|
value="<?= h($filterCode) ?>"
|
|
<?= in_array($filterCode, $selectedFilters, true) ? 'checked' : '' ?>
|
|
>
|
|
<span class="tcrb-filter-swatch" style="background: <?= h($filterColor) ?>"></span>
|
|
<span><?= h($filterCode) ?></span>
|
|
</label>
|
|
<span class="tcrb-filter-count"><?= (int) $filter['count'] ?></span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card tcrb-chart-card">
|
|
<h2>Lichtkurve</h2>
|
|
|
|
<?php if (empty($errors) && !empty($observations)): ?>
|
|
<div class="tcrb-summary-grid">
|
|
<div class="tcrb-summary-box">
|
|
<strong><?= (int) $summary['count'] ?></strong>
|
|
<span>Messpunkte</span>
|
|
</div>
|
|
<div class="tcrb-summary-box">
|
|
<strong><?= h(formatGermanDate($summary['min_utc'])) ?></strong>
|
|
<span>Erster Beobachtungszeitpunkt</span>
|
|
</div>
|
|
<div class="tcrb-summary-box">
|
|
<strong><?= h(formatGermanDate($summary['max_utc'])) ?></strong>
|
|
<span>Letzter Beobachtungszeitpunkt</span>
|
|
</div>
|
|
<div class="tcrb-summary-box">
|
|
<strong><?= number_format((float) $summary['min_mag'], 3, '.', '') ?> bis <?= number_format((float) $summary['max_mag'], 3, '.', '') ?></strong>
|
|
<span>Magnitudenbereich</span>
|
|
</div>
|
|
<div class="tcrb-summary-box">
|
|
<strong><?= h($nextPossibleEruptionUtc->format('d.m.Y')) ?></strong>
|
|
<span>Nächster möglicher Ausbruch<br>(in <?= number_format($daysUntilNextEruption, 1, ',', '.') ?> Tagen)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tcrb-chart-wrap">
|
|
<div class="tcrb-plot" id="tcrbPlot" aria-label="T-CrB-Lichtkurve"></div>
|
|
</div>
|
|
|
|
<div class="tcrb-chart-legend">
|
|
<?php foreach ($chartDatasets as $dataset): ?>
|
|
<?php $filterDescription = $filterDescriptions[$dataset['filter']] ?? $dataset['filter']; ?>
|
|
<div class="tcrb-chart-legend-item" title="<?= h($filterDescription) ?>">
|
|
<span style="background: <?= h($dataset['color']) ?>"></span>
|
|
<?= h($dataset['filter']) ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="tcrb-chart-empty">Noch keine darstellbaren Daten vorhanden.</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (empty($errors) && !empty($observations)): ?>
|
|
<script>
|
|
(function () {
|
|
const payload = <?= json_encode($plotlyPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
|
const plotEl = document.getElementById('tcrbPlot');
|
|
const filterForm = document.getElementById('tcrbFilterForm');
|
|
const selectAllEl = document.getElementById('tcrbSelectAll');
|
|
const selectNoneEl = document.getElementById('tcrbSelectNone');
|
|
const filterCheckboxes = filterForm ? Array.from(filterForm.querySelectorAll('input[name="filters[]"]')) : [];
|
|
|
|
if (!plotEl || typeof Plotly === 'undefined' || !payload || !Array.isArray(payload.datasets) || payload.datasets.length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (filterForm) {
|
|
filterForm.addEventListener('submit', function (event) {
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
|
|
const layout = {
|
|
margin: { t: 20, r: 24, b: 56, l: 72 },
|
|
paper_bgcolor: 'rgba(0,0,0,0)',
|
|
plot_bgcolor: 'rgba(0,0,0,0)',
|
|
hovermode: 'closest',
|
|
showlegend: false,
|
|
xaxis: {
|
|
title: { text: 'Beobachtungszeitpunkt (UTC)' },
|
|
type: 'date',
|
|
gridcolor: 'rgba(255,255,255,0.08)',
|
|
linecolor: '#f8fafc',
|
|
tickfont: { color: '#cbd5e1' },
|
|
zeroline: false
|
|
},
|
|
yaxis: {
|
|
title: { text: 'Magnitude' },
|
|
autorange: 'reversed',
|
|
gridcolor: 'rgba(255,255,255,0.12)',
|
|
linecolor: '#f8fafc',
|
|
tickfont: { color: '#cbd5e1' },
|
|
zeroline: false
|
|
},
|
|
font: {
|
|
color: '#f8fafc'
|
|
}
|
|
};
|
|
|
|
const config = {
|
|
responsive: true,
|
|
displaylogo: false,
|
|
modeBarButtonsToRemove: ['select2d', 'lasso2d']
|
|
};
|
|
|
|
function buildDatasetTrace(dataset) {
|
|
const errorValues = [];
|
|
let hasErrors = false;
|
|
|
|
dataset.points.forEach(function (point) {
|
|
const errorValue = point.magnitude_error !== null ? Number(point.magnitude_error) : 0;
|
|
errorValues.push(errorValue);
|
|
if (errorValue > 0) {
|
|
hasErrors = true;
|
|
}
|
|
});
|
|
|
|
return {
|
|
type: 'scatter',
|
|
mode: 'lines+markers',
|
|
name: dataset.filter,
|
|
x: dataset.points.map(function (point) { return point.observed_at_utc; }),
|
|
y: dataset.points.map(function (point) { return Number(point.magnitude); }),
|
|
line: {
|
|
color: dataset.color,
|
|
width: 2,
|
|
shape: 'spline',
|
|
smoothing: 0.65
|
|
},
|
|
marker: {
|
|
color: dataset.color,
|
|
size: 3,
|
|
line: {
|
|
color: '#0f172a',
|
|
width: 1
|
|
}
|
|
},
|
|
error_y: {
|
|
type: 'data',
|
|
array: errorValues,
|
|
visible: hasErrors,
|
|
thickness: 1.1,
|
|
width: 3,
|
|
color: dataset.color
|
|
},
|
|
customdata: dataset.points.map(function (point) {
|
|
return [
|
|
point.jd,
|
|
point.observer_code !== '' ? point.observer_code : '-',
|
|
point.magnitude_error !== null ? Number(point.magnitude_error).toFixed(3) : '-'
|
|
];
|
|
}),
|
|
hovertemplate: 'Filter %{fullData.name}<br>'
|
|
+ 'Magnitude %{y:.3f}<br>'
|
|
+ 'Fehler %{customdata[2]}<br>'
|
|
+ 'UTC %{x}<br>'
|
|
+ 'JD %{customdata[0]}<br>'
|
|
+ 'Observer %{customdata[1]}<extra></extra>'
|
|
};
|
|
}
|
|
|
|
function getActiveFilterCodes() {
|
|
return filterCheckboxes
|
|
.filter(function (checkbox) { return checkbox.checked; })
|
|
.map(function (checkbox) { return checkbox.value; });
|
|
}
|
|
|
|
function renderPlot() {
|
|
const activeFilterCodes = getActiveFilterCodes();
|
|
const activeDatasets = payload.datasets.filter(function (dataset) {
|
|
return activeFilterCodes.includes(dataset.filter);
|
|
});
|
|
const traces = activeDatasets.map(buildDatasetTrace);
|
|
|
|
if (traces.length === 0) {
|
|
Plotly.react(plotEl, [], layout, config);
|
|
return;
|
|
}
|
|
|
|
Plotly.react(plotEl, traces, layout, config);
|
|
}
|
|
|
|
const initiallySelected = Array.isArray(payload.selectedFilters) && payload.selectedFilters.length > 0
|
|
? payload.selectedFilters
|
|
: ['V'];
|
|
|
|
filterCheckboxes.forEach(function (checkbox) {
|
|
checkbox.checked = initiallySelected.includes(checkbox.value);
|
|
checkbox.addEventListener('change', renderPlot);
|
|
});
|
|
|
|
if (selectAllEl) {
|
|
selectAllEl.addEventListener('click', function () {
|
|
filterCheckboxes.forEach(function (checkbox) {
|
|
checkbox.checked = true;
|
|
});
|
|
renderPlot();
|
|
});
|
|
}
|
|
|
|
if (selectNoneEl) {
|
|
selectNoneEl.addEventListener('click', function () {
|
|
filterCheckboxes.forEach(function (checkbox) {
|
|
checkbox.checked = false;
|
|
});
|
|
renderPlot();
|
|
});
|
|
}
|
|
|
|
renderPlot();
|
|
})();
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<?php require __DIR__ . '/footer.php'; ?>
|