Provides access to objects needed for graph-drawing operations.
Inheritance: VisualizationBase
Exemple #1
0
        DrawBackground
        (
            IGraph oGraph,
            GraphDrawingContext oGraphDrawingContext
        )
        {
            Debug.Assert(oGraph != null);
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            // Draw the background color, followed by the background image if one
            // was specified.

            DrawingVisual oBackgroundDrawingVisual = new DrawingVisual();

            using (DrawingContext oDrawingContext =
                       oBackgroundDrawingVisual.RenderOpen())
            {
                oDrawingContext.DrawRectangle(
                    CreateFrozenSolidColorBrush(m_oBackColor), null,
                    oGraphDrawingContext.GraphRectangle);

                if (m_oBackgroundImage != null)
                {
                    oDrawingContext.DrawImage(m_oBackgroundImage,
                                              new Rect(new Size(m_oBackgroundImage.Width,
                                                                m_oBackgroundImage.Height)));
                }
            }

            m_oVisualCollection.Add(oBackgroundDrawingVisual);
        }
Exemple #2
0
        DrawEdge
        (
            IEdge oEdge,
            GraphDrawingContext oGraphDrawingContext
        )
        {
            Debug.Assert(oEdge != null);
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            EdgeDrawingHistory oEdgeDrawingHistory;

            if (m_oEdgeDrawer.TryDrawEdge(oEdge, oGraphDrawingContext,
                                          out oEdgeDrawingHistory))
            {
                Debug.Assert(oEdgeDrawingHistory != null);

                GetEdgeDrawingVisuals(oEdgeDrawingHistory).Children.Add(
                    oEdgeDrawingHistory.DrawingVisual);

                // Save the EdgeDrawingHistory object.

                oEdge.SetValue(ReservedMetadataKeys.EdgeDrawingHistory,
                               oEdgeDrawingHistory);
            }
        }
Exemple #3
0
        DrawVertex
        (
            IVertex oVertex,
            GraphDrawingContext oGraphDrawingContext
        )
        {
            Debug.Assert(oVertex != null);
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            VertexDrawingHistory oVertexDrawingHistory;

            if (m_oVertexDrawer.TryDrawVertex(oVertex, oGraphDrawingContext,
                                              out oVertexDrawingHistory))
            {
                Debug.Assert(oVertexDrawingHistory != null);

                DrawingVisual oVertexChildDrawingVisual =
                    oVertexDrawingHistory.DrawingVisual;

                m_oAllVertexDrawingVisuals.Children.Add(oVertexChildDrawingVisual);

                oVertex.SetValue(ReservedMetadataKeys.VertexDrawingHistory,
                                 oVertexDrawingHistory);

                // Save the vertex on the DrawingVisual for later retrieval.

                SaveVertexOnDrawingVisual(oVertex, oVertexChildDrawingVisual);
            }
        }
Exemple #4
0
        UndrawVertex
        (
            IVertex vertex,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(vertex != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            // Retrieve the VertexDrawingHistory object for the vertex, if one
            // exists.  (If the vertex was previously hidden, there won't be a
            // VertexDrawingHistory object for it.)

            VertexDrawingHistory oVertexDrawingHistory;

            if (TryGetVertexDrawingHistory(vertex, out oVertexDrawingHistory))
            {
                // Remove the VertexDrawingHistory object from the vertex.

                vertex.RemoveKey(ReservedMetadataKeys.VertexDrawingHistory);

                // Remove the vertex's DrawingVisual object, which will cause the
                // vertex to disappear.

                m_oAllVertexDrawingVisuals.Children.Remove(
                    oVertexDrawingHistory.DrawingVisual);
            }
        }
Exemple #5
0
        UndrawEdge
        (
            IEdge edge,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(edge != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            // Retrieve the EdgeDrawingHistory object for the edge, if one exists.
            // (If the edge was previously hidden, there won't be an
            // EdgeDrawingHistory object for it.)

            EdgeDrawingHistory oEdgeDrawingHistory;

            if (TryGetEdgeDrawingHistory(edge, out oEdgeDrawingHistory))
            {
                // Remove the EdgeDrawingHistory object from the edge.

                edge.RemoveKey(ReservedMetadataKeys.EdgeDrawingHistory);

                // Remove the edge's DrawingVisual object, which will cause the
                // edge to disappear.

                GetEdgeDrawingVisuals(oEdgeDrawingHistory).Children.Remove(
                    oEdgeDrawingHistory.DrawingVisual);
            }
        }
Exemple #6
0
        TryDrawGroupRectangles
        (
            IGraph graph,
            GraphDrawingContext graphDrawingContext,
            out Visual visual
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            visual = null;

            GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

            if (
                !GroupMetadataManager.TryGetGroupLayoutDrawingInfo(
                    graph, out oGroupLayoutDrawingInfo)
                ||
                oGroupLayoutDrawingInfo.PenWidth == 0
                )
            {
                return(false);
            }

            DrawingVisual oGroupRectangleDrawingVisual = new DrawingVisual();

            using (DrawingContext oDrawingContext =
                       oGroupRectangleDrawingVisual.RenderOpen())
            {
                // Note: Don't try to use an alpha value of anything except 255
                // for the rectangle colors.  The rectangles overlap, and
                // transparent overlapping rectangles would have uneven opacities.

                Color oColor = GetContrastingColor(graphDrawingContext, 255,
                                                   false);

                // Note that 1.0 is used where the GraphScale would normally be
                // used.  Group rectangles don't get scaled.

                Pen oPen = CreateFrozenPen(CreateFrozenSolidColorBrush(oColor),
                                           oGroupLayoutDrawingInfo.PenWidth * 1.0, DashStyles.Solid);

                foreach (GroupInfo oGroupInfo in
                         oGroupLayoutDrawingInfo.GroupsToDraw)
                {
                    Rect oGroupRectangle;

                    if (TryGetGroupRectangle(oGroupInfo, out oGroupRectangle))
                    {
                        WpfGraphicsUtil.DrawPixelAlignedRectangle(oDrawingContext,
                                                                  null, oPen, oGroupRectangle);
                    }
                }
            }

            visual = oGroupRectangleDrawingVisual;
            return(true);
        }
Exemple #7
0
        DrawNewVertex
        (
            IVertex newVertex,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(newVertex != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            DrawVertex(newVertex, graphDrawingContext);
        }
Exemple #8
0
        DrawNewEdge
        (
            IEdge newEdge,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(newEdge != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            DrawEdge(newEdge, graphDrawingContext);
        }
Exemple #9
0
        RedrawVertex
        (
            IVertex vertex,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(vertex != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            UndrawVertex(vertex, graphDrawingContext);
            DrawVertex(vertex, graphDrawingContext);
        }
Exemple #10
0
        RedrawEdge
        (
            IEdge edge,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(edge != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            UndrawEdge(edge, graphDrawingContext);
            DrawEdge(edge, graphDrawingContext);
        }
Exemple #11
0
        GetContrastingColor
        (
            GraphDrawingContext oGraphDrawingContext,
            Byte btAlpha,
            Boolean bUseMaximumContrast
        )
        {
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            return(WpfGraphicsUtil.SetWpfColorAlpha(
                       WpfGraphicsUtil.GetContrastingColor(
                           oGraphDrawingContext.BackColor,
                           bUseMaximumContrast), btAlpha));
        }
        CreateVisual
        (
            Point currentMouseLocation,
            Color backColor,
            VertexDrawer vertexDrawer
        )
        {
            Debug.Assert(vertexDrawer != null);
            Debug.Assert(m_bDragIsInProgress);
            AssertValid();

            // This method redraws the dragged vertices at an offset location, and
            // adds the resulting Visuals to a ContainerVisual.
            //
            // Figure out the offset.

            Double dOffsetX = currentMouseLocation.X - m_oMouseDownLocation.X;
            Double dOffsetY = currentMouseLocation.Y - m_oMouseDownLocation.Y;

            GraphDrawingContext oGraphDrawingContext = new GraphDrawingContext(
                m_oGraphRectangle, m_iMargin, backColor);

            ContainerVisual oContainerVisual = new ContainerVisual();

            foreach (IVertex oVertex in m_aoVertices)
            {
                System.Drawing.PointF oOriginalLocation =
                    GetOriginalVertexLocation(oVertex);

                oVertex.Location = new System.Drawing.PointF(
                    oOriginalLocation.X + (Single)dOffsetX,
                    oOriginalLocation.Y + (Single)dOffsetY
                    );

                VertexDrawingHistory oVertexDrawingHistory;

                if (vertexDrawer.TryDrawVertex(oVertex, oGraphDrawingContext,
                                               out oVertexDrawingHistory))
                {
                    oContainerVisual.Children.Add(
                        oVertexDrawingHistory.DrawingVisual);
                }
            }

            m_oVisual = oContainerVisual;

            return(m_oVisual);
        }
Exemple #13
0
        TryDrawCombinedIntergroupEdges
        (
            IGraph graph,
            GraphDrawingContext graphDrawingContext,
            out Visual visual
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            visual = null;

            GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

            if (!GroupMetadataManager.TryGetGroupLayoutDrawingInfo(
                    graph, out oGroupLayoutDrawingInfo))
            {
                return(false);
            }

            IEnumerable <IntergroupEdgeInfo> oCombinedIntergroupEdges =
                oGroupLayoutDrawingInfo.CombinedIntergroupEdges;

            if (oCombinedIntergroupEdges == null)
            {
                return(false);
            }

            DrawingVisual oCombinedIntergroupEdgeDrawingVisual =
                new DrawingVisual();

            using (DrawingContext oDrawingContext =
                       oCombinedIntergroupEdgeDrawingVisual.RenderOpen())
            {
                foreach (IntergroupEdgeInfo oCombinedIntergroupEdge in
                         oCombinedIntergroupEdges)
                {
                    DrawCombinedIntergroupEdge(oDrawingContext,
                                               graphDrawingContext, oCombinedIntergroupEdge,
                                               oGroupLayoutDrawingInfo);
                }
            }

            visual = oCombinedIntergroupEdgeDrawingVisual;
            return(true);
        }
Exemple #14
0
        DrawLabelBackground
        (
            DrawingContext oDrawingContext,
            GraphDrawingContext oGraphDrawingContext,
            FormattedText oFormattedText,
            Color oFormattedTextColor,
            Byte btBackgroundAlpha,
            Point oTextOrigin
        )
        {
            Debug.Assert(oDrawingContext != null);
            Debug.Assert(oGraphDrawingContext != null);
            Debug.Assert(oFormattedText != null);
            AssertValid();

            if (oFormattedText.Width == 0 || oFormattedText.Height == 0)
            {
                return;
            }

            // Note: Don't make the background any more opaque than the text, which
            // might be translucent itself.

            Color oBackgroundColor = WpfGraphicsUtil.SetWpfColorAlpha(
                oGraphDrawingContext.BackColor,
                Math.Min(btBackgroundAlpha, oFormattedTextColor.A));

            SolidColorBrush oBackgroundBrush = CreateFrozenSolidColorBrush(
                oBackgroundColor);

            Rect oBackgroundRectangle = WpfGraphicsUtil.GetFormattedTextBounds(
                oFormattedText, oTextOrigin);

            // Draw a rounded rectangle with a small amount of padding.

            const Int32 Padding = 1;
            const Int32 Radius  = 2;

            oBackgroundRectangle.Inflate(Padding, Padding);

            oDrawingContext.DrawRoundedRectangle(oBackgroundBrush, null,
                                                 oBackgroundRectangle, Radius, Radius);
        }
        DrawLabel
        (
            DrawingContext drawingContext,
            GraphDrawingContext graphDrawingContext,
            VertexDrawingHistory vertexDrawingHistory,
            FormattedText formattedText,
            Color formattedTextColor
        )
        {
            Debug.Assert(drawingContext != null);
            Debug.Assert(graphDrawingContext != null);
            Debug.Assert(vertexDrawingHistory != null);
            Debug.Assert(formattedText != null);
            AssertValid();

            DrawLabel(drawingContext, graphDrawingContext, vertexDrawingHistory,
                      GetLabelPosition(vertexDrawingHistory.Vertex), formattedText,
                      formattedTextColor, true);
        }
        StayWithinHorizontalBounds
        (
            Double dX,
            GraphDrawingContext oGraphDrawingContext,
            Double dLabelBoundsLeft,
            Double dLabelBoundsRight
        )
        {
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            Rect oGraphRectangleMinusMargin =
                oGraphDrawingContext.GraphRectangleMinusMargin;

            dX += Math.Max(0, oGraphRectangleMinusMargin.Left - dLabelBoundsLeft);

            dX -= Math.Max(0, dLabelBoundsRight - oGraphRectangleMinusMargin.Right);

            return(dX);
        }
Exemple #17
0
        TryDrawGroupLabels
        (
            IGraph graph,
            GraphDrawingContext graphDrawingContext,
            out Visual visual
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            visual = null;

            GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

            if (
                m_eLabelPosition == VertexLabelPosition.Nowhere
                ||
                !GroupMetadataManager.TryGetGroupLayoutDrawingInfo(graph,
                                                                   out oGroupLayoutDrawingInfo)
                )
            {
                return(false);
            }

            DrawingVisual oGroupLabelDrawingVisual = new DrawingVisual();

            using (DrawingContext oDrawingContext =
                       oGroupLabelDrawingVisual.RenderOpen())
            {
                foreach (GroupInfo oGroupInfo in
                         oGroupLayoutDrawingInfo.GroupsToDraw)
                {
                    DrawGroupLabel(oDrawingContext, graphDrawingContext,
                                   oGroupInfo);
                }
            }

            visual = oGroupLabelDrawingVisual;
            return(true);
        }
        StayWithinVerticalBounds
        (
            Double dY,
            GraphDrawingContext oGraphDrawingContext,
            Double dLabelBoundsTop,
            Double dLabelBoundsBottom
        )
        {
            Debug.Assert(oGraphDrawingContext != null);
            AssertValid();

            Rect oGraphRectangleMinusMargin =
                oGraphDrawingContext.GraphRectangleMinusMargin;

            dY += Math.Max(0, oGraphRectangleMinusMargin.Top - dLabelBoundsTop);

            dY -= Math.Max(0,
                           dLabelBoundsBottom - oGraphRectangleMinusMargin.Bottom);

            return(dY);
        }
    DrawLabelBackground
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        FormattedText oFormattedText,
        Color oFormattedTextColor,
        Byte btBackgroundAlpha,
        Point oTextOrigin
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oFormattedText != null);
        AssertValid();

        if (oFormattedText.Width == 0 || oFormattedText.Height == 0)
        {
            return;
        }

        // Note: Don't make the background any more opaque than the text, which
        // might be translucent itself.

        Color oBackgroundColor = WpfGraphicsUtil.SetWpfColorAlpha(
            oGraphDrawingContext.BackColor,
            Math.Min(btBackgroundAlpha, oFormattedTextColor.A) );

        SolidColorBrush oBackgroundBrush = CreateFrozenSolidColorBrush(
            oBackgroundColor);

        Rect oBackgroundRectangle = WpfGraphicsUtil.GetFormattedTextBounds(
            oFormattedText, oTextOrigin);

        // Draw a rounded rectangle with a small amount of padding.

        const Int32 Padding = 1;
        const Int32 Radius = 2;
        oBackgroundRectangle.Inflate(Padding, Padding);

        oDrawingContext.DrawRoundedRectangle(oBackgroundBrush, null,
            oBackgroundRectangle, Radius, Radius);
    }
    TryDrawSelfLoop
    (
        IVertex oVertex,
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        Color oColor,
        Double dWidth,
        Boolean bDrawArrow
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(dWidth >= 0);
        AssertValid();

        // Retrieve the information about how the vertex was drawn.

        VertexDrawingHistory oVertexDrawingHistory;

        if ( !TryGetVertexDrawingHistory(oVertex, out oVertexDrawingHistory) )
        {
            // The edge's vertex is hidden, so the edge should be hidden also.

            return (false);
        }

        // Determine the edge of the graph rectangle that is farthest from the
        // vertex.

        Point oVertexLocation = oVertexDrawingHistory.VertexLocation;

        RectangleEdge eFarthestGraphRectangleEdge =
            WpfGraphicsUtil.GetFarthestRectangleEdge(oVertexLocation,
            oGraphDrawingContext.GraphRectangle);

        // Get the point on the vertex at which to draw the self-loop.

        Point oSelfLoopEndpoint = oVertexDrawingHistory.GetSelfLoopEndpoint(
            eFarthestGraphRectangleEdge);

        DrawSelfLoopAt(oDrawingContext, oGraphDrawingContext, oColor, dWidth,
            oSelfLoopEndpoint, eFarthestGraphRectangleEdge, bDrawArrow);

        return (true);
    }
    DrawBezierLabel
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        FormattedText oFormattedText,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        PathGeometry oBezierCurve,
        Double dLabelOriginAsFractionOfEdgeLength,
        Double dEdgeLength,
        Double dBufferWidth,
        Color oLabelTextColor,
        Color oTranslucentRectangleColor,
        Double dFontSize
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oFormattedText != null);
        Debug.Assert(oBezierCurve != null);
        Debug.Assert(dLabelOriginAsFractionOfEdgeLength >= 0);
        Debug.Assert(dEdgeLength >= 0);
        Debug.Assert(dBufferWidth >= 0);
        Debug.Assert(dFontSize > 0);
        AssertValid();

        // This method uses a modified version of the technique described in
        // "Render Text On A Path With WPF," by Charles Petzold, at
        // http://msdn.microsoft.com/en-us/magazine/dd263097.aspx.

        if (oEdgeEndpoint2.X < oEdgeEndpoint1.X)
        {
            // Don't let text be drawn upside-down.

            WpfGraphicsUtil.SwapPoints(ref oEdgeEndpoint1, ref oEdgeEndpoint2);

            oBezierCurve = WpfPathGeometryUtil.ReverseQuadraticBezierCurve(
                oBezierCurve);
        }

        Double dTextWidth = oFormattedText.Width;
        Double dTextWidthToEdgeLengthRatio = dTextWidth / dEdgeLength;

        Double dLineOffset = 0;
        Point oOrigin = new Point(0, 0);

        String [] asLines =
            oFormattedText.Text.Split( new char[] {'\r', '\n'} );

        foreach (String sLine in asLines)
        {
            // The label characters will be drawn one by one using positions
            // computed by PathGeometry.GetPointAtFractionLength().  The first
            // character will be at dLabelOriginAsFractionOfEdgeLength.

            Double dFractionOfEdgeLength = dLabelOriginAsFractionOfEdgeLength;

            Boolean bUsedEllipses = false;

            foreach (Char c in sLine)
            {
                if (bUsedEllipses)
                {
                    break;
                }

                Point oPointAtFractionLength;
                Point oPointTangent;

                oBezierCurve.GetPointAtFractionLength(dFractionOfEdgeLength,
                    out oPointAtFractionLength, out oPointTangent);

                Double dCharacterAngleDegrees = MathUtil.RadiansToDegrees(
                    Math.Atan2(oPointTangent.Y, oPointTangent.X) );

                String sChar = c.ToString();

                if ( (oPointAtFractionLength - oEdgeEndpoint2).Length <=
                    6.0 * dBufferWidth)
                {
                    // There is probably not enough room for the rest of the
                    // string.  Terminate it with ellipses.  (The buffer width
                    // multiple was determined experimentally.)

                    bUsedEllipses = true;
                    sChar = "...";
                }

                FormattedText oCharacterFormattedText =
                    m_oFormattedTextManager.CreateFormattedText(
                        sChar, oLabelTextColor, dFontSize, m_dGraphScale);

                Double dCharacterWidth =
                    oCharacterFormattedText.WidthIncludingTrailingWhitespace;

                Double dCharacterHeight = oCharacterFormattedText.Height;

                // Apply a RotateTransform to make the character's base
                // approximately parallel to the Bezier curve's tangent, and a
                // TranslateTransform to vertically position the character.

                oDrawingContext.PushTransform( new TranslateTransform(
                    oPointAtFractionLength.X,

                    oPointAtFractionLength.Y - (oFormattedText.Height / 2.0)
                        + dLineOffset
                    ) );

                oDrawingContext.PushTransform( new RotateTransform(
                    dCharacterAngleDegrees, 0,
                    (oFormattedText.Height / 2.0) - dLineOffset
                    ) );

                // A translucent rectangle is drawn for each character.
                // Rounding errors cause the underlying edge to show through
                // faintly between the rectangles, which is a bug.  How to fix
                // this?

                Rect oTranslucentRectangle = new Rect( oOrigin,
                    new Size(dCharacterWidth, dCharacterHeight) );

                DrawTranslucentRectangle(oDrawingContext,
                    oTranslucentRectangle, oTranslucentRectangleColor);

                oDrawingContext.DrawText(oCharacterFormattedText, oOrigin);

                oDrawingContext.Pop();
                oDrawingContext.Pop();

                dFractionOfEdgeLength += dCharacterWidth / dEdgeLength;
            }

            dLineOffset += oFormattedText.Height / asLines.Length;
        }
    }
Exemple #22
0
        DrawGraph
        (
            IGraph graph,
            GraphDrawingContext graphDrawingContext
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(graphDrawingContext != null);
            AssertValid();

            // Implementation note:
            //
            // In a previous GDI+ implementation of this graph drawer, the edges
            // had to be drawn first to allow the vertices to cover the ends of the
            // edges.  That required a complex three-step drawing process: 1) allow
            // the vertex drawer to move each vertex if necessary to prevent the
            // vertex from falling outside the graph rectangle; 2) draw the edges
            // using the moved vertex locations; and 3) draw the vertices.
            //
            // This WPF implementation is simpler, for two reasons:
            //
            // 1. WPF uses retained-mode graphics, so covering the ends of the
            //    edges can be accomplished simply by adding
            //    m_oUnselectedEdgeDrawingVisuals to m_oVisualCollection before
            //    adding m_oAllVertexDrawingVisuals.  That means that the vertices
            //    can be drawn onto m_oAllVertexDrawingVisuals first, and the
            //    vertex drawer can move the vertices as necessary before drawing
            //    them.  A three-step process is no longer required.
            //
            // 2. The edges in this implementation don't actually need to be
            //    covered, because they terminate at the vertex boundaries instead
            //    of the vertex centers, as in the GDI+ implementation.

            m_oVisualCollection.Clear();

            DrawBackground(graph, graphDrawingContext);

            Visual oGroupVisual;

            if (m_oGroupDrawer.TryDrawGroupRectangles(graph, graphDrawingContext,
                                                      out oGroupVisual))
            {
                m_oVisualCollection.Add(oGroupVisual);
            }

            if (m_oGroupDrawer.TryDrawCombinedIntergroupEdges(
                    graph, graphDrawingContext, out oGroupVisual))
            {
                m_oVisualCollection.Add(oGroupVisual);
            }

            m_oAllVertexDrawingVisuals      = new DrawingVisual();
            m_oUnselectedEdgeDrawingVisuals = new DrawingVisual();
            m_oSelectedEdgeDrawingVisuals   = new DrawingVisual();

            // Draw the vertices after moving them if necessary.  Each vertex needs
            // to be individually hit-tested and possibly redrawn by
            // RedrawVertex(), so each vertex is put into its own DrawingVisual
            // that becomes a child of m_oAllVertexDrawingVisuals.
            //
            // Selected vertices should always be on top of unselected vertices, so
            // draw them first.  Note that this could also be accomplished by using
            // two DrawingVisual objects, m_oUnselectedVertexDrawingVisuals and
            // m_oSelectedVertexDrawingVisuals, in a manner similar to what is done
            // for edges.  However, vertices have to be hit-tested, and having two
            // DrawingVisual objects for two sets of vertices would complicate and
            // slow down hit-testing, so this solution is the simpler one.

            LinkedList <IVertex> oSelectedVertices = new LinkedList <IVertex>();

            foreach (IVertex oVertex in GetVerticesToDraw(graph))
            {
                if (m_oVertexDrawer.GetDrawAsSelected(oVertex))
                {
                    oSelectedVertices.AddLast(oVertex);
                }
                else
                {
                    DrawVertex(oVertex, graphDrawingContext);
                }
            }

            foreach (IVertex oVertex in oSelectedVertices)
            {
                DrawVertex(oVertex, graphDrawingContext);
            }

            oSelectedVertices = null;

            // Draw the edges.  The edges don't need to be hit-tested, but they
            // might need to be redrawn by RedrawEdge(), so each edge is put into
            // its own DrawingVisual that becomes a child of either
            // m_oUnselectedEdgeDrawingVisuals or m_oSelectedEdgeDrawingVisuals.

            foreach (IEdge oEdge in graph.Edges)
            {
                DrawEdge(oEdge, graphDrawingContext);
            }

            // Selected edges need to be drawn on top of all the vertices
            // (including selected vertices) to guarantee that they will be
            // visible; hence the addition order seen here.

            m_oVisualCollection.Add(m_oUnselectedEdgeDrawingVisuals);
            m_oVisualCollection.Add(m_oAllVertexDrawingVisuals);
            m_oVisualCollection.Add(m_oSelectedEdgeDrawingVisuals);

            if (m_oGroupDrawer.TryDrawGroupLabels(graph, graphDrawingContext,
                                                  out oGroupVisual))
            {
                m_oVisualCollection.Add(oGroupVisual);
            }
        }
    DrawGroupLabel
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        GroupInfo oGroupInfo
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oGroupInfo != null);
        Debug.Assert(m_eLabelPosition != VertexLabelPosition.Nowhere);
        AssertValid();

        String sLabel = oGroupInfo.Label;
        Rect oGroupRectangle;

        if (
            String.IsNullOrEmpty(sLabel)
            ||
            !TryGetGroupRectangle(oGroupInfo, out oGroupRectangle)
            )
        {
            return;
        }

        FormattedText oFormattedText =
            m_oFormattedTextManager.CreateFormattedText(sLabel,
                m_oLabelTextColor, m_dLabelScale);

        oFormattedText.MaxTextWidth = oGroupRectangle.Width;
        oFormattedText.MaxTextHeight = oGroupRectangle.Height;

        // The alignment needs to be set before the width and height of the
        // FormattedText are obtained.

        SetTextAlignment(oFormattedText, m_eLabelPosition);

        // You can't use FormattedText.Width to get the width of the actual
        // text when text wrapping is enabled (FormattedText.MaxTextWidth > 0).
        // Instead, use a method that takes wrapping into account.

        Double dLabelWidth =
            WpfGraphicsUtil.GetFormattedTextSize(oFormattedText).Width;

        Double dLabelHeight = oFormattedText.Height;

        Double dGroupRectangleWidth = oGroupRectangle.Width;
        Double dGroupRectangleHeight = oGroupRectangle.Height;
        Double dMaxTextWidth = oFormattedText.MaxTextWidth;

        Double dTextOffsetXForCenter =
            (dGroupRectangleWidth - dMaxTextWidth) / 2.0;

        Double dTextOffsetYForMiddle =
            (dGroupRectangleHeight - dLabelHeight) / 2.0;

        Double dTextOffsetYForBottom =
            dGroupRectangleHeight - dLabelHeight - LabelVerticalMargin;

        Point oTextOrigin = oGroupRectangle.Location;
        Double dTextOffsetX = 0;
        Double dTextOffsetY = 0;

        switch (m_eLabelPosition)
        {
            case VertexLabelPosition.TopLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = LabelVerticalMargin;
                break;

            case VertexLabelPosition.TopCenter:

                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = LabelVerticalMargin;

                break;

            case VertexLabelPosition.TopRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = LabelVerticalMargin;

                break;

            case VertexLabelPosition.MiddleLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.MiddleCenter:

                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.MiddleRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.BottomLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            case VertexLabelPosition.BottomCenter:


                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            case VertexLabelPosition.BottomRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            default:

                Debug.Assert(false);
                break;
        }

        oTextOrigin.Offset(dTextOffsetX, dTextOffsetY);

        DrawLabelBackground(oDrawingContext, oGraphDrawingContext,
            oFormattedText, m_oLabelTextColor, LabelBackgroundAlpha,
            oTextOrigin);

        oDrawingContext.DrawText(oFormattedText, oTextOrigin);
    }
    TryDrawGroupLabels
    (
        IGraph graph,
        GraphDrawingContext graphDrawingContext,
        out Visual visual
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        visual = null;

        GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

        if (
            m_eLabelPosition == VertexLabelPosition.Nowhere
            ||
            !GroupMetadataManager.TryGetGroupLayoutDrawingInfo(graph,
                out oGroupLayoutDrawingInfo)
            )
        {
            return (false);
        }

        DrawingVisual oGroupLabelDrawingVisual = new DrawingVisual();

        using ( DrawingContext oDrawingContext =
            oGroupLabelDrawingVisual.RenderOpen() )
        {
            foreach (GroupInfo oGroupInfo in
                oGroupLayoutDrawingInfo.GroupsToDraw)
            {
                DrawGroupLabel(oDrawingContext, graphDrawingContext,
                    oGroupInfo);
            }
        }

        visual = oGroupLabelDrawingVisual;
        return (true);
    }
    TryDrawGroupRectangles
    (
        IGraph graph,
        GraphDrawingContext graphDrawingContext,
        out Visual visual
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        visual = null;

        GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

        if (
            !GroupMetadataManager.TryGetGroupLayoutDrawingInfo(
                graph, out oGroupLayoutDrawingInfo)
            ||
            oGroupLayoutDrawingInfo.PenWidth == 0
            )
        {
            return (false);
        }

        DrawingVisual oGroupRectangleDrawingVisual = new DrawingVisual();

        using ( DrawingContext oDrawingContext =
            oGroupRectangleDrawingVisual.RenderOpen() )
        {
            // Note: Don't try to use an alpha value of anything except 255
            // for the rectangle colors.  The rectangles overlap, and
            // transparent overlapping rectangles would have uneven opacities.

            Color oColor = GetContrastingColor(graphDrawingContext, 255,
                false);

            // Note that 1.0 is used where the GraphScale would normally be
            // used.  Group rectangles don't get scaled.

            Pen oPen = CreateFrozenPen(CreateFrozenSolidColorBrush(oColor),
                oGroupLayoutDrawingInfo.PenWidth * 1.0, DashStyles.Solid);

            foreach (GroupInfo oGroupInfo in
                oGroupLayoutDrawingInfo.GroupsToDraw)
            {
                Rect oGroupRectangle;

                if ( TryGetGroupRectangle(oGroupInfo, out oGroupRectangle) )
                {
                    WpfGraphicsUtil.DrawPixelAlignedRectangle(oDrawingContext,
                        null, oPen, oGroupRectangle);
                }
            }
        }

        visual = oGroupRectangleDrawingVisual;
        return (true);
    }
    DrawStraightEdge
    (
        IEdge oEdge,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        Boolean bDrawAsSelected,
        Boolean bDrawArrow,
        Pen oPen,
        Color oColor,
        Double dWidth,
        VisibilityKeyValue eVisibility,
        String sLabel
    )
    {
        Debug.Assert(oEdge != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oPen != null);
        AssertValid();

        if (bDrawArrow)
        {
            // Draw the arrow and set the second endpoint to the center of the
            // flat end of the arrow.

            Double dArrowAngle = WpfGraphicsUtil.GetAngleBetweenPointsRadians(
                oEdgeEndpoint1, oEdgeEndpoint2);

            oEdgeEndpoint2 = DrawArrow(oDrawingContext, oEdgeEndpoint2,
                dArrowAngle, oColor, dWidth);
        }

        oDrawingContext.DrawLine(oPen, oEdgeEndpoint1, oEdgeEndpoint2);

        if ( !String.IsNullOrEmpty(sLabel) )
        {
            DrawLabel(oEdge, oDrawingContext, oGraphDrawingContext,
                bDrawAsSelected, oColor, eVisibility, oEdgeEndpoint1,
                oEdgeEndpoint2, null, new Point(), sLabel);
        }
    }
    RedrawVertex
    (
        IVertex vertex,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(vertex != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        UndrawVertex(vertex, graphDrawingContext);
        DrawVertex(vertex, graphDrawingContext);
    }
    RedrawEdge
    (
        IEdge edge,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(edge != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        UndrawEdge(edge, graphDrawingContext);
        DrawEdge(edge, graphDrawingContext);
    }
    DrawNewVertex
    (
        IVertex newVertex,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(newVertex != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        DrawVertex(newVertex, graphDrawingContext);
    }
    DrawNewEdge
    (
        IEdge newEdge,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(newEdge != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        DrawEdge(newEdge, graphDrawingContext);
    }
    UndrawVertex
    (
        IVertex vertex,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(vertex != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        // Retrieve the VertexDrawingHistory object for the vertex, if one
        // exists.  (If the vertex was previously hidden, there won't be a
        // VertexDrawingHistory object for it.)

        VertexDrawingHistory oVertexDrawingHistory;

        if ( TryGetVertexDrawingHistory(vertex, out oVertexDrawingHistory) )
        {
            // Remove the VertexDrawingHistory object from the vertex.

            vertex.RemoveKey(ReservedMetadataKeys.VertexDrawingHistory);

            // Remove the vertex's DrawingVisual object, which will cause the
            // vertex to disappear.

            m_oAllVertexDrawingVisuals.Children.Remove(
                oVertexDrawingHistory.DrawingVisual);
        }
    }
    UndrawEdge
    (
        IEdge edge,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(edge != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        // Retrieve the EdgeDrawingHistory object for the edge, if one exists.
        // (If the edge was previously hidden, there won't be an
        // EdgeDrawingHistory object for it.)

        EdgeDrawingHistory oEdgeDrawingHistory;

        if ( TryGetEdgeDrawingHistory(edge, out oEdgeDrawingHistory) )
        {
            // Remove the EdgeDrawingHistory object from the edge.

            edge.RemoveKey(ReservedMetadataKeys.EdgeDrawingHistory);

            // Remove the edge's DrawingVisual object, which will cause the
            // edge to disappear.

            GetEdgeDrawingVisuals(oEdgeDrawingHistory).Children.Remove(
                oEdgeDrawingHistory.DrawingVisual);
        }
    }
    DrawEdge
    (
        IEdge oEdge,
        GraphDrawingContext oGraphDrawingContext
    )
    {
        Debug.Assert(oEdge != null);
        Debug.Assert(oGraphDrawingContext != null);
        AssertValid();

        EdgeDrawingHistory oEdgeDrawingHistory;

        if ( m_oEdgeDrawer.TryDrawEdge(oEdge, oGraphDrawingContext,
            out oEdgeDrawingHistory) )
        {
            Debug.Assert(oEdgeDrawingHistory != null);

            GetEdgeDrawingVisuals(oEdgeDrawingHistory).Children.Add(
                oEdgeDrawingHistory.DrawingVisual);

            // Save the EdgeDrawingHistory object.

            oEdge.SetValue(ReservedMetadataKeys.EdgeDrawingHistory,
                oEdgeDrawingHistory);
        }
    }
    GetBezierControlPoint
    (
        GraphDrawingContext oGraphDrawingContext,
        Point oEndpoint1,
        Point oEndpoint2,
        Double dBezierDisplacementFactor
    )
    {
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(dBezierDisplacementFactor >= 0);
        AssertValid();

        // This method finds the midpoint of the straight line between the two
        // endpoints, then calculates a point that is displaced from the
        // midpoint at a right angle.  This is analagous to pulling a taut
        // string at its midpoint.
        //
        // The calculations are based on the anonymous post "How Can I
        // Calculate The Cartesian Coordinates Of A The Third Corner Of A
        // Triangle If I Have The Lengths Of All Three Sides And The
        // Coordinates Of The First Two Corners?" at
        // http://www.blurtit.com/q9044151.html.

        // Point a, the first endpoint, is one vertex of a right triangle.

        Double dPointAX = oEndpoint1.X;
        Double dPointAY = oEndpoint1.Y;

        // Point b, the midpoint of the line between the two endpoints, is
        // another vertex of the right triangle.  The angle at b is 90 degrees.

        Double dPointBX = dPointAX + (oEndpoint2.X - dPointAX) / 2.0;
        Double dPointBY = dPointAY + (oEndpoint2.Y - dPointAY) / 2.0;

        // Side C connects points a and b.

        Double dSideCLength = WpfGraphicsUtil.GetDistanceBetweenPoints(
            new Point(dPointBX, dPointBY), oEndpoint1);

        // Side A connects points b and c, where c is the point we need to
        // calculate.  Make the length of A, which is the displacement
        // mentioned above, proportional to the length of the line between the
        // two endpoints, so that a longer line gets displaced more than a
        // shorter line.

        Double dSideALength = dSideCLength * dBezierDisplacementFactor;

        // Calculate the angle of the line between the two endpoints.

        Double dAbsAtan2 = Math.Abs( Math.Atan2(
            Math.Max(oEndpoint2.Y, dPointAY) - Math.Min(oEndpoint2.Y, dPointAY),
            Math.Max(oEndpoint2.X, dPointAX) - Math.Min(oEndpoint2.X, dPointAX)
            ) );

        Rect oGraphRectangle = oGraphDrawingContext.GraphRectangle;

        if (dAbsAtan2 >= Math.PI / 4.0 && dAbsAtan2 <= 3.0 * Math.PI / 4.0)
        {
            // The line between the two endpoints is closer to vertical than
            // horizontal.
            //
            // As explained in the post mentioned above, the length of side A
            // can be negative or positive, depending on which direction point
            // c should be displaced.  The following adjustments to
            // dSideALength were determined experimentally.

            if (oEndpoint2.Y > dPointAY)
            {
                dSideALength *= -1.0;
            }

            if (dPointBX - oGraphRectangle.Left <
                oGraphRectangle.Right - dPointBX)
            {
                dSideALength *= -1.0;
            }
        }
        else
        {
            // The line between the two endpoints is closer to horizontal than
            // vertical.

            if (oEndpoint2.X < dPointAX)
            {
                dSideALength *= -1.0;
            }

            if (dPointBY - oGraphRectangle.Top <
                oGraphRectangle.Bottom - dPointBY)
            {
                dSideALength *= -1.0;
            }
        }

        // Calculate point c.

        Double dPointCX = dPointBX +
            ( dSideALength * (dPointAY - dPointBY) ) / dSideCLength;

        Double dPointCY = dPointBY +
            ( dSideALength * (dPointBX - dPointAX) ) / dSideCLength;

        // Don't let point c fall outside the graph's margins.

        return ( WpfGraphicsUtil.MovePointWithinBounds(
            new Point(dPointCX, dPointCY),
            oGraphDrawingContext.GraphRectangleMinusMargin) );
    }
    DrawPlusSign
    (
        VertexShape eVertexShape,
        Color oVertexColor,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        Double dGraphScale,
        VertexLabelDrawer oVertexLabelDrawer,
        FormattedTextManager oFormattedTextManager,
        VertexDrawingHistory oVertexDrawingHistory
    )
    {
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(dGraphScale >= GraphDrawer.MinimumGraphScale);
        Debug.Assert(dGraphScale <= GraphDrawer.MaximumGraphScale);
        Debug.Assert(oVertexLabelDrawer != null);
        Debug.Assert(oFormattedTextManager != null);
        Debug.Assert(oVertexDrawingHistory != null);
        AssertValid();

        Color oFillColor;

        switch (eVertexShape)
        {
            case VertexShape.Circle:
            case VertexShape.Square:
            case VertexShape.Diamond:
            case VertexShape.Triangle:

                // The fill color is the color of the background.  Adjust the
                // fill color for the opacity of the vertex.

                oFillColor = WpfGraphicsUtil.SetWpfColorAlpha(
                    oGraphDrawingContext.BackColor, oVertexColor.A);

                break;

            default:

                oFillColor = oVertexColor;
                break;
        }

        Color oContrastingColor =
            WpfGraphicsUtil.GetContrastingColor(oFillColor);

        // The font size used below was chosen so that it is large enough to be
        // easily readable, but small enough to fit within the smallest
        // collapsed group vertex created by this class.

        oVertexLabelDrawer.DrawLabel(oDrawingContext, oGraphDrawingContext,
            oVertexDrawingHistory, VertexLabelPosition.MiddleCenter,

            oFormattedTextManager.CreateFormattedText("+", oContrastingColor,
                15.0, dGraphScale),

            oContrastingColor, false);
    }
    TryDrawCombinedIntergroupEdges
    (
        IGraph graph,
        GraphDrawingContext graphDrawingContext,
        out Visual visual
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        visual = null;

        GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

        if ( !GroupMetadataManager.TryGetGroupLayoutDrawingInfo(
            graph, out oGroupLayoutDrawingInfo) )
        {
            return (false);
        }

        IEnumerable<IntergroupEdgeInfo> oCombinedIntergroupEdges =
            oGroupLayoutDrawingInfo.CombinedIntergroupEdges;

        if (oCombinedIntergroupEdges == null)
        {
            return (false);
        }

        DrawingVisual oCombinedIntergroupEdgeDrawingVisual =
            new DrawingVisual();

        using ( DrawingContext oDrawingContext =
            oCombinedIntergroupEdgeDrawingVisual.RenderOpen() )
        {
            foreach (IntergroupEdgeInfo oCombinedIntergroupEdge in
                oCombinedIntergroupEdges)
            {
                DrawCombinedIntergroupEdge(oDrawingContext,
                    graphDrawingContext, oCombinedIntergroupEdge,
                    oGroupLayoutDrawingInfo);
            }
        }

        visual = oCombinedIntergroupEdgeDrawingVisual;
        return (true);
    }
    PostDrawVertex
    (
        VertexShape vertexShape,
        Color vertexColor,
        GraphDrawingContext graphDrawingContext,
        DrawingContext drawingContext,
        Boolean drawAsSelected,
        Double graphScale,
        VertexLabelDrawer vertexLabelDrawer,
        FormattedTextManager formattedTextManager,
        VertexDrawingHistory vertexDrawingHistory
    )
    {
        Debug.Assert(graphDrawingContext != null);
        Debug.Assert(drawingContext != null);
        Debug.Assert(graphScale >= GraphDrawer.MinimumGraphScale);
        Debug.Assert(graphScale <= GraphDrawer.MaximumGraphScale);
        Debug.Assert(vertexLabelDrawer != null);
        Debug.Assert(formattedTextManager != null);
        Debug.Assert(vertexDrawingHistory != null);
        AssertValid();

        if (m_oCollapsedGroupVertex == null)
        {
            // The vertex that was drawn is not a collapsed group vertex.  Do
            // nothing.

            return;
        }

        if (m_oCollapsedGroupAttributes.Count == 0)
        {
            // The vertex that was drawn represents a collapsed group, but no
            // attributes were specified for it.  By default, such a vertex
            // gets a plus sign drawn on top of it.

            DrawPlusSign(vertexShape, vertexColor, graphDrawingContext,
                drawingContext, graphScale, vertexLabelDrawer,
                formattedTextManager, vertexDrawingHistory);
        }
        else
        {
            // Check whether this this is a collapsed motif.

            switch ( m_oCollapsedGroupAttributes.GetGroupType() )
            {
                case CollapsedGroupAttributeValues.FanMotifType:

                    DrawFanMotifFan(vertexColor, drawingContext,
                        drawAsSelected, graphScale, vertexDrawingHistory);

                    break;

                default:

                    // Do nothing.  This includes the D-connector motif case,
                    // which requires no post-drawing action.

                    break;
            }
        }
    }
    DrawCombinedIntergroupEdge
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        IntergroupEdgeInfo oCombinedIntergroupEdge,
        GroupLayoutDrawingInfo oGroupLayoutDrawingInfo
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oCombinedIntergroupEdge != null);
        Debug.Assert(oGroupLayoutDrawingInfo != null);
        AssertValid();

        Rect oGroupRectangle1, oGroupRectangle2;

        if (
            !TryGetGroupRectangle(

                oGroupLayoutDrawingInfo.GroupsToDraw[
                    oCombinedIntergroupEdge.Group1Index],

                out oGroupRectangle1)
            ||
            !TryGetGroupRectangle(

                oGroupLayoutDrawingInfo.GroupsToDraw[
                    oCombinedIntergroupEdge.Group2Index],

                out oGroupRectangle2)
            )
        {
            return;
        }

        Point oGroupRectangle1Center =
            WpfGraphicsUtil.GetRectCenter(oGroupRectangle1);

        Point oGroupRectangle2Center =
            WpfGraphicsUtil.GetRectCenter(oGroupRectangle2);

        Point oBezierControlPoint = GetBezierControlPoint(oGraphDrawingContext,
            oGroupRectangle1Center, oGroupRectangle2Center,
            CombinedIntergroupEdgeBezierDisplacementFactor
            );

        PathGeometry oBezierCurve =
            WpfPathGeometryUtil.GetQuadraticBezierCurve(oGroupRectangle1Center,
                oGroupRectangle2Center, oBezierControlPoint);

        Color oColor = GetContrastingColor(
            oGraphDrawingContext, CombinedIntergroupEdgeAlpha, true);

        Pen oPen = CreateFrozenPen(CreateFrozenSolidColorBrush(oColor),

            GetCombinedIntergroupEdgePenWidth(oCombinedIntergroupEdge) *
                this.GraphScale,

            DashStyles.Solid, PenLineCap.Round);

        oDrawingContext.DrawGeometry(null, oPen, oBezierCurve);
    }
    DrawLabel
    (
        IEdge oEdge,
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        Boolean bDrawAsSelected,
        Color oEdgeColor,
        VisibilityKeyValue eVisibility,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        PathGeometry oBezierCurve,
        Point oBezierControlPoint,
        String sLabel
    )
    {
        Debug.Assert(oEdge != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(sLabel != null);
        AssertValid();

        if (sLabel.Length == 0)
        {
            return;
        }

        sLabel = TruncateLabel(sLabel);

        Double dFontSize = GetLabelFontSize(oEdge);

        Color oLabelTextColor = GetLabelTextColor(oEdge, bDrawAsSelected,
            oEdgeColor, eVisibility);

        FormattedText oFormattedText =
            m_oFormattedTextManager.CreateFormattedText(sLabel,
                oLabelTextColor, dFontSize, m_dGraphScale);

        oFormattedText.Trimming = TextTrimming.CharacterEllipsis;

        // Don't let the FormattedText class break long lines when drawing
        // Bezier curves.  DrawBezierLabel(), which draws the label
        // character-by-character, is unable to tell where such line breaks
        // occur, because FormattedText doesn't expose this information.
        //
        // For consistency, do the same when drawing straight edges.

        oFormattedText.MaxLineCount = sLabel.Count(c => c == '\n') + 1;

        // The ends of the label text are between one and two "buffer units"
        // from the ends of the edge.  The buffer unit is the width of an
        // arbitrary character.

        Double dBufferWidth =
            m_oFormattedTextManager.CreateFormattedText("i", oLabelTextColor,
                dFontSize, m_dGraphScale).Width;

        Double dEdgeLength = WpfGraphicsUtil.GetDistanceBetweenPoints(
            oEdgeEndpoint1, oEdgeEndpoint2);

        Double dEdgeLengthMinusBuffers = dEdgeLength - 2 * dBufferWidth;

        if (dEdgeLengthMinusBuffers <= 0)
        {
            return;
        }

        // Determine where to draw the label text.

        Double dTextWidth = oFormattedText.Width;
        Double dLabelOriginAsFractionOfEdgeLength;

        if (dTextWidth > dEdgeLengthMinusBuffers)
        {
            // The label text should start one buffer unit from the first
            // endpoint, and terminate with ellipses approximately one buffer
            // length from the second endpoint.

            dLabelOriginAsFractionOfEdgeLength = dBufferWidth / dEdgeLength;

            oFormattedText.MaxTextWidth = dEdgeLengthMinusBuffers;
        }
        else
        {
            // The label should be centered along the edge's length.

            dLabelOriginAsFractionOfEdgeLength = 
                ( (dEdgeLength - dTextWidth) / 2.0 ) / dEdgeLength;
        }

        // Note: Don't make the translucent rectangle any more opaque than the
        // edge, which might be translucent itself.

        Color oTranslucentRectangleColor = WpfGraphicsUtil.SetWpfColorAlpha(
            oGraphDrawingContext.BackColor,
            Math.Min(LabelBackgroundAlpha, oEdgeColor.A)
            );

        if (this.ShouldDrawBezierCurve)
        {
            DrawBezierLabel(oDrawingContext, oGraphDrawingContext,
                oFormattedText, oEdgeEndpoint1, oEdgeEndpoint2, oBezierCurve,
                dLabelOriginAsFractionOfEdgeLength, dEdgeLength,
                dBufferWidth, oLabelTextColor, oTranslucentRectangleColor,
                dFontSize);
        }
        else
        {
            DrawStraightLabel(oDrawingContext, oGraphDrawingContext,
                oFormattedText, oEdgeEndpoint1, oEdgeEndpoint2,
                dLabelOriginAsFractionOfEdgeLength, dEdgeLength, dBufferWidth,
                oTranslucentRectangleColor);
        }
    }
    GetContrastingColor
    (
        GraphDrawingContext oGraphDrawingContext,
        Byte btAlpha,
        Boolean bUseMaximumContrast
    )
    {
        Debug.Assert(oGraphDrawingContext != null);
        AssertValid();

        return ( WpfGraphicsUtil.SetWpfColorAlpha(
            WpfGraphicsUtil.GetContrastingColor(
                oGraphDrawingContext.BackColor,
                bUseMaximumContrast), btAlpha) );
    }
    DrawGraph
    (
        Rect oGraphRectangle
    )
    {
        AssertValid();

        #if TRACE_LAYOUT_AND_DRAW
        Debug.WriteLine("NodeXLControl: DrawGraph(), oGraphRectangle = "
            + oGraphRectangle);
        #endif

        m_oLastGraphDrawingContext = new GraphDrawingContext(
            oGraphRectangle, m_oLayout.Margin, m_oGraphDrawer.BackColor);

        m_oGraphDrawer.DrawGraph(m_oGraph, m_oLastGraphDrawingContext);
    }
    MoveVertexBoundsIfNecessary
    (
        GraphDrawingContext graphDrawingContext,
        Double graphScale,
        ref Rect vertexBounds
    )
    {
        Debug.Assert(graphDrawingContext != null);
        Debug.Assert(graphScale >= GraphDrawer.MinimumGraphScale);
        Debug.Assert(graphScale <= GraphDrawer.MaximumGraphScale);
        AssertValid();

        // Additional elements are drawn only for the fan motif.

        String sType;

        if (
            m_oCollapsedGroupVertex != null
            &&
            m_oCollapsedGroupAttributes.TryGetValue(
                CollapsedGroupAttributeKeys.Type, out sType)
            &&
            sType == CollapsedGroupAttributeValues.FanMotifType
            )
        {
            // Move the fan bounds within the bounds of the graph rectangle's
            // margin.

            Rect oFanBounds = GetCircleSegment(vertexBounds,
                GetFanMotifAngleRadians(), graphScale).Bounds;

            Rect oMovedFanBounds = WpfGraphicsUtil.MoveRectangleWithinBounds(
                oFanBounds, graphDrawingContext.GraphRectangleMinusMargin,
                false);

            // Now move the vertex bounds by the same amount.

            vertexBounds = Rect.Offset(vertexBounds,
                oMovedFanBounds.Left - oFanBounds.Left,
                oMovedFanBounds.Top - oFanBounds.Top
                );
        }
    }
Exemple #43
0
        DrawCombinedIntergroupEdge
        (
            DrawingContext oDrawingContext,
            GraphDrawingContext oGraphDrawingContext,
            IntergroupEdgeInfo oCombinedIntergroupEdge,
            GroupLayoutDrawingInfo oGroupLayoutDrawingInfo
        )
        {
            Debug.Assert(oDrawingContext != null);
            Debug.Assert(oGraphDrawingContext != null);
            Debug.Assert(oCombinedIntergroupEdge != null);
            Debug.Assert(oGroupLayoutDrawingInfo != null);
            AssertValid();

            Rect oGroupRectangle1, oGroupRectangle2;

            if (
                !TryGetGroupRectangle(

                    oGroupLayoutDrawingInfo.GroupsToDraw[
                        oCombinedIntergroupEdge.Group1Index],

                    out oGroupRectangle1)
                ||
                !TryGetGroupRectangle(

                    oGroupLayoutDrawingInfo.GroupsToDraw[
                        oCombinedIntergroupEdge.Group2Index],

                    out oGroupRectangle2)
                )
            {
                return;
            }

            Point oGroupRectangle1Center =
                WpfGraphicsUtil.GetRectCenter(oGroupRectangle1);

            Point oGroupRectangle2Center =
                WpfGraphicsUtil.GetRectCenter(oGroupRectangle2);

            Point oBezierControlPoint = GetBezierControlPoint(oGraphDrawingContext,
                                                              oGroupRectangle1Center, oGroupRectangle2Center,
                                                              CombinedIntergroupEdgeBezierDisplacementFactor
                                                              );

            PathGeometry oBezierCurve =
                WpfPathGeometryUtil.GetQuadraticBezierCurve(oGroupRectangle1Center,
                                                            oGroupRectangle2Center, oBezierControlPoint);

            Color oColor = GetContrastingColor(
                oGraphDrawingContext, CombinedIntergroupEdgeAlpha, true);

            Pen oPen = CreateFrozenPen(CreateFrozenSolidColorBrush(oColor),

                                       GetCombinedIntergroupEdgePenWidth(oCombinedIntergroupEdge) *
                                       this.GraphScale,

                                       DashStyles.Solid, PenLineCap.Round);

            oDrawingContext.DrawGeometry(null, oPen, oBezierCurve);
        }
    DrawCurveThroughIntermediatePoints
    (
        IEdge oEdge,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        VertexDrawingHistory oVertex1DrawingHistory,
        VertexDrawingHistory oVertex2DrawingHistory,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        Pen oPen
    )
    {
        Debug.Assert(oEdge != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oVertex1DrawingHistory != null);
        Debug.Assert(oVertex2DrawingHistory != null);
        Debug.Assert(oPen != null);
        AssertValid();

        // Note: Don't attempt to draw an arrow in this case.

        // Create a list of intermediate points, excluding those that fall
        // within the vertex bounds.  An edge always terminates on the vertex
        // bounds, so we don't want it venturing into the vertex itself.

        List<Point> oCurvePoints = FilterIntermediatePoints(oEdge,
            oVertex1DrawingHistory, oVertex2DrawingHistory);

        Int32 iCurvePoints = oCurvePoints.Count;

        if (iCurvePoints == 0)
        {
            // Just draw a straight line.

            oDrawingContext.DrawLine(oPen, oEdgeEndpoint1, oEdgeEndpoint2);
            return;
        }

        #if false
        // Draw intermediate points for testing.

        foreach (Point oPoint in oCurvePoints)
        {
            oDrawingContext.DrawEllipse(Brushes.Green, null, oPoint, 2, 2);
        }
        #endif

        // The endpoints were originally calculated as if the edge was a
        // straight line between the two vertices.  Recalculate them so they
        // connect more smoothly to the adjacent intermediate curve points.

        oVertex1DrawingHistory.GetEdgeEndpoint(oCurvePoints[0],
            out oEdgeEndpoint1);

        oVertex2DrawingHistory.GetEdgeEndpoint(oCurvePoints[iCurvePoints - 1],
            out oEdgeEndpoint2);

        oCurvePoints.Insert(0, oEdgeEndpoint1);
        oCurvePoints.Add(oEdgeEndpoint2);

        PathGeometry oCurveThroughPoints =
            WpfPathGeometryUtil.GetCurveThroughPoints(oCurvePoints, 0.5,
                CurveThroughIntermediatePointsTolerance);

        oDrawingContext.DrawGeometry(null, oPen, oCurveThroughPoints);

        // Note: Don't attempt to draw a label in this case.
    }
Exemple #45
0
        DrawGroupLabel
        (
            DrawingContext oDrawingContext,
            GraphDrawingContext oGraphDrawingContext,
            GroupInfo oGroupInfo
        )
        {
            Debug.Assert(oDrawingContext != null);
            Debug.Assert(oGraphDrawingContext != null);
            Debug.Assert(oGroupInfo != null);
            Debug.Assert(m_eLabelPosition != VertexLabelPosition.Nowhere);
            AssertValid();

            String sLabel = oGroupInfo.Label;
            Rect   oGroupRectangle;

            if (
                String.IsNullOrEmpty(sLabel)
                ||
                !TryGetGroupRectangle(oGroupInfo, out oGroupRectangle)
                )
            {
                return;
            }

            FormattedText oFormattedText =
                m_oFormattedTextManager.CreateFormattedText(sLabel,
                                                            m_oLabelTextColor, m_dLabelScale);

            oFormattedText.MaxTextWidth  = oGroupRectangle.Width;
            oFormattedText.MaxTextHeight = oGroupRectangle.Height;

            // The alignment needs to be set before the width and height of the
            // FormattedText are obtained.

            SetTextAlignment(oFormattedText, m_eLabelPosition);

            // You can't use FormattedText.Width to get the width of the actual
            // text when text wrapping is enabled (FormattedText.MaxTextWidth > 0).
            // Instead, use a method that takes wrapping into account.

            Double dLabelWidth =
                WpfGraphicsUtil.GetFormattedTextSize(oFormattedText).Width;

            Double dLabelHeight = oFormattedText.Height;

            Double dGroupRectangleWidth  = oGroupRectangle.Width;
            Double dGroupRectangleHeight = oGroupRectangle.Height;
            Double dMaxTextWidth         = oFormattedText.MaxTextWidth;

            Double dTextOffsetXForCenter =
                (dGroupRectangleWidth - dMaxTextWidth) / 2.0;

            Double dTextOffsetYForMiddle =
                (dGroupRectangleHeight - dLabelHeight) / 2.0;

            Double dTextOffsetYForBottom =
                dGroupRectangleHeight - dLabelHeight - LabelVerticalMargin;

            Point  oTextOrigin  = oGroupRectangle.Location;
            Double dTextOffsetX = 0;
            Double dTextOffsetY = 0;

            switch (m_eLabelPosition)
            {
            case VertexLabelPosition.TopLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = LabelVerticalMargin;
                break;

            case VertexLabelPosition.TopCenter:

                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = LabelVerticalMargin;

                break;

            case VertexLabelPosition.TopRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = LabelVerticalMargin;

                break;

            case VertexLabelPosition.MiddleLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.MiddleCenter:

                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.MiddleRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForMiddle;

                break;

            case VertexLabelPosition.BottomLeft:

                dTextOffsetX = LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            case VertexLabelPosition.BottomCenter:


                dTextOffsetX = dTextOffsetXForCenter;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            case VertexLabelPosition.BottomRight:

                dTextOffsetX = -LabelHorizontalMargin;
                dTextOffsetY = dTextOffsetYForBottom;

                break;

            default:

                Debug.Assert(false);
                break;
            }

            oTextOrigin.Offset(dTextOffsetX, dTextOffsetY);

            DrawLabelBackground(oDrawingContext, oGraphDrawingContext,
                                oFormattedText, m_oLabelTextColor, LabelBackgroundAlpha,
                                oTextOrigin);

            oDrawingContext.DrawText(oFormattedText, oTextOrigin);
        }
    DrawStraightLabel
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        FormattedText oFormattedText,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        Double dLabelOriginAsFractionOfEdgeLength,
        Double dEdgeLength,
        Double dBufferWidth,
        Color oTranslucentRectangleColor
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oFormattedText != null);
        Debug.Assert(dLabelOriginAsFractionOfEdgeLength >= 0);
        Debug.Assert(dEdgeLength >= 0);
        Debug.Assert(dBufferWidth >= 0);
        AssertValid();

        if (oEdgeEndpoint2.X < oEdgeEndpoint1.X)
        {
            // Don't let text be drawn upside-down.

            WpfGraphicsUtil.SwapPoints(ref oEdgeEndpoint1, ref oEdgeEndpoint2);
        }

        // To avoid trigonometric calculations, use a RotateTransform to make
        // the edge look as if it is horizontal, with oEdgeEndpoint2 to the
        // right of oEdgeEndpoint1.

        Double dEdgeAngleDegrees = MathUtil.RadiansToDegrees(
            WpfGraphicsUtil.GetAngleBetweenPointsRadians(
                oEdgeEndpoint1, oEdgeEndpoint2) );

        RotateTransform oRotateTransform = new RotateTransform(
            dEdgeAngleDegrees, oEdgeEndpoint1.X, oEdgeEndpoint1.Y);

        oRotateTransform.Angle = -dEdgeAngleDegrees;
        oDrawingContext.PushTransform(oRotateTransform);

        Double dTextWidth = oFormattedText.Width;
        Double dEdgeLengthMinusBuffers = dEdgeLength - 2 * dBufferWidth;

        Point oLabelOrigin = oEdgeEndpoint1;

        // The text should be vertically centered on the edge.

        oLabelOrigin.Offset(dLabelOriginAsFractionOfEdgeLength * dEdgeLength,
            -oFormattedText.Height / 2.0);

        // Determine where to draw a translucent rectangle behind the text.
        // The translucent rectangle serves to obscure, but not completely
        // hide, the underlying edge.

        Rect oTranslucentRectangle;

        if (dTextWidth > dEdgeLengthMinusBuffers)
        {
            // The translucent rectangle should be the same width as the text.

            oTranslucentRectangle = new Rect( oLabelOrigin,
                new Size(dEdgeLengthMinusBuffers, oFormattedText.Height) );
        }
        else
        {
            // The label is centered along the edge's length.

            // The translucent rectangle should extend between zero and one
            // buffer units beyond the ends of the text.  This provides a
            // margin between the text and the unobscured edge, if there is
            // enough space.

            oTranslucentRectangle = new Rect( oLabelOrigin,

                new Size(
                    Math.Min(dTextWidth + 2 * dBufferWidth,
                        dEdgeLengthMinusBuffers),

                    oFormattedText.Height)
                );

            oTranslucentRectangle.Offset(-dBufferWidth, 0);
        }

        DrawTranslucentRectangle(oDrawingContext, oTranslucentRectangle,
            oTranslucentRectangleColor);

        oDrawingContext.DrawText(oFormattedText, oLabelOrigin);

        oDrawingContext.Pop();
    }
    DrawGraph
    (
        IGraph graph,
        GraphDrawingContext graphDrawingContext
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        // Implementation note:
        //
        // In a previous GDI+ implementation of this graph drawer, the edges
        // had to be drawn first to allow the vertices to cover the ends of the
        // edges.  That required a complex three-step drawing process: 1) allow
        // the vertex drawer to move each vertex if necessary to prevent the
        // vertex from falling outside the graph rectangle; 2) draw the edges
        // using the moved vertex locations; and 3) draw the vertices.
        //
        // This WPF implementation is simpler, for two reasons:
        //
        // 1. WPF uses retained-mode graphics, so covering the ends of the
        //    edges can be accomplished simply by adding
        //    m_oUnselectedEdgeDrawingVisuals to m_oVisualCollection before
        //    adding m_oAllVertexDrawingVisuals.  That means that the vertices
        //    can be drawn onto m_oAllVertexDrawingVisuals first, and the
        //    vertex drawer can move the vertices as necessary before drawing
        //    them.  A three-step process is no longer required.
        //
        // 2. The edges in this implementation don't actually need to be
        //    covered, because they terminate at the vertex boundaries instead
        //    of the vertex centers, as in the GDI+ implementation.

        m_oVisualCollection.Clear();

        DrawBackground(graph, graphDrawingContext);

        Visual oGroupVisual;

        if ( m_oGroupDrawer.TryDrawGroupRectangles(graph, graphDrawingContext,
            out oGroupVisual) )
        {
            m_oVisualCollection.Add(oGroupVisual);
        }

        if ( m_oGroupDrawer.TryDrawCombinedIntergroupEdges(
            graph, graphDrawingContext, out oGroupVisual) )
        {
            m_oVisualCollection.Add(oGroupVisual);
        }

        m_oAllVertexDrawingVisuals = new DrawingVisual();
        m_oUnselectedEdgeDrawingVisuals = new DrawingVisual();
        m_oSelectedEdgeDrawingVisuals = new DrawingVisual();

        // Draw the vertices after moving them if necessary.  Each vertex needs
        // to be individually hit-tested and possibly redrawn by
        // RedrawVertex(), so each vertex is put into its own DrawingVisual
        // that becomes a child of m_oAllVertexDrawingVisuals.
        //
        // Selected vertices should always be on top of unselected vertices, so
        // draw them first.  Note that this could also be accomplished by using
        // two DrawingVisual objects, m_oUnselectedVertexDrawingVisuals and
        // m_oSelectedVertexDrawingVisuals, in a manner similar to what is done
        // for edges.  However, vertices have to be hit-tested, and having two
        // DrawingVisual objects for two sets of vertices would complicate and
        // slow down hit-testing, so this solution is the simpler one.

        LinkedList<IVertex> oSelectedVertices = new LinkedList<IVertex>();

        foreach ( IVertex oVertex in GetVerticesToDraw(graph) )
        {
            if ( m_oVertexDrawer.GetDrawAsSelected(oVertex) )
            {
                oSelectedVertices.AddLast(oVertex);
            }
            else
            {
                DrawVertex(oVertex, graphDrawingContext);
            }
        }

        foreach (IVertex oVertex in oSelectedVertices)
        {
            DrawVertex(oVertex, graphDrawingContext);
        }

        oSelectedVertices = null;

        // Draw the edges.  The edges don't need to be hit-tested, but they
        // might need to be redrawn by RedrawEdge(), so each edge is put into
        // its own DrawingVisual that becomes a child of either
        // m_oUnselectedEdgeDrawingVisuals or m_oSelectedEdgeDrawingVisuals.

        foreach (IEdge oEdge in graph.Edges)
        {
            DrawEdge(oEdge, graphDrawingContext);
        }

        // Selected edges need to be drawn on top of all the vertices
        // (including selected vertices) to guarantee that they will be
        // visible; hence the addition order seen here.

        m_oVisualCollection.Add(m_oUnselectedEdgeDrawingVisuals);
        m_oVisualCollection.Add(m_oAllVertexDrawingVisuals);
        m_oVisualCollection.Add(m_oSelectedEdgeDrawingVisuals);

        if ( m_oGroupDrawer.TryDrawGroupLabels(graph, graphDrawingContext,
            out oGroupVisual) )
        {
            m_oVisualCollection.Add(oGroupVisual);
        }
    }
    TryDrawEdge
    (
        IEdge edge,
        GraphDrawingContext graphDrawingContext,
        out EdgeDrawingHistory edgeDrawingHistory
    )
    {
        Debug.Assert(edge != null);
        Debug.Assert(graphDrawingContext != null);
        AssertValid();

        edgeDrawingHistory = null;

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

        // If the edge is hidden, do nothing.

        VisibilityKeyValue eVisibility = GetVisibility(edge);

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

        DrawingVisual oDrawingVisual = new DrawingVisual();

        using ( DrawingContext oDrawingContext = oDrawingVisual.RenderOpen() )
        {
            Boolean bDrawAsSelected = GetDrawAsSelected(edge);
            Color oColor = GetColor(edge, eVisibility, bDrawAsSelected);
            Double dWidth = GetWidth(edge);
            DashStyle oDashStyle = GetDashStyle(edge, dWidth, bDrawAsSelected);

            Boolean bDrawArrow =
                (m_bDrawArrowOnDirectedEdge && edge.IsDirected);

            IVertex oVertex1 = edge.Vertex1;
            IVertex oVertex2 = edge.Vertex2;

            if (edge.IsSelfLoop)
            {
                if ( !TryDrawSelfLoop(oVertex1, oDrawingContext,
                    graphDrawingContext, oColor, dWidth, bDrawArrow) )
                {
                    // The edge's vertex is hidden, so the edge should be
                    // hidden also.

                    return (false);
                }
            }
            else
            {
                VertexDrawingHistory oVertex1DrawingHistory,
                    oVertex2DrawingHistory;

                Point oEdgeEndpoint1, oEdgeEndpoint2;

                if (
                    !TryGetVertexInformation(oVertex1, oVertex2,
                        out oVertex1DrawingHistory, out oVertex2DrawingHistory,
                        out oEdgeEndpoint1, out oEdgeEndpoint2)
                    ||
                    oEdgeEndpoint1 == oEdgeEndpoint2
                    )
                {
                    // One of the edge's vertices is hidden, so the edge should
                    // be hidden also, or the edge has zero length.

                    return (false);
                }

                Pen oPen = GetPen(oColor, dWidth, oDashStyle);

                Object oLabelAsObject;
                String sLabel = null;

                if ( edge.TryGetValue(ReservedMetadataKeys.PerEdgeLabel,
                    typeof(String), out oLabelAsObject)
                    && oLabelAsObject != null)
                {
                    sLabel = (String)oLabelAsObject;
                }

                if (m_eCurveStyle ==
                    EdgeCurveStyle.CurveThroughIntermediatePoints)
                {
                    DrawCurveThroughIntermediatePoints(
                        edge, graphDrawingContext, oDrawingContext,
                        oVertex1DrawingHistory, oVertex2DrawingHistory,
                        oEdgeEndpoint1, oEdgeEndpoint2, oPen);
                }
                else if (this.ShouldDrawBezierCurve)
                {
                    DrawBezierCurve(
                        edge, graphDrawingContext, oDrawingContext,
                        oEdgeEndpoint1, oEdgeEndpoint2, bDrawAsSelected,
                        bDrawArrow, oPen, oColor, dWidth, eVisibility, sLabel);
                }
                else
                {
                    DrawStraightEdge(
                        edge, graphDrawingContext, oDrawingContext,
                        oEdgeEndpoint1, oEdgeEndpoint2, bDrawAsSelected,
                        bDrawArrow, oPen, oColor, dWidth, eVisibility, sLabel);
                }
            }

            // Retain information about the edge that was drawn.

            edgeDrawingHistory = new EdgeDrawingHistory(
                edge, oDrawingVisual, bDrawAsSelected, dWidth);

            return (true);
        }
    }
        DrawLabel
        (
            DrawingContext drawingContext,
            GraphDrawingContext graphDrawingContext,
            VertexDrawingHistory vertexDrawingHistory,
            VertexLabelPosition labelPosition,
            FormattedText formattedText,
            Color formattedTextColor,
            Boolean drawBackground
        )
        {
            Debug.Assert(drawingContext != null);
            Debug.Assert(graphDrawingContext != null);
            Debug.Assert(vertexDrawingHistory != null);
            Debug.Assert(formattedText != null);
            AssertValid();

            if (labelPosition == VertexLabelPosition.Nowhere)
            {
                return;
            }

            // The alignment needs to be set before the width and height of the
            // FormattedText are obtained.

            SetTextAlignment(formattedText, labelPosition);

            // You can't use FormattedText.Width to get the width of the actual
            // text when text wrapping is enabled (FormattedText.MaxTextWidth > 0).
            // Instead, use a method that takes wrapping into account.

            Double dLabelWidth = WpfGraphicsUtil.GetFormattedTextSize(
                formattedText).Width;

            Double dLabelHeight = formattedText.Height;

            // This is the point where the label will be drawn using
            // DrawingContext.Draw().  It initially assumes a text height of zero,
            // a margin of zero, and no adjustment for alignment (see
            // dAdjustmentForTextAlignmentX below), but this will be modified
            // appropriately within the switch statement below.

            Point  oDraw  = vertexDrawingHistory.GetLabelLocation(labelPosition);
            Double dDrawX = oDraw.X;
            Double dDrawY = oDraw.Y;

            // These are the left and right bounds of the rectangle where the text
            // will actually appear.

            Double dLabelBoundsLeft  = 0;
            Double dLabelBoundsRight = 0;

            AdjustForTextAlignment(dLabelWidth, dLabelHeight, labelPosition,
                                   formattedText, ref dDrawX, ref dDrawY, ref dLabelBoundsLeft,
                                   ref dLabelBoundsRight);

            // Don't let the text exceed the bounds of the graph rectangle.

            dDrawX = StayWithinHorizontalBounds(
                dDrawX, graphDrawingContext, dLabelBoundsLeft, dLabelBoundsRight);

            Double dLabelBoundsTop    = dDrawY;
            Double dLabelBoundsBottom = dDrawY + dLabelHeight;

            dDrawY = StayWithinVerticalBounds(
                dDrawY, graphDrawingContext, dLabelBoundsTop, dLabelBoundsBottom);

            if (drawBackground)
            {
                dLabelBoundsLeft = StayWithinHorizontalBounds(
                    dLabelBoundsLeft, graphDrawingContext,
                    dLabelBoundsLeft, dLabelBoundsRight);

                DrawLabelBackground(drawingContext, graphDrawingContext,
                                    formattedText, formattedTextColor, m_btBackgroundAlpha,
                                    new Point(dLabelBoundsLeft, dDrawY));
            }

            drawingContext.DrawText(formattedText, new Point(dDrawX, dDrawY));
        }
    DrawSelfLoopAt
    (
        DrawingContext oDrawingContext,
        GraphDrawingContext oGraphDrawingContext,
        Color oColor,
        Double dWidth,
        Point oSelfLoopEndpoint,
        RectangleEdge eFarthestGraphRectangleEdge,
        Boolean bDrawArrow
    )
    {
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(dWidth >= 0);
        AssertValid();

        // The self-loop is drawn as a circle.  Figure out the location of the
        // circle's center and the tip of the arrow, if there is an arrow.

        Double dCircleX, dCircleY, dArrowTipX, dArrowTipY, dArrowAngle;

        dCircleX = dArrowTipX = oSelfLoopEndpoint.X;
        dCircleY = dArrowTipY = oSelfLoopEndpoint.Y;
        Double dSelfLoopCircleDiameter = 2.0 * SelfLoopCircleRadius;
        dArrowAngle = 0;

        switch (eFarthestGraphRectangleEdge)
        {
            case RectangleEdge.Top:

                dCircleY -= SelfLoopCircleRadius;
                dArrowTipY -= dSelfLoopCircleDiameter;
                break;

            case RectangleEdge.Left:

                dCircleX -= SelfLoopCircleRadius;
                dArrowTipX -= dSelfLoopCircleDiameter;
                dArrowAngle = Math.PI / 2.0;  // (90 degrees.)
                break;

            case RectangleEdge.Right:

                dCircleX += SelfLoopCircleRadius;
                dArrowTipX += dSelfLoopCircleDiameter;
                dArrowAngle = -Math.PI / 2.0;  // (-90 degrees.)
                break;

            case RectangleEdge.Bottom:

                dCircleY += SelfLoopCircleRadius;
                dArrowTipY += dSelfLoopCircleDiameter;
                dArrowAngle = Math.PI;  // (180 degrees.)
                break;

            default:

                Debug.Assert(false);
                break;
        }

        oDrawingContext.DrawEllipse(null, GetPen(oColor, dWidth),
            new Point(dCircleX, dCircleY), SelfLoopCircleRadius,
            SelfLoopCircleRadius);

        if (bDrawArrow)
        {
            // Rotate the arrow slightly to adjust to the circular shape of the
            // edge connected to it.

            dArrowAngle += Math.PI / 13.0;

            DrawArrow(oDrawingContext, new Point(dArrowTipX, dArrowTipY),
                dArrowAngle, oColor, dWidth);
        }
    }
Exemple #51
0
        GetBezierControlPoint
        (
            GraphDrawingContext oGraphDrawingContext,
            Point oEndpoint1,
            Point oEndpoint2,
            Double dBezierDisplacementFactor
        )
        {
            Debug.Assert(oGraphDrawingContext != null);
            Debug.Assert(dBezierDisplacementFactor >= 0);
            AssertValid();

            // This method finds the midpoint of the straight line between the two
            // endpoints, then calculates a point that is displaced from the
            // midpoint at a right angle.  This is analagous to pulling a taut
            // string at its midpoint.
            //
            // The calculations are based on the anonymous post "How Can I
            // Calculate The Cartesian Coordinates Of A The Third Corner Of A
            // Triangle If I Have The Lengths Of All Three Sides And The
            // Coordinates Of The First Two Corners?" at
            // http://www.blurtit.com/q9044151.html.

            // Point a, the first endpoint, is one vertex of a right triangle.

            Double dPointAX = oEndpoint1.X;
            Double dPointAY = oEndpoint1.Y;

            // Point b, the midpoint of the line between the two endpoints, is
            // another vertex of the right triangle.  The angle at b is 90 degrees.

            Double dPointBX = dPointAX + (oEndpoint2.X - dPointAX) / 2.0;
            Double dPointBY = dPointAY + (oEndpoint2.Y - dPointAY) / 2.0;

            // Side C connects points a and b.

            Double dSideCLength = WpfGraphicsUtil.GetDistanceBetweenPoints(
                new Point(dPointBX, dPointBY), oEndpoint1);

            // Side A connects points b and c, where c is the point we need to
            // calculate.  Make the length of A, which is the displacement
            // mentioned above, proportional to the length of the line between the
            // two endpoints, so that a longer line gets displaced more than a
            // shorter line.

            Double dSideALength = dSideCLength * dBezierDisplacementFactor;

            // Calculate the angle of the line between the two endpoints.

            Double dAbsAtan2 = Math.Abs(Math.Atan2(
                                            Math.Max(oEndpoint2.Y, dPointAY) - Math.Min(oEndpoint2.Y, dPointAY),
                                            Math.Max(oEndpoint2.X, dPointAX) - Math.Min(oEndpoint2.X, dPointAX)
                                            ));

            Rect oGraphRectangle = oGraphDrawingContext.GraphRectangle;

            if (dAbsAtan2 >= Math.PI / 4.0 && dAbsAtan2 <= 3.0 * Math.PI / 4.0)
            {
                // The line between the two endpoints is closer to vertical than
                // horizontal.
                //
                // As explained in the post mentioned above, the length of side A
                // can be negative or positive, depending on which direction point
                // c should be displaced.  The following adjustments to
                // dSideALength were determined experimentally.

                if (oEndpoint2.Y > dPointAY)
                {
                    dSideALength *= -1.0;
                }

                if (dPointBX - oGraphRectangle.Left <
                    oGraphRectangle.Right - dPointBX)
                {
                    dSideALength *= -1.0;
                }
            }
            else
            {
                // The line between the two endpoints is closer to horizontal than
                // vertical.

                if (oEndpoint2.X < dPointAX)
                {
                    dSideALength *= -1.0;
                }

                if (dPointBY - oGraphRectangle.Top <
                    oGraphRectangle.Bottom - dPointBY)
                {
                    dSideALength *= -1.0;
                }
            }

            // Calculate point c.

            Double dPointCX = dPointBX +
                              (dSideALength * (dPointAY - dPointBY)) / dSideCLength;

            Double dPointCY = dPointBY +
                              (dSideALength * (dPointBX - dPointAX)) / dSideCLength;

            // Don't let point c fall outside the graph's margins.

            return(WpfGraphicsUtil.MovePointWithinBounds(
                       new Point(dPointCX, dPointCY),
                       oGraphDrawingContext.GraphRectangleMinusMargin));
        }
    DrawBezierCurve
    (
        IEdge oEdge,
        GraphDrawingContext oGraphDrawingContext,
        DrawingContext oDrawingContext,
        Point oEdgeEndpoint1,
        Point oEdgeEndpoint2,
        Boolean bDrawAsSelected,
        Boolean bDrawArrow,
        Pen oPen,
        Color oColor,
        Double dWidth,
        VisibilityKeyValue eVisibility,
        String sLabel
    )
    {
        Debug.Assert(oEdge != null);
        Debug.Assert(oGraphDrawingContext != null);
        Debug.Assert(oDrawingContext != null);
        Debug.Assert(oPen != null);
        AssertValid();

        Point oBezierControlPoint = GetBezierControlPoint(oGraphDrawingContext,
            oEdgeEndpoint1, oEdgeEndpoint2, m_dBezierDisplacementFactor);

        if (bDrawArrow)
        {
            // When the edge is a Bezier curve, the arrow should be aligned
            // along the tangent of the curve at the second endpoint.  The
            // tangent runs from the second endpoint to the Bezier control
            // point.

            Double dArrowAngle = WpfGraphicsUtil.GetAngleBetweenPointsRadians(
                oBezierControlPoint, oEdgeEndpoint2);

            // Draw the arrow and set the second endpoint to the center of the
            // flat end of the arrow.  Note that in the Bezier case, moving the
            // second endpoint causes a small distortion of the Bezier curve.

            oEdgeEndpoint2 = DrawArrow(oDrawingContext, oEdgeEndpoint2,
                dArrowAngle, oColor, dWidth);
        }

        PathGeometry oBezierCurve =
            WpfPathGeometryUtil.GetQuadraticBezierCurve(oEdgeEndpoint1,
                oEdgeEndpoint2, oBezierControlPoint);

        oDrawingContext.DrawGeometry(null, oPen, oBezierCurve);

        if ( !String.IsNullOrEmpty(sLabel) )
        {
            DrawLabel(oEdge, oDrawingContext, oGraphDrawingContext,
                bDrawAsSelected, oColor, eVisibility, oEdgeEndpoint1,
                oEdgeEndpoint2, oBezierCurve, oBezierControlPoint, sLabel);
        }
    }
    DrawVertex
    (
        IVertex oVertex,
        GraphDrawingContext oGraphDrawingContext
    )
    {
        Debug.Assert(oVertex != null);
        Debug.Assert(oGraphDrawingContext != null);
        AssertValid();

        VertexDrawingHistory oVertexDrawingHistory;

        if ( m_oVertexDrawer.TryDrawVertex(oVertex, oGraphDrawingContext,
            out oVertexDrawingHistory) )
        {
            Debug.Assert(oVertexDrawingHistory != null);

            DrawingVisual oVertexChildDrawingVisual =
                oVertexDrawingHistory.DrawingVisual;

            m_oAllVertexDrawingVisuals.Children.Add(oVertexChildDrawingVisual);

            oVertex.SetValue(ReservedMetadataKeys.VertexDrawingHistory,
                oVertexDrawingHistory);

            // Save the vertex on the DrawingVisual for later retrieval.

            SaveVertexOnDrawingVisual(oVertex, oVertexChildDrawingVisual);
        }
    }