Answered by:
Showing tooltip on mouse hover

Hi, I'm working on web application (ASP.NET) and have a need to make my graph a little bit interactive: be able to show tooltips when mouse goes over the node (all nodes are rectangles). I discovered html regions and everything looks like should work. The only outstanding question: how can I find geometrical position of each of my node after it's rendered into image ? I discovered BoundingBox property of a Node and can see proper Height and Width inside the ; at the same time I cannot figure out the position of ToLeft corner of the rectangle... I expected it should be counted from the topleft corner of the resulting image (who has X=0; Y=0) but actual numbers are completely different.
Please help to find TopLeft corner coordinates or propose another solution !
Thanks!
 Edited by vvv90 Saturday, January 07, 2012 2:00 AM
Question
Answers

A good way to think about it is that you have a linear mapping from you screen to MSAGL graph coordinates.This mapping stretches and shifts the X coordinate and stretches, reverses and shifts the screen Y coordinate. The Y axis in MSAGL is pointing up, as in school geometry, that is why you need to reverse the screen Y coordinate. In MSAGL the y of Top is greater than y of the Bottom of the box.
Sample DrawingFromGeometryGraphSample calculates the Transform for Graphics
private void SetGraphicsTransform(Graphics graphics) {
RectangleF r = this.ClientRectangle;
Microsoft.Msagl.Splines.Rectangle gr = this.gleeGraph.BoundingBox;
if (r.Height > 1 && r.Width > 1) {
float scale = Math.Min(r.Width / (float)gr.Width, r.Height / (float)gr.Height);
float g0 = (float)(gr.Left + gr.Right) / 2;
float g1 = (float)(gr.Top + gr.Bottom) / 2;float c0 = (r.Left + r.Right) / 2;
float c1 = (r.Top + r.Bottom) / 2;
float dx = c0  scale * g0;
float dy = c1 + scale * g1;
graphics.Transform = new System.Drawing.Drawing2D.Matrix(scale, 0, 0, scale, dx, dy);
}
}
You just need to reverse this matrix to get your mapping.Thanks
Lev Nachmanson Marked as answer by Lev NachmansonOwner Sunday, January 08, 2012 5:30 PM
All replies

A good way to think about it is that you have a linear mapping from you screen to MSAGL graph coordinates.This mapping stretches and shifts the X coordinate and stretches, reverses and shifts the screen Y coordinate. The Y axis in MSAGL is pointing up, as in school geometry, that is why you need to reverse the screen Y coordinate. In MSAGL the y of Top is greater than y of the Bottom of the box.
Sample DrawingFromGeometryGraphSample calculates the Transform for Graphics
private void SetGraphicsTransform(Graphics graphics) {
RectangleF r = this.ClientRectangle;
Microsoft.Msagl.Splines.Rectangle gr = this.gleeGraph.BoundingBox;
if (r.Height > 1 && r.Width > 1) {
float scale = Math.Min(r.Width / (float)gr.Width, r.Height / (float)gr.Height);
float g0 = (float)(gr.Left + gr.Right) / 2;
float g1 = (float)(gr.Top + gr.Bottom) / 2;float c0 = (r.Left + r.Right) / 2;
float c1 = (r.Top + r.Bottom) / 2;
float dx = c0  scale * g0;
float dy = c1 + scale * g1;
graphics.Transform = new System.Drawing.Drawing2D.Matrix(scale, 0, 0, scale, dx, dy);
}
}
You just need to reverse this matrix to get your mapping.Thanks
Lev Nachmanson Marked as answer by Lev NachmansonOwner Sunday, January 08, 2012 5:30 PM

Hi, Lev
Thank you for your reply ! I have another set of questions:
1. Where can I find this sample ? I Inherited code from other person and have just MSAGL dlls referenced in the solution  not sure where those came from. I'm using VS2010 Ultimate edition.
2. Do I correctly understand your idea that I ONLY use MSAGL as a source of geometrical coordinates and after having those (after transformation) I should do drawing by my own ?
Or MSAGL can draw in transformed mode ?  if second is true .. than I have a problem: I can only see Graphics object (a function parameter in your example) when I do renderring in NodeRenderingOverride function (on each node level); I don't see it in the context of entire Microsoft.Msagl.Drawing.Graph...
My draft code is below :
public string GetTaskGraph(List<MyNode> tasks, List<MyLink> taskLinks)
{string graphRenderedImage;
try
{
Microsoft.Msagl.Drawing.Graph graph = new Microsoft.Msagl.Drawing.Graph("Task Graph");
graph.Attr.LayerDirection = Microsoft.Msagl.Drawing.LayerDirection.TB;
graph.Directed = false;
// MyNode/MyLink data BEGINforeach (MyNode task in tasks)
{
Node node = addNode(task, graph);
node.DrawNodeDelegate = new DelegateToOverrideNodeRendering(NodeRenderingOverride);
}
foreach (MyLink taskLink in taskLinks.Where(q => q.Previous != null))
graph.AddEdge(taskLink.Previous.ToString(), taskLink.Next.ToString());// MyNode/MyLink data END
Microsoft.Msagl.GraphViewerGdi.GraphRenderer renderer = new Microsoft.Msagl.GraphViewerGdi.GraphRenderer(graph);
renderer.CalculateLayout();
Bitmap bitmap = new Bitmap((int)Math.Round(graph.Width * 1.25), (int)Math.Round(graph.Height * 1.25), PixelFormat.Format32bppPArgb);
renderer.Render(bitmap);
MemoryStream mem = new MemoryStream();
bitmap.Save(mem, System.Drawing.Imaging.ImageFormat.Png);
graphRenderedImage = Encoding.Default.GetString(mem.ToArray());
}
catch (Exception e)
{
....
}
return graphRenderedImage;
}Thank you a lot in advance !!!
 Edited by vvv90 Sunday, January 08, 2012 7:37 PM

GraphRenderer draws the geometry from Microsoft.Msagl.GeometryGraph.
I think you can get to the GeometryGraph from Microsoft.Msagl.Drawing.Node by node.Attr.GeometryNode.Parent.
To fit GeometryGraph into the the image GraphRenderer sets the Graphics.Transform as in the sample.
If you purchase MSAGL you will have the sample, as it comes with the distribution. I will paste it below.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.Msagl; using Microsoft.Msagl.Splines; using P = Microsoft.Msagl.Point; namespace DrawingFromMsaglGraph { public partial class Form1 : Form { GeometryGraph gleeGraph; public Form1() { InitializeComponent(); this.SizeChanged += new EventHandler(Form1_SizeChanged); } void Form1_SizeChanged(object sender, EventArgs e) { this.Invalidate(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (gleeGraph == null) gleeGraph = CreateAndLayoutGraph(); DrawFromGraph(e.Graphics); } private void DrawFromGraph(Graphics graphics) { SetGraphicsTransform(graphics); Pen pen = new Pen(Brushes.Black); DrawNodes(pen,graphics); DrawEdges(pen,graphics); } private void SetGraphicsTransform(Graphics graphics) { RectangleF r = this.ClientRectangle; Microsoft.Msagl.Splines.Rectangle gr = this.gleeGraph.BoundingBox; if (r.Height > 1 && r.Width > 1) { float scale = Math.Min(r.Width / (float)gr.Width, r.Height / (float)gr.Height); float g0 = (float)(gr.Left + gr.Right) / 2; float g1 = (float)(gr.Top + gr.Bottom) / 2; float c0 = (r.Left + r.Right) / 2; float c1 = (r.Top + r.Bottom) / 2; float dx = c0  scale * g0; float dy = c1 + scale * g1; graphics.Transform = new System.Drawing.Drawing2D.Matrix(scale, 0, 0, scale, dx, dy); } } private void DrawEdges( Pen pen, Graphics graphics) { foreach (Edge e in gleeGraph.Edges) DrawEdge(e, pen, graphics); } private void DrawEdge(Edge e, Pen pen, Graphics graphics) { ICurve curve = e.Curve; Curve c = curve as Curve; if (c != null) { foreach (ICurve s in c.Segments) { LineSegment l = s as LineSegment; if (l != null) graphics.DrawLine(pen, MsaglPointToDrawingPoint(l.Start), MsaglPointToDrawingPoint(l.End)); CubicBezierSegment cs = s as CubicBezierSegment; if (cs != null) graphics.DrawBezier(pen, MsaglPointToDrawingPoint(cs.B(0)), MsaglPointToDrawingPoint(cs.B(1)), MsaglPointToDrawingPoint(cs.B(2)), MsaglPointToDrawingPoint(cs.B(3))); } if (e.ArrowheadAtSource) DrawArrow(e, pen, graphics, e.Curve.Start, e.ArrowheadAtSourcePosition); if (e.ArrowheadAtTarget) DrawArrow(e, pen, graphics, e.Curve.End, e.ArrowheadAtTargetPosition); } } private void DrawArrow(Edge e, Pen pen, Graphics graphics, P start, P end) { PointF[] points; float arrowAngle = 30; P dir = end  start; P h = dir; dir /= dir.Length; P s = new P(dir.Y, dir.X); s *= h.Length * ((float)Math.Tan(arrowAngle * 0.5f * (Math.PI / 180.0))); points = new PointF[] { MsaglPointToDrawingPoint(start + s), MsaglPointToDrawingPoint(end), MsaglPointToDrawingPoint(start  s) }; graphics.FillPolygon(pen.Brush, points); } private void DrawNodes(Pen pen, Graphics graphics) { foreach (Node n in gleeGraph.NodeMap.Values) DrawNode(n, pen, graphics); } private void DrawNode(Node n, Pen pen, Graphics graphics) { ICurve curve = n.BoundaryCurve; Ellipse el = curve as Ellipse; if (el != null) { graphics.DrawEllipse(pen, new RectangleF((float)el.BBox.Left, (float)el.BBox.Bottom, (float)el.BBox.Width, (float)el.BBox.Height)); } else { Curve c = curve as Curve; foreach (ICurve seg in c.Segments) { LineSegment l=seg as LineSegment; if(l!=null) graphics.DrawLine(pen, MsaglPointToDrawingPoint(l.Start),MsaglPointToDrawingPoint(l.End)); } } } private System.Drawing.Point MsaglPointToDrawingPoint(Microsoft.Msagl.Point point) { return new System.Drawing.Point((int)point.X, (int)point.Y); } static internal GeometryGraph CreateAndLayoutGraph() { double w = 30; double h = 20; GeometryGraph graph = new GeometryGraph(); Node a = new Node("a", new Ellipse(w, h, new P())); Node b = new Node("b", CurveFactory.CreateBox(w, h, new P())); graph.AddNode(a); graph.AddNode(b); Edge e = new Edge(a, b); e.ArrowheadAtSource = true; graph.AddEdge(e); graph.AddEdge(new Edge(a,b)); graph.CalculateLayout(); //graph.Save("c:\\tmp\\saved.msagl"); return graph; } } }
Lev Nachmanson Edited by Lev NachmansonOwner Tuesday, January 10, 2012 1:20 AM

Hi, Lev
Thanks a lot for your help! I was able to find all examples, debug through and basically able to achieve what I was intending.
There is one outstanding problem left: transformation described in SetGraphicsTransform() flips Y axis. This works perfectly for DrawingFromMsaglGraph project where you don't have any text written. At the same time if you try to whire any text inside your Ellipse or rectangle it will appear flipped by 180 degrees. What would you recommend in this case ?
Again, really appreciate your time and efforts

This is the function that draws a string in a RectangleF, which is a Windows.Forms rectangle. I apply another transformation to draw the string correclty.
static void DrawStringInRectCenter(Graphics g, Brush brush, Font f, string s, RectangleF r ){ if(String.IsNullOrEmpty(s)) return; using (Matrix m = g.Transform){ using (Matrix saveM = m.Clone()){ //flip the label around the horizontal line passing through the label center float c = (r.Bottom + r.Top)/2; using (var m2 = new Matrix(1, 0, 0, 1, 0, 2*c)){ m.Multiply(m2); } g.Transform = m; using (StringFormat stringFormat = StringFormat.GenericTypographic){ g.DrawString(s, f, brush, r.Left, r.Top, stringFormat); } g.Transform = saveM; } } }
Lev Nachmanson