Search code examples
c#winformsgraphicsonpaint

Defining Regions for Venn Diagram using Graphics Regions


I have created a Venn diagram using simple Graphics functions provided by WinForm in the onPaint event. Here is my code for creating the Venn.

  using (Brush brushLeft = new SolidBrush(LeftVennColor))
  {
    leftvennPath.AddEllipse(leftVenn);
    leftOnlyRegion = new Region(leftVenn);
    e.Graphics.FillEllipse(brushLeft, leftVenn);
    e.Graphics.DrawEllipse(pen, leftVenn);
  }

  using (Brush brushRight = new SolidBrush(RightVennColor))
  {
    rightvennPath.AddEllipse(rightVenn);
    rightOnlyRegion = new Region(rightVenn);
    e.Graphics.FillEllipse(brushRight, rightVenn);
    e.Graphics.DrawEllipse(pen, rightVenn);
   }

  using (GraphicsPath circle_path = new GraphicsPath())
  {
    circle_path.AddEllipse(leftVenn);
    commonRegion.Intersect(circle_path);
  }

  using (GraphicsPath circle_path = new GraphicsPath())
  {
    circle_path.AddEllipse(rightVenn);
    commonRegion.Intersect(circle_path);
  }

The Venn diagram is created, but with this code my common region is the intersection of both left and right ellipses. I want to have two separate regions out of that common area, which is separated by a line. Here is the image for that,

enter image description here

So basically, I need all these four regions separated and clickable ( different colors for each region ).. I use Region.IsVisible(e.location) in the mouse click event to handle the click event. Could someone please help?


Solution

  • Final solution:

    cx0, cy0, radius0 center and radius of left circle
    cx1, cy1, radius1 center and radius of right circle

    The function takes the regions by ref.

    private void FindRegions(int cx0, int cx1, int cy0, int cy1, int radius0, int radius1, ref Region rgnLeft, ref Region rgnRight)
        {
            //Left circle
            GraphicsPath gpL = new GraphicsPath();
            //Right circle
            GraphicsPath gpR = new GraphicsPath();
            //The right small region (yellow color)
            GraphicsPath gp = new GraphicsPath();
            //Points of intersection
            PointF pnt1 = new PointF();
            PointF pnt2 = new PointF();
    
            Graphics g = this.CreateGraphics();
    
            gpL.AddEllipse(new Rectangle(cx0 - radius0, cy0 - radius0, 2 * radius0, 2 * radius0));
            gpR.AddEllipse(new Rectangle(cx1 - radius0, cy1 - radius1, 2 * radius1, 2 * radius1));
    
            g.DrawPath(Pens.Red, gpL);
            g.DrawPath(Pens.Blue, gpR);
    
            int numPoints = FindCircleCircleIntersections((single)cx0, (single)cx1, (single)cy0, (single)cy1, (single)radius0, (single)radius1, ref pnt1, ref pnt2);
    
            if (numPoints != 2)
            {
                //No regions
                return;
            }
    
            Double theta, fe;
            Double dx = (double)pnt1.X - (double)pnt2.X;
            Double dy = (double)pnt1.Y - (double)pnt2.Y;
            Double dist = Math.Sqrt(dx * dx + dy * dy);
    
            PointF minPoint, maxPoint;
    
            if (pnt2.Y < pnt1.Y)
            {
                minPoint = pnt2;
                maxPoint = pnt1;
            }
            else
            {
                minPoint = pnt1;
                maxPoint = pnt2;
            }
    
            //theta is the angle between the three points pnt1, pnt2 and left center
            theta = Math.Acos((dist / 2D) / 100D);
            theta = (theta * 180D) / Math.PI;
            theta = 90D - theta;
            theta *= 2D;
    
            //fe is the starting angle of the point(between pnt1 and pnt2) with 
            //the smaller y coordinate. The angle is measured from x axis and clockwise
            fe = Math.Asin( Math .Abs ( (-(Double)minPoint.Y + (double)cy0) )/ (double)radius0);
            fe = (fe * 180D) / Math.PI;
    
            if (minPoint.X > cx0 && minPoint.Y >= cy0)
            {
                //fe = (90 - fe) + 270;
            }
            else if (minPoint.X > cx0 && minPoint.Y < cy0)
            {
                fe = (90D - fe) + 270D;
            }
            else if (minPoint.X == cx0 && minPoint.Y < cy0)
            {
                fe = 270D;
            }
            else
            {
                fe += 180D;
            }
    
            gp.AddArc(new Rectangle(cx0 - radius0, cy0 - radius0, 2 * radius0, 2 * radius0), (float)fe, (float)theta);
            gp.AddLine(maxPoint, minPoint);
    
            gp.CloseFigure();
    
            g.DrawPath(Pens.Green, gp);
    
            Region rgnL = new Region(gpL);
            Region rgnR = new Region(gpR);
            Region rgnInt = new Region(gpL);
            Region rgn = new Region(gp); //right small
    
            rgnInt.Intersect(rgnR);
    
            rgnInt.Exclude(rgn); //left small
    
            g.FillRegion(Brushes.DarkGreen, rgnInt);
            g.FillRegion(Brushes.DarkGray, rgn);
    
            rgnLeft = rgnInt.Clone();
            rgnRight = rgn.Clone();
    
            g.Dispose();
            rgnL.Dispose();
            rgnR.Dispose();
            rgnInt.Dispose();
            rgn.Dispose();
            gpL.Dispose();
            gpR.Dispose();
            gp.Dispose(); 
        }
    
        private int FindCircleCircleIntersections(Single cx0, Single cx1, Single cy0, Single cy1, Single radius0, Single radius1, 
                                                   ref PointF intersection1, ref PointF intersection2)
        {
            // Find the distance between the centers.
            Single dx = cx0 - cx1;
            Single dy = cy0 - cy1;
            Double dist = Math.Sqrt(dx * dx + dy * dy);
    
            // See how many solutions there are.
            if (dist > radius0 + radius1) 
            {
                //No solutions, the circles are too far apart.
                intersection1 = new PointF(Single.NaN, Single.NaN);
                intersection2 = new PointF(Single.NaN, Single.NaN);
                return 0;
            }
            else if (dist < Math.Abs(radius0 - radius1))
            {
                // No solutions, one circle contains the other.
                intersection1 = new PointF(Single.NaN, Single.NaN);
                intersection2 = new PointF(Single.NaN, Single.NaN);
                return 0;
            }
            else if ((dist == 0) && (radius0 == radius1)) 
            {
                // No solutions, the circles coincide.
                intersection1 = new PointF(Single.NaN, Single.NaN);
                intersection2 = new PointF(Single.NaN, Single.NaN);
                return 0;
            }
            else
            {
                // Find a and h.
                Double a  = (radius0 * radius0 - radius1 * radius1 + dist * dist) / (2 * dist);
                Double h  = Math.Sqrt(radius0 * radius0 - a * a);
    
                // Find P2.
                Double cx2 = cx0 + a * (cx1 - cx0) / dist;
                Double cy2 = cy0 + a * (cy1 - cy0) / dist;
    
                // Get the points P3.
                intersection1 = new PointF( (Single)(cx2 + h * (cy1 - cy0) / dist), (Single)(cy2 - h * (cx1 - cx0) / dist));
                intersection2 = new PointF( (Single)(cx2 - h * (cy1 - cy0) / dist), (Single)(cy2 + h * (cx1 - cx0) / dist));
    
                // See if we have 1 or 2 solutions.
                if (dist == radius0 + radius1) return 1;
                return 2;
            }
        }
    

    EDIT

    Region has only a Fill method and no Draw one. So you cant do it with regions. GraphicPath however HAS both Fill and Draw.
    You said that you need to validate if a point is inside the region BUT you can do the same with GraphicPath

    myGraphicPath.IsVisible();
    

    So, dont use regions but paths. It is better for another reason. GraphicPath can draw AntiAlias but regions dont. Set

    g.SmoothingMode = SmoothingMode.AntiAlias;
    

    To enable AntiAlias. All goodies with paths!

    Change the function name from FindRegions to FindPaths and send paths as refference:

    private void FindPaths(int cx0, int cx1, int cy0, int cy1, int radius0, int radius1, ref GraphicsPath gpLeft, ref GraphicsPath gpRight) 
    

    The code is exactly the same, but add in the and:

    private void FindPaths(int cx0, int cx1, int cy0, int cy1, int radius0, int radius1, ref GraphicsPath gpLeft, ref GraphicsPath gpRight)
    {
        ...
        ...
        //Above code exactly the same    
    
        //replace these
        //rgnLeft = rgnInt.Clone();
        //rgnRight = rgn.Clone();
    
        //with these
        GraphicsPath gpLeftSmall = (GraphicsPath)gp.Clone();
        Matrix matrix = new Matrix();
    
        PointF pntf = new PointF();
    
        pntf.X = (float)(Math.Min((double)pnt1.X, (double)pnt2.X) + Math.Abs((double)(pnt1.X - pnt2.X) / 2D));
        pntf.Y = (float)(Math.Min((double)pnt1.Y, (double)pnt2.Y) + Math.Abs((double)(pnt1.Y - pnt2.Y) / 2D));
    
        matrix.RotateAt(180, pntf);
    
        gpLeftSmall.Transform(matrix);
    
        g.DrawPath(Pens.Black, gpLeftSmall); //If you want to draw it
    
        //passed by refference
        gpLeft = gpLeftSmall.Clone();
        gpRight = gp.Clone();
    
    
        g.Dispose();
    
        rgnL.Dispose();
        rgnR.Dispose();
        rgnInt.Dispose();
        rgn.Dispose();
    
        gpL.Dispose();
        gpR.Dispose();
        gp.Dispose();
        gpLeftSmall.Dispose();
    
        matrix.Dispose();   
    }
    

    Reference: Determine where two circles intersect