@props([ /** @var array 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, /* | The scale. Left null, the line stretches over its own smallest and largest | value — the classic sparkline, and the right choice when the question is | "what shape did this take". | | It is the WRONG choice when the question is "how busy is this", and the | host page proved it: a machine sitting at 0.2 % CPU, wobbling between 0.1 | and 0.3, drew a seismograph and flatly contradicted the figure printed | beside it. Passing min=0 (and max=100 for a percentage) anchors the box so | a calm host looks calm — the same rule the Chart.js version already | carried as "an auto-scaled axis makes 3 % look like a wall of load". */ 'min' => null, 'max' => null, // Rendered size in px. The viewBox stays 96×34, so this only stretches. 'width' => 80, 'height' => 32, ]) @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')); $low = $min ?? ($numeric ? min($numeric) : 0); $high = $max ?? ($numeric ? max($numeric) : 0); $span = max(0.0001, $high - $low); $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; } // Clamped: a value outside the given bounds belongs at the edge, not // outside the box where it would be clipped into a straight line. $ratio = min(1, max(0, ((float) $v - $low) / $span)); $current[] = [round($i * $step, 1), round(30 - $ratio * 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)); // Own id per render: two gradients sharing one would be harmless, but a // fill referring to a def that a later render replaced would not. $fade = 'spark-fade-'.substr(md5($tone.serialize($values).$width), 0, 10); @endphp @if ($segments !== []) @endif