none
Showing tooltip on mouse hover

    Question

  • 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 top-left 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
    Friday, January 06, 2012 6:44 PM

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
    Sunday, January 08, 2012 4:22 PM
    Owner

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
    Sunday, January 08, 2012 4:22 PM
    Owner
  • 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 BEGIN

                   foreach (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
    Sunday, January 08, 2012 6:20 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
    Monday, January 09, 2012 7:14 PM
    Owner
  • 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

     

     

     

     
    Tuesday, January 10, 2012 4:57 PM
  • 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
    Tuesday, January 10, 2012 6:20 PM
    Owner