TL;DR: I made the thing at this site (hosted there => https://awesome-bell-1df461.netlify.com/) in react based-off the art of this guy https://www.winklerwordart.com/**

A few weeks ago a friend of mine from college met an artist named Michael Winkler (https://www.winklerwordart.com/) at a talk he was giving at some place. His art is (which I have some opinions on) consists of paintings that spell out words by connecting the lines between letters on an alphabet circle (see Figure 1). Michael Winkler and my friend spoke after the talk he gave and he mentioned that he had been trying to find/have someone write a program that would allow him to quickly mock up a word-circle. My friend thought I would bd2 make it.

The images are all SVGs (which can be programed as components using react). The components are all functional and use the hot-new-react-thing which are ‘state-hooks.’ I did have to start out by making an ipython notebook where I calculated all the relative points.


Figure 1: Circle and words (https://www.winklerwordart.com/images/Michael-Winkler-Art_Traced-Legend-2017_Website18.png)

Specifications for the circle:

  • E is at the top/north

  • Vowels (aeiou (not y)) are equidistant from each other

  • Consonants are spread equally between their bounding vowels

Figure 2: Paintings of words from Fig 1 (https://www.winklerwordart.com/images/Michael-Winkler-Art_TRACED_2017_Installed-NAP_400.png)

Regards,

Jesse

PS: check out that ‘https’ on the site (honestly it wasn't me but netlifly)!

In [62]:
import string

from math import pi

import pandas as pd
abcs = list(string.ascii_lowercase)
import numpy as maths # the british way of using numpy
In [63]:
import pygal
from IPython.display import SVG, display, HTML, display_svg

pie_chart = pygal.Pie()
pie_chart.title = 'equidistant ' + string.ascii_lowercase
abcs = list(string.ascii_lowercase)
for c in abcs: pie_chart.add(c, 360/26)
display(SVG(pie_chart.render(disable_xml_declaration=True)))
equidistant abcdefghijklmnopqrstuvwxyz13.84615385379.28248213267705147.726028777118813.84615385407.40577717816063154.6577984413473813.84615385433.0529793197484168.1184885128771813.84615385454.7335663927578187.3258126123684213.84615385471.1875414822332211.1635108789411713.84615385481.45865936374014238.2462229358499113.84615385484.95000000000005266.9999999999999413.84615385481.4586593637402295.7537770641500313.84615385471.1875414822332322.836489121058813.84615385454.73356639275784346.674187387631513.84615385433.05297931974843365.881511487122813.84615385407.40577717816075379.3422015586525613.84615385379.28248213267716386.273971222881213.84615385350.3175178673231386.273971222881213.84615385322.1942228218395379.342201558652613.84615385296.54702068025176365.881511487122913.84615385274.86643360724236346.674187387631713.84615385258.41245851776694322.8364891210589513.84615385248.1413406362599295.7537770641502613.84615385244.65267.000000000000213.84615385248.14134063625977238.246222935850213.84615385258.4124585177667211.1635108789414513.84615385274.866433607242187.3258126123686813.84615385296.54702068025136168.1184885128773813.84615385322.194222821839154.6577984413475513.84615385350.3175178673226147.72602877711887equidistant abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
In [3]:
vowels = list('aeiou')
maths.linspace(0, 360, 5+1).tolist()
Out[3]:
[0.0, 72.0, 144.0, 216.0, 288.0, 360.0]
In [65]:
vowels_pie = pygal.Pie()
vowels_pie.title = 'vowels equidistant (aeiou)'
for c in vowels: vowels_pie.add(c, 360/5)
display(SVG(vowels_pie.render(disable_xml_declaration=True)))
vowels equidistant (aeiou)72435.42239806294066169.7966081258500772479.06944043286273304.1283918741499472364.8387.1572250.5305595671373304.1283918741499472294.17760193705936169.79660812585007vowels equidistant (aeiou)aeiou
In [30]:
alphabet_ix = {c:ix for ix, c in enumerate(abcs)}
_faux_vowels = [*vowels, 'a']
the_in_betweens = [maths.linspace(0, 360, 5+1).tolist()[i:i+2] for i in range(5)]
angles_from_north = []
for naybors, in_between in zip([_faux_vowels[i:i+2] for i in range(5)], the_in_betweens):
    diff = alphabet_ix[naybors[1]] - alphabet_ix[naybors[0]]
    diff = 26+diff if diff <0 else diff
    angles_from_north.extend(maths.linspace(*in_between, diff+1).tolist()) # them angs
    
angles_from_north = list(sorted(set(angles_from_north)))
angles_diff = [angles_from_north[i+1]-angles_from_north[i] for i in range(len(angles_from_north)-1)]
print("here the angle diffs", angles_diff)
#     abcde
#     efghi
#     ijklmno
#     opqrstu
#     uvwxyza
here the angle diffs [18.0, 18.0, 18.0, 18.0, 18.0, 18.0, 18.0, 18.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 12.0]
In [18]:
thingy_spacing = pygal.Pie() # really fantastic obj naming here (take note)
thingy_spacing.title = 'All letters with equidistant vowels (aeiou)'
for c, angle in zip(abcs, angles_diff): thingy_spacing.add(c, angle)
thingy_spacing.render()
display(SVG(thingy_spacing.render(disable_xml_declaration=True)))
vowels (aeiou)18383.59560097458376148.329245877494218419.34695854370653159.945566118767618449.7588797595637182.041120240436318471.8544338812324212.4530414562934518483.4707541225058248.2043990254162518483.4707541225058285.7956009745837518471.8544338812324321.546958543706518449.7588797595637351.958879759563712424.875371.052952264700312401.92839187414995381.269440432862712377.3590948616086386.4918057284980412352.2409051383915386.4918057284980412327.6716081258501381.269440432862712304.7250000000001371.0529522647003712284.40395764598327356.28885078110912267.59660812585014337.6223980629407612255.0375132642416315.8694076655575612247.27556577183327291.980589651753912244.65267.000000000000112247.27556577183324242.019410348246312255.03751326424148218.1305923344426712267.59660812585196.3776019370593812284.4039576459832177.7111492188910812304.725162.947047735299712327.6716081258502152.7305595671372812352.24090513839155147.50819427150196vowels (aeiou)abcdefghijklmnopqrstuvwxyz
In [31]:
maths.array(list(zip(abcs, angles_from_north)))
Out[31]:
array([['a', '0.0'],
       ['b', '18.0'],
       ['c', '36.0'],
       ['d', '54.0'],
       ['e', '72.0'],
       ['f', '90.0'],
       ['g', '108.0'],
       ['h', '126.0'],
       ['i', '144.0'],
       ['j', '156.0'],
       ['k', '168.0'],
       ['l', '180.0'],
       ['m', '192.0'],
       ['n', '204.0'],
       ['o', '216.0'],
       ['p', '228.0'],
       ['q', '240.0'],
       ['r', '252.0'],
       ['s', '264.0'],
       ['t', '276.0'],
       ['u', '288.0'],
       ['v', '300.0'],
       ['w', '312.0'],
       ['x', '324.0'],
       ['y', '336.0'],
       ['z', '348.0']], dtype='<U5')
In [35]:
from svgwrite import Drawing
import numpy
import pandas as pd
xy=240
mid = xy//2
radius = ((xy/2) - 24)
# drawing = Drawing('afile.svg',height=xy, width=xy, profile='full') 

arr = maths.array(list(zip(abcs[5:]+abcs[:5], angles_from_north)))

a = arr[:,1]
a = a.astype(float)
arr[:, 1] = a
arr = numpy.vstack([arr[:,0],a,( (a)*numpy.pi/180 )- (numpy.pi/2)]).T
arr = maths.array(list(zip(abcs, angles_from_north)))
dat = pd.DataFrame(arr)
dat.columns=['abc', 'deg']
dat.deg = (dat.deg.astype(numpy.float32) -72.0)%360
dat['rad'] = dat.deg*numpy.pi / 180 -(numpy.pi/2)
dat['xk'] = numpy.cos(dat['rad'])
dat['yk'] = numpy.sin(dat['rad'])
dat['dx'] = dat['xk']*radius
dat['x_txt'] = dat['xk']*radius*1.04+mid
dat['x'] = dat['dx'] + mid
dat['dy'] = dat['yk']*radius
dat['y'] = dat['dy'] + mid
dat['y_txt'] = dat['yk']*radius*1.04 + mid
dat['toops'] =  dat[['abc','deg','x', 'y', 'x_txt', 'y_txt']].apply(tuple, axis=1)

Here is the data that is used for the svgs

In [37]:
dat.describe()
Out[37]:
deg rad xk yk dx x_txt x dy y y_txt
count 26.000000 26.000000 2.600000e+01 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000 26.000000
mean 173.076920 1.449966 -3.667978e-08 0.117076 -0.000003 120.000000 119.999992 11.239252 131.239243 131.688812
std 97.764175 1.706307 7.342519e-01 0.697581 70.488190 73.307716 70.488190 66.967766 66.967766 69.646477
min 0.000000 -1.570796 -9.945219e-01 -1.000000 -95.474106 20.706932 24.525894 -96.000000 24.000000 20.160004
25% 99.000000 0.157080 -7.043051e-01 -0.518093 -67.613283 49.682186 52.386717 -49.736946 70.263054 68.273580
50% 174.000000 1.466077 -4.371139e-08 0.206773 -0.000004 119.999992 119.999992 19.850173 139.850174 140.644180
75% 249.000000 2.775074 7.043049e-01 0.774045 67.613274 190.317810 187.613274 74.308361 194.308361 197.280685
max 342.000000 4.398230 9.945219e-01 1.000000 95.474106 219.293060 215.474106 96.000000 216.000000 219.839996

Time to actually make the SVGs that are gonna be used by react

In [42]:
drawing = Drawing('base.svg',height=xy, width=xy) 
drawing.viewbox(height=xy, width=xy)
drawing.add(drawing.rect((0, 0), (xy,xy), fill='black'))
drawing.add(drawing.rect((0+1, 0+1), (xy-(1*2),xy-(1*2)), fill='white'))
drawing.add(drawing.circle((xy/2, xy/2), r=radius+0.25))
drawing.add(drawing.circle((xy/2, xy/2), r=radius-0.25, fill='white'))
drawing.fit(scale='meet')
g = drawing.g(style=f"font-size:{int(numpy.sqrt(xy))};font-family:Helvetica;font-weight:lighter;stroke:black")
# time to make this cirlce 

pts = {}
for abc, deg, x, y, x_txt, y_txt in dat['toops'].tolist():
    pts[abc]=(x,y)
    if abc in 'aeiou':
        drawing.add(drawing.circle((x, y), r=2, fill='black'))
        drawing.add(drawing.circle((x, y), r=1.5, fill='white'))
        drawing.add(drawing.circle((x, y), r=1, fill='black'))
        drawing.add(drawing.circle((x, y), r=0.5, fill='white'))
    else:
        drawing.add(drawing.circle((x, y), r=1.5, fill='black'))
        drawing.add(drawing.circle((x, y), r=1, fill='white'))
    g.add(drawing.text(str(abc).upper(), x=[x_txt], y=[y_txt],rotate=[deg]))

drawing.add(g)
drawing.save()


string = drawing.tostring()
display(SVG(string))
ABCDEFGHIJKLMNOPQRSTUVWXYZ
In [ ]:
## This is lots of line making
In [43]:
# import os
# def mkline(cb):
#     drawing = Drawing(os.path.join('lines',cb+'.svg'),height=xy, width=xy) 
#     drawing.viewbox(height=xy, width=xy)
# #     drawing.add(drawing.rect((0, 0), (xy,xy), fill='black'))
# #     drawing.add(drawing.rect((0+1, 0+1), (xy-(1*2),xy-(1*2)), fill='white'))
#     aline = drawing.g(id=cb+'line')
#     c1, c2 = cb
#     pt1, pt2 = pts[c1], pts[c2]
#     aline.add(drawing.line(start=pt1, end=pt2, stroke='black', stroke_width='1'))
#     aline.add(drawing.circle(pt1, r=0.5, fill='black'))
#     aline.add(drawing.circle(pt2, r=0.5, fill='black'))
#     drawing.add(aline)
#     drawing.save()

# mkline('fu') # super holistic testing happening right here
# from itertools import combinations

# combos = [mkline(''.join(cb)) for cb in combinations(abcs, r=2)]
In [56]:
letter_xys = {k: {'x':v[0], 'y':v[1]} for k, v in  pts.items()}
from pprint import pprint
pprint(letter_xys)
{'a': {'x': 28.698570251464844, 'y': 90.33436584472656},
 'b': {'x': 42.334373474121094, 'y': 63.57260513305664},
 'c': {'x': 63.572601318359375, 'y': 42.334381103515625},
 'd': {'x': 90.33435821533203, 'y': 28.698577880859375},
 'e': {'x': 119.99999237060547, 'y': 24.0},
 'f': {'x': 149.66563415527344, 'y': 28.698570251464844},
 'g': {'x': 176.42738342285156, 'y': 42.33436584472656},
 'h': {'x': 197.66563415527344, 'y': 63.57261657714844},
 'i': {'x': 211.30142211914062, 'y': 90.33436584472656},
 'j': {'x': 215.47410583496094, 'y': 109.96527099609375},
 'k': {'x': 215.47410583496094, 'y': 130.03472900390625},
 'l': {'x': 211.30142211914062, 'y': 149.66563415527344},
 'm': {'x': 203.138427734375, 'y': 168.0},
 'n': {'x': 191.34190368652344, 'y': 184.23654174804688},
 'o': {'x': 176.42738342285156, 'y': 197.66563415527344},
 'p': {'x': 159.04672241210938, 'y': 207.7003631591797},
 'q': {'x': 139.9595184326172, 'y': 213.9021759033203},
 'r': {'x': 119.99999237060547, 'y': 216.0},
 's': {'x': 100.04047393798828, 'y': 213.90216064453125},
 't': {'x': 80.95325469970703, 'y': 207.70034790039062},
 'u': {'x': 63.57262420654297, 'y': 197.66563415527344},
 'v': {'x': 48.65808868408203, 'y': 184.2365264892578},
 'w': {'x': 36.86156463623047, 'y': 168.0},
 'x': {'x': 28.698562622070312, 'y': 149.66561889648438},
 'y': {'x': 24.525894165039062, 'y': 130.03472900390625},
 'z': {'x': 24.525894165039062, 'y': 109.96529388427734}}
In [60]:
# from pupy import sjson
# sjson('dato.jason_greenberg', letter_xys)