def plot_box(texts): # Make a context to calculate font sizes with # There must be a better way to do this, I just don't know it! surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) ctx = cairo.Context(surface) ctx.select_font_face("sans-serif", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(15) max_text_len = 0 for leveltxt, style in texts: # Calculate surface size so that texts will fit cur_len = ctx.text_extents(leveltxt)[4] if cur_len > max_text_len: max_text_len = cur_len + 16 box_height = 26 * len(texts) if len(texts) == 1: box_height += 8 # Longest text fits lenght of 268, nicer if markings are of # regular size if possible if max_text_len < 268: max_text_len = 268 # Make the actual surface surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(max_text_len), box_height) ctx = cairo.Context(surface) ctx.select_font_face(*CAIRO_BOLD) ctx.set_font_size(15) ctx.set_line_width(0.6) # Fill surface with white width, height = surface.get_width(), surface.get_height() ctx.set_source_rgb(1.0, 1.0, 1.0) ctx.rectangle(0, 0, width, height) ctx.fill() ctx.set_line_width(10) ctx.set_source_rgb(1.0, 0, 0) ctx.rectangle(0, 0, width, height) ctx.stroke() curpos = 5 for text, style in texts: curpos = curpos + 18 ctx.select_font_face(*style) text_len = ctx.text_extents(text)[4] + 4 ctx.move_to(max_text_len / 2 - (text_len / 2) + 2, curpos) ctx.show_text(text) return cairo_surface_to_png(surface)
def _calculate_textlen(text): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) ctx = cairo.Context(surface) ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(12) # Calculate surface size so that texts will fit text_len = ctx.text_extents(text)[4] return text_len
def plot_error(request, text="No data"): # Just return an error message surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, _calculate_textlen(text), 25) ctx = cairo.Context(surface) ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(12) ctx.set_line_width(0.6) ctx.set_source_rgb(0, 0, 0) ctx.move_to(0, 20) ctx.show_text(request.getText(text)) return cairo_surface_to_png(surface)
def draw_topology(request, args, key): args = [x.strip() for x in args.split(',')] topology, flowfile, color = '', '', '' rotate, width = '', '' graph = GraphShower(request.page.page_name, request) # take flow file specification from arguments as flow=k.csv, # otherwise assume that the argument specifies the topology for arg in args: if '=' in arg: key, val = [x.strip() for x in arg.split('=', 1)] if key == 'color': color = val if key == 'flow': flowfile = val if key == 'rotate': rotate = True if key == 'width': try: width = float(val) except ValueError: pass else: topology = arg _ = request.getText # Get all containers args = 'CategoryContainer, %s=/.+/' % (topology) # Note, metatable_parseargs deals with permissions pagelist, metakeys, styles = metatable_parseargs(request, args, get_all_keys=True) if not pagelist: return (False, "", render_error("%s: %s" % (_("No such topology or empty topology"), topology))) coords = dict() images = dict() aliases = dict() areas = dict() colors = dict() # Make a context to calculate font sizes with # There must be a better way to do this, I just don't know it! surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0) ctx = cairo.Context(surface) ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(12) allcoords = list() for page in pagelist: data = get_metas(request, page, [topology, 'gwikishapefile', 'tia-name', color], checkAccess=False, formatLinks=True) crds = [x.split(',') for x in data.get(topology, list)] if not crds: continue crds = [x.strip() for x in crds[0]] if not len(crds) == 2: continue try: start_x, start_y = int(crds[0]), int(crds[1]) except ValueError: continue coords[page] = start_x, start_y allcoords.append((start_x, start_y)) img = data.get('gwikishapefile', list()) if color: clr = data.get(color, list()) if clr: colors[page] = clr[0] alias = data.get('tia-name', list()) # Newer versions of analyzer do not use aliases anymore if not alias: alias = [page] aliases[page] = alias[0] if img: # Get attachment name, name of the page it's on, strip # link artifacts, find filesys name img = img[0].split(':')[1] pname = '/'.join(img.split('/')[:-1]) img = img.split('/')[-1] img = img.split('|')[0] img = img.rstrip('}').rstrip(']]') imgname = AttachFile.getFilename(request, pname, img) try: images[page] = cairo.ImageSurface.create_from_png(imgname) end_x = start_x + images[page].get_width() end_y = start_y + images[page].get_height() except cairo.Error: end_x = start_x end_y = start_y pass text_len = ctx.text_extents(aliases[page])[4] text_end = start_x + text_len if text_end > end_x: end_x = text_end # If there was no image or a problem with loading the image if page not in images: # Lack of image -> black 10x10 rectangle is drawn end_x, end_y = start_x + 10, start_y + 10 allcoords.append((end_x, end_y)) if flowfile: flowcoords = list() flowname = AttachFile.getFilename(request, topology, flowfile) try: flows = csv.reader(file(flowname, 'r').readlines(), delimiter=';') except IOError: return (False, "", render_error("%s: %s" % (_("No such flowfile as attachment on topology page"), flowfile))) flows.next() for line in flows: if not line: continue try: flowcoords.append((line[0], line[6])) except IndexError: # Pasted broken lines? pass max_x = max([x[0] for x in allcoords]) min_x = min([x[0] for x in allcoords]) max_y = max([x[1] for x in allcoords]) min_y = min([x[1] for x in allcoords]) # Make room for text under pictures if rotate: surface_y = max_y - min_y surface_x = max_x - min_x + 25 else: surface_y = max_y - min_y + 25 surface_x = max_x - min_x try: # Get background image, if any toponame = AttachFile.getFilename(request, topology, 'shapefile.png') background = cairo.ImageSurface.create_from_png(toponame) h = background.get_height() w = background.get_width() diff_x = w - surface_x diff_y = h - surface_y if diff_x > 0: surface_x = w else: diff_x = 0 if diff_y > 0: surface_y = h else: diff_y = 0 except cairo.Error: background = None diff_x = 0 diff_y = 0 pass # Setup Cairo surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(surface_x), int(surface_y)) # request.write(repr([surface_x, surface_y])) ctx = cairo.Context(surface) ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(12) ctx.set_source_rgb(1.0, 1.0, 1.0) ctx.rectangle(0, 0, surface_x, surface_y) ctx.fill() if background: # Center background if not large. Again, I'm just guessing # where the background image should be, and trying to mimic # analyzer. h = background.get_height() w = background.get_width() start_x, start_y = 0, 0 if w < surface_x: start_x = start_x - min_x - w/2 if h < surface_y: start_y = start_y - min_y - h/2 ctx.set_source_surface(background, start_x, start_y) ctx.rectangle(start_x, start_y, w, h) ctx.fill() midcoords = dict() for page in pagelist: if page not in coords: continue x, y = coords[page] # FIXME need more data to align different backgrounds # correctly, this is just guessing start_x = x - min_x + (diff_x / 2) start_y = y - min_y + (diff_y / 3) w, h = 10, 10 if page not in images: ctx.set_source_rgb(0, 0, 0) else: h = images[page].get_height() w = images[page].get_width() if page in colors: clr = graph.hashcolor(colors[page]) r, g, b = [int(''.join(i), 16) / 255.0 for i in zip(clr[1::2], clr[2::2])] ctx.set_source_rgb(r, g, b) else: ctx.set_source_rgb(1, 1, 1) midcoords[page] = (start_x + w / 2, start_y + h / 2) ctx.rectangle(start_x, start_y, w, h) ctx.fill() if page in images: ctx.set_source_surface(images[page], start_x, start_y) ctx.rectangle(start_x, start_y, w, h) ctx.fill() text = make_tooltip(request, page) areas["%s,%s,%s,%s" % (start_x, start_y, start_x + w, start_y + h)] = \ [page, text, 'rect'] if page in aliases: ctx.set_source_rgb(0, 0, 0) if rotate: ctx.move_to(start_x + w + 10, start_y + h) else: ctx.move_to(start_x, start_y + h + 10) # FIXME, should parse links more nicely, now just removes # square brackets text = aliases[page].lstrip('[').rstrip(']') if rotate: ctx.rotate(-90.0*math.pi/180.0) ctx.show_text(text) if rotate: ctx.rotate(90.0*math.pi/180.0) if flowfile: ctx.set_line_width(1) ctx.set_source_rgb(0, 0, 0) for start, end in flowcoords: if (start not in midcoords) or (end not in midcoords): continue sx, sy = midcoords[start] ex, ey = midcoords[end] ctx.move_to(sx, sy) ctx.line_to(ex, ey) ctx.stroke() s2 = surface if width: # For scaling new_surface_y = width factor = surface_y/new_surface_y new_surface_x = surface_x / factor # Recalculate image map data newareas = dict() for coords, data in areas.iteritems(): corners = [float(i) for i in coords.split(',')] corners = tuple(i / factor for i in corners) newareas['%s,%s,%s,%s' % corners] = data areas = newareas else: new_surface_y = surface_y new_surface_x = surface_x if rotate: temp = new_surface_x new_surface_x = new_surface_y new_surface_y = temp temp = surface_x surface_x = surface_y surface_y = temp s2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(new_surface_x), int(new_surface_y)) ctx = cairo.Context(s2) if rotate: ctx.rotate(90.0*math.pi/180.0) # Recalculate image map data newareas = dict() for coords, data in areas.iteritems(): corners = coords.split(',') corners = [float(i) for i in coords.split(',')] corners = tuple([new_surface_x - corners[1], corners[0], new_surface_x - corners[3], corners[2]]) newareas['%s,%s,%s,%s' % corners] = data areas = newareas if width: ctx.scale(new_surface_x/surface_x, new_surface_y/surface_y) if rotate: ctx.translate(0, -surface_x) ctx.set_source_surface(surface, 0, 0) ctx.paint() data = cairo_surface_to_png(s2) map = '' for coords in areas: name, text, shape = areas[coords] pagelink = request.script_root + u'/' + name tooltip = "%s\n%s" % (name, text) map += u'<area href="%s" shape="%s" coords="%s" title="%s">\n' % \ (form_escape(pagelink), shape, coords, tooltip) return True, data, map
def execute(pagename, request): if not have_cairo(): error = request.getText( "ERROR: Cairo Python extensions not installed. " + "Not performing layout.") request.content_type = 'text/plain' request.write(error) return request.content_type = "image/png" # Grab arguments args = ', '.join(x for x in request.values.getlist('arg')) params = {'height': 0, 'width': 0} # Height and Width for attr in ['height', 'width']: if request.values.has_key(attr): val = ''.join(request.values.getlist(attr)) try: params[attr] = int(val) except ValueError: pass # Aggregate set of included values of a page values = set() if request.values.has_key('value'): values.update(map(ordervalue, request.values.getlist('value'))) # Values that need to be included to form a complete scale, say 1-10 scale = list() if request.values.has_key('scale'): scale = request.values['scale'] if not params['height'] and params['width']: params['height'] = params['width'] elif params['height'] and not params['width']: params['width'] = params['height'] elif not params['height'] and not params['width']: params['width'] = params['height'] = 1000 # calculate center and radius, leave some room for text and stuff center = (params['height'] / 2, params['width'] / 2) radius = min(center) if radius > 50: radius -= 50 # Setup Cairo surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, params['height'], params["width"]) ctx = cairo.Context(surface) ctx.select_font_face("Times-Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size(12) ctx.set_source_rgb(1.0, 1.0, 1.0) ctx.rectangle(0, 0, params['width'], params['height']) ctx.fill() ctx.set_source_rgb(0.0, 0.0, 0.0) # Note, metatable_parseargs deals with permissions pagelist, metakeys, _ = metatable_parseargs(request, args, get_all_keys=True) metakeys = filter(lambda x: x not in SPECIAL_ATTRS, metakeys) # If no keys, print nothing if not pagelist: request.write(plot_error(request)) return # Populate data to the radar chart data = dict() for page in pagelist: # On links, we want a list of page names, not their markup metas = get_metas(request, page, metakeys, checkAccess=False) # Get the maximum value of each key on a page for key in metakeys: data.setdefault(key, list()) if metas[key]: scale.extend(map(ordervalue, metas[key])) if page == pagename: data[key].append(max(map(ordervalue, metas[key]))) # Get values for the chart axes data_per_axis = dict() for axis, key in enumerate(metakeys): data_per_axis[axis] = key if not values: for x in data.values(): values.update(x) if scale: values.update(scale) values = sorted(values) # Refuse to draw if no values for any key if not len(values): request.write(plot_error(request)) return no_values = len(values) + 1 per_value = radius / no_values sectors = len(data) angle = 2 * math.pi / sectors cur_radius = per_value # Make the base grid for x in range(1, no_values): ctx, gridpoints = spider_radius(ctx, center, cur_radius, sectors) cur_radius += per_value # Apply ink from strokes so far ctx.stroke() # Now start to make chart on top of the base ctx.set_source_rgb(50 / 255.0, 137 / 255.0, 37 / 255.0) ctx.set_line_width(5) endpoints = list() # Find coords for each value for i in range(sectors): val = data[data_per_axis[i]] if val: val = val.pop() radius = values.index(val) + 1 else: # If no values exist, it's in the bottom radius = 0 x, y = spider_coords(radius * per_value, i * angle) endpoints.append(add_to_center(center, (x, y))) draw_path(ctx, endpoints) # Draw path filling the contents ctx.stroke_preserve() ctx.set_source_rgba(150 / 255.0, 190 / 255.0, 13 / 255.0, 0.2) ctx.fill() # Write axis names on top of it all for point, axis in zip(gridpoints, data_per_axis.keys()): text = data_per_axis[axis] ctx.set_source_rgba(1, 1, 1, 0.9) width, height = ctx.text_extents(text)[2:4] # Move texts on the left side a bit left if point[0] < center[0]: point = (point[0] - width, point[1]) width, height = width * 1.2, -height * 1.2 x, y = point[0] - 0.1 * width, point[1] + 0.1 * height ctx.rectangle(x, y, width, height) ctx.fill() ctx.set_source_rgb(0, 0, 0) ctx.move_to(*point) ctx.show_text(text) request.write(cairo_surface_to_png(surface))