Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic Callout shapes (Read 2430 times)
jf2020
Junior Member
**
Offline


I love FlowChart!

Posts: 63
Joined: Feb 23rd, 2006
Callout shapes
Jul 7th, 2010 at 9:17am
Print Post  
Good Day,

I need to implement callouts shape, but I have to admit that I'm a bit puzzled on how to achieve and even if this is possible within the current FlowChart. Can you give me some pointers on how to do it? I guess this requires custom drawing and custom handling of the handles but if you had a basis to get me started it would be very helpful.

Thanks

JF
  
Back to top
 
IP Logged
 
Stoyo
God Member
*****
Offline


MindFusion support

Posts: 13230
Joined: Jul 20th, 2005
Re: Callout shapes
Reply #1 - Jul 7th, 2010 at 10:56am
Print Post  
Good day,

We have implemented such nodes in one of our project as a combination of an elliptical node and a custom drawn link. The DrawLink handler draws the callout arrow like this:

Code
Select All
void OnDrawLink(object sender, DrawLinkEventArgs e)
{
	RectangleF bounds = e.Link.Origin.Bounds;
	if (bounds.Width == 0 || bounds.Height == 0)
		return;

	PointF startPoint = e.Points[0];
	PointF endPoint = e.Points[e.Points.Count - 1];

	float distance = Distance(startPoint, endPoint);
	if (distance == 0)
		return;
	float openingAngle = (float)(Math.Atan(1 / distance) * 360 / Math.PI) * 1.5f;

	float angle = 0;
	float radius = 0;
	GeometryConvert.DekartToPolar(endPoint, startPoint, ref angle, ref radius);

	PointF leftKnee = startPoint;
	PointF rightKnee = startPoint;
	GeometryConvert.PolarToDekart(endPoint, angle - openingAngle, radius, ref leftKnee);
	GeometryConvert.PolarToDekart(endPoint, angle + openingAngle, radius, ref rightKnee);

	PointF leftInter1 = PointF.Empty;
	PointF leftInter2 = PointF.Empty;
	PointF rightInter1 = PointF.Empty;
	PointF rightInter2 = PointF.Empty;
	GetEllipseIntr(bounds, endPoint, leftKnee, ref leftInter1, ref leftInter2);
	GetEllipseIntr(bounds, endPoint, rightKnee, ref rightInter1, ref rightInter2);

	PointF leftInter = startPoint;
	if (!float.IsNaN(leftInter1.X) && !float.IsNaN(leftInter1.Y))
	{
		leftInter = Distance(leftInter1, endPoint) < Distance(leftInter2, endPoint) ? leftInter1 : leftInter2;
	}

	PointF rightInter = startPoint;
	if (!float.IsNaN(rightInter2.X) && !float.IsNaN(rightInter2.Y))
	{
		rightInter = Distance(rightInter1, endPoint) < Distance(rightInter2, endPoint) ? rightInter1 : rightInter2;
	}

	float leftKneeAngle = 0;
	float rightKneeAngle = 0;
	PointF center = new PointF(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);
	GeometryConvert.DekartToPolar(center, leftInter, ref leftKneeAngle, ref radius);
	GeometryConvert.DekartToPolar(center, rightInter, ref rightKneeAngle, ref radius);

	leftKneeAngle = (-leftKneeAngle + 360) % 360;
	rightKneeAngle = (-rightKneeAngle + 360) % 360;

	GraphicsPath path = new GraphicsPath();
	path.AddLine(endPoint, leftInter);
	path.AddArc(bounds, leftKneeAngle, (rightKneeAngle - leftKneeAngle +
	  360) % 360);
	path.AddLine(rightInter, endPoint);

	// Use the same brush as the origin node's
	using (Brush brush =
	  e.Link.Origin.Brush.CreateGdiBrush(path.GetBounds()))
	e.Graphics.FillPath(brush, path);

	path.Dispose();
}

static void GetEllipseIntr(RectangleF rcBox, PointF pt1, PointF pt2, ref PointF out1, ref PointF out2)
{
	RectangleF rc = new RectangleF(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y),
		Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));

	float x1 = pt1.X;
	float y1 = pt1.Y;
	float x2 = pt2.X;
	float y2 = pt2.Y;

	if (Math.Abs(x1 - x2) > 0.0001)
	{
		float cx = (rcBox.Left + rcBox.Right) / 2;
		float cy = (rcBox.Top + rcBox.Bottom) / 2;
		float ea = (rcBox.Right - rcBox.Left) / 2;
		float eb = (rcBox.Bottom - rcBox.Top) / 2;
		float a = (y1 - y2) / (x1 - x2);
		float b = (x1 * y2 - x2 * y1) / (x1 - x2);

		double A = eb * eb + a * a * ea * ea;
		double B = 2 * a * (b - cy) * ea * ea - 2 * cx * eb * eb;
		double C = eb * eb * cx * cx + ea * ea * (b - cy) * (b - cy) - ea * ea * eb * eb;

		float X1, X2, Y1, Y2;

		double D = Math.Sqrt(B * B - 4 * A * C);
		X1 = (float)((-B + D) / (2 * A));
		X2 = (float)((-B - D) / (2 * A));
		Y1 = a * X1 + b;
		Y2 = a * X2 + b;

		out1.X = X1; out1.Y = Y1;
		out2.X = X2; out2.Y = Y2;
	}
	else
	{
		float cx = (rcBox.Left + rcBox.Right) / 2;
		float cy = (rcBox.Top + rcBox.Bottom) / 2;
		float ea = (rcBox.Right - rcBox.Left) / 2;
		float eb = (rcBox.Bottom - rcBox.Top) / 2;

		float X = x1;
		float Y1 = cy - (float)Math.Sqrt((1 - (X - cx) * (X - cx) / (ea * ea)) * eb * eb);
		float Y2 = cy + (float)Math.Sqrt((1 - (X - cx) * (X - cx) / (ea * ea)) * eb * eb);

		out1.X = X; out1.Y = Y1;
		out2.X = X; out2.Y = Y2;
	}
} 



If you also set the link.Dynamic property, the arrow will automatically adjust its position between the nodes. The code that finds intersection points works for Shape.Ellipse. You will have to modify it if you need rectangular nodes.

I hope that helps,
Stoyan
  
Back to top
 
IP Logged
 
jf2020
Junior Member
**
Offline


I love FlowChart!

Posts: 63
Joined: Feb 23rd, 2006
Re: Callout shapes
Reply #2 - Jul 7th, 2010 at 8:09pm
Print Post  
Hi Stoyo,

Thanks for the fast answer. The key is to use a combination of a shapeNode and a link while my tries were just using a node...

JF
  
Back to top
 
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint