@@ -24,18 +24,19 @@ import {
2424 STATISTIC_PERIODS ,
2525 STATISTIC_TYPES ,
2626 StatisticPeriod ,
27- isAutoPeriodConfig as getIsAutoPeriodConfig ,
27+ getIsAutoPeriodConfig ,
2828} from "./recorder-types" ;
2929import { parseTimeDuration } from "./duration/duration" ;
3030
3131const componentName = isProduction ? "plotly-graph" : "plotly-graph-dev" ;
3232
33+ const isDefined = ( y : any ) => y !== null && y !== undefined ;
3334function patchLonelyDatapoints ( xs : Datum [ ] , ys : Datum [ ] ) {
3435 /* Ghost traces when data has single non-unavailable states sandwiched between unavailable ones
3536 see: https://github.com/dbuezas/lovelace-plotly-graph-card/issues/103
3637 */
3738 for ( let i = 1 ; i < xs . length - 1 ; i ++ ) {
38- if ( ys [ i - 1 ] === null && ys [ i ] !== null && ys [ i + 1 ] === null ) {
39+ if ( ! isDefined ( ys [ i - 1 ] ) && isDefined ( ys [ i ] ) && ! isDefined ( ys [ i + 1 ] ) ) {
3940 ys . splice ( i , 0 , ys [ i ] ) ;
4041 xs . splice ( i , 0 , xs [ i ] ) ;
4142 }
@@ -50,14 +51,14 @@ console.info(
5051
5152const padding = 1 ;
5253export class PlotlyGraph extends HTMLElement {
53- contentEl ! : Plotly . PlotlyHTMLElement & {
54+ contentEl : Plotly . PlotlyHTMLElement & {
5455 data : ( Plotly . PlotData & { entity : string } ) [ ] ;
5556 layout : Plotly . Layout ;
5657 } ;
57- msgEl ! : HTMLElement ;
58- cardEl ! : HTMLElement ;
59- buttonEl ! : HTMLButtonElement ;
60- titleEl ! : HTMLElement ;
58+ msgEl : HTMLElement ;
59+ cardEl : HTMLElement ;
60+ resetButtonEl : HTMLButtonElement ;
61+ titleEl : HTMLElement ;
6162 config ! : InputConfig ;
6263 parsed_config ! : Config ;
6364 cache = new Cache ( ) ;
@@ -78,10 +79,10 @@ export class PlotlyGraph extends HTMLElement {
7879 this . handles . restyleListener ! . off ( "plotly_restyle" , this . onRestyle ) ;
7980 clearTimeout ( this . handles . refreshTimeout ! ) ;
8081 }
81- connectedCallback ( ) {
82- if ( ! this . contentEl ) {
83- const shadow = this . attachShadow ( { mode : "open" } ) ;
84- shadow . innerHTML = `
82+ constructor ( ) {
83+ super ( ) ;
84+ const shadow = this . attachShadow ( { mode : "open" } ) ;
85+ shadow . innerHTML = `
8586 <ha-card>
8687 <style>
8788 ha-card{
@@ -124,20 +125,21 @@ export class PlotlyGraph extends HTMLElement {
124125 <span id="msg"> </span>
125126 <button id="reset" class="hidden">↻</button>
126127 </ha-card>` ;
127- this . msgEl = shadow . querySelector ( "#msg" ) ! ;
128- this . cardEl = shadow . querySelector ( "ha-card" ) ! ;
129- this . contentEl = shadow . querySelector ( "div#plotly" ) ! ;
130- this . buttonEl = shadow . querySelector ( "button#reset" ) ! ;
131- this . titleEl = shadow . querySelector ( "ha-card > #title" ) ! ;
132- this . buttonEl . addEventListener ( "click" , this . exitBrowsingMode ) ;
133- insertStyleHack ( shadow . querySelector ( "style" ) ! ) ;
134- this . contentEl . style . visibility = "hidden" ;
135- this . withoutRelayout ( ( ) => Plotly . newPlot ( this . contentEl , [ ] , { } ) ) ;
136- }
128+ this . msgEl = shadow . querySelector ( "#msg" ) ! ;
129+ this . cardEl = shadow . querySelector ( "ha-card" ) ! ;
130+ this . contentEl = shadow . querySelector ( "div#plotly" ) ! ;
131+ this . resetButtonEl = shadow . querySelector ( "button#reset" ) ! ;
132+ this . titleEl = shadow . querySelector ( "ha-card > #title" ) ! ;
133+ this . resetButtonEl . addEventListener ( "click" , this . exitBrowsingMode ) ;
134+ insertStyleHack ( shadow . querySelector ( "style" ) ! ) ;
135+ this . contentEl . style . visibility = "hidden" ;
136+ this . withoutRelayout ( ( ) => Plotly . newPlot ( this . contentEl , [ ] , { } ) ) ;
137+ }
138+ connectedCallback ( ) {
137139 this . setupListeners ( ) ;
138- this . fetch ( this . getAutoFetchRange ( ) )
139- . then ( ( ) => this . fetch ( this . getAutoFetchRange ( ) ) ) // again so home assistant extends until end of time axis
140- . then ( ( ) => ( this . contentEl . style . visibility = "" ) ) ;
140+ this . fetch ( this . getAutoFetchRange ( ) ) . then (
141+ ( ) => ( this . contentEl . style . visibility = "" )
142+ ) ;
141143 }
142144 async withoutRelayout ( fn : Function ) {
143145 this . isInternalRelayout ++ ;
@@ -184,21 +186,23 @@ export class PlotlyGraph extends HTMLElement {
184186 return [ + new Date ( ) - ms , + new Date ( ) ] as [ number , number ] ;
185187 }
186188 getVisibleRange ( ) {
187- return this . contentEl . layout . xaxis ! . range ! . map ( ( date ) => + parseISO ( date ) ) ;
189+ return this . contentEl . layout . xaxis ! . range ! . map ( ( date ) =>
190+ // if autoscale is used after scrolling, plotly returns the dates as numbers instead of strings
191+ Number . isFinite ( date ) ? date : + parseISO ( date )
192+ ) ;
188193 }
189194 async enterBrowsingMode ( ) {
190195 this . isBrowsing = true ;
191- this . buttonEl . classList . remove ( "hidden" ) ;
196+ this . resetButtonEl . classList . remove ( "hidden" ) ;
192197 }
193198 exitBrowsingMode = async ( ) => {
194199 this . isBrowsing = false ;
195- this . buttonEl . classList . add ( "hidden" ) ;
200+ this . resetButtonEl . classList . add ( "hidden" ) ;
196201 this . withoutRelayout ( async ( ) => {
197202 await Plotly . relayout ( this . contentEl , {
198- uirevision : Math . random ( ) ,
199- xaxis : { range : this . getAutoFetchRange ( ) } ,
203+ uirevision : Math . random ( ) , // to trigger the autoranges in all y-yaxes
204+ xaxis : { range : this . getAutoFetchRange ( ) } , // to reset xaxis to hours_to_show quickly, before refetching
200205 } ) ;
201- await Plotly . restyle ( this . contentEl , { visible : true } ) ;
202206 } ) ;
203207 await this . fetch ( this . getAutoFetchRange ( ) ) ;
204208 } ;
@@ -218,6 +222,18 @@ export class PlotlyGraph extends HTMLElement {
218222 // The user supplied configuration. Throw an exception and Lovelace will
219223 // render an error card.
220224 async setConfig ( config : InputConfig ) {
225+ try {
226+ this . msgEl . innerText = "" ;
227+ return await this . _setConfig ( config ) ;
228+ } catch ( e : any ) {
229+ console . error ( e ) ;
230+ this . msgEl . innerText = JSON . stringify ( e . message || "" ) . replace (
231+ / \\ " / g,
232+ '"'
233+ ) ;
234+ }
235+ }
236+ async _setConfig ( config : InputConfig ) {
221237 config = JSON . parse ( JSON . stringify ( config ) ) ;
222238 this . config = config ;
223239 const schemeName = config . color_scheme ?? "category10" ;
@@ -328,7 +344,7 @@ export class PlotlyGraph extends HTMLElement {
328344 this . parsed_config = newConfig ;
329345 const is = this . parsed_config ;
330346 if ( ! this . contentEl ) return ;
331- if ( is . hours_to_show !== was . hours_to_show ) {
347+ if ( is . hours_to_show !== was ? .hours_to_show ) {
332348 this . exitBrowsingMode ( ) ;
333349 }
334350 await this . fetch ( this . getAutoFetchRange ( ) ) ;
@@ -460,7 +476,6 @@ export class PlotlyGraph extends HTMLElement {
460476 if ( mergedTrace . show_value ) {
461477 mergedTrace . legendgroup ??= "group" + traceIdx ;
462478 show_value_traces . push ( {
463- // @ts -expect-error (texttemplate missing in plotly typings)
464479 texttemplate : `%{y:.2~f}%{customdata.unit_of_measurement}` , // here so it can be overwritten
465480 ...mergedTrace ,
466481 mode : "text+markers" ,
@@ -507,9 +522,15 @@ export class PlotlyGraph extends HTMLElement {
507522 const yAxisTitles = Object . fromEntries (
508523 units . map ( ( unit , i ) => [ "yaxis" + ( i == 0 ? "" : i + 1 ) , { title : unit } ] )
509524 ) ;
510-
511525 const layout = merge (
512526 { uirevision : true } ,
527+ {
528+ xaxis : {
529+ range : this . isBrowsing
530+ ? this . getVisibleRange ( )
531+ : this . getAutoFetchRange ( ) ,
532+ } ,
533+ } ,
513534 this . parsed_config . no_default_layout ? { } : yAxisTitles ,
514535 this . getThemedLayout ( ) ,
515536 this . size ,
@@ -579,9 +600,9 @@ export class PlotlyGraph extends HTMLElement {
579600 return historyGraphCard . constructor . getConfigElement ( ) ;
580601 }
581602}
582- //@ts -ignore
603+ //@ts -expect-error
583604window . customCards = window . customCards || [ ] ;
584- //@ts -ignore
605+ //@ts -expect-error
585606window . customCards . push ( {
586607 type : componentName ,
587608 name : "Plotly Graph Card" ,
0 commit comments