My crazy-or-is-it idea: how I got light icons with dynamic colors for OH3

I’ve been experimenting with custom icons for the past couple of weeks, creating a floor plan-based UI in OH3 that my wife will tolerate. We have a lot of smart lights, and one of my goals was to help her (and me) find the right lamp quickly without relying on tooltips and text prompts. This is especially important for rooms where there is a cluster of lights close together.

My first rev was just some custom icons for each light type (which you can download from GitHub, though this is still a work in progress). But I wanted more. Namely, could I make the icons match the color of the light?

The answer to that, strictly speaking, is “no”. But…you can get really close if you are crazy, don’t mind having directories with thousands of files, and are willing to work with lengthy-ish expressions in the UI. Here’s what that looks like:

color1

color2

color3

My solution was to auto-generate icons with hue variations in 30-degree increments, from 0 to 360 (360 is needed for rounding), each with saturation variations in 25% steps and brightness levels in 10% steps. For those doing the math at home, that means each base icon has 780 variations. I named them “iconxHxSxB.svg” and then used an expression in the UI to set the icon dynamically:

  icon: ='oh:colorspotlightx'+
    Math.round(items.LibraryIris_Color.state.split(',')[0]/30)*30+'x'+Math.round(items.LibraryIris_Color.state.split(',')[1]/25)*25+'x'+
    Math.round(items.LibraryIris_Color.state.split(',')[2]/10)*10

This gives each icon a reasonably accurate representation of the color of the light itself. It’s not perfect, but it works and looks great (or I think it does). As long as you don’t mind the aforementioned inconveniences.

This started somewhat as a thought exercise, and now I’m kinda addicted to it. There are still some tweaks to be made, but when I have things to a point where I’m confident they will work, I’ll be sharing the color icons, too, along with quick instructions on how to use them.

4 Likes

Don’t know about crazy but that’s definitely some SERIOUS level of detail… :grinning:

Looks good, Putting it on my list of things to try out when I have some time.

1 Like

Thanks, @rbuurveld. Hopefully I’ll be ready to release when you are ready to try things out. The delay on my end is 1) making sure there’s not a better/easier way to do this in the UI (the less work and complexity on the UI end the better), and 2) updating icons so that they will still be visible when a light goes white, which will mean putting a thin border around those lights that don’t currently have one.

1 Like

Did you look into using the f7 icons and expressions to control the color of the icon? Then you wouldn’t need to create/save so many icons. What I don’t know is if one can define a custom color or have to use one of the defaults.

I tried to get F7 icons to work with arbitrary colors but neither hex syntax or rgb() syntax worked. The only thing that did work was their default set of color names.

But beyond that, the F7 icon selection is very generic. The question I got from my wife was, “Which one is the lamp?” :slight_smile:

Also, drawing the icons is fun. And I wrote a Python script to generate the color variations so once an icon is created it takes all of 5 seconds to build them out. The ext3 and ext4 filesystems don’t have any perf losses when you are referencing a file by name in a large directory, so there haven’t really been issues so far.

It would be nice if OH supported subdirectories for icons, though, as it would help keep things organized.

One feature of the markers that is not too obvious is that you can use a combination of useTooltipAsLabel, tooltipPermanent and tooltipStyles to pretty much “write” anything you want on the floorplan, and apply CSS in the process.

      coords: 266.41878600484523,877.2325880647344
      useTooltipAsLabel: true
      tooltipPermanent: true
      tooltip: ⬮
      tooltipStyles:
        textShadow: 0 0 2px black
        fontSize: 20px
        color: yellow

image

The thing here is that openHAB color items are HSV and CSS colors only accept HSL - it’s not too hard to convert from one to the other but in an expression it’s still quite tedious. There are util functions in f7 to help with that but they are not part of the expression scope (yet).

3 Likes

This is one of the first ideas I tried, but ran into exactly the issue you describe: transforming hsb to rgb or hsl with inline expressions ranges from ugly to really ugly.

CSS support for HWB is proposed for CSS4. But now I am addicted to icons, so…

You can use the style attribute and then use any CSS color you like for your icons.

You can use the style attribute and then use any CSS color you like for your icons

Now that I have a proof of concept, I am exploring options for using CSS to selectively colorize the SVG icons. That moves the load to the client which I am not super-excited about, but it would eliminate the need for pregens and give you the full color range…provided you are willing to do the HSV→HSL conversion (which is easy, but a bit of a hassle as an inline expression).

Hey @mechalas

First of all, nice work! It seems to be very ambitious project if I have a look at the dozens of images in your github repo - I like your passion! :slight_smile:


Don’t know if it’s ‘easier’, but there might be ways to reduce the complexity if the other extreme would be to creating thousands of icons :slight_smile:


It should work with css styles, besides the ones that the config parameter color has to offer (which is indeed limited to the f7-colorset) as Thomas mentioned.

Fully agree with you (and your wife) here - it would make your UI easier to handle and also a bit more unique. :slight_smile:


Another example of how passionate you are with this. :smiley: :+1:


I do similar things in my setup right now and this would be the solution I would recommend here.


What I had in mind would be to combine the possibility that Yannick mentioned. Reusing the always visible tooltip-window to apply your svg and changing its elements colors with the help of expressions inside that svg - which could look something like this (I used your Hue Iris image as an example here):

manipulating_svg

I did not apply any hsv2hsl conversion as it’s just a quick shot, but it should be possible and maybe the mentioned f7 util functions will be implemented at some point, which would make this a lot easier & cleaner.

This is what the plan-marker component would look like:

  - component: oh-plan-marker
    config:
      coords: 300,252
      useTooltipAsLabel: true
      tooltipPermanent: true
      iconSize: 70
      tooltipStyles:
        width: 100%
        height: 100%
        background-image: ='url(\'data:image/svg+xml,%3Csvg
          xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 896.34
          892.91\"%3E%3Cpath
          d=\"M448.5,660.5l-93,159s-26-5-30,17,11.5,46.5,31,59c11.29,7.24,68.48,30.93,84,3,5-9,3-18-2-32l96-155Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23606060\"/%3E%3Cpath
          d=\"M911,771.4c38.3-58.38,62.38-117.91,85.46-191.89,2.51-8.51-639-403.94-639-403.94a865.13,865.13,0,0,0-124.37,162.2l-9.76,19.55C177.61,584,722.3,907.5,911,771.4Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23878787\"/%3E%3Cpath
          d=\"M345,188S999,571,996.49,579.51C959.7,697.44,920.38,778.64,825,877c-81.79,82.35-192.2,129.84-302.24,129.84C399.42,1006.83,276.54,947.15,195,810,61.54,588.72,212,338,345,188m-4.07-25.56L330,174.73c-58.68,66.17-137.33,170.37-178.87,291-20.92,60.77-30.25,119.88-27.72,175.7,2.88,63.57,21.18,123.73,54.38,178.81,40.85,68.67,93.15,121.48,155.48,157a377,377,0,0,0,92.7,37.51,389.1,389.1,0,0,0,96.75,12.1c115.41,0,230.75-49.48,316.43-135.75l.08-.08.09-.09c47.69-49.18,82.91-95.45,110.83-145.59,24.75-44.46,44.31-92.27,65.4-159.86l0-.15,0-.15c1.43-4.84,2.28-14.26-8-22.86-1.62-1.36-3.71-2.93-6.58-4.95-4.84-3.4-11.74-8-21.1-14-16.85-10.77-41.7-26.16-73.85-45.73-54-32.86-129.45-78.05-224.26-134.3C520.52,267.63,356.74,171.7,355.11,170.74l-14.18-8.3Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23606060\"/%3E%3Cpath
          d=\"M890.09,646.62c-85.32,0-202.38-41.09-313.15-109.91-87.66-54.46-161.48-119.81-207.85-184-48.15-66.65-61.26-124.94-36.92-164.13,18.16-29.22,55.29-44.67,107.38-44.67,85.32,0,202.38,41.09,313.15,109.9,87.66,54.47,161.48,119.81,207.84,184,48.16,66.66,61.27,125,36.93,164.13C979.31,631.17,942.18,646.62,890.09,646.62Z\"
          transform="translate(-123.05 -133.92)\" fill=\"hsl('
          +items.YOUR_COLOR_ITEM.state.split(',')[0]+','+items.YOUR_COLOR_ITEM.state.split(',')[1]+'%,'+items.YOUR_COLOR_ITEM.state.split(',')[2]+'%'+')\"/%3E%3Cpath
          d=\"M439.55,153.92c80.37,0,194.73,38.11,307.87,108.4,179,111.23,287.17,260.92,241.55,334.35-16.79,27-52.07,40-98.88,40-80.37,0-194.74-38.11-307.87-108.4C403.19,417,295.05,267.3,340.67,193.87c16.79-27,52.07-40,98.88-40m20-20h-20c-70.41,0-101.87,26.86-115.87,49.4C297.05,226.18,310.3,288.42,361,358.58c47.11,65.21,121.93,131.49,210.67,186.63,55.81,34.67,114.67,63.19,170.22,82.46,54.58,18.94,105.83,29,148.21,29,70.4,0,101.87-26.87,115.87-49.4,26.63-42.86,13.38-105.1-37.31-175.26C921.54,366.75,846.72,300.47,758,245.33c-55.81-34.67-114.67-63.19-170.22-82.46-46.25-16.05-90.11-25.69-128.21-28.26v-.69Z\"
          transform=\"translate(-123.05 -133.92)\"
          fill=\"%23606060\"/%3E%3Cellipse cx=\"660.94\" cy=\"407.9\"
          rx=\"97.02\" ry=\"228.58\" transform=\"translate(-157.38 620.12)
          rotate(-58.15)\" fill=\"%23d6e7f0\"/%3E%3Cpath
          d=\"M911,771.4C722.3,907.5,177.61,584,223.35,357.32l9.76-19.55C149.26,479.46,102.51,656.64,195,810c154.3,259.52,456.54,241.65,630,67,39.18-40.41,68.9-77.92,92.87-116.27C915.64,764.28,913.37,767.85,911,771.4Z\"
          transform=\"translate(-123.05 -133.92)\" fill=\"hsl('
          +items.YOUR_COLOR_ITEM.state.split(',')[0]+','+items.YOUR_COLOR_ITEM.state.split(',')[1]+'%,'+items.YOUR_COLOR_ITEM.state.split(',')[2]+'%'+')\"
          opacity=\"0.3\"/%3E%3Cpath
          d=\"M890.09,646.62c-85.32,0-202.38-41.09-313.15-109.91-87.66-54.46-161.48-119.81-207.85-184-48.15-66.65-61.26-124.94-36.92-164.13,18.16-29.22,55.29-44.67,107.38-44.67,85.32,0,202.38,41.09,313.15,109.9,87.66,54.47,161.48,119.81,207.84,184,48.16,66.66,61.27,125,36.93,164.13C979.31,631.17,942.18,646.62,890.09,646.62Z\"
          transform=\"translate(-123.05 -133.92)\" fill=\"%23ffc84d\"
          opacity=\"0.5\"/%3E%3Cpath
          d=\"M439.55,153.92c80.37,0,194.73,38.11,307.87,108.4,179,111.23,287.17,260.92,241.55,334.35-16.79,27-52.07,40-98.88,40-80.37,0-194.74-38.11-307.87-108.4C403.19,417,295.05,267.3,340.67,193.87c16.79-27,52.07-40,98.88-40m20-20h-20c-70.41,0-101.87,26.86-115.87,49.4C297.05,226.18,310.3,288.42,361,358.58c47.11,65.21,121.93,131.49,210.67,186.63,55.81,34.67,114.67,63.19,170.22,82.46,54.58,18.94,105.83,29,148.21,29,70.4,0,101.87-26.87,115.87-49.4,26.63-42.86,13.38-105.1-37.31-175.26C921.54,366.75,846.72,300.47,758,245.33c-55.81-34.67-114.67-63.19-170.22-82.46-46.25-16.05-90.11-25.69-128.21-28.26v-.69Z\"
          transform=\"translate(-123.05 -133.92)\"
          fill=\"%23606060\"/%3E%3Cellipse cx=\"660.94\" cy=\"407.9\"
          rx=\"97.02\" ry=\"228.58\" transform=\"translate(-157.38 620.12)
          rotate(-58.15)\" fill=\"hsl('
          +items.YOUR_COLOR_ITEM.state.split(',')[0]+','+items.YOUR_COLOR_ITEM.state.split(',')[1]+'%,'+items.YOUR_COLOR_ITEM.state.split(',')[2]+'%'+')\"/%3E%3C/svg%3E\')'
        background-size: contain
        background-repeat: no-repeat
        background-position: center
      name: Hue Iris
      action: popover
      actionModal: widget:light_color_selection
      actionModalConfig:
        item: YOUR_COLOR_ITEM

Some things you have to be aware of with this solution:

  • You have to escape the quotation marks inside the encoded svg to make the whole background-image a valid expression.
  • The name of the item you want to control inside the background-image expression have to be hardcoded, as it’s not possible to reuse the parameters of other widgets or set property parameters on the floor pages itself right now (afaik).
  • You can manipulate the size of the image with the ‘iconSize’ config parameter

I’m curious to see your final solution. Keep up the good work!

1 Like

Wow! You have saved me a lot of time here, and the result looks terrific! And it’s pretty fun seeing my Iris image lit up using this method.

I can use a script to automate the embedded SVG, including the quoting and the item name substitution. That will eliminate accidents and speed up entry. It’s also something I can include in my GitHub archive, and give people the option of pregens or on-the-fly coloration.

There are a couple of compact representations of HSV->HLS conversion that should be good enough/close enough for this purpose.

I need to go over my SVG drawings again and make sure they are as compact as possible. Just for sanity’s sake.

Can’t thank you enough for your help and input. It is much appreciated!

1 Like

You created pretty nice representations of the real-world objects - so it’s also beautiful to look at.


If you’re into scripting, this sounds like an awesome idea and would make it much easier for other people recreating something similar.


Yes, think so too. But still the easiest and maybe cleanest way without concatenating calculations would be the f7 function. You might also save some lines of code with that.


They seems to be thought out well already without that much room for optimizations. :slight_smile:

In regards of saving some bytes of code, you could…

  • …group the elements that the color should be assigned to and apply the fill style to that group instead of the individual element, which would reduce the needed expressions from 3 to 1 (in this case).
  • …combine the css background properties like this:
        background: ='no-repeat url(\'data:image/svg+xml,%3Csvg
          xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 896.34
          892.91\"%3E%3Cpath
          d=\"M448.5,660.5l-93,159s-26-5-30,17,11.5,46.5,31,59c11.29,7.24,68.48,30.93,84,3,5-9,3-18-2-32l96-155Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23606060\"/%3E%3Cpath
          d=\"M911,771.4c38.3-58.38,62.38-117.91,85.46-191.89,2.51-8.51-639-403.94-639-403.94a865.13,865.13,0,0,0-124.37,162.2l-9.76,19.55C177.61,584,722.3,907.5,911,771.4Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23878787\"/%3E%3Cpath
          d=\"M345,188S999,571,996.49,579.51C959.7,697.44,920.38,778.64,825,877c-81.79,82.35-192.2,129.84-302.24,129.84C399.42,1006.83,276.54,947.15,195,810,61.54,588.72,212,338,345,188m-4.07-25.56L330,174.73c-58.68,66.17-137.33,170.37-178.87,291-20.92,60.77-30.25,119.88-27.72,175.7,2.88,63.57,21.18,123.73,54.38,178.81,40.85,68.67,93.15,121.48,155.48,157a377,377,0,0,0,92.7,37.51,389.1,389.1,0,0,0,96.75,12.1c115.41,0,230.75-49.48,316.43-135.75l.08-.08.09-.09c47.69-49.18,82.91-95.45,110.83-145.59,24.75-44.46,44.31-92.27,65.4-159.86l0-.15,0-.15c1.43-4.84,2.28-14.26-8-22.86-1.62-1.36-3.71-2.93-6.58-4.95-4.84-3.4-11.74-8-21.1-14-16.85-10.77-41.7-26.16-73.85-45.73-54-32.86-129.45-78.05-224.26-134.3C520.52,267.63,356.74,171.7,355.11,170.74l-14.18-8.3Z\"
          transform="translate(-123.05 -133.92)\" fill=\"%23606060\"/%3E%3Cpath
          d=\"M890.09,646.62c-85.32,0-202.38-41.09-313.15-109.91-87.66-54.46-161.48-119.81-207.85-184-48.15-66.65-61.26-124.94-36.92-164.13,18.16-29.22,55.29-44.67,107.38-44.67,85.32,0,202.38,41.09,313.15,109.9,87.66,54.47,161.48,119.81,207.84,184,48.16,66.66,61.27,125,36.93,164.13C979.31,631.17,942.18,646.62,890.09,646.62Z\"
          transform="translate(-123.05 -133.92)\" fill=\"hsl('
          +items.Bad_Licht_Main_Clr.state.split(',')[0]+','+items.Bad_Licht_Main_Clr.state.split(',')[1]+'%,'+items.Bad_Licht_Main_Clr.state.split(',')[2]+'%'+')\"/%3E%3Cpath
          d=\"M439.55,153.92c80.37,0,194.73,38.11,307.87,108.4,179,111.23,287.17,260.92,241.55,334.35-16.79,27-52.07,40-98.88,40-80.37,0-194.74-38.11-307.87-108.4C403.19,417,295.05,267.3,340.67,193.87c16.79-27,52.07-40,98.88-40m20-20h-20c-70.41,0-101.87,26.86-115.87,49.4C297.05,226.18,310.3,288.42,361,358.58c47.11,65.21,121.93,131.49,210.67,186.63,55.81,34.67,114.67,63.19,170.22,82.46,54.58,18.94,105.83,29,148.21,29,70.4,0,101.87-26.87,115.87-49.4,26.63-42.86,13.38-105.1-37.31-175.26C921.54,366.75,846.72,300.47,758,245.33c-55.81-34.67-114.67-63.19-170.22-82.46-46.25-16.05-90.11-25.69-128.21-28.26v-.69Z\"
          transform=\"translate(-123.05 -133.92)\"
          fill=\"%23606060\"/%3E%3Cellipse cx=\"660.94\" cy=\"407.9\"
          rx=\"97.02\" ry=\"228.58\" transform=\"translate(-157.38 620.12)
          rotate(-58.15)\" fill=\"%23d6e7f0\"/%3E%3Cpath
          d=\"M911,771.4C722.3,907.5,177.61,584,223.35,357.32l9.76-19.55C149.26,479.46,102.51,656.64,195,810c154.3,259.52,456.54,241.65,630,67,39.18-40.41,68.9-77.92,92.87-116.27C915.64,764.28,913.37,767.85,911,771.4Z\"
          transform=\"translate(-123.05 -133.92)\" fill=\"hsl('
          +items.Bad_Licht_Main_Clr.state.split(',')[0]+','+items.Bad_Licht_Main_Clr.state.split(',')[1]+'%,'+items.Bad_Licht_Main_Clr.state.split(',')[2]+'%'+')\"
          opacity=\"0.3\"/%3E%3Cpath
          d=\"M890.09,646.62c-85.32,0-202.38-41.09-313.15-109.91-87.66-54.46-161.48-119.81-207.85-184-48.15-66.65-61.26-124.94-36.92-164.13,18.16-29.22,55.29-44.67,107.38-44.67,85.32,0,202.38,41.09,313.15,109.9,87.66,54.47,161.48,119.81,207.84,184,48.16,66.66,61.27,125,36.93,164.13C979.31,631.17,942.18,646.62,890.09,646.62Z\"
          transform=\"translate(-123.05 -133.92)\" fill=\"%23ffc84d\"
          opacity=\"0.5\"/%3E%3Cpath
          d=\"M439.55,153.92c80.37,0,194.73,38.11,307.87,108.4,179,111.23,287.17,260.92,241.55,334.35-16.79,27-52.07,40-98.88,40-80.37,0-194.74-38.11-307.87-108.4C403.19,417,295.05,267.3,340.67,193.87c16.79-27,52.07-40,98.88-40m20-20h-20c-70.41,0-101.87,26.86-115.87,49.4C297.05,226.18,310.3,288.42,361,358.58c47.11,65.21,121.93,131.49,210.67,186.63,55.81,34.67,114.67,63.19,170.22,82.46,54.58,18.94,105.83,29,148.21,29,70.4,0,101.87-26.87,115.87-49.4,26.63-42.86,13.38-105.1-37.31-175.26C921.54,366.75,846.72,300.47,758,245.33c-55.81-34.67-114.67-63.19-170.22-82.46-46.25-16.05-90.11-25.69-128.21-28.26v-.69Z\"
          transform=\"translate(-123.05 -133.92)\"
          fill=\"%23606060\"/%3E%3Cellipse cx=\"660.94\" cy=\"407.9\"
          rx=\"97.02\" ry=\"228.58\" transform=\"translate(-157.38 620.12)
          rotate(-58.15)\" fill=\"hsl('
          +items.Bad_Licht_Main_Clr.state.split(',')[0]+','+items.Bad_Licht_Main_Clr.state.split(',')[1]+'%,'+items.Bad_Licht_Main_Clr.state.split(',')[2]+'%'+')\"/%3E%3C/svg%3E\')
          center / contain'

You’re welcome!

P.S. All of this could be done also in the layout pages with the benefit that you could use props inside the svg to assign different items dynamically to the image.

Thank you for the compliments.

Adobe Illustrator did the SVG export and like any automated process it can only optimize so much. Now that I have most of these drawings where I want them, I can tweak the SVG by hand. I won’t have as much time to poke at this in the coming week or two, but I will keep at it. There are some great suggestions in here.