@@ -455,6 +455,10 @@ function generateSnippetLocally() {
455455 // Adjust this value if text appears misaligned on the e-ink display
456456 const TEXT_Y_OFFSET = 0 ;
457457
458+ // Global offset for rectangle widgets to match device rendering
459+ // Negative value moves rectangles UP on the display
460+ const RECT_Y_OFFSET = - 15 ;
461+
458462 // Helper to wrap widget rendering with conditional logic
459463 const wrapWithCondition = ( lines , w , contentFn ) => {
460464 const p = w . props || { } ;
@@ -1058,21 +1062,50 @@ function generateSnippetLocally() {
10581062 const entityId = ( w . entity_id || "" ) . trim ( ) ;
10591063 const localSensorId = entityId . replace ( / ^ s e n s o r \. / , "" ) . replace ( / \. / g, "_" ) . replace ( / - / g, "_" ) || "none" ;
10601064
1065+ // Get line styling options
1066+ const lineType = ( p . line_type || "SOLID" ) . toUpperCase ( ) ;
1067+ const lineThickness = parseInt ( p . line_thickness || 3 , 10 ) ;
1068+ const border = p . border !== false ;
1069+ const continuous = ! ! p . continuous ;
1070+
10611071 lines . push ( ` - id: ${ safeId } ` ) ;
1062- lines . push ( ` sensor: ${ localSensorId } ` ) ;
10631072 lines . push ( ` duration: ${ duration } ` ) ;
10641073 lines . push ( ` width: ${ width } ` ) ;
10651074 lines . push ( ` height: ${ height } ` ) ;
1075+ lines . push ( ` border: ${ border } ` ) ;
10661076
10671077 // Grid configuration (only output if grid is enabled)
10681078 if ( gridEnabled && xGrid ) lines . push ( ` x_grid: ${ xGrid } ` ) ;
10691079 if ( gridEnabled && yGrid ) lines . push ( ` y_grid: ${ yGrid } ` ) ;
10701080
1081+ // Traces section (required for ESPHome graph component)
1082+ // This defines the actual data line(s) to be drawn
1083+ lines . push ( ` traces:` ) ;
1084+ lines . push ( ` - sensor: ${ localSensorId } ` ) ;
1085+ // ALWAYS output line_thickness - ESPHome default is 1px which is invisible on e-paper
1086+ // Our designer default is 3px which provides good visibility
1087+ lines . push ( ` line_thickness: ${ lineThickness } ` ) ;
1088+ if ( lineType !== "SOLID" ) {
1089+ lines . push ( ` line_type: ${ lineType } ` ) ;
1090+ }
1091+ if ( continuous ) {
1092+ lines . push ( ` continuous: true` ) ;
1093+ }
1094+
1095+ // Min/Max value configuration (required for Y-axis scaling)
1096+ // These go after traces in ESPHome YAML
1097+ const minValue = p . min_value ;
1098+ const maxValue = p . max_value ;
1099+ if ( minValue !== undefined && minValue !== null && String ( minValue ) . trim ( ) !== "" ) {
1100+ lines . push ( ` min_value: ${ minValue } ` ) ;
1101+ }
1102+ if ( maxValue !== undefined && maxValue !== null && String ( maxValue ) . trim ( ) !== "" ) {
1103+ lines . push ( ` max_value: ${ maxValue } ` ) ;
1104+ }
1105+
10711106 // Range configuration
10721107 if ( maxRange !== null ) lines . push ( ` max_range: ${ maxRange } ` ) ;
10731108 if ( minRange !== null ) lines . push ( ` min_range: ${ minRange } ` ) ;
1074-
1075- // Color is handled in lambda draw call
10761109 } ) ;
10771110 lines . push ( "" ) ;
10781111 }
@@ -1605,6 +1638,7 @@ function generateSnippetLocally() {
16051638 const fill = p . fill ;
16061639 const opacity = parseInt ( p . opacity || 100 , 10 ) ;
16071640 const isGray = colorProp . toLowerCase ( ) === "gray" ;
1641+ const rectY = w . y + RECT_Y_OFFSET ;
16081642
16091643 lines . push ( ` // widget:shape_rect id:${ w . id } type:shape_rect x:${ w . x } y:${ w . y } w:${ w . width } h:${ w . height } fill:${ fill } border:${ borderWidth } opacity:${ opacity } color:${ colorProp } ${ getCondProps ( w ) } ` ) ;
16101644
@@ -1615,15 +1649,15 @@ function generateSnippetLocally() {
16151649 lines . push ( ` for (int dy = 0; dy < ${ w . height } ; dy++) {` ) ;
16161650 lines . push ( ` for (int dx = 0; dx < ${ w . width } ; dx++) {` ) ;
16171651 lines . push ( ` if ((dx + dy) % 2 == 0) {` ) ;
1618- lines . push ( ` it.draw_pixel_at(${ w . x } +dx, ${ w . y } +dy, COLOR_ON);` ) ;
1652+ lines . push ( ` it.draw_pixel_at(${ w . x } +dx, ${ rectY } +dy, COLOR_ON);` ) ;
16191653 lines . push ( ` }` ) ;
16201654 lines . push ( ` }` ) ;
16211655 lines . push ( ` }` ) ;
16221656 } else {
1623- lines . push ( ` it.filled_rectangle(${ w . x } , ${ w . y } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
1657+ lines . push ( ` it.filled_rectangle(${ w . x } , ${ rectY } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
16241658 }
16251659 } else {
1626- lines . push ( ` it.rectangle(${ w . x } , ${ w . y } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
1660+ lines . push ( ` it.rectangle(${ w . x } , ${ rectY } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
16271661 // TODO: Handle border width > 1 for non-filled rects if needed (by drawing multiple rects or using a helper)
16281662 }
16291663
@@ -2277,6 +2311,7 @@ function generateSnippetLocally() {
22772311 const color = getColorConst ( colorProp ) ;
22782312 const opacity = parseInt ( p . opacity || 100 , 10 ) ;
22792313 const isGray = colorProp . toLowerCase ( ) === "gray" ;
2314+ const rectY = w . y + RECT_Y_OFFSET ;
22802315 lines . push ( ` // widget:shape_rect id:${ w . id } type:shape_rect x:${ w . x } y:${ w . y } w:${ w . width } h:${ w . height } fill:${ fill } border:${ borderWidth } opacity:${ opacity } color:${ colorProp } ${ getCondProps ( w ) } ` ) ;
22812316 if ( fill ) {
22822317 if ( isGray ) {
@@ -2285,19 +2320,19 @@ function generateSnippetLocally() {
22852320 lines . push ( ` for (int dy = 0; dy < ${ w . height } ; dy++) {` ) ;
22862321 lines . push ( ` for (int dx = 0; dx < ${ w . width } ; dx++) {` ) ;
22872322 lines . push ( ` if ((dx + dy) % 2 == 0) {` ) ;
2288- lines . push ( ` it.draw_pixel_at(${ w . x } +dx, ${ w . y } +dy, COLOR_ON);` ) ;
2323+ lines . push ( ` it.draw_pixel_at(${ w . x } +dx, ${ rectY } +dy, COLOR_ON);` ) ;
22892324 lines . push ( ` }` ) ;
22902325 lines . push ( ` }` ) ;
22912326 lines . push ( ` }` ) ;
22922327 } else {
2293- lines . push ( ` it.filled_rectangle(${ w . x } , ${ w . y } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
2328+ lines . push ( ` it.filled_rectangle(${ w . x } , ${ rectY } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
22942329 }
22952330 } else {
22962331 if ( borderWidth <= 1 ) {
2297- lines . push ( ` it.rectangle(${ w . x } , ${ w . y } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
2332+ lines . push ( ` it.rectangle(${ w . x } , ${ rectY } , ${ w . width } , ${ w . height } , ${ color } );` ) ;
22982333 } else {
22992334 lines . push ( ` for (int i=0; i<${ borderWidth } ; i++) {` ) ;
2300- lines . push ( ` it.rectangle(${ w . x } +i, ${ w . y } +i, ${ w . width } -2*i, ${ w . height } -2*i, ${ color } );` ) ;
2335+ lines . push ( ` it.rectangle(${ w . x } +i, ${ rectY } +i, ${ w . width } -2*i, ${ w . height } -2*i, ${ color } );` ) ;
23012336 lines . push ( ` }` ) ;
23022337 }
23032338 }
@@ -2380,11 +2415,13 @@ function generateSnippetLocally() {
23802415 const url = p . url || "" ;
23812416 const invert = ! ! p . invert ;
23822417 const renderMode = p . render_mode || "Auto" ;
2418+ // Sanitize widget ID to match the online_image: component declaration
2419+ const onlineImageId = `online_image_${ w . id } ` . replace ( / - / g, "_" ) ;
23832420 lines . push ( ` // widget:online_image id:${ w . id } type:online_image x:${ w . x } y:${ w . y } w:${ w . width } h:${ w . height } url:"${ url } " invert:${ invert } render_mode:"${ renderMode } " ${ getCondProps ( w ) } ` ) ;
23842421 if ( invert ) {
2385- lines . push ( ` it.image(${ w . x } , ${ w . y } , id(online_image_ ${ w . id } ), COLOR_OFF, COLOR_ON);` ) ;
2422+ lines . push ( ` it.image(${ w . x } , ${ w . y } , id(${ onlineImageId } ), COLOR_OFF, COLOR_ON);` ) ;
23862423 } else {
2387- lines . push ( ` it.image(${ w . x } , ${ w . y } , id(online_image_ ${ w . id } ));` ) ;
2424+ lines . push ( ` it.image(${ w . x } , ${ w . y } , id(${ onlineImageId } ));` ) ;
23882425 }
23892426 } else if ( t === "puppet" ) {
23902427 const url = p . image_url || "" ;
0 commit comments