Beispiel #1
0
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)
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #5
0
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))