"""
+-------------------------------------------------------------------+
| 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 += '' + self.build_bar(value, width, height, color) + ' | '
fader += '
'
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 = ''
value += ' ' + self.absValuesPrefix + str(val) + self.absValuesSuffix
if sum:
value += ' / ' + self.absValuesPrefix + str(sum) + self.absValuesSuffix
value += ' | '
return value
def build_legend(self, barColors):
"""return the legend"""
if hasattr(self.legend, 'split'):
self.legend = self.legend.split(',')
legend = ''
legend += ''
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 += '' + \
self.build_bar(
'',
self.barWidth,
self.barWidth,
color) + ' | '
legend += '' + text + ' | '
legend += ' '
i += 1
legend += ' |
'
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 += ''
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 += ' | | '
else:
if max_neg:
bar += ''
bar += ' | '
if percent:
bar += ''
bar += self.build_bar(value, int(round(percent * mul)), self.barWidth, bColor)
bar += ' | '
else:
bar += ' | '
bar += ''
if self.showValues < 2:
bar += ' ' + str(_number_format(percent, self.percValuesDecimals)) + '%'
bar += ' | '
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 += self.build_bar(value, self.barWidth, int(round(percent * mul)), bColor)
bar += ' |
'
bar += ''
bar += (self.showValues < 2) and '' + \
str(_number_format(percent, self.percValuesDecimals)) + \
'%' or ' '
bar += ' | '
else:
bar += ''
if self.showValues < 2:
bar += str(_number_format(percent, self.percValuesDecimals)) + '%'
bar += ' | '
if percent:
bar += '
'
bar += self.build_bar(value, self.barWidth, int(round(percent * mul)), bColor)
bar += ' | '
else:
bar += '
| '
if max_neg:
bar += '
'
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.charts > 1:
divide = math.ceil(labels / self.charts)
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 += ' 1) and ' rowspan=' + str(rowspan) or '') + '>'
graph += ' ' + label + ' | '
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 += '' + titleBar + ' | '
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 += '' + titleValue + ' | '
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 += '' + titleLabel + ' | '
for i in range(len(v)):
label = (
lcnt < len(r)) and r[lcnt].strip() or str(lcnt + 1)
colspan = len(v[i])
graph += ' 1) and ' colspan=' + str(colspan) or '') + '>'
graph += ' ' + label + ' | '
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 += ''
graph += ' ' + label + ' | '
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 += ''
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 += ' | | '
graph += '
| '
if self.showValues < 2:
graph += ' ' + \
str(_number_format(percent, self.percValuesDecimals)) + '% | '
graph += ' '
if self.labelSpace and i < len(v) - 1:
graph += ' | '
lcnt += 1
else:
graph += '' + self.__err_type + ' | '
graph += ' '
if chart < self.charts - 1 and len(val[chart + 1]):
graph += ' | '
if self.type == 'vbar':
graph += ' '
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__)