说明:一些注意事项和前提,在初次实现博客中,已经说明,本博客,直接上再次实现的代码。
初次实现:https://blog.csdn.net/weixin_42163563/article/details/103273925
代码展示:
from urllib.request import urlopen
from reportlab.graphics.shapes import *
from reportlab.graphics.charts.lineplots import LinePlot
from reportlab.graphics.charts.textlabels import Label
from reportlab.graphics import renderPDF
URL = 'ftp://ftp.swpc.noaa.gov/pub/weekly/Predict.txt'
COMMENT_CHARS = '#:'
drawing = Drawing(400, 200)
data = []
for line in urlopen(URL).readlines():
line = line.decode()
if not line.isspace() and line[0] not in COMMENT_CHARS:
data.append([float(n) for n in line.split()])
pred = [row[2] for row in data]
high = [row[3] for row in data]
low = [row[4] for row in data]
times = [row[0] + row[1] / 12.0 for row in data]
lp = LinePlot()
lp.x = 50
lp.y = 50
lp.height = 125
lp.width = 300
lp.data = [list(zip(times, pred)),
list(zip(times, high)), list(zip(times, low))]
lp.lines[0].strokeColor = colors.blue
lp.lines[1].strokeColor = colors.red
lp.lines[2].strokeColor = colors.green
drawing.add(lp)
drawing.add(String(250, 150, 'Sunspots', fontSize=14, fillColor=colors.red))
renderPDF.drawToFile(drawing, 'report2.pdf', 'Sunspots')
运行结果,生成report2.pdf文件,打开如下:我的建议,多读源代码,我自己也在慢慢读的。
部分源代码展示:
class LinePlot(AbstractLineChart):
"""Line plot with multiple lines.
Both x- and y-axis are value axis (so there are no seperate
X and Y versions of this class).
"""
_attrMap = AttrMap(BASE=PlotArea,
reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1),
lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1),
lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'),
lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'),
lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'),
joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'),
strokeColor = AttrMapValue(isColorOrNone, desc='Color used for background border of plot area.'),
fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'),
lines = AttrMapValue(None, desc='Handle of the lines.'),
xValueAxis = AttrMapValue(None, desc='Handle of the x axis.'),
yValueAxis = AttrMapValue(None, desc='Handle of the y axis.'),
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) x/y tuples.'),
annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1),
behindAxes = AttrMapValue(isBoolean, desc='If true use separate line group.',advancedUsage=1),
gridFirst = AttrMapValue(isBoolean, desc='If true use draw grids before axes.',advancedUsage=1),
)
def __init__(self):
PlotArea.__init__(self)
self.reversePlotOrder = 0
self.xValueAxis = XValueAxis()
self.yValueAxis = YValueAxis()
# this defines two series of 3 points. Just an example.
self.data = [
((1,1), (2,2), (2.5,1), (3,3), (4,5)),
((1,2), (2,3), (2.5,2), (3,4), (4,6))
]
self.lines = TypedPropertyCollection(LinePlotProperties)
self.lines.strokeWidth = 1
self.lines[0].strokeColor = colors.red
self.lines[1].strokeColor = colors.blue
self.lineLabels = TypedPropertyCollection(Label)
self.lineLabelFormat = None
self.lineLabelArray = None
# this says whether the origin is inside or outside
# the bar - +10 means put the origin ten points
# above the tip of the bar if value > 0, or ten
# points inside if bar value < 0. This is different
# to label dx/dy which are not dependent on the
# sign of the data.
self.lineLabelNudge = 10
# if you have multiple series, by default they butt
# together.
# New line chart attributes.
self.joinedLines = 1 # Connect items with straight lines.
#private attributes
self._inFill = None
self.annotations = []
self.behindAxes = 0
self.gridFirst = 0
def demo(self):
"""Shows basic use of a line chart."""
drawing = Drawing(400, 200)
data = [
((1,1), (2,2), (2.5,1), (3,3), (4,5)),
((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
]
lp = LinePlot()
lp.x = 50
lp.y = 50
lp.height = 125
lp.width = 300
lp.data = data
lp.joinedLines = 1
lp.lineLabelFormat = '%2.0f'
lp.strokeColor = colors.black
lp.lines[0].strokeColor = colors.red
lp.lines[0].symbol = makeMarker('FilledCircle')
lp.lines[1].strokeColor = colors.blue
lp.lines[1].symbol = makeMarker('FilledDiamond')
lp.xValueAxis.valueMin = 0
lp.xValueAxis.valueMax = 5
lp.xValueAxis.valueStep = 1
lp.yValueAxis.valueMin = 0
lp.yValueAxis.valueMax = 7
lp.yValueAxis.valueStep = 1
drawing.add(lp)
return drawing
def calcPositions(self):
"""Works out where they go.
Sets an attribute _positions which is a list of
lists of (x, y) matching the data.
"""
self._seriesCount = len(self.data)
self._rowLength = max(list(map(len,self.data)))
self._positions = []
for rowNo in range(len(self.data)):
line = []
for colNo in range(len(self.data[rowNo])):
datum = self.data[rowNo][colNo] # x,y value
if isinstance(datum[0],str):
x = self.xValueAxis.scale(mktime(mkTimeTuple(datum[0])))
else:
x = self.xValueAxis.scale(datum[0])
y = self.yValueAxis.scale(datum[1])
line.append((x, y))
self._positions.append(line)
def _innerDrawLabel(self, rowNo, colNo, x, y):
"Draw a label for a given item in the list."
labelFmt = self.lineLabelFormat
labelValue = self.data[rowNo][colNo][1] ###
if labelFmt is None:
labelText = None
elif isinstance(labelFmt,str):
if labelFmt == 'values':
labelText = self.lineLabelArray[rowNo][colNo]
else:
labelText = labelFmt % labelValue
elif hasattr(labelFmt,'__call__'):
if not hasattr(labelFmt,'__labelFmtEX__'):
labelText = labelFmt(labelValue)
else:
labelText = labelFmt(self,rowNo,colNo,x,y)
else:
raise ValueError("Unknown formatter type %s, expected string or function"% labelFmt)
if labelText:
label = self.lineLabels[(rowNo, colNo)]
if not label.visible: return
#hack to make sure labels are outside the bar
if y > 0:
label.setOrigin(x, y + self.lineLabelNudge)
else:
label.setOrigin(x, y - self.lineLabelNudge)
label.setText(labelText)
else:
label = None
return label
def drawLabel(self, G, rowNo, colNo, x, y):
'''Draw a label for a given item in the list.
G must have an add method'''
G.add(self._innerDrawLabel(rowNo,colNo,x,y))
def makeLines(self):
g = Group()
yA = self.yValueAxis
xA = self.xValueAxis
bubblePlot = getattr(self,'_bubblePlot',None)
if bubblePlot:
bubbleR = min(yA._bubbleRadius,xA._bubbleRadius)
bubbleMax = xA._bubbleMax
labelFmt = self.lineLabelFormat
P = list(range(len(self._positions)))
if self.reversePlotOrder: P.reverse()
inFill = getattr(self,'_inFill',None)
lines = self.lines
styleCount = len(lines)
if inFill or [rowNo for rowNo in P if getattr(lines[rowNo%styleCount],'inFill',False)]:
inFillY = getattr(inFill,'yValue',None)
if inFillY is None:
inFillY = xA._y
else:
inFillY = yA.scale(inFillY)
inFillX0 = yA._x
inFillX1 = inFillX0 + xA._length
inFillG = getattr(self,'_inFillG',g)
lG = getattr(self,'_lineG',g)
# Iterate over data rows.
for rowNo in P:
row = self._positions[rowNo]
styleRowNo = rowNo % styleCount
rowStyle = lines[styleRowNo]
rowColor = getattr(rowStyle,'strokeColor',None)
dash = getattr(rowStyle, 'strokeDashArray', None)
if hasattr(rowStyle, 'strokeWidth'):
width = rowStyle.strokeWidth
elif hasattr(lines, 'strokeWidth'):
width = lines.strokeWidth
else:
width = None
# Iterate over data columns.
if self.joinedLines:
points = []
for xy in row:
points += [xy[0], xy[1]]
if inFill or getattr(rowStyle,'inFill',False):
fpoints = [inFillX0,inFillY] + points + [inFillX1,inFillY]
filler = getattr(rowStyle, 'filler', None)
if filler:
filler.fill(self,inFillG,rowNo,rowColor,fpoints)
else:
inFillG.add(Polygon(fpoints,fillColor=rowColor,strokeColor=rowColor,strokeWidth=width or 0.1))
if inFill in (None,0,2):
line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1)
if width:
line.strokeWidth = width
if dash:
line.strokeDashArray = dash
lG.add(line)
if hasattr(rowStyle, 'symbol'):
uSymbol = rowStyle.symbol
elif hasattr(lines, 'symbol'):
uSymbol = lines.symbol
else:
uSymbol = None
if uSymbol:
if bubblePlot: drow = self.data[rowNo]
for j,xy in enumerate(row):
if (styleRowNo,j) in lines._children:
juSymbol = getattr(lines[styleRowNo,j],'symbol',uSymbol)
else:
juSymbol = uSymbol
if juSymbol is uSymbol:
symbol = uSymbol
symColor = rowColor
else:
symbol = juSymbol
symColor = getattr(symbol,'fillColor',rowColor)
symbol = uSymbol2Symbol(symbol,xy[0],xy[1],symColor)
if symbol:
if bubblePlot:
symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5
g.add(symbol)
else:
if bubblePlot: drow = self.data[rowNo]
for j,xy in enumerate(row):
juSymbol = getattr(lines[styleRowNo,j],'symbol',None)
if not juSymbol: continue
symColor = getattr(juSymbol,'fillColor',getattr(juSymbol,'strokeColor',rowColor))
symbol = uSymbol2Symbol(juSymbol,xy[0],xy[1],symColor)
if symbol:
if bubblePlot:
symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5
g.add(symbol)
# Draw data labels.
for colNo in range(len(row)):
x1, y1 = row[colNo]
self.drawLabel(g, rowNo, colNo, x1, y1)
shader = getattr(rowStyle, 'shader', None)
if shader: shader.shade(self,g,rowNo,rowColor,row)
return g
def draw(self):
yA = self.yValueAxis
xA = self.xValueAxis
if getattr(self,'_bubblePlot',None):
yA._bubblePlot = xA._bubblePlot = 1
yA.setPosition(self.x, self.y, self.height)
if yA: yA.joinAxis = xA
if xA: xA.joinAxis = yA
yA.configure(self.data)
# if zero is in chart, put x axis there, otherwise use bottom.
xAxisCrossesAt = yA.scale(0)
if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
y = self.y
else:
y = xAxisCrossesAt
xA.setPosition(self.x, y, self.width)
xA.configure(self.data)
self.calcPositions()
g = Group()
g.add(self.makeBackground())
if self._inFill or self.behindAxes:
xA._joinToAxis()
if self._inFill:
self._inFillG = Group()
g.add(self._inFillG)
if self.behindAxes:
self._lineG = Group()
g.add(self._lineG)
xA._joinToAxis()
yA._joinToAxis()
xAex = xA.visibleAxis and [xA._y] or []
yAex = yA.visibleAxis and [yA._x] or []
skipGrid = getattr(xA,'skipGrid','none')
if skipGrid!=None:
if skipGrid in ('both','top'):
yAex.append(xA._x+xA._length)
if skipGrid in ('both','bottom'):
yAex.append(xA._x)
skipGrid = getattr(yA,'skipGrid','none')
if skipGrid!=None:
if skipGrid in ('both','top'):
xAex.append(yA._y+yA._length)
if skipGrid in ('both','bottom'):
xAex.append(yA._y)
if self.gridFirst:
xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
g.add(xA.draw())
g.add(yA.draw())
if not self.gridFirst:
xAdgl = getattr(xA,'drawGridLast',False)
yAdgl = getattr(yA,'drawGridLast',False)
if not xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
if not yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
annotations = getattr(self,'annotations',[])
for a in annotations:
if getattr(a,'beforeLines',None):
g.add(a(self,xA.scale,yA.scale))
g.add(self.makeLines())
if not self.gridFirst:
if xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
if yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
for a in annotations:
if not getattr(a,'beforeLines',None):
g.add(a(self,xA.scale,yA.scale))
return g
def addCrossHair(self,name,xv,yv,strokeColor=colors.black,strokeWidth=1,beforeLines=True):
from reportlab.graphics.shapes import Group, Line
annotations = [a for a in getattr(self,'annotations',[]) if getattr(a,'name',None)!=name]
def annotation(self,xScale,yScale):
x = xScale(xv)
y = yScale(yv)
g = Group()
xA = xScale.__self__ #the x axis
g.add(Line(xA._x,y,xA._x+xA._length,y,strokeColor=strokeColor,strokeWidth=strokeWidth))
yA = yScale.__self__ #the y axis
g.add(Line(x,yA._y,x,yA._y+yA._length,strokeColor=strokeColor,strokeWidth=strokeWidth))
return g
annotation.beforeLines = beforeLines
annotations.append(annotation)
self.annotations = annotations
至此,再次实现项目完成。