From 7837951c5ab2564dedce127487075f829dbc9306 Mon Sep 17 00:00:00 2001 From: Nassim Kammah Date: Wed, 7 Oct 2015 12:11:49 -0600 Subject: [PATCH] Add option to draw 'absolute' data set (not percentage) - aka simple bar charts --- bin/draw_stack_bars | 29 +++++++++++++++++ lib/stackbars/grouped_stackbars.py | 50 +++++++++++++++++++++++++----- lib/stackbars/stackbar.py | 37 +++++++++++++++------- 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/bin/draw_stack_bars b/bin/draw_stack_bars index 7526b4e..3fa167c 100755 --- a/bin/draw_stack_bars +++ b/bin/draw_stack_bars @@ -35,6 +35,15 @@ def drawStackBars(series, options): stacked_bars.render(series) stacked_bars.save(options.get('output_file')) +def setDefaultColors(options): + #import pdb;pdb.set_trace() + if options.draw_mode == "percentage": + options.full_color = options.full_color or '#00e600' + options.empty_color = options.empty_color or '#FF0000' + elif options.draw_mode == "absolute": + options.full_color = options.full_color or '#3333ff' + return options + def parse_options(): usage = "usage: %prog [options] label=value label=value" @@ -60,11 +69,31 @@ def parse_options(): help="Width of the border around the bars", dest="bar_border", default=1) + parser.add_option("-m", "--mode", type="choice", + help="Drawing mode : percentage, absolute", + choices = ['percentage', 'absolute'], + dest="draw_mode", default="percentage") + + parser.add_option("--full-color", type="string", + help="Color for full bars (eg. #00e600)", + dest="full_color") + + parser.add_option("--empty-color", type="string", + help="Color for empty bars (eg. #00e600)", + dest="empty_color") + + parser.add_option("--font", type="choice", + help="Font to use", + choices = ['aerial', 'arial_black', 'Tahoma'], + dest="font_name", default = "arial_black") + + options, data = parser.parse_args() if not data: sys.stderr.write("No data to plot !\n\n") parser.print_help() sys.exit(1) + options = setDefaultColors(options) return options, data if __name__=='__main__': diff --git a/lib/stackbars/grouped_stackbars.py b/lib/stackbars/grouped_stackbars.py index f4e89a8..78e8b13 100644 --- a/lib/stackbars/grouped_stackbars.py +++ b/lib/stackbars/grouped_stackbars.py @@ -17,13 +17,15 @@ def __init__(self, options = {}): self.bar_height_ratio = options.get('bar_height_ratio', .2) assert self.bar_height_ratio <=1, "bar_height_ratio must be <= 1" - self.font_type = options.get('font_type', get_font('arial_black.ttf')) + font_name = "%s.ttf" % options.get('font_name', 'arial_black') + self.font_type = get_font(font_name) self.min_font_size = 15 self.img = None self.draw = None self.stack_bars = [] + self.draw_mode = options.get('draw_mode', 'percentage') def initializeDims(self, data, optimize_bar_area_width = True): self.bars_width = int(self.bar_area_ratio * self.width) @@ -37,7 +39,7 @@ def initializeDims(self, data, optimize_bar_area_width = True): optimal_font_size = get_optimal_font_size( self.draw, self.font_type, - self.width -self.bars_width, + self.width - self.bars_width, self.bar_height, longest_label, self.min_font_size @@ -66,26 +68,58 @@ def get_longest_label(self, data): def initiliazeStackBars(self, data): self.stack_bars = [] # get the dimensions for the gauges area and for the text area - for label, value in data: - sb = Stackbar(self.draw, label, value, self.options) + for label, value, fill_value in data: + sb = Stackbar(self.draw, label, value, fill_value, self.options) self.stack_bars.append(sb) def renderStackBars(self): offset = 0 for i, sb in enumerate(self.stack_bars): draw_bottom_border = i == (len(self.stack_bars) - 1) - sb.render(self.width, offset, self.bars_width, self.bar_height, self.font_size, draw_bottom_border) + if self.draw_mode == "percentage": + sb.render_percentage(self.width, offset, self.bars_width, self.bar_height, self.font_size, draw_bottom_border) + elif self.draw_mode == "absolute": + sb.render_absolute(self.width, offset, self.bars_width, self.bar_height, self.font_size, draw_bottom_border) offset += self.bar_height + # Format the data + # to always make sure the max value is 100 + # in an absolute mode + # in percentage mode, raise an exception if the value > 100 + # example input : [('ios-boe-aatp-tests', 180), ('android-boe-aatp-tests', 10)] + # output : [('ios-boe-aatp-tests', 180, 100), ('android-boe-aatp-tests', 10, 10 * 100 / 180)] + def format_data(self, data): + # Find the max input + max_val = 0 + formatted_data = [] + + for k,v in data: + if self.draw_mode == "percentage": + assert v <= 100, "percentage draw mode : invalid fill value for %s - must be <= 100" %k + elif self.draw_mode == "absolute": + max_val = max(max_val, v) + + for k,v in data: + value = v + fill_value = v + if self.draw_mode == "percentage": + value = "%i%%" % value + elif self.draw_mode == "absolute": + fill_value = v * 100. / max_val + + formatted_data.append([k,str(value),fill_value]) + return formatted_data + def render(self, data): - if not data: + formatted_data = self.format_data(data) + if not formatted_data: return # Initialize the drawing area - self.initializeDims(data) + self.initializeDims(formatted_data) # Create new stack bars - self.initiliazeStackBars(data) + self.initiliazeStackBars(formatted_data) # render each gauge according to the optimal dims self.renderStackBars() diff --git a/lib/stackbars/stackbar.py b/lib/stackbars/stackbar.py index 5e9a9bb..28b5baf 100644 --- a/lib/stackbars/stackbar.py +++ b/lib/stackbars/stackbar.py @@ -3,17 +3,17 @@ from chart_utils import get_optimal_font_size, get_font class Stackbar: - def __init__(self, draw, legend, fill_value, options = {}): - - assert fill_value <= 100, "Invalid fill value - must be <= 100" - + def __init__(self, draw, legend, data_value, fill_value, options = {}): self.legend = legend + self.data_value = data_value self.fill_value = fill_value self.border = options.get('bar_border', 1) - self.full_color = options.get('green', (255, 0, 0)) - self.empty_color = options.get('red', (0, 255, 0)) - self.font_type = options.get('font_type', get_font('arial_black.ttf')) + self.full_color = options.get('full_color', (255, 0, 0)) + self.empty_color = options.get('empty_color', (0, 255, 0)) + + font_name = "%s.ttf" % options.get('font_name', 'arial_black') + self.font_type = get_font(font_name) self.light_font_color = options.get('fontcolor', (255, 255, 255)) self.dark_font_color = options.get('fontcolor', (0, 0, 0)) @@ -29,18 +29,17 @@ def drawFullRectangle(self, width, bar_height, height_offset, font_size, bottom_ self.draw.rectangle((full_top_left, full_bottom_right), fill=self.full_color) # Draw text - rect_label = "%i%%" % self.fill_value - optimal_font_size = get_optimal_font_size(self.draw, self.font_type, rect_width, bar_height, rect_label, 7, font_size) + optimal_font_size = get_optimal_font_size(self.draw, self.font_type, rect_width, bar_height, self.data_value, 7, font_size) if not optimal_font_size: return font = ImageFont.truetype(self.font_type, optimal_font_size) - label_width, label_height = self.draw.textsize(rect_label, font=font) + label_width, label_height = self.draw.textsize(self.data_value, font=font) label_margin = (rect_width - label_width) / 2 label_offset = (bar_height - label_height) / 2 self.draw.text( (self.border + 1 + label_margin, height_offset + self.border + 1 + label_offset), - rect_label, + self.data_value, font=font, fill=self.light_font_color ) @@ -85,7 +84,7 @@ def drawLegend(self, width_offset, bar_height, height_offset, font_size): def drawBase(self, width, bar_height, height_offset): self.draw.rectangle((0,height_offset, width, height_offset + bar_height), fill=(0,0,0)) - def render(self, width, height_offset, bars_width, bar_height, font_size, draw_bottom_border = True): + def render_percentage(self, width, height_offset, bars_width, bar_height, font_size, draw_bottom_border = True): if draw_bottom_border: bottom_border_offset = 1 else: @@ -98,3 +97,17 @@ def render(self, width, height_offset, bars_width, bar_height, font_size, draw_b self.drawEmptyRectangle(bars_width, bar_height, height_offset, font_size - 1, bottom_border_offset) self.drawLegend(bars_width, bar_height, height_offset, font_size) + + def render_absolute(self, width, height_offset, bars_width, bar_height, font_size, draw_bottom_border = True): + if draw_bottom_border: + bottom_border_offset = 1 + else: + bottom_border_offset = 0 + + #self.drawBase(bars_width, bar_height, height_offset) + + self.drawFullRectangle(bars_width, bar_height, height_offset, font_size - 1, bottom_border_offset) + + #self.drawEmptyRectangle(bars_width, bar_height, height_offset, font_size - 1, bottom_border_offset) + + self.drawLegend(bars_width, bar_height, height_offset, font_size) \ No newline at end of file