Click here to Skip to main content
13,192,207 members (32,218 online)
Click here to Skip to main content

Stats

50K views
1.8K downloads
39 bookmarked
Posted 22 Apr 2012

Visual AST for ANTLR Generated Parser Output

, 17 May 2012
Build a visual AST from ANTLR parser
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace EntMapping.Utility
{
    public class VisualAST
    {
        private static Font NodeTextFont = new Font("Verdana", 8f);
        private static SizeF MinimumNodeSize = new SizeF(32, 28);
        private static Size NodeGapping = new Size(4, 32);
        private static Dictionary<string, Pen> Pens = new Dictionary<string, Pen>();

        public IASTTreeNode ASTTreeNode { get; private set; }

        public VisualAST(IASTTreeNode astTreeNode)
        {
            ASTTreeNode = astTreeNode;
        }

        private static Bitmap CreateNodeImage(Size size, string text, Font font)
        {
            Bitmap img = new Bitmap(size.Width, size.Height);
            using (var g = Graphics.FromImage(img))
            {
                g.SmoothingMode = SmoothingMode.HighQuality;
                var rcl = new Rectangle(1, 1, img.Width - 2, img.Height - 2);
                g.FillRectangle(Brushes.White, rcl);

                LinearGradientBrush linearBrush = new LinearGradientBrush(rcl, Color.LightBlue, Color.White, LinearGradientMode.ForwardDiagonal);
                g.DrawEllipse(NodeBorderPen, rcl);
                g.FillEllipse(linearBrush, rcl);
                linearBrush.Dispose();

                var sizeText = g.MeasureString(text, font);
                g.DrawString(text, font, Brushes.Black, Math.Max(0, (size.Width - sizeText.Width) / 2), Math.Max(0, (size.Height - sizeText.Height) / 2));
            }
            return img;
        }

        private static Pen ConnectionPen
        {
            get
            {
                string penName = "ConnectionPen";
                if (!Pens.ContainsKey(penName))
                {
                    Pens.Add(penName, new Pen(Brushes.Black, 1) { EndCap = LineCap.ArrowAnchor, StartCap = LineCap.Round });
                }
                return Pens[penName];
            }
        }

        private static Pen NodeBorderPen
        {
            get
            {
                string penName = "NodeBorderPen";
                if (!Pens.ContainsKey(penName))
                {
                    Pens.Add(penName, new Pen(Color.Silver, 1));
                }
                return Pens[penName];
            }
        }

        public Image Draw()
        {
            int center;
            Image image = Draw(this.ASTTreeNode, out center);
            return image;
        }

        private Image Draw(IASTTreeNode astTreeNode, out int center)
        {
            var nodeText = astTreeNode.Text;
            var nodeSize = TextMeasurer.MeasureString("*" + nodeText + "*", NodeTextFont);
            nodeSize.Width = Math.Max(MinimumNodeSize.Width, nodeSize.Width);
            nodeSize.Height = Math.Max(MinimumNodeSize.Height, nodeSize.Height);

            var childCentres = new int[astTreeNode.Count];
            var childImages = new Image[astTreeNode.Count];
            var childSizes = new Size[astTreeNode.Count];

            var enumerator = astTreeNode.Children.GetEnumerator(); ;
            int i = 0;
            while (enumerator.MoveNext())
            {
                var currentNode = enumerator.Current;
                var lCenter = 0;
                childImages[i] = Draw(currentNode, out lCenter);
                childCentres[i] = lCenter;
                if (childImages[i] != null)
                {
                    childSizes[i] = childImages[i] != null ? childImages[i].Size : new Size();
                }
                ++i;
            }

            // draw current node and it's children
            var under = childImages.Any(nodeImg => nodeImg != null);// if true the current node has childs
            var maxHeight = astTreeNode.Count > 0 ? childSizes.Max(c => c.Height) : 0;
            var totalFreeWidth = astTreeNode.Count > 0 ? NodeGapping.Width * (astTreeNode.Count - 1) : NodeGapping.Width;
            var totalChildWidth = childSizes.Sum(s => s.Width);

            var nodeImage = CreateNodeImage(nodeSize.ToSize(), nodeText, NodeTextFont);

            var totalSize = new Size
            {
                Width = Math.Max(nodeImage.Size.Width, totalChildWidth) + totalFreeWidth,
                Height = nodeImage.Size.Height + (under ? maxHeight + NodeGapping.Height : 0)
            };

            var result = new Bitmap(totalSize.Width, totalSize.Height);
            var g = Graphics.FromImage(result);
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0), totalSize));

            var left = (totalSize.Width - nodeImage.Width) / 2;

            g.DrawImage(nodeImage, left, 0);

            center = Math.Max(totalSize.Width / 2, (nodeImage.Width + NodeGapping.Width) / 2);

            var fromLeft = 0;

            for (int j = 0; j < astTreeNode.Count; ++j)
            {
                float x1 = center;
                float y1 = nodeImage.Height;
                float y2 = nodeImage.Height + NodeGapping.Height;
                float x2 = fromLeft + childCentres[j];
                var h = y2 - y1;
                var w = x1 - x2;
                var childImg = childImages[j];
                if (childImg != null)
                {
                    g.DrawImage(childImg, fromLeft, nodeImage.Size.Height + NodeGapping.Height);
                    fromLeft += childImg.Width + NodeGapping.Width; // Prepare next child left starting point 
                    var points1 = new List<PointF>
                                  {
                                      new PointF(x1, y1),
                                      new PointF(x1 - w/6, y1 + h/3.5f),
                                      new PointF(x2 + w/6, y2 - h/3.5f),
                                      new PointF(x2, y2),
                                  };
                    g.DrawCurve(ConnectionPen, points1.ToArray(), 0.5f);
                }

                childImages[j].Dispose(); // Release child image as it aleady drawn on parent node's surface 
            }

            g.Dispose();

            return result;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matthew So (Hong Kong)
Founder Software Force
Hong Kong Hong Kong
I am always interested in finding innovative ways for building better applications and founded a technology company since 2003. Welcome to exchange any idea with you and if I am not too busy before deadline of projects, I will reply your emails. Also, if you willing to pay for consulting works and customized software development, you can leave me message.

You may also be interested in...

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171017.2 | Last Updated 17 May 2012
Article Copyright 2012 by Matthew So (Hong Kong)
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid