3) If you use attached nodes as link labels, arranging them to avoid overlapping labels and other nodes could look as below. If you need to rearrange all labels (e.g. after applying a layout algorithm), you'd better remove all label nodes and add them again, so that the area previously occupied by label nodes can be reused. This all can be done with custom drawn labels too, but then you will have to check both nodes locations and labels locations in the code, and you must store a label's location somewhere.
I hope that helps,
Stoyan
private void diagram_LinkModified(object sender, LinkEventArgs e)
{
ArrangeLabel(e.Link);
}
private void diagram_NodeModified(object sender, NodeEventArgs e)
{
foreach (DiagramLink link in e.Node.GetAllLinks())
ArrangeLabel(link);
}
private void diagram_LinkCreated(object sender, LinkEventArgs e)
{
string labelText = "link label\nline 2\nline 3";
SizeF size = diagram.MeasureString(
labelText, diagram.Font, int.MaxValue, new StringFormat());
ShapeNode label = new ShapeNode(diagram);
label.Bounds = e.Link.GetBounds();
label.Text = labelText;
label.Resize(size.Width + 3, size.Height + 3);
label.Obstacle = false;
label.IgnoreLayout = true;
label.Locked = true;
diagram.Nodes.Add(label);
label.ZTop();
label.AttachTo(e.Link, AttachToLink.Point, 0);
ArrangeLabel(e.Link);
}
class Vector
{
public Vector(float x, float y)
{
X = x;
Y = y;
}
public Vector(PointF p1, PointF p2)
{
X = p2.X - p1.X;
Y = p2.Y - p1.Y;
}
public float Length
{
get { return (float)Math.Sqrt(X * X + Y * Y); }
}
static public Vector operator*(Vector v, float factor)
{
return new Vector(v.X * factor, v.Y * factor);
}
static public PointF operator+ (PointF p, Vector v)
{
return new PointF(p.X + v.X, p.Y + v.Y);
}
public float X;
public float Y;
}
void ArrangeLabel(DiagramLink link)
{
DiagramNode label = link.SubordinateGroup.AttachedNodes[0];
// the label should stay inside the diagram
RectangleF bounds = diagram.Bounds;
bounds.Inflate(-label.Bounds.Width / 2, -label.Bounds.Height / 2);
float bestEval = float.MaxValue;
float step = 4f; // millimeters
PointF bestPoint = label.GetCenter();
// for all link segments
for (int i = 0; i < link.SegmentCount; ++i)
{
// get the segment key points
PointF start = link.ControlPoints[i];
PointF end = link.ControlPoints[i + 1];
PointF middle = InternalUtils.MidPoint(start, end);
// and the segment direction vector
Vector segmentDir = new Vector(start, end);
int pointsToTry = (int)(segmentDir.Length / step);
segmentDir = segmentDir * (step / segmentDir.Length);
// we don't like short segments
float shortSegmentPenalty = pointsToTry > 7 ? 0 :
pointsToTry > 3 ? 25 : 50;
// for every point at distance "step" along the segment
foreach (PointF point in PointsAlongVector(start, segmentDir, pointsToTry))
{
// we prefer points close to the segment center
float penalty = shortSegmentPenalty + Utilities.Distance(point, middle);
float eval = EvalPos(label, point, penalty);
if (eval < bestEval && bounds.Contains(point))
{
bestPoint = point;
bestEval = eval;
}
}
}
label.Move(
bestPoint.X - label.Bounds.Width / 2,
bestPoint.Y - label.Bounds.Height / 2);
}
IEnumerable<PointF> PointsAlongVector(PointF start, Vector vector, int points)
{
for (int i = 0; i <= points; ++i)
{
yield return start;
start += vector;
}
}
float EvalPos(DiagramNode label, PointF point, float segmentPenalty)
{
RectangleF rect = label.Bounds;
rect.Location = point;
rect.Offset(-rect.Width / 2, -rect.Height / 2);
// leave a margin so label is not placed very near to other nodes
rect.Inflate(10, 10);
float worstCase = 0;
foreach (DiagramNode node in diagram.Nodes)
{
if (node == label)
continue;
if (rect.IntersectsWith(node.Bounds))
{
// use the area of the intersection that would occur
// if the label is placed here as evaluation how good the location is
RectangleF intersection = RectangleF.Intersect(rect, node.Bounds);
float area = intersection.Width * intersection.Height;
worstCase = Math.Max(worstCase, area);
}
}
// add the segment's penalty to the total cost so we prefer
// points near the segment center, avoid short segments, etc
return worstCase + segmentPenalty;
}