RE: [Zope] graphics on the fly [long, with code]
Here is some code, for the community should they find it helpful, that works with the python gd module to produce bar, line, and pie charts. I wrote it about two years ago. It's not Zope specific. It's been useful for my purposes, and I'm hoping there are some ideas in it worthy of inclusion in some greater whole. The code should be quite self explanatory, and there's a test included for each chart type. The gd module is, of course, required. Enjoy, Bill Scherer #=========================== BEGIN CODE ======================================= #!/usr/local/bin/python """ Python-GD Charting - William K Scherer 4/24/1998 scherbi@bam.com Python-GD Charting provides for 2D charting (pie, line & bar). The GD module is required. Value based sorting of input data is optional, well as the insertion of mean, standard deviation, and median statistical values. Charting range is automatically derived from the input data. Negative values are acceptable. Instantiation for all three subclasses is the same: 'chart = Chart.LineChart(data, stats, sort, chart, xaxis, yaxis, xsize, ysize)' ...BarChart(... ...PieChart(... where: data - a list(or tuple) of lists(or tuples) of the form [('xItem1', yvalue1), ('yItem1', yvalue1)] data is the only required parameter stats - any non zero number will enable statistics. Default is disabled. sort - any number other than 1 wil disable sorting by value (data[0][1]). Default is enabled. chart title - The title for the chart. Defaults to 'Chart'. xaxis - The x axis title. Defaults to 'X Axis'. Not used in PieChart. yaxis - The y axis title. Defaults to 'Y Axis'. Not used in PieChart. xsize - The x dimension, in pixels, of the final image. See note below. ysize - The y dimension, in pixels, of the final image. See note below. Note: xsize and ysize default to 550x440. Code to implement changing these values has not been done yet. In other words, don't mess with these unless you're prepared to do some coding! See Test() for example usage. Running this module will execute Test() and output three sample charts. Feedback is welcomed. See address above. License & Warranty info: This code is free for anyone to use and modify as they see fit. This code is provided 'as is', and as such there is no warranty of any kind. If this code causes you, your company, or anyone else harm of any kind, there is no one to sue over it. Use this code at your own discretion. """ import gd, time, math class Chart: """ Base class. Sets up colors and common features of image, common functions. """ def __init__(self,data,Stat=0,Sort=1,charttitle='Chart',\ xtitle='X Axis',ytitle='Y Axis',xsize=550,ysize=440): if Sort == 1: self.data = self.ValueSort(data) else: self.data = data if Stat != 0: self.InsertMean() self.InsertStDev() self.InsertMedian() ## initialize image self.image=gd.image((xsize, ysize)) ## setup color space # bgcolors self.white = self.image.colorAllocate((255,255,255)) self.black = self.image.colorAllocate((0,0,0)) self.dkgray = self.image.colorAllocate((105,105,105)) self.gray = self.image.colorAllocate((190,190,190)) self.ltgray = self.image.colorAllocate((211,211,211)) self.vltgray = self.image.colorAllocate((235,235,235)) #fgcolors self.MediumBlue = self.image.colorAllocate((0,0,180)) self.blue = self.image.colorAllocate((0,0,255)) self.green = self.image.colorAllocate((0,255,0)) self.ForestGreen = self.image.colorAllocate((34,139,34)) self.OliveDrab = self.image.colorAllocate((107,142,35)) self.DarkGoldenrod = self.image.colorAllocate((184,134,11)) self.SaddleBrown = self.image.colorAllocate((139,69,19)) self.sienna = self.image.colorAllocate((160,82,45)) self.chocolate = self.image.colorAllocate((210,105,30)) self.firebrick = self.image.colorAllocate((178,34,34)) self.brown = self.image.colorAllocate((165,42,42)) self.OrangeRed = self.image.colorAllocate((255,69,0)) self.red = self.image.colorAllocate((255,0,0)) self.maroon = self.image.colorAllocate((176,48,96)) self.RoyalBlue4 = self.image.colorAllocate((39,64,139)) self.SteelBlue4 = self.image.colorAllocate((54,100,139)) self.DeepSkyBlue4 = self.image.colorAllocate((0,104,139)) self.turquoise4 = self.image.colorAllocate((0,134,139)) self.cyan = self.image.colorAllocate((0,139,139)) self.SeaGreen = self.image.colorAllocate((46,139,87)) self.green = self.image.colorAllocate((0,255,0)) self.green3 = self.image.colorAllocate((0,205,0)) self.chartreuse = self.image.colorAllocate((102,205,0)) self.OliveDrab = self.image.colorAllocate((105,139,34)) self.yellow = self.image.colorAllocate((139,139,0)) self.gold = self.image.colorAllocate((139,117,0)) self.brown = self.image.colorAllocate((205,51,51)) self.salmon = self.image.colorAllocate((139,76,57)) self.orange = self.image.colorAllocate((139,90,0)) self.DarkOrange = self.image.colorAllocate((139,69,0)) self.coral = self.image.colorAllocate((139,62,47)) self.tomato = self.image.colorAllocate((205,79,57)) self.OrangeRed = self.image.colorAllocate((205,55,0)) self.red = self.image.colorAllocate((205,0,0)) self.DeepPink = self.image.colorAllocate((139,10,80)) self.HotPink = self.image.colorAllocate((139,58,98)) self.maroon = self.image.colorAllocate((139,28,98)) self.VioletRed = self.image.colorAllocate((139,34,82)) self.magenta = self.image.colorAllocate((139,0,139)) self.MediumOrchid = self.image.colorAllocate((122,55,139)) self.DarkOrchid = self.image.colorAllocate((104,34,139)) self.purple = self.image.colorAllocate((85,26,139)) ## foreground color list fgcolors = [self.MediumBlue,self.blue,self.SeaGreen,self.green, self.ForestGreen,self.OliveDrab,self.DarkGoldenrod,self.SaddleBrown, self.sienna,self.chocolate,self.firebrick,self.brown,self.OrangeRed, self.red,self.maroon,self.RoyalBlue4, self.SteelBlue4,self.DeepSkyBlue4, self.turquoise4,self.cyan,self.SeaGreen,self.green, self.green3,self.chartreuse,self.OliveDrab,self.yellow,self.gold, self.brown,self.salmon,self.orange, self.DarkOrange,self.coral,self.tomato,self.OrangeRed,self.red, self.DeepPink,self.HotPink,self.maroon,self.VioletRed,self.magenta, self.MediumOrchid,self.DarkOrchid,self.purple] self.fgcolors = self.RandList(fgcolors) # border rectangle self.image.rectangle((0,0), (xsize,2),self.black,self.black) self.image.rectangle((xsize-2,0), (xsize,ysize), self.black,self.black) self.image.rectangle((xsize,ysize-2), (0,xsize), self.black,self.black) self.image.rectangle((2,ysize-2), (0,0), self.black,self.black) # chart client space dividers (assuming default size for now) self.image.rectangle((0,40), (550,42), self.black,self.black) # title bottom border self.image.line((400,70), (550,70), self.black) # legend title bottom border self.image.line((475,71), (475,399), self.dkgray) # legend space divider self.image.rectangle((398,42), (400,438), self.black, self.black) # legend left border self.image.line((398,400), (550,400), self.black) # legend space bottom border if self.type == 'Bar' or self.type == 'Line': self.image.line((40,400), (550,400), self.black) # x axis title border self.image.line((40,40), (40,400), self.black) # y axis title border self.image.line((0,440), (40,400), self.black) # lower left corner line # fills self.image.fill((500,430), self.dkgray) # time stamp self.image.fill((500,60), self.dkgray) # legend self.image.fill((10,10), self.ltgray) # title# if self.type == 'Bar' or self.type == 'Line': self.image.fill((10,100), self.vltgray) # y axis self.image.fill((100,430), self.vltgray) # x axis ## titles, lables, timestamp, vanity self.image.string(gd.gdFontSmall, (403,403), time.ctime(time.time()), self.white) self.image.string(gd.gdFontSmall, (422,414), 'Python-GD Charting', self.white) self.image.string(gd.gdFontSmall, (437,425), 'WKS 1998', self.white) self.image.string(gd.gdFontMediumBold, (445,50), 'Legend', self.white) self.image.string(gd.gdFontGiant, (16,16), charttitle, self.black) if self.type == 'Bar' or self.type == 'Line': self.image.string(gd.gdFontMediumBold, (100,410), xtitle, self.black) self.image.stringUp(gd.gdFontMediumBold, (20,300), ytitle, self.black) def Legend(self): xpos, ypos, ccount = 408, 77, 0 for each in self.data: color = self.fgcolors[ccount] if each[0] == 'Mean': color = self.black elif each[0] == 'StDev': color = self.dkgray elif each[0] == 'Median': color = self.gray self.image.rectangle((xpos,ypos), (xpos+8, ypos+5), color, color) self.image.string(gd.gdFontSmall, (xpos + 12, ypos-3), each[0], self.black) ypos = ypos + 10 if ypos > 390: if xpos == 485: break else: xpos, ypos = 480, 76 ccount = ccount + 1 if ccount > len(self.fgcolors) - 1: ccount = 0 def Output(self, file='./test.gif'): self.image.interlace(1) self.image.writeGif(file) def RandList(self, List): import whrandom whrandom.seed(1,2,3) myList = List[:] newList = [] for i in List: item=whrandom.choice(myList) newList.append(item) myList.remove(item) return newList def ValueSort(self, data): newdata = [] for each in data: tmp = [] tmp.append(each[1]) tmp.append(each[0]) newdata.append(tmp) newdata.sort() data = [] for each in newdata: tmp = [] tmp.append(each[1]) tmp.append(each[0]) data.append(tmp) return data def InsertMean(self): num = len(self.data) sum = 0 for each in self.data: sum = sum + each[1] avg = sum / num self.data.append('Mean', avg) def InsertStDev(self): m = self.data[-1:][0][1] var = long(0) for item in self.data: n = long(item[1]) n = n - m var = var + (n * n) std = math.sqrt(var/float(len(self.data)-1)) self.data.append('StDev', std) def InsertMedian(self): x = map(None, self.data) x.sort() n = len(x) if n % 2 : median = x[n/2][1] else: n = n / 2 [a,b] = x[n-1:n+1] median = (a[1]+b[1])/2.0 self.data.append('Median', median) class BarChart(Chart): def __init__(self,data,Stat=0,Sort=1,charttitle='Chart',\ xtitle='X Axis',ytitle='Y Axis',xsize=550,ysize=440): self.type = 'Bar' Chart.__init__(self,data,Stat,Sort,charttitle,xtitle,ytitle,xsize,ysize) def Bounds(self): self.xsize = len(self.data) self.ysize = 0 self.ymin = 0 for each in self.data: if each[1] > self.ysize: self.ysize = each[1] if each[1] < self.ymin: self.ymin = each[1] if self.ymin < 0: self.ysize = self.ysize + abs(self.ymin) try: self.xdif = (300.0 / self.xsize) except: self.ydif = 1 try: self.ydif = (300.0 / self.ysize) except: self.ydif = 1 try: xgran = self.xsize / self.xdif except: xgran = 1 try: ygran = int(pow((self.ysize / self.ydif), 0.5)) except: ygran = 1 if xgran < 1: xgran = 1 if ygran < 1: ygran = 1 self.granularity = (ygran,xgran) def Grid(self): self.Origin = self.image.getOrigin() self.image.origin((70,390), 1, -1) ypoints, xpoints = self.ymin, 0 for y in range(0, self.ysize + self.granularity[0], self.granularity[0]): y = y * self.ydif if y < 335: self.image.line((0,y), (self.xsize * self.xdif + 5,y), self.ltgray) self.image.string(gd.gdFontTiny, (-27,y + 4), str(ypoints), self.red) ypoints = ypoints + self.granularity[0] for x in range(0, self.xsize + self.granularity[1], self.granularity[1]): x = x * self.xdif if x > 300: break self.image.line((x,y+5), (x,0), self.gray) xpoints = xpoints + self.granularity[1] if self.ymin < 0: yy = abs(self.ymin) * self.ydif self.image.line((0,yy), (305,yy), self.red) self.image.string(gd.gdFontSmall, (-8, yy+5), '0', self.red) self.image.origin(self.Origin[0], self.Origin[1]) ## reset origin def Plot(self): self.image.origin((70,390), 1, -1) x = ccount = 0 for a in self.data: h = a[1] + abs(self.ymin) color = self.fgcolors[ccount] if a[0] == 'Mean': color = self.black elif a[0] == 'StDev': color = self.dkgray elif a[0] == 'Median': color = self.gray #self.image.rectangle((x+2,0),(x+self.xdif-2,h*self.ydif), color, color) self.image.rectangle((x+3,abs(self.ymin) * self.ydif),(x+self.xdif-3,h*self.ydif), color, color) x = x + self.xdif ccount = ccount + 1 if ccount > len(self.fgcolors) - 1: ccount = 0 self.image.origin(self.Origin[0], self.Origin[1]) def MakeChart(self): self.Legend() self.Bounds() self.Grid() self.Plot() class LineChart(BarChart): def __init__(self,data,Stat=0,Sort=1,charttitle='Chart',\ xtitle='X Axis',ytitle='Y Axis',xsize=550,ysize=440): self.type = 'Line' self.LastPoint = None Chart.__init__(self,data,Stat,Sort,charttitle,xtitle,ytitle,xsize,ysize) def Plot(self): self.image.origin((70,390), 1, -1) x = ccount = 0 for a in self.data: h = a[1] + abs(self.ymin) color = self.fgcolors[ccount] if a[0] == 'Mean': color = self.black elif a[0] == 'StDev': color = self.dkgray elif a[0] == 'Median': color = self.gray point = (x,h*self.ydif) self.image.arc(point, (4,4), 0, 360, color) ## plot the dot ## connect the dots if self.LastPoint != None: self.image.line(self.LastPoint, point, self.dkgray) self.LastPoint = point else: self.LastPoint = point x = x + self.xdif ccount = ccount + 1 if ccount > len(self.fgcolors) - 1: ccount = 0 self.image.origin(self.Origin[0], self.Origin[1]) class PieChart(Chart): def __init__(self,data,Stat=0,Sort=1,charttitle='Chart',\ xtitle='X Axis',ytitle='Y Axis',xsize=550,ysize=440): self.type = 'Pie' Chart.__init__(self,data,Stat,Sort,charttitle,xtitle,ytitle,xsize,ysize) def circle(self): self.Origin = self.image.getOrigin() self.image.origin((200,240), 1, -1) self.image.arc((0,0), (330,330), 0, 360, self.black) self.image.origin(self.Origin[0], self.Origin[1], self.Origin[2]) ## reset origin def wedge(self,Range,color): circ = 1093 radius = 165 deg2rad = 0.0174532925199 self.Origin = self.image.getOrigin() self.image.origin((200,240), 1, -1) ## first, find end point of range[0] and plot the line try: angle = 360.0 / (100.0/Range[0]) except ZeroDivisionError: angle = 0 angle = angle * deg2rad x1 = int(radius * math.cos(angle)) y1 = int(radius * math.sin(angle)) self.image.line((0,0), (x1,y1), self.black) ## then the second try: angle = 360.0 / (100.0/Range[1]) except ZeroDivisionError: angle = 0 angle = angle * deg2rad x2 = int(radius * math.cos(angle)) y2 = int(radius * math.sin(angle)) self.image.line((0,0), (x2,y2), self.black) ##then fill it tmp = ((Range[1] - Range[0]) / 2) + Range[0] try: angle = 360.0 / (100.0/tmp) except ZeroDivisionError: angle = 0 angle = angle * deg2rad x = int((radius*.80) * math.cos(angle)) y = int((radius*.80) * math.sin(angle)) self.image.fill((x,y), color) # print percent value percent = round(Range[1] - Range[0], 1) pct_text = str(percent) + '%' if percent >= 2.0: ## if the wedge is too small, move the text outside the circle x = int((radius*0.80) * math.cos(angle)) y = int((radius*0.80) * math.sin(angle)) self.image.string(gd.gdFontSmall, (x-10,y+5), pct_text, self.black) else: x = int((radius*1.09) * math.cos(angle)) y = int((radius*1.09) * math.sin(angle)) self.image.string(gd.gdFontSmall, (x-10,y+5), pct_text, self.black) self.image.origin(self.Origin[0], self.Origin[1], self.Origin[2]) ## reset origin def plot(self): ccount = 0 init = 0 for a in self.data: Range = (init*100, (a[1] + init)*100) self.wedge(Range, self.fgcolors[ccount]) init = init + a[1] ccount = ccount + 1 if ccount > len(self.fgcolors) -1: ccount = 4 def datacheck(self): total = 0 total = float(total) data2 = [] for a in self.data: total = total + a[1] for a in self.data: a = a[0], a[1] / total data2.append(a) self.data = data2 def MakeChart(self): self.circle() self.datacheck() self.Legend() self.plot() def Test(): data1 = [('Bob',30), ('Joe', 40), ('Ralph', 15), ('Mike', 60), ('Ed', 35), ('Bill',47.5), ('Frank', 75), ('Marlo', 25), ('Tarak', 90), ('Jon', 5), ('Jerry', 42.5), ('Pina', 110), ('Sara', 65), ('Lisa', 160), ('Megan', 45), ('Rascal', 115), ('Zippy', 10), ('Homer', 95)] data2 = [('TCP/IP', 70), ('IPX', 19), ('VINES', 7), ('DECNET', 4)] data3 = [] for a in range(32): txt = 'Item' + str(a) b = (a*a) data3.append(txt, b) data4 = [('A', 10), ('B', 5), ('C', 2), ('D', 0), ('E', -2), ('F', -5), ('G', -10)] data5 = [] for a in range(-30, 31): data5.append(str(a), a*a*a/500) bc = BarChart(data1,1,1,"Bar Chart") bc.MakeChart() bc.Output("./bartest.gif") pc = PieChart(data2, 0, 1, "Pie Chart") pc.MakeChart() pc.Output("./pietest.gif") lc = LineChart(data5,0,0,"Line Chart") lc.MakeChart() lc.Output("./linetest.gif") if __name__ == '__main__': Test() #====================== END CODE ================================
-----Original Message----- From: Ulrich Wisser [mailto:u.wisser@luna-park.de] Sent: Thursday, November 04, 1999 11:43 AM To: zope@zope.org Subject: [Zope] graphics on the fly
Hi,
is there a Zope (or Python) Interface to GD? I need to make graphics as dynamic objects. The data will be stored in a SQL datbase and from there I will have to make a bar/pie/line chart out of it.
I know how to do it in Perl (<- forgive me :). Is there an object or library to do it in Zope/Python?
Thanks
Ulli -- ----------------- Die Website Effizienzer ------------------ luna-park Bravo Sanchez, Vollmert, Wisser GbR Ulrich Wisser mailto:u.wisser@luna-park.de Alter Schlachthof, Immenburgstr. 20 Tel +49-228-9654055 D-53121 Bonn X-Mozilla-Status: 00094057 ------------------http://www.luna-park.de ------------------
_______________________________________________ Zope maillist - Zope@zope.org http://lists.zope.org/mailman/listinfo/zope No cross posts or HTML encoding! (Related lists - http://lists.zope.org/mailman/listinfo/zope-announce http://lists.zope.org/mailman/listinfo/zope-dev )
participants (1)
-
ScherBi@BAM.com