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); }
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); } }
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); } }
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); } }
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); }
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); }
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); }
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); }
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); }
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); }
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; } }
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 ); } }
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. }
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); } }
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); } }