Multivariate renderer
MultivariateRenderer.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 


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