1
2 """
3 KhipuDraw.py generates eps, svg, and/or pdf drawings of khipu.
4
5 Some examples of its use::
6
7 KhipuDraw.py --help
8 KhipuDraw.py UR001 ## generate out/UR001.eps and out/UR001.pdf
9 KhipuDraw.py -f eps UR001 ## generate out/UR001.eps
10 KhipuDraw.py -f svg UR001 ## generate out/UR001.svg
11 KhipuDraw.py -o foo UR001 ## generate foo/UR001.{eps,pdf}
12 KhipuDraw.py all ## generate out/*.{eps,pdf}
13 KhipuDraw.py -d yourdatabase ... ## use custom database
14
15 """
16 from __future__ import division, with_statement
17 import collections, itertools, math, os, os.path, traceback, warnings
18 import KhipuDB
19 import Drawing
20
21
22 ps_centimeter = 28.3464567
23
24
25
26
27
28
29
30 special_strings = {
31 'S9 E': (0,1,1), 'S8 L9': (0,1,1), 'S1 S4 L7': (0,1,1), 'S1 S4 L6': (0,1,1),
32 'S7 L6': (0,1,0), 'S5 L3': (0,1,0), 'S5 L4': (0,1,0), 'S6 L6': (0,1,0), 'S4 L8': (0,1,0), 'S1 S9 L2': (0,1,0), 'S6 L3': (0,1,0), 'S1 S4 L3': (0,1,0), 'S4 L7': (0,1,0), 'S8 L7': (0,1,0), 'S3 L8': (0,1,0),
33 'S2 L6': (0,0,1), 'S1 S3 L6': (0,0,1), 'S3 L6': (0,0,1), 'S2 E': (0,0,1), 'S6 L5': (0,0,1), 'S1 S2 L3': (0,0,1), 'S3 L9': (0,0,1), 'S2 L3': (0,0,1), 'S8 L4': (0,0,1), 'S7 E': (0,0,1),
34 'S1 L9': (1,0,0), 'S1 L8': (1,0,0), 'S1 L3': (1,0,0), 'S1 L2': (1,0,0), 'S1 L5': (1,0,0), 'S1 L4': (1,0,0), 'S1 L7': (1,0,0), 'S2 L7': (1,0,0), 'S2 L4': (1,0,0), 'S2 L2': (1,0,0), 'S2 L5': (1,0,0), 'S1 L6': (1,0,0), 'S1 S1 L2': (1,0,0), 'S3 L7': (1,0,0), 'S3 L5': (1,0,0), 'S3 L4': (1,0,0), 'S4 E': (1,0,0), 'S3 L2': (1,0,0), 'S2 L8': (1,0,0), 'S4 L5': (1,0,0), 'S4 L2': (1,0,0), 'S6 E': (1,0,0), 'S2 L9': (1,0,0), 'S1 E': (1,0,0), 'S5 L5': (1,0,0), 'S5 L2': (1,0,0), 'S4 L6': (1,0,0), 'S4 L4': (1,0,0), 'S3 E': (1,0,0), 'S5 L9': (1,0,0), 'S4 L3': (1,0,0),
35 }
36
37
39 """Draw primary cord with accurate horizontal spacing."""
40 height = 5
41 default_width = 0.1
42
44 self.primary = (0,0)
45 self.cords = []
46 self.bottoms = []
47 self.width = self.default_width
48 @classmethod
49 - def make (Class, khipu, colormap, labelmap):
76 - def postscript (self, ps, y):
77 if not self.cords: return
78 ps.comment ('ACCURATE PRIMARY CORD')
79 ps.setgray (0)
80 ps.setlinecap ('round')
81 ps.setlinewidth ((self.width or self.default_width) * ps_centimeter)
82 with ps.group(classes=('c'+str(self.primary[0])+' cord_upper')):
83 with ps.stroke() as path:
84 path.moveto (self.primary[1] * ps_centimeter, y)
85 path.lineto (self.primary[2] * ps_centimeter, y)
86
87
88 ps.comment ('Attached cords')
89 for id, coord, bottom, width, up, color in self.cords:
90 cn = 'c'+str(id)
91 with ps.group(classes=(cn+' cord_upper'), link='#'+cn):
92 ps.setgrayorrgb (color)
93 ps.setlinewidth ((width or self.default_width) * ps_centimeter)
94 with ps.stroke() as path:
95 path.moveto (coord * ps_centimeter, y)
96 if up:
97 path.lineto(bottom * ps_centimeter, y + self.height * ps_centimeter)
98 else:
99 path.lineto(bottom * ps_centimeter, y - self.height * ps_centimeter)
100
101
102
103
104
105
106
107
109 """Hierarchical drawing of a khipu with accurate vertical spacing.
110
111 This class does not attempt to draw knots; it merely renders them as circles.
112 However, the `make` method could be used for drawing knots however you like
113 in a subclass (and `KhipuKnotTreeAccurateVertical` does exactly that).
114 """
115 relative_each_width = 4
116 relative_descent = 4
117 default_width = 0.1
118 relative_knot_radius = 1.5
119 relative_font_size = 3
120 relative_text_space = 2
121 text_angle = 80
125 @classmethod
126 - def make (Class, khipu, colormap, labelmap):
127 self = Class ()
128 minwidth = khipu.min_nonzero_thickness ()
129 if minwidth is not None: self.default_width = float(minwidth) / 2
130 primary_cord = khipu.primary_cord ()
131 def recurse (cord, x, y):
132 width = float(cord.thickness or self.default_width)
133 x += self.relative_each_width * width/2
134 self.cords.append ((cord.cord_id,
135 (x, y), (x, y - float(cord.cord_length)), width,
136 colormap[cord], labelmap[cord], cord.termination))
137
138
139
140
141
142
143
144 for knot in cord.knots ():
145 self.knots.append (((x, y - float(knot.position ())), width,
146 knot.type_code, knot.num_turns, knot.direction))
147 newx = x
148 newx += self.relative_each_width * width/2
149 for subcord in reversed (list (cord.cords ())):
150 subwidth = float(subcord.thickness or self.default_width)
151 thisy = y - float(subcord.position ())
152 self.cords.append ((subcord.cord_id, (x, thisy),
153 (newx + self.relative_each_width * subwidth/2,
154 thisy - self.relative_descent * subwidth),
155 subwidth, colormap[subcord], None, None))
156 newx = recurse (subcord, newx, thisy - self.relative_descent * subwidth)
157 return newx
158 x = 0
159 for cord in primary_cord.cords ():
160 width = float(cord.thickness or self.default_width)
161 if cord.cluster_ordinal == 1:
162 x += self.relative_each_width * width
163 x = recurse (cord, x, 0)
164 self.cords.append ((primary_cord.pcord_id, (0, 0), (x, 0),
165 float(primary_cord.thickness or self.default_width), 0, None,
166 primary_cord.termination))
167 return self
168 - def postscript (self, ps, y):
169 ps.comment ('KHIPU TREE, ACCURATE VERTICAL')
170 ys = [cord[1][1] for cord in self.cords] + \
171 [cord[2][1] for cord in self.cords] + \
172 [knot[0][1] for knot in self.knots]
173 if not ys: return
174 yoff = y - min (ys) * ps_centimeter
175 ps.comment ('Cords')
176 ps.setfont ('Times-Roman', self.relative_font_size * width * ps_centimeter)
177 for cord_id, (x1, y1), (x2, y2), width, color, label, termination in self.cords:
178 if not width: width = self.default_width
179 ps.setlinewidth (width * ps_centimeter)
180 ps.setgrayorrgb (color)
181 with ps.stroke() as path:
182 path.moveto (x1 * ps_centimeter, y1 * ps_centimeter + yoff)
183 path.lineto (x2 * ps_centimeter, y2 * ps_centimeter + yoff)
184
185 if termination == 'B':
186 draw_X (ps, x2 * ps_centimeter, y2 * ps_centimeter + yoff,
187 width * ps_centimeter * self.relative_knot_radius)
188 if label:
189 ps.setgray (0)
190 ps.text(x1 * ps_centimeter, (y1 + width) * ps_centimeter + yoff,
191 label)
192 ps.comment ('Knots')
193 last = (None, None)
194 clusteroff = 0
195 for (x, y), width, type, turns, direction in self.knots:
196 if (x, y) == last:
197 clusteroff += 2 * (width or self.default_width) * \
198 self.relative_knot_radius * ps_centimeter
199 else:
200 clusteroff = 0
201 last = (x, y)
202 with ps.fill() as path:
203 path.circle (x * ps_centimeter, y * ps_centimeter + yoff - clusteroff,
204 (width or self.default_width) * self.relative_knot_radius * \
205 ps_centimeter)
206
207
208 -def draw_X (ps, x, y, radius):
209 with ps.stroke() as path:
210 path.moveto (x - radius, y - radius)
211 path.lineto (x + radius, y + radius)
212 path.moveto (x - radius, y + radius)
213 path.lineto (x + radius, y - radius)
214
216 scale = 1.5
217 def draw (ps, knot_key, (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
218
219 ps.setgrayorrgb (fill_color)
220 with ps.fill() as path:
221 path.circle (x, y - scale / 2, outer_width * scale)
222 ps.setgrayorrgb (stroke_color)
223 ps.setlinewidth ((1-inner_ratio)/2 * outer_width)
224 with ps.stroke() as path:
225 path.circle (x, y - scale / 2, outer_width * scale)
226 return outer_width * scale
227 return draw
228
229 -def simple_knot_S (ps, knot_key, (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
230
231
232
233 scale = outer_width / 195
234 ps.comment ('Simple knot')
235 ps.setgrayorrgb (fill_color)
236 with ps.fill() as path:
237 path.circle (x-3*scale, y-271*scale, 183 * scale)
238 with ps.fill () as path:
239 path.circle (x+90*scale, y-131*scale, 131 * scale)
240 ps.setgrayorrgb (stroke_color)
241
242 ps.setlinewidth ((1-inner_ratio)/2 * outer_width)
243 ps.setlinecap ('round')
244 with ps.stroke() as path:
245 path.arcn (x-3.1*scale, y-272.2*scale, 184.9*scale, 14.6123, 120.2152)
246 with ps.stroke() as path:
247 path.arc (x-130.8*scale, y-9.9*scale, 222.5*scale, -59.1121, -2.8157)
248 with ps.stroke() as path:
249 path.arc (x+58.7*scale, y-223.3*scale, 76.5*scale, 164.3158, 259.6954)
250 with ps.stroke() as path:
251 path.arcn (x+26.5*scale, y-103.2*scale, 196.1*scale, 354.5894, 274.4478)
252 with ps.stroke() as path:
253 path.arcn (x-131.6*scale, y-210.2*scale, 103.7*scale, 71.3697, 5.6789)
254 with ps.stroke() as path:
255 path.arcn (x+116.3*scale, y-111.7*scale, 105.9*scale, 94.1480, -12.7264)
256 return 454*scale
257
258 -def reflect_knot (ps, knot, knot_key, (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
259 with ps.translate(x,y).scale(-1,1):
260 return cached_knot (ps, knot, knot_key, (0, 0, outer_width, inner_ratio, stroke_color, fill_color))
261
265
309
313
314 -def long_2_knot_S (ps, knot_key, (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
315
316
317 scale = outer_width / 195
318 ps.setgrayorrgb (fill_color)
319 with ps.fill () as path:
320 path.circle (x-2*scale, y-169*scale, 173*scale)
321 with ps.fill () as path:
322 path.circle (x-8*scale, y-361*scale, 173*scale)
323 with ps.fill () as path:
324 path.circle (x+108*scale, y-347*scale, 91*scale)
325 with ps.fill () as path:
326 path.circle (x-38*scale, y-211*scale, 173*scale)
327 with ps.fill () as path:
328 path.circle (x-51*scale, y-286*scale, 173*scale)
329 ps.setgrayorrgb (stroke_color)
330
331 ps.setlinewidth ((1-inner_ratio)/2 * outer_width)
332 ps.setlinecap ('round')
333 with ps.stroke () as path:
334 path.arcn (x+21.3*scale, y-247.5*scale, 133.7*scale, -146.6297, 140.8330)
335 with ps.stroke () as path:
336 path.arc (x-2.3*scale, y-167.6*scale, 174.5*scale, -119.8184, 60.3693)
337 with ps.stroke () as path:
338 path.arc (x-37.7*scale, y-49.9*scale, 122.9*scale, -110.6191, 13.1398)
339 with ps.stroke () as path:
340 path.arc (x+89.2*scale, y-261.1*scale, 312.0*scale, 122.5411, -137.7146)
341 with ps.stroke () as path:
342 path.arc (x+153.7*scale, y-227.4*scale, 259.6*scale, -158.3845, -125.3882)
343 with ps.stroke () as path:
344 path.arc (x+75.8*scale, y-344.9*scale, 118.6*scale, -127.5088, 47.0929)
345 with ps.stroke () as path:
346 path.arc (x-6.4*scale, y-355.8*scale, 177.8*scale, -138.7502, -31.6226)
347 with ps.stroke () as path:
348 path.moveto (x-81*scale, y-161*scale)
349 path.lineto (x-81*scale, y-0*scale)
350 return 540*scale
351
353 if k == 2: return long_2_knot_S
354 def long_k_knot_S (ps, knot_key, (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
355
356
357
358 scale = outer_width / 195 * 0.8
359 up = (9-k)*140*scale
360 if k == 3: up -= 30*scale
361 ps.setgrayorrgb (fill_color)
362 with ps.fill () as path:
363 path.circle (x+75*scale, y-124*scale, 124*scale)
364 with ps.fill () as path:
365 path.circle (x-21*scale, y-241*scale, 240*scale)
366 if k >= 4:
367 with ps.fill () as path:
368 path.circle (x+117*scale, y-361*scale, 112*scale)
369 if k >= 5:
370 with ps.fill () as path:
371 path.circle (x+114*scale, y-508*scale, 112*scale)
372 if k >= 6:
373 with ps.fill () as path:
374 path.circle (x+114*scale, y-643*scale, 112*scale)
375 if k >= 7:
376 with ps.fill () as path:
377 path.circle (x+114*scale, y-784*scale, 112*scale)
378 if k >= 8:
379 with ps.fill () as path:
380 path.circle (x+114*scale, y-925*scale, 112*scale)
381 if k >= 9:
382 with ps.fill () as path:
383 path.circle (x+111*scale, y-1072*scale, 112*scale)
384 with ps.fill () as path:
385 path.circle (x-93*scale, y-1144*scale+up, 171*scale)
386 with ps.fill () as path:
387 path.circle (x+82*scale, y-1167*scale+up, 112*scale)
388 with ps.fill () as path:
389 path.circle (x-69*scale, y-1255*scale+up, 112*scale)
390 with ps.fill () as path:
391 path.circle (x+24*scale, y-1288*scale+up, 112*scale)
392 with ps.fill () as path:
393 path.moveto (x-264*scale, y-226*scale)
394 path.lineto (x+159*scale, y-226*scale)
395 path.lineto (x+159*scale, y-1126*scale+up)
396 path.lineto (x-264*scale, y-1126*scale+up)
397 ps.setgrayorrgb (stroke_color)
398
399 ps.setlinewidth ((1-inner_ratio)/2 * outer_width)
400 ps.setlinecap ('round')
401 if k >= 5:
402 with ps.stroke () as path:
403 path.arcn (x-29.2*scale, y-32.4*scale, 366.5*scale, -51.6882, -103.3708)
404 if k >= 5:
405 with ps.stroke () as path:
406 path.arcn (x+40.6*scale, y-480.8*scale, 158.9*scale, -164.9553, 146.4629)
407 if k >= 6:
408 with ps.stroke () as path:
409 path.arcn (x-29.2*scale, y-165.4*scale, 366.5*scale, -51.6882, -103.3708)
410 if k >= 5:
411 with ps.stroke () as path:
412 path.arcn (x+123.7*scale, y-374.2*scale, 108.7*scale, 36.2225, -48.0347)
413 if k >= 6:
414 with ps.stroke () as path:
415 path.arcn (x+40.6*scale, y-621.8*scale, 158.9*scale, -164.9553, 146.4629)
416 if k >= 7:
417 with ps.stroke () as path:
418 path.arcn (x-29.2*scale, y-306.4*scale, 366.5*scale, -51.6882, -103.3708)
419 if k >= 6:
420 with ps.stroke () as path:
421 path.arcn (x+123.7*scale, y-515.2*scale, 108.7*scale, 36.2225, -48.0347)
422 if k >= 7:
423 with ps.stroke () as path:
424 path.arcn (x+40.6*scale, y-759.8*scale, 158.9*scale, -164.9553, 146.4629)
425 if k >= 8:
426 with ps.stroke () as path:
427 path.arcn (x-29.2*scale, y-444.4*scale, 366.5*scale, -51.6882, -103.3708)
428 if k >= 7:
429 with ps.stroke () as path:
430 path.arcn (x+123.7*scale, y-653.2*scale, 108.7*scale, 36.2225, -48.0347)
431 if k >= 8:
432 with ps.stroke () as path:
433 path.arcn (x+40.6*scale, y-894.8*scale, 158.9*scale, -164.9553, 146.4629)
434 if k >= 9:
435 with ps.stroke () as path:
436 path.arcn (x-29.2*scale, y-579.4*scale, 366.5*scale, -51.6882, -103.3708)
437 if k >= 8:
438 with ps.stroke () as path:
439 path.arcn (x+123.7*scale, y-788.2*scale, 108.7*scale, 36.2225, -48.0347)
440 if k >= 9:
441 with ps.stroke () as path:
442 path.arcn (x+40.6*scale, y-1035.8*scale, 158.9*scale, -164.9553, 146.4629)
443 if k >= 4:
444 with ps.stroke () as path:
445 path.arcn (x-29.2*scale, y-720.4*scale+up, 366.5*scale, -51.6882, -103.3708)
446 if k >= 9:
447 with ps.stroke () as path:
448 path.arcn (x+123.7*scale, y-929.2*scale, 108.7*scale, 36.2225, -48.0347)
449 with ps.stroke () as path:
450 path.arc (x-30.1*scale, y-7.4*scale, 123.2*scale, -119.0830, 1.5613)
451 with ps.stroke () as path:
452 path.arcn (x+58.6*scale, y-138.8*scale, 145.2*scale, 75.7951, -14.4403)
453 with ps.stroke () as path:
454 path.arcn (x+1.1*scale, y-23.5*scale, 248.6*scale, -37.5364, -117.5226)
455 with ps.stroke () as path:
456 path.arcn (x+40.6*scale, y-202.8*scale, 158.9*scale, -164.9553, 146.4629)
457 if k >= 4:
458 with ps.stroke () as path:
459 path.arcn (x+40.6*scale, y-347.8*scale, 158.9*scale, -164.9553, 146.4629)
460 with ps.stroke () as path:
461 path.arcn (x+123.9*scale, y-241.1*scale, 108.9*scale, 45.8136, -47.9755)
462 if k >= 4:
463 with ps.stroke () as path:
464 path.arcn (x+143.6*scale, y-1072.4*scale+up, 86.4*scale, 47.2854, -59.6924)
465 with ps.stroke () as path:
466 path.arcn (x+23.9*scale, y-818.7*scale+up, 366.9*scale, -63.4701, -102.4539)
467 with ps.stroke () as path:
468 path.arc (x+218.9*scale, y-985.6*scale+up, 343.5*scale, -162.8295, -117.2286)
469 with ps.stroke () as path:
470 path.arc (x+67.3*scale, y-1163.3*scale+up, 130.7*scale, -91.6330, 11.1605)
471 with ps.stroke () as path:
472 path.arcn (x+53.6*scale, y-1085.0*scale+up, 315.3*scale, -99.5645, -173.6356)
473 with ps.stroke () as path:
474 path.arc (x+44.8*scale, y-275.6*scale, 306.6*scale, 116.3868, 172.9506)
475 with ps.stroke () as path:
476 path.arcn (x+32.7*scale, y-1297.8*scale+up, 103.1*scale, 12.2140, -107.6668)
477 with ps.stroke () as path:
478 path.moveto (x-93*scale, y-115*scale)
479 path.lineto (x-93*scale, y-1*scale)
480 path.moveto (x-261*scale, y-1123*scale+up)
481 path.lineto (x-261*scale, y-241*scale)
482 return 1400*scale+up
483 return long_k_knot_S
484
486
487 knot = long_knot_S(k)
488
489 return lambda ps, (type, turns, direction), params: \
490 reflect_knot(ps, knot, (type, turns, 'S'), params)
491
492 knotCache = {}
493 -def cached_knot (ps, knot, (type, turns, direction),
494 (x, y, outer_width, inner_ratio, stroke_color, fill_color)):
510 name = uniqid(type + str(turns) + direction)
511 if name is None:
512 return knot(ps, (type, turns, direction), (x, y, outer_width, inner_ratio, stroke_color, fill_color))
513
514 with ps.defineSymbol(name):
515 height= knot(ps, (type, turns, direction), (0, 0, outer_width, inner_ratio, stroke_color, fill_color))
516 knotCache[knotid] = (name, height)
517 ps.refSymbol(name, x, y)
518 return height
519
521 fill_color = 0.5
522 stroke_color = 0
523 inner_ratio = 0.5
524 - def postscript (self, ps, y):
525 ps.comment ('KHIPU KNOT TREE, ACCURATE VERTICAL')
526 ys = [cord[1][1] for cord in self.cords] + \
527 [cord[2][1] for cord in self.cords] + \
528 [knot[0][1] for knot in self.knots]
529 if not ys: return
530 yoff = y - min (ys) * ps_centimeter
531 ps.setlinecap ('round')
532 bb = {}
533 ps.comment ('Cord borders')
534 with ps.group(classes="cord_border"):
535 for id, (x1, y1), (x2, y2), width, color, label, termination in self.cords:
536 ps.setgrayorrgb (color)
537 ps.setlinewidth (width * ps_centimeter)
538 with ps.group(classes=('c'+str(id))):
539 with ps.stroke () as path:
540 path.moveto (x1 * ps_centimeter, y1 * ps_centimeter + yoff)
541 path.lineto (x2 * ps_centimeter, y2 * ps_centimeter + yoff)
542 bb.setdefault(id, Drawing.BBox()).updateBBox(ps.bbox())
543 ps.comment ('Cord innards')
544 with ps.group(classes="cord_innard"):
545 for id, (x1, y1), (x2, y2), width, color, label, termination in self.cords:
546 if isinstance (color, tuple):
547 ps.setgrayorrgb (((color[0] + 1)/2, (color[1] + 1)/2, (color[2] + 1)/2))
548 else:
549 ps.setgrayorrgb ((color + 1)/2)
550 ps.setlinewidth (self.inner_ratio * width * ps_centimeter)
551 with ps.group(classes=('c'+str(id))):
552 with ps.stroke () as path:
553 path.moveto (x1 * ps_centimeter, y1 * ps_centimeter + yoff)
554 path.lineto (x2 * ps_centimeter, y2 * ps_centimeter + yoff)
555 ps.comment ('Labels')
556 ps.setgray (0)
557 for id, (x1, y1), (x2, y2), width, color, label, termination in self.cords:
558 ps.setfont ('Times-Roman', self.relative_font_size * width * ps_centimeter)
559 if label:
560
561 y = 0
562 with ps.translate(x1 * ps_centimeter + width * ps_centimeter,
563 (y + self.relative_text_space * width) * ps_centimeter + yoff)\
564 .rotate(self.text_angle):
565 ps.text (0, 0, label, id=('label_'+str(id)))
566
567
568 ps.comment ('Broken cords')
569 with ps.group(classes="cord_broken"):
570 for id, (x1, y1), (x2, y2), width, color, label, termination in self.cords:
571 if termination == 'B':
572 ps.setgrayorrgb (color)
573 ps.setlinewidth (width * ps_centimeter)
574 with ps.group(classes=('c'+str(id))):
575 draw_X (ps, x2 * ps_centimeter, y2 * ps_centimeter + yoff,
576 width * ps_centimeter * self.relative_knot_radius)
577 bb[id].updateBBox(ps.bbox())
578 ps.comment ('Cord views')
579 for id, bbox in bb.iteritems():
580 ps.view(bbox, id=('c'+str(id)), viewTarget='label_'+str(id))
581 ps.comment ('Knots')
582 last = (None, None)
583 clusteroff = 0
584 for (x, y), width, type, turns, direction in self.knots:
585 if (x, y) == last:
586 clusteroff += last_height
587 else:
588 clusteroff = 0
589 last = (x, y)
590 if type == 'S':
591 if direction == 'Z':
592 knot = simple_knot_Z
593 else:
594 knot = simple_knot_S
595 direction = 'S'
596 if turns not in (0, 1):
597 warnings.warn ('S knot has %d turns; not rendering' % turns)
598 elif type == 'E':
599 if direction == 'Z':
600 knot = figure_eight_knot_Z
601 else:
602 knot = figure_eight_knot_S
603 direction = 'S'
604 if turns not in (0, 1):
605 warnings.warn ('E knot has %d turns; not rendering' % turns)
606 elif type == 'L':
607 if 2 <= turns <= 9:
608 if direction == 'Z':
609 knot = long_knot_Z (turns)
610 else:
611 knot = long_knot_S (turns)
612 direction = 'S'
613 else:
614 warnings.warn ('L knot has %d turns; rendering as dot' % turns)
615 knot = dot_knot (type, turns)
616 else:
617 warnings.warn ('Unknown knot type %r; rendering as dot' % type)
618 knot = dot_knot (type, turns)
619 last_height = cached_knot (ps, knot, (type, turns, direction),
620 (x * ps_centimeter,
621 y * ps_centimeter + yoff - clusteroff,
622 width * ps_centimeter, self.inner_ratio,
623 self.stroke_color, self.fill_color))
624 return max (ys) + yoff
625
634
635
636
643
645 colors = [(1,0,0),(0,1,0),(0,0,1),
646 (0,1,1),(1,0,1),
647 (0.5,0,0),(0,0.5,0),(0,0,0.5),(0.5,0.5,0),
648 (0,0.5,0.5),(0.5,0,0.5),(0.5,0.5,0.5),
649 (1,0.5,0),(0.5,1,0),(1,0,0.5),(0.5,0,1),
650 (0,1,0.5),(0,0.5,1),
651 (1,0.5,0.5),(0.5,1,0.5),(0.5,0.5,1),
652
653 (1,0.5,1),(0.5,1,1),
654 ]
669
676
677 -class Labelmap (collections.defaultdict):
681 if isinstance (key, KhipuDB.Cord):
682 return self[key.cord_id]
685
691
701
703 name = khipu.investigator_num.encode ('utf8').replace ('/', '_')
704 outdir = options.get('outdir')
705 if outdir is not None:
706 name = os.path.join(outdir, name)
707 return name
708
714
717
719 out = options.get ('out')
720 if out is None:
721 out = default_filename (khipu, **options) + '.' + ext
722 if isinstance (out, str):
723 dirpart = os.path.dirname(out)
724 if dirpart != '' and not os.path.exists(dirpart):
725 os.makedirs(dirpart)
726 out = open (out, 'w')
727 try:
728 options['out'] = out
729 return render_vector (khipu, ps, ext, **options)
730 finally:
731 out.close ()
732 print 'Wrote', out.name
733
734
735 colormap = PopularCordColormap (khipu)
736
737 if options.get ('labelmap'):
738 labelmap = Labelmap (khipu)
739 labelmapOpt = options['labelmap']
740 if isinstance (labelmapOpt, str):
741
742 labelmapOpt = eval (open (labelmapOpt, 'r').read ())
743 else:
744 labelmap = Labelmap (khipu)
745 for k,v in labelmapOpt.iteritems():
746 labelmap[int(k)] = v
747 else:
748
749
750 labelmap = NumberLabelmap (khipu)
751
752 global knotCache
753 knotCache = {}
754
755 KhipuKnotTreeAccurateVertical.make (khipu, colormap, labelmap).postscript (ps, 0)
756 top = ps.bbox()[3]
757 ps.comment ('Khipu name')
758 link = "http://projects.csail.mit.edu/khipu/draw/%s.svg" % khipu.investigator_num
759 with ps.group(link=link, classes='khipu_title'):
760 ps.setgray (0)
761 ps.setfont ('Times-Roman', 0.5 * AccuratePrimaryCord.height * ps_centimeter)
762 ps.text (0, top + 0.25 * AccuratePrimaryCord.height * ps_centimeter,
763 khipu.investigator_num)
764 AccuratePrimaryCord.make (khipu, colormap, labelmap) \
765 .postscript (ps, top + 2 * AccuratePrimaryCord.height * ps_centimeter)
766 out.write (str (ps))
767
769 out = options.get ('out')
770 if out is None:
771 out = default_filename (khipu, **options)
772 if out.endswith ('.pdf'):
773 epsout = out[:-4] + '.eps'
774 pdfout = out
775 else:
776 epsout = out + '.eps'
777 pdfout = out + '.pdf'
778 options['out'] = epsout
779 render_ps (khipu, **options)
780 os.system ('perl /usr/bin/epstopdf ' + epsout)
781 print 'Wrote', pdfout
782
783
784
785
786
787 return pdfout
788
789 -def render (khipu, **options):
797
799 import optparse, sys
800 optparser = optparse.OptionParser (usage='%prog [options] (all | investigator_num)')
801 optparser.add_option ('-l', '--labelmap', dest='labelmap',
802 help='specify a mapping of cords to labels')
803 optparser.add_option ('-f', '--format', dest='format', default='pdf',
804 help='which output to generate: svg, eps, or pdf')
805 optparser.add_option ('-o', '--outdir', dest='outdir', default='out',
806 help='the directory in which to put the output')
807 optparser.add_option ('-d', '--db', dest='dbfile', default='khipu.db',
808 help='the name of the database file to use (or "sql")')
809 optparser.add_option ('-s', '--script', dest='script',
810 help='generate a reference in SVG output to the javascript file with the given name/URL.')
811 options, args = optparser.parse_args ()
812 if not args:
813 optparser.error ('specify khipu name or "all"')
814 if options.dbfile == 'sql':
815 options.dbfile = None
816 db = KhipuDB.KhipuDB (options.dbfile)
817 if args[0] == 'all':
818 if options.format is None:
819 options.format = 'pdf'
820 for khipu in db:
821
822 print '***', khipu.investigator_num
823 sys.stdout.flush ()
824 try:
825 render (khipu, **options.__dict__)
826 except KeyboardInterrupt:
827 return
828 except:
829 traceback.print_exc ()
830 else:
831 render (db.khipu_with_investigator_num (args[0]), **options.__dict__)
832 db.save_cache ()
833
834 if __name__ == '__main__': main ()
835