source: orange/orange/orngMisc.py @ 8042:ffcb93bc9028

Revision 8042:ffcb93bc9028, 27.3 KB checked in by markotoplak, 3 years ago (diff)

Hierarchical clustering: also catch RuntimeError when importing matplotlib (or the documentation could not be built on server).

Line 
1from __future__ import with_statement
2import random, types
3
4## -> Orange.misc
5
6def getobjectname(x, default=""):
7    if type(x)==types.StringType:
8        return x
9     
10    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
11        if getattr(x, i, ""):
12            return getattr(x, i)
13
14    if hasattr(x, "__class__"):
15        r = repr(x.__class__)
16        if r[1:5]=="type":
17            return str(x.__class__)[7:-2]
18        elif r[1:6]=="class":
19            return str(x.__class__)[8:-2]
20
21    return default
22
23
24def demangleExamples(x):
25    if type(x)==types.TupleType:
26        return x
27    else:
28        return x, 0
29   
30
31## -> Orange.misc.counters
32
33class BooleanCounter:
34  def __init__(self, bits):
35    self.bits = bits
36    self.state = None
37
38  def __iter__(self):
39    if self.state:
40        return self
41    else:
42        return BooleanCounter(self.bits)
43   
44  def next(self):
45    if self.state:
46      for bit in range(self.bits-1, -1, -1):
47        self.state[bit] = (self.state[bit]+1) % 2
48        if self.state[bit]:
49          break
50      else:
51        self.state = None
52    else:
53      self.state = [0]*self.bits
54
55    if not self.state:
56        raise StopIteration, "BooleanCounter: counting finished"
57
58    return self.state
59
60
61class LimitedCounter:
62  def __init__(self, limits):
63    self.limits = limits
64    self.state = None
65   
66  def __iter__(self):
67    if self.state:
68        return self
69    else:
70        return LimitedCounter(self.limits)
71
72  def next(self):
73    if self.state:
74      i = len(self.limits)-1
75      while (i>=0) and (self.state[i]==self.limits[i]-1):
76        self.state[i] = 0
77        i -= 1
78      if i==-1:
79        self.state = None
80      else:
81        self.state[i] += 1
82    else:
83      self.state = [0]*len(self.limits)
84 
85    if not self.state:
86      raise StopIteration, "LimitedCounter: counting finished"
87
88    return self.state
89
90
91class MofNCounter:
92    def __init__(self, m, n):
93        if m > n:
94            raise TypeError, "Number of selected items exceeds the number of items"
95       
96        self.state = None
97        self.m = m
98        self.n = n
99       
100    def __iter__(self):
101        if self.state:
102            return self
103        else:
104            return MofNCounter(self.m, self.n)
105       
106    def next(self):
107        if self.state:
108            m, n, state = self.m, self.n, self.state
109            for place in range(m-1, -1, -1):
110                if state[place] + m-1-place < n-1:
111                    state[place] += 1
112                    for place in range(place+1, m):
113                        state[place] = state[place-1] + 1
114                    break
115            else:
116                self.state = None
117                raise StopIteration, "MofNCounter: counting finished"
118        else:
119            self.state = range(self.m)
120           
121        return self.state[:]
122             
123class NondecreasingCounter:
124  def __init__(self, places):
125    self.state=None
126    self.subcounter=None
127    self.places=places
128
129  def __iter__(self):
130    if self.state:
131        return self
132    else:
133        return NondecreasingCounter(self.places)
134
135  def next(self):
136    if not self.subcounter:
137      self.subcounter=BooleanCounter(self.places-1)
138    if self.subcounter.next():
139      self.state=[0]
140      for add_one in self.subcounter.state:
141        self.state.append(self.state[-1]+add_one)
142    else:
143      self.state=None
144 
145    if not self.state:
146      raise StopIteration, "NondecreasingCounter: counting finished"
147
148    return self.state
149
150
151class CanonicFuncCounter:
152  def __init__(self, places):
153    self.places = places
154    self.state = None
155
156  def __iter__(self):
157    if self.state:
158        return self
159    else:
160        return CanonicFuncCounter(self.places)
161
162  def next(self):
163    if self.state:
164      i = self.places-1
165      while (i>0) and (self.state[i]==max(self.state[:i])+1):
166        self.state[i] = 0
167        i -= 1
168      if i:
169        self.state[i] += 1
170      else:
171        self.state=None
172    else:
173      self.state = [0]*self.places
174
175    if not self.state:
176      raise StopIteration, "CanonicFuncCounter: counting finished"
177   
178    return self.state
179
180
181## Orange.misc.selection
182
183import random
184
185class BestOnTheFly:
186    def __init__(self, compare=cmp, seed = 0, callCompareOn1st = False):
187        self.randomGenerator = random.Random(seed)
188        self.compare=compare
189        self.wins=0
190        self.bestIndex, self.index = -1, -1
191        self.best = None
192        self.callCompareOn1st = callCompareOn1st
193
194    def candidate(self, x):
195        self.index += 1
196        if not self.wins:
197            self.best=x
198            self.wins=1
199            self.bestIndex=self.index
200            return 1
201        else:
202            if self.callCompareOn1st:
203                cmpr=self.compare(x[0], self.best[0])
204            else:
205                cmpr=self.compare(x, self.best)
206            if cmpr>0:
207                self.best=x
208                self.wins=1
209                self.bestIndex=self.index
210                return 1
211            elif cmpr==0:
212                self.wins=self.wins+1
213                if not self.randomGenerator.randint(0, self.wins-1):
214                    self.best=x
215                    self.bestIndex=self.index
216                    return 1
217        return 0
218
219    def winner(self):
220        return self.best
221
222    def winnerIndex(self):
223        if self.best is not None:
224            return self.bestIndex
225        else:
226            return None
227
228def selectBest(x, compare=cmp, seed = 0, callCompareOn1st = False):
229    bs=BestOnTheFly(compare, seed, callCompareOn1st)
230    for i in x:
231        bs.candidate(i)
232    return bs.winner()
233
234def selectBestIndex(x, compare=cmp, seed = 0, callCompareOn1st = False):
235    bs=BestOnTheFly(compare, seed, callCompareOn1st)
236    for i in x:
237        bs.candidate(i)
238    return bs.winnerIndex()
239
240def compare2_firstBigger(x, y):
241    return cmp(x[0], y[0])
242
243def compare2_firstSmaller(x, y):
244    return -cmp(x[0], y[0])
245
246def compare2_lastBigger(x, y):
247    return cmp(x[-1], y[-1])
248
249def compare2_lastSmaller(x, y):
250    return -cmp(x[-1], y[-1])
251
252def compare2_bigger(x, y):
253    return cmp(x, y)
254
255def compare2_smaller(x, y):
256    return -cmp(x, y)
257
258
259# Orange.misc
260
261def frange(*argw):
262    start, stop, step = 0.0, 1.0, 0.1
263    if len(argw)==1:
264        start=step=argw[0]
265    elif len(argw)==2:
266        stop, step = argw
267    elif len(argw)==3:
268        start, stop, step = argw
269    elif len(argw)>3:
270        raise AttributeError, "1-3 arguments expected"
271
272    stop+=1e-10
273    i=0
274    res=[]
275    while 1:
276        f=start+i*step
277        if f>stop:
278            break
279        res.append(f)
280        i+=1
281    return res
282
283
284# Orange.misc
285
286verbose = 0
287
288def printVerbose(text, *verb):
289    if len(verb) and verb[0] or verbose:
290        print text
291
292import sys
293
294class ConsoleProgressBar(object):
295    def __init__(self, title="", charwidth=40, step=1, output=sys.stderr):
296        self.title = title + " "
297        self.charwidth = charwidth
298        self.step = step
299        self.currstring = ""
300        self.state = 0
301        self.output = output
302
303    def clear(self, i=-1):
304        try:
305            if hasattr(self.output, "isatty") and self.output.isatty():
306                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
307            else:
308                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
309        except Exception: ## If for some reason we failed
310            self.output.write("\n")
311
312    def getstring(self):
313        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
314        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
315
316    def printline(self, string):
317        try:
318            self.clear()
319            self.output.write(string)
320            self.output.flush()
321        except Exception:
322            pass
323        self.currstring = string
324
325    def __call__(self, newstate=None):
326        if newstate == None:
327            newstate = self.state + self.step
328        if int(newstate) != int(self.state):
329            self.state = newstate
330            self.printline(self.getstring())
331        else:
332            self.state = newstate
333
334    def finish(self):
335        self.__call__(100)
336        self.output.write("\n")
337       
338def progressBarMilestones(count, iterations=100):
339    return set([int(i*count/float(iterations)) for i in range(iterations)])
340
341
342## Odkrij, kdo to uporablja
343     
344class ColorPalette(object):
345    def __init__(self, colors, gamma=None, overflow=(255, 255, 255), underflow=(255, 255, 255), unknown=(0, 0, 0)):
346        self.colors = colors
347        self.gammaFunc = lambda x, gamma:((math.exp(gamma*math.log(2*x-1)) if x > 0.5 else -math.exp(gamma*math.log(-2*x+1)) if x!=0.5 else 0.0)+1)/2.0
348        self.gamma = gamma
349        self.overflow = overflow
350        self.underflow = underflow
351        self.unknown = unknown
352
353    def get_rgb(self, val, gamma=None):
354        if val is None:
355            return self.unknown
356        gamma = self.gamma if gamma is None else gamma
357        index = int(val * (len(self.colors) - 1))
358        if val < 0.0:
359            return self.underflow
360        elif val > 1.0:
361            return self.overflow
362        elif index == len(self.colors) - 1:
363            return tuple(self.colors[-1][i] for i in range(3)) # self.colors[-1].green(), self.colors[-1].blue())
364        else:
365            red1, green1, blue1 = [self.colors[index][i] for i in range(3)] #, self.colors[index].green(), self.colors[index].blue()
366            red2, green2, blue2 = [self.colors[index + 1][i] for i in range(3)] #, self.colors[index + 1].green(), self.colors[index + 1].blue()
367            x = val * (len(self.colors) - 1) - index
368            if gamma is not None:
369                x = self.gammaFunc(x, gamma)
370            return [(c2 - c1) * x + c1 for c1, c2 in [(red1, red2), (green1, green2), (blue1, blue2)]]
371       
372    def __call__(self, val, gamma=None):
373        return self.get_rgb(val, gamma)
374   
375import math
376
377#from ColorPalette import ColorPalette
378   
379class GeneratorContextManager(object):
380   def __init__(self, gen):
381       self.gen = gen
382   def __enter__(self):
383       try:
384           return self.gen.next()
385       except StopIteration:
386           raise RuntimeError("generator didn't yield")
387   def __exit__(self, type, value, traceback):
388       if type is None:
389           try:
390               self.gen.next()
391           except StopIteration:
392               return
393           else:
394               raise RuntimeError("generator didn't stop")
395       else:
396           try:
397               self.gen.throw(type, value, traceback)
398               raise RuntimeError("generator didn't stop after throw()")
399           except StopIteration:
400               return True
401           except:
402               # only re-raise if it's *not* the exception that was
403               # passed to throw(), because __exit__() must not raise
404               # an exception unless __exit__() itself failed.  But
405               # throw() has to raise the exception to signal
406               # propagation, so this fixes the impedance mismatch
407               # between the throw() protocol and the __exit__()
408               # protocol.
409               #
410               if sys.exc_info()[1] is not value:
411                   raise
412
413def contextmanager(func):
414    def helper(*args, **kwds):
415        return GeneratorContextManager(func(*args, **kwds))
416    return helper
417
418from functools import wraps
419def with_state(func):
420    @wraps(func)
421    def wrap(self, *args, **kwargs):
422        with self.state(**kwargs):
423            r = func(self, *args)
424        return r
425    return wrap
426
427def with_gc_disabled(func):
428    import gc
429    def disabler():
430        gc.disable()
431        try:
432            yield
433        finally:
434            gc.enable()
435    @wraps(func)
436    def wrapper(*args, **kwargs):
437        with contextmanager(disabler)():
438            return func(*args, **kwargs)
439    return wrapper 
440
441
442## -> Orange.misc.render
443
444import numpy
445
446class Renderer(object):
447    render_state_attributes = ["font", "stroke_color", "fill_color", "render_hints", "transform", "gradient", "text_alignment"]
448   
449    ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER = range(3)
450     
451    def __init__(self, width, height):
452        self.width = width
453        self.height = height
454        self.render_state = {}
455        self.render_state["font"] = ("Times-Roman", 10)
456        self.render_state["fill_color"] = (0, 0, 0)
457        self.render_state["gradient"] = {}
458        self.render_state["stroke_color"] = (0, 0, 0)
459        self.render_state["stroke_width"] = 1
460        self.render_state["text_alignment"] = self.ALIGN_LEFT
461        self.render_state["transform"] = numpy.matrix(numpy.eye(3))
462        self.render_state["view_transform"] = numpy.matrix(numpy.eye(3))
463        self.render_state["render_hints"] = {}
464        self.render_state_stack = []
465       
466    def font(self):
467        return self.render_state["font"]
468   
469    def set_font(self, family, size):
470        self.render_state["font"] = family, size
471
472    def fill_color(self):
473        return self.render_state["fill_color"]
474   
475    def set_fill_color(self, color):
476        self.render_state["fill_color"] = color
477       
478    def set_gradient(self, gradient):
479        self.render_state["gradient"] = gradient
480       
481    def gradient(self):
482        return self.render_state["gradient"]
483       
484    def stroke_color(self):
485        return self.render_state["stroke_color"]
486   
487    def set_stroke_color(self, color):
488        self.render_state["stroke_color"] = color
489   
490    def stroke_width(self):
491        return self.render_state["stroke_width"]
492   
493    def set_stroke_width(self, width):
494        self.render_state["stroke_width"] = width
495       
496    def set_text_alignment(self, align):
497        self.render_state["text_alignment"] = align
498       
499    def text_alignment(self):
500        return self.render_state["text_alignment"]
501       
502    def transform(self):
503        return self.render_state["transform"]
504   
505    def set_transform(self, transform):
506        self.render_state["transform"] = transform
507       
508    def render_hints(self):
509        return self.render_state["render_hints"]
510   
511    def set_render_hints(self, hints):
512        self.render_state["render_hints"].update(hints)
513   
514    def save_render_state(self):
515        import copy
516        self.render_state_stack.append(copy.deepcopy(self.render_state))
517   
518    def restore_render_state(self):
519        self.render_state = self.render_state_stack.pop(-1)
520       
521    def apply_transform(self, transform):
522        self.render_state["transform"] = self.render_state["transform"] * transform
523         
524    def translate(self, x, y):
525        transform = numpy.eye(3)
526        transform[:, 2] = x, y, 1
527        self.apply_transform(transform)
528   
529    def rotate(self, angle):
530        angle *= 2 * math.pi / 360.0
531        transform = numpy.eye(3)
532        transform[:2, :2] = [[math.cos(angle), -math.sin(angle)], [math.sin(angle), math.cos(angle)]]
533        self.apply_transform(transform)
534   
535    def scale(self, sx, sy):
536        transform = numpy.eye(3)
537        transform[(0, 1), (0, 1)] = sx, sy
538        self.apply_transform(transform)
539       
540    def skew(self, sx, sy):
541        transform = numpy.eye(3)
542        transform[(1, 0), (0, 1)] = numpy.array([sx, sy]) * 2 * math.pi / 360.0
543        self.apply_transform(transform)
544   
545    def draw_line(self, sx, sy, ex, ey, **kwargs):
546        raise NotImplemented
547   
548    def draw_lines(self, points, **kwargs):
549        raise NotImplemented
550   
551    def draw_rect(self, x, y, w, h, **kwargs):
552        raise NotImplemented
553   
554    def draw_polygon(self, vertices, **kwargs):
555        raise NotImplemented
556
557    def draw_arch(self, something, **kwargs):
558        raise NotImplemented
559   
560    def draw_text(self, x, y, text, **kwargs):
561        raise NotImplemented
562   
563    def string_size_hint(self, text, **kwargs):
564        raise NotImpemented
565   
566    @contextmanager
567    def state(self, **kwargs):
568        self.save_render_state()
569        for key, value in kwargs.items():
570            if key in ["translate", "rotate", "scale", "skew"]:
571                getattr(self, key)(*value)
572            else:
573                getattr(self, "set_" + key)(value)
574        try:
575            yield
576        finally:
577            self.restore_render_state()
578           
579    def save(self):
580        raise NotImplemented
581   
582    def close(self, file):
583        pass
584   
585class EPSRenderer(Renderer):
586    EPS_DRAW_RECT = """/draw_rect
587{/h exch def /w exch def
588 /y exch def /x exch def
589 newpath
590 x y moveto
591 w 0 rlineto
592 0 h neg rlineto
593 w neg 0 rlineto
594 closepath
595} def"""
596   
597    EPS_SET_GRADIENT = """<< /PatternType 2
598 /Shading
599   << /ShadingType 2
600      /ColorSpace /DeviceRGB
601      /Coords [%f %f %f %f]
602      /Function
603      << /FunctionType 0
604         /Domain [0 1]
605         /Range [0 1 0 1 0 1]
606         /BitsPerSample 8
607         /Size [%i]
608         /DataSource <%s>
609      >>
610   >>
611>>
612matrix
613makepattern
614/mypattern exch def
615/Pattern setcolorspace
616mypattern setcolor
617
618"""
619
620    EPS_SHOW_FUNCTIONS = """/center_align_show
621{ dup stringwidth pop
622  2 div
623  neg
624  0 rmoveto
625  show } def
626 
627/right_align_show
628{ dup stringwidth pop
629  neg
630  0 rmoveto
631  show } def
632"""
633    def __init__(self, width, height):
634        Renderer.__init__(self, width, height)
635        from StringIO import StringIO
636        self._eps = StringIO()
637        self._eps.write("%%!PS-Adobe-3.0 EPSF-3.0\n%%%%BoundingBox: 0 0 %i %i\n" % (width, height))
638        self._eps.write(self.EPS_SHOW_FUNCTIONS)
639        self._eps.write("%f %f translate\n" % (0, self.height))
640        self.set_font(*self.render_state["font"])
641        self._inline_func = dict(stroke_color=lambda color: "%f %f %f setrgbcolor" % tuple(255.0 / c for c in color),
642                                 fill_color=lambda color:"%f %f %f setrgbcolor" % tuple(255.0 / c for c in color),
643                                 stroke_width=lambda w: "%f setlinewidth" % w)
644       
645    def set_font(self, family, size):
646        Renderer.set_font(self, family, size)
647        self._eps.write("/%s findfont %f scalefont setfont\n" % self.font())
648       
649    def set_fill_color(self, color):
650        Renderer.set_fill_color(self, color)
651        self._eps.write("%f %f %f setrgbcolor\n" % tuple(c/255.0 for c in color))
652       
653    def set_gradient(self, gradient):
654        Renderer.set_gradient(self, gradient)
655        (x1, y1, x2, y2), samples = gradient
656        binary = "".join([chr(int(c)) for p, s in samples for c in s])
657        import binascii
658        self._eps.write(self.EPS_SET_GRADIENT % (x1, y1, x2, y2, len(samples), binascii.hexlify(binary))) 
659       
660    def set_stroke_color(self, color):
661        Renderer.set_stroke_color(self, color)
662        self._eps.write("%f %f %f setrgbcolor\n" % tuple(c/255.0 for c in color))
663       
664    def set_stroke_width(self, width):
665        Renderer.set_stroke_width(self, width)
666        self._eps.write("%f setlinewidth\n" % width)
667       
668    def set_render_hints(self, hints):
669        Renderer.set_render_hints(self, hints)
670        if hints.get("linecap", None):
671            map = {"butt":0, "round":1, "rect":2}
672            self._eps.write("%i setlinecap\n" % (map.get(hints.get("linecap"), 0)))
673       
674    @with_state 
675    def draw_line(self, sx, sy, ex, ey, **kwargs):
676        self._eps.write("newpath\n%f %f moveto %f %f lineto\nstroke\n" % (sx, -sy, ex, -ey))
677       
678    @with_state
679    def draw_rect(self, x, y, w, h, **kwargs):
680        self._eps.write("newpath\n%(x)f %(y)f moveto %(w)f 0 rlineto\n0 %(h)f rlineto %(w)f neg 0 rlineto\nclosepath\n" % dict(x=x,y=-y, w=w, h=-h))
681        self._eps.write("gsave\n")
682        if self.gradient():
683            self.set_gradient(self.gradient())
684        else:
685            self.set_fill_color(self.fill_color())
686        self._eps.write("fill\ngrestore\n")
687        self.set_stroke_color(self.stroke_color())
688        self._eps.write("stroke\n")
689       
690    @with_state
691    def draw_polygon(self, vertices, **kwargs):
692        self._eps.write("newpath\n%f %f moveto\n" % vertices[0])
693        for x, y in vertices[1:]:
694            self._eps.write("%f %f lineto\n" % (x, y))
695        self._eps.write("closepath\n")
696        self._eps.write("gsave\n")
697        self.set_fill_color(self.fill_color())
698        self._eps.write("fill\ngrestore\n")
699        self.set_stroke_color(self.stroke_color())
700        self._eps.write("stroke\n")
701       
702    @with_state
703    def draw_text(self, x, y, text, **kwargs):
704        show = ["show", "right_align_show", "center_align_show"][self.text_alignment()]
705        self._eps.write("%f %f moveto (%s) %s\n" % (x, -y, text, show))
706       
707    def save_render_state(self):
708        Renderer.save_render_state(self)
709        self._eps.write("gsave\n")
710       
711    def restore_render_state(self):
712        Renderer.restore_render_state(self)
713        self._eps.write("grestore\n")
714       
715    def translate(self, dx, dy):
716        Renderer.translate(self, dx, dy)
717        self._eps.write("%f %f translate\n" % (dx, -dy))
718       
719    def rotate(self, angle):
720        Renderer.rotate(self, angle)
721        self._eps.write("%f rotate\n" % -angle)
722       
723    def scale(self, sx, sy):
724        Renderer.scale(self, sx, sy)
725        self._eps.write("%f %f scale\n" % (sx, sy))
726   
727    def skew(self, sx, sy):
728        Renderer.skew(self, sx, sy)
729        self._eps.write("%f %f skew\n" % (sx, sy))
730       
731    def save(self, filename):
732#        self._eps.close()
733        open(filename, "wb").write(self._eps.getvalue())
734       
735    def string_size_hint(self, text, **kwargs):
736        import warnings
737        warnings.warn("EpsRenderer class does not suport exact string width estimation", stacklevel=2)
738        return len(text) * self.font()[1]
739       
740       
741class PILRenderer(Renderer):
742    def __init__(self, width, height):
743        Renderer.__init__(self, width, height)
744        import Image, ImageDraw, ImageFont
745        self._pil_image = Image.new("RGB", (width, height), (255, 255, 255))
746        self._draw =  ImageDraw.Draw(self._pil_image, "RGB")
747        self._pil_font = ImageFont.load_default()
748
749    def _transform(self, x, y):
750        p = self.transform() * [[x], [y], [1]]
751        return p[0, 0], p[1, 0]
752   
753    def set_font(self, family, size):
754        Renderer.set_font(self, family, size)
755        import ImageFont
756        try:
757            self._pil_font = ImageFont.load(family + ".ttf", size)
758        except Exception:
759            import warnings
760            warnings.warn("Could not load %s.ttf font!", stacklevel=2)
761            try:
762                self._pil_font = ImageFont.load("cour.ttf", size)
763            except Exception:
764                warnings.warn("Could not load the cour.ttf font!! Loading the default", stacklevel=2)
765                self._pil_font = ImageFont.load_default()
766       
767    @with_state
768    def draw_line(self, sx, sy, ex, ey, **kwargs):
769        sx, sy = self._transform(sx, sy)
770        ex, ey = self._transform(ex, ey)
771        self._draw.line((sx, sy, ex, ey), fill=self.stroke_color(), width=int(self.stroke_width()))
772
773    @with_state
774    def draw_rect(self, x, y, w, h, **kwargs):
775        x1, y1 = self._transform(x, y)
776        x2, y2 = self._transform(x + w, y + h)
777        self._draw.rectangle((x1, y1, x2 ,y2), fill=self.fill_color(), outline=self.stroke_color())
778       
779    @with_state
780    def draw_text(self, x, y, text, **kwargs):
781        x, y = self._transform(x, y - self.font()[1])
782        self._draw.text((x, y), text, font=self._pil_font, fill=self.stroke_color())
783       
784    def save(self, file):
785        self._pil_image.save(file)
786       
787    def string_size_hint(self, text, **kwargs):
788        return self._pil_font.getsize(text)[1]
789
790class SVGRenderer(Renderer):
791    SVG_HEADER = """<?xml version="1.0" ?>
792<svg height="%f" version="1.0" width="%f" xmlns="http://www.w3.org/2000/svg">
793<defs>
794    %s
795</defs>
796    %s
797</svg>
798"""
799    def __init__(self, width, height):
800        Renderer.__init__(self, width, height)
801        self.transform_count_stack = [0]
802        import StringIO
803        self._svg = StringIO.StringIO()
804        self._defs = StringIO.StringIO()
805        self._gradients = {}
806       
807    def set_gradient(self, gradient):
808        Renderer.set_gradient(self, gradient)
809        if gradient not in self._gradients.items():
810            id = "grad%i" % len(self._gradients)
811            self._gradients[id] = gradient
812            (x1, y1, x2, y2), stops = gradient
813            (x1, y1, x2, y2) = (0, 0, 100, 0)
814           
815            self._defs.write('<linearGradient id="%s" x1="%f%%" y1="%f%%" x2="%f%%" y2="%f%%">\n' % (id, x1, y1, x2, y2))
816            for offset, color in stops:
817                self._defs.write('<stop offset="%f" style="stop-color:rgb(%i, %i, %i); stop-opacity:1"/>\n' % ((offset,) + color))
818            self._defs.write('</linearGradient>\n')
819       
820    def get_fill(self):
821        if self.render_state["gradient"]:
822            return 'style="fill:url(#%s)"' % ([key for key, gr in self._gradients.items() if gr == self.render_state["gradient"]][0])
823        else:
824            return 'fill="rgb(%i %i %i)"' % self.fill_color()
825       
826    def get_stroke(self):
827#        if self.render_state["gradient"]:
828#            return ""
829#        else:
830            return 'stroke="rgb(%i, %i, %i)"' % self.stroke_color() + ' stroke-width="%f"' % self.stroke_width()
831       
832    def get_text_alignment(self):
833        return 'text-anchor="%s"' % (["start", "end", "middle"][self.text_alignment()])
834   
835    def get_linecap(self):
836        return 'stroke-linecap="%s"' % self.render_hints().get("linecap", "butt")
837       
838    @with_state
839    def draw_line(self, sx, sy, ex, ey):
840        self._svg.write('<line x1="%f" y1="%f" x2="%f" y2="%f" %s %s/>\n' % ((sx, sy, ex, ey) + (self.get_stroke(), self.get_linecap())))
841       
842#    @with_state
843#    def draw_lines(self):
844       
845    @with_state
846    def draw_rect(self, x, y, w, h):
847        self._svg.write('<rect x="%f" y="%f" width="%f" height="%f" %s %s/>\n' % ((x, y, w, h) + (self.get_fill(),) + (self.get_stroke(),)))
848           
849    @with_state
850    def draw_polygon(self, vertices, **kwargs):
851        path = "M %f %f L " % vertices[0]
852        path += " L ".join("%f %f" % vert for vert in vertices[1:])
853        path += " z"
854        self._svg.write('<path d="%s" %s/>' % ((path,) + (self.get_stroke(),)))
855       
856    @with_state
857    def draw_text(self, x, y, text):
858        self._svg.write('<text x="%f" y="%f" font-family="%s" font-size="%f" %s>%s</text>\n' % ((x, y) + self.font() +(self.get_text_alignment(), text)))
859       
860    def translate(self, x, y):
861        self._svg.write('<g transform="translate(%f,%f)">\n' % (x, y))
862        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
863       
864    def rotate(self, angle):
865        self._svg.write('<g transform="rotate(%f)">\n' % angle)
866        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
867       
868    def scale(self, sx, sy):
869        self._svg.write('<g transform="scale(%f,%f)">\n' % (sx, sy))
870        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 1
871       
872    def skew(self, sx, sy):
873        self._svg.write('<g transform="skewX(%f)">' % sx)
874        self._svg.write('<g transform="skewY(%f)">' % sy)
875        self.transform_count_stack[-1] = self.transform_count_stack[-1] + 2
876
877    def save_render_state(self):
878        Renderer.save_render_state(self)
879        self.transform_count_stack.append(0)
880       
881    def restore_render_state(self):
882        Renderer.restore_render_state(self)
883        count = self.transform_count_stack.pop(-1)
884        self._svg.write('</g>\n' * count)
885       
886    def save(self, filename):
887        open(filename, "wb").write(self.SVG_HEADER % (self.height, self.width, self._defs.getvalue(), self._svg.getvalue()))
888       
889class CairoRenderer(Renderer):
890    def __init__(self, width, height):
891        Renderer.__init__(self, width, height)
892       
Note: See TracBrowser for help on using the repository browser.