Author: croberts Date: 2011-11-21 20:09:23 +0000 (Mon, 21 Nov 2011) New Revision: 5142
Added: branches/noflash/cumin/python/cumin/charts.py Log: Got a little whack happy with previous commit. Reverting to include charts.py for now.
Added: branches/noflash/cumin/python/cumin/charts.py =================================================================== --- branches/noflash/cumin/python/cumin/charts.py (rev 0) +++ branches/noflash/cumin/python/cumin/charts.py 2011-11-21 20:09:23 UTC (rev 5142) @@ -0,0 +1,439 @@ +import logging + +from cairo import * +from random import random +from time import time +from math import sqrt, pi, ceil + +from formats import * +from util import * + +log = logging.getLogger("cumin.chart") + +class CuminPieChart(object): + def __init__(self, radius=400): + self.width = radius * 2 + self.height = self.width + self.centerx = radius + self.centery = radius + self.radius = radius + + def plot_pie(self, slices, colors): + surface = ImageSurface(FORMAT_ARGB32, int(self.width), int(self.height)) + cr = Context(surface) + cr.set_line_width(2) + + total = float(sum(slices)) + + if not total: + # draw a grey placeholder circle + cr.new_path() + cr.arc(self.centerx, self.centery, + self.radius, 0, 2 * pi) + cr.close_path() + cr.set_source_rgba(0, 0, 0, 0.1) + cr.stroke() + return surface + + wedge = cum_wedges = end = 0.0 + + # are we going to draw more than one slice + draw_border = sum([1 for x in slices if x > 0]) > 1 + + for slice, color in zip(slices, colors): + if slice > 0: + wedge = slice / total + + start = end + end = (cum_wedges + wedge) * 2 * pi + cum_wedges += wedge + + # draw the wedge + self.draw_slice_path(cr, self.centerx, self.centery, + self.radius, start, end) + cr.set_source_rgb(*color) + cr.fill() + + # draw a white border + if draw_border: + self.draw_slice_path(cr, self.centerx, self.centery, + self.radius, start, end) + cr.set_source_rgb(1,1,1) + cr.stroke() + + return surface + + def draw_slice_path(self, cr, x, y, radius, start, end): + cr.new_path() + cr.move_to(x, y) + cr.arc(x, y, radius, start, end) + cr.close_path() + +class HeatMapChart(object): + def __init__(self, width=400, height=400): + self.width = width # final width + self.height = height + self.max_width = width # max width + self.max_height = height + self.cols = 0 # columns + self.rows = 0 + self.max_size = 28 # size of each slot + self.surface = None + self.gap = 3 + + def plot_colored_shape(self, interior, shape, width, height): + surface = ImageSurface(FORMAT_ARGB32, int(width), int(height)) + cr = Context(surface) + cr.set_line_width(1) + + cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) + cr.rectangle(0, 0, width - 1, height - 1) + cr.fill() + + cr.set_line_width(2) + self._plot_shape(cr, interior, shape, 0, 0, width, height) + return surface + + def plot_colored_rect(self, interior, width, height): + surface = ImageSurface(FORMAT_ARGB32, int(width), int(height)) + cr = Context(surface) + cr.set_line_width(1) + + cr.set_source_rgb(*interior) + cr.rectangle(0, 0, width - 1, height - 1) + cr.fill() + return surface + + def plot_state(self, state, width, height): + surface = ImageSurface(FORMAT_ARGB32, int(width), int(height)) + cr = Context(surface) + cr.set_line_width(2) + + self._plot_square(cr, (0.7, 0.7, 0.7), state, 0, 0, width, height) + return surface + + def _plot_square(self, cr, colors, state, x, y, width, height): + if state == "Unclaimed": + shape = "box" + elif state == "Claimed": + shape = "solid" + elif state in ("Owner", "Unavailable"): + shape = "diagonal" + elif state in ("Matched", "Preempting", "Preempting/Matched"): + shape = "filled_triangle" + else: + shape = "box" + + self._plot_shape(cr, colors, shape, x, y, width, height) + + def _plot_shape(self, cr, colors, shape, x, y, width, height): + cr.set_source_rgb(*colors) + #cr.move_to(x, y) + cr.rectangle(x, y, width - self.gap, height - self.gap) + + if shape == "box": # leave empty + cr.stroke() + elif shape == "solid": # solid fill + cr.fill() + elif shape == "diagonal": # diagonal line + cr.move_to(x, y) + cr.line_to(x + width - self.gap, y + height - self.gap) + cr.stroke() + elif shape == "filled_triangle": # triangle + cr.stroke() + cr.move_to(x + width - self.gap, y) + cr.line_to(x, y + height - self.gap) + cr.line_to(x + width - self.gap, y + height - self.gap) + cr.close_path() + cr.fill() + + def plot_shapes(self, shape_info, zl): + count = len(shape_info) + slot_size = self.slot_size(count, zl) + x = 0 + y = 0 + col = 0 + self.rows = 1 + # the width and height depend on the number of slots + self.surface = ImageSurface(FORMAT_ARGB32, int(self.width), int(self.height)) + cr = Context(self.surface) + + cr.set_line_width( zl == 1 and 1 or 2 ) + + for slot in shape_info: + interior = slot["color"] + shape = slot["shape"] + + if col >= self.cols: + x = 0 + y = y + slot_size + col = 0 + self.rows = self.rows + 1 + + self._plot_shape(cr, interior, shape, x, y, slot_size, slot_size) + + x = x + slot_size + col = col + 1 + return slot_size + + def slot_size(self, count, zoom=1): + sq = sqrt(count) + cols = int(sq) + if sq > cols: + cols = cols + 1 + size = int(self.width * zoom / cols) + if size < 2: + size = 2 + if size > self.max_size * zoom: + size = self.max_size * zoom + + self.width = int((size * cols) + 1) + self.height = int((ceil(count * 1.0 / cols) * size) + 1) + self.cols = cols + return size + + def write(self, writer): + self.surface.write_to_png(writer) + +class TimeSeriesChart(object): + def __init__(self, width, height, interval=0): + self.width = width - 40 + self.height = height - 20 + real_height = height + ((interval > 10) and 12 or 0) + self.surface = ImageSurface(FORMAT_ARGB32, int(width), int(real_height)) + self.surface.set_device_offset(1.5, 5.5) + self.x_max = 1 + self.x_min = 0 + self.y_max = 1 + self.y_min = 0 + + def plot_values(self, samples, color=(0, 0, 0)): + cr = Context(self.surface) + cr.set_line_width(1.5) + cr.set_source_rgba(color[0], color[1], color[2], 0.66) + + tnow = time() + points = list() + + for dt, value, dev in samples: + if value is None: + continue + + t = secs(dt) + + # prevent line from drawing off right side of grid + if tnow - t < 0: + t = tnow + + x = self.width - ((tnow - t) / float(self.x_max)) * self.width + y = self.height - (value / float(self.y_max)) * self.height + cr.line_to(x, y) + if x < 0: + break + points.append([int(x), int(y), "%.2f" % value]) + + cr.stroke() + return points + + def plot_ticks(self, samples, color=(0, 0, 0)): + cr = Context(self.surface) + cr.set_line_width(1.5) + + tnow = time() + dot_size = max(int(self.height / 100), 2) + half_dot = int(dot_size / 2) + + for dt, value, dev in samples: + if value is None: + continue + + t = secs(dt) + + # prevent line from drawing off right side of grid + if tnow - t < 0: + t = tnow + + cr.set_source_rgba(color[0], color[1], color[2], 0.66) + x = self.width - ((tnow - t) / float(self.x_max)) * self.width + y = self.height - (value / float(self.y_max)) * self.height + cr.move_to(x, y) + cr.rectangle(x - half_dot, y - half_dot, dot_size, dot_size) + cr.fill() + + if dev: + cr.set_source_rgba(0, 0, 0, 0.1) + half_dev = int(((float(dev) / float(self.y_max)) * self.height) / 2) + cr.move_to(x - dot_size, y - half_dev) + cr.line_to(x + dot_size, y - half_dev) + + cr.move_to(x, y - half_dev) + cr.line_to(x, y + half_dev) + + cr.move_to(x - dot_size, y + half_dev) + cr.line_to(x + dot_size, y + half_dev) + + cr.stroke() + + def plot_legend(self, titles, colors): + cr = Context(self.surface) + cr.set_line_width(1.5) + title_xy = dict() + + for i, item in enumerate(zip(titles, colors)): + title, color = item + y = 16 + i * 16 + + cr.set_source_rgba(1, 1, 1, 0.75) + cr.rectangle(4, y - 12, 16 + 6.5 * len(title), 16) + cr.fill() + cr.stroke() + + cr.set_source_rgb(*color) + cr.rectangle(8, y, 8, -8) + cr.fill() + cr.stroke() + + cr.move_to(20, y) + cr.set_source_rgb(0, 0, 0) + cr.show_text(title) + cr.stroke() + + width, height = cr.text_extents(title)[2:4] + title_xy[color] = [width, y] + return title_xy + + def plot_frame(self, color=(0.8, 0.8, 0.8)): + cr = Context(self.surface) + cr.set_line_width(1) + cr.set_source_rgb(*color) + + cr.move_to(self.width, 0) + cr.line_to(self.width, self.height) + cr.line_to(0, self.height) + + cr.stroke() + + def plot_interval(self, interval): + if interval > 10: + cr = Context(self.surface) + cr.set_line_width(0.2) + cr.set_source_rgb(0.2, 0.2, 0.2) + + cr.move_to(2, self.height + 24) + cr.show_text("* Samples averaged over %i second interval" % interval) + cr.stroke() + + def plot_x_axis(self, intervals, step): + cr = Context(self.surface) + cr.set_line_width(0.2) + cr.set_source_rgb(0.6, 0.6, 0.6) + + interval = self.width / float(intervals) + absmax = self.x_max - self.x_min + + for i in range(0, intervals + 1): + x = self.width - (i * interval) + + cr.move_to(x, 0) + cr.line_to(x, self.height + 12) + cr.move_to(x + 2, self.height + 12) + + if i % step == 0: + value = absmax - (absmax * x / float(self.width)) + text = fmt_duration_brief(value) + if text: + cr.show_text(text) + + cr.stroke() + + def plot_y_axis(self, intervals, step): + cr = Context(self.surface) + cr.set_line_width(0.2) + cr.set_source_rgb(0.6, 0.6, 0.6) + + interval = self.height / float(intervals) + + for i in range(0, intervals + 1): + y = self.height - (i * interval) + + cr.move_to(0, y) + cr.line_to(self.width + 3, y) + cr.move_to(self.width + 4, y + 3) + + if i % step == 0: + fraction = (self.height - y) / float(self.height) + value = fraction * self.y_max - self.y_min + value = int(round(value + self.y_min)) + + if value >= 1000000: + svalue = "%.2fM" % (round(value / 1000000.0, 2)) + elif value >= 10000: + svalue = "%ik" % int(round(value / 1000.0, -1)) + else: + svalue = str(value) + + cr.show_text(svalue) + + cr.stroke() + + def write(self, writer): + self.surface.write_to_png(writer) + +class StackedValueChart(TimeSeriesChart): + def __init__(self, width, height, legend_height = 0): + super(StackedValueChart, self).__init__(width, height) + + if legend_height: + self.surface = ImageSurface(FORMAT_ARGB32, int(width), int(height + legend_height)) + self.surface.set_device_offset(1.5, 5.5) + + self.legend_height = legend_height + self.bar_width = 2 + + def plot_values(self, dt, samples, colors): + cr = Context(self.surface) + cr.set_line_width(self.bar_width) + + tnow = time() + y = self.height + v = 0 + for value, color in zip(samples, colors): + if value is None: + continue + + t = secs(dt) + + if tnow - t < 0: + t = tnow + + x = self.width - ((tnow - t) / float(self.x_max)) * self.width + cr.move_to(x, y) + cr.set_source_rgba(color[0], color[1], color[2], 0.66) + + v = v + value + y = self.height - (v / float(self.y_max)) * self.height + cr.line_to(x, y) + cr.stroke() + + def plot_legend(self, titles, colors): + cr = Context(self.surface) + + for i, item in enumerate(zip(titles, colors)): + title, color = item + y = self.height + 26 + i * 16 + + cr.set_source_rgba(1, 1, 1, 0.75) + cr.rectangle(4, y - 12, 16 + 6.5 * len(title), 16) + cr.fill() + cr.stroke() + + cr.set_source_rgb(*color) + cr.rectangle(8, y, 8, -8) + cr.fill() + cr.stroke() + + cr.move_to(20, y) + cr.set_source_rgb(0, 0, 0) + cr.show_text(title) + cr.stroke() +
cumin-developers@lists.fedorahosted.org