1from reportlab.lib import colors
2from reportlab.lib.attrmap import *
3from reportlab.pdfgen.canvas import Canvas
4from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, Polygon, Line
5
6def _getShaded(col,shd=None,shading=0.1):
7    if shd is None:
8        from reportlab.lib.colors import Blacker
9        if col: shd = Blacker(col,1-shading)
10    return shd
11
12def _getLit(col,shd=None,lighting=0.1):
13    if shd is None:
14        from reportlab.lib.colors import Whiter
15        if col: shd = Whiter(col,1-lighting)
16    return shd
17
18
19def _draw_3d_bar(G, x1, x2, y0, yhigh, xdepth, ydepth,
20                fillColor=None, fillColorShaded=None,
21                strokeColor=None, strokeWidth=1, shading=0.1):
22    fillColorShaded = _getShaded(fillColor,None,shading)
23    fillColorShadedTop = _getShaded(fillColor,None,shading/2.0)
24
25    def _add_3d_bar(x1, x2, y1, y2, xoff, yoff,
26                    G=G,strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor):
27        G.add(Polygon((x1,y1, x1+xoff,y1+yoff, x2+xoff,y2+yoff, x2,y2),
28            strokeWidth=strokeWidth, strokeColor=strokeColor, fillColor=fillColor,strokeLineJoin=1))
29
30    usd = max(y0, yhigh)
31    if xdepth or ydepth:
32        if y0!=yhigh:   #non-zero height
33            _add_3d_bar( x2, x2, y0, yhigh, xdepth, ydepth, fillColor=fillColorShaded) #side
34
35        _add_3d_bar(x1, x2, usd, usd, xdepth, ydepth, fillColor=fillColorShadedTop)    #top
36
37    G.add(Polygon((x1,y0,x2,y0,x2,yhigh,x1,yhigh),
38        strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor,strokeLineJoin=1)) #front
39
40    if xdepth or ydepth:
41        G.add(Line( x1, usd, x2, usd, strokeWidth=strokeWidth, strokeColor=strokeColor or fillColorShaded))
42
43class _YStrip:
44    def __init__(self,y0,y1, slope, fillColor, fillColorShaded, shading=0.1):
45        self.y0 = y0
46        self.y1 = y1
47        self.slope = slope
48        self.fillColor = fillColor
49        self.fillColorShaded = _getShaded(fillColor,fillColorShaded,shading)
50
51def _ystrip_poly( x0, x1, y0, y1, xoff, yoff):
52    return [x0,y0,x0+xoff,y0+yoff,x1+xoff,y1+yoff,x1,y1]
53
54
55def _make_3d_line_info( G, x0, x1, y0, y1, z0, z1,
56                    theta_x, theta_y,
57                    fillColor, fillColorShaded=None, tileWidth=1,
58                    strokeColor=None, strokeWidth=None, strokeDashArray=None,
59                    shading=0.1):
60    zwidth = abs(z1-z0)
61    xdepth = zwidth*theta_x
62    ydepth = zwidth*theta_y
63    depth_slope  = xdepth==0 and 1e150 or -ydepth/float(xdepth)
64
65    x = float(x1-x0)
66    slope = x==0 and 1e150 or (y1-y0)/x
67
68    c = slope>depth_slope and _getShaded(fillColor,fillColorShaded,shading) or fillColor
69    zy0 = z0*theta_y
70    zx0 = z0*theta_x
71
72    tileStrokeWidth = 0.6
73    if tileWidth is None:
74        D = [(x1,y1)]
75    else:
76        T = ((y1-y0)**2+(x1-x0)**2)**0.5
77        tileStrokeWidth *= tileWidth
78        if T<tileWidth:
79            D = [(x1,y1)]
80        else:
81            n = int(T/float(tileWidth))+1
82            dx = float(x1-x0)/n
83            dy = float(y1-y0)/n
84            D = []
85            a = D.append
86            for i in range(1,n):
87                a((x0+dx*i,y0+dy*i))
88
89    a = G.add
90    x_0 = x0+zx0
91    y_0 = y0+zy0
92    for x,y in D:
93        x_1 = x+zx0
94        y_1 = y+zy0
95        P = Polygon(_ystrip_poly(x_0, x_1, y_0, y_1, xdepth, ydepth),
96                    fillColor = c, strokeColor=c, strokeWidth=tileStrokeWidth)
97        a((0,z0,z1,x_0,y_0,P))
98        x_0 = x_1
99        y_0 = y_1
100
101from math import pi, sin, cos
102_pi_2 = pi*0.5
103_2pi = 2*pi
104_180_pi=180./pi
105
106def _2rad(angle):
107    return angle/_180_pi
108
109def mod_2pi(radians):
110    radians = radians % _2pi
111    if radians<-1e-6: radians += _2pi
112    return radians
113
114def _2deg(o):
115    return o*_180_pi
116
117def _360(a):
118    a %= 360
119    if a<-1e-6: a += 360
120    return a
121
122_ZERO = 1e-8
123_ONE = 1-_ZERO
124class _Segment:
125    def __init__(self,s,i,data):
126        S = data[s]
127        x0 = S[i-1][0]
128        y0 = S[i-1][1]
129        x1 = S[i][0]
130        y1 = S[i][1]
131        if x1<x0:
132            x0,y0,x1,y1 = x1,y1,x0,y0
133        # (y-y0)*(x1-x0) = (y1-y0)*(x-x0)
134        # (x1-x0)*y + (y0-y1)*x = y0*(x1-x0)+x0*(y0-y1)
135        # a*y+b*x = c
136        self.a = float(x1-x0)
137        self.b = float(y1-y0)
138        self.x0 = x0
139        self.x1 = x1
140        self.y0 = y0
141        self.y1 = y1
142        self.series = s
143        self.i = i
144        self.s = s
145
146    def __str__(self):
147        return '[(%s,%s),(%s,%s)]' % (self.x0,self.y0,self.x1,self.y1)
148
149    __repr__ = __str__
150
151    def intersect(self,o,I):
152        '''try to find an intersection with _Segment o
153        '''
154        x0 = self.x0
155        ox0 = o.x0
156        assert x0<=ox0
157        if ox0>self.x1: return 1
158        if o.s==self.s and o.i in (self.i-1,self.i+1): return
159        a = self.a
160        b = self.b
161        oa = o.a
162        ob = o.b
163        det = ob*a - oa*b
164        if -1e-8<det<1e-8: return
165        dx = x0 - ox0
166        dy = self.y0 - o.y0
167        u = (oa*dy - ob*dx)/det
168        ou = (a*dy - b*dx)/det
169        if u<0 or u>1 or ou<0 or ou>1: return
170        x = x0 + u*a
171        y = self.y0 + u*b
172        if _ZERO<u<_ONE:
173            t = self.s,self.i,x,y
174            if t not in I: I.append(t)
175        if _ZERO<ou<_ONE:
176            t = o.s,o.i,x,y
177            if t not in I:  I.append(t)
178
179def _segKey(a):
180    return (a.x0,a.x1,a.y0,a.y1,a.s,a.i)
181
182def find_intersections(data,small=0):
183    '''
184    data is a sequence of series
185    each series is a list of (x,y) coordinates
186    where x & y are ints or floats
187
188    find_intersections returns a sequence of 4-tuples
189        i, j, x, y
190
191    where i is a data index j is an insertion position for data[i]
192    and x, y are coordinates of an intersection of series data[i]
193    with some other series. If correctly implemented we get all such
194    intersections. We don't count endpoint intersections and consider
195    parallel lines as non intersecting (even when coincident).
196    We ignore segments that have an estimated size less than small.
197    '''
198
199    #find all line segments
200    S = []
201    a = S.append
202    for s in range(len(data)):
203        ds = data[s]
204        if not ds: continue
205        n = len(ds)
206        if n==1: continue
207        for i in range(1,n):
208            seg = _Segment(s,i,data)
209            if seg.a+abs(seg.b)>=small: a(seg)
210    S.sort(key=_segKey)
211    I = []
212    n = len(S)
213    for i in range(0,n-1):
214        s = S[i]
215        for j in range(i+1,n):
216            if s.intersect(S[j],I)==1: break
217    I.sort()
218    return I
219
220if __name__=='__main__':
221    from reportlab.graphics.shapes import Drawing
222    from reportlab.lib.colors import lightgrey, pink
223    D = Drawing(300,200)
224    _draw_3d_bar(D, 10, 20, 10, 50, 5, 5, fillColor=lightgrey, strokeColor=pink)
225    _draw_3d_bar(D, 30, 40, 10, 45, 5, 5, fillColor=lightgrey, strokeColor=pink)
226
227    D.save(formats=['pdf'],outDir='.',fnRoot='_draw_3d_bar')
228
229    print(find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(.2666666667,0.4),(0.1,0.4),(0.1,0.2),(0,0),(1,1)],[(0,1),(0.4,0.1),(1,0.1)]]))
230    print(find_intersections([[(0.1, 0.2), (0.1, 0.4)], [(0, 1), (0.4, 0.1)]]))
231    print(find_intersections([[(0.2, 0.4), (0.1, 0.4)], [(0.1, 0.8), (0.4, 0.1)]]))
232    print(find_intersections([[(0,0),(1,1)],[(0.4,0.1),(1,0.1)]]))
233    print(find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(0,0),(1,1)],[(0.1,0.8),(0.4,0.1),(1,0.1)]]))
234