Perspective graphic outline color darker than fill

If I have a graphic, example a simple valve, and I do a fill color dynamically, how would I set the stroke to a color a little darker than the fill color? I’m not really seeing a way to bind this.

I assume you mean the Valve Symbol component.

You can change the fill and stroke by adding them to the style, though if you are also using the animation then those may possibly be overwritten.


Of course you can add bindings there as needed.

Alternatively you can target them using CSS. You can then bind to the style.clasess property and change the class as needed.

There is more on that here:

Not sure what the status of the feature request is.

Sorry, my bad for being vague. The graphic is irrelevant, but for example take a simple SVG, a circle. If the fill color is green I want the stroke color to be a few shades darker and I want this dynamic. The fill color may come from a theme or a style, it just depends. How can we set stroke based on another color, in this case fill or background? I can’t always use a style as I may want to target only one element. I don’t see a way to bind something in an element to the fill color from a style.

Well you could bind to the fill color and then use the java.awt.Color class in a script transform.

Something like:

from java.awt import Color
ret = Color.decode(value).darker()
return "#{:0<6X}".format(abs(ret.getRGB())

The string conversion is needed because CSS will not recognize a Java Color

If you want more control you could do something like this:

from java.awt import Color
org = Color.dcode(value)
hsbvals = [0.0] * 3
hsb = Color.RGBtoHSB(org.getRed(),org.getGreen(),org.getBlue(),hsbvals)
hsbvals[2] -= 15
ret = Color.HSBtoRGB(hsbvals[0],hsbvals[1],hsbvals[2])
return "#{:0<6X}".format(abs(ret))

Though if I’m being honest, I don’t know all that much about the HSB Color Model and I’m not entirely sure this will yield what you are looking for. I know that in my testing it didn’t yield what I would have expected. Though from my understanding these should return a color which is “darker” than what it was given.

I feel like there should be a way to do that with css, but I can’t figure it out…
Can’t target the stroke specifically to use filter on, and couldn’t find a way to use fill's color with rgba or hsl without having to use dirty tricks.

Damn. @victordcq ?

Define your starter colors in a theme?

For most graphics the starter colors will come from a theme.

@lrose got me in the right direction, I will try and see if that gets me anywhere. So far, the script didn’t work, perhaps because ‘value’ is a color from a theme??

That’s cheating though.

(clearly this is what should be done. I was just trying to go from #0000FF to let’s say #000099)

Yeah, the script assumes a #000000 format. So anything like var(--color) wouldn’t work.

There isn’t a way to get the actual color values from a theme file, because that all happens on the browser end. In that case you would have to target the stroke with CSS.

EDIT: Note, if you are using standard color names (e.g. red,black,blue,etc) for you CSS variable names, it would be theoretically possible to use the script. Prerequisites for that would include:

  1. The name used would have to be included in the java.awt.Color static fields
  2. You would need to slice or otherwise pull the color name out of the color

For instance if your fill color was set to something like var(--Red) then you could use:

clr = Color.getColor(value[6:-1]) 

Thanks all, looks like I will cheat as @pascal.fragnoud put it :slight_smile:

That code is definitely cleaner than mine, I would only caution against interchanging HSB and HSL as they are not the same.

Here’s my whacky attempt using Expression Language.

  • On the valve create a custom property called darkness. Its value can be between 0 and 1. Set it to 0.5 to test it.
  • Create an expression binding on the stroke property. Add in the expression:
'#' + right("000000" + 	// Ensure leading zeros.
		floor(fromHex(substring({}, 1, 3)) * {this.custom.darkness}) * 0x10000 // Red
		+ floor(fromHex(substring({}, 3, 5)) * {this.custom.darkness}) * 0x100 // Green
		+ floor(fromHex(substring({}, 5, 7)) * {this.custom.darkness})         // Blue
	6			// 6 rightmost characters.

Valve darkness

The result.

The expression just takes each of the RGB values and scales it by the darkness setting before recombining them into the hex color string.

(The wonky color wheel is due to GIF’s limit of 256 colors.)

Valve darkness fader

The effect with a fixed fill color and variable darkness.

1 Like

@Transistor look at you go! Thanks, I will try this later. From what I see, it works well.

Oh, I forgot, I’m using themes, so that expression won’t work, but I will keep that in my arsenal for future use. Thanks for sharing @Transistor

css does not know what colors the other elements use, so it would not be possible to let it decide its stroke color itself…

However you could try reworking the svg to use some sort of shadow only on the edges with a transparent color. That would make it look like a darker stroke.

I think the solutions mentioned before me are better.

Is it possible to create a gateway script to read the theme files in and extract the theme colors into a dictionary? This could then be written to a session variable for use by components such as SVGs and charts which don’t understand the CSS variables.

Does Ignition have access to its own files on the various systems? It should at least be able to gain access by HTTPS.

theme files are located here
C:\Program Files\Inductive Automation\Ignition\data\modules\com.inductiveautomation.perspective\themes

im sure you can read them tho idk if there is an easy way to parse these. you will probably need a library of sorts. you can acces them in the inspector tho:

       --white: #FFFFFF;
    --black: #000000;
    --neutral-10: #FAFAFA;
    --neutral-20: #F4F4F4;
    --neutral-30: #D8D8D8;
    --neutral-40: #BDBDBD;
    --neutral-50: #A1A1A1;
    --neutral-60: #767676;
    --neutral-70: #5E5E5E;
    --neutral-80: #515151;
    --neutral-90: #323232;
    --neutral-100: #161616;
    --seq-1: #AE74E8;
    --seq-2: #9659D6;
    --seq-3: #7D3CBD;
    --seq-4: #642B9E;
    --seq-5: #4B2175;
    --seq-6: #371C52;
    --div-1: #642B9E;
    --div-2: #7D3CBD;
    --div-3: #9656D6;
    --div-4: #AE74E8;
    --div-5: #C79BF2;
    --div-6: #DABCF7;
    --div-7: #EAD9FA;
    --div-8: #F5F0FA;
    --div-9: #D7FAF8;
    --div-10: #83F2EB;
    --div-11: #43DED3;
    --div-12: #21C2B7;
    --div-13: #0EA197;
    --div-14: #08827A;
    --div-15: #086962;
    --div-16: #09524D;
    --qual-1: #7D3CBD;
    --qual-2: #229AD6;
    --qual-3: #086962;
    --qual-4: #B01355;
    --qual-5: #F55353;
    --qual-6: #661414;
    --qual-7: #038537;
    --qual-8: #114599;
    --qual-9: #ED5393;
    --qual-10: #CF7911;
    --containerRoot: var(--neutral-10);
    --container: var(--neutral-20);
    --containerNested: var(--neutral-30);
    --input: var(--white);
    --input--disabled: var(--neutral-40);
    --icon: var(--neutral-70);
    --icon--hover: var(--neutral-60);
    --icon--disabled: var(--neutral-40);
    --icon--selected: var(--neutral-10);
    --label: var(--neutral-90);
    --label--disabled: var(--neutral-60);
    --border: var(--neutral-50);
    --border--disabled: var(--neutral-50);
    --containerBorder: 1px solid var(--border);
    --boxShadow1: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
    --boxShadow2: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
    --boxShadow3: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
    --boxShadow4: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
    --boxShadow5: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
    --boxShadow--inset: inset 0 0 4px 2px rgba(34, 34, 34, 0.4);
    --callToAction: #0C7BB3;
    --callToActionHighlight: #E8F4FA;
    --callToAction--hover: #229AD6;
    --callToAction--active: #066391;
    --callToAction--disabled: var(--neutral-50);
    --error: #DE1B1B;
    --info: #3272D9;
    --infoSecondary: #ACCBFC;
    --warning: #AD5F00;
    --warningSecondary: #F5BC76;
    --success: #038537;
    --borderRadius: 4px;
    --borderRadiusInput: 2px;
    --opacity-25: rgba(0, 0, 0, 0.25);
    --opacity-50: rgba(0, 0, 0, 0.50);
    --opacity-85: rgba(0, 0, 0, 0.85);
    --font-NotoSans: 'Noto Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    --indicator: #1EC963;
    --indicatorOff: #0A2E18;
    --checkbox--checked: var(--callToAction);
    --checkbox--unchecked: var(--border);
    --checkbox--indeterminate: var(--callToAction);
    --checkbox--disabled: var(--input--disabled);
    --progressLinearTrack--determinate: var(--neutral-10);
    --progressLinearTrack--indeterminate: var(--neutral-10);
    --progressLinearBar--determinate: var(--infoSecondary);
    --progressLinearBar--indeterminate: var(--border);
    --toggleSwitch--selected: var(--callToAction);
    --toggleSwitch--unselected: var(--neutral-10);
    --radio--selected: var(--callToAction);
    --radio--unselected: var(--border);
    --radio--disabled: var(--input--disabled);
    --pipePrimaryFill: var(--neutral-20);
    --pipeSecondaryFill: #cccccc;
    --pipeStroke: var(--neutral-70);
    --pipeSelectStroke: var(--callToAction);
    --symbolFill--default: var(--neutral-40);
    --symbolFillAnimation--default: var(--neutral-50);
    --symbolStroke--default: var(--neutral-70);
    --symbolFill--running: var(--white);
    --symbolFillAnimation--running: var(--neutral-50);
    --symbolStroke--running: var(--neutral-70);
    --symbolFill--stopped: var(--neutral-40);
    --symbolStroke--stopped: var(--neutral-70);
    --symbolFill--faulted: #FAB6B6;
    --symbolStroke--faulted: #B80D0D;

I was thinking of binding the fill’s color to the stroke property and using this in some sort of css function, in which case it doesn’t need to know the color of other element, we’re providing it.
I’m kind of surprised there isn’t already something like darken(color, x%).

There is
filter: brightness(50%)
(and you can go > 100% too) but it applies to both the fill and the stroke. I can’t see a way to restrict it to the stroke.