"Area with dynamic label" #Autogenerated by ReportLab guiedit do not edit from reportlab.lib.normalDate import NormalDate from reportlab.graphics.charts.lineplots import AreaLinePlot, LinePlot from reportlab.graphics.charts.legends import LineLegend from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String, Line from reportlab.pdfbase.pdfmetrics import stringWidth, EmbeddedType1Face, registerTypeFace, Font, registerFont from reportlab.lib.colors import PCMYKColor, black, white from reportlab.graphics.charts.axes import XValueAxis, YValueAxis, AdjYValueAxis, NormalDateXValueAxis from reportlab.lib.formatters import DecimalFormatter from reportlab.graphics.charts.textlabels import Label class DynamicLabel(Label): def __init__(self,text,ux,uy,**kw): Label.__init__(self,**kw) self._text = text self._ux = ux self._uy = uy def asAnnotation(self,chart,xscale,yscale): self.x = xscale(self._ux) #- 3 # moves it off the edge self.y = yscale(self._uy) #+ 7 # lift it up the chart return None def addConditionalSubTicks(chart): ''' if the dates of the axis jumps one year then this will add subticks between ''' chart.xValueAxis.configure([chart.data]) if int(str(chart.xValueAxis._tickValues[-1])[:4])-int(str(chart.xValueAxis._tickValues[-2])[:4])>1: chart.xValueAxis.subTickNum = 1 chart.xValueAxis.visibleSubTicks = True chart.xValueAxis.subTickLo = chart.xValueAxis.tickDown class AreaChart02(_DrawingEditorMixin,Drawing): ''' Chart Features -------------- This is an Area Chart that demonstrates a dynamic labels that holds the ending value of the charting period. - The Chart vertical axis (the value axis) can show different formatting for last label - A label that can fit itself automatically by looking for an empty space to fit itself. The label text can be of any choice and has access to the data of the chart so can display them in any given format. ReportLab can also show different types of labels such as highest value, lowest value, begining value, etc ... **NOTE**: placing the label is done with python code so is not shown to the attributes list in the side pane. - The X Axis can show extra tick when the distance between teh first and the second ticks are longer than the distances between other ticks. see chart.xValueAxis.specialTickClear=True - The labels formats can be achieved with different ways, we support Microsoft Excel Formatting Notation such as chart.xValueAxis.xLabelFormat={m}/{yy} and we also support any Python Function for advanced formatting. ''' def __init__(self,width=340,height=126,*args,**kw): Drawing.__init__(self, width, height, *args, **kw) # font fontName = 'Helvetica' # chart self._add(self,AreaLinePlot(),name='chart',validate=None,desc=None) self.chart.x = 25 self.chart.y = 17.5 self.chart.width = 305 self.chart.height = 100 self.chart.lines[0].strokeColor = PCMYKColor(100, 0, 90, 50, alpha=40) gridStickOut = 4 strokeDashArray = (0.4,0.4) # x axis self.chart.xValueAxis = NormalDateXValueAxis() self.chart.xValueAxis.labels.boxAnchor ='autox' self.chart.xValueAxis.labels.fontName = fontName self.chart.xValueAxis.labels.fontSize = 6 self.chart.xValueAxis.strokeWidth = 0.5 self.chart.xValueAxis.strokeDashArray = strokeDashArray self.chart.xValueAxis.labels.topPadding = 3 self.chart.xValueAxis.maximumTicks = 15 self.chart.xValueAxis.visibleTicks = True self.chart.xValueAxis.forceFirstDate = 1 self.chart.xValueAxis.maximumTicks = 6 self.chart.xValueAxis.niceMonth = 0 self.chart.xValueAxis.xLabelFormat = '{m}/{yy}' # y axis self.chart.yValueAxis.labels.fontName = fontName self.chart.yValueAxis.labels.fontSize = 6 self.chart.yValueAxis.forceZero = 1 self.chart.yValueAxis.gridStrokeDashArray = strokeDashArray self.chart.yValueAxis.visibleGrid = 1 self.chart.yValueAxis.labelTextFormat = lambda v,a=self.chart.yValueAxis: (abs(v-a._valueMax)<1e-6 and '$%2d' or '%2d')%v self.chart.yValueAxis.strokeWidth = 0 self.chart.yValueAxis.strokeDashArray = strokeDashArray self.chart.yValueAxis.labels.rightPadding = 0 #self.chart.yValueAxis.origShiftIPC = 1 self.chart.yValueAxis.maximumTicks = 6 self.chart.yValueAxis.rangeRound ='both' #self.chart.yValueAxis.avoidBoundFrac = 0.1 self.chart.yValueAxis.gridStart = self.chart.x-gridStickOut self.chart.yValueAxis.gridEnd = self.chart.x+self.chart.width+gridStickOut # subgrid settings self.chart.yValueAxis.subTickNum = 1 # one minor ticks between ticks self.chart.yValueAxis.visibleSubGrid = True self.chart.yValueAxis.visibleSubTicks = True self.chart.yValueAxis.subGridStrokeWidth = self.chart.yValueAxis.gridStrokeWidth self.chart.yValueAxis.subGridStrokeDashArray= strokeDashArray self.chart.yValueAxis.subGridStart = self.chart.yValueAxis.gridStart self.chart.yValueAxis.subGridEnd = self.chart.yValueAxis.gridEnd self.chart.yValueAxis.visibleTicks = False self._label_format = DecimalFormatter(0, prefix='$', suffix=None, thousandSep=',') # sample data #self.chart.data = [(20030228, 10020.0), (20030331, 9910.0), (20030430, 10240.0), (20030530, 10660.0), (20030630, 10680.0), (20030731, 10690.0), (20030829, 10850.0), (20030930, 10760.0)] self.chart.data = [(20030228, 10020.0), (20030331, 9910.0), (20030430, 10240.0), (20030530, 10660.0), (20030630, 10680.0), (20030731, 10690.0), (20030829, 10850.0), (20030930, 10760.0), (20031031, 11170.0), (20031128, 11280.0), (20031231, 11553.190000000001), (20040130, 11635.57), (20040227, 11707.65), (20040331, 11635.57), (20040430, 11388.440000000001), (20040528, 11460.52), (20040630, 11651.870000000001), (20040730, 11233.110000000001), (20040831, 11306.4), (20040930, 11358.74), (20041029, 11536.709999999999), (20041130, 11945.0), (20041231, 12295.450000000001), (20050131, 11966.209999999999), (20050228, 12102.450000000001), (20050331, 11932.15), (20050429, 11716.440000000001), (20050531, 12034.33), (20050630, 11991.209999999999), (20050729, 12426.84), (20050831, 12254.879999999999), (20050930, 12289.280000000001), (20051031, 12128.780000000001), (20051130, 12564.41), (20051230, 12584.99), (20060131, 12967.76), (20060228, 12921.360000000001), (20060331, 13257.74), (20060428, 13338.93), (20060531, 12874.969999999999), (20060630, 12857.6), (20060731, 12834.35), (20060831, 12985.48), (20060929, 13171.49), (20061031, 13555.120000000001), (20061130, 13776.0), (20061229, 13886.870000000001), (20070131, 14053.4), (20070228, 13848.43), (20070330, 13925.299999999999), (20070430, 14335.24), (20070531, 14655.51), (20070629, 14460.98), (20070731, 14190.08), (20070831, 14319.08), (20070928, 14770.59), (20071031, 14925.389999999999), (20071130, 14460.98), (20071231, 14494.120000000001), (20080131, 13829.26), (20080229, 13637.18), (20080331, 13651.959999999999)] # chart horizonatl line on the axis self.chart.annotations=[lambda c,cA,vA: Line(c.x,c.y-gridStickOut,c.x,c.y+c.height+gridStickOut,strokeColor=black,strokeWidth=0.5)] # chart vertical line on the right of the chart self.chart.annotations.append(lambda c,cA,vA: Line(c.x+c.width,c.y-gridStickOut,c.x+c.width,c.y+c.height+gridStickOut,strokeColor=black,strokeWidth=0.5)) self._add(self,DynamicLabel('', NormalDate(self.chart.data[0][0]),self.chart.data[0][1]),name='label',validate=None,desc=None) fontName = 'Helvetica' fontSize = 7 self.label.fontName = fontName self.label.fontSize = fontSize self.label.fillColor = self.chart.lines[0].strokeColor self.label.boxFillColor = white self.label.textAnchor = 'end' self.label.boxAnchor ='se' self.label.textAnchor ='start' self.label.dx = -1 self.chart.annotations.append(self.label.asAnnotation) self.chart.xValueAxis.specialTickClear = 1 def getContents(self): #self.chart.debug = 1 # font bold # label - ending value dates = [d[0] for d in self.chart.data] values = [d[1] for d in self.chart.data] # if the sequence is 1,3,5 or 2,4,6 add subticks addConditionalSubTicks(self.chart) #look for the highest value under the final label - last 25% of data points mg = max(values) sf = mg/self.chart.height m = max(values[int(0.75*len(values)):])+sf*5 mq = m + sf*self.label.fontSize self.chart.yValueAxis.valueMax = max(mg,mq) format = self._label_format self.label._ux = NormalDate(dates[-1]) self.label._uy = m self.label._text = ' Ending Value %s '%format(values[-1]*1000) self.label.fillColor = black return Drawing.getContents(self) if __name__=="__main__": #NORUNTESTS AreaChart02().save(formats=['pdf'],outDir='.',fnRoot=None)