fallback and 404
This commit is contained in:
@@ -31,6 +31,9 @@
|
||||
width?: number;
|
||||
/** Draw a low-alpha area fill below (or above, on inverted axes) the line */
|
||||
fill?: boolean;
|
||||
/** With `fill`, fill the area between this line and another data array
|
||||
* instead of the baseline (e.g. an ensemble min-max band) */
|
||||
bandTo?: (number | null)[];
|
||||
/** Opacity of the area fill (default 0.15) */
|
||||
fillOpacity?: number;
|
||||
/** Draw the line dashed */
|
||||
@@ -92,6 +95,8 @@
|
||||
series: ChartSeries[];
|
||||
/** Background bands (epoch seconds), e.g. daylight */
|
||||
bands?: { start: number; end: number }[];
|
||||
/** Highlighted time range (epoch seconds), e.g. the selected day */
|
||||
highlight?: { start: number; end: number };
|
||||
/** Unit label for the left y axis (also used in tooltip values) */
|
||||
unit?: string;
|
||||
/** Unit label for the right y axis; when set, right-axis labels are drawn */
|
||||
@@ -129,6 +134,7 @@
|
||||
timezone,
|
||||
series,
|
||||
bands = [],
|
||||
highlight,
|
||||
unit = '',
|
||||
unitRight,
|
||||
height = 300,
|
||||
@@ -443,6 +449,33 @@
|
||||
if (x2 > x1) ctx.fillRect(x1, padTop, x2 - x1, plotH);
|
||||
}
|
||||
|
||||
// Selected-day highlight: soft tint + dashed edge lines
|
||||
if (highlight && highlight.end > viewStart && highlight.start < viewEnd) {
|
||||
const accent = cssColor('--primary', '#e08a3c');
|
||||
const x1 = Math.max(PAD_LEFT, xPix(highlight.start));
|
||||
const x2 = Math.min(plotRight, xPix(highlight.end));
|
||||
if (x2 > x1) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.08;
|
||||
ctx.fillStyle = accent;
|
||||
ctx.fillRect(x1, padTop, x2 - x1, plotH);
|
||||
ctx.globalAlpha = 0.55;
|
||||
ctx.strokeStyle = accent;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.setLineDash([5, 4]);
|
||||
ctx.beginPath();
|
||||
for (const edge of [highlight.start, highlight.end]) {
|
||||
const x = xPix(edge);
|
||||
if (x >= PAD_LEFT && x <= plotRight) {
|
||||
ctx.moveTo(x, padTop);
|
||||
ctx.lineTo(x, plotBottom);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal grid lines + left axis labels
|
||||
ctx.font = font;
|
||||
ctx.textAlign = 'right';
|
||||
@@ -530,17 +563,20 @@
|
||||
}
|
||||
|
||||
// Line series: draw fill and stroke per contiguous non-null run
|
||||
// (points outside the view are handled by the clip rect)
|
||||
const runs: Array<Array<[number, number]>> = [];
|
||||
let run: Array<[number, number]> = [];
|
||||
// (points outside the view are handled by the clip rect). Each point
|
||||
// is [x, y, yBand] — yBand only used when s.bandTo is set.
|
||||
const runs: Array<Array<[number, number, number]>> = [];
|
||||
let run: Array<[number, number, number]> = [];
|
||||
for (let i = 0; i < timestamps.length; i++) {
|
||||
const v = s.data[i];
|
||||
if (v === null || v === undefined || !isFinite(v)) {
|
||||
const b = s.bandTo?.[i];
|
||||
const bandInvalid = s.bandTo != null && (b === null || b === undefined || !isFinite(b));
|
||||
if (v === null || v === undefined || !isFinite(v) || bandInvalid) {
|
||||
if (run.length > 0) runs.push(run);
|
||||
run = [];
|
||||
continue;
|
||||
}
|
||||
run.push([xPix(timestamps[i]), yPix(v, axis)]);
|
||||
run.push([xPix(timestamps[i]), yPix(v, axis), s.bandTo ? yPix(b as number, axis) : 0]);
|
||||
}
|
||||
if (run.length > 0) runs.push(run);
|
||||
|
||||
@@ -551,8 +587,13 @@
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
for (let i = 1; i < points.length; i++) ctx.lineTo(points[i][0], points[i][1]);
|
||||
ctx.lineTo(points[points.length - 1][0], baseline);
|
||||
ctx.lineTo(points[0][0], baseline);
|
||||
if (s.bandTo) {
|
||||
// close the polygon along the second line, walked backwards
|
||||
for (let i = points.length - 1; i >= 0; i--) ctx.lineTo(points[i][0], points[i][2]);
|
||||
} else {
|
||||
ctx.lineTo(points[points.length - 1][0], baseline);
|
||||
ctx.lineTo(points[0][0], baseline);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.globalAlpha = s.fillOpacity ?? 0.15;
|
||||
ctx.fillStyle = s.color;
|
||||
@@ -789,29 +830,6 @@
|
||||
</script>
|
||||
|
||||
<div bind:this={containerEl} class="relative w-full select-none {className}">
|
||||
{#if showLegend && series.length > 0}
|
||||
<div class="mb-1 flex flex-wrap items-center gap-x-3 gap-y-1 px-1">
|
||||
{#each series.filter((s) => s.showInLegend !== false) as s (s.name)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex cursor-pointer items-center gap-1.5 text-xs transition-opacity {legendHidden.has(
|
||||
s.name
|
||||
)
|
||||
? 'opacity-40'
|
||||
: ''}"
|
||||
onclick={() => toggleSeries(s.name)}
|
||||
title="Toggle {s.name}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
|
||||
style:background-color={s.color}
|
||||
></span>
|
||||
<span class="text-muted-foreground">{s.name}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="relative">
|
||||
<canvas
|
||||
bind:this={canvasEl}
|
||||
@@ -856,4 +874,28 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Legend sits below the graph -->
|
||||
{#if showLegend && series.length > 0}
|
||||
<div class="mt-1.5 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 px-1">
|
||||
{#each series.filter((s) => s.showInLegend !== false) as s (s.name)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex cursor-pointer items-center gap-1.5 text-xs transition-opacity {legendHidden.has(
|
||||
s.name
|
||||
)
|
||||
? 'opacity-40'
|
||||
: ''}"
|
||||
onclick={() => toggleSeries(s.name)}
|
||||
title="Toggle {s.name}"
|
||||
>
|
||||
<span
|
||||
class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
|
||||
style:background-color={s.color}
|
||||
></span>
|
||||
<span class="text-muted-foreground">{s.name}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user