1
1
from __future__ import division
2
- import svgwrite
3
2
import math
4
3
import colorsys
5
4
import sys
19
18
AND 22632024504
20
19
TO 19347398077
21
20
21
+ A correctly formatted file containing about 100,000 words can be found at:
22
+
23
+ http://norvig.com/google-books-common-words.txt
24
+
22
25
To run the utility supply the path to the word file, and the path to the svg file as follows:
23
26
24
27
python wordvis.py words.txt word_chart.svg
@@ -76,13 +79,62 @@ def add_chars(parent_node, chars, count):
76
79
add_chars (self .root , word , count )
77
80
78
81
82
+ class Svg :
83
+ def __init__ (self , height , width ):
84
+ self .template = open ('template.svg' ).read ().replace ('%height%' , str (height )).replace ('%width%' , str (width ))
85
+ self .styles = []
86
+ self .content = []
87
+
88
+ def add_styles (self , selector , styles ):
89
+ styles_txt = []
90
+ for k ,v in styles .iteritems ():
91
+ styles_txt .append ('{0}:{1};' .format (k ,v ))
92
+
93
+ self .styles .append ('{0}{{{1}}}' .format (selector ,'' .join (styles_txt )))
94
+
95
+ def add_text (self , text , x , y ):
96
+ self .content .append ('<text x="{0}" y="{1}">{2}</text>' .format (x , y , text ))
97
+
98
+ def add_path (self , d , clazz ):
99
+ self .content .append ('<path d="{0}" class="{1}"/>' .format (d , clazz ))
100
+
101
+ def add_segment (self , letter , start_x1 , start_y1 , end_x1 , end_y1 , start_x2 , start_y2 , end_x2 , end_y2 , r1 , r2 ):
102
+ path = "M{0} {1} A {2} {3}, 0, 0, 1, {4} {5} L {6} {7} A {8} {9}, 0, 0, 0, {10} {11} Z" .format (
103
+ start_x1 , start_y1 ,
104
+ r1 , r1 ,
105
+ end_x1 , end_y1 ,
106
+ end_x2 , end_y2 ,
107
+ r2 , r2 ,
108
+ start_x2 , start_y2
109
+ )
110
+ self .add_path (path , letter )
111
+
112
+ def save (self , out_file ):
113
+ part1 , tmp = self .template .split ('%style%' )
114
+ part2 , part3 = tmp .split ('%substance%' )
115
+
116
+ f = open (out_file , 'w' )
117
+ f .write (part1 )
118
+ for style in self .styles :
119
+ f .write (style )
120
+ f .write (part2 )
121
+ for content in self .content :
122
+ f .write (content )
123
+ f .write (part3 )
124
+ f .close ()
125
+
126
+
79
127
class CircleDiagram :
80
- def __init__ (self , svg_file ):
128
+ def __init__ (self , svg ):
81
129
self .ring_count = 0
82
- size = MAX_RINGS * RING_DEPTH * 2
83
- self .dwg = svgwrite .Drawing (svg_file , profile = 'tiny' , size = (size , size ))
130
+ self .svg = svg
84
131
self .center = (size / 2 , size / 2 )
85
132
133
+ for letter in 'abcdefghijklmnopqrstuvwxyz' :
134
+ svg .add_styles ('.' + letter , {'fill' : self ._colour_for_letter (letter ), 'stroke' : LINE_COLOUR })
135
+
136
+ svg .add_styles ('text' , {'fill' :FONT_COLOUR , 'font-family' : FONT_NAME , 'font-size' : FONT_SIZE })
137
+
86
138
def _colour_for_letter (self , letter ):
87
139
rgb = colorsys .hls_to_rgb ((ord (letter ) - ord ('a' )) / 26 , COLOUR_LIGHTNESS , 1 )
88
140
return '#' + '' .join ('%02x' % i for i in map (lambda x : x * 255 , rgb ))
@@ -91,8 +143,6 @@ def _calc_coords(self, r, a):
91
143
return self .center [0 ] + math .sin (a ) * r , self .center [1 ] + - math .cos (a ) * r
92
144
93
145
def _draw_segment (self , letter , level , start_angle , end_angle ):
94
- d = self .dwg
95
-
96
146
r1 = RING_DEPTH * level
97
147
r2 = RING_DEPTH * (level + 1 )
98
148
@@ -101,24 +151,15 @@ def _draw_segment(self, letter, level, start_angle, end_angle):
101
151
end_x1 , end_y1 = self ._calc_coords (r1 , end_angle )
102
152
end_x2 , end_y2 = self ._calc_coords (r2 , end_angle )
103
153
104
- d .add (d .path (d = "M{0} {1} A {2} {3}, 0, 0, 1, {4} {5} L {6} {7} A {8} {9}, 0, 0, 0, {10} {11} Z" .format (
105
- start_x1 , start_y1 ,
106
- r1 , r1 ,
107
- end_x1 , end_y1 ,
108
- end_x2 , end_y2 ,
109
- r2 , r2 ,
110
- start_x2 , start_y2
111
- ), fill = self ._colour_for_letter (letter ), stroke = LINE_COLOUR ))
154
+ self .svg .add_segment (letter , start_x1 , start_y1 , end_x1 , end_y1 , start_x2 , start_y2 , end_x2 , end_y2 , r1 , r2 )
112
155
113
156
def _draw_letter (self , letter , level , start_angle , end_angle ):
114
- d = self .dwg
115
-
116
157
r1 = RING_DEPTH * level
117
158
r2 = RING_DEPTH * (level + 1 )
118
159
119
160
letter_x , letter_y = self ._calc_coords ((r1 + r2 ) / 2 , (start_angle + end_angle ) / 2 )
120
- d . add ( d . text ( letter . upper (), insert = ( letter_x - 2 , letter_y + 3 ),
121
- font_size = FONT_SIZE , fill = FONT_COLOUR , font_family = FONT_NAME ) )
161
+
162
+ self . svg . add_text ( letter . upper (), letter_x - 2 , letter_y + 3 )
122
163
123
164
def add_ring (self , parts ):
124
165
level = self .ring_count
@@ -140,8 +181,8 @@ def add_ring(self, parts):
140
181
141
182
self .ring_count += 1
142
183
143
- def save (self ):
144
- self .dwg .save ()
184
+ def save (self , svg_file ):
185
+ self .svg .save (svg_file )
145
186
146
187
147
188
class Rings :
@@ -186,10 +227,12 @@ def get(self):
186
227
word , count = line .split ('\t ' )
187
228
tree .add (word .lower ().strip (), int (count ))
188
229
189
- rings = Rings (tree )
190
- diagram = CircleDiagram (svg_file )
230
+ size = MAX_RINGS * RING_DEPTH * 2
231
+ svg = Svg (size , size )
232
+ diagram = CircleDiagram (svg )
191
233
234
+ rings = Rings (tree )
192
235
for ring in rings .get ():
193
236
diagram .add_ring (ring )
194
237
195
- diagram .save ()
238
+ diagram .save (svg_file )
0 commit comments