cloud cover
This commit is contained in:
@@ -62,8 +62,14 @@
|
|||||||
labelExtrema?: boolean;
|
labelExtrema?: boolean;
|
||||||
/** Formatter for extrema labels (defaults to the tooltip format) */
|
/** Formatter for extrema labels (defaults to the tooltip format) */
|
||||||
labelFormat?: (value: number) => string;
|
labelFormat?: (value: number) => string;
|
||||||
/** Render as a puffy cloud band hanging from the top instead of a line */
|
/** Render as a soft cloud band instead of a line (see `cloudLayer`) */
|
||||||
cloudBand?: boolean;
|
cloudBand?: boolean;
|
||||||
|
/**
|
||||||
|
* Which slot a cloud band occupies. Omitted (total cover) hangs from the
|
||||||
|
* top of the plot; the named layers stack in their real vertical order,
|
||||||
|
* each in its own slot, so a layered chart reads like the sky itself.
|
||||||
|
*/
|
||||||
|
cloudLayer?: 'high' | 'mid' | 'low';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupState {
|
interface GroupState {
|
||||||
@@ -229,8 +235,13 @@
|
|||||||
// edge inset used for BOTH rows so pictograms and arrows clamp to the same
|
// edge inset used for BOTH rows so pictograms and arrows clamp to the same
|
||||||
// centre and stay aligned with each other
|
// centre and stay aligned with each other
|
||||||
const ICON_EDGE = ARROW_PX / 2;
|
const ICON_EDGE = ARROW_PX / 2;
|
||||||
// Puffy cloud band: 100% cover hangs 40px from the top of the plot
|
// Total cloud cover: 100% reaches this far down from the top of the plot.
|
||||||
const CLOUD_BAND_MAX = 40;
|
const CLOUD_BAND_MAX = 48;
|
||||||
|
// Layered cover (high / mid / low): each layer owns a slot of this height and
|
||||||
|
// grows symmetrically out of the centre line of that slot.
|
||||||
|
const CLOUD_LAYER_H = 42;
|
||||||
|
const CLOUD_LAYER_GAP = 6;
|
||||||
|
const CLOUD_LAYER_ORDER = ['high', 'mid', 'low'] as const;
|
||||||
|
|
||||||
/** Return a colour string with the given alpha (handles rgb/rgba/#hex). */
|
/** Return a colour string with the given alpha (handles rgb/rgba/#hex). */
|
||||||
function withAlpha(color: string, alpha: number): string {
|
function withAlpha(color: string, alpha: number): string {
|
||||||
@@ -256,6 +267,83 @@
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Light [1 2 1] smoothing pass. Cloud cover is noisy hour to hour; smoothing
|
||||||
|
* the values before interpolating gives the band a slow, rolling silhouette
|
||||||
|
* instead of one that tracks every single sample.
|
||||||
|
*/
|
||||||
|
function smoothCover(values: number[], passes = 2): number[] {
|
||||||
|
let out = values;
|
||||||
|
for (let p = 0; p < passes; p++) {
|
||||||
|
const next = new Array<number>(out.length);
|
||||||
|
for (let i = 0; i < out.length; i++) {
|
||||||
|
const prev = out[i - 1] ?? out[i];
|
||||||
|
const nxt = out[i + 1] ?? out[i];
|
||||||
|
next[i] = (prev + 2 * out[i] + nxt) / 4;
|
||||||
|
}
|
||||||
|
out = next;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vertical offset of a cloud series' slot from the top of the plot. */
|
||||||
|
function cloudSlotOffset(s: ChartSeries): number {
|
||||||
|
if (!s.cloudLayer) return 0;
|
||||||
|
return CLOUD_LAYER_ORDER.indexOf(s.cloudLayer) * (CLOUD_LAYER_H + CLOUD_LAYER_GAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traces a monotone cubic (Fritsch-Carlson) curve through the points. Unlike a
|
||||||
|
* plain Catmull-Rom spline it never overshoots the data, so a cloud band can't
|
||||||
|
* bulge past 0% or 100% between two samples.
|
||||||
|
*/
|
||||||
|
function traceMonotone(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
pts: Array<[number, number]>,
|
||||||
|
continuePath = false
|
||||||
|
): void {
|
||||||
|
const n = pts.length;
|
||||||
|
if (!continuePath) ctx.moveTo(pts[0][0], pts[0][1]);
|
||||||
|
if (n === 2) {
|
||||||
|
ctx.lineTo(pts[1][0], pts[1][1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// secant slopes, then tangents averaged from the neighbouring secants
|
||||||
|
const slope: number[] = [];
|
||||||
|
for (let i = 0; i < n - 1; i++) {
|
||||||
|
const dx = pts[i + 1][0] - pts[i][0];
|
||||||
|
slope.push(dx === 0 ? 0 : (pts[i + 1][1] - pts[i][1]) / dx);
|
||||||
|
}
|
||||||
|
const m: number[] = [slope[0]];
|
||||||
|
for (let i = 1; i < n - 1; i++) m.push((slope[i - 1] + slope[i]) / 2);
|
||||||
|
m.push(slope[n - 2]);
|
||||||
|
|
||||||
|
// clamp the tangents back onto the monotone circle of radius 3
|
||||||
|
for (let i = 0; i < n - 1; i++) {
|
||||||
|
if (slope[i] === 0) {
|
||||||
|
m[i] = 0;
|
||||||
|
m[i + 1] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const a = m[i] / slope[i];
|
||||||
|
const b = m[i + 1] / slope[i];
|
||||||
|
const h = a * a + b * b;
|
||||||
|
if (h > 9) {
|
||||||
|
const t = 3 / Math.sqrt(h);
|
||||||
|
m[i] = t * a * slope[i];
|
||||||
|
m[i + 1] = t * b * slope[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < n - 1; i++) {
|
||||||
|
const [x0, y0] = pts[i];
|
||||||
|
const [x1, y1] = pts[i + 1];
|
||||||
|
const dx = (x1 - x0) / 3;
|
||||||
|
ctx.bezierCurveTo(x0 + dx, y0 + m[i] * dx, x1 - dx, y1 - m[i + 1] * dx, x1, y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── State ──────────────────────────────────────────────────────────────────
|
// ─── State ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let containerEl: HTMLDivElement | undefined = $state();
|
let containerEl: HTMLDivElement | undefined = $state();
|
||||||
@@ -934,23 +1022,101 @@
|
|||||||
|
|
||||||
const interval = timestamps.length > 1 ? timestamps[1] - timestamps[0] : HOUR;
|
const interval = timestamps.length > 1 ? timestamps[1] - timestamps[0] : HOUR;
|
||||||
|
|
||||||
// Puffy cloud band: overlapping circles hang from the top of the plot,
|
// Cloud bands: cover drives how far the band reaches into its slot, traced
|
||||||
// each reaching down by (cover/100) × CLOUD_BAND_MAX. Neighbouring puffs
|
// as a monotone-interpolated curve so the silhouette flows instead of
|
||||||
// merge into a soft, rounded silhouette.
|
// stepping hour to hour. Both gradients run from near-transparent at the
|
||||||
|
// anchor to solid at full cover, so a 10% sky barely registers while an
|
||||||
|
// overcast one is unmistakable.
|
||||||
|
//
|
||||||
|
// * total cover hangs from the top of the plot
|
||||||
|
// * high / mid / low each grow symmetrically out of their slot's centre
|
||||||
|
// line, stacked in the order the layers actually sit in the sky
|
||||||
for (const s of cloudBandSeries) {
|
for (const s of cloudBandSeries) {
|
||||||
ctx.save();
|
const slotTop = padTop + cloudSlotOffset(s);
|
||||||
ctx.fillStyle = s.color;
|
const layered = s.cloudLayer != null;
|
||||||
ctx.globalAlpha = 0.5;
|
const slotH = layered ? CLOUD_LAYER_H : CLOUD_BAND_MAX;
|
||||||
|
const centre = slotTop + slotH / 2;
|
||||||
|
|
||||||
|
// Samples just outside the viewport are kept so the curve enters and
|
||||||
|
// leaves the plot at the right slope.
|
||||||
|
const xs: number[] = [];
|
||||||
|
const covers: number[] = [];
|
||||||
for (let i = 0; i < timestamps.length; i++) {
|
for (let i = 0; i < timestamps.length; i++) {
|
||||||
const v = s.data[i];
|
|
||||||
if (v === null || v === undefined || !isFinite(v) || v <= 0) continue;
|
|
||||||
const t = timestamps[i];
|
const t = timestamps[i];
|
||||||
if (t < viewStart - interval || t > viewEnd + interval) continue;
|
if (t < viewStart - interval * 2 || t > viewEnd + interval * 2) continue;
|
||||||
const r = (Math.min(100, v) / 100) * CLOUD_BAND_MAX;
|
const v = s.data[i];
|
||||||
if (r < 1) continue;
|
xs.push(xPix(t));
|
||||||
|
covers.push(
|
||||||
|
v === null || v === undefined || !isFinite(v) ? 0 : Math.min(100, Math.max(0, v))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (xs.length < 2) continue;
|
||||||
|
|
||||||
|
const smoothed = smoothCover(covers);
|
||||||
|
const top: Array<[number, number]> = [];
|
||||||
|
const bottom: Array<[number, number]> = [];
|
||||||
|
for (let i = 0; i < xs.length; i++) {
|
||||||
|
const frac = smoothed[i] / 100;
|
||||||
|
if (layered) {
|
||||||
|
const half = frac * (slotH / 2);
|
||||||
|
top.push([xs[i], centre - half]);
|
||||||
|
bottom.push([xs[i], centre + half]);
|
||||||
|
} else {
|
||||||
|
top.push([xs[i], slotTop]);
|
||||||
|
bottom.push([xs[i], slotTop + frac * slotH]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The gradient runs ALONG the series rather than top-to-bottom: every
|
||||||
|
// sample contributes a stop at its own x, so the band's density tracks
|
||||||
|
// the cover itself - a clear spell dissolves, an overcast one goes
|
||||||
|
// solid - and the fade always lines up with the silhouette above it.
|
||||||
|
const first = xs[0];
|
||||||
|
const span = xs[xs.length - 1] - first || 1;
|
||||||
|
const base = layered ? 0.08 : 0.05;
|
||||||
|
const peak = layered ? 0.92 : 0.68;
|
||||||
|
// Total cover ramps up late: a broken sky stays nearly clear on the
|
||||||
|
// plot and the density only really builds as it closes over.
|
||||||
|
const curve = layered ? 1 : 2.2;
|
||||||
|
const fill = ctx.createLinearGradient(first, 0, xs[xs.length - 1], 0);
|
||||||
|
// The edge follows the same values but starts later still: below a
|
||||||
|
// fifth of the sky there is no silhouette to draw at all.
|
||||||
|
const EDGE_FLOOR = 20;
|
||||||
|
const edge = layered ? null : ctx.createLinearGradient(first, 0, xs[xs.length - 1], 0);
|
||||||
|
for (let i = 0; i < xs.length; i++) {
|
||||||
|
const pos = Math.max(0, Math.min(1, (xs[i] - first) / span));
|
||||||
|
const cover = smoothed[i];
|
||||||
|
fill.addColorStop(pos, withAlpha(s.color, base + (peak - base) * Math.pow(cover / 100, curve)));
|
||||||
|
if (edge) {
|
||||||
|
const above = Math.max(0, (cover - EDGE_FLOOR) / (100 - EDGE_FLOOR));
|
||||||
|
edge.addColorStop(pos, withAlpha(s.color, 0.85 * Math.pow(above, 1.6)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(xPix(t), padTop, r, 0, Math.PI * 2);
|
traceMonotone(ctx, bottom);
|
||||||
|
if (layered) {
|
||||||
|
// back along the mirrored upper edge
|
||||||
|
ctx.lineTo(top[top.length - 1][0], top[top.length - 1][1]);
|
||||||
|
traceMonotone(ctx, [...top].reverse(), true);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(bottom[bottom.length - 1][0], slotTop);
|
||||||
|
ctx.lineTo(bottom[0][0], slotTop);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fillStyle = fill;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
|
// The layers are gradient only - an outline would fight the soft mass
|
||||||
|
// they are meant to look like.
|
||||||
|
if (edge) {
|
||||||
|
ctx.beginPath();
|
||||||
|
traceMonotone(ctx, bottom);
|
||||||
|
ctx.strokeStyle = edge;
|
||||||
|
ctx.lineWidth = 1.25;
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@
|
|||||||
Both stay perfectly centered when compact (the night icon melts
|
Both stay perfectly centered when compact (the night icon melts
|
||||||
away and the day icon re-centers on its own); when full, small
|
away and the day icon re-centers on its own); when full, small
|
||||||
k-driven nudges line each temp up under its icon. -->
|
k-driven nudges line each temp up under its icon. -->
|
||||||
<div class="flex w-full items-center justify-center">
|
<div class="icon-row flex w-full items-center justify-center">
|
||||||
<div class="icon-wrap">
|
<div class="icon-wrap">
|
||||||
<svg class="day-icon fill-foreground">
|
<svg class="day-icon fill-foreground">
|
||||||
<use
|
<use
|
||||||
@@ -413,9 +413,11 @@
|
|||||||
.sentinel,
|
.sentinel,
|
||||||
.daystrip {
|
.daystrip {
|
||||||
--cell-w-full: 76px;
|
--cell-w-full: 76px;
|
||||||
--cell-w-min: 58px;
|
|
||||||
--cell-h-full: 140px;
|
--cell-h-full: 140px;
|
||||||
--cell-h-min: 64px;
|
--cell-h-min: 64px;
|
||||||
|
/* collapsed cells are square: the width follows the compact height, so the
|
||||||
|
docked bar reads as a row of tiles instead of narrow slivers */
|
||||||
|
--cell-w-min: var(--cell-h-min);
|
||||||
--icon-full: 44px;
|
--icon-full: 44px;
|
||||||
--icon-min: 21px;
|
--icon-min: 21px;
|
||||||
--pt-full: 7px;
|
--pt-full: 7px;
|
||||||
@@ -439,9 +441,18 @@
|
|||||||
/* full state only: nudge each temp to sit under "its" icon */
|
/* full state only: nudge each temp to sit under "its" icon */
|
||||||
--tmax-nudge: 0px;
|
--tmax-nudge: 0px;
|
||||||
--tmin-nudge: 5px;
|
--tmin-nudge: 5px;
|
||||||
/* cell bottom padding (roomy when full, tight when compact) */
|
/* full state only: gap above the icons, and the day / night icon offsets
|
||||||
|
that tighten the pair around the cell centre */
|
||||||
|
--icon-gap-full: 2px;
|
||||||
|
--day-icon-nudge: 2px;
|
||||||
|
--night-icon-nudge: 4px;
|
||||||
|
/* compact only: signed shift that lands the lone icon dead centre in the
|
||||||
|
square tile (measured against the tile's mid-line) */
|
||||||
|
--icon-lift: -1px;
|
||||||
|
/* cell bottom padding (roomy when full, a little breathing room under the
|
||||||
|
temps when compact so they don't sit on the tile edge) */
|
||||||
--pb-full: 6px;
|
--pb-full: 6px;
|
||||||
--pb-min: 6px;
|
--pb-min: 8px;
|
||||||
/* extra scrub distance beyond the height difference — slows the collapse
|
/* extra scrub distance beyond the height difference — slows the collapse
|
||||||
down; the surplus just slides content under the (opaque) bar */
|
down; the surplus just slides content under the (opaque) bar */
|
||||||
--collapse-extra: 0px;
|
--collapse-extra: 0px;
|
||||||
@@ -460,7 +471,6 @@
|
|||||||
.daystrip {
|
.daystrip {
|
||||||
--cell-w-full: 120px;
|
--cell-w-full: 120px;
|
||||||
--cell-h-full: 208px;
|
--cell-h-full: 208px;
|
||||||
--cell-w-min: 64px;
|
|
||||||
--cell-h-min: 56px;
|
--cell-h-min: 56px;
|
||||||
--icon-full: 80px;
|
--icon-full: 80px;
|
||||||
--icon-min: 22px;
|
--icon-min: 22px;
|
||||||
@@ -476,8 +486,11 @@
|
|||||||
--tmax-padx-full: 12px;
|
--tmax-padx-full: 12px;
|
||||||
--tmax-nudge: 0px;
|
--tmax-nudge: 0px;
|
||||||
--tmin-nudge: 8px;
|
--tmin-nudge: 8px;
|
||||||
|
--icon-gap-full: 6px;
|
||||||
|
--day-icon-nudge: 3px;
|
||||||
|
--night-icon-nudge: 6px;
|
||||||
--pb-full: 10px;
|
--pb-full: 10px;
|
||||||
--pb-min: 2px;
|
--pb-min: 5px;
|
||||||
--collapse-extra: 60px;
|
--collapse-extra: 60px;
|
||||||
}
|
}
|
||||||
.daystrip {
|
.daystrip {
|
||||||
@@ -652,6 +665,10 @@
|
|||||||
width: var(--cell-w);
|
width: var(--cell-w);
|
||||||
padding-top: var(--pt);
|
padding-top: var(--pt);
|
||||||
padding-bottom: calc(var(--pb-min) + (var(--pb-full) - var(--pb-min)) * var(--k));
|
padding-bottom: calc(var(--pb-min) + (var(--pb-full) - var(--pb-min)) * var(--k));
|
||||||
|
/* The collapsed rows that shrink to zero height still paid for their flex
|
||||||
|
gap, which pushed the temps onto the tile's bottom edge. Fading the gap
|
||||||
|
out with the collapse gives that space back as real bottom padding. */
|
||||||
|
row-gap: calc(2px * var(--k));
|
||||||
}
|
}
|
||||||
/* Side buttons keep the compact width in BOTH states, so almost nothing to
|
/* Side buttons keep the compact width in BOTH states, so almost nothing to
|
||||||
the left of the first day changes size during the collapse — the first
|
the left of the first day changes size during the collapse — the first
|
||||||
@@ -659,6 +676,13 @@
|
|||||||
.strip-side {
|
.strip-side {
|
||||||
width: var(--cell-w-min);
|
width: var(--cell-w-min);
|
||||||
}
|
}
|
||||||
|
.icon-row {
|
||||||
|
/* full: keep the icons off the weekday / date block above them.
|
||||||
|
compact: the icon is the only thing between the two text rows, so lift
|
||||||
|
it to sit optically dead centre in the square tile. */
|
||||||
|
margin-top: calc(var(--icon-gap-full) * var(--k));
|
||||||
|
transform: translateY(calc(-1 * var(--icon-lift) * (1 - var(--k))));
|
||||||
|
}
|
||||||
.icon-wrap {
|
.icon-wrap {
|
||||||
/* tuck the icon into its line box: the glyphs carry generous built-in
|
/* tuck the icon into its line box: the glyphs carry generous built-in
|
||||||
padding, so pulling the neighbours in keeps the cells tight */
|
padding, so pulling the neighbours in keeps the cells tight */
|
||||||
@@ -668,6 +692,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
width: var(--icon);
|
width: var(--icon);
|
||||||
height: var(--icon);
|
height: var(--icon);
|
||||||
|
/* full only: settle the pair a touch right of centre */
|
||||||
|
transform: translateX(calc(var(--day-icon-nudge) * var(--k)));
|
||||||
}
|
}
|
||||||
/* The night icon sits beside the day icon (slightly low, like a companion)
|
/* The night icon sits beside the day icon (slightly low, like a companion)
|
||||||
and melts away completely when compact so the day icon re-centers. */
|
and melts away completely when compact so the day icon re-centers. */
|
||||||
@@ -678,6 +704,7 @@
|
|||||||
opacity: var(--rel);
|
opacity: var(--rel);
|
||||||
margin-left: calc(-4px * var(--rel));
|
margin-left: calc(-4px * var(--rel));
|
||||||
margin-bottom: calc(6px * var(--rel));
|
margin-bottom: calc(6px * var(--rel));
|
||||||
|
transform: translateX(calc(-1 * var(--night-icon-nudge) * var(--k)));
|
||||||
}
|
}
|
||||||
/* weekday centered when full, pushed to the edges when compact */
|
/* weekday centered when full, pushed to the edges when compact */
|
||||||
.dow-row {
|
.dow-row {
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ export interface ChartVariableDef {
|
|||||||
windArrows?: boolean;
|
windArrows?: boolean;
|
||||||
/** Marker-only variable (icons / arrows): contributes no plotted series */
|
/** Marker-only variable (icons / arrows): contributes no plotted series */
|
||||||
marker?: boolean;
|
marker?: boolean;
|
||||||
/** Render as a puffy cloud band hanging from the top (0-100 → 0-40px) */
|
/** Render as a soft cloud band instead of a line */
|
||||||
cloudBand?: boolean;
|
cloudBand?: boolean;
|
||||||
|
/** Which slot the band occupies; omitted hangs from the top (total cover) */
|
||||||
|
cloudLayer?: 'high' | 'mid' | 'low';
|
||||||
/** Transform raw values before plotting (e.g. m → km) */
|
/** Transform raw values before plotting (e.g. m → km) */
|
||||||
transform?: (v: number) => number;
|
transform?: (v: number) => number;
|
||||||
/** Preset range to use when this variable lands on a shared right axis */
|
/** Preset range to use when this variable lands on a shared right axis */
|
||||||
@@ -121,6 +123,9 @@ export const CHART_VARIABLES: ChartVariableDef[] = [
|
|||||||
color: 'rgb(150, 155, 165)',
|
color: 'rgb(150, 155, 165)',
|
||||||
cloudBand: true
|
cloudBand: true
|
||||||
},
|
},
|
||||||
|
// The three layers stack in their real vertical order (high at the top of the
|
||||||
|
// plot, low at the bottom of the band group) and darken towards the ground,
|
||||||
|
// the way the layers actually look from below.
|
||||||
{
|
{
|
||||||
key: 'cloud_cover_low',
|
key: 'cloud_cover_low',
|
||||||
label: 'Cloud Cover Low',
|
label: 'Cloud Cover Low',
|
||||||
@@ -128,8 +133,9 @@ export const CHART_VARIABLES: ChartVariableDef[] = [
|
|||||||
field: 'cloud_cover_low',
|
field: 'cloud_cover_low',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
kind: 'percent',
|
kind: 'percent',
|
||||||
color: '#94a3b8',
|
color: 'rgb(110, 118, 132)',
|
||||||
width: 2
|
cloudBand: true,
|
||||||
|
cloudLayer: 'low'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cloud_cover_mid',
|
key: 'cloud_cover_mid',
|
||||||
@@ -138,8 +144,9 @@ export const CHART_VARIABLES: ChartVariableDef[] = [
|
|||||||
field: 'cloud_cover_mid',
|
field: 'cloud_cover_mid',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
kind: 'percent',
|
kind: 'percent',
|
||||||
color: '#64748b',
|
color: 'rgb(148, 156, 170)',
|
||||||
width: 2
|
cloudBand: true,
|
||||||
|
cloudLayer: 'mid'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cloud_cover_high',
|
key: 'cloud_cover_high',
|
||||||
@@ -148,8 +155,9 @@ export const CHART_VARIABLES: ChartVariableDef[] = [
|
|||||||
field: 'cloud_cover_high',
|
field: 'cloud_cover_high',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
kind: 'percent',
|
kind: 'percent',
|
||||||
color: '#cbd5e1',
|
color: 'rgb(186, 194, 208)',
|
||||||
width: 2
|
cloudBand: true,
|
||||||
|
cloudLayer: 'high'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'precipitation',
|
key: 'precipitation',
|
||||||
@@ -475,6 +483,7 @@ export function buildPanelDef(
|
|||||||
dashed: d.dashed,
|
dashed: d.dashed,
|
||||||
axis,
|
axis,
|
||||||
cloudBand: d.cloudBand,
|
cloudBand: d.cloudBand,
|
||||||
|
cloudLayer: d.cloudLayer,
|
||||||
segmentColor: d.colorScale ? (v: number) => getColor(v, units.temperature_unit) : undefined,
|
segmentColor: d.colorScale ? (v: number) => getColor(v, units.temperature_unit) : undefined,
|
||||||
foregroundLine: d.foregroundLine,
|
foregroundLine: d.foregroundLine,
|
||||||
outline: d.outline,
|
outline: d.outline,
|
||||||
|
|||||||
Reference in New Issue
Block a user