import cairo import pango from math import * from fringwalker import sum_list from fringutil import * import os class Hotspot: def __init__(self, is_dir, path, size, value): self.is_dir = is_dir self.path = path self.size = size self.value = value self.percentage = value*100 class SideLink(Hotspot): def __init__(self, is_dir, path, size, value, minpos, maxpos): Hotspot.__init__(self, is_dir, path, size, value) self.minx,self.miny = minpos self.maxx,self.maxy = maxpos class Segment(Hotspot): def __init__(self, is_dir, path, size, start, end): Hotspot.__init__(self, is_dir, path, size, end-start) self.start, self.end = start, end class FringRenderer: def __init__(self): self.MAX_LEVEL = 1 self.WIDTH, self.HEIGHT = 1024, 768 self.INNER_RADIUS = 100 self.RING_RADIUS = 60 self.RING_SPACE = 0 self.RINGS_MAX = 4 self.LINE_WIDTH = .5 self.LEG_LINE_WIDTH = 1 self.LABEL_UNTIL_RING = 0 self.side_links = [] self.lookup_data = [] def prepare_layouts(self,ctx): self.linklayout = ctx.create_layout() self.linklayout.set_font_description(pango.FontDescription("sans 8")) def draw_segment(self,ctx, ring, start_angle, end_angle, start_hue, end_hue, data, previouspath=""): assert isinstance(data, sum_list) if ring == 0: self.lookup_data = [] self.side_links = [] while len(self.lookup_data) <= ring: self.lookup_data.append([]) CENTERX, CENTERY = self.WIDTH/2, self.HEIGHT/2 ctx.move_to(CENTERX, CENTERY) n = len(data.data) i = 0 accumulated = 0 last = start_angle for fn, d in data.data: start = last value = self._list_value(d) accumulated += value if data.the_sum == 0: continue end = start_angle+(end_angle - start_angle)*1.0*accumulated/data.the_sum if end-start >= .01: p = previouspath+os.sep+fn self.lookup_data[ring].append(Segment(isinstance(d, sum_list), p, value, start, end)) v = start_hue + (end_hue-start_hue)*1.0*i/n color = self._choose_color(start_hue + (end_hue-start_hue)*1.0*i/n, ring) r = self.INNER_RADIUS + ring * (self.RING_RADIUS + self.RING_SPACE) ctx.move_to(CENTERX+r*cos(start*2*pi), CENTERY+r*sin(start*2*pi)) ctx.arc(CENTERX, CENTERY, r+self.RING_RADIUS, start*2*pi, end*2*pi) ctx.arc_negative(CENTERX, CENTERY, r, end*2*pi, start*2*pi) ctx.close_path() ctx.set_line_width(self.LINE_WIDTH) ctx.set_source_rgb(*color) ctx.fill_preserve() ctx.set_source_rgb(0, 0, 0) ctx.stroke() if isinstance(d, sum_list) and ring+1 < self.RINGS_MAX: self.draw_segment(ctx, ring+1, start, end, v, start_hue + (end_hue-start_hue)*1.0*(i+1)/n, d, previouspath+os.sep+fn) r += self.RING_RADIUS/2 middle = CENTERX+r*cos((start+end)*pi), CENTERY+r*sin((start+end)*pi) if ring <= 0: ctx.move_to(*middle) leg_radius = self.INNER_RADIUS + (self.RINGS_MAX+1) * (self.RING_RADIUS + self.RING_SPACE) leg_radius -= (self.RING_RADIUS + self.RING_SPACE) / 2 ctx.line_to(CENTERX+leg_radius*cos((start+end)*pi), CENTERY+leg_radius*sin((start+end)*pi)) if cos((start+end)*pi) >= 0: x = self.WIDTH/2 + leg_radius align_x = 1 else: x = self.WIDTH/2 - leg_radius align_x = 0 # get line target point y = CENTERY+leg_radius*sin((start+end)*pi) xmod = 2*(align_x*2-1) # draw line ctx.line_to(x, y) ctx.set_source_rgb(.5, .5, .5) ctx.set_line_width(self.LEG_LINE_WIDTH) ctx.stroke() # write path name and register a hotspot ctx.move_to(x+xmod,y) # draw the side link label if isinstance(d, sum_list): ctx.set_source_rgb(0,0,1) width,height = self._draw_centered_text(ctx, fn + "/", align_x) else: ctx.set_source_rgb(0.5,0.5,0.5) width,height = self._draw_centered_text(ctx, fn, align_x) if align_x == 0: width *= -1 self.__register_side_link(isinstance(d, sum_list),p,value,end-start,x,y+(height/2),x+(width),y-(height/2)) # write disk usage on segments if self.RING_RADIUS >= ctx.text_extents("55%")[3]: ctx.move_to(*middle) ctx.set_source_rgb(0, 0, 0) percent = (end-start)*100 if percent >= 8: #self._draw_centered_text2(ctx, "%.0f%%" % ((end-start)*100), pretty_size(value)) self._draw_centered_text(ctx, "%.0f%%" %percent) last = end i += 1 if ring == 0: ctx.set_source_rgb(.3,.3,.3) i = format_disk_space(data.the_sum) ctx.move_to(CENTERX, CENTERY) width,height = self._draw_centered_text(ctx, i[0], .5, 1 ) ctx.move_to(CENTERX, CENTERY+height) self._draw_centered_text(ctx, i[1], .5, 1 ) def get_hotspot_at(self,x,y): for h in self.side_links: if x >= h.minx and x <= h.maxx and \ y >= h.miny and y <= h.maxy: return h def sqr(x): return x*x CENTERX, CENTERY = self.WIDTH/2, self.HEIGHT/2 radius = sqrt(sqr(x - CENTERX) + sqr(y - CENTERY)) angle = atan2(y - CENTERY, x - CENTERX) v = angle/(2*pi) if v < 0: v+=1 if radius <= self.INNER_RADIUS: return None # A simple bisection algorithm ring = int((radius - self.INNER_RADIUS)/(self.RING_RADIUS + self.RING_SPACE)) try: data = self.lookup_data[ring] except IndexError: return None minidx, maxidx = 0, len(data)-1 p = int(maxidx*v) # Initial estimation while True: try: d = data[p] except IndexError: return None if v <= d.start: maxidx = p-1 elif v <= d.end: return d else: minidx = p+1 if minidx > maxidx: return None np = int((minidx+maxidx))/2 if np == p: return None p = np # Funny! def __register_side_link(self,is_dir,path,size,value,x0,y0,x1,y1): if x1 < x0: x0,x1 = x1,x0 # swap if y1 < y0: y0,y1 = y1,y0 self.side_links.append(SideLink(is_dir, path, size, value, (x0,y0), (x1,y1))) def hsv2rgb(self,h,s,v): if s<=0: return 255*v,255*v,255*v h=6.0*(h%1.0) f=h-int(h) p=int(255*v*(1.0-s)) q=int(255*v*(1.0-(s*f))) t=int(255*v*(1.0-(s*(1.0-f)))) v=int(255*v) return ((v,t,p), (q,v,p), (p,v,t), (p,q,v), (t,p,v), (v,p,q))[int(h)] def _list_value(self,l): if isinstance(l, sum_list): return l.the_sum return l def _choose_color(self,v, ring): color = self.hsv2rgb(v, .61, 1-ring*0.1) return color[0]/256.0, color[1]/256.0, color[2]/256.0, def _draw_centered_text(self,ctx, text, align_x = .5, align_y = .5): try: text = unicode(text) except: return self.linklayout.set_text(text) width,height = self.linklayout.get_pixel_size() ctx.rel_move_to(-width*(1-align_x), -height*align_y) ctx.show_layout(self.linklayout) return width,height def _draw_centered_text2(self,ctx, text1, text2): try: text1 = unicode(text1) text2 = unicode(text2) except: return extents1 = ctx.text_extents(text1) extents2 = ctx.text_extents(text2) h = max((extents1[3], extents2[3])) p = ctx.get_current_point() ctx.rel_move_to(-extents1[2]/2, -1) ctx.show_text(text1) ctx.move_to(*p) ctx.rel_move_to(-extents2[2]/2, h+1) ctx.show_text(text2)