Select to view content in your preferred language

Custom Expression to Assign Colors in Symbology

1353
5
07-28-2023 01:46 PM
Labels (1)
AimeeJones
Emerging Contributor

Hi there!

I've been scouring the community, dev resources, and Pro's Help section but can't find a solution to my problem. I have a custom expression in Arcade to assign labels. I would like to be able to simply write in this same expression assigned colors for each label. This would be simple enough normally, however, I'm combining 3 or more fields based on their results so attribute symbology (I don't think) is an option. Is there anyway possible I can just write in an RGB/hex value into the expression instead of creating new fields or doing a ton of individual work arounds? I have 44 symbol classes and I'd like to avoid doing a ton of manual entries for this. 

Here's the expression I'm using currently:

var pt1 = $feature.Points_1
var pt2 = $feature.Points_2
var pt3 = $feature.Points_3

if (pt1 == 0 && pt2 == 0 && pt3 == 0){
return "None"
}
else if (pt1 >= 1 && pt2 >= 1 && pt3 >= 1){
return Concatenate($feature.Trtmt_1 + ", " + $feature.Trtmt_2 + ", " + $feature.Trtmt_3 + " - " + (pt1 + pt2 + pt3))
}
else if (pt1 >= 1 && pt2 >= 1 && pt3 == 0){
return Concatenate($feature.Trtmt_1 + ", " + $feature.Trtmt_2 + " - " + (pt1 + pt2))
}
else if (pt1 >= 1 && pt2 == 0 && pt3 >= 1){
return Concatenate($feature.Trtmt_1 + ", " + $feature.Trtmt_3 + " - " + (pt1 + pt3))
}
else if (pt1 == 0 && pt2 >= 1 && pt3 >= 1){
return Concatenate($feature.Trtmt_2 + ", " + $feature.Trtmt_3 + " - " + (pt2 + pt3))
}
if (pt1 == 0 && pt2 == 0 && pt3 == 0){
return "None";
}
else if (pt1 == 1 && pt2 == 0 && pt3 == 0){
return "Overlay - 1";
}
else if (pt1 == 1.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 1.5";
}
else if (pt1 == 2 && pt2 == 0 && pt3 == 0){
return "Overlay - 2";
}
else if (pt1 == 2.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 2.5";
}
else if (pt1 == 3 && pt2 == 0 && pt3 == 0){
return "Overlay - 3";
}
else if (pt1 == 3.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 3.5";
}
else if (pt1 == 4 && pt2 == 0 && pt3 == 0){
return "Overlay - 4";
}
else if (pt1 == 4.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 4.5";
}
else if (pt1 == 5 && pt2 == 0 && pt3 == 0){
return "Overlay - 5";
}
else if (pt1 == 5.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 5.5";
}
else if (pt1 == 6 && pt2 == 0 && pt3 == 0){
return "Overlay - 6";
}
else if (pt1 == 6.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 6.5";
}
else if (pt1 == 7 && pt2 == 0 && pt3 == 0){
return "Overlay - 7";
}
else if (pt1 == 7.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 7.5";
}
else if (pt2 == 8 && pt2 == 0 && pt3 == 0){
return "Overlay - 8";
}
else if (pt1 == 8.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 8.5";
}
else if (pt1 == 9 && pt2 == 0 && pt3 == 0){
return "Overlay - 9";
}
else if (pt1 == 9.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 9.5";
}
else if (pt1 == 10 && pt2 == 0 && pt3 == 0){
return "Overlay - 10";
}
else if (pt1 == 10.5 && pt2 == 0 && pt3 == 0){
return "Overlay - 10.5";
}
else if (pt2 == 1 && pt1 == 0 && pt3 == 0){
return "Seal - 1";
}
else if (pt2 == 1.5 && pt1 == 0 && pt3 == 0){
return "Seal - 1.5";
}
else if (pt2 == 2 && pt1 == 0 && pt3 == 0){
return "Seal - 2";
}
else if (pt2 == 2.5 && pt1 == 0 && pt3 == 0){
return "Seal - 2.5";
}
else if (pt2 == 3 && pt1 == 0 && pt3 == 0){
return "Seal - 3";
}
else if (pt2 == 3.5 && pt1 == 0 && pt3 == 0){
return "Seal - 3.5";
}
else if (pt2 == 4 && pt1 == 0 && pt3 == 0){
return "Seal - 4";
}
else if (pt2 == 4.5 && pt1 == 0 && pt3 == 0){
return "Seal - 4.5";
}
else if (pt2 == 5 && pt1 == 0 && pt3 == 0){
return "Seal - 5";
}
else if (pt2 == 5.5 && pt1 == 0 && pt3 == 0){
return "Seal - 5.5";
}
else if (pt2 == 6 && pt1 == 0 && pt3 == 0){
return "Seal - 6";
}
else if (pt2 == 6.5 && pt1 == 0 && pt3 == 0){
return "Seal - 6.5";
}
else if (pt2 == 7 && pt1 == 0 && pt3 == 0){
return "Seal - 7";
}
else if (pt2 == 7.5 && pt1 == 0 && pt3 == 0){
return "Seal - 7.5";
}
else if (pt2 == 8 && pt1 == 0 && pt3 == 0){
return "Seal - 8";
}
else if (pt2 == 8.5 && pt1 == 0 && pt3 == 0){
return "Seal - 8.5";
}
else if (pt2 == 9 && pt1 == 0 && pt3 == 0){
return "Seal - 9";
}
else if (pt2 == 9.5 && pt1 == 0 && pt3 == 0){
return "Seal - 9.5";
}
else if (pt2 == 10 && pt1 == 0 && pt3 == 0){
return "Seal - 10";
}
else if (pt2 == 10.5 && pt1 == 0 && pt3 == 0){
return "Seal - 10.5";
}
else if (pt3 == 1 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 1";
}
else if (pt3 == 1.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 1.5";
}
else if (pt3 == 2 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 2";
}
else if (pt3 == 2.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 2.5";
}
else if (pt3 == 3 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 3";
}
else if (pt3 == 3.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 3.5";
}
else if (pt3 == 4 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 4";
}
else if (pt3 == 4.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 4.5";
}
else if (pt3 == 5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 5";
}
else if (pt3 == 5.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 5.5";
}
else if (pt3 == 6 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 6";
}
else if (pt3 == 6.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 6.5";
}
else if (pt3 == 7 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 7";
}
else if (pt3 == 7.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 7.5";
}
else if (pt3 == 8 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 8";
}
else if (pt3 == 8.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 8.5";
}
else if (pt3 == 9 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 9";
}
else if (pt3 == 9.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 9.5";
}
else if (pt3 == 10 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 10";
}
else if (pt3 == 10.5 && pt1 == 0 && pt2 == 0){
return "Crack Fill - 10.5";
}

5 Replies
KenBuja
MVP Esteemed Contributor

Are you looking at adding a formatting tag to each of these? If so, that would look like this

//...
else if (pt1 == 0 && pt2 >= 1 && pt3 >= 1){
  return `<CLR red = '255'>${$feature.Trtmt_2}, ${$feature.Trtmt_3} - ${(pt2 + pt3)}</CLR>`;
}
if (pt1 == 0 && pt2 == 0 && pt3 == 0){
  return `<CLR red = '255' green = '200' blue = '100'>None</CLR>`;
}
//...

 This also uses template literals to embed expressions.

0 Kudos
AimeeJones
Emerging Contributor

I'm trying to change the line segment colors on the map without having to do it manually.

0 Kudos
JohannesLindner
MVP Frequent Contributor

Your expression for "Overlay - 8" is wrong!

 

You labeling expression can be massively simplified:

var pt = [
    $feature.Points_1,
    $feature.Points_2,
    $feature.Points_3,
    ]

// count the zeroes
var zeroes = 0
for(var p in pt) { zeroes += pt[p] == 0 }

// all values are zero -> None
if(zeroes == 3) { return "None" }

// two values are zero -> return the non-zero value
if(zeroes == 2) {
    if(pt[0] > 0) { return "Overlay - " + pt[0] }
    if(pt[1] > 0) { return "Seal - " + pt[1] }
    if(pt[2] > 0) { return "Crack Fill - " + pt[2] }
}

// one or no value(s) are zero -> return all non-zero Trtmt values and the sum of the Points values
var trtmt = [
    $feature.Trtmt_1,
    $feature.Trtmt_2,
    $feature.Trtmt_3,
    ]
var trt = []
for(var t in trtmt) {
    if(pt[t] > 0) { Push(trt, trtmt[t]) }
}
return Concatenate(trt, ", ") + " - " + Sum(pt)

 

 

You can input that expression into the symbology as Unique Value "field":

JohannesLindner_0-1690585081185.png

This will give you a legend like this:

JohannesLindner_1-1690585159608.png

 

But that's not really practical. There are 65 conditions in your original expression, and there are actually a lot more possible values, because you take the sum of all non-zero Point values. You would have to format all the colors, and it wouldn't provide any useful information to your viewers, because the color would be too similar.

In my opinion, a better way would be to only symbolize the combination of treatments and only display the actual values in the label.

This is the last part of the label expression, slightly adapted:

var pt = [
    $feature.Points_1,
    $feature.Points_2,
    $feature.Points_3,
]
var trtmt = [
    $feature.Trtmt_1,
    $feature.Trtmt_2,
    $feature.Trtmt_3,
    ]
var trt = []
for(var t in trtmt) {
    if(pt[t] > 0) { Push(trt, trtmt[t]) }
}
if(Count(trt) == 0) { return "None" }
return Concatenate(trt, ", ")

 

And this gives you a much more manageable legend (only 8 items) that actually provides information to your viewers:

JohannesLindner_2-1690585940053.png

 

Together with the labels:

JohannesLindner_3-1690586009877.png

 


Have a great day!
Johannes
AimeeJones
Emerging Contributor

Okay first of all, wow! Thank you for giving me a way to simplify the code. I've been working in excel for the past 2 weeks straight so my first instinct is to write things out how it's required there. Lol it's very counter-productive.

Second of all, I agree that scaling it back it the best solution. The only problem is, is finding a visual solution for 9,500 line segments that can be shown on an 11x17 print out. 😩 I think I'll combine some of the points system (i.e. lump segments with points 1-3 as one) so there's far less colors/variables. I unfortunately can't totally remove the points category just yet. And I was truly looking for a way to just add colors automatically instead of manually doing it. So thanks for helping with the scaled back code, at least!

0 Kudos
JohannesLindner
MVP Frequent Contributor

OK, I may have found a solution that works.

You can enable symbol property connections in the symbology, which lets you choose your color programmatically (or with a field). I didn't suggest that originally, because this isn't reflected in the legend.

But luckily, you have three treatment types which you can let correspond to the three color channels of an RGB value (red, green, blue). You can then vary the lightness of the color by using the rgb channel of the missing treatment(s). The varying lightness won't show up in the legend, so you have a manageable number of items there, and your viewers can get a sense of your point system. But they won't be able to directly get the points by looking at the color, because the color doesn't show up in the legend, so they still have to rely on labels or popups for that.

 

  • Overlay corresponds to the red channel, Seal corresponds  to the green channel, Crack Fill corresponds  to the blue channel.
  • Use the symbology expression from my previous answer and set the colors accordingly (you have to do that manually). This is just for the legend, the actual symbol color won't be affected by this. I chose gray instead of white for all 3 treatments:
    JohannesLindner_0-1690878215390.png

  • Allow symbol property connections
    JohannesLindner_1-1690878310537.png

     

  • For each of the treatment combinations (except None), map the symbol color to the expression below.
    JohannesLindner_2-1690879190844.png

     

  • The first three lines of the expression control how the color will be varied.
    • rgb_channel_min: the minimum value for the missing channels (RGB channels go from 0 to 255). This should be 0 except for all 3 treatments, 100 works nicely there.
    • rgb_channel_max: the maximum value for the missing channels. Don't set it too high, else your points are just white. Values I found to work:
      • 1 treatment: 200
      • 2 treatments: 230
      • 3 treatments: 250
    • low_values_are_lighter: Should low point values be lighter than high point values?
var rgb_channel_min = 0
var rgb_channel_max = 200
var low_values_are_lighter = false

var pt = [
    $feature.Points_1,
    $feature.Points_2,
    $feature.Points_3,
    ]

var zeroes = 0
for(var p in pt) { zeroes += Includes([null, 0], pt[p]) }

var value = Sum(pt)
var max_value = (3 - zeroes) * 10.5
var normalized_value = value / max_value

var rgb_channel = IIf(low_values_are_lighter,
    rgb_channel_max - normalized_value * (rgb_channel_max - rgb_channel_min),
    rgb_channel_min + normalized_value * (rgb_channel_max - rgb_channel_min)
    )
var rgb = [0, 0, 0]
for(var p in pt) {
    rgb[p] = IIf(pt[p] > 0 && zeroes > 0, 255, Round(rgb_channel))
}
return `rgb (${Concatenate(rgb, ", ")})`

 

So it's still a little manual work, but only for 8 items, not for 65+. Here is the result:

JohannesLindner_3-1690879378462.png

 

 


Have a great day!
Johannes
0 Kudos