Perspective Templates - Hiccups while developing company library (styles, SVGs, embedded views)

I could use some pointers here. I am developing our company’s standard library in perspective. We are a large system integrator, so getting this right the first time is important. I am using the new drawing tools introduced in 8.3 for our svg’s. To give some background, we have a style we would like to stick with. This includes gradient colors on almost every object. For example: a valve that is in auto would have a purple head, manual would be yellow, faulted would be red. We want to incorporate a greyscale option since that is becoming more and more common in the industry. This means I would need a total of 6 gradients for just the head.

I couldn’t quite figure out how to tie a style to the svg elements, and also couldn’t quite figure out how to make a style with a gradient color. The simplest option to me seemed to have custom properties on the drawing with the color gradients. Then write a simple if statement to change color (based on greyscale, and feedback from the PLC). I have it working ok, but this will be a standard at my company for a long time. I want to keep it as simple as possible, and this feels a bit more messy than I would like. I am running into another issue that might be caused my some of the extra steps I am taking here.

What is happening is that some of the valves won’t show up when dragged into new views, or when opening views with valves. Luckily it seems isolated to designer, not client sessions. Still a bit frustrating, and makes me worried we will run into issues when we start to work on bigger projects.

If you change something on the broken valve properties, it will magically fix itself. For example, I toggled the “NormallyOpen” bit off then on, and now the valve shows up.

Again, I really want to nip these issues in the bud before I build out any more of the library. Any tips or “lessons learned” while developing a library would be much appreciated!

1 Like

<I don’t know what’s up with my account… but logging into it from a new PC has seemingly created a new account, possibly because I got my email address changed recently from an old work aliased address to my actual work email, and now the system is confused..>

Anyway, how I would always do colours is via theme variables. I would create themes for you different display modes: standard/ASM, colourful, colourblind?, etc. Each theme defines the same set of colour variables, only with different values in each. E.g.:

/* ASM Theme */
:root {
  --device--on-grad-outer: hsl(0, 0%, 80%);
  --device--on-grad-inner: hsl(0, 0%, 0%);

  --device--off-grad-outer: hsl(0, 0%, 40%);
  --device--off-grad-inner: hsl(0, 0%, 60%);
}

Then reference these colour variables in your graphics. Unfortunately it’s not possible to use CSS gradient functions within SVG fills, so you’ll need to create an SVG gradient as you have, and reference the CSS variables directly within the gradient stop colours in the SVG elements which is a bit painful. If you do need to use the gradient more than once within the same SVG, you can define the gradient essentially as a variable within the SVG and reference this. I would use Inkscape to create this for you as this is what it defaults to using when you add a gradient to anything. See my beautiful SVG example below.

Embedded SVG Ignition component JSON
[{"type":"ia.shapes.svg","version":0,"props":{"viewBox":"0 0 26.458333 26.458333","elements":[{"type":"defs","name":"defs1","id":"defs1","elements":[{"type":"linearGradient","name":"linearGradient1","id":"linearGradient1","elements":[{"type":"stop","name":"stop1","id":"stop1","offset":"0","style":{"stopOpacity":"1","stopColor":"var(--device--on-grad-outer)"}},{"type":"stop","name":"stop3","id":"stop3","offset":"0.51104099","style":{"stopOpacity":"1","stopColor":"var(--device--on-grad-inner)"}},{"type":"stop","name":"stop2","id":"stop2","offset":1,"style":{"stopOpacity":"1","stopColor":"var(--device--on-grad-outer)"}}]},{"type":"linearGradient","name":"linearGradient2","id":"linearGradient2","x1":"7.0625973","y1":"8.1149709","x2":"17.679879","y2":"8.1149709","gradientUnits":"userSpaceOnUse","href":"#linearGradient1"}]},{"type":"group","name":"layer1","id":"layer1","elements":[{"type":"rect","name":"rect1","id":"rect1","x":"7.0625973","y":"2.1047475","width":"10.617282","height":"12.020447","fill":{"url":"url(#linearGradient2)"},"stroke":{"width":"0.264583"}},{"type":"ellipse","name":"path1","id":"path1","cx":"13.283296","cy":"17.469404","rx":"10.757599","ry":"8.0214262","fill":{"paint":"#000000"},"stroke":{"width":"0.264583"}}]}]},"meta":{"name":"drawing_0"},"position":{"x":347,"y":535,"height":100,"width":100},"custom":{}}]

You would then put your expression binding on those stopColor values, like:

case({view.custom.status}
	,'Closed', 'var(--device--off-grad-outer)'
	,'Opened', 'var(--device--on-grad-outer)'
	,'Faulted', 'var(--device--fault-grad-outer)'
	...
)

Or, what I like to do, is have a dataset tag which maps my statuses to style classes. In your case where you can’t use style classes, they would have to reference the variable names, or at least their partial names like --device--on and then you would append onto that the rest of what you need. Then in the expression above, you’d instead use:

lookup({[default]System/Datasets/StyleClasses/Device Statuses}, {view.custom.status}, '', 'value', 'cssColourPrefix') + '-grad-outer'

Of course, I would encourage you to go away from using gradients at all which is the recommendation of the ISA-101 HMI standards.

2 Likes

Ps. The dataset is used so that you're centrally defining the map, not duplicating it everywhere and creating a lot more work for any changes.

Pps. That above is actually me… I'm on my phone now which which is still signed in to my proper account

1 Like

IA support can help you with changing your account email.

Yeah I know, I'm still on long service leave so I haven't reached out yet, but I'll get it fixed!

Instead of doing a series of gradient fill colors consider drawing your symbols with sharp grayscale gradients and then make a partially opaque fill overlay over top of that which you animate the color on. This allows you to do a fill on the opaque overlay with a simple color fill and retain the gradient detail.

I’ve been busy with a number of 8.1 apps so I haven’t touched 8.3 since it first came out. I’m just mentioning this as a general design decision that simplifies animations generally.

2 Likes

If you do this option, you would want to set the blend mode of the fill colour element to something like color so that you don’t end up with a washed out colour

3 Likes

Thanks for the feedback I’ve received so far. It has been very helpful. I have brought up concerns with following ISA 101 a bit closer, that is why we are looking to add the greyscale option. In reality, it will be more of an “ISA standard” option. In the end, we are just integrators. It all comes down to what the customers want.

I think I have solved the broken objects issue. Our original objects included some colors using 3 stop gradients, and others using 5 stops. For this reason, I was pushing the entire stops array. While testing, I found that if I changed all colors we use to be 3 stops only, I can achieve almost the same look. Now I only write the colors to the stopColor value. When I did this, I no longer get the random valves not working (which for the record, it did also happen in the clients later on). It is more work to program, but it is working really well. The real question is.. did I make a mistake the first time, never caught it, and fixed it without realizing it? Or was there more to the story?

I really like the idea Steve_Laubach shared of using an overlay, but as nminchin mentioned, my colors did look very washed out. Where can I find/add the “fill colour element” value? I’m still pretty new to editing svg’s like this.

Did you mean the blend mode?

This shows you where to add it, and what some of the different modes do. color is most relevant to you here for this.

Here’s that SVG if it helps:

SVG JSON - Copy and Paste into a Perspective View
[
  {
    "type": "ia.shapes.svg",
    "version": 0,
    "props": {
      "viewBox": "0 0 26.458333 26.458333",
      "elements": [
        {
          "elements": [
            {
              "elements": [
                {
                  "id": "stop1",
                  "name": "stop1",
                  "offset": 0,
                  "style": {
                    "stopOpacity": "1",
                    "stopColor": "hsl(0 0% 60%)"
                  },
                  "type": "stop"
                },
                {
                  "id": "stop3",
                  "name": "stop3",
                  "offset": "0.51104099",
                  "style": {
                    "stopColor": "hsl(0 0% 80%)",
                    "stopOpacity": "1"
                  },
                  "type": "stop"
                },
                {
                  "id": "stop2",
                  "name": "stop2",
                  "offset": 1,
                  "style": {
                    "stopOpacity": 1,
                    "stopColor": "hsl(0 0% 60%)"
                  },
                  "type": "stop"
                }
              ],
              "id": "linearGradient1",
              "name": "linearGradient1",
              "type": "linearGradient"
            },
            {
              "gradientUnits": "userSpaceOnUse",
              "href": "#linearGradient1",
              "id": "linearGradient2",
              "name": "linearGradient2",
              "type": "linearGradient",
              "x1": "7.0625973",
              "x2": "17.679879",
              "y1": "8.1149709",
              "y2": "8.1149709"
            }
          ],
          "id": "defs1",
          "name": "defs1",
          "type": "defs"
        },
        {
          "elements": [
            {
              "fill": {
                "url": "url(#linearGradient2)"
              },
              "height": 25,
              "id": "GRADIENT",
              "name": "GRADIENT",
              "stroke": {
                "width": "0.264583"
              },
              "type": "rect",
              "width": 10,
              "x": 7,
              "y": 2
            },
            {
              "fill": {
                "paint": "#FF8100"
              },
              "id": "path1",
              "name": "path1",
              "stroke": {
                "width": "0.264583"
              },
              "type": "rect",
              "style": {
                "mixBlendMode": "color"
              },
              "x": 2,
              "y": 4,
              "width": 20,
              "height": 2
            },
            {
              "type": "rect",
              "id": "path2",
              "name": "path2",
              "fill": {
                "paint": "#FF8100"
              },
              "stroke": {
                "width": "0.264583"
              },
              "x": 2,
              "y": 6.5,
              "width": 20,
              "height": 2,
              "style": {
                "mixBlendMode": "difference"
              }
            },
            {
              "type": "rect",
              "id": "path1",
              "name": "path1",
              "fill": {
                "paint": "#FF8100"
              },
              "stroke": {
                "width": "0.264583"
              },
              "style": {
                "mixBlendMode": "screen"
              },
              "x": 2,
              "y": 9,
              "width": 20,
              "height": 2
            },
            {
              "type": "rect",
              "id": "path1",
              "name": "path1",
              "fill": {
                "paint": "#FF8100"
              },
              "stroke": {
                "width": "0.264583"
              },
              "style": {
                "mixBlendMode": "overlay"
              },
              "x": 2,
              "y": 11.5,
              "width": 20,
              "height": 2
            },
            {
              "type": "rect",
              "id": "path1",
              "name": "path1",
              "fill": {
                "paint": "#FF8100"
              },
              "stroke": {
                "width": "0.264583"
              },
              "style": {
                "mixBlendMode": "soft-light"
              },
              "x": 2,
              "y": 14,
              "width": 20,
              "height": 2
            },
            {
              "type": "rect",
              "id": "path1",
              "name": "path1",
              "fill": {
                "paint": "#FF8100"
              },
              "stroke": {
                "width": "0.264583"
              },
              "style": {
                "mixBlendMode": "hard-light"
              },
              "x": 2,
              "y": 16.5,
              "width": 20,
              "height": 2
            }
          ],
          "id": "layer1",
          "name": "layer1",
          "type": "group"
        }
      ]
    },
    "meta": {
      "name": "drawing_0"
    },
    "position": {
      "x": 23,
      "y": 156.5,
      "height": 250,
      "width": 245
    },
    "custom": {}
  }
]

You are a saint! I I used the mixBlendMode:multiply, it worked great. I should note when you use the dropdown on styles>properties, there is no mixBlendMode. I just added it anyways, seemed to work fine. I guess that’s where it pays to know how to use svg’s. Thanks for the help. Between using overlays and not writing the stop arrays, I think I can call this issue resolved.

No worries!

To be honest, I've never used the dropdown. There are just too many options and props that don't mean anything unless you know what they are. I just add the props manually if I know them, and google/ask AI how to do something with SVGs or CSS if I'm unsure. There’s a little bit of prior knowledge needed for some things, definitely. Blend modes certainly isn't something I'd be aware of if I hadn’t used Photoshop and Inkscape extensively before, and would be difficult to get in results returned from both Google and AI since you most likely wouldn’t be asking questions outside of the box that you know. I'm sure there are tonnes of things that I'm simply unaware of which could make my life and coding easier! but I digress