@@ -4,6 +4,7 @@ import type { HomeAssistant } from 'custom-card-helpers';
44
55import type { DiscoveredEntities , SunPositionAttributes } from '../types' ;
66import { findFovWindows , sampleDay , startOfDayInZone , type SunSample } from '../lib/sun-model' ;
7+ import { elevationBandFraction } from '../lib/geometry' ;
78import { formatClock } from '../lib/formatters' ;
89import { t } from '../lib/i18n' ;
910
@@ -31,7 +32,7 @@ export class ElevationChart extends LitElement {
3132 protected render ( ) : TemplateResult | typeof nothing {
3233 if ( ! this . hass || ! this . discovered ) return nothing ;
3334 const attrs = this . _sunAttrs ( ) ;
34- const { latitude, longitude, time_zone } = this . hass . config as unknown as {
35+ const { latitude, longitude, time_zone } = ( this . hass . config ?? { } ) as unknown as {
3536 latitude ?: number ;
3637 longitude ?: number ;
3738 time_zone ?: string ;
@@ -72,6 +73,27 @@ export class ElevationChart extends LitElement {
7273 const currentSample = this . _interpAt ( samples , now ) ;
7374 const currentY = currentSample ? yAt ( currentSample . elevation ) : null ;
7475
76+ // Elevation limits (optional integration attrs). When present, the FOV
77+ // time-bands are clipped to the in-band elevation range and horizontal
78+ // limit gridlines are drawn. Graceful no-op when both are absent.
79+ const hasMin = typeof attrs . min_elevation === 'number' ;
80+ const hasMax = typeof attrs . max_elevation === 'number' ;
81+ const plotTop = PAD_T ;
82+ const plotBottom = VIEWBOX_H - PAD_B ;
83+ // loFrac → lower elevation (axisMin side, bottom of plot); hiFrac → upper.
84+ const { loFrac, hiFrac } = elevationBandFraction (
85+ attrs . min_elevation ,
86+ attrs . max_elevation ,
87+ minElev ,
88+ maxElev ,
89+ ) ;
90+ // Map a 0..1 elevation-axis fraction to a y coordinate (0 = bottom).
91+ const yForFrac = ( frac : number ) : number => plotBottom - frac * ( plotBottom - plotTop ) ;
92+ const bandTopY = yForFrac ( hiFrac ) ;
93+ const bandBottomY = yForFrac ( loFrac ) ;
94+ const bandY = hasMin || hasMax ? bandTopY : plotTop ;
95+ const bandHeight = hasMin || hasMax ? bandBottomY - bandTopY : plotBottom - plotTop ;
96+
7597 const fovBands = fovWindows . map ( ( w ) => ( {
7698 x0 : xAt ( samples [ w . startIdx ] . t ) ,
7799 x1 : xAt ( samples [ w . endIdx ] . t ) ,
@@ -117,14 +139,27 @@ export class ElevationChart extends LitElement {
117139 <!- - horizon - - >
118140 <line class= "horizon" x1 = ${ PAD_L } y1= ${ horizonY } x2= ${ VIEWBOX_W - PAD_R } y2= ${ horizonY } / >
119141
120- <!- - FOV shaded band s (each time the sun is actually in FOV + above horizon ) - - >
142+ <!- - elevation limit gridlines (drawn only for limits actually set) - - >
143+ ${
144+ hasMin
145+ ? svg `<line class= "limit-line" x1 = ${ PAD_L } y1= ${ bandBottomY } x2= ${ VIEWBOX_W - PAD_R } y2= ${ bandBottomY } / > `
146+ : nothing
147+ }
148+ ${
149+ hasMax
150+ ? svg `<line class= "limit-line" x1 = ${ PAD_L } y1= ${ bandTopY } x2= ${ VIEWBOX_W - PAD_R } y2= ${ bandTopY } / > `
151+ : nothing
152+ }
153+
154+ <!- - FOV shaded band s (each time the sun is actually in FOV + above horizon ),
155+ clipped to the in-band elevation range when limits are present -- >
121156 ${ fovBands . map (
122157 ( b ) => svg `<rect
123158 class= "fov-band"
124159 x = ${ b . x0 }
125- y= ${ PAD_T }
160+ y= ${ bandY }
126161 width= ${ b . x1 - b . x0 }
127- height= ${ VIEWBOX_H - PAD_T - PAD_B }
162+ height= ${ bandHeight }
128163 / > ` ,
129164 ) }
130165
@@ -215,6 +250,12 @@ export class ElevationChart extends LitElement {
215250 stroke-width : 1 ;
216251 stroke-dasharray : 2 2 ;
217252 }
253+ .limit-line {
254+ stroke : var (--warning-color , gold);
255+ stroke-width : 1 ;
256+ stroke-dasharray : 4 3 ;
257+ opacity : 0.7 ;
258+ }
218259 .fov-band {
219260 fill : var (--warning-color , gold);
220261 fill-opacity : 0.18 ;
0 commit comments