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