pyx-project / pyx

Repository of PyX, a Python package for the creation of PostScript, PDF, and SVG files.
https://pyx-project.org/
GNU General Public License v2.0
109 stars 18 forks source link

Inconsistent intersections #25

Closed Volker-Weissmann closed 3 years ago

Volker-Weissmann commented 3 years ago

Let's look at this code:

from pyx import *
from math import *

c = canvas.canvas()
angles = {"B": 0, "A": 2*pi/3, "C": 2*pi*2/3}

circles={}
for k,v in angles.items():
    circles[k] = path.circle(sin(v), cos(v), 1.7)
    c.stroke(circles[k])

def intersect(cp1, cp2):
    (int1), (int2) = cp1.intersect(cp2)
    arch1 = cp1.split(int1)[1]
    return arch1

c.stroke(intersect(circles["B"], circles["A"]), [color.rgb.red])
c.stroke(intersect(circles["C"], circles["B"]), [color.rgb.green])
c.stroke(intersect(circles["A"], circles["C"]), [color.rgb.blue])

c.writePDFfile()

As one might expect, it produces this figure

But If I replace the last part with

c.stroke(intersect(circles["A"], circles["B"]), [color.rgb.red])
c.stroke(intersect(circles["B"], circles["C"]), [color.rgb.green])
c.stroke(intersect(circles["C"], circles["A"]), [color.rgb.blue])

the results looks like this.

The way the blue line looks is inconsistent with the rest.

wobsta commented 3 years ago

PyX behaves very well defined given the fact, that circles are closed paths, but still have a begin and end point. It is the point to the right, but you may rotate it, like to the top like so path.circle(sin(v), cos(v), 1.7).transformed(trafo.rotate(90, sin(v), cos(v))). The intersect return values are ordered along the first path. Quoting from the split example of the gallery (https://nbviewer.jupyter.org/urls/sf.net/p/pyx/gallery/split/attachment/split.ipynb):

The first segment returned by the split method ends at the first splitting point, the second segment goes from the first splitting point to the second splitting point (and maybe this path element will thus have a different orientation than the original path). Finally, for an open path the last item in the list of returned segments will go from the last splitting point to the end of the path. However, for a closed path this last path item is prepended to the first segment returned by the split method. Thus for a closed path the first segment will always go from the last to the first splitting point.

Your code should probably return the shorter of the two paths created from the split method ...

Volker-Weissmann commented 3 years ago

Returning the shorter of two paths might we correct in this case, but is wrong in other cases.

IMHO, there should be a documented way how to get the intersecting area of two closed paths, e.g. for a venn diagram.

wobsta commented 3 years ago

I am not quite sure whether it is the right format to discuss on a close issue, but I don't mind. In addition I am not familiar with venn diagrams.

Regarding the area of two intersecting closed paths, there clearly are several solutions:


from pyx import *

c = canvas.canvas()

def intersect(y, p1, p2):
    c1 = path.circle(0, y, 4)
    c2 = path.circle(3, y, 2)
    c.stroke(c1)
    c.stroke(c2)
    (int1), (int2) = c1.intersect(c2)
    c1segs = c1.split(int1)
    c2segs = c2.split(int2)
    c.fill(c1segs[p1] << c2segs[p2].reversed(), [color.rgb.red])

intersect(0, 0, 0)
intersect(-10, 1, 0)
intersect(-20, 0, 1)
intersect(-30, 1, 1)

c.writePDFfile()

intersects.pdf