How to render feature using color in attribute

1163
4
Jump to solution
10-07-2020 02:50 PM
Douglas_DMeredith
New Contributor II

Is it possible to render each feature in a feature class using a color specified in an attribute of that feature class? All I've been able to find is how to do this through the UI. I need to do it using .NET SDK. I had hoped to use one of the RendererDefinition subclasses but none fit the bill. 

0 Kudos
1 Solution

Accepted Solutions
Wolf
by Esri Regular Contributor
Esri Regular Contributor

Hi Douglas,

 Actually there is a sample for attribute driven symbology here:  Renderer - AttributeDrivenSymbology.cs even so this sample shows rotation, however, if you don't have a sample for your rendering needs, you can follow the approach outlined here: 

 I use the CIMViewer to take a look at a symbol that is using attribute values for its color.  The CIMViewer add-in allows you to look at the cartographic information model for each CIMFeatureLayer, which in turn contains the CIMRenderer definition.  I changed my layer's symbology through the Pro UI to use an attribute driven color and looking at the CIMViewer's XML output, I found that my CIMSimpleRenderer had been modified as shown below in order to support the coloring by attribute value (you can see the XML of the CIM Renderer).  Also a matching 'PrimitiveName' reference was added to the symbol layer that needed the color replaced (not shown here):

<Renderer xsi:type="typens:CIMSimpleRenderer">
  <Patch>Default</Patch>
  <Symbol xsi:type="typens:CIMSymbolReference">
    <PrimitiveOverrides xsi:type="typens:ArrayOfCIMPrimitiveOverride">
      <CIMPrimitiveOverride xsi:type="typens:CIMPrimitiveOverride">
        <PrimitiveName>d704d106-8f6f-4ef4-8ee9-3e8c4c7a2c8c</PrimitiveName>
        <PropertyName>FillColor</PropertyName>
        <Expression />
        <ValueExpressionInfo xsi:type="typens:CIMExpressionInfo">
          <Expression>$feature.Color</Expression>
          <ReturnType>Default</ReturnType>
        </ValueExpressionInfo>
      </CIMPrimitiveOverride>
    </PrimitiveOverrides>
    <Symbol xsi:type="typens:CIMPointSymbol">
    .......‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
    <SymbolLayers xsi:type="typens:ArrayOfCIMSymbolLayer">
      <CIMSymbolLayer xsi:type="typens:CIMVectorMarker">
	...
        <MarkerGraphics xsi:type="typens:ArrayOfCIMMarkerGraphic">
          <CIMMarkerGraphic xsi:type="typens:CIMMarkerGraphic">
            <PrimitiveName>d704d106-8f6f-4ef4-8ee9-3e8c4c7a2c8c</PrimitiveName>
          </CIMMarkerGraphic>
        </MarkerGraphics>
	...
      </CIMSymbolLayer>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

If you look at the sample referenced above using the CIMView's XML output, you can come up with a snippet like the one below that changes the color using the text field "TestPoints.Color", which contains values like 'Red', 'Green', 'Blue'.  Note that the 'PrimitiveName' in the primitive override has to match the one in the CIMMarkerGraphics.

protected override void OnClick()
{
    var lyr = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(f => f.Name == "TestPoints").FirstOrDefault();
    if (lyr == null) return;
    QueuedTask.Run(() => {
        //Get the layer's renderer
        var renderer = lyr.GetRenderer() as CIMSimpleRenderer;
        var primitiveName = Guid.NewGuid().ToString();  

        var cimPO = new CIMPrimitiveOverride()
        {
            PrimitiveName = primitiveName,
            PropertyName = @"FillColor",
            Expression = null,
            ValueExpressionInfo = new CIMExpressionInfo()
            {
                Expression = @"$feature.Color",
                ReturnType = ExpressionReturnType.Default                         
            }
        };

        (renderer.Symbol.Symbol as CIMPointSymbol).SymbolLayers[0].PrimitiveName = primitiveName;

        var overrideList = new CIMPrimitiveOverride[1];
        overrideList[0] = cimPO;

        //Apply symbol overrides
        renderer.Symbol.PrimitiveOverrides = overrideList;

        //Apply the renderer to the feature layer
        lyr.SetRenderer(renderer);
    });
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

4 Replies
Wolf
by Esri Regular Contributor
Esri Regular Contributor

Hi Douglas,

 Actually there is a sample for attribute driven symbology here:  Renderer - AttributeDrivenSymbology.cs even so this sample shows rotation, however, if you don't have a sample for your rendering needs, you can follow the approach outlined here: 

 I use the CIMViewer to take a look at a symbol that is using attribute values for its color.  The CIMViewer add-in allows you to look at the cartographic information model for each CIMFeatureLayer, which in turn contains the CIMRenderer definition.  I changed my layer's symbology through the Pro UI to use an attribute driven color and looking at the CIMViewer's XML output, I found that my CIMSimpleRenderer had been modified as shown below in order to support the coloring by attribute value (you can see the XML of the CIM Renderer).  Also a matching 'PrimitiveName' reference was added to the symbol layer that needed the color replaced (not shown here):

<Renderer xsi:type="typens:CIMSimpleRenderer">
  <Patch>Default</Patch>
  <Symbol xsi:type="typens:CIMSymbolReference">
    <PrimitiveOverrides xsi:type="typens:ArrayOfCIMPrimitiveOverride">
      <CIMPrimitiveOverride xsi:type="typens:CIMPrimitiveOverride">
        <PrimitiveName>d704d106-8f6f-4ef4-8ee9-3e8c4c7a2c8c</PrimitiveName>
        <PropertyName>FillColor</PropertyName>
        <Expression />
        <ValueExpressionInfo xsi:type="typens:CIMExpressionInfo">
          <Expression>$feature.Color</Expression>
          <ReturnType>Default</ReturnType>
        </ValueExpressionInfo>
      </CIMPrimitiveOverride>
    </PrimitiveOverrides>
    <Symbol xsi:type="typens:CIMPointSymbol">
    .......‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
    <SymbolLayers xsi:type="typens:ArrayOfCIMSymbolLayer">
      <CIMSymbolLayer xsi:type="typens:CIMVectorMarker">
	...
        <MarkerGraphics xsi:type="typens:ArrayOfCIMMarkerGraphic">
          <CIMMarkerGraphic xsi:type="typens:CIMMarkerGraphic">
            <PrimitiveName>d704d106-8f6f-4ef4-8ee9-3e8c4c7a2c8c</PrimitiveName>
          </CIMMarkerGraphic>
        </MarkerGraphics>
	...
      </CIMSymbolLayer>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

If you look at the sample referenced above using the CIMView's XML output, you can come up with a snippet like the one below that changes the color using the text field "TestPoints.Color", which contains values like 'Red', 'Green', 'Blue'.  Note that the 'PrimitiveName' in the primitive override has to match the one in the CIMMarkerGraphics.

protected override void OnClick()
{
    var lyr = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(f => f.Name == "TestPoints").FirstOrDefault();
    if (lyr == null) return;
    QueuedTask.Run(() => {
        //Get the layer's renderer
        var renderer = lyr.GetRenderer() as CIMSimpleRenderer;
        var primitiveName = Guid.NewGuid().ToString();  

        var cimPO = new CIMPrimitiveOverride()
        {
            PrimitiveName = primitiveName,
            PropertyName = @"FillColor",
            Expression = null,
            ValueExpressionInfo = new CIMExpressionInfo()
            {
                Expression = @"$feature.Color",
                ReturnType = ExpressionReturnType.Default                         
            }
        };

        (renderer.Symbol.Symbol as CIMPointSymbol).SymbolLayers[0].PrimitiveName = primitiveName;

        var overrideList = new CIMPrimitiveOverride[1];
        overrideList[0] = cimPO;

        //Apply symbol overrides
        renderer.Symbol.PrimitiveOverrides = overrideList;

        //Apply the renderer to the feature layer
        lyr.SetRenderer(renderer);
    });
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
Douglas_DMeredith
New Contributor II

Thanks for the tip on using CIMViewer, and for your development work on that. That's a handy tool!

Mine came out slightly different than your example, due to the fact that I'm working with polys rather than point symbols. The CIMViewer made it easy to figure out what I needed to change.

...
	var renderer = flyr.GetRenderer() as CIMSimpleRenderer;
	var primitiveName = Guid.NewGuid().ToString();

	var cimPO = new CIMPrimitiveOverride() {
		PrimitiveName = primitiveName,
		PropertyName = @"Color",
		Expression = null,
		ValueExpressionInfo = new CIMExpressionInfo() {
			Title = "Custom",
			Expression = @"$feature.Symbol",
			ReturnType = ExpressionReturnType.Default
		}
	};

	//TODO: Probably better to iterate the SymbolLayers looking for CIMSolidFill explicitly 
	//rather than relying on position.
	(renderer.Symbol.Symbol as CIMPolygonSymbol).SymbolLayers[1].PrimitiveName = primitiveName;

	var overrideList = new CIMPrimitiveOverride[1];
	overrideList[0] = cimPO;

	//Apply symbol overrides
	renderer.Symbol.PrimitiveOverrides = overrideList;

	//Apply the renderer to the feature layer
	flyr.SetRenderer(renderer);
...

Now, to push things a little further. What if the colors are in another table in the geodatabase, referenced by a field in the feature table? Is there a way to follow that key relationship inline here? Do I instead need to build some sort of dictionary from the table in which the colors are stored? I've only given dictionary renderers a cursory look so far, but they seem pretty heavy for generation on-the-fly.

0 Kudos
Wolf
by Esri Regular Contributor
Esri Regular Contributor

I am not sure if attributes in related tables are supported, however, you can try set this up through the Pro UI and then check the CIM, if it is supported.  You can also use a Unique Value Renderer which you can build from your color lookup table.  With a UniqueValue Renderer you also get a proper legend in the table of content.  Unique Value Renderer sample

0 Kudos
Douglas_DMeredith
New Contributor II

In my first attempt at this, I tried to use UniqueValueRenderer. I can load the ramp with my colors, but I couldn't figure out how to map them to specific values. It seems I can only tell the renderer what fields to look at, and then it wants to map to the values to colors in the ramp on I guess a first-come basis. It's very possible that I am missing something in how I set it up. I'm new to this and the UniqueValueRenderer is not intuitive.

I'll experiment with the CIMViewer and this. My suspicion is that this will be beyond what I can do with the simple renderer mods used above.  

If I can't figure anything out, I'll post a specific Q to the community and see if anyone else has tried this.

Thanks for your help, Wolfgang!

0 Kudos