Can you parameterize a style?

This will eventually be for Perspective.

I want to dynamically select a fill style for an SVG, between two predefined styles. I understand how to achieve this via a binding.

But I want to create these pairs of of styles in different sets of colors. EG 2 styles of red, 2 styles of green and 2 styles of yellow. This means for any single SVG I want to apply a style to, my binding will have to select the pair to use, and then which style within the pair to actually apply.

What I would want to do is create a single style pair, and then parameterize the actual color the style will use. And then in the instance of the SVG, supply that color as a separate parameter.

Is this possible, or will I get my pony first?

Have you taken a look at Map Transforms and Expression Transforms in Perspective? A map transform in Perspective with an Output Type of Color may get what you’re looking for.

I’m not sure a map transform will get what I want.

I wasn’t too explicit in my original post, but what I am wanting to parameterize in a style is a radial fill from one color to another. The first color is fixed, but I want to parameterize the second color. That way I don’t have a humungous number of styles just hanging around. (actually if I could parameterize both colors then that would mean I only need 1 style to rule them all)

And after some thought, I actually want 4 styles per color, so this becomes a bit of maintenance nightmare if I want all the styles to look the same (except for color changes)

A simple solution would be to create session variables to hold the various gradients.
Radial gradient session props

And then reference the session variables in the binding. e.g. session.custom.gradients[1].

Alternately a session.custom object such as

"gradients": {
 "red":   "radial-gradient(#770000, #cc0000)",
 "green": "radial-gradient(#007700, #00cc00)",
 "blue":  "radial-gradient(#000077, #0000cc)",
 "grey":  "radial-gradient(#777777, #cc7777)"
}

and now you can bind with something like session.custom.gradient.blue.

Better again is to not name your gradients by colour but by function.

"gradients": {
 "off":      "radial-gradient(#770000, #cc0000)",
 "on":       "radial-gradient(#007700, #00cc00)",
 "safe" :    "radial-gradient(#000077, #0000cc)",
 "disabled": "radial-gradient(#777777, #cc7777)"
}

That way, if you change your colour scheme your gradient names won’t be wrong.

Thanks for that, but I don’t think your solution captures what I am trying to do.

This image below is a closer example of what I want to be able to display. The columns represent the 4 states that I want to represent, and the rows represent examples of colors that I want to use for each set of states. I have shown 4 colors, which means using your method I need to select between 16 different gradients.

But those are just the 4 colors I chose to illustrate my requirements. I want to be free in my choice of color without having to manually create and edit gradients for each new color I desire to use.

Screen Shot 2022-04-01 at 11.43.55 AM

Nothing to stop you from using CSS variables in a theme in combination with Transistors method.

"gradients": {
"off":            "radial-gradient(var(--offStart),var(--offEnd))",
"on":             "radial-gradient(var(--onStart),var(--onEnd))",
"safe":           "radial-gradient(var(--safeStart),var(--safeEnd))",
"disabled":       "radial-gradient(var(--disabledStart),var(--disabledEnd))"
}

Does that mean I would end up with themes named by my color schemes? And then separately bind both the selected theme and required gradient?

I can see how this works, but I also get the impression that this is just punting the problem to another location - where the themes are maintained, and still having to manually edit each color.

But it may be that I get my pony first :rofl:

You would have to declare the variables in a theme file yes. But you would only need one theme file to hold the variables. If you have a light theme and a dark theme, there is nothing that prevents them from referencing the same file.

You’re going to have type in the values at some point. You pick where you want that to happen?

When you say you want to “parameterize” this, you still have to set that parameter right?

You can also use the CSS rgb() or rgba() functions. CSS rgba() function.

I strongly believe what you are wanting to do is possible.

Typing in all of the colors is what I am trying to avoid.

From my example image, the fills are technically like this for each column (left to right, as pseudo code using @Transistors naming):

"gradients::{
  "off":      "radial-gradient( [BaseColor], [BaseColor])",
  "on":       "radial-gradient( White, [BaseColor])",
  "safe":     "radial-gradient( [BaseColor], [BaseColor], Black)",
  "disabled": "radial-gradient(White, [BaseColor], Black)",
}

The only thing that changes across all the 4 columns is the [BaseColor], which would be fixed for any single instance of the object. And I'd prefer to set it as a static parameter on the object instance itself. And it is also the [BaseColor] that I am trying to parameterize.

Okay, so create a project script with the following code:

def getRadialGrad(state,baseColor):
    #add as many key-value pairs as you want
    grad = {'off':'radial-gradient({0},{0})','on':'radial-gradient(var(--white),{0}'}
    return grad[state].format(baseColor)

Then add an expression binding to the background-image style attribute with this expression:

runScript('project.shared.getRadialGrad',0,self.custom.state,self.custom.baseColor)

Now you just have to set your base color to a CSS recognized color or function and you’re good.

As an aside, it might be very worth your while to review the RealPars short video series on High Performance HMI before you go too far down this road. Garish is bad.

Another thing to consider: What will a colourblind person see when they look at your screen? Here’s part of your image rendered by the Coblis ColorBline Simulator set at Green-Blind/Deuteranopia.

Colourblind HMI

Colorblindly is a Chrome extension that will quickly show you how your browser view will appear to an affected user.

In general garish is bad, but my use case for this particular situation is to fully replicate physical operator control panels in a skeuomorphic manner in order to aid in system test of the software. The idea is that I want to prove that my software can be used to drive the machinery in the same manner as an operator would, and to see the same feedbacks. Making the controls look like the actual panel helps in that.

OTOH It's kinda hard to fit a 100m tall crane into my home office, and I'm definitely not getting the panels shipped from China and trying to wire them up in my garage :rofl:

That’s exactly the sort of thing that I am looking for!

Thanks for that.

@lrose Can you show an example of this binding? I can't seem to make it work.

Sorry for the delayed response, been out on vacation.

In this instance, I added the script to the Project Library, I named the script testScript

Then on a label, I added a custom property sate. I chose to use the backgroundColor attribute of the style to select the base color, though you could also create a custom property for this. Using the backgroundColor allows you to use the color picker.

Then I added a binding to the backgroundImage style attribute with the following expression:

The following is the result:

What, you took vacations??? How dare you! :wink:

But thanks for that. But unfortunately that method doesn’t seem to work for an SVG circle. The background image seems to be the extent of the rectangle that encompasses the circle:
Screen Shot 2022-04-11 at 7.38.04 AM

But don’t concern yourself with it any more. I moved on to a method that involves calculating the gradient breakpoint colors with the help of a python script and binding those values directly to the SVG applied gradient.

:laughing: I know, I know

I can't help myself. :wink:

I'm not sure how I didn't pick up on you wanting to do this on an SVG. Somehow I missed it though. For completeness, it is possible, you just have to know what it's looking for.

For this, I added a blank SVG and chose to embed it, I then added a circle element to it with the following values:
image

I added two custom properties (state and baseColor). I modified the script to provide the proper structure and added the binding to a paint object in the Fill for the element. Binding was modified to account for custom property
Binding:

Script:

def getRadialGrad(state,baseColor):
	grad = { 'off': 	[{'offset':0,'stopColor':baseColor},{'offset':1,'stopColor':baseColor}],
			 'on':		[{'offset':0,'stopColor':'var(--white)'},{'offset':1,'stopColor':baseColor}],
			 'safe':	[{'offset':0,'stopColor':baseColor},{'offset':0.5,'stopColor':baseColor},{'offset':1,'stopColor':'var(--Black)'}],
			 'disabled':[{'offset':0,'stopColor':'var(--white)'},{'offset':0.5,'stopColor':baseColor},{'offset':1,'stopColor':'var(--Black)'}]}
	return {'type':'radial','cx':22,'cy':21.5,'r':16,'gradientUnits':'userSpaceOnUse','stops':grad[state]}

Result with the state set to 'safe' and a baseColor of #FF8F00

Hopefully, it's obvious, but the offsets will need to be changed to suit your purposes. Also, you don't have to use hexadecimal colors, any valid CSS that resolves to a color will work. The cx and cy points were selected based on what I have here, they are dependent upon how your SVG is configured.

Needless to say that this technique could also be applied to a linear gradient.

1 Like

That looks a lot less complicated than what I ended up using. What I did still works OK, but this is more explicit as to how to achieve what I want. So I might implement it.