ArcObjects Library Reference  

MultivariateRenderer

About the Multivariate renderer Sample

[C#]

MultivariateRenderer.cs


using System;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.Runtime.InteropServices;

using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;

namespace MultivariateRenderers
{
	public enum EColorCombinationType: int
	{
		enuComponents,
		enuCIELabColorRamp,
		enuLabLChColorRamp,
		enuRGBAverage,
		enuCIELabMatrix
	}

    [Guid("6A921DB3-5D31-4D85-9857-687CEDBC0D29")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    [ProgId("MultiVariateRenderers.MultiVariateRendererCS")]
	public class MultivariateRenderer : ExportSupport, IExportSupport, IFeatureRenderer, IMultivariateRenderer, ILegendInfo, IPersistVariant, IRotationRenderer, ITransparencyRenderer
	{

		// class definition for MultivariateRenderer, a custom multivariate feature renderer
		//   consisting of 

		// data members
		private EColorCombinationType m_eColorCombinationMethod = EColorCombinationType.enuComponents;
		private IFeatureRenderer m_pShapePatternRend;
		private IFeatureRenderer m_pColorRend1;
		private IFeatureRenderer m_pColorRend2;
		private IFeatureRenderer m_pSizeRend;
		// for the renderer's TOC and legend entry 
		// current implementation is simple, but could be extended
		private ILegendGroup[] m_pLegendGroups;
		private string m_sRotationField;
        
		private esriSymbolRotationType m_eRotationType = esriSymbolRotationType.esriRotateSymbolGeographic;
		private string m_sTransparencyField;
		private IFeatureRenderer m_pMainRend; // as renderers are assigned, use this to keep track of which one has the base symbols
		private esriGeometryType m_ShapeType;
		private IFeatureClass m_pFeatureClass;
		private IQueryFilter m_pQueryFilter;
		private long[,] m_OLEColorMatrix = new long[4,4];

		public MultivariateRenderer()
		{
      
		}

		~MultivariateRenderer()
		{
			m_pShapePatternRend = null;
			m_pColorRend1 = null;
			m_pColorRend2 = null;
		}

		public void CreateLegend()
		{
			// NOT IMPL

		}

		public bool CanRender(IFeatureClass featClass, IDisplay Display)
		{
			// only use this renderer if we have points, lines, or polygons
			return (featClass.ShapeType == esriGeometryType.esriGeometryPoint) | (featClass.ShapeType == esriGeometryType.esriGeometryPolyline) | (featClass.ShapeType == esriGeometryType.esriGeometryPolygon);
		}

		public void Draw(IFeatureCursor cursor, esriDrawPhase DrawPhase, IDisplay Display, ITrackCancel trackCancel)
		{
			string ActiveErrorHandler = null;

	try
	{
			// loop through and draw each feature

			IFeature pFeat = null;
			IFeatureRenderer pRend = null;

			bool bContinue = false;

			// do not draw features if no display
			if (Display == null)
				return;

			// we can't draw without somewhere to get our base symbols from
			if (m_pMainRend == null)
				return;

			if (m_pSizeRend != null)
			{
				// size varies
				if (m_ShapeType == esriGeometryType.esriGeometryPoint | m_ShapeType == esriGeometryType.esriGeometryPolyline)
				{
					if (DrawPhase == esriDrawPhase.esriDPGeography)
					{
						// draw symbols in order from large to small
						DrawSymbolsInOrder(cursor, DrawPhase, Display, trackCancel);
					}
				}
				else if (m_ShapeType == esriGeometryType.esriGeometryPolygon)
				{
					if (DrawPhase == esriDrawPhase.esriDPAnnotation)
					{
						// draw primary symbology from large to small
						DrawSymbolsInOrder(cursor, DrawPhase, Display, trackCancel);
					}
					else if (DrawPhase == esriDrawPhase.esriDPGeography)
					{
						// draw background symbology
						pFeat = cursor.NextFeature();
						bContinue = true;

						// while there are still more features and drawing has not been cancelled
                        IFillSymbol pBackFillSym;

                        
						while ((pFeat != null) & (bContinue == true))
						{
							// draw the feature
                            IFeatureDraw pFeatDraw = pFeat as IFeatureDraw;
							if (m_pSizeRend is IClassBreaksRenderer)
							{
                                IClassBreaksRenderer pCBRend = m_pSizeRend as IClassBreaksRenderer;
								pBackFillSym = pCBRend.BackgroundSymbol;
							}
							else
							{
                                IProportionalSymbolRenderer pPropRend = m_pSizeRend as IProportionalSymbolRenderer;
								pBackFillSym = pPropRend.BackgroundSymbol;
							}
							Display.SetSymbol(pBackFillSym as ISymbol);

              //implementation of IExportSupport
              BeginFeature(pFeat, Display);
              
							pFeatDraw.Draw(DrawPhase, Display, pBackFillSym as ISymbol, true, null, esriDrawStyle.esriDSNormal);

              //implementation of IExportSupport
							GenerateExportInfo(pFeat, Display);
							EndFeature(Display);
							
							pFeat = cursor.NextFeature();
              if (trackCancel != null)
                bContinue = trackCancel.Continue();
						}
					}
					else
					{
                        Marshal.ThrowExceptionForHR(147500037); //E_FAIL
					}
				}

			}
			else
			{
				// size does not vary
				if (DrawPhase != esriDrawPhase.esriDPGeography)
				{
                    Marshal.ThrowExceptionForHR(147500037); //E_FAIL
				}
				else
					DrawSymbols(cursor, DrawPhase, Display, trackCancel);
			}

}

catch
{
}
		}

		public IFeatureIDSet ExclusionSet
		{
			set
			{
				// NOT IMPL    
			}
		}

		public void PrepareFilter(IFeatureClass fc, IQueryFilter queryFilter)
		{
			// prepare filter for drawing

			// must add OID
			queryFilter.AddField(fc.OIDFieldName);

			m_ShapeType = fc.ShapeType;
			if (m_ShapeType == esriGeometryType.esriGeometryPoint)
			{
                if (m_sRotationField != null)
                {
                    if (m_sRotationField != "")
                    {
                        queryFilter.AddField(m_sRotationField);
                    }
                }
			}

			// save the fc and the query filter so that multiple cursors can be built in DrawSymbols
			m_pFeatureClass = fc;
			m_pQueryFilter = queryFilter;

			// prepare filters on constituent renderers so I can use SymbolByFeature in Draw
			if (m_pShapePatternRend != null)
					m_pShapePatternRend.PrepareFilter(fc, queryFilter);
			if (m_pColorRend1 != null)
					m_pColorRend1.PrepareFilter(fc, queryFilter);
			if (m_pColorRend2 != null)
					m_pColorRend2.PrepareFilter(fc, queryFilter);
			if (m_pSizeRend != null)
					m_pSizeRend.PrepareFilter(fc, queryFilter);

			// if we're combining colors from two (sequential) quantitative schemes, build color matrix now
			//   this gives flexibility to extend in future
			// in current impl. we determine combined color based on two colors, one from each constituent 
			//   ClassBreaksRenderer.  so, we could determine color on demand when drawing. but, by creating 
			//   the color matrix here and storing for later use, we leave open the possibility of swapping in 
			//   different logic for determining combined colors based on all known colors in each constituent
			//   renderer, not just the colors for the given feature
			if ((m_pColorRend1 != null) & (m_pColorRend2 != null))
			{
				if (! (m_eColorCombinationMethod == EColorCombinationType.enuComponents))
					BuildColorMatrix();
			}
			
			//implementation of IExportSupport
			AddExportFields(fc, queryFilter);

		}

    bool IFeatureRenderer.get_RenderPhase(ESRI.ArcGIS.esriSystem.esriDrawPhase DrawPhase)
        {
            return (DrawPhase == esriDrawPhase.esriDPGeography) | (DrawPhase == esriDrawPhase.esriDPAnnotation);
        }
    ESRI.ArcGIS.Display.ISymbol IFeatureRenderer.get_SymbolByFeature(ESRI.ArcGIS.Geodatabase.IFeature Feature)
        {
            return GetFeatureSymbol(Feature);
        }

    ESRI.ArcGIS.Carto.ILegendGroup ILegendInfo.get_LegendGroup(int Index)
        {
            string strHeading = null;
            ILegendInfo pLegendInfo = null;
            switch (Index)
            {
                case 0:
                    pLegendInfo = m_pMainRend as ILegendInfo;
                    if (m_pMainRend == m_pShapePatternRend)
                        strHeading = "Shape/Pattern: ";
                    else if (m_pMainRend == m_pSizeRend)
                        strHeading = "Size: ";
                    else
                        strHeading = "Color 1: ";
                    break;
                case 1:
                    if (m_pShapePatternRend != null)
                    {
                        if (m_pSizeRend != null)
                        {
                            pLegendInfo = m_pSizeRend as ILegendInfo;
                            strHeading = "Size: ";
                        }
                        else
                        {
                            pLegendInfo = m_pColorRend1 as ILegendInfo;
                            strHeading = "Color 1: ";
                        }
                    }
                    else
                    {
                        if (m_pSizeRend != null)
                        {
                            pLegendInfo = m_pColorRend1 as ILegendInfo;
                            strHeading = "Color 1: ";
                        }
                        else
                        {
                            pLegendInfo = m_pColorRend2 as ILegendInfo;
                            strHeading = "Color 2: ";
                        }
                    }
                    break;
                case 2:
                    pLegendInfo = m_pColorRend1 as ILegendInfo;
                    strHeading = "Color 1: ";
                    break;
                case 3:
                    pLegendInfo = m_pColorRend2 as ILegendInfo;
                    strHeading = "Color 2: ";

                    break;
            }

            ILegendGroup pLegendGroup = null;
            pLegendGroup = pLegendInfo.get_LegendGroup(0);
            //pLegendGroup.Heading = strHeading & pLegendGroup.Heading

            return pLegendGroup;
        }

		public int LegendGroupCount
		{
			get
			{
				ILegendInfo pLegInfo = null;
				int n = 0;

				n = 0;
				if (m_pSizeRend != null)
				{
					pLegInfo = m_pSizeRend as ILegendInfo;
					if (pLegInfo.get_LegendGroup(0) != null)
							n = n + 1;
				}
				if (m_pShapePatternRend != null)
				{
					pLegInfo = m_pShapePatternRend as ILegendInfo;
					if (pLegInfo.get_LegendGroup(0) != null)
							n = n + 1;
				}
				if (m_pColorRend1 != null)
				{
					pLegInfo = m_pColorRend1 as ILegendInfo;
					if (pLegInfo.get_LegendGroup(0) != null)
							n = n + 1;
				}
				if (m_pColorRend2 != null & ! (m_pColorRend2 == m_pColorRend1))
				{
					//If Not m_pColorRend2 Is Nothing Then
					pLegInfo = m_pColorRend2 as ILegendInfo;
					if (pLegInfo.get_LegendGroup(0) != null)
							n = n + 1;
				}

				return n;
			}
		}

		public ILegendItem LegendItem
		{
			get
			{
				return null;
			}
		}

		public bool SymbolsAreGraduated
		{
			get
			{
				return false;
			}
			set
			{
				// NOT IMPL
			}
		}

		public UID ID
		{
			get
			{
				UID pUID = new UID();
        pUID.Value = "MultivariateRenderers.MultiVariateRendererCS";
				//pUID.Value = ClassId
				return pUID;
			}
		}

		public void Load(IVariantStream Stream)
		{
			//load the persisted parameters of the renderer

			m_eColorCombinationMethod = (EColorCombinationType)Stream.Read();
			m_pShapePatternRend = Stream.Read() as IFeatureRenderer;
			m_pColorRend1 = Stream.Read() as IFeatureRenderer;
			m_pColorRend2 = Stream.Read() as IFeatureRenderer;
			m_pSizeRend = Stream.Read() as IFeatureRenderer;
			//m_pLegendGroups = = Stream.Read
			m_sRotationField = (string)Stream.Read();
            m_eRotationType = (esriSymbolRotationType)Stream.Read();
			m_sTransparencyField = (String)Stream.Read();
			m_pMainRend = Stream.Read() as IFeatureRenderer;

			//CreateLegend() ' not needed now
		}

		public void Save(IVariantStream Stream)
		{
			//persist the settings for the renderer

			Stream.Write(m_eColorCombinationMethod);
			Stream.Write(m_pShapePatternRend);
			Stream.Write(m_pColorRend1);
			Stream.Write(m_pColorRend2);
			Stream.Write(m_pSizeRend);
			//Stream.Write(m_pLegendGroups)
			Stream.Write(m_sRotationField);
			Stream.Write(m_eRotationType);
			Stream.Write(m_sTransparencyField);
			Stream.Write(m_pMainRend);
		}

		private ISymbol GetFeatureSymbol(IFeature pFeat)
		{

			ISymbol pSym = null;

			// get base symbol
			pSym = m_pMainRend.get_SymbolByFeature(pFeat);

			// modify base symbol as necessary

			if ((m_pSizeRend != null) && (! (m_pMainRend == m_pSizeRend)) && (pSym != null))
				pSym = ApplySize(pSym, pFeat);

			
			if (((m_pColorRend1 != null) | (m_pColorRend2 != null)) && (pSym != null))
				pSym = ApplyColor(pSym, pFeat);

			if (((m_ShapeType == esriGeometryType.esriGeometryPoint) | ((m_ShapeType == esriGeometryType.esriGeometryPolygon) & pSym is IMarkerSymbol)) && (pSym != null))
			{
                if (m_sRotationField != null)
                {
                    if ((m_sRotationField != "") && (m_sRotationField != null))
                    {
                        pSym = ApplyRotation(pSym as IMarkerSymbol, pFeat as IFeature) as ISymbol;
                    }
                }
			}

			// support for point, line, and poly features
			
            if (m_sTransparencyField != null)
            {
                if (m_sTransparencyField != "")
                {
                    pSym = ApplyTransparency(pSym);
                }
            }
			

			return pSym;

		}


		private IFeatureCursor SortData(IFeatureCursor pCursor, ITrackCancel pTrackCancel)
		{
			// sort in descending by value
			ITable pTable = null;
			pTable = m_pFeatureClass as ITable;

			ITableSort pTableSort = null;
			pTableSort = new TableSort();
			pTableSort.Table = pTable;
			pTableSort.Cursor = pCursor as ICursor;

			//set up the query filter.
			IQueryFilter pQF = null;
			pQF = new QueryFilter();
			pQF.SubFields = "*";
			pQF.WhereClause = m_pQueryFilter.WhereClause;
			pTableSort.QueryFilter = pQF;

			IProportionalSymbolRenderer pPSRend = null;
      pPSRend = m_pSizeRend as IProportionalSymbolRenderer;
			string strValueField = null;
			strValueField = pPSRend.Field;
			pTableSort.Fields = strValueField;
			pTableSort.set_Ascending(strValueField, false);

			IDataNormalization pDataNorm = null;
            pDataNorm = pPSRend as IDataNormalization;
			if (pDataNorm.NormalizationType == esriDataNormalization.esriNormalizeByField)
			{
				// comparison is not simple comparison of field values, use callback to do custom compare

				// get normalization field and add to table sort
				string strFields = "";
				strFields = strFields + strValueField;
				string strNormField = null;
				strNormField = pDataNorm.NormalizationField;
				strFields = strFields + ",";
				strFields = strFields + strNormField;
				pTableSort.Fields = strFields;
				pTableSort.set_Ascending(strNormField, false);

				// create new custom table call sort object and connect to the TableSort object
				ITableSortCallBack pTableSortCallBack = null;
				pTableSortCallBack = new SortCallBack(pTable.Fields.FindField(strValueField), pTable.Fields.FindField(strNormField));
				pTableSort.Compare = pTableSortCallBack;
			}

			// call the sort
			pTableSort.Sort(pTrackCancel);

			// retrieve the sorted rows
			IFeatureCursor pSortedCursor = null;
			pSortedCursor = pTableSort.Rows as IFeatureCursor;

			return pSortedCursor;
		}

		private void DrawSymbolsInOrder(IFeatureCursor Cursor, esriDrawPhase drawPhase, IDisplay Display, ITrackCancel trackCancel)
		{
			// this sub draws either markers or line symbols from large small so that the smallest symbols will be drawn on top

			// in graduated symbol case, a cursor is built and parsed n times for n size classes
			// in proportional symbol case, symbols are sorted and drawn from largest to smallest

			int iSizeIndex = 0;
			int iCurrentDrawableSymbolIndex = 0;
			IFeatureCursor pMyCursor = null;
			IFeature pFeat = null;
			IFeatureDraw pFeatDraw = null;
			bool bContinue = true;
			ISymbol pSizeSym = null;
			ISymbol pDrawSym = null;
			IFeatureCursor pSortedCursor = null;

			if (m_pSizeRend is IProportionalSymbolRenderer)
			{
				// sort 
				pSortedCursor = SortData(Cursor, trackCancel);

				// draw
				pFeat = pSortedCursor.NextFeature();
				while (pFeat != null)
				{
					pDrawSym = GetFeatureSymbol(pFeat);
					// draw the feature
					pFeatDraw = pFeat as IFeatureDraw;
					Display.SetSymbol(pDrawSym);

          //implementation of IExportSupport
          BeginFeature(pFeat, Display);
          					
					pFeatDraw.Draw(drawPhase, Display, pDrawSym, true, null, esriDrawStyle.esriDSNormal);

          //implementation of IExportSupport
          GenerateExportInfo(pFeat, Display);
          EndFeature(Display);
					
					// get next feature
					pFeat = pSortedCursor.NextFeature();
					if (trackCancel != null)
							bContinue = trackCancel.Continue();
				}

			}
			else
			{
				IClassBreaksRenderer pSizeCBRend = null;
                pSizeCBRend = m_pSizeRend as IClassBreaksRenderer;
				pMyCursor = Cursor;
				for (iCurrentDrawableSymbolIndex = (pSizeCBRend.BreakCount - 1); iCurrentDrawableSymbolIndex >= 0; iCurrentDrawableSymbolIndex--)
				{
					// do not build a cursor the 1st time because we already have one
					if (iCurrentDrawableSymbolIndex < (pSizeCBRend.BreakCount - 1))
					{
						// build pMyCursor
						pMyCursor = m_pFeatureClass.Search(m_pQueryFilter, true);
					}
					pFeat = pMyCursor.NextFeature();
					while (pFeat != null)
					{
						// check to see if we will draw in this pass
						pSizeSym = m_pSizeRend.get_SymbolByFeature(pFeat);
						iSizeIndex = GetSymbolIndex(pSizeSym, pSizeCBRend);
						if (iSizeIndex == iCurrentDrawableSymbolIndex)
						{
							// go ahead and draw the symbol
							// get symbol to draw
							pDrawSym = GetFeatureSymbol(pFeat);

							// draw the feature
							pFeatDraw = pFeat as IFeatureDraw;
							Display.SetSymbol(pDrawSym);

              //implementation of IExportSupport
              BeginFeature(pFeat, Display);
							
							pFeatDraw.Draw(drawPhase, Display, pDrawSym, true, null, esriDrawStyle.esriDSNormal);

              //implementation of IExportSupport
              GenerateExportInfo(pFeat, Display);
              EndFeature(Display);
              
							if (trackCancel != null)
									bContinue = trackCancel.Continue();
						}

						pFeat = pMyCursor.NextFeature();
					}

				} // increment DOWN to next symbol size

			}

		}

		private void DrawSymbols(IFeatureCursor Cursor, esriDrawPhase drawPhase, IDisplay Display, ITrackCancel trackCancel)
		{

			IFeature pFeat = null;
			IFeatureDraw pFeatDraw = null;
			bool bContinue = true;
			ISymbol pDrawSym = null;

			pFeat = Cursor.NextFeature();
			bContinue = true;
			// while there are still more features and drawing has not been cancelled
			while ((pFeat != null) & (bContinue == true))
			{
				// get symbol to draw
				pDrawSym = GetFeatureSymbol(pFeat);
				// draw the feature
				pFeatDraw = pFeat as IFeatureDraw;
				Display.SetSymbol(pDrawSym);

        //implementation of IExportSupport
        BeginFeature(pFeat, Display);
        
				pFeatDraw.Draw(drawPhase, Display, pDrawSym, true, null, esriDrawStyle.esriDSNormal);

        //implementation of IExportSupport
        GenerateExportInfo(pFeat, Display);
        EndFeature(Display);
        
				// get next feature
				pFeat = Cursor.NextFeature();
				if (trackCancel != null)
						bContinue = trackCancel.Continue();
			}

		}


		private ESRI.ArcGIS.Display.IColor GetCombinedColor(IColor pColor1, IColor pColor2, EColorCombinationType eCombinationMethod)
		{
			return GetCombinedColor(pColor1, pColor2, eCombinationMethod, null);
		}



		private ESRI.ArcGIS.Display.IColor GetCombinedColor(IColor pColor1, IColor pColor2, EColorCombinationType eCombinationMethod, IColor pOriginColor)
		{
			// combines the input colors based on m_eColorCombinationMethod

			// (11/08/04) -- RGB and enuLabLChColorRamp aren't used by GUI

			IColor pOutColor = null;

			long MyOLE_COLOR = 0; // As OLE_COLOR in VB6
			IRgbColor pMainRGBColor = null;
			IRgbColor pVariationRGBColor = null;
			IRgbColor pMergedRGBColor = null;
			bool bOK = false;
			IAlgorithmicColorRamp pAlgorithmicCR = null;

			// if either of the colors are null, then don't run the color through any algorithm,
			//   instead, just return the other color.  if both are null, then return a null color
			if (pColor1.NullColor)
			{
				pOutColor = pColor2;

			}
			else if (pColor2.NullColor)
			{
				pOutColor = pColor1;

			}
			else if (eCombinationMethod == EColorCombinationType.enuComponents)
			{
				// HSV components
				// create a new HSV color
				IHsvColor pHSVDrawColor = null;
				pHSVDrawColor = new HsvColor();
				// get HSV values from Color1 and Color2 and assign to pHSVDrawColor
				IHsvColor pHSVColor1 = null;
				IHsvColor pHSVColor2 = null;

				// (new 4/27/04) didn't think I had to do this...
				//pHSVColor1 = pColor1
				//pHSVColor2 = pColor2
				pHSVColor1 = new HsvColor();
				pHSVColor1.RGB = pColor1.RGB;
				pHSVColor2 = new HsvColor();
				pHSVColor2.RGB = pColor2.RGB;

				pHSVDrawColor.Hue = pHSVColor1.Hue;
				pHSVDrawColor.Saturation = pHSVColor2.Saturation;
				pHSVDrawColor.Value = pHSVColor2.Value;

				pOutColor = pHSVDrawColor;

			}
			else if (eCombinationMethod == EColorCombinationType.enuRGBAverage)
			{
				// use additive color model to merge the two colors
				MyOLE_COLOR = pColor1.RGB;
				pMainRGBColor = new RgbColor();
				pMainRGBColor.RGB = (int)MyOLE_COLOR;
				MyOLE_COLOR = pColor2.RGB;
				pVariationRGBColor = new RgbColor();
				pVariationRGBColor.RGB = (int)MyOLE_COLOR;
				// merged color = RGB average of the two colors
				pMergedRGBColor = new RgbColor();
				pMergedRGBColor.Red = (pMainRGBColor.Red + pVariationRGBColor.Red) / 2;
				pMergedRGBColor.Green = (pMainRGBColor.Green + pVariationRGBColor.Green) / 2;
				pMergedRGBColor.Blue = (pMainRGBColor.Blue + pVariationRGBColor.Blue) / 2;

				pOutColor = pMergedRGBColor;
			}
			else if ((eCombinationMethod == EColorCombinationType.enuCIELabColorRamp) | (eCombinationMethod == EColorCombinationType.enuLabLChColorRamp))
			{
				// use color ramp and take central color between the two colors
				pAlgorithmicCR = new AlgorithmicColorRamp();
				if (m_eColorCombinationMethod == EColorCombinationType.enuCIELabColorRamp)
					pAlgorithmicCR.Algorithm = esriColorRampAlgorithm.esriCIELabAlgorithm;
				else
					pAlgorithmicCR.Algorithm = esriColorRampAlgorithm.esriLabLChAlgorithm;
				pAlgorithmicCR.Size = 3;
				pAlgorithmicCR.FromColor = pColor1;
				pAlgorithmicCR.ToColor = pColor2;
				pAlgorithmicCR.CreateRamp(out bOK);

				pOutColor = pAlgorithmicCR.get_Color(1); // middle color in ramp
			}
			else // EColorCombinationType.enuCIELabMatrix
			{

				double[] iLab1 = new double[4]; // L, a, b values for Color1
				double[] iLab2 = new double[4]; // L, a, b values for Color2
				double[] iLabOrig = new double[4]; // L, a, b values for pOriginColor
				pColor1.GetCIELAB(out iLab1[0], out iLab1[1], out iLab1[2]);
				pColor2.GetCIELAB(out iLab2[0], out iLab2[1], out iLab2[2]);
				pOriginColor.GetCIELAB(out iLabOrig[0], out iLabOrig[1], out iLabOrig[2]);

				double[] iLabOut = new double[4];
				// add color1 vector and color2 vector, then subtract the origin color vector
				iLabOut[0] = iLab1[0] + iLab2[0] - iLabOrig[0];
				iLabOut[1] = iLab1[1] + iLab2[1] - iLabOrig[1];
				iLabOut[2] = iLab1[2] + iLab2[2] - iLabOrig[2];

				CorrectLabOutofRange(ref iLabOut[0], ref iLabOut[1], ref iLabOut[2]);

				IHsvColor pHSVColor = null;
				pHSVColor = new HsvColor();
				pHSVColor.SetCIELAB(iLabOut[0], iLabOut[1], iLabOut[2]);
				pOutColor = pHSVColor;
			}

			return pOutColor;

		}

		private void CorrectLabOutofRange(ref double L, ref double a, ref double b)
		{

			if (L > 100)
				L = 100;
			else if (L < 0)
				L = 0;

			if (a > 120)
				a = 120;
			else if (a < -120)
				a = -120;

			if (b > 120)
				b = 120;
			else if (b < -120)
				b = -120;

		}

		private void RemoveLegend()
		{

			int i = 0;

			if (m_pLegendGroups != null)
			{

				int tempFor1 = m_pLegendGroups.GetUpperBound(0);
				for (i = 0; i <= tempFor1; i++)
				{
					m_pLegendGroups[i] = null;
				}
			}
		}

		private IFeatureRenderer CalcMainRend()
		{
			// consider using an internal array to keep track of active arrays in correct order, this will make it easier to implement ILegendInfo

			if (m_pShapePatternRend != null)
			{
				if ((m_ShapeType == esriGeometryType.esriGeometryPolygon) & m_pSizeRend != null)
					return m_pSizeRend;
				else
					return m_pShapePatternRend;
			}
			else if (m_pSizeRend != null)
				return m_pSizeRend;
			else if (m_pColorRend1 != null)
				return m_pColorRend1;
			else if (m_pColorRend2 != null)
				return m_pColorRend2;
			else
				return null; // must have shape or color or size, if not you can't render...

		}

		public EColorCombinationType ColorCombinationMethod
		{
			get
			{
				return m_eColorCombinationMethod;
			}
			set
			{
				m_eColorCombinationMethod = value;
			}
		}

		public ESRI.ArcGIS.Carto.IFeatureRenderer ColorRend1
		{
			get
			{
				return m_pColorRend1;
			}
			set
			{
				m_pColorRend1 = value;
				m_pMainRend = CalcMainRend();
			}
		}

		public ESRI.ArcGIS.Carto.IFeatureRenderer ColorRend2
		{
			get
			{
				return m_pColorRend2;
			}
			set
			{
				m_pColorRend2 = value;
			}
		}

		public ESRI.ArcGIS.Carto.IFeatureRenderer ShapePatternRend
		{
			get
			{
				return m_pShapePatternRend;
			}
			set
			{
				m_pShapePatternRend = value;
				m_pMainRend = CalcMainRend();
			}
		}

		public ESRI.ArcGIS.Carto.IFeatureRenderer SizeRend
		{
			get
			{
				return m_pSizeRend;
			}
			set
			{
				m_pSizeRend = value;
				m_pMainRend = CalcMainRend();
			}
		}

		private IMarkerSymbol ApplyRotation(IMarkerSymbol pMarkerSym, IFeature pFeat)
		{

			double lAngle = 0;
            int tempoindex = 0;

            tempoindex = pFeat.Fields.FindField(m_sRotationField);

            lAngle = Convert.ToDouble(pFeat.get_Value(tempoindex));
          
            
			if (m_eRotationType == esriSymbolRotationType.esriRotateSymbolGeographic)
				pMarkerSym.Angle = pMarkerSym.Angle - lAngle;
			else
				pMarkerSym.Angle = pMarkerSym.Angle + lAngle - 90;

			return pMarkerSym;
		}

		private ISymbol ApplyTransparency(ISymbol pSym)
		{

			// TODO

			return pSym;
		}

		private ISymbol ApplyColor(ISymbol pSym, IFeature pFeat)
		{
	try
	{
			ISymbol pSym1 = null;
			ISymbol pSym2 = null;
			IColor pColor = null;
			IHsvColor pHSVColor = null;

			if ((m_pColorRend1 != null) & (m_pColorRend2 != null)) // for now both color renderers need to be set to apply color
			{
				pSym1 = m_pColorRend1.get_SymbolByFeature(pFeat);
				pSym2 = m_pColorRend2.get_SymbolByFeature(pFeat);
				// only use GetCombinedColor for HSV component-type combination method
				if (m_eColorCombinationMethod == EColorCombinationType.enuComponents)
				{
					pColor = GetCombinedColor(GetSymbolColor(pSym1), GetSymbolColor(pSym2), m_eColorCombinationMethod);

					
					// Hue is good when I do this...
					pHSVColor = pColor as IHsvColor;
					//'MsgBox(Str(pHSVColor.Hue) & " " & Str(pHSVColor.Saturation) & " " & " " & Str(pHSVColor.Value()))

				}
				else
				{
					
					pColor = new RgbColor();
                    pColor.RGB = (int)m_OLEColorMatrix[GetSymbolIndex(pSym1 as ISymbol, m_pColorRend1 as IClassBreaksRenderer), GetSymbolIndex(pSym2 as ISymbol, m_pColorRend2 as IClassBreaksRenderer)];
					
					
				}


				if (pSym is IMarkerSymbol)
				{
					IMarkerSymbol pMarkerSym = null;
                    pMarkerSym = pSym as IMarkerSymbol;
					pMarkerSym.Color = pColor;
				}
				else if (pSym is ILineSymbol)
				{
					ILineSymbol pLineSym = null;
                    pLineSym = pSym as ILineSymbol;
					pLineSym.Color = pColor;
				}
				else if (pSym != null)
				{
					IFillSymbol pFillSym = null;
					pFillSym = pSym as IFillSymbol;
					pFillSym.Color = pColor;
					

				}


			}

			return pSym;
			return null;
}

catch
{
    return null;
}
		}

		private ISymbol ApplySize(ISymbol pSym, IFeature pFeat)
		{

			if (pSym is IMarkerSymbol)
			{
				// Marker Symbol
				IMarkerSymbol pTargetMarkerSym = null;
                pTargetMarkerSym = pSym as IMarkerSymbol;
				IMarkerSymbol pSourceMarkerSym = null;
                pSourceMarkerSym = m_pSizeRend.get_SymbolByFeature(pFeat) as IMarkerSymbol;
                if (pSourceMarkerSym != null)
                {
                    pTargetMarkerSym.Size = pSourceMarkerSym.Size;
                }

			}
			else
			{
				// Line Symbol
				ILineSymbol pTargetLineSym = null;
				pTargetLineSym = pSym as ILineSymbol;
				ILineSymbol pSourceLineSym = null;
				pSourceLineSym = m_pSizeRend.get_SymbolByFeature(pFeat) as ILineSymbol;
                if (pSourceLineSym != null)
                {
                    pTargetLineSym.Width = pSourceLineSym.Width;
                }

			}

			return pSym;
		}
		


		public string RotationField
		{
			get
			{
				return m_sRotationField;
			}
			set
			{
				m_sRotationField = value;
			}
		}

		public string TransparencyField
		{
			get
			{
				return m_sTransparencyField;
			}
			set
			{
				m_sTransparencyField = value;
			}
		}

		public ESRI.ArcGIS.Carto.esriSymbolRotationType RotationType
		{
			get
			{
				return m_eRotationType;
			}
			set
			{
				m_eRotationType = value;
			}
		}

		private int GetSymbolIndex(ISymbol pSym, IClassBreaksRenderer pRend)
		{
			// given an input symbol and a renderer, this function returns the index of
			//   the class that the symbol represents in the renderer

			int i = 0;
			int iNumBreaks = 0;

		
			iNumBreaks = pRend.BreakCount;
			i = 0;
			ILegendInfo pLegendInfo = null;
			pLegendInfo = pRend as ILegendInfo;
			while (i < iNumBreaks - 1)
			{
				if (pLegendInfo.SymbolsAreGraduated)
				{
					// compare based on size
					if (SymbolsAreSameSize(pSym, pRend.get_Symbol(i)))
							break;
				}
				else
				{
					// compare based on color
					if (SymbolsAreSameColor(pSym, pRend.get_Symbol(i)))
							break;
				}
				i = i + 1;
			}

			return i;

			// NOTE: for some reason we can't test that the symbol objects are the same, so above we do quick test for equal properties instead
			//Do While (i < iNumBreaks - 1)
			//    If pSym Is pRend.Symbol(i) Then Exit Do
			//    i = i + 1
			//Loop

			//Return i

			// (I think this only works for renderer that does Graduated symbols)
			//If m_ShapeType = esriGeometryType.esriGeometryPoint Or m_ShapeType = esriGeometryType.esriGeometryPolygon Then
			//    ' determine the symbol index based on marker symbol size
			//    pInMarkerSym = pSym
			//    i = 0
			//    pClassMarkerSym = pRend.Symbol(0)
			//    dblSize = pClassMarkerSym.Size
			//    Do While (i < iNumBreaks - 1) And (pInMarkerSym.Size > dblSize)
			//        pClassMarkerSym = pRend.Symbol(i)
			//        dblSize = pClassMarkerSym.Size
			//        i = i + 1
			//    Loop
			//    iReturnVal = i
			//Else ' m_shapetype = esriGeometryLine
			//    ' determine the symbol index based on line symbol width
			//    pInLineSym = pSym
			//    i = 0
			//    pClassLineSym = pRend.Symbol(0)
			//    dblWidth = pClassLineSym.Width
			//    Do While (i < iNumBreaks - 1) And (pInLineSym.Width > dblWidth)
			//        pClassLineSym = pRend.Symbol(i)
			//        dblSize = pClassLineSym.Width
			//        i = i + 1
			//    Loop
			//    iReturnVal = i
			//End If

		}

		private bool SymbolsAreSameSize(ISymbol pSym1, ISymbol psym2)
		{

			if (pSym1 is IMarkerSymbol)
			{
				IMarkerSymbol pMS1 = null;
				IMarkerSymbol pMS2 = null;
				pMS1 = pSym1 as IMarkerSymbol;
                pMS2 = psym2 as IMarkerSymbol;
				return pMS1.Size == pMS2.Size;
			}
			else
			{
				ILineSymbol pLS1 = null;
				ILineSymbol pLS2 = null;
				pLS1 = pSym1 as ILineSymbol;
                pLS2 = psym2 as ILineSymbol;
				return pLS1.Width == pLS2.Width;
			}

		}

		private bool SymbolsAreSameColor(ISymbol pSym1, ISymbol psym2)
		{

			IColor pColor1 = null;
			IColor pColor2 = null;
			pColor1 = GetSymbolColor(pSym1);
			pColor2 = GetSymbolColor(psym2);
			return pColor1.RGB == pColor2.RGB;

		}

		private void BuildColorMatrix()
		{
	try
	{
//			On Error GoTo ErrHand

			IClassBreaksRenderer pCBRend1 = null;
			IClassBreaksRenderer pCBRend2 = null;
            pCBRend1 = m_pColorRend1 as IClassBreaksRenderer;
            pCBRend2 = m_pColorRend2 as IClassBreaksRenderer;

			int i = 0;
			int j = 0;
			IColor pColor1 = null;
			IColor pColor2 = null;
			IColor pColor = null;

			if (m_eColorCombinationMethod == EColorCombinationType.enuCIELabMatrix)
			{
				// new (11/5/04) 

				// origin (CIELab average now, but would be better to extend both lines to intersection point,
				//   or average of points where they are closest)
				pColor1 = GetSymbolColor(pCBRend1.get_Symbol(0));
				pColor2 = GetSymbolColor(pCBRend2.get_Symbol(0));
				pColor = GetCombinedColor(pColor1, pColor2, EColorCombinationType.enuCIELabColorRamp);
				IColor pOriginColor = null;
				pOriginColor = pColor;
				m_OLEColorMatrix[i, j] = pColor.RGB;

				// bottom edge (known)

				int tempFor1 = pCBRend1.BreakCount;
				for (i = 1; i < tempFor1; i++)
				{
					pColor = GetSymbolColor(pCBRend1.get_Symbol(i));
					m_OLEColorMatrix[i, 0] = pColor.RGB;
				}

				// left edge (known)

				int tempFor2 = pCBRend2.BreakCount;
				for (j = 1; j < tempFor2; j++)
				{
					pColor = GetSymbolColor(pCBRend2.get_Symbol(j));
					m_OLEColorMatrix[0, j] = pColor.RGB;
				}

				// remaining values (interpolated)

				int tempFor3 = pCBRend1.BreakCount;
				for (i = 1; i < tempFor3; i++)
				{

					int tempFor4 = pCBRend2.BreakCount;
					for (j = 1; j < tempFor4; j++)
					{
						pColor1 = GetSymbolColor(pCBRend1.get_Symbol(i));
						pColor2 = GetSymbolColor(pCBRend2.get_Symbol(j));
						pColor = GetCombinedColor(pColor1, pColor2, EColorCombinationType.enuCIELabMatrix, pOriginColor);
						m_OLEColorMatrix[i, j] = pColor.RGB;
						//m_pColorMatrix(i, j) = GetCombinedColor(pColor1, pColor2)
					}
				}

			}
			else
			{

		
				int tempFor5 = pCBRend1.BreakCount;
				for (i = 0; i < tempFor5; i++)
				{

					int tempFor6 = pCBRend2.BreakCount;
					for (j = 0; j < tempFor6; j++)
					{
						pColor1 = GetSymbolColor(pCBRend1.get_Symbol(i));
						pColor2 = GetSymbolColor(pCBRend2.get_Symbol(j));
						pColor = GetCombinedColor(pColor1, pColor2, m_eColorCombinationMethod);
						m_OLEColorMatrix[i, j] = pColor.RGB;
						//m_pColorMatrix(i, j) = GetCombinedColor(pColor1, pColor2)
					}
				}

			}


			return;
}

catch
{
    Console.WriteLine("error");
}
		}

		private IColor GetSymbolColor(ISymbol pSym)
		{
			IMarkerSymbol pMarkerSym = null;
			ILineSymbol pLineSym = null;
			IFillSymbol pFillSym = null;
			IColor pColor = null;

			if (pSym is IMarkerSymbol)
			{
				pMarkerSym = pSym as IMarkerSymbol;
				pColor = pMarkerSym.Color;
			}
			else if (pSym is ILineSymbol)
			{
				pLineSym = pSym as ILineSymbol;
				pColor = pLineSym.Color;
			}
			else
			{
				pFillSym = pSym as IFillSymbol;
				pColor = pFillSym.Color;
			}

			return pColor;

		}


	}

  //implementation of IExportSupport
  // ExportSupport is a private helper class to help the renderer implement of IExportSupport.  This class contains
  // the reference to the ExportInfoGenerator object used by the renderer.
  public class ExportSupport : IExportSupport
  {
    ISymbologyEnvironment2 m_symbologyEnvironment2;
    IFeatureExportInfoGenerator m_exportInfoGenerator;
    bool m_exportAttributes;
    bool m_exportHyperlinks;

    public ExportSupport()
    {
      m_exportAttributes = false;
      m_exportHyperlinks = false;
    }

    ~ExportSupport()
    {
      m_symbologyEnvironment2 = null;
      m_exportInfoGenerator = null;
    }

    public void GetExportSettings()
    {
      m_exportAttributes = false;
      m_exportHyperlinks = false;
      if (m_exportInfoGenerator == null)
        return;

      if (m_symbologyEnvironment2 == null)
        m_symbologyEnvironment2 = new SymbologyEnvironmentClass();

      m_exportAttributes = m_symbologyEnvironment2.OutputGDICommentForFeatureAttributes;
      m_exportHyperlinks = m_symbologyEnvironment2.OutputGDICommentForHyperlinks;
    }

    public void GenerateExportInfo(IFeature feature, IDisplay display)
    {
      if (m_exportInfoGenerator == null)
        return;
        
      if (m_exportAttributes)
        m_exportInfoGenerator.GenerateFeatureInfo(feature, display);
      if (m_exportHyperlinks)
        m_exportInfoGenerator.GenerateHyperlinkInfo(feature, display);
    }

    public void GenerateExportInfo(IFeatureDraw featureDraw, IDisplay display)
    {
      if (m_exportInfoGenerator == null)
        return;
      
      if (m_exportAttributes)
        m_exportInfoGenerator.GenerateFeatureInfo(featureDraw as IFeature, display);
      if (m_exportHyperlinks)
        m_exportInfoGenerator.GenerateHyperlinkInfo(featureDraw as IFeature, display);
    }

    public void AddExportFields(IFeatureClass fc, IQueryFilter queryFilter)
    {
      if (m_exportInfoGenerator == null)
        return;

      GetExportSettings();

      if (m_exportAttributes || m_exportHyperlinks)
        m_exportInfoGenerator.PrepareExportFilter(fc, queryFilter);
    }

    public void BeginFeature(IFeature feature, IDisplay display)
    {
      if (m_exportInfoGenerator == null)
        return;

      m_exportInfoGenerator.BeginFeature(feature, display);
    }

    public void EndFeature(IDisplay display)
    {
      if (m_exportInfoGenerator == null)
        return;

      m_exportInfoGenerator.EndFeature(display);
    }

    #region IExportSupport Members

    public IFeatureExportInfoGenerator ExportInfo
    {
      set
      {
        m_exportInfoGenerator = value;
      }
    }

    #endregion
  }

    [Guid("13137B0F-2255-46a4-9D6E-0A68FA560379")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    [ProgId("MultiVariateRenderers.SortCallBack")]
	public class SortCallBack : ITableSortCallBack
	{

		// class definition for SortCallBack which implements custom table sorting based on field / normalization field

		// data members
		private Microsoft.VisualBasic.VariantType m_value1;
		private Microsoft.VisualBasic.VariantType m_value2;
		private int m_iValueIndex;
		private int m_iNormIndex;

		public SortCallBack(int ValueIndex, int NormIndex)
		{
			m_iValueIndex = ValueIndex;
			m_iNormIndex = NormIndex;

		}

		public int Compare(object value1, object value2, int FieldIndex, int fieldSortIndex)
		{
			int tempCompare = 0;

			// sort normalized values 

			if (FieldIndex == m_iValueIndex)
			{

                m_value1 = (Microsoft.VisualBasic.VariantType)value1;
                m_value2 = (Microsoft.VisualBasic.VariantType)value2;
				return 0; // ?
			}

			if (FieldIndex == m_iNormIndex)
			{

                if (((double)value1 == 0) | ((double)value2 == 0)) // ?
						return 0;

				double dblNormedVal1 = 0;
				double dblNormedVal2 = 0;

				dblNormedVal1 = (double)m_value1 / (double)value1;
                dblNormedVal2 = (double)m_value2 / (double)value2;

				if (dblNormedVal1 > dblNormedVal2)
					tempCompare = 1;
				else if (dblNormedVal1 < dblNormedVal2)
					tempCompare = -1;
				else
					tempCompare = 0;
			}

			return tempCompare;
		}

	}
} //end of root namespace
[Visual Basic .NET]

MultivariateRenderer.vb

Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geometry

Public Enum EColorCombinationType
    enuComponents
    enuCIELabColorRamp
    enuLabLChColorRamp
    enuRGBAverage
    enuCIELabMatrix
End Enum

<ComClass(MultivariateRenderer.ClassId, MultivariateRenderer.InterfaceId, MultivariateRenderer.EventsId)> _
Public Class MultivariateRenderer

  Inherits ExportSupport

  ' class definition for MultivariateRenderer, a custom multivariate feature renderer
  '   consisting of 

  Implements IFeatureRenderer     ' all feature renderers must support this interface
  Implements IMultivariateRenderer ' custom interface
  Implements ILegendInfo          ' for TOC and legend support
  Implements IPersistVariant      ' to support saving and loading .mxd and .lyr files that contain this renderer
  Implements IRotationRenderer    ' to support symbol rotation by field value
  Implements ITransparencyRenderer ' we don't do anything real with this




#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "6A921DB3-5D31-4D85-9857-687CEDBC0D29"
    Public Const InterfaceId As String = "DDC4CD50-DF02-4B2F-9B85-DA87FDED9EA7"
    Public Const EventsId As String = "36E15D76-2463-41DD-A673-0C83021AC30A"
#End Region

    ' data members
    Private m_eColorCombinationMethod As EColorCombinationType = EColorCombinationType.enuComponents
    Private m_pShapePatternRend As IFeatureRenderer
    Private m_pColorRend1 As IFeatureRenderer
    Private m_pColorRend2 As IFeatureRenderer
    Private m_pSizeRend As IFeatureRenderer
    ' for the renderer's TOC and legend entry 
    ' current implementation is simple, but could be extended
    Private m_pLegendGroups() As ILegendGroup
    Private m_sRotationField As String
    Private m_eRotationType As esriSymbolRotationType = esriSymbolRotationType.esriRotateSymbolGeographic
    Private m_sTransparencyField As String
    Private m_pMainRend As IFeatureRenderer ' as renderers are assigned, use this to keep track of which one has the base symbols
    Private m_ShapeType As esriGeometryType
    Private m_pFeatureClass As IFeatureClass
    Private m_pQueryFilter As IQueryFilter
    Private m_OLEColorMatrix(3, 3) As Long

    Private Const E_FAIL As Long = &H80004005

    Public Sub New()
        
    End Sub

    Protected Overrides Sub Finalize()
        m_pShapePatternRend = Nothing
        m_pColorRend1 = Nothing
        m_pColorRend2 = Nothing
        MyBase.Finalize()
    End Sub

    Public Sub CreateLegend() Implements IMultivariateRenderer.CreateLegend
        ' NOT IMPL

        ' this is a place holder sub for logic that can be called that creates a more
        '   involved entry for the layer's TOC and legend entry

    End Sub

    Public Function CanRender(ByVal featClass As IFeatureClass, ByVal Display As IDisplay) As Boolean Implements IFeatureRenderer.CanRender
        ' only use this renderer if we have points, lines, or polygons
        Return (featClass.ShapeType = esriGeometryType.esriGeometryPoint) Or _
               (featClass.ShapeType = esriGeometryType.esriGeometryPolyline) Or _
               (featClass.ShapeType = esriGeometryType.esriGeometryPolygon)
    End Function

    Public Sub Draw(ByVal cursor As IFeatureCursor, ByVal DrawPhase As esriDrawPhase, ByVal Display As IDisplay, ByVal trackCancel As ITrackCancel) Implements IFeatureRenderer.Draw
        ' loop through and draw each feature

        Dim pFeat As IFeature
        Dim pRend As IFeatureRenderer
        Dim pFeatDraw As IFeatureDraw

        Dim bContinue As Boolean

        ' do not draw features if no display
        If (Display Is Nothing) Then
            Exit Sub
        End If

        ' we can't draw without somewhere to get our base symbols from
        If (m_pMainRend Is Nothing) Then
            Exit Sub
        End If

        If Not m_pSizeRend Is Nothing Then
            ' size varies
            If m_ShapeType = esriGeometryType.esriGeometryPoint Or m_ShapeType = esriGeometryType.esriGeometryPolyline Then
                If DrawPhase = esriDrawPhase.esriDPGeography Then
                    ' draw symbols in order from large to small
                    DrawSymbolsInOrder(cursor, DrawPhase, Display, trackCancel)
                End If
            ElseIf m_ShapeType = esriGeometryType.esriGeometryPolygon Then
                If (DrawPhase = esriDrawPhase.esriDPAnnotation) Then
                    ' draw primary symbology from large to small
                    DrawSymbolsInOrder(cursor, DrawPhase, Display, trackCancel)
                ElseIf (DrawPhase = esriDrawPhase.esriDPGeography) Then
                    ' draw background symbology
                    pFeat = cursor.NextFeature
                    bContinue = True

                    ' while there are still more features and drawing has not been cancelled
                    Dim pBackFillSym As IFillSymbol
                    Do While (Not pFeat Is Nothing) And (bContinue = True)
                        ' draw the feature
                        pFeatDraw = pFeat
                        If TypeOf m_pSizeRend Is IClassBreaksRenderer Then
                            Dim pCBRend As IClassBreaksRenderer
                            pCBRend = m_pSizeRend
                            pBackFillSym = pCBRend.BackgroundSymbol
                        Else
                            Dim pPropRend As IProportionalSymbolRenderer
                            pPropRend = m_pSizeRend
                            pBackFillSym = pPropRend.BackgroundSymbol
                        End If
                        Display.SetSymbol(pBackFillSym)

                        'implementation of IExportSupport
                        BeginFeature(pFeat, Display)

                        pFeatDraw.Draw(DrawPhase, Display, pBackFillSym, True, Nothing, esriDrawStyle.esriDSNormal)

                        'implementation of IExportSupport
                        GenerateExportInfo(pFeat, Display)
                        EndFeature(Display)

                        pFeat = cursor.NextFeature
                        If Not trackCancel Is Nothing Then bContinue = trackCancel.[Continue]
                    Loop
                Else
                    ' raising this error makes the selection symbol draw for selected features
                    On Error GoTo 0
                    Err.Raise(E_FAIL)
                End If
            End If

        Else
            ' size does not vary
            If (DrawPhase <> esriDrawPhase.esriDPGeography) Then
                ' raising this error makes the selection symbol draw for selected features
                On Error GoTo 0
                Err.Raise(E_FAIL)
            Else
                DrawSymbols(cursor, DrawPhase, Display, trackCancel)
            End If
        End If

    End Sub

    Public WriteOnly Property ExclusionSet() As IFeatureIDSet Implements IFeatureRenderer.ExclusionSet
        Set(ByVal Value As IFeatureIDSet)
            ' NOT IMPL    
        End Set
    End Property

    Public Sub PrepareFilter(ByVal fc As IFeatureClass, ByVal queryFilter As IQueryFilter) Implements IFeatureRenderer.PrepareFilter
        ' prepare filter for drawing

        ' must add OID
        queryFilter.AddField(fc.OIDFieldName)

        m_ShapeType = fc.ShapeType
        If m_ShapeType = esriGeometryType.esriGeometryPoint Then
            If Not m_sRotationField = "" Then
                queryFilter.AddField(m_sRotationField)
            End If
        End If

        ' save the feature class and the query filter so that multiple cursors can be built in DrawSymbols
        m_pFeatureClass = fc
        m_pQueryFilter = queryFilter

        ' prepare filters on constituent renderers so I can use SymbolByFeature in Draw
        If Not m_pShapePatternRend Is Nothing Then m_pShapePatternRend.PrepareFilter(fc, queryFilter)
        If Not m_pColorRend1 Is Nothing Then m_pColorRend1.PrepareFilter(fc, queryFilter)
        If Not m_pColorRend2 Is Nothing Then m_pColorRend2.PrepareFilter(fc, queryFilter)
        If Not m_pSizeRend Is Nothing Then m_pSizeRend.PrepareFilter(fc, queryFilter)

        ' if we're combining colors from two (sequential) quantitative schemes, build color matrix now
        '   this gives flexibility to extend in future
        ' in current implementation we determine combined color based on two colors, one from each constituent 
        '   ClassBreaksRenderer.  so, we could determine color on demand when drawing. but, by creating 
        '   the color matrix here and storing for later use, we leave open the possibility of swapping in 
        '   different logic for determining combined colors based on all known colors in each constituent
        '   renderer, not just the colors for the given feature
        If (Not m_pColorRend1 Is Nothing) And (Not m_pColorRend2 Is Nothing) Then
            If Not m_eColorCombinationMethod = EColorCombinationType.enuComponents Then
                BuildColorMatrix()
            End If
        End If
        'implementation of IExportSupport
        AddExportFields(fc, queryFilter)

    End Sub

    Public ReadOnly Property RenderPhase(ByVal DrawPhase As esriDrawPhase) As Boolean Implements IFeatureRenderer.RenderPhase
        Get
            Return (DrawPhase = esriDrawPhase.esriDPGeography) Or (DrawPhase = esriDrawPhase.esriDPAnnotation)
        End Get
    End Property

    Public ReadOnly Property SymbolByFeature(ByVal Feature As IFeature) As ISymbol Implements IFeatureRenderer.SymbolByFeature
        Get
            Return GetFeatureSymbol(Feature)
        End Get
    End Property

    Public ReadOnly Property LegendGroup(ByVal Index As Integer) As ILegendGroup Implements ILegendInfo.LegendGroup
        Get
            Dim pLegendInfo As ILegendInfo = Nothing
            Dim strHeading As String

            Select Case Index
                Case 0
                    pLegendInfo = m_pMainRend
                    If m_pMainRend Is m_pShapePatternRend Then
                        strHeading = "Shape/Pattern: "
                    ElseIf m_pMainRend Is m_pSizeRend Then
                        strHeading = "Size: "
                    Else
                        strHeading = "Color 1: "
                    End If
                Case 1
                    If Not m_pShapePatternRend Is Nothing Then
                        If Not m_pSizeRend Is Nothing Then
                            pLegendInfo = m_pSizeRend
                            strHeading = "Size: "
                        Else
                            pLegendInfo = m_pColorRend1
                            strHeading = "Color 1: "
                        End If
                    Else
                        If Not m_pSizeRend Is Nothing Then
                            pLegendInfo = m_pColorRend1
                            strHeading = "Color 1: "
                        Else
                            pLegendInfo = m_pColorRend2
                            strHeading = "Color 2: "
                        End If
                    End If
                Case 2
                    pLegendInfo = m_pColorRend1
                    strHeading = "Color 1: "
                Case 3
                    pLegendInfo = m_pColorRend2
                    strHeading = "Color 2: "

            End Select

            Dim pLegendGroup As ILegendGroup
            pLegendGroup = pLegendInfo.LegendGroup(0)
            'pLegendGroup.Heading = strHeading & pLegendGroup.Heading

            Return pLegendGroup

        End Get
    End Property

    Public ReadOnly Property LegendGroupCount() As Integer Implements ILegendInfo.LegendGroupCount
        Get
            Dim pLegInfo As ILegendInfo
            Dim n As Integer

            n = 0
            If Not m_pSizeRend Is Nothing Then
                pLegInfo = m_pSizeRend
                If Not pLegInfo.LegendGroup(0) Is Nothing Then n = n + 1
            End If
            If Not m_pShapePatternRend Is Nothing Then
                pLegInfo = m_pShapePatternRend
                If Not pLegInfo.LegendGroup(0) Is Nothing Then n = n + 1
            End If
            If Not m_pColorRend1 Is Nothing Then
                pLegInfo = m_pColorRend1
                If Not pLegInfo.LegendGroup(0) Is Nothing Then n = n + 1
            End If
            If Not m_pColorRend2 Is Nothing And Not m_pColorRend2 Is m_pColorRend1 Then
                'If Not m_pColorRend2 Is Nothing Then
                pLegInfo = m_pColorRend2
                If Not pLegInfo.LegendGroup(0) Is Nothing Then n = n + 1
            End If

            Return n
        End Get
    End Property

    Public ReadOnly Property LegendItem() As ILegendItem Implements ILegendInfo.LegendItem
        Get
            Return Nothing
        End Get
    End Property

    Public Property SymbolsAreGraduated() As Boolean Implements ILegendInfo.SymbolsAreGraduated
        Get
            Return False
        End Get
        Set(ByVal Value As Boolean)
            ' NOT IMPL
        End Set
    End Property

    Public ReadOnly Property ID() As UID Implements IPersistVariant.ID
        Get
            Dim pUID As New UID
            pUID.Value = "MultivariateRenderer"
            'pUID.Value = ClassId
            Return pUID
        End Get
    End Property

    Public Sub Load(ByVal Stream As IVariantStream) Implements IPersistVariant.Load
        'load the persisted parameters of the renderer

        m_eColorCombinationMethod = Stream.Read
        m_pShapePatternRend = Stream.Read
        m_pColorRend1 = Stream.Read
        m_pColorRend2 = Stream.Read
        m_pSizeRend = Stream.Read
        'm_pLegendGroups = = Stream.Read
        m_sRotationField = Stream.Read
        m_eRotationType = Stream.Read
        m_sTransparencyField = Stream.Read
        m_pMainRend = Stream.Read

        'CreateLegend() ' not needed now
    End Sub

    Public Sub Save(ByVal Stream As IVariantStream) Implements IPersistVariant.Save
        'persist the settings for the renderer

        Stream.Write(m_eColorCombinationMethod)
        Stream.Write(m_pShapePatternRend)
        Stream.Write(m_pColorRend1)
        Stream.Write(m_pColorRend2)
        Stream.Write(m_pSizeRend)
        'Stream.Write(m_pLegendGroups)
        Stream.Write(m_sRotationField)
        Stream.Write(m_eRotationType)
        Stream.Write(m_sTransparencyField)
        Stream.Write(m_pMainRend)
    End Sub

    Private Function GetFeatureSymbol(ByVal pFeat As IFeature) As ISymbol

        Dim pSym As ISymbol

        ' get base symbol
        pSym = m_pMainRend.SymbolByFeature(pFeat)

        ' modify base symbol as necessary

        If (Not m_pSizeRend Is Nothing) And (Not m_pMainRend Is m_pSizeRend) And (Not pSym Is Nothing) Then
            pSym = ApplySize(pSym, pFeat)
        End If

        If ((Not m_pColorRend1 Is Nothing) Or (Not m_pColorRend2 Is Nothing)) And (Not pSym Is Nothing) Then
            pSym = ApplyColor(pSym, pFeat)
        End If

        If ((m_ShapeType = esriGeometryType.esriGeometryPoint) Or ((m_ShapeType = esriGeometryType.esriGeometryPolygon) And TypeOf pSym Is IMarkerSymbol)) And (Not pSym Is Nothing) Then
            If m_sRotationField <> "" Then
                pSym = ApplyRotation(pSym, pFeat)
            End If
        End If

        If m_sTransparencyField <> "" Then
            pSym = ApplyTransparency(pSym)
        End If
        'End If

        Return pSym

    End Function


    Private Function SortData(ByVal pCursor As IFeatureCursor, ByVal pTrackCancel As ITrackCancel) As IFeatureCursor
        ' sort in descending by value
        Dim pTable As ITable
        pTable = m_pFeatureClass

        Dim pTableSort As ITableSort
        pTableSort = New TableSort
        pTableSort.Table = pTable
        pTableSort.Cursor = pCursor

        ' why do I have to do this?
        Dim pQF As IQueryFilter
        pQF = New QueryFilter
        pQF.SubFields = "*"
        pQF.WhereClause = m_pQueryFilter.WhereClause
        pTableSort.QueryFilter = pQF

        Dim pPSRend As IProportionalSymbolRenderer
        pPSRend = m_pSizeRend
        Dim strValueField As String
        strValueField = pPSRend.Field
        pTableSort.Fields = strValueField
        pTableSort.Ascending(strValueField) = False

        Dim pDataNorm As IDataNormalization
        pDataNorm = pPSRend
        If pDataNorm.NormalizationType = esriDataNormalization.esriNormalizeByField Then
            ' comparison is not simple comparison of field values, use callback to do custom compare

            ' get normalization field and add to table sort
            Dim strFields As String = ""
            strFields = strFields & strValueField
            Dim strNormField As String
            strNormField = pDataNorm.NormalizationField
            strFields = strFields & ","
            strFields = strFields & strNormField
            pTableSort.Fields = strFields
            pTableSort.Ascending(strNormField) = False

            ' create new custom table call sort object and connect to the TableSort object
            Dim pTableSortCallBack As ITableSortCallBack
            pTableSortCallBack = New SortCallBack(pTable.Fields.FindField(strValueField), pTable.Fields.FindField(strNormField))
            pTableSort.Compare = pTableSortCallBack
        End If

        ' call the sort
        pTableSort.Sort(pTrackCancel)

        ' retrieve the sorted rows
        Dim pSortedCursor As IFeatureCursor
        pSortedCursor = pTableSort.Rows()

        Return pSortedCursor
    End Function

    Private Sub DrawSymbolsInOrder(ByVal Cursor As IFeatureCursor, ByVal drawPhase As esriDrawPhase, ByVal Display As IDisplay, ByVal trackCancel As ITrackCancel)
        ' this sub draws either markers or line symbols from large small so that the smallest symbols will be drawn on top

        ' in graduated symbol case, a cursor is built and parsed n times for n size classes
        ' in proportional symbol case, symbols are sorted and drawn from largest to smallest

        Dim iSizeIndex As Integer
        Dim iCurrentDrawableSymbolIndex As Integer
        Dim pMyCursor As IFeatureCursor
        Dim pFeat As IFeature
        Dim pFeatDraw As IFeatureDraw
        Dim bContinue As Boolean = True
        Dim pSizeSym As ISymbol
        Dim pDrawSym As ISymbol
        Dim pSortedCursor As IFeatureCursor

        If TypeOf m_pSizeRend Is IProportionalSymbolRenderer Then
            ' sort 
            pSortedCursor = SortData(Cursor, trackCancel)

            ' draw
            pFeat = pSortedCursor.NextFeature
            Do While Not pFeat Is Nothing
                pDrawSym = GetFeatureSymbol(pFeat)
                ' draw the feature
                pFeatDraw = pFeat
                Display.SetSymbol(pDrawSym)

                'implementation of IExportSupport
                BeginFeature(pFeat, Display)

                pFeatDraw.Draw(drawPhase, Display, pDrawSym, True, Nothing, esriDrawStyle.esriDSNormal)

                'implementation of IExportSupport
                GenerateExportInfo(pFeat, Display)
                EndFeature(Display)

                ' get next feature
                pFeat = pSortedCursor.NextFeature
                If Not trackCancel Is Nothing Then bContinue = trackCancel.[Continue]
            Loop

        Else
            Dim pSizeCBRend As IClassBreaksRenderer
            pSizeCBRend = m_pSizeRend
            pMyCursor = Cursor
            For iCurrentDrawableSymbolIndex = (pSizeCBRend.BreakCount - 1) To 0 Step -1
                ' do not build a cursor the 1st time because we already have one
                If iCurrentDrawableSymbolIndex < (pSizeCBRend.BreakCount - 1) Then
                    ' build pMyCursor
                    pMyCursor = m_pFeatureClass.Search(m_pQueryFilter, True)
                End If
                pFeat = pMyCursor.NextFeature
                Do While Not pFeat Is Nothing
                    ' check to see if we will draw in this pass
                    pSizeSym = m_pSizeRend.SymbolByFeature(pFeat)
                    iSizeIndex = GetSymbolIndex(pSizeSym, pSizeCBRend)
                    If (iSizeIndex = iCurrentDrawableSymbolIndex) Then
                        ' go ahead and draw the symbol
                        ' get symbol to draw
                        pDrawSym = GetFeatureSymbol(pFeat)

                        ' draw the feature
                        pFeatDraw = pFeat
                        Display.SetSymbol(pDrawSym)

                        'implementation of IExportSupport
                        BeginFeature(pFeat, Display)

                        pFeatDraw.Draw(drawPhase, Display, pDrawSym, True, Nothing, esriDrawStyle.esriDSNormal)

                        'implementation of IExportSupport
                        GenerateExportInfo(pFeat, Display)
                        EndFeature(Display)

                        If Not trackCancel Is Nothing Then bContinue = trackCancel.[Continue]
                    End If

                    pFeat = pMyCursor.NextFeature
                Loop

            Next iCurrentDrawableSymbolIndex ' increment DOWN to next symbol size

        End If

    End Sub

    Private Sub DrawSymbols(ByVal Cursor As IFeatureCursor, ByVal drawPhase As esriDrawPhase, ByVal Display As IDisplay, ByVal trackCancel As ITrackCancel)

        Dim pFeat As IFeature
        Dim pFeatDraw As IFeatureDraw
        Dim bContinue As Boolean = True
        Dim pDrawSym As ISymbol

        pFeat = Cursor.NextFeature
        bContinue = True
        ' while there are still more features and drawing has not been cancelled
        Do While (Not pFeat Is Nothing) And (bContinue = True)
            ' get symbol to draw
            pDrawSym = GetFeatureSymbol(pFeat)
            ' draw the feature
            pFeatDraw = pFeat
            Display.SetSymbol(pDrawSym)

            'implementation of IExportSupport
            BeginFeature(pFeat, Display)

            pFeatDraw.Draw(drawPhase, Display, pDrawSym, True, Nothing, esriDrawStyle.esriDSNormal)

            'implementation of IExportSupport
            GenerateExportInfo(pFeat, Display)
            EndFeature(Display)

            ' get next feature
            pFeat = Cursor.NextFeature
            If Not trackCancel Is Nothing Then bContinue = trackCancel.[Continue]
        Loop

    End Sub

    Private Function GetCombinedColor(ByVal pColor1 As IColor, ByVal pColor2 As IColor, ByVal eCombinationMethod As EColorCombinationType, Optional ByVal pOriginColor As IColor = Nothing) As ESRI.ArcGIS.Display.IColor
        ' combines the input colors based on m_eColorCombinationMethod

        Dim pOutColor As IColor

        Dim MyOLE_COLOR As Long ' As OLE_COLOR in VB6
        Dim pMainRGBColor As IRgbColor
        Dim pVariationRGBColor As IRgbColor
        Dim pMergedRGBColor As IRgbColor
        Dim bOK As Boolean
        Dim pAlgorithmicCR As IAlgorithmicColorRamp

        ' if either of the colors are null, then don't run the color through any algorithm,
        '   instead, just return the other color.  if both are null, then return a null color
        If pColor1.NullColor Then
            pOutColor = pColor2

        ElseIf pColor2.NullColor Then
            pOutColor = pColor1

        ElseIf eCombinationMethod = EColorCombinationType.enuComponents Then
            ' HSV components
            ' create a new HSV color
            Dim pHSVDrawColor As IHsvColor
            pHSVDrawColor = New HsvColor
            ' get HSV values from Color1 and Color2 and assign to pHSVDrawColor
            Dim pHSVColor1 As IHsvColor
            Dim pHSVColor2 As IHsvColor

            pHSVColor1 = New HsvColor
            pHSVColor1.RGB = pColor1.RGB
            pHSVColor2 = New HsvColor
            pHSVColor2.RGB = pColor2.RGB

            pHSVDrawColor.Hue = pHSVColor1.Hue
            pHSVDrawColor.Saturation = pHSVColor2.Saturation
            pHSVDrawColor.Value = pHSVColor2.Value

            pOutColor = pHSVDrawColor

        ElseIf eCombinationMethod = EColorCombinationType.enuRGBAverage Then
            ' use additive color model to merge the two colors
            MyOLE_COLOR = pColor1.RGB
            pMainRGBColor = New RgbColor
            pMainRGBColor.RGB = MyOLE_COLOR
            MyOLE_COLOR = pColor2.RGB
            pVariationRGBColor = New RgbColor
            pVariationRGBColor.RGB = MyOLE_COLOR
            ' merged color = RGB average of the two colors
            pMergedRGBColor = New RgbColor
            pMergedRGBColor.Red = (pMainRGBColor.Red + pVariationRGBColor.Red) / 2
            pMergedRGBColor.Green = (pMainRGBColor.Green + pVariationRGBColor.Green) / 2
            pMergedRGBColor.Blue = (pMainRGBColor.Blue + pVariationRGBColor.Blue) / 2

            pOutColor = pMergedRGBColor
        ElseIf (eCombinationMethod = EColorCombinationType.enuCIELabColorRamp) Or (eCombinationMethod = EColorCombinationType.enuLabLChColorRamp) Then
            ' use color ramp and take central color between the two colors
            pAlgorithmicCR = New AlgorithmicColorRamp
            If m_eColorCombinationMethod = EColorCombinationType.enuCIELabColorRamp Then
                pAlgorithmicCR.Algorithm = esriColorRampAlgorithm.esriCIELabAlgorithm
            Else
                pAlgorithmicCR.Algorithm = esriColorRampAlgorithm.esriLabLChAlgorithm
            End If
            pAlgorithmicCR.Size = 3
            pAlgorithmicCR.FromColor = pColor1
            pAlgorithmicCR.ToColor = pColor2
            pAlgorithmicCR.CreateRamp(bOK)

            pOutColor = pAlgorithmicCR.Color(1)     ' middle color in ramp
        Else ' EColorCombinationType.enuCIELabMatrix

            Dim iLab1(3) As Double ' L, a, b values for Color1
            Dim iLab2(3) As Double ' L, a, b values for Color2
            Dim iLabOrig(3) As Double ' L, a, b values for pOriginColor
            pColor1.GetCIELAB(iLab1(0), iLab1(1), iLab1(2))
            pColor2.GetCIELAB(iLab2(0), iLab2(1), iLab2(2))
            pOriginColor.GetCIELAB(iLabOrig(0), iLabOrig(1), iLabOrig(2))

            Dim iLabOut(3) As Double
            ' add color1 vector and color2 vector, then subtract the origin color vector
            iLabOut(0) = iLab1(0) + iLab2(0) - iLabOrig(0)
            iLabOut(1) = iLab1(1) + iLab2(1) - iLabOrig(1)
            iLabOut(2) = iLab1(2) + iLab2(2) - iLabOrig(2)

            CorrectLabOutofRange(iLabOut(0), iLabOut(1), iLabOut(2))

            Dim pHSVColor As IHsvColor
            pHSVColor = New HsvColor
            pHSVColor.SetCIELAB(iLabOut(0), iLabOut(1), iLabOut(2))
            pOutColor = pHSVColor
        End If

        Return pOutColor

    End Function

    Private Sub CorrectLabOutofRange(ByRef L As Double, ByRef a As Double, ByRef b As Double)

        If L > 100 Then
            L = 100
        ElseIf L < 0 Then
            L = 0
        End If

        If a > 120 Then
            a = 120
        ElseIf a < -120 Then
            a = -120
        End If

        If b > 120 Then
            b = 120
        ElseIf b < -120 Then
            b = -120
        End If

    End Sub

    Private Sub RemoveLegend()

        Dim i As Integer

        If Not m_pLegendGroups Is Nothing Then
            For i = 0 To UBound(m_pLegendGroups)
                m_pLegendGroups(i) = Nothing
            Next i
        End If
    End Sub

    Private Function CalcMainRend() As IFeatureRenderer
        ' consider using an internal array to keep track of active arrays in correct order, this will make it easier to implement ILegendInfo

        If (Not m_pShapePatternRend Is Nothing) Then
            If (m_ShapeType = esriGeometryType.esriGeometryPolygon) And Not m_pSizeRend Is Nothing Then
                Return m_pSizeRend
            Else
                Return m_pShapePatternRend
            End If
        ElseIf (Not m_pSizeRend Is Nothing) Then
            Return m_pSizeRend
        ElseIf (Not m_pColorRend1 Is Nothing) Then
            Return m_pColorRend1
        ElseIf (Not m_pColorRend2 Is Nothing) Then
            Return m_pColorRend2
        Else
            Return Nothing ' must have shape or color or size, if not you can't render...
        End If

    End Function

    Public Property ColorCombinationMethod() As EColorCombinationType Implements IMultivariateRenderer.ColorCombinationMethod
        Get
            Return m_eColorCombinationMethod
        End Get
        Set(ByVal Value As EColorCombinationType)
            m_eColorCombinationMethod = Value
        End Set
    End Property

    Public Property ColorRend1() As ESRI.ArcGIS.Carto.IFeatureRenderer Implements IMultivariateRenderer.ColorRend1
        Get
            Return m_pColorRend1
        End Get
        Set(ByVal Value As ESRI.ArcGIS.Carto.IFeatureRenderer)
            m_pColorRend1 = Value
            m_pMainRend = CalcMainRend()
        End Set
    End Property

    Public Property ColorRend2() As ESRI.ArcGIS.Carto.IFeatureRenderer Implements IMultivariateRenderer.ColorRend2
        Get
            Return m_pColorRend2
        End Get
        Set(ByVal Value As ESRI.ArcGIS.Carto.IFeatureRenderer)
            m_pColorRend2 = Value
        End Set
    End Property

    Public Property ShapePatternRend() As ESRI.ArcGIS.Carto.IFeatureRenderer Implements IMultivariateRenderer.ShapePatternRend
        Get
            Return m_pShapePatternRend
        End Get
        Set(ByVal Value As ESRI.ArcGIS.Carto.IFeatureRenderer)
            m_pShapePatternRend = Value
            m_pMainRend = CalcMainRend()
        End Set
    End Property

    Public Property SizeRend() As ESRI.ArcGIS.Carto.IFeatureRenderer Implements IMultivariateRenderer.SizeRend
        Get
            Return m_pSizeRend
        End Get
        Set(ByVal Value As ESRI.ArcGIS.Carto.IFeatureRenderer)
            m_pSizeRend = Value
            m_pMainRend = CalcMainRend()
        End Set
    End Property

    Private Function ApplyRotation(ByVal pMarkerSym As IMarkerSymbol, ByVal pFeat As IFeature) As IMarkerSymbol

        Dim lAngle As Double
        lAngle = Convert.ToDouble(pFeat.Value(pFeat.Fields.FindField(m_sRotationField)))

        If m_eRotationType = esriSymbolRotationType.esriRotateSymbolGeographic Then
            pMarkerSym.Angle = pMarkerSym.Angle - lAngle
        Else
            pMarkerSym.Angle = pMarkerSym.Angle + lAngle - 90
        End If

        Return pMarkerSym
    End Function

    Private Function ApplyTransparency(ByVal pSym As ISymbol) As ISymbol

        ' TODO

        Return pSym
    End Function

    Private Function ApplyColor(ByVal pSym As ISymbol, ByVal pFeat As IFeature) As ISymbol
        On Error GoTo ErrHand


        Dim pSym1 As ISymbol
        Dim pSym2 As ISymbol
        Dim pColor As IColor
        Dim pHSVColor As IHsvColor

        If (Not m_pColorRend1 Is Nothing) And (Not m_pColorRend2 Is Nothing) Then ' for now both color renderers need to be set to apply color
            pSym1 = m_pColorRend1.SymbolByFeature(pFeat)
            pSym2 = m_pColorRend2.SymbolByFeature(pFeat)
            ' only use GetCombinedColor for HSV component-type combination method
            If m_eColorCombinationMethod = EColorCombinationType.enuComponents Then
                pColor = GetCombinedColor(GetSymbolColor(pSym1), GetSymbolColor(pSym2), m_eColorCombinationMethod)

                pHSVColor = pColor

            Else
                'pColor = m_pColorMatrix(GetSymbolIndex(pSym1, m_pColorRend1), GetSymbolIndex(pSym2, m_pColorRend2))
                pColor = New RgbColor
                pColor.RGB = m_OLEColorMatrix(GetSymbolIndex(pSym1, m_pColorRend1), GetSymbolIndex(pSym2, m_pColorRend2))
            End If


            If TypeOf pSym Is IMarkerSymbol Then
                Dim pMarkerSym As IMarkerSymbol
                pMarkerSym = pSym
                pMarkerSym.Color = pColor
            ElseIf TypeOf pSym Is ILineSymbol Then
                Dim pLineSym As ILineSymbol
                pLineSym = pSym
                pLineSym.Color = pColor
            Else
                Dim pFillSym As IFillSymbol
                pFillSym = pSym
                pFillSym.Color = pColor

            End If


        End If

        Return pSym
        Exit Function
ErrHand:
        MsgBox("Apply Color " & Err.Description & "Line: " & Err.Erl)
    End Function


    

    Private Function ApplySize(ByVal pSym As ISymbol, ByVal pFeat As IFeature) As ISymbol

        If TypeOf pSym Is IMarkerSymbol Then
            ' Marker Symbol
            Dim pTargetMarkerSym As IMarkerSymbol
            pTargetMarkerSym = pSym
            Dim pSourceMarkerSym As IMarkerSymbol
            pSourceMarkerSym = m_pSizeRend.SymbolByFeature(pFeat)
            If Not (pSourceMarkerSym Is Nothing) Then
                pTargetMarkerSym.Size = pSourceMarkerSym.Size
            End If
        Else
            ' Line Symbol
            Dim pTargetLineSym As ILineSymbol
            pTargetLineSym = pSym
            Dim pSourceLineSym As ILineSymbol
            pSourceLineSym = m_pSizeRend.SymbolByFeature(pFeat)

            If Not (pSourceLineSym Is Nothing) Then
                pTargetLineSym.Width = pSourceLineSym.Width
            End If
        End If

        Return pSym
    End Function

    Public Property RotationField() As String Implements IRotationRenderer.RotationField
        Get
            Return m_sRotationField
        End Get
        Set(ByVal Value As String)
            m_sRotationField = Value
        End Set
    End Property

    Public Property TransparencyField() As String Implements ITransparencyRenderer.TransparencyField
        Get
            Return m_sTransparencyField
        End Get
        Set(ByVal Value As String)
            m_sTransparencyField = Value
        End Set
    End Property

    Public Property RotationType() As ESRI.ArcGIS.Carto.esriSymbolRotationType Implements IRotationRenderer.RotationType
        Get
            Return m_eRotationType
        End Get
        Set(ByVal Value As ESRI.ArcGIS.Carto.esriSymbolRotationType)
            m_eRotationType = Value
        End Set
    End Property

    Private Function GetSymbolIndex(ByVal pSym As ISymbol, ByVal pRend As IClassBreaksRenderer) As Integer
        ' given an input symbol and a renderer, this function returns the index of
        '   the class that the symbol represents in the renderer

        Dim i As Integer
        Dim iNumBreaks As Integer

        iNumBreaks = pRend.BreakCount
        i = 0
        Dim pLegendInfo As ILegendInfo
        pLegendInfo = pRend
        Do While (i < iNumBreaks - 1)
            If pLegendInfo.SymbolsAreGraduated Then
                ' compare based on size
                If SymbolsAreSameSize(pSym, pRend.Symbol(i)) Then Exit Do
            Else
                ' compare based on color
                If SymbolsAreSameColor(pSym, pRend.Symbol(i)) Then Exit Do
            End If
            i = i + 1
        Loop

        Return i

    End Function

    Private Function SymbolsAreSameSize(ByVal pSym1 As ISymbol, ByVal psym2 As ISymbol) As Boolean

        If TypeOf pSym1 Is IMarkerSymbol Then
            Dim pMS1 As IMarkerSymbol
            Dim pMS2 As IMarkerSymbol
            pMS1 = pSym1
            pMS2 = psym2
            Return pMS1.Size = pMS2.Size
        Else
            Dim pLS1 As ILineSymbol
            Dim pLS2 As ILineSymbol
            pLS1 = pSym1
            pLS2 = psym2
            Return pLS1.Width = pLS2.Width
        End If

    End Function

    Private Function SymbolsAreSameColor(ByVal pSym1 As ISymbol, ByVal psym2 As ISymbol) As Boolean

        Dim pColor1 As IColor
        Dim pColor2 As IColor
        pColor1 = GetSymbolColor(pSym1)
        pColor2 = GetSymbolColor(psym2)
        Return pColor1.RGB = pColor2.RGB

    End Function

    Private Sub BuildColorMatrix()
        On Error GoTo ErrHand

        Dim pCBRend1 As IClassBreaksRenderer
        Dim pCBRend2 As IClassBreaksRenderer

        pCBRend1 = New ClassBreaksRenderer()
        pCBRend2 = New ClassBreaksRenderer()

        If ((TypeOf m_pColorRend1 Is IFeatureRenderer) And (TypeOf m_pColorRend2 Is IFeatureRenderer)) Then
            pCBRend1 = CType(m_pColorRend1, IClassBreaksRenderer)
            pCBRend2 = CType(m_pColorRend2, IClassBreaksRenderer)

            Dim i As Integer
            Dim j As Integer
            Dim pColor1 As IColor
            Dim pColor2 As IColor
            Dim pColor As IColor

            If m_eColorCombinationMethod = EColorCombinationType.enuCIELabMatrix Then
                ' new (11/5/04) 

                ' origin (CIELab average now, but would be better to extend both lines to intersection point,
                '   or average of points where they are closest)
                pColor1 = GetSymbolColor(pCBRend1.Symbol(0))
                pColor2 = GetSymbolColor(pCBRend2.Symbol(0))
                pColor = GetCombinedColor(pColor1, pColor2, EColorCombinationType.enuCIELabColorRamp)
                Dim pOriginColor As IColor
                pOriginColor = pColor
                m_OLEColorMatrix(i, j) = pColor.RGB

                ' bottom edge (known)
                For i = 1 To pCBRend1.BreakCount - 1
                    pColor = GetSymbolColor(pCBRend1.Symbol(i))
                    m_OLEColorMatrix(i, 0) = pColor.RGB
                Next

                ' left edge (known)
                For j = 1 To pCBRend2.BreakCount - 1
                    pColor = GetSymbolColor(pCBRend2.Symbol(j))
                    m_OLEColorMatrix(0, j) = pColor.RGB
                Next

                ' remaining values (interpolated)
                For i = 1 To pCBRend1.BreakCount - 1
                    For j = 1 To pCBRend2.BreakCount - 1
                        pColor1 = GetSymbolColor(pCBRend1.Symbol(i))
                        pColor2 = GetSymbolColor(pCBRend2.Symbol(j))
                        pColor = GetCombinedColor(pColor1, pColor2, EColorCombinationType.enuCIELabMatrix, pOriginColor)
                        m_OLEColorMatrix(i, j) = pColor.RGB
                    Next j
                Next i

            Else

                For i = 0 To pCBRend1.BreakCount - 1
                    For j = 0 To pCBRend2.BreakCount - 1
                        pColor1 = GetSymbolColor(pCBRend1.Symbol(i))
                        pColor2 = GetSymbolColor(pCBRend2.Symbol(j))
                        pColor = GetCombinedColor(pColor1, pColor2, m_eColorCombinationMethod)
                        m_OLEColorMatrix(i, j) = pColor.RGB

                    Next j
                Next i

            End If
        
        End If

        Exit Sub
ErrHand:
        MsgBox(Err.Description)

    End Sub

    Private Function GetSymbolColor(ByVal pSym As ISymbol) As IColor
        Dim pMarkerSym As IMarkerSymbol
        Dim pLineSym As ILineSymbol
        Dim pFillSym As IFillSymbol
        Dim pColor As IColor


        If TypeOf pSym Is IMarkerSymbol Then
            pMarkerSym = pSym
            pColor = pMarkerSym.Color
        ElseIf TypeOf pSym Is ILineSymbol Then
            pLineSym = pSym
            pColor = pLineSym.Color
        ElseIf Not pSym Is Nothing Then
            pFillSym = pSym
            pColor = pFillSym.Color
        Else
            pColor = Nothing
        End If

        Return pColor

    End Function


End Class



'implementation of IExportSupport
' ExportSupport is a private helper class to help the renderer implement of IExportSupport.  This class contains
' the reference to the ExportInfoGenerator object used by the renderer.

Public Class ExportSupport
  Implements IExportSupport

  Dim m_symbologyEnvironment2 As ISymbologyEnvironment2
  Dim m_exportInfoGenerator As IFeatureExportInfoGenerator
  Dim m_exportAttributes As Boolean
  Dim m_exportHyperlinks As Boolean


  Public WriteOnly Property ExportInfo() As ESRI.ArcGIS.Carto.IFeatureExportInfoGenerator Implements ESRI.ArcGIS.Carto.IExportSupport.ExportInfo
    Set(ByVal value As ESRI.ArcGIS.Carto.IFeatureExportInfoGenerator)
      m_exportInfoGenerator = value
    End Set
  End Property


  Public Sub New()
    m_exportAttributes = False
    m_exportHyperlinks = False
  End Sub


  Protected Overrides Sub Finalize()
    m_symbologyEnvironment2 = Nothing
    m_exportInfoGenerator = Nothing
    MyBase.Finalize()
  End Sub



  Public Sub GetExportSettings()

    m_exportAttributes = False
    m_exportHyperlinks = False

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    If m_symbologyEnvironment2 Is Nothing Then
      m_symbologyEnvironment2 = New SymbologyEnvironment
    End If

    m_exportAttributes = m_symbologyEnvironment2.OutputGDICommentForFeatureAttributes
    m_exportHyperlinks = m_symbologyEnvironment2.OutputGDICommentForHyperlinks

  End Sub


  Public Sub GenerateExportInfo(ByRef feature As IFeature, ByRef display As IDisplay)

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    If m_exportAttributes Then m_exportInfoGenerator.GenerateFeatureInfo(feature, display)
    If m_exportHyperlinks Then m_exportInfoGenerator.GenerateHyperlinkInfo(feature, display)

  End Sub


  Public Sub GenerateExportInfo(ByRef featureDraw As IFeatureDraw, ByRef display As IDisplay)

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    Dim feature As IFeature
    feature = featureDraw

    If m_exportAttributes Then m_exportInfoGenerator.GenerateFeatureInfo(feature, display)
    If m_exportHyperlinks Then m_exportInfoGenerator.GenerateHyperlinkInfo(feature, display)

  End Sub


  Public Sub AddExportFields(ByRef fc As IFeatureClass, ByRef queryFilter As IQueryFilter)

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    GetExportSettings()

    If m_exportAttributes Or m_exportHyperlinks Then
      m_exportInfoGenerator.PrepareExportFilter(fc, queryFilter)
    End If

  End Sub


  Public Sub BeginFeature(ByRef feature As IFeature, ByRef display As IDisplay)

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    m_exportInfoGenerator.BeginFeature(feature, display)

  End Sub


  Public Sub EndFeature(ByRef display As IDisplay)

    If m_exportInfoGenerator Is Nothing Then Exit Sub

    m_exportInfoGenerator.EndFeature(display)

  End Sub


End Class



<ComClass(SortCallBack.ClassId, SortCallBack.InterfaceId, SortCallBack.EventsId)> _
Public Class SortCallBack ' would like to declare this as private

    ' class definition for SortCallBack which implements custom table sorting based on field / normalization field

    Implements ITableSortCallBack

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "13137B0F-2255-46a4-9D6E-0A68FA560379"
    Public Const InterfaceId As String = "68A049DC-8E6C-4759-9797-960076474729"
    Public Const EventsId As String = "7BE0E19F-9AB3-4dd4-BB79-6EBD85E7930E"

#End Region

    ' data members
    Private m_value1 As VariantType
    Private m_value2 As VariantType
    Private m_iValueIndex As Integer
    Private m_iNormIndex As Integer

    Public Sub New(ByVal ValueIndex As Integer, ByVal NormIndex As Integer)
        m_iValueIndex = ValueIndex
        m_iNormIndex = NormIndex

    End Sub

    Public Function Compare(ByVal value1 As Object, ByVal value2 As Object, ByVal FieldIndex As Integer, ByVal fieldSortIndex As Integer) As Integer Implements ITableSortCallBack.Compare

        ' sort normalized values 

        If (FieldIndex = m_iValueIndex) Then

            m_value1 = value1
            m_value2 = value2
            Exit Function ' ?
        End If

        If (FieldIndex = m_iNormIndex) Then

            If (value1 = 0) Or (value2 = 0) Then Exit Function ' ?

            Dim dblNormedVal1 As Double
            Dim dblNormedVal2 As Double

            dblNormedVal1 = m_value1 / value1
            dblNormedVal2 = m_value2 / value2

            If dblNormedVal1 > dblNormedVal2 Then
                Compare = 1
            ElseIf dblNormedVal1 < dblNormedVal2 Then
                Compare = -1
            Else
                Compare = 0
            End If
        End If

    End Function

End Class