internal override bool sameNode(Link otherLink) { ChartObject obj = otherLink.getNode(); if(obj == null) return false; else return (obj == table); }
internal bool objsIntersect(Link link) { return Node.nodesIntersect(this.getNode(), link.getNode()); }
internal abstract bool sameNode(Link otherLink);
internal override bool sameNode(Link otherLink) { ChartObject obj = otherLink.getNode(); return (obj != null && obj == host); }
internal override bool sameNode(Link otherLink) { return false; }
internal override void restoreState(ItemState state) { base.restoreState(state); ArrowState astate = (ArrowState)state; style = astate.style; segmentCount = astate.segmentCount; points = astate.points.Clone(); reflexive = astate.reflexive; cascadeStartHorizontal = astate.cascadeStartHorizontal; if (orgnLink != astate.orgnLink) { orgnLink.removeArrowFromObj(); orgnLink = astate.orgnLink; orgnLink.addArrowToObj(); } orgnAnchor = astate.orgnAnchor; if (destLink != astate.destLink) { destLink.removeArrowFromObj(); destLink = astate.destLink; destLink.addArrowToObj(); } destAnchor = astate.destAnchor; updateFromPoints(false); if (subordinateGroup != null) subordinateGroup.onRestoreState(); orgnLink.RelativePosition = astate.orgnPoint; destLink.RelativePosition = astate.destPoint; resetCrossings(); updateText(); }
private void setEndPoints(Link orgnLink, Link destLink, PointF end) { reflexive = destLink.sameNode(orgnLink); // align the end points of the arrow to the outlines of the connected nodes if (orgnLink.calcIntrs()) ptOrg = points[0] = orgnLink.getInitialPt(); if (destLink.calcIntrs()) ptEnd = destLink.getInitialPt(); else ptEnd = end; if (!orgnLink.objsIntersect(destLink) && !reflexive) { if (orgnLink.calcIntrs()) ptOrg = points[0] = orgnLink.getIntersection(ptOrg, ptEnd); if (destLink.calcIntrs()) ptEnd = destLink.getIntersection(ptEnd, ptOrg); } else { // if the arrow will be routed, choose the closest points on the // horizontal line intersections with the node outlines. These points // should not be contained within a node if (autoRoute && !dynamic && orgnAnchor == -1 && destAnchor == -1) { chooseRouteOutlinePoints(orgnLink, destLink, ref ptOrg, ref ptEnd); points[0] = ptOrg; } } }
private void putEndPointsAtNodeBorders(bool routing, Link orgnLink, Link destLink) { PointF ptOrg = orgnLink.getInitialPt(); PointF ptDest = destLink.getInitialPt(); if (!orgnLink.objsIntersect(destLink)) { // if destination and source do not intersect, use the points // where the line connecting their centers crosses their outlines if (style == ArrowStyle.Cascading) updateEndPtsPrp(); ptOrg = orgnLink.getIntersection(ptOrg, routing ? ptDest : points[1]); ptDest = destLink.getIntersection(ptDest, routing ? ptOrg : points[points.Count - 2]); } else { // if the arrow will be rerouted, choose the closest points on the // horizontal line intersections with the node outlines. These points // should not be contained within a node if (routing) chooseRouteOutlinePoints(orgnLink, destLink, ref ptOrg, ref ptDest); } // snap to nearest anchors ptOrg = orgnLink.getNode().getAnchor( ptOrg, this, false, ref orgnAnchor); ptDest = destLink.getNode().getAnchor( ptDest, this, true, ref destAnchor); if (style != ArrowStyle.Cascading) retain(ptOrg.X - points[0].X, ptOrg.Y - points[0].Y, true); points[0] = ptOrg; if (style != ArrowStyle.Cascading) retain(ptDest.X - points[points.Count - 1].X, ptDest.Y - points[points.Count - 1].Y, false); points[points.Count - 1] = ptDest; if (style == ArrowStyle.Cascading) updateEndPtsPrp(); }
private void chooseRouteOutlinePoints(Link orgnLink, Link destLink, ref PointF ptOrg, ref PointF ptDest) { RoutingOptions rop = flowChart.RoutingOptions; Node origin = orgnLink.getNode(); Node destination = destLink.getNode(); bool reflexive = orgnLink.sameNode(destLink); // how many points to consider ? int srcCount = rop.StartOrientation == Orientation.Auto ? 4 : 2; int dstCount = rop.EndOrientation == Orientation.Auto ? 4 : 2; PointF[] srcPoints = new PointF[srcCount]; PointF[] dstPoints = new PointF[dstCount]; // determine the coordinates of the points to be checked ... int srcIdx = 0, dstIdx = 0; // source points if (rop.StartOrientation != Orientation.Vertical) { srcPoints[srcIdx++] = orgnLink.getIntersection(ptOrg, new PointF(ptOrg.X - 2*origin.BoundingRect.Width, ptOrg.Y)); srcPoints[srcIdx++] = orgnLink.getIntersection(ptOrg, new PointF(ptOrg.X + 2*origin.BoundingRect.Width, ptOrg.Y)); } if (rop.StartOrientation != Orientation.Horizontal) { srcPoints[srcIdx++] = orgnLink.getIntersection(ptOrg, new PointF(ptOrg.X, ptOrg.Y - 2*origin.BoundingRect.Height)); srcPoints[srcIdx++] = orgnLink.getIntersection(ptOrg, new PointF(ptOrg.X, ptOrg.Y + 2*origin.BoundingRect.Height)); } // destination points if (rop.EndOrientation != Orientation.Vertical) { dstPoints[dstIdx++] = dstPoints[1] = destLink.getIntersection(ptDest, new PointF(ptDest.X - 2*destination.BoundingRect.Width, ptDest.Y)); dstPoints[dstIdx++] = destLink.getIntersection(ptDest, new PointF(ptDest.X + 2*destination.BoundingRect.Width, ptDest.Y)); } if (rop.EndOrientation != Orientation.Horizontal) { dstPoints[dstIdx++] = destLink.getIntersection(ptDest, new PointF(ptDest.X, ptDest.Y - 2*destination.BoundingRect.Height)); dstPoints[dstIdx++] = destLink.getIntersection(ptDest, new PointF(ptDest.X, ptDest.Y + 2*destination.BoundingRect.Height)); } // check which of these points are closest ptOrg = srcPoints[0]; ptDest = dstPoints[0]; float minDist = Single.MaxValue; for (int sidx = 0; sidx < srcCount; ++sidx) { if (!reflexive && destination.containsPoint(srcPoints[sidx])) continue; for (int didx = 0; didx < dstCount; ++didx) { if (!reflexive && origin.containsPoint(dstPoints[didx])) continue; float dist = Utilities.Distance( srcPoints[sidx], dstPoints[didx]); if (dist < 2 * rop.GridSize) continue; // we prefer these to be on the same side if (sidx != didx) dist *= 3; if (dist < minDist) { minDist = dist; ptOrg = srcPoints[sidx]; ptDest = dstPoints[didx]; } } } }
internal void setOrgAndDest(Link orgLink, Link trgLink) { orgnLink = orgLink; destLink = trgLink; // place the end points of the arrow at their respective object edge points[0] = orgnLink.getInitialPt(); points[points.Count-1] = destLink.getInitialPt(); if (!orgnLink.objsIntersect(destLink)) { points[0] = orgnLink.getIntersection(points[0], points[points.Count-1]); points[points.Count-1] = destLink.getIntersection(points[points.Count-1], points[0]); } points[0] = orgnLink.getAnchor(points[0], this, false, ref orgnAnchor); points[points.Count-1] = destLink.getAnchor(points[points.Count-1], this, true, ref destAnchor); // set reflexive arrow position & form reflexive = orgnLink.sameNode(destLink); if (reflexive) { setReflexive(); } else { // compute the arrow points updatePoints(points[points.Count-1]); if (dynamic) updatePosFromOrgAndDest(true); } rectFromPoints(); // relative position to nodes orgnLink.saveEndRelative(); destLink.saveEndRelative(); doRoute(); }
private void routeGetEndPoints(ref PointF startPoint, ref PointF endPoint, ref int oNearest, ref int dNearest, Link orgnLink, Link destLink, bool nowCreating) { if (flowChart.RoutingOptions.Anchoring == Anchoring.Ignore) { float gridSize = flowChart.RoutingOptions.GridSize; // find the intersection of the nodes outlines with the four rays // that go outwards from the start/end points PointF[] opts = new PointF[4]; PointF ptoCenter = startPoint; RectangleF or = orgnLink.getNodeRect(true); Utilities.getProjections(startPoint, or, opts); PointF[] dpts = new PointF[4]; PointF ptdCenter = endPoint; RectangleF dr = destLink.getNodeRect(true); Utilities.getProjections(endPoint, dr, dpts); // get the intersection point nearest to the origin and destination centers. int oLen = opts.Length; if (flowChart.RoutingOptions.StartOrientation == Orientation.Horizontal) { oNearest = 2; } else if (flowChart.RoutingOptions.StartOrientation == Orientation.Vertical) { oLen = 2; } for (int i = oNearest + 1; i < oLen; i++) { if (Math.Abs(opts[i].X - ptoCenter.X) + Math.Abs(opts[i].Y - ptoCenter.Y) < Math.Abs(opts[oNearest].X - ptoCenter.X) + Math.Abs(opts[oNearest].Y - ptoCenter.Y)) oNearest = i; } int dLen = dpts.Length; if (flowChart.RoutingOptions.EndOrientation == Orientation.Horizontal) { dNearest = 2; } else if (flowChart.RoutingOptions.EndOrientation == Orientation.Vertical) { dLen = 2; } for (int i = dNearest + 1; i < dLen; i++) { if (Math.Abs(dpts[i].X - ptdCenter.X) + Math.Abs(dpts[i].Y - ptdCenter.Y) < Math.Abs(dpts[dNearest].X - ptdCenter.X) + Math.Abs(dpts[dNearest].Y - ptdCenter.Y)) dNearest = i; } startPoint = opts[oNearest]; endPoint = dpts[dNearest]; // Offset the starting and ending point a little bit float[,] pull = new float[4, 2] { { 0, -1 }, { 0, 1 }, { 1, 0 }, { -1, 0 } }; startPoint.X += pull[oNearest, 0] * gridSize; startPoint.Y += pull[oNearest, 1] * gridSize; endPoint.X += pull[dNearest, 0] * gridSize; endPoint.Y += pull[dNearest, 1] * gridSize; } else { oNearest = -1; dNearest = -1; if (nowCreating || flowChart.RoutingOptions.Anchoring == Anchoring.Reassign) { putEndPointsAtNodeBorders(true, orgnLink, destLink); startPoint = points[0]; endPoint = points[points.Count - 1]; } } }
internal void doRoute(bool force, Link orgnLink, Link destLink, bool nowCreating) { if (!force) if (!autoRoute) return; if (flowChart.DontRouteForAwhile) return; int i; float gridSize = flowChart.RoutingOptions.GridSize; PointF startPoint = points[0]; PointF endPoint = points[points.Count - 1]; // get a rectangle bounding both the origin and the destination RectangleF bounds = orgnLink.getNodeRect(true); bounds = Utilities.unionRects(bounds, destLink.getNodeRect(true)); bounds = RectangleF.Union(bounds, Utilities.normalizeRect( RectangleF.FromLTRB(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y))); if (bounds.Width < gridSize * 4) bounds.Inflate(gridSize * 4, 0); if (bounds.Height < gridSize * 4) bounds.Inflate(0, gridSize * 4); bounds.Inflate(bounds.Width, bounds.Height); int oNearest = 0, dNearest = 0; routeGetEndPoints(ref startPoint, ref endPoint, ref oNearest, ref dNearest, orgnLink, destLink, nowCreating); // Get the starting and ending square Point ptStart = new Point((int)((startPoint.X - bounds.X) / gridSize), (int)((startPoint.Y - bounds.Y) / gridSize)); Point ptEnd = new Point((int)((endPoint.X - bounds.X) / gridSize), (int)((endPoint.Y - bounds.Y) / gridSize)); if (ptStart.X == ptEnd.X && ptStart.Y == ptEnd.Y) return; // init the route grid int gridCols = (int)(bounds.Width / gridSize); int gridRows = (int)(bounds.Height / gridSize); RoutingGrid routingGrid = flowChart.RoutingGrid; routingGrid.allocate(gridCols, gridRows, bounds, this); byte[,] grid = routingGrid.getCostGrid(); PathNode[,] gridClosed = routingGrid.getClosedGrid(); PathNode[,] gridOpen = routingGrid.getOpenGrid(); bool hurry = (gridCols * gridRows > 90000) && flowChart.RoutingOptions.DontOptimizeLongRoutes; RouteHeuristics calcRouteHeuristics = hurry ? RoutingOptions.DistSquare : flowChart.RoutingOptions.RouteHeuristics; routeFixEndRegions(grid, ref ptStart, oNearest, ref ptEnd, dNearest, gridCols, gridRows); grid[ptStart.X, ptStart.Y] = 0; grid[ptEnd.X, ptEnd.Y] = 0; //---------- A* algorithm initialization ----------- SortedList open = new SortedList(); ArrayList closed = new ArrayList(); Stack stack = new Stack(); PathNode temp = new PathNode(ptStart.X, ptStart.Y); temp.G = 0; temp.H = calcRouteHeuristics(ptStart, ptEnd); temp.F = temp.G + temp.H; open.Add(temp, temp); gridOpen[temp.X, temp.Y] = temp; // setup A* cost function int adjcCost = flowChart.RoutingOptions.LengthCost; int turnCost = flowChart.RoutingOptions.TurnCost; PathNode best = null; bool found = false; int iterations = 0; for ( ; ; ) { iterations++; // Get the best node from the open list if (open.Count == 0) break; PathNode pstmp = open.GetByIndex(0) as PathNode; open.RemoveAt(0); gridOpen[pstmp.X, pstmp.Y] = null; closed.Add(pstmp); gridClosed[pstmp.X, pstmp.Y] = pstmp; if ((best = pstmp) == null) break; // If best == destination -> path found if (best.X == ptEnd.X && best.Y == ptEnd.Y) { found = true; break; } // Generate best's successors int x = best.X; int y = best.Y; int[,] off = new int[4, 2] { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; for (i = 0; i < 4; i++) { byte localCost = grid[x + off[i, 0], y + off[i, 1]]; if (localCost == 255) continue; int g = best.G + adjcCost + localCost; bool straight = best.Parent == null || (best.Parent.Y == best.Y && off[i, 1] == 0) || (best.Parent.X == best.X && off[i, 0] == 0); if (best.Parent == null && oNearest >= 0 && ( oNearest < 2 && off[i, 1] == 0 || oNearest >= 2 && off[i, 1] == 1)) straight = false; if (!straight) g += turnCost; PathNode check = null; // if the successor is an open node, add it to the path check = gridOpen[x + off[i, 0], y + off[i, 1]]; if (check != null) { best.Children[best.ChildCount++] = check; // and update its cost if now it is reached via a better path if (g < check.G) { open.Remove(check); // keep sorted check.Parent = best; check.G = g; check.F = g + check.H; open.Add(check, check); // keep sorted } } else { // if the successor is a closed node, add it to the path check = gridClosed[x + off[i, 0], y + off[i, 1]]; if (check != null) { best.Children[best.ChildCount++] = check; // and update its cost if now it is reached via a better path if (g < check.G) { check.Parent = best; check.G = g; check.F = g + check.H; // and update its child items int gg = check.G; int cc = check.ChildCount; PathNode kid = null; for (int j = 0; j < cc; j++) { kid = check.Children[j]; int gi = adjcCost; straight = check.Parent == null || (check.Parent.Y == check.Y && check.Y == kid.Y) || (check.Parent.X == check.X && check.X == kid.X); if (!straight) gi += turnCost; if (g + gi < kid.G) { bool wasOpen = gridOpen[kid.X, kid.Y] != null; if (wasOpen) open.Remove(kid); // keep sorted kid.G = g + gi; kid.F = kid.G + kid.H; kid.Parent = check; stack.Push(kid); if (wasOpen) open.Add(kid, kid); } } PathNode parent; while (stack.Count > 0) { parent = stack.Pop() as PathNode; cc = parent.ChildCount; for (int j = 0; j < cc; j++) { kid = parent.Children[j]; int gi = adjcCost; straight = parent.Parent == null || (parent.Parent.Y == parent.Y && parent.Y == kid.Y) || (parent.Parent.X == parent.X && parent.X == kid.X); if (!straight) gi += turnCost; if (parent.G + gi < kid.G) { bool wasOpen = gridOpen[kid.X, kid.Y] != null; if (wasOpen) open.Remove(kid); // keep sorted kid.G = parent.G + gi; kid.F = kid.G + kid.H; kid.Parent = parent; stack.Push(kid); if (wasOpen) open.Add(kid, kid); } } } } } else { // haven't considered this grid square by now // create and initialize a path node for it Point current = new Point(x + off[i, 0], y + off[i, 1]); PathNode newNode = new PathNode(current.X, current.Y); newNode.Parent = best; newNode.G = g; newNode.H = calcRouteHeuristics(current, ptEnd); newNode.F = newNode.G + newNode.H; // add it to the list of open nodes to be evaluated later open.Add(newNode, newNode); gridOpen[newNode.X, newNode.Y] = newNode; // add to the path best.Children[best.ChildCount++] = newNode; } } } } if (found) { PtCollection current = new PtCollection(0); current.Add(new Point((int)((points[points.Count - 1].X - bounds.X) / gridSize), (int)((points[points.Count - 1].Y - bounds.Y) / gridSize))); while (best != null) { current.Add(new Point(best.X, best.Y)); best = best.Parent; } current.Add(new Point((int)((points[0].X - bounds.X) / gridSize), (int)((points[0].Y - bounds.Y) / gridSize))); // Remove all unneeded points Point pt1, pt2, pt3; for (i = 1; i < current.Count - 1;) { pt1 = current[i - 1]; pt2 = current[i]; pt3 = current[i + 1]; if (pt1.X == pt2.X && pt2.X == pt3.X) current.RemoveAt(i); else if(pt1.Y == pt2.Y && pt2.Y == pt3.Y) current.RemoveAt(i); else i++; } // Save the first and last points of the arrow PointF ptFirst = points[0]; PointF ptLast = points[points.Count - 1]; // no perp. arrows on a single line if (style == ArrowStyle.Cascading && current.Count == 2 && ptFirst.X != ptLast.X && ptFirst.Y != ptLast.Y) { Point orgPt = current[0]; Point trgPt = current[current.Count-1]; if (orgPt.X == trgPt.X || orgPt.Y == trgPt.Y) { Point insPt = new Point( (orgPt.X + trgPt.X) / 2, (orgPt.Y + trgPt.Y) / 2); current.Insert(1, insPt); current.Insert(1, insPt); } } // Re-segment the arrow points = new PointCollection(current.Count); points[0] = ptFirst; points[points.Count - 1] = ptLast; // Assign the points from the path i = current.Count - 1; i--; // Skip the first point while (i > 0) { Point pt = current[i]; PointF ptDoc = new PointF(0, 0); ptDoc.X = bounds.X + pt.X * gridSize + gridSize / 2; ptDoc.Y = bounds.Y + pt.Y * gridSize + gridSize / 2; if (i == 1) { // Align to the last point if (pt.Y == current[0].Y) ptDoc.Y = ptLast.Y; else ptDoc.X = ptLast.X; } if (i == current.Count - 2) { // Align to the first point if (pt.Y == current[current.Count - 1].Y) ptDoc.Y = ptFirst.Y; else ptDoc.X = ptFirst.X; if (style == ArrowStyle.Cascading) cascadeStartHorizontal = (ptDoc.X != ptFirst.X); } points[current.Count - i - 1] = ptDoc; i--; } PointF ptf, ptf1, ptf2, ptf3; // If the line is perpendicular make it at least 2 segments if(style == ArrowStyle.Cascading && points.Count == 2) { ptf1 = points[0]; ptf2 = points[points.Count - 1]; ptf = ptf1; if (cascadeStartHorizontal) ptf.X = ptf2.X; else ptf.Y = ptf2.Y; points.Insert(1, ptf); } // If the line is straight there might be more unneeded points if (style == ArrowStyle.Polyline) { i = 0; while(i < points.Count - 2) { ptf1 = points[i]; ptf2 = points[i + 2]; ChartObject obj = flowChart.objectIntersectedBy(ptf1, ptf2, orgnLink.getNode(), destLink.getNode()); if(obj == null) points.RemoveAt(i + 1); else i++; } } // If the line is bezier, smooth it a bit if (style == ArrowStyle.Bezier) { PointCollection newPoints = new PointCollection(0); newPoints.Add(points[0]); i = 0; while(i < points.Count - 2) { ptf1 = points[i]; ptf2 = points[i + 1]; newPoints.Add(ptf2); newPoints.Add(ptf2); if(i != points.Count - 3) { ptf3 = points[i + 2]; ptf = new PointF((ptf2.X + ptf3.X) / 2, (ptf2.Y + ptf3.Y) / 2); newPoints.Add(ptf); } else { newPoints.Add(points[i + 2]); } i += 1; } if (newPoints.Count == 1) { newPoints = new PointCollection(4); ptf1 = points[0]; ptf2 = points[points.Count - 1]; ptf = new PointF((ptf1.X + ptf2.X) / 2, (ptf1.Y + ptf2.Y) / 2); newPoints[0] = ptf1; newPoints[1] = ptf; newPoints[2] = ptf; newPoints[3] = ptf2; } points.Clear(); points = newPoints; } // Update SegmentCount property value if (style == ArrowStyle.Bezier) segmentCount = (short)((points.Count - 1) / 3); else segmentCount = (short)(points.Count - 1); } else { // No path found -> reset the arrow, leaving as little points as possible int ptsToLeave = 2; if (style == ArrowStyle.Cascading) ptsToLeave = 4; else if (style == ArrowStyle.Bezier) ptsToLeave = 4; if (style == ArrowStyle.Cascading) { cascadeOrientation = Orientation.Auto; segmentCount = 3; } else segmentCount = 1; while (points.Count > ptsToLeave) points.RemoveAt(1); if (style == ArrowStyle.Cascading && points.Count == 3) segmentCount = 2; updatePoints(points[points.Count - 1]); } updateArrowHeads(); if (subordinateGroup != null) { subordinateGroup.onSegmentsChanged(); subordinateGroup.updateObjects(new InteractionState(this, -1, Action.Modify)); } resetCrossings(); updateText(); flowChart.fireArrowRoutedEvent(this); }