TCrB Diagramm verbessert
This commit is contained in:
+170
-93
@@ -108,8 +108,9 @@ $summary = [
|
||||
'max_mag' => null,
|
||||
];
|
||||
|
||||
if (empty($errors) && !empty($selectedFilters)) {
|
||||
$placeholders = implode(',', array_fill(0, count($selectedFilters), '?'));
|
||||
if (empty($errors) && !empty($availableFilters)) {
|
||||
$queryFilters = array_column($availableFilters, 'code');
|
||||
$placeholders = implode(',', array_fill(0, count($queryFilters), '?'));
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
`observed_at_utc`,
|
||||
@@ -123,7 +124,7 @@ if (empty($errors) && !empty($selectedFilters)) {
|
||||
WHERE `filter_code` IN ({$placeholders})
|
||||
ORDER BY `observed_at_utc` ASC, `jd` ASC
|
||||
");
|
||||
$stmt->execute($selectedFilters);
|
||||
$stmt->execute($queryFilters);
|
||||
$rows = $stmt->fetchAll();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
@@ -155,23 +156,33 @@ if (empty($errors) && empty($observations)) {
|
||||
$errors[] = 'Fuer die ausgewaehlten Filter wurden keine Daten gefunden.';
|
||||
}
|
||||
|
||||
$filterPalette = [
|
||||
'U' => '#4c6ef5',
|
||||
'B' => '#1c7ed6',
|
||||
'V' => '#2b8a3e',
|
||||
'R' => '#c92a2a',
|
||||
'I' => '#862e9c',
|
||||
'CV' => '#495057',
|
||||
'TG' => '#0b7285',
|
||||
'TB' => '#364fc7',
|
||||
'TR' => '#d9480f',
|
||||
];
|
||||
$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 ($selectedFilters as $selectedFilter) {
|
||||
$chartDatasets[$selectedFilter] = [
|
||||
'filter' => $selectedFilter,
|
||||
'color' => $filterPalette[$selectedFilter] ?? sprintf('#%06X', crc32($selectedFilter) & 0xFFFFFF),
|
||||
foreach ($availableFilters as $availableFilter) {
|
||||
$filterCode = $availableFilter['code'];
|
||||
$chartDatasets[$filterCode] = [
|
||||
'filter' => $filterCode,
|
||||
'color' => $filterPalette[$filterCode] ?? sprintf('#%06X', crc32($filterCode) & 0xFFFFFF),
|
||||
'points' => [],
|
||||
];
|
||||
}
|
||||
@@ -247,74 +258,10 @@ foreach ($chartDatasets as $dataset) {
|
||||
];
|
||||
}
|
||||
|
||||
$meanSourcePoints = [];
|
||||
foreach ($chartDatasets as $dataset) {
|
||||
foreach ($dataset['points'] as $point) {
|
||||
$meanSourcePoints[] = $point;
|
||||
}
|
||||
}
|
||||
|
||||
usort($meanSourcePoints, static function (array $a, array $b): int {
|
||||
return strcmp((string) $a['observed_at_utc'], (string) $b['observed_at_utc']);
|
||||
});
|
||||
|
||||
if (count($meanSourcePoints) >= 3) {
|
||||
$windowSize = min(11, count($meanSourcePoints));
|
||||
if ($windowSize % 2 === 0) {
|
||||
$windowSize--;
|
||||
}
|
||||
|
||||
$halfWindow = intdiv($windowSize, 2);
|
||||
$meanX = [];
|
||||
$meanY = [];
|
||||
$meanCustomData = [];
|
||||
|
||||
foreach ($meanSourcePoints as $index => $point) {
|
||||
$start = max(0, $index - $halfWindow);
|
||||
$end = min(count($meanSourcePoints) - 1, $index + $halfWindow);
|
||||
$sum = 0.0;
|
||||
$count = 0;
|
||||
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
$sum += (float) $meanSourcePoints[$i]['magnitude'];
|
||||
$count++;
|
||||
}
|
||||
|
||||
if ($count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meanValue = $sum / $count;
|
||||
$meanX[] = (string) $point['observed_at_utc'];
|
||||
$meanY[] = $meanValue;
|
||||
$meanCustomData[] = [$count];
|
||||
}
|
||||
|
||||
if (!empty($meanX)) {
|
||||
$plotlyTraces[] = [
|
||||
'type' => 'scatter',
|
||||
'mode' => 'lines',
|
||||
'name' => 'Mittelkurve',
|
||||
'x' => $meanX,
|
||||
'y' => $meanY,
|
||||
'line' => [
|
||||
'color' => '#f4d35e',
|
||||
'width' => 3,
|
||||
'dash' => 'solid',
|
||||
'shape' => 'spline',
|
||||
'smoothing' => 0.7,
|
||||
],
|
||||
'customdata' => $meanCustomData,
|
||||
'hovertemplate' => 'Mittelkurve<br>'
|
||||
. 'Magnitude %{y:.3f}<br>'
|
||||
. 'UTC %{x}<br>'
|
||||
. 'Fenster %{customdata[0]} Punkte<extra></extra>',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$plotlyPayload = [
|
||||
'traces' => $plotlyTraces,
|
||||
'datasets' => $chartDatasets,
|
||||
'selectedFilters' => $selectedFilters,
|
||||
'summary' => $summary,
|
||||
];
|
||||
?>
|
||||
@@ -342,6 +289,17 @@ $plotlyPayload = [
|
||||
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;
|
||||
@@ -460,15 +418,20 @@ $plotlyPayload = [
|
||||
<div class="tcrb-layout">
|
||||
<div class="card">
|
||||
<h2>Filter</h2>
|
||||
<form method="get" action="" class="form-stack">
|
||||
<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">
|
||||
<label>
|
||||
<div class="tcrb-filter-option" title="<?= h($filterDescription) ?>">
|
||||
<label title="<?= h($filterDescription) ?>">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="filters[]"
|
||||
@@ -482,7 +445,6 @@ $plotlyPayload = [
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Diagramm aktualisieren</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -509,7 +471,7 @@ $plotlyPayload = [
|
||||
</div>
|
||||
<div class="tcrb-summary-box">
|
||||
<strong><?= h($nextPossibleEruptionUtc->format('d.m.Y')) ?></strong>
|
||||
<span>Naechster moeglicher Ausbruch<br>(in <?= number_format($daysUntilNextEruption, 1, ',', '.') ?> Tagen)</span>
|
||||
<span>Nächster möglicher Ausbruch<br>(in <?= number_format($daysUntilNextEruption, 1, ',', '.') ?> Tagen)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -519,7 +481,8 @@ $plotlyPayload = [
|
||||
|
||||
<div class="tcrb-chart-legend">
|
||||
<?php foreach ($chartDatasets as $dataset): ?>
|
||||
<div class="tcrb-chart-legend-item">
|
||||
<?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>
|
||||
@@ -536,11 +499,21 @@ $plotlyPayload = [
|
||||
(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.traces) || payload.traces.length === 0) {
|
||||
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)',
|
||||
@@ -556,7 +529,7 @@ $plotlyPayload = [
|
||||
zeroline: false
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: 'Magnitude (kleiner = heller)' },
|
||||
title: { text: 'Magnitude' },
|
||||
autorange: 'reversed',
|
||||
gridcolor: 'rgba(255,255,255,0.12)',
|
||||
linecolor: '#f8fafc',
|
||||
@@ -574,7 +547,111 @@ $plotlyPayload = [
|
||||
modeBarButtonsToRemove: ['select2d', 'lasso2d']
|
||||
};
|
||||
|
||||
Plotly.newPlot(plotEl, payload.traces, layout, config);
|
||||
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; ?>
|
||||
|
||||
Reference in New Issue
Block a user