""" +-------------------------------------------------------------------+ | H T M L - G R A P H S (v4.8) | | | | Copyright Gerd Tentler www.gerd-tentler.de/tools | | Created: Sep. 17, 2002 Last modified: Feb. 13, 2010 | +-------------------------------------------------------------------+ | This program may be used and hosted free of charge by anyone for | | personal purpose as long as this copyright notice remains intact. | | | | Obtain permission before selling the code for this program or | | hosting this software on a commercial website or redistributing | | this software over the Internet or in any other medium. In all | | cases copyright must remain intact. | +-------------------------------------------------------------------+ ===================================================================================================== Example: import graphs graph = graphs.BarGraph('hBar') graph.values = [234, 125, 289, 147, 190] print graph.create() Returns HTML code ===================================================================================================== """ import math import re class BarGraph: """Creates horizontal and vertical bar graphs, progress bars, and faders.""" def __init__(self, type=''): #------------------------------------------------------------------------- # Configuration #------------------------------------------------------------------------- # graph type: "hBar", "vBar", "pBar", or "fader" self.type = type and type or 'hBar' self.values = [] # graph data: list # graph background color: string self.graphBGColor = '' # graph border: string (CSS-spec: "size style color"; doesn't work with NN4) self.graphBorder = '' # graph padding: integer (pixels) self.graphPadding = 0 # titles: array or string with comma-separated values self.titles = [] self.titleColor = 'black' # title font color: string # title background color: string self.titleBGColor = '#C0E0FF' # title border: string (CSS specification) self.titleBorder = '2px groove white' # title font family: string (CSS specification) self.titleFont = 'Arial, Helvetica' # title font size: integer (pixels) self.titleSize = 12 # title text align: "left", "center", or "right" self.titleAlign = 'center' # title padding: integer (pixels) self.titlePadding = 2 # label names: list or string with comma-separated values self.labels = [] self.labelColor = 'black' # label font color: string # label background color: string self.labelBGColor = '#C0E0FF' # label border: string (CSS-spec: "size style color"; doesn't work with # NN4) self.labelBorder = '2px groove white' # label font family: string (CSS-spec) self.labelFont = 'Arial, Helvetica' # label font size: integer (pixels) self.labelSize = 12 # label text align: "left", "center", or "right" self.labelAlign = 'center' # additional space between labels: integer (pixels) self.labelSpace = 0 self.barWidth = 20 # bar width: integer (pixels) # bar length ratio: float (from 0.1 to 2.9) self.barLength = 1.0 # bar colors OR bar images: list or string with comma-separated values self.barColors = [] # bar background color: string self.barBGColor = '' # bar border: string (CSS-spec: "size style color"; doesn't work with NN4) self.barBorder = '2px outset white' # bar level colors: ascending list (bLevel, bColor[,...]); draw bars >= bLevel with bColor self.barLevelColors = [] # show values: 0 = % only, 1 = abs. and %, 2 = abs. only, 3 = none self.showValues = 0 # base value: integer or float (only hBar and vBar) self.baseValue = 0 # abs. values font color: string self.absValuesColor = 'black' # abs. values background color: string self.absValuesBGColor = '#C0E0FF' # abs. values border: string (CSS-spec: "size style color"; doesn't work with NN4) self.absValuesBorder = '2px groove white' # abs. values font family: string (CSS-spec) self.absValuesFont = 'Arial, Helvetica' # abs. values font size: integer (pixels) self.absValuesSize = 12 # abs. values prefix: string (e.g. "$") self.absValuesPrefix = '' # abs. values suffix: string (e.g. " kg") self.absValuesSuffix = '' # perc. values font color: string self.percValuesColor = 'black' # perc. values font family: string (CSS-spec) self.percValuesFont = 'Arial, Helvetica' # perc. values font size: integer (pixels) self.percValuesSize = 12 # perc. values number of decimals: integer self.percValuesDecimals = 0 self.charts = 1 # number of charts: integer # hBar/vBar only: # legend items: list or string with comma-separated values self.legend = [] self.legendColor = 'black' # legend font color: string # legend background color: string self.legendBGColor = '#F0F0F0' # legend border: string (CSS-spec: "size style color"; doesn't work with NN4) self.legendBorder = '2px groove white' # legend font family: string (CSS-spec) self.legendFont = 'Arial, Helvetica' # legend font size: integer (pixels) self.legendSize = 12 # legend vertical align: "top", "center", "bottom" self.legendAlign = 'top' # debug mode: 0 = off, 1 = on; just views some extra information self.debug = 0 #------------------------------------------------------------------------- # Default bar colors; only used if barColors isn't set. __colors = ( '#0000FF', '#FF0000', '#00E000', '#A0A0FF', '#FFA0A0', '#00A000') # Error messages. __err_type = 'ERROR: Type must be "hBar", "vBar", "pBar", or "fader"' # CSS names (don't change). __cssGRAPH = '' __cssBAR = '' __cssBARBG = '' __cssTITLE = '' __cssLABEL = '' __cssLABELBG = '' __cssLEGEND = '' __cssLEGENDBG = '' __cssABSVALUES = '' __cssPERCVALUES = '' # Search pattern for images. __img_pattern = re.compile(r'\.(jpg|jpeg|jpe|gif|png)') def set_styles(self): """set graph styles""" if self.graphBGColor: self.__cssGRAPH += 'background-color:' + self.graphBGColor + ';' if self.graphBorder: self.__cssGRAPH += 'border:' + self.graphBorder + ';' if self.barBorder: self.__cssBAR += 'border:' + self.barBorder + ';' if self.barBGColor: self.__cssBARBG += 'background-color:' + self.barBGColor + ';' if self.titleColor: self.__cssTITLE += 'color:' + self.titleColor + ';' if self.titleBGColor: self.__cssTITLE += 'background-color:' + self.titleBGColor + ';' if self.titleBorder: self.__cssTITLE += 'border:' + self.titleBorder + ';' if self.titleFont: self.__cssTITLE += 'font-family:' + self.titleFont + ';' if self.titleAlign: self.__cssTITLE += 'text-align:' + self.titleAlign + ';' if self.titleSize: self.__cssTITLE += 'font-size:' + str(self.titleSize) + 'px;' if self.titleBGColor: self.__cssTITLE += 'background-color:' + self.titleBGColor + ';' if self.titlePadding: self.__cssTITLE += 'padding:' + str(self.titlePadding) + 'px;' if self.labelColor: self.__cssLABEL += 'color:' + self.labelColor + ';' if self.labelBGColor: self.__cssLABEL += 'background-color:' + self.labelBGColor + ';' if self.labelBorder: self.__cssLABEL += 'border:' + self.labelBorder + ';' if self.labelFont: self.__cssLABEL += 'font-family:' + self.labelFont + ';' if self.labelSize: self.__cssLABEL += 'font-size:' + str(self.labelSize) + 'px;' if self.labelAlign: self.__cssLABEL += 'text-align:' + self.labelAlign + ';' if self.labelBGColor: self.__cssLABELBG += 'background-color:' + self.labelBGColor + ';' if self.legendColor: self.__cssLEGEND += 'color:' + self.legendColor + ';' if self.legendFont: self.__cssLEGEND += 'font-family:' + self.legendFont + ';' if self.legendSize: self.__cssLEGEND += 'font-size:' + str(self.legendSize) + 'px;' if self.legendBGColor: self.__cssLEGENDBG += 'background-color:' + self.legendBGColor + ';' if self.legendBorder: self.__cssLEGENDBG += 'border:' + self.legendBorder + ';' if self.absValuesColor: self.__cssABSVALUES += 'color:' + self.absValuesColor + ';' if self.absValuesBGColor: self.__cssABSVALUES += 'background-color:' + self.absValuesBGColor + ';' if self.absValuesBorder: self.__cssABSVALUES += 'border:' + self.absValuesBorder + ';' if self.absValuesFont: self.__cssABSVALUES += 'font-family:' + self.absValuesFont + ';' if self.absValuesSize: self.__cssABSVALUES += 'font-size:' + str(self.absValuesSize) + 'px;' if self.percValuesColor: self.__cssPERCVALUES += 'color:' + self.percValuesColor + ';' if self.percValuesFont: self.__cssPERCVALUES += 'font-family:' + self.percValuesFont + ';' if self.percValuesSize: self.__cssPERCVALUES += 'font-size:' + str(self.percValuesSize) + 'px;' def level_color(self, value, color): """return bar color for each level""" if self.barLevelColors: for i in range(0, len(self.barLevelColors), 2): try: if (self.barLevelColors[i] > 0 and value >= self.barLevelColors[i]) or \ (self.barLevelColors[i] < 0 and value <= self.barLevelColors[i]): color = self.barLevelColors[i + 1] except IndexError: pass return color def build_bar(self, value, width, height, color): """return a single bar""" title = self.absValuesPrefix + str(value) + self.absValuesSuffix bg = self.__img_pattern.search(color) and 'background' or 'bgcolor' bar = '' bar += '
' or '>' bar += '
' bar += '
' return bar def build_fader(self, value, width, height, x, color): """return a single fader""" fader = '' x -= int(round(width / 2)) if x > 0: fader += '' fader += '' fader += '
' + self.build_bar(value, width, height, color) + '
' return fader def build_value(self, val, max_dec, sum=0, align=''): """return a single bar/fader value""" val = _number_format(val, max_dec) if sum: sum = _number_format(sum, max_dec) value = '' legend += '' i = 0 for color in barColors: if len(self.legend) >= i + 1: text = hasattr( self.legend[i], 'strip') and self.legend[i].strip() or str(self.legend[i]) else: text = '' legend += '' legend += '' legend += '' legend += '' i += 1 legend += '
' + \ self.build_bar( '', self.barWidth, self.barWidth, color) + '' + text + '
' return legend def build_hTitle(self, titleLabel, titleValue, titleBar): """return horizontal titles""" title = '' title += '' + titleLabel + '' if titleValue != '': title += '' + titleValue + '' title += '' + titleBar + '' title += '' return title def create_hBar(self, value, percent, mPerc, mPerc_neg, max_neg, mul, valSpace, bColor, border, spacer, spacer_neg): """return a single horizontal bar with label and values (abs./perc.)""" bar = '' if percent < 0: percent *= -1 bar += '' else: if max_neg: bar += '' if percent: bar += '' else: bar += '' bar += '' bar += '
' if self.showValues < 2: bar += '' + \ str(_number_format(percent, self.percValuesDecimals)) + '%' bar += ' ' bar += self.build_bar(value, int(round(percent * mul)), self.barWidth, bColor) bar += '' bar += '
' bar += self.build_bar(value, int(round(percent * mul)), self.barWidth, bColor) bar += '' if self.showValues < 2: bar += ' ' + str(_number_format(percent, self.percValuesDecimals)) + '%' bar += ' 
' return bar def create_vBar(self, value, percent, mPerc, mPerc_neg, max_neg, mul, valSpace, bColor, border, spacer, spacer_neg): """return a single vertical bar with label and values (abs./perc.)""" bar = '' if percent < 0: percent *= -1 bar += '' bar += '' else: bar += '' if percent: bar += '' else: bar += '' if max_neg: bar += '' bar += '
' bar += self.build_bar(value, self.barWidth, int(round(percent * mul)), bColor) bar += '
' bar += (self.showValues < 2) and '' + \ str(_number_format(percent, self.percValuesDecimals)) + \ '%' or ' ' bar += '' if self.showValues < 2: bar += str(_number_format(percent, self.percValuesDecimals)) + '%' bar += '
' bar += self.build_bar(value, self.barWidth, int(round(percent * mul)), bColor) bar += '
' bar += '
' return bar def create(self): """create a complete bar graph (horizontal, vertical, progress, or fader)""" self.type = self.type.lower() d = self.values t = hasattr( self.titles, 'split') and self.titles.split( ',') or self.titles r = hasattr( self.labels, 'split') and self.labels.split( ',') or self.labels drc = hasattr( self.barColors, 'split') and self.barColors.split( ',') or self.barColors val = [] bc = [] if self.barLength < 0.1: self.barLength = 0.1 elif self.barLength > 2.9: self.barLength = 2.9 labels = (len(d) > len(r)) and len(d) or len(r) if self.type == 'pbar' or self.type == 'fader': if not self.barBGColor: self.barBGColor = self.labelBGColor if self.labelBGColor == self.barBGColor and len(t) == 0: self.labelBGColor = '' self.labelBorder = '' self.set_styles() graph = '' graph += '' if self.legend and self.type != 'pbar' and self.type != 'fader': graph += '
' if self.type == 'vbar': graph += '' graph += '
' if self.charts > 1: divide = math.ceil(labels / self.charts) graph += '' if self.showValues < 2: graph += '' graph += '' if self.labelSpace and i < len(v) - 1: graph += '' lcnt += 1 else: graph += '' graph += '
' else: divide = 0 sum = 0 max = 0 max_neg = 0 max_dec = 0 ccnt = 0 lcnt = 0 chart = 0 for i in range(labels): if divide and i and not i % divide: lcnt = 0 chart += 1 try: drv = len(d[i]) and [e for e in d[i]] or [d[i]] except: drv = [d[i]] j = 0 dec = 0 if len(val) <= chart: val.append([]) for v in drv: s = str(v) if s.find('.') != -1: dec = len(s[s.find('.') + 1:]) if dec > max_dec: max_dec = dec if len(val[chart]) <= lcnt: val[chart].append([]) val[chart][lcnt].append(v) if v != 0: v -= self.baseValue if v > max: max = v elif v < max_neg: max_neg = v if v < 0: v *= -1 sum += v if len(bc) <= j: if ccnt >= len(self.__colors): ccnt = 0 if len(drc) <= j or len(drc[j]) < 3: bc.append(self.__colors[ccnt]) ccnt += 1 else: bc.append(drc[j].strip()) j += 1 lcnt += 1 border = int(self.barBorder[0]) mPerc = sum and int(round(max * 100.0 / sum)) or 0 if self.type == 'pbar' or self.type == 'fader': mul = 2 else: mul = mPerc and 100.0 / mPerc or 1 mul *= self.barLength if self.showValues < 2: if self.type == 'hbar': valSpace = (self.percValuesDecimals * (self.percValuesSize / 1.6)) + \ (self.percValuesSize * 3.2) else: valSpace = self.percValuesSize * 1.2 else: valSpace = self.percValuesSize spacer = maxSize = int(round(mPerc * mul + valSpace + border * 2)) if max_neg: mPerc_neg = sum and int(round(-max_neg * 100.0 / sum)) or 0 if mPerc_neg > mPerc and self.type != 'pbar' and self.type != 'fader': mul = 100.0 / mPerc_neg * self.barLength spacer_neg = int(round(mPerc_neg * mul + valSpace + border * 2)) maxSize += spacer_neg else: mPerc_neg = spacer_neg = 0 titleLabel = '' titleValue = '' titleBar = '' if len(t) > 0: titleLabel = (t[0] == '') and ' ' or t[0] if self.showValues == 1 or self.showValues == 2: titleValue = (t[1] == '') and ' ' or t[1] titleBar = (t[2] == '') and ' ' or t[2] else: titleBar = (t[1] == '') and ' ' or t[1] chart = 0 lcnt = 0 for v in val: graph += '' if self.type == 'hbar': if len(t) > 0: graph += self.build_hTitle(titleLabel, titleValue, titleBar) for i in range(len(v)): label = ( lcnt < len(r)) and r[lcnt].strip() or str(lcnt + 1) rowspan = len(v[i]) graph += '' for j in range(len(v[i])): value = v[i][j] and v[i][j] - self.baseValue or 0 percent = sum and value * 100.0 / sum or 0 value = _number_format(v[i][j], max_dec) bColor = self.level_color(v[i][j], bc[j]) if self.showValues == 1 or self.showValues == 2: graph += self.build_value(v[i] [j], max_dec, 0, 'right') graph += '' graph += self.create_hBar( value, percent, mPerc, mPerc_neg, max_neg, mul, valSpace, bColor, border, spacer, spacer_neg) graph += '' if j < len(v[i]) - 1: graph += '' if self.labelSpace and i < len(v) - 1: graph += '' lcnt += 1 elif self.type == 'vbar': graph += '' if titleBar != '': titleBar = titleBar.replace('-', '-
') graph += '' for i in range(len(v)): for j in range(len(v[i])): value = v[i][j] and v[i][j] - self.baseValue or 0 percent = sum and value * 100.0 / sum or 0 value = _number_format(v[i][j], max_dec) bColor = self.level_color(v[i][j], bc[j]) graph += '' graph += self.create_vBar( value, percent, mPerc, mPerc_neg, max_neg, mul, valSpace, bColor, border, spacer, spacer_neg) graph += '' if self.labelSpace: graph += '' if self.showValues == 1 or self.showValues == 2: graph += '' if titleValue != '': graph += '' for i in range(len(v)): for j in range(len(v[i])): graph += self.build_value(v[i][j], max_dec) if self.labelSpace: graph += '' graph += '' if titleLabel != '': graph += '' for i in range(len(v)): label = ( lcnt < len(r)) and r[lcnt].strip() or str(lcnt + 1) colspan = len(v[i]) graph += '' if self.labelSpace: graph += '' lcnt += 1 graph += '' elif self.type == 'pbar' or self.type == 'fader': if len(t) > 0: graph += self.build_hTitle(titleLabel, titleValue, titleBar) for i in range(len(v)): try: m = (len(v[i]) > 1) and True or False except: m = False if m or not i: label = ( lcnt < len(r)) and r[lcnt].strip() or str(i + 1) graph += '' if len(r): graph += '' try: sum = v[i][1] and v[i][1] or v[-1][0] except: sum = v[-1][0] percent = sum and v[i][0] * 100.0 / sum or 0 value = _number_format(v[i][0], max_dec) if self.showValues == 1 or self.showValues == 2: graph += self.build_value(v[i] [0], max_dec, sum, 'right') graph += '' self.barColors = ( len(drc) >= i + 1) and drc[i].strip() or self.__colors[0] bColor = self.level_color(v[i][0], self.barColors) graph += '
1) and ' rowspan=' + str(rowspan) or '') + '>' graph += ' ' + label + ' 
' + titleBar + '
' + titleValue + '
' + titleLabel + ' 1) and ' colspan=' + str(colspan) or '') + '>' graph += ' ' + label + ' 
' graph += ' ' + label + ' 
' graph += '
' if self.type == 'fader': graph += self.build_fader( value, int(round(self.barWidth / 2)), self.barWidth, int(round(percent * mul)), bColor) else: graph += self.build_bar(value, int(round(percent * mul)), self.barWidth, bColor) graph += '
 ' + \ str(_number_format(percent, self.percValuesDecimals)) + '%
' + self.__err_type + '
' if chart < self.charts - 1 and len(val[chart + 1]): graph += '
' chart += 1 if self.charts > 1: graph += '
' if self.legend and self.type != 'pbar' and self.type != 'fader': graph += ' ' graph += self.build_legend(bc) graph += '' if self.debug: graph += "
sum=%s max=%s max_neg=%s max_dec=%s " % (sum, max, max_neg, max_dec) graph += "mPerc=%s mPerc_neg=%s mul=%s valSpace=%s" % (mPerc, mPerc_neg, mul, valSpace) graph += '' return graph def _number_format(val, dec): """return float with dec decimals; if dec is 0, return integer""" return dec and ('%.' + str(dec) + 'f') % val or int(round(val)) if __name__ == '__main__': print(__doc__)