CluPilotCloud/resources/views/components/ui/spark.blade.php

75 lines
3.1 KiB
PHP

@props([
/** @var array<int, int|float|null> the series, in the order it happened */
'points' => [],
// muted where the figure is observed, accent where it can be acted on.
// Four accent charts in a row is how an accent stops meaning anything.
'tone' => 'accent',
'area' => false,
])
@php
/*
| A missing sample BREAKS the line; it is not filtered out.
|
| This used to start with array_filter(…, 'is_numeric'), which threw the
| gaps away and joined their neighbours. Two things went wrong at once: the
| line claimed a measurement that was never taken, and every point after
| the gap slid left — an hour of samples with two gaps drew itself as
| fifty-eight minutes and put the newest reading somewhere in the middle.
|
| The x position is therefore taken from the point's place in the ORIGINAL
| series, and consecutive readings are collected into segments that are
| drawn as separate paths. A series with nothing missing yields exactly one
| segment and the same picture as before — which is what every existing
| caller passes.
*/
$values = array_values($points);
$numeric = array_values(array_filter($values, 'is_numeric'));
$min = $numeric ? min($numeric) : 0;
$max = $numeric ? max($numeric) : 0;
$span = max(0.0001, $max - $min);
$step = count($values) > 1 ? 96 / (count($values) - 1) : 96;
$segments = [];
$current = [];
foreach ($values as $i => $v) {
if (! is_numeric($v)) {
// A single reading on its own cannot be a line; it ends the segment
// without drawing one, rather than emitting a path of one point.
if (count($current) > 1) {
$segments[] = $current;
}
$current = [];
continue;
}
$current[] = [round($i * $step, 1), round(30 - (($v - $min) / $span) * 26, 1)];
}
if (count($current) > 1) {
$segments[] = $current;
}
// Nur die Koordinaten, ohne Befehlsbuchstaben — der Aufrufer setzt das `M`
// oder das `L` davor. Vorher trug diese Funktion ihr `M` selbst, und im
// Flächenpfad stand damit `… L` unmittelbar vor einem `M`: gültig gelesen,
// nicht gezeichnet, und die Füllung verschwand ohne eine einzige Meldung.
$coords = fn (array $seg) => implode(' ', array_map(fn ($p) => $p[0].' '.$p[1], $seg));
@endphp
@if ($segments !== [])
<svg class="spark {{ $tone === 'accent' ? '' : 'muted' }}" viewBox="0 0 96 34" preserveAspectRatio="none"
style="width:80px;height:32px" aria-hidden="true">
@foreach ($segments as $seg)
@if ($area)
{{-- Closed to the baseline under ITS OWN span, not under the whole
box: an area that ran from 0 to 96 would fill the gaps the
line above deliberately leaves open. --}}
<path class="area" d="M{{ $seg[0][0] }} 34 L{{ $coords($seg) }} L{{ end($seg)[0] }} 34 Z" />
@endif
<path class="line" d="M{{ $coords($seg) }}" />
@endforeach
</svg>
@endif