# Implementation of module {hacks}. # Last edited on 2021-03-16 12:09:06 by jstolfi import color import palette import rn import pyx from math import sqrt, floor, ceil, sin, cos, inf, nan, pi from PIL import Image import sys def real_quadratic_roots(A, B, C): assert A != 0 if A < 0: # Reverse signs to make {A} positive: A = -A; B = -B; C = -C # Rescale by replacing {t = M s} so that {A} is 1: M = 1/sqrt(A) A = 1; B = B*M # sys.stderr.write("A = %10.7f B = %10.7f C = %10.7f M = %12.7f \n" % (A, B, C, M)) # Now solve {s^2 + B*s + C = 0} Delta = B*B - 4*C if Delta <= 0: # Zero or one root return None, None elif Delta == inf: # Pretend the roots are infinite but symmetric: return -inf, +inf else: sD = sqrt(Delta) s0 = (-B - sD)/2; t0 = M*s0 s1 = (-B + sD)/2; t1 = M*s1 assert -inf < t0 and t0 < t1 and t1 < +inf return t0, t1 # GEOMETRY def is_point(p): if not (type(p) is tuple or type(p) is list): return False if len(p) != 2: return False for c in p: if (type(c) is not float) and (type(c) is not int): return False return True # PLOTTING def round_box(B, mrg): plo = ( floor(B[0][0] - mrg), floor(B[0][1] - mrg) ) phi = ( ceil(B[1][0] + mrg), ceil(B[1][1] + mrg) ) return (plo, phi) # ---------------------------------------------------------------------- def plot_line(c, clr, wd, p, q): if clr == None: clr = pyx.color.rgb.black peps = 0 if p != q else 1.0e-8*max(1,rn.norm(p)) sty = [ pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] c.stroke(pyx.path.line(p[0]-peps, p[1]-peps, q[0]+peps, q[1]+peps), sty) def plot_box(c, clr, dp, B): if clr == None: clr = pyx.color.rgb.black if dp == None: dp = (0,0) xp = B[0][0] + dp[0]; xsz = B[1][0] - B[0][0] yp = B[0][1] + dp[1]; ysz = B[1][1] - B[0][1] sty = [ clr ] c.fill(pyx.path.rect(xp, yp, xsz, ysz), sty) def plot_frame(c, clr, wd, dp, B, mrg): if clr == None: clr = pyx.color.rgb.black if dp == None: dp = (0,0) if mrg == None: mrg = 0 xmin = dp[0] + B[0][0] + mrg; xmax = dp[0] + B[1][0] - mrg ymin = dp[1] + B[0][1] + mrg; ymax = dp[1] + B[1][1] - mrg sty = [ pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] c.stroke(pyx.path.line( xmin, ymin, xmax, ymin), sty) c.stroke(pyx.path.line( xmax, ymin, xmax, ymax), sty) c.stroke(pyx.path.line( xmax, ymax, xmin, ymax), sty) c.stroke(pyx.path.line( xmin, ymax, xmin, ymin), sty) def plot_grid(c, clr, wd, dp, B, mrg, xstep, ystep): if clr == None: clr = pyx.color.grey(0.8) if dp == None: dp = (0,0) if mrg == None: mrg = 0 if xstep == None: xstep = 1 if ystep == None: ystep = 1 xmin = B[0][0] + mrg; xmax = B[1][0] - mrg ymin = B[0][1] + mrg; ymax = B[1][1] - mrg # Grid line index ranges, with some extra: kxmin = int(floor(xmin/xstep)) - 1; kxmax = int(ceil(xmax/xstep)) + 1 kymin = int(floor(ymin/ystep)) - 1; kymax = int(ceil(ymax/ystep)) + 1 sty = [ pyx.style.linestyle.dashed, pyx.style.linejoin.round, pyx.style.linecap.round, pyx.style.linewidth(wd), clr ] eps = 1.0e-6*wd for dkx in range(kxmax-kxmin+1): x = (kxmin+dkx)*xstep if x >= xmin+eps and x < xmax-eps: c.stroke(pyx.path.line(dp[0] + x, dp[1] + ymin, dp[0] + x, dp[1] + ymax), sty) for dky in range(kymax-kymin+1): y = (kymin+dky)*ystep if y >= ymin+eps and y < ymax-eps: c.stroke(pyx.path.line(dp[0] + xmin, dp[1] + y, dp[0] + xmax, dp[1] + y), sty) def write_plot(c, name): #sys.stderr.write("writing %s.eps, %s.png, %s.eps ...\n" % (name, name, name)) c.writeEPSfile(name + ".eps") convert_eps_to_png(name) #convert_eps_to_jpg(name) #sys.stderr.write("done.\n") def convert_eps_to_png(name): im = Image.open(name + '.eps') im.load(scale=2) fig = im.convert('RGBA') fig.save(name + '.png', lossless = True) im.close() def convert_eps_to_jpg(name): im = Image.open(name + '.eps') im.load(scale=2) fig = im.convert('RGB') fig.save(name + '.jpg', quality = 95) im.close() def adjust_dash_pattern(rdist, rlen, rgap): assert rlen > 0 if rgap <= 0: return False, None # No gaps -- might as wel use solid. if rdist <= rlen: return False, None # Not enough space for a single dash. # Compute the approx number {ngap} of gaps: xgap = (rdist - rlen)/(rgap + rlen) # Fractional number of gaps with ideal pattern. assert xgap >= 0 ngap = floor(xgap + 0.5) if ngap == 0: ngap = 1 # Check whether {ngap+1} or {ngap-1} may be better: nbest = None ebest = +inf for dn in 0, -1, +1: nk = ngap + dn if nk >= 0: rdk = nk*(rlen+rgap) + rlen edk = rdk/rdist if rdk >= rdist else rdist/rdk if edk < ebest: ebest = edk; nbest = nk assert nbest != None # Compute the adjustment factor {fac}: fac = rdist/(nbest*(rlen+rgap) + rlen) minfac = 0.75; maxfac = 1/minfac if fac < minfac or fac > maxfac: # Would have to shrink or squeeze too much. Only if {rdist} is small, so: return False, None # Okeey: dashpat = [ fac*rlen, fac*rgap ] return True, dashpat def trace_colors(nc): Ymin = 0.540; Ymax = 0.560 Smin = 0.700; Smax = 1.000 colors = [None]*nc if nc == 1: colors[0] = pyx.color.rgb(0.200, 0.600, 0.000) else: # Fill half the table with palette colors, half with complements: nh = nc//2 for k in range(nh): RGBk = palette.discrete(k, Ymin,Ymax,Smin,Smax) colors[k] = pyx.color.rgb(RGBk[0], RGBk[1], RGBk[2]) colors[nc-1-k] = pyx.color.rgb(1-RGBk[0], 1-RGBk[1], 1- RGBk[2]) if nc % 2 == 1: # Insert a gray color in the middle entry: colors[nh] = pyx.color.rgb(0.500,0.500,0.500) return colors