import re, math def fmt(string): ## string.format(**vars()) using tags {expression!format} by CMG Lee def f(tag): i_sep = tag.rfind('!'); return (re.sub('\.0$', '', str(eval(tag[1:-1]))) if (i_sep < 0) else ('{:%s}' % tag[i_sep + 1:-1]).format(eval(tag[1:i_sep]))) return (re.sub(r'(?<!{){[^{}]+}', lambda m:f(m.group()), string) .replace('{{', '{').replace('}}', '}')) def append(obj, string): return obj.append(fmt(string)) def tabbify(cellss, separator='|'): cellpadss = [list(rows) + [''] * (len(max(cellss, key=len)) - len(rows)) for rows in cellss] fmts = ['%%%ds' % (max([len(str(cell)) for cell in cols])) for cols in zip(*cellpadss)] return '\n'.join([separator.join(fmts) % tuple(rows) for rows in cellpadss]) def format_sign(x): return ('%+d' % (x)).replace('+', '+ ').replace('-','− ') def scale_x(x): return 50 * x def scale_y(y): return -y n_search = 20 cubic_bez_dx = 15 quad_bez_dx = 15 linear_dx = 15 fmt_out = '{id}\ |{root0}|{root1}|{root2}|{max_x}|{max_y}|{min_x}|{min_y}|{inf_x}|{inf_y}|{inf_c}|{inf_m}\ |{cubic_b}|{cubic_c}|{cubic_d}|{quad_b}|{quad_c}\ ' double_dash = '-' * 2 outss = [] outs = [] for i_pass in range(2): ## 0: to find best values, 1: output SVG id_best = None if (i_pass == 1): outss = sorted(outss, key=lambda outs:[max(abs(int(outs[5])), abs(int(outs[7]))), max(abs(int(outs[1])), abs(int(outs[3])))]) id_best = int(outss[0][0]) print(tabbify([fmt_out.replace('{','').replace('}','').split('|')] + outss)) id = 0 for k2 in range(-n_search, n_search + 1): for k1 in range(k2 + 1, n_search + 1): for k0 in range(k1 + 1, n_search + 1): (root0, root1, root2) = (-k0, -k1, -k2) if (root0 == 0 or root0 == root1 or root1 == 0 or root1 == root2 or root2 == 0 or root2 == root0): continue cubic_a = 1 cubic_b = k0 + k1 + k2 cubic_c = k0 * k1 + k1 * k2 + k2 * k0 cubic_d = k0 * k1 * k2 quad_a = cubic_a * 3 quad_b = cubic_b * 2 quad_c = cubic_c linear_a = quad_a * 2 linear_b = quad_b if (cubic_a == 0 or quad_a == 0 or linear_a == 0 or cubic_b == 0 or quad_b == 0 or linear_b == 0 or cubic_c == 0 or quad_c == 0 or cubic_d == 0): continue sqrt_disc = (4 * (k0 ** 2 + k1 ** 2 + k2 ** 2 - quad_c)) ** 0.5 if ((quad_b + sqrt_disc) % linear_a != 0 or (quad_b - sqrt_disc) % linear_a != 0): continue (max_x, min_x) = [(-quad_b + sign * sqrt_disc) / linear_a for sign in (-1,1)] quad_bez_x1 = inf_x = (max_x + min_x) / 2 linear_x0 = inf_x - linear_dx linear_x1 = inf_x + linear_dx cubic_bez_x0 = inf_x - cubic_bez_dx cubic_bez_x3 = inf_x + cubic_bez_dx (inf_y, max_y, min_y, cubic_bez_y0, cubic_bez_y3) = [ cubic_a * x ** 3 + cubic_b * x ** 2 + cubic_c * x + cubic_d for x in (inf_x, max_x, min_x, cubic_bez_x0, cubic_bez_x3)] quad_bez_x0 = inf_x - quad_bez_dx quad_bez_x2 = inf_x + quad_bez_dx (inf_m, quad_bez_y0, quad_bez_y2, cubic_bez_m0, cubic_bez_m3) = [ quad_a * x ** 2 + quad_b * x + quad_c for x in (inf_x, quad_bez_x0, quad_bez_x2, cubic_bez_x0, cubic_bez_x3)] inf_c = inf_y - inf_m * inf_x quad_bez_y1 = ((linear_a * quad_bez_x0 + quad_b) * (quad_bez_x1 - quad_bez_x0) + quad_bez_y0) cubic_bez_x1 = (3 * inf_x - cubic_bez_dx) // 3 ## not sure how to get this cubic_bez_x2 = inf_x + (inf_x - cubic_bez_x1) cubic_bez_y1 = cubic_bez_m0 * (cubic_bez_x1 - cubic_bez_x0) + cubic_bez_y0 cubic_bez_y2 = cubic_bez_m3 * (cubic_bez_x2 - cubic_bez_x3) + cubic_bez_y3 if (id == id_best): path_cubic = fmt('''M {scale_x(cubic_bez_x0)},{scale_y(cubic_bez_y0)} C\ {scale_x(cubic_bez_x1)},{scale_y(cubic_bez_y1)}\ {scale_x(cubic_bez_x2)},{scale_y(cubic_bez_y2)}\ {scale_x(cubic_bez_x3)},{scale_y(cubic_bez_y3)}''') path_quad = fmt('''M {scale_x(quad_bez_x0)},{scale_y(quad_bez_y0)} Q\ {scale_x(quad_bez_x1)},{scale_y(quad_bez_y1)}\ {scale_x(quad_bez_x2)},{scale_y(quad_bez_y2)}''') path_linear = fmt('''M {scale_x(linear_x0)},{scale_y(linear_x0 * linear_a + linear_b)} L\ {scale_x(linear_x1)},{scale_y(linear_x1 * linear_a + linear_b)}''') path_tangent = fmt('''M {scale_x(float(min_y - inf_c) / inf_m)!.0f},{scale_y(min_y)} L\ {scale_x(float(max_y - inf_c) / inf_m)!.0f},{scale_y(max_y)}''') append(outs,'''\ <use xlink:href="#axes"/> <g stroke-width="4"> <g mask="url(#mask_line)"> <path class="line_cubic" d="{path_cubic}"/> <path class="line_quad" d="{path_quad}" stroke-dasharray="20,5"/> <path class="line_linear" d="{path_linear}" stroke-dasharray="6,4" stroke-width="6"/> <path class="line_tangent" d="{path_tangent}" stroke-dasharray="25,5,5,5,5,5"/> </g> <g class="line_concav"> <path d="M {scale_x(max_x)},{scale_y(max_y)} V 0 M {scale_x(min_x)},{scale_y(min_y)} V 0 M {scale_x(inf_x)},{scale_y(inf_y)} V 500" stroke-dasharray="20,5,5,5"/> <path d="M -630 460 Q -630 470 -620 470 H 35 Q 45 470 45 460 M 55 460 Q 55 470 65 470 H 620 Q 630 470 630 460"/> </g> </g> <g stroke-width="8"> <g class="label_cubic"> <text class="equation" x="-70" y="-630"><tspan class="var">f</tspan><tspan dx="0.2ex">(</tspan><tspan class="var">x</tspan><tspan>) = </tspan><tspan class="var">x</tspan><tspan>³ {format_sign(cubic_b)}</tspan><tspan class="var">x</tspan><tspan>² {format_sign(cubic_c)}</tspan><tspan class="var">x</tspan><tspan> {format_sign(cubic_d)}</tspan></text> <g transform="translate({scale_x(root0)}, 0)"><use xlink:href="#root"/><text x="0.5ex" y="2ex">root ({root0})</text></g> <g transform="translate({scale_x(root1)}, 0)"><use xlink:href="#root"/><text x="0.5ex" y="2ex">root ({root1})</text></g> <g transform="translate({scale_x(root2)}, 0)"><use xlink:href="#root"/><text x="-0.5ex" y="2ex" class="end">root ({root2})</text></g> <g transform="translate({scale_x(max_x)},{scale_y(max_y)})"><use xlink:href="#tp" /><text x="-9ex" y="-0.8ex">turning point, stationary point & local maximum ({max_x}, {max_y})</text></g> <g transform="translate({scale_x(min_x)},{scale_y(min_y)})"><use xlink:href="#tp" /><text x="5ex" y="2ex" class="end">turning point, stationary point & local minimum ({min_x}, {min_y})</text></g> <g transform="translate({scale_x(inf_x)},{scale_y(inf_y)})"><use xlink:href="#ip" /><text y="-1ex">falling inflection point ({inf_x}, {inf_y})</text></g> <!-{double_dash} <g transform="translate(0 ,{scale_y(cubic_d)})"><use xlink:href="#yi" /><text x="0.5ex">y-intercept ({cubic_d})</text></g> {double_dash}> </g> <g class="label_quad"> <text class="equation" x="-530" y="-260"><tspan class="var">f</tspan><tspan dx="0.2ex" class="var">'</tspan><tspan dx="0.2ex">(</tspan><tspan class="var">x</tspan><tspan>) = {quad_a}</tspan><tspan class="var">x</tspan><tspan>² {format_sign(quad_b)}</tspan><tspan class="var">x</tspan><tspan> {format_sign(quad_c)}</tspan></text> <g transform="translate({scale_x(max_x)}, 0)"><use xlink:href="#root"/><text x="0.5ex" y="-0.2ex">root ({max_x})</text></g> <g transform="translate({scale_x(min_x)}, 0)"><use xlink:href="#root"/><text x="-0.3ex" y="-0.2ex" class="end">root ({min_x})</text></g> <g transform="translate({scale_x(inf_x)},{scale_y(inf_m)})"><use xlink:href="#tp" /><text x="-1ex" y="2ex" class="end"><tspan>turning point, stationary point</tspan><tspan x="-1ex" dy="2ex">& local maximum ({inf_x}, {inf_m})</tspan></text></g> <use xlink:href="#tp" transform="translate({scale_x(inf_x)},{scale_y(inf_m)})"/> </g> <g class="label_linear"> <text class="equation" x="-560" y="120"><tspan class="var">f</tspan><tspan dx="0.2ex" class="var">''</tspan><tspan dx="0.2ex">(</tspan><tspan class="var">x</tspan><tspan>) = {linear_a}</tspan><tspan class="var">x</tspan><tspan> {format_sign(linear_b)}</tspan></text> <g transform="translate({scale_x(inf_x)}, 0)"><use xlink:href="#root"/><text x="-0.5ex" y="-0.2ex" class="end">root ({inf_x})</text></g> </g> <g class="label_concav"> <text x="-295" y="505"><tspan class="var">f</tspan><tspan dx="0.2ex">(</tspan><tspan class="var">x</tspan><tspan>) curve concave (downwards)</tspan></text> <text x="345" y="505"><tspan class="var">f</tspan><tspan dx="0.2ex">(</tspan><tspan class="var">x</tspan><tspan>) convex (downwards)</tspan></text> </g> <g class="label_tangent"> <g transform="translate(-140,-860)"><text><tspan>tangent at inflection point:</tspan><tspan x="15" dy="2ex" class="var">y</tspan><tspan> = -147</tspan><tspan class="var">x</tspan><tspan> + 433</tspan></text></g> </g> </g> ''') outss.append(fmt(fmt_out).split('|')) id += 1 out_p = fmt('width="100%" height="100%" viewBox="-640 -1024 1280 1536"') ## Compile everything into an .svg file myself = open(__file__, 'r').read() ## the contents of this very file file_out = open(__file__[:__file__.rfind('.')] + '.svg', 'w') ## *.* -> *.svg try: ## use try/finally so that file is closed even if write fails file_out.write('''<?xml version="1.0" encoding="utf-8"?><!%s %s%s%s\n%s%s''' % ('-' + '-', ## because SVG comments cannot have 2 consecutive '-'s myself[ : myself.find('width',myself.find('<svg'))], ## assume width specified before height/viewBox out_p, ## replace SVG width/height/viewBox with {out_p} & dynamic SVG block with {outs} contents myself[myself.find('>',myself.find('<svg')) : myself.find('\n',myself.find('BEGIN_'+'DYNAMIC_SVG'))], '\n'.join(outs), myself[myself.rfind('\n',0,myself.find('END_'+'DYNAMIC_SVG')) : ])) finally: file_out.close() ## SVG-Python near-polyglot framework version 2 by CMG Lee (Feb 2016) -->