Issue with getting a CircularArc

Jan 14, 2013 at 1:13 PM
Edited Jan 14, 2013 at 1:15 PM

I have the following issue: I am creating a diagram from a (self-made) state machine definition. I can get everything to appear properly on the screen but when i have a state-transition to itself i would like to make a circulararc to show this propery.

What i do first is add all my unique states to the diagram, then loop through them and add the connections with a polyline. However when using a circulararc i need to set a 3rd vertex which i cannot seem to get working properly.

 

int x = 10, y = 500;
            
foreach (Form1.FSM fsm in IFSM)
{
  RectangleBase shape;
  if (!shapeDict.TryGetValue(fsm.StartStateID, out shape))
  {
    shape = (RectangleBase)project1.ShapeTypes["ellipse"].CreateInstance();
    shape.Width = 100;
    shape.Height = 60;
    shape.X = x + 50;
    shape.Y = y + 50;
    x += 120;
    if (x > 1200)
    {
      x = 10;
      y += 70;
    }
    shape.SetCaptionText(0, fsm.StartStateID);
    shapeDict.Add(fsm.StartStateID, shape);
    diagram.Shapes.Add(shape);
  }
}

foreach (RectangleBase s in diagram.Shapes)
{
  RectangleBase nextshape;
  string startstateId = s.GetCaptionText(0);
  var q = from f in IFSM
          where f.StartStateID == startstateId
          select f.NewStateID;
  foreach (var result in q)
  {
    nextshape = shapeDict[result];
    if (s != nextshape)
    {
      //RectangularLine arrow = (RectangularLine)project1.ShapeTypes["RectangularLine"].CreateInstance();
      Polyline arrow = (Polyline)project1.ShapeTypes["Polyline"].CreateInstance();
      diagram.Shapes.Add(arrow);
      arrow.EndCapStyle = project1.Design.CapStyles.ClosedArrow;
      arrow.Connect(ControlPointId.FirstVertex, s, ControlPointId.Reference);
      arrow.Connect(ControlPointId.LastVertex, nextshape, ControlPointId.Reference);

    }
    else
    {
      CircularArc arrow = (CircularArc)project1.ShapeTypes["CircularArc"].CreateInstance();
      arrow.EndCapStyle = project1.Design.CapStyles.ClosedArrow;

      arrow.AddVertex(1, 1);

 diagram.Shapes.Add(arrow); arrow.Connect(ControlPointId.FirstVertex, s, ControlPointId.Reference); arrow.Connect(ControlPointId.LastVertex, nextshape, ControlPointId.Reference); } } } cachedRepository1.InsertAll(diagram); display1.Diagram = diagram;

 

It is about the part where i try to add another vertex to the line but whatever i try to fill in here i keep getting errors.

Coordinator
Jan 14, 2013 at 4:10 PM
Edited Jan 14, 2013 at 4:11 PM

The problem is, that you use the wrong method for this purpose:

AddVertex adds a vertex at the given position - which implies that the given position has to be part of the shape. This is because otherwise the sequence of vertices would not be clarly defined.
InsertVertex creates a new vertex at the given position. Due to the fact that you have to specify a vertex (the vertex before the new vertex), the sequence of the vertices is defined properly in this case.

(...)
      CircularArc arrow = (CircularArc)project1.ShapeTypes["CircularArc"].CreateInstance();
      arrow.EndCapStyle = project1.Design.CapStyles.ClosedArrow;

      // Calculate position of new vertex
      Point firstPt = arrow.GetControlPointPosition(ControlPointId.FirstVertex);
      Point lastPt = arrow.GetControlPointPosition(ControlPointId.LastVertex);
      Point p = new Point((firstPt.X + lastPt.X) / 2, (lastPt.Y + lastPt.Y) / 2 + 10);
      // Insert vertex before last vertex at the calculated position
      arrow.InsertVertex(ControlPointId.LastVertex, p.X, p.Y);
    
      diagram.Shapes.Add(arrow);
      arrow.Connect(ControlPointId.FirstVertex, s, ControlPointId.Reference);
      arrow.Connect(ControlPointId.LastVertex, nextshape, ControlPointId.Reference);
(...)

Jan 14, 2013 at 4:18 PM

I have been trying to figure out how the whole thing with the control points worked but the documentation is lacking on some points so i was using the source to try to figure out how to get the proper points. This should help me quite a bit. Thanks a lot!

Jan 16, 2013 at 1:59 PM

I am still having issues with this, i am not sure if this is a problem somewhere else in the code. The problem is that it still does not make a nice arced line pointing to itself, if i execute the code i get a diagram on screen with all the proper states and transitions besides the ones pointed to themselves, which are shown as an arrow within the states. When moving other objects than the object with the transition to itself i have no issues moving them around, the lines stay properly connected.

However when i try to move one of the objects containing a transition to itself i get an error in Program.cs:

Index was out of range. Must be non-negative and less than the size of the collection.

I have rewritten the code a bit but the same principle still holds:

            //Definition of variables used in function
            int x = 10, y = 500;
            List<State> stateList = new List<State>();

            //Reset dictionaries, repositories and diagrams
            shapeDict.Clear(); 
            diagram.Clear();
            cachedRepository1.Close();
            cachedRepository1.Create();

            //Loop through each state in the statemachine
            foreach (Form1.FSM fsm in IFSM)
            {
                State tempState;
                RectangleBase shape;

                //If state is not yet in the dictionary, add it
                if (!shapeDict.TryGetValue(fsm.StartStateID, out shape))
                {
                    //processEllipse makes an ellipse with a certain width, height, caption and tag
                    shape = processEllipse(100, 60, ref x, ref y, fsm.StartStateID, fsm.StartStateID);
                    //Add the new shape to the different objects
                    shapeDict.Add(fsm.StartStateID, shape);
                    diagram.Shapes.Add(shape);
                    tempState = new State(shape);
                    stateList.Add(tempState);
                }
            }

            //Loop through each shape in the diagram
            foreach (RectangleBase s in diagram.Shapes)
            {
                RectangleBase nextshape;
                CircularArc arrow;
                State tempState;
                Dataweb.NShape.GeneralShapes.Label label;

                //Defines if the diagram shows the captions on the transitions
                bool transitions = false;

                //Define startStateId for use in functions
                string startStateId = s.GetCaptionText(0);
                
                //Find all transitions in IFSM
                var q = IFSM.Where(state => state.StartStateID == startStateId);

                //Loop through transitions
                foreach (var result in q)
                {
                    //Define the state that is transitioned to
                    nextshape = shapeDict[result.NewStateID];
                    //Define the transition
                    arrow = processLine(s, nextshape);

                    Point firstPt = arrow.GetControlPointPosition(ControlPointId.FirstVertex);
                    Point lastPt = arrow.GetControlPointPosition(ControlPointId.LastVertex);
                    Point dstPos = Point.Empty;
                    dstPos.X = ((firstPt.X + lastPt.X) / 2) + 1;
                    dstPos.Y = ((firstPt.Y + lastPt.Y) / 2) + 1;

                    //If startstate and next state are the same, it should add a vertex
                    if (s == nextshape)
                    {
                        arrow.InsertVertex(ControlPointId.LastVertex, dstPos.X, dstPos.Y);
                    }

                    //Add the transition to the diagram
                    diagram.Shapes.Add(arrow);

                    //Add label to transition
                    if (transitions)
                    {
                        label = processLabel(result.Event, dstPos, arrow);
                        diagram.Shapes.Add(label);
                    }
                }
            }

Coordinator
Jan 17, 2013 at 8:55 AM
Edited Jan 17, 2013 at 10:40 AM

The main problem is the point-to-shape connection on both ends to the shape shape:
The point-to-shape connections try to calculate the intersection point between the line and its partner shape and maintain the basic layout of the line at the same time. When pointing to the same shape, this is not really possible as both ends of the line always move at the same time.

By the way, you need to calculate the position of the third vertex with a more sophisticated algorithm - the code from my last post was just a simple example.
Furthermore, I would strongly recommend to use point-to-point connections for the case that both ends of the line should be connected to the same shape.
Sample:

      // Define the transition
      arrow = processLine(s, nextshape);

// Move the vertices to their partner point positions and connect the shape
// to connection points instead of ControlPointId.Reference
Point firstPt = s.GetControlPointPosition(topConnPtId);
arrow.MoveControlPointTo(ControlPointId.FirstVertex, firstPt.X, firstPt.Y, ResizeModifiers.None);
arrow.Connect(ControlPointId.FirstVertex, s, topConnPtId);       Point lastPt = arrow.GetControlPointPosition(ControlPointId.LastVertex);
arrow.MoveControlPointTo(ControlPointId.LastVertex, lastPt.X, lastPt.Y, ResizeModifiers.None);
arrow.Connect(ControlPointId.LastVertex, s, rightConnPtId);       // Calculate the position of the arc's third point that defines the radius:
      // - Get the bounds of the target shape
      // - Find suitable connection points at the top and the right side of the shape
      // - Use the size of the target shape as radius for the arc

      Rectangle shapeBounds = s.GetBoundingRectangle(true);
      ControlPointId topConnPtId = s.FindNearestControlPoint(shapeBounds.Left + shapeBounds.Width / 2, shapeBounds.Top, 10, ControlPointCapabilities.Connect);
Debug.Assert(topConnPtId != ControlPointId.None);
ControlPointId rightConnPtId = s.FindNearestControlPoint(shapeBounds.Right, shapeBounds.Top + shapeBounds.Height / 2, 10, ControlPointCapabilities.Connect);
Debug.Assert(rightConnPtId != ControlPointId.None);
Point radiusPt = shapeBounds.Location;
radiusPt.Offset(shapeBounds.Width / 2, -shapebounds.Height / 2);
// Insert the vertex at the calculated position
arrow.InsertVertex(ControlPointId.Last, radiusPt.X, radiusPt.Y);

// If startstate and next state are the same, it should add a vertex      
if (s == nextshape)         
arrow.InsertVertex(ControlPointId.LastVertex, dstPos.X, dstPos.Y);      

// Add the transition to the diagram
  diagram.Shapes.Add(arrow);
Jan 17, 2013 at 9:14 AM

The calculation of the additional vertex isnt the main issue, this i can always do some other way when i get the system working ;)

Coordinator
Jan 17, 2013 at 10:38 AM
Edited Jan 17, 2013 at 10:45 AM

[Edited]

Sorry for not pointing this out clear enough:
The cause of the Exception you get is the Point-to-shape connection on both ends - this is not allowed for a circular arc.
When connecting to the reference point of a shape (ControlPointId.Reference), this means that you connect the line with the whole shape (point-to-shape connection).
In this case, you have to connect the line to a specific connection point of the shape in order to avoid re-calculation of the end points when moving the line's vertices.

When moving one of the vertices while both ends are connected point-to-shape, the framework tries to re-calculate the positions of all other points... and moving one of the other points cause a re-calculation of the all end points connected via point-to-shape connection... which causes all other points to be re-calculated... so on.

I will investigate this issue more closely because the exception is misleading.
An exception should be thrown that tells you more about the real source of the problem.

[Edited]