Manages the drawing of the vertex that represents a collapsed group and its incident edges.
Call PreDrawVertex before drawing a vertex, and after drawing the vertex. Call after expanding a collapsed group.
Inheritance: VisualizationBase
    DrawSimpleShape
    (
        IVertex oVertex,
        VertexShape eShape,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        DrawingVisualPlus oDrawingVisual,
        VisibilityKeyValue eVisibility,
        Boolean bDrawAsSelected,
        String sAnnotation,
        VertexLabelDrawer oVertexLabelDrawer,
        CollapsedGroupDrawingManager oCollapsedGroupDrawingManager
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oDrawingVisual != null);
        Debug.Assert(oVertexLabelDrawer != null);
        Debug.Assert(oCollapsedGroupDrawingManager != null);
        AssertValid();

        Double dRadius = GetRadius(oVertex);
        Color oColor = GetColor(oVertex, eVisibility, bDrawAsSelected);
        Point oVertexLocation = GetVertexLocation(oVertex);

        oDrawingVisual.SetEffect( GetSimpleShapeEffect(
            oGraphDrawingContext, dRadius, oColor) );

        Rect oVertexBounds;

        if (eShape == VertexShape.Triangle ||
            eShape == VertexShape.SolidTriangle)
        {
            oVertexBounds =
                WpfGraphicsUtil.TriangleBoundsFromCenterAndHalfWidth(
                    oVertexLocation, dRadius);
        }
        else if (eShape == VertexShape.SolidTaperedDiamond)
        {
            // Note that the bounds of a tapered diamond can't be calculated
            // using simple equations.  Instead, create the tapered diamond and
            // let WPF compute the bounds.
            //
            // There is some inefficiency here, because the tapered diamond
            // gets created again before it is drawn, in its possibly-moved
            // location.

            oVertexBounds = WpfPathGeometryUtil.GetTaperedDiamond(
                oVertexLocation, dRadius).Bounds;
        }
        else if (eShape == VertexShape.SolidRoundedX)
        {
            // Note that the bounds of a rounded X can't be calculated
            // using simple equations.  Instead, create the rounded X and
            // let WPF compute the bounds.
            //
            // There is some inefficiency here, because the rounded X
            // gets created again before it is drawn, in its possibly-moved
            // location.

            oVertexBounds = WpfPathGeometryUtil.GetRoundedX(
                oVertexLocation, dRadius).Bounds;
        }
        else
        {
            oVertexBounds = WpfGraphicsUtil.SquareFromCenterAndHalfWidth(
                oVertexLocation, dRadius);
        }

        // Move the vertex if it falls outside the graph rectangle.

        MoveVertexIfNecessary(oVertex, oGraphDrawingContext,
            oCollapsedGroupDrawingManager, ref oVertexBounds);

        oVertexLocation = GetVertexLocation(oVertex);
        VertexDrawingHistory oVertexDrawingHistory = null;

        // Note that for the "hollow" shapes -- Circle, Square, Diamond, and
        // Triangle -- Brushes.Transparent is used instead of a null brush.
        // This allows the entire area of these shapes to be hit-tested.  Using
        // a null brush would cause hit-testing to fail if the shapes'
        // interiors were clicked.

        switch (eShape)
        {
            case VertexShape.Circle:
            case VertexShape.Disk:

                Boolean bIsDisk = (eShape == VertexShape.Disk);

                oDrawingContext.DrawEllipse(
                    bIsDisk ? GetBrush(oColor) : Brushes.Transparent,
                    bIsDisk ? null : GetPen(oColor, DefaultPenThickness),
                    oVertexLocation, dRadius, dRadius
                    );

                oVertexDrawingHistory = bIsDisk ?

                    new DiskVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius)
                    :
                    new CircleVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            case VertexShape.Sphere:

                RadialGradientBrush oRadialGradientBrush =
                    new RadialGradientBrush();

                oRadialGradientBrush.GradientOrigin =
                    oRadialGradientBrush.Center = new Point(0.3, 0.3);

                GradientStopCollection oGradientStops =
                    oRadialGradientBrush.GradientStops;

                oGradientStops.Add( new GradientStop(
                    WpfGraphicsUtil.SetWpfColorAlpha(Colors.White, oColor.A),
                    0.0) );

                oGradientStops.Add( new GradientStop(oColor, 1.0) );

                WpfGraphicsUtil.FreezeIfFreezable(oRadialGradientBrush);

                oDrawingContext.DrawEllipse(oRadialGradientBrush, null,
                    oVertexLocation, dRadius, dRadius);

                oVertexDrawingHistory = new SphereVertexDrawingHistory(
                    oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            case VertexShape.Square:

                WpfGraphicsUtil.DrawPixelAlignedRectangle(oDrawingContext,
                    Brushes.Transparent, GetPen(oColor, DefaultPenThickness),
                    oVertexBounds);

                oVertexDrawingHistory = new SquareVertexDrawingHistory(oVertex,
                    oDrawingVisual, bDrawAsSelected, oVertexBounds);

                break;

            case VertexShape.SolidSquare:

                oDrawingContext.DrawRectangle(GetBrush(oColor), null,
                    oVertexBounds);

                oVertexDrawingHistory = new SolidSquareVertexDrawingHistory(
                    oVertex, oDrawingVisual, bDrawAsSelected, oVertexBounds);

                break;

            case VertexShape.Diamond:
            case VertexShape.SolidDiamond:

                Boolean bIsSolidDiamond = (eShape == VertexShape.SolidDiamond);

                PathGeometry oDiamond =
                    WpfPathGeometryUtil.GetDiamond(oVertexLocation, dRadius);

                oDrawingContext.DrawGeometry(

                    bIsSolidDiamond ? GetBrush(oColor) : Brushes.Transparent,

                    bIsSolidDiamond ? null :
                        GetPen(oColor, DefaultPenThickness),

                    oDiamond
                    );

                oVertexDrawingHistory = bIsSolidDiamond ?

                    new SolidDiamondVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius)
                    :
                    new DiamondVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            case VertexShape.Triangle:
            case VertexShape.SolidTriangle:

                Boolean bIsSolidTriangle =
                    (eShape == VertexShape.SolidTriangle);

                PathGeometry oTriangle =
                    WpfPathGeometryUtil.GetTriangle(oVertexLocation, dRadius);

                oDrawingContext.DrawGeometry(

                    bIsSolidTriangle ? GetBrush(oColor) : Brushes.Transparent,

                    bIsSolidTriangle ? null :
                        GetPen(oColor, DefaultPenThickness),

                    oTriangle
                    );

                oVertexDrawingHistory = bIsSolidTriangle ?

                    new SolidTriangleVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius)
                    :
                    new TriangleVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            case VertexShape.SolidTaperedDiamond:

                Geometry oTaperedDiamond =
                    WpfPathGeometryUtil.GetTaperedDiamond(
                        oVertexLocation, dRadius);

                // Note that as of August 2012, the tapered diamond shape is
                // used only for collapsed connector motifs.  Collapsed motifs
                // have an outline, so draw an outline here.

                oDrawingContext.DrawGeometry(GetBrush(oColor),

                    GetPen( CollapsedGroupDrawingManager
                        .GetCollapsedMotifOutlineColor(oColor.A),
                        DefaultPenThickness),

                    oTaperedDiamond);

                oVertexDrawingHistory =
                    new SolidTaperedDiamondVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            case VertexShape.SolidRoundedX:

                Geometry oRoundedX =
                    WpfPathGeometryUtil.GetRoundedX(
                        oVertexLocation, dRadius);

                // Note that as of August 2012, the rounded X shape is
                // used only for collapsed clique motifs.  Collapsed motifs
                // have an outline, so draw an outline here.

                oDrawingContext.DrawGeometry(GetBrush(oColor),

                    GetPen(CollapsedGroupDrawingManager
                        .GetCollapsedMotifOutlineColor(oColor.A),
                        DefaultPenThickness),

                    oRoundedX);

                oVertexDrawingHistory =
                    new SolidRoundedXVertexDrawingHistory(
                        oVertex, oDrawingVisual, bDrawAsSelected, dRadius);

                break;

            default:

                Debug.Assert(false);
                break;
        }

        if (sAnnotation != null)
        {
            oVertexLabelDrawer.DrawLabel(oDrawingContext,
                oGraphDrawingContext, oVertexDrawingHistory,

                CreateFormattedTextWithWrap(sAnnotation, oColor,
                    m_oFormattedTextManager.FontSize),

                oColor);
        }

        Debug.Assert(oVertexDrawingHistory != null);

        return (oVertexDrawingHistory);
    }
    TryDrawVertex
    (
        IVertex vertex,
        GraphDrawingContext graphDrawingContext,
        out VertexDrawingHistory vertexDrawingHistory
    )
    {
        AssertValid();

        vertexDrawingHistory = null;

        CheckDrawVertexArguments(vertex, graphDrawingContext);

        if (graphDrawingContext.GraphRectangleMinusMarginIsEmpty)
        {
            return (false);
        }

        // If the vertex is hidden, do nothing.

        VisibilityKeyValue eVisibility = GetVisibility(vertex);

        if (eVisibility == VisibilityKeyValue.Hidden)
        {
            return (false);
        }

        // Check whether the vertex represents a collapsed group and perform
        // collapsed group tasks if necessary.

        CollapsedGroupDrawingManager oCollapsedGroupDrawingManager =
            new CollapsedGroupDrawingManager();

        oCollapsedGroupDrawingManager.PreDrawVertex(vertex);

        // Check for a per-vertex label.

        Object oLabelAsObject;
        String sLabel = null;

        if ( vertex.TryGetValue(ReservedMetadataKeys.PerVertexLabel,
            typeof(String), out oLabelAsObject) )
        {
            sLabel = (String)oLabelAsObject;

            if ( String.IsNullOrEmpty(sLabel) )
            {
                sLabel = null;
            }
            else
            {
                sLabel = TruncateLabel(sLabel);
            }
        }

        Boolean bDrawAsSelected = GetDrawAsSelected(vertex);
        Point oLocation = WpfGraphicsUtil.PointFToWpfPoint(vertex.Location);
        DrawingVisualPlus oDrawingVisual = new DrawingVisualPlus();
        VertexShape eShape = GetShape(vertex);

        VertexLabelDrawer oVertexLabelDrawer =
            new VertexLabelDrawer(m_eLabelPosition, m_btBackgroundAlpha);

        using ( DrawingContext oDrawingContext = oDrawingVisual.RenderOpen() )
        {
            if (eShape == VertexShape.Label)
            {
                if (sLabel != null)
                {
                    // Draw the vertex as a label.

                    vertexDrawingHistory = DrawLabelShape(vertex,
                        graphDrawingContext, oDrawingContext, oDrawingVisual,
                        eVisibility, bDrawAsSelected, sLabel,
                        oCollapsedGroupDrawingManager);
                }
                else
                {
                    // Default to something usable.

                    eShape = VertexShape.Disk;
                }
            }
            else if (eShape == VertexShape.Image)
            {
                Object oImageSourceAsObject;

                if (vertex.TryGetValue(ReservedMetadataKeys.PerVertexImage,
                    typeof(ImageSource), out oImageSourceAsObject)
                    )
                {
                    // Draw the vertex as an image.

                    vertexDrawingHistory = DrawImageShape(vertex,
                        graphDrawingContext, oDrawingContext, oDrawingVisual,
                        eVisibility, bDrawAsSelected, sLabel,
                        (ImageSource)oImageSourceAsObject, oVertexLabelDrawer,
                        oCollapsedGroupDrawingManager);
                }
                else
                {
                    // Default to something usable.

                    eShape = VertexShape.Disk;
                }
            }

            if (vertexDrawingHistory == null)
            {
                // Draw the vertex as a simple shape.

                vertexDrawingHistory = DrawSimpleShape(vertex, eShape,
                    graphDrawingContext, oDrawingContext, oDrawingVisual,
                    eVisibility, bDrawAsSelected, sLabel, oVertexLabelDrawer,
                    oCollapsedGroupDrawingManager);
            }

            // Perform collapsed group tasks if necessary.

            oCollapsedGroupDrawingManager.PostDrawVertex(eShape,
                GetColor(vertex, eVisibility, bDrawAsSelected),
                graphDrawingContext, oDrawingContext, bDrawAsSelected,
                m_dGraphScale, oVertexLabelDrawer, m_oFormattedTextManager,
                vertexDrawingHistory);
        }

        return (true);
    }
    DrawLabelShape
    (
        IVertex oVertex,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        DrawingVisualPlus oDrawingVisual,
        VisibilityKeyValue eVisibility,
        Boolean bDrawAsSelected,
        String sLabel,
        CollapsedGroupDrawingManager oCollapsedGroupDrawingManager
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oDrawingVisual != null);
        Debug.Assert( !String.IsNullOrEmpty(sLabel) );
        Debug.Assert(oCollapsedGroupDrawingManager != null);
        AssertValid();

        // Figure out what colors to use.

        Color oOutlineColor;

        Color oTextColor = GetColor(oVertex, eVisibility, false);

        Color oFillColor = GetColor(oVertex, eVisibility,
            ReservedMetadataKeys.PerVertexLabelFillColor, m_oLabelFillColor,
            true);

        if (bDrawAsSelected)
        {
            // The outline color is always the selected color.

            oOutlineColor = m_oSelectedColor;

            // The text color is the default or per-vertex color with no alpha.

            oTextColor.A = 255;

            // The fill color is the default or per-vertex fill color with no
            // alpha.

            oFillColor.A = 255;
        }
        else
        {
            // The outline color is the default or per-vertex color with alpha.

            oOutlineColor = oTextColor;

            // The text color is the default or per-vertex color with alpha.

            // The fill color is the default or per-vertex fill color with
            // alpha.
        }

        Double dLabelFontSize = GetLabelFontSize(oVertex);

        FormattedText oFormattedText = CreateFormattedTextWithWrap(
            sLabel, oTextColor, dLabelFontSize);

        Rect oVertexRectangle = GetVertexRectangle(
            GetVertexLocation(oVertex), oFormattedText.Width,
            oFormattedText.Height);

        // Pad the text.

        Rect oVertexRectangleWithPadding = oVertexRectangle;
        Double dLabelPadding = GetLabelPadding(dLabelFontSize);

        oVertexRectangleWithPadding.Inflate(dLabelPadding,
            dLabelPadding * 0.7);

        if (m_oFormattedTextManager.Typeface.Style != FontStyles.Normal)
        {
            // This is a hack to move the right edge of the padded rectangle
            // to the right to adjust for wider italic text, which
            // FormattedText.Width does not account for.  What is the correct
            // way to do this?  It might involve the FormattedText.Overhang*
            // properties, but I'll be darned if I can understand how those
            // properties work.

            Double dItalicCompensation = dLabelFontSize / 7.0;
            oVertexRectangleWithPadding.Inflate(dItalicCompensation, 0);
            oVertexRectangleWithPadding.Offset(dItalicCompensation, 0);
        }

        // Move the vertex if it falls outside the graph rectangle.

        Double dOriginalVertexRectangleWithPaddingX =
            oVertexRectangleWithPadding.X;

        Double dOriginalVertexRectangleWithPaddingY =
            oVertexRectangleWithPadding.Y;

        MoveVertexIfNecessary(oVertex, oGraphDrawingContext,
            oCollapsedGroupDrawingManager, ref oVertexRectangleWithPadding);

        oVertexRectangle.Offset(

            oVertexRectangleWithPadding.X -
                dOriginalVertexRectangleWithPaddingX,

            oVertexRectangleWithPadding.Y -
                dOriginalVertexRectangleWithPaddingY
            );

        oDrawingVisual.SetEffect( GetRectangleEffect(
            oGraphDrawingContext, oOutlineColor) );

        // Draw the padded rectangle, then the text.

        WpfGraphicsUtil.DrawPixelAlignedRectangle(oDrawingContext,
            GetBrush(oFillColor), GetPen(oOutlineColor, DefaultPenThickness),
            oVertexRectangleWithPadding);

        oDrawingContext.DrawText(oFormattedText, oVertexRectangle.Location);

        // Return information about how the vertex was drawn.

        return ( new LabelVertexDrawingHistory(oVertex, oDrawingVisual,
            bDrawAsSelected, oVertexRectangleWithPadding) );
    }
    MoveVertexIfNecessary
    (
        IVertex oVertex,
        GraphDrawingContext oGraphDrawingContext,
        CollapsedGroupDrawingManager oCollapsedGroupDrawingManager,
        ref Rect oVertexBounds
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oCollapsedGroupDrawingManager != null);
        AssertValid();

        if (!m_bLimitVerticesToBounds ||
            oGraphDrawingContext.GraphRectangleMinusMarginIsEmpty)
        {
            // The vertex shouldn't be moved.

            return;
        }

        // First, assume that this is a normal vertex and not a vertex that
        // represents a collapsed group.  Move the vertex bounds within the
        // bounds of the graph rectangle's margin.

        Rect oMovedVertexBounds = WpfGraphicsUtil.MoveRectangleWithinBounds(
            oVertexBounds, oGraphDrawingContext.GraphRectangleMinusMargin,
            false);

        // If the vertex actually represents a collapsed group, move it further
        // if necessary to accomodate the additional elements that
        // CollapsedGroupmanager.PostDrawVertex() may draw on top of the
        // vertex.

        oCollapsedGroupDrawingManager.MoveVertexBoundsIfNecessary(
            oGraphDrawingContext, m_dGraphScale, ref oMovedVertexBounds);

        oVertex.Location = System.Drawing.PointF.Add( oVertex.Location,
            new System.Drawing.SizeF(
                (Single)(oMovedVertexBounds.X - oVertexBounds.X),
                (Single)(oMovedVertexBounds.Y - oVertexBounds.Y)
                ) );

        oVertexBounds = oMovedVertexBounds;
    }
    DrawImageShape
    (
        IVertex oVertex,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        DrawingVisualPlus oDrawingVisual,
        VisibilityKeyValue eVisibility,
        Boolean bDrawAsSelected,
        String sAnnotation,
        ImageSource oImageSource,
        VertexLabelDrawer oVertexLabelDrawer,
        CollapsedGroupDrawingManager oCollapsedGroupDrawingManager
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oDrawingVisual != null);
        Debug.Assert(oImageSource != null);
        Debug.Assert(oVertexLabelDrawer != null);
        Debug.Assert(oCollapsedGroupDrawingManager != null);
        AssertValid();

        // Move the vertex if it falls outside the graph rectangle.

        Rect oVertexRectangle = GetVertexRectangle(
            GetVertexLocation(oVertex), oImageSource.Width * m_dGraphScale,
            oImageSource.Height * m_dGraphScale);

        MoveVertexIfNecessary(oVertex, oGraphDrawingContext,
            oCollapsedGroupDrawingManager, ref oVertexRectangle);

        Byte btAlpha = 255;

        if (!bDrawAsSelected)
        {
            // Check for a non-opaque alpha value.

            btAlpha = GetAlpha(oVertex, eVisibility, btAlpha);
        }

        VertexDrawingHistory oVertexDrawingHistory =
            new ImageVertexDrawingHistory(oVertex, oDrawingVisual,
                bDrawAsSelected, oVertexRectangle);

        if (btAlpha > 0)
        {
            oDrawingContext.DrawImage(oImageSource, oVertexRectangle);

            Color oColor = GetColor(oVertex, eVisibility, bDrawAsSelected);

            oDrawingVisual.SetEffect( GetRectangleEffect(
                oGraphDrawingContext, oColor) );

            // Draw an outline rectangle.

            WpfGraphicsUtil.DrawPixelAlignedRectangle(oDrawingContext, null,
                GetPen(oColor, DefaultPenThickness), oVertexRectangle);

            if (btAlpha < 255)
            {
                // Real transparency can't be achieved with arbitrary images,
                // so simulate transparency by drawing on top of the image with
                // a translucent brush the same color as the graph's
                // background.
                //
                // This really isn't a good solution.  Is there are better way
                // to simulate transparency?

                Color oTranslucentColor = oGraphDrawingContext.BackColor;
                oTranslucentColor.A = (Byte)( (Byte)255 - btAlpha );

                oDrawingContext.DrawRectangle(
                    CreateFrozenSolidColorBrush(oTranslucentColor), null,
                        oVertexRectangle);
            }

            if (sAnnotation != null)
            {
                oVertexLabelDrawer.DrawLabel(oDrawingContext,
                    oGraphDrawingContext, oVertexDrawingHistory,

                    CreateFormattedTextWithWrap(sAnnotation, oColor,
                        m_oFormattedTextManager.FontSize),

                    oColor
                    );
            }
        }

        return (oVertexDrawingHistory);
    }