Reportlab生成PDF文件

散点图(Scatter Plot)也是折线图的一种,毕竟用线把点连上就是折线图了。这个散点图例子比较特殊,每组数据只有一个点,然后选择一个点做交叉瞄准线:

代码和注释如下,个别行的注释参见前面的帖子:

"Scatter plot with legend"
from reportlab.lib.colors import black, rgb2cmyk, toColor, red, green, blue
from reportlab.graphics.charts.lineplots import ScatterPlot
from reportlab.graphics.charts.legends import Legend
from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin
from reportlab.lib.validators import Auto
from reportlab.graphics.widgets.markers import makeMarker
from reportlab.graphics.charts.textlabels import Label

class ScatterChart_CrossHair(_DrawingEditorMixin,Drawing):
    '''
        带交叉瞄准线的散点图
    '''
    def __init__(self,width=144,height=85,*args,**kw):
        Drawing.__init__(self,width,height,*args,**kw)

        # 公共变量
        strokeDashArray = 1, 1
        fontName = 'Helvetica'
        fontSize = 6

        # 散点图
        self._add(self,ScatterPlot(),name='chart',validate=None,desc=None)
        self.chart.leftPadding  = 0     # 左留白
        self.chart.rightPadding = 0     # 右留白
        self.chart.topPadding   = 0     # 上留白
        self.chart.bottomPadding = 0    # 下留白
        self.chart.lineLabelFormat  = None
        self.chart.outerBorderColor = black # 图表周线的颜色
        # self.chart.outerBorderColor = red

        # 定义散点的点符号
        self.chart.lines.symbol = makeMarker('FilledSquare')
        self.chart.lines.symbol.size        = 6
        # self.chart.lines.strokeWidth     = 10

        # 坐标轴自带的标签为空
        self.chart.xLabel           = ''
        self.chart.yLabel           = ''

        # x轴
        self.chart.xValueAxis.labels.fontName   = fontName
        self.chart.xValueAxis.labels.fontSize   = fontSize
        # self.chart.xValueAxis.valueStep             = 0.5   # x轴上刻度步长
        self.chart.xValueAxis.visibleGrid           = 1
        # 显示x轴上的格子线
        self.chart.xValueAxis.gridStrokeDashArray   = strokeDashArray
        # 格子线的样式
        self.chart.xValueAxis.minimumTickSpacing    = 10
        self.chart.xValueAxis.maximumTicks          = 8
        self.chart.xValueAxis.forceZero             = 1
        self.chart.xValueAxis.avoidBoundFrac        = 1
        self.chart.xValueAxis.visibleTicks          = False
        # y轴
        self.chart.yValueAxis.labels.fontName   = fontName
        self.chart.yValueAxis.labels.fontSize   = fontSize
        self.chart.yValueAxis.labelTextFormat       = None
        self.chart.yValueAxis.visibleGrid           = 1
        self.chart.yValueAxis.gridStrokeDashArray   = strokeDashArray
        self.chart.yValueAxis.drawGridLast          = True
        # 如果为真,其他都画完后才画格子线,默认为False
        self.chart.yValueAxis.minimumTickSpacing    = 10
        self.chart.yValueAxis.maximumTicks          = 8
        self.chart.yValueAxis.forceZero             = 1
        self.chart.yValueAxis.avoidBoundFrac        = 1
        self.chart.yValueAxis.visibleTicks          = False

        # 图例
        self._add(self,Legend(),name='legend',validate=None,desc=None)
        self.legend.fontName                        = fontName
        self.legend.fontSize                        = 8
        self.legend.strokeWidth                     = 3
        self.legend.strokeColor                     = blue
        self.legend.boxAnchor                       = 'w'
        self.legend.alignment                       = 'right'
        self.legend.dx = self.legend.dy             = 6
        self.legend.deltax                          = 30
        self.legend.deltay                          = 0
        self.legend.dxTextSpace                     = 2
        self.legend.columnMaximum                   = 1
        self.legend.variColumn                      = 1

        # xal
        self._add(self,Label(),name='xal',validate=None,desc=None)
        self.xal.fontName       = fontName
        self.xal.fontSize         = 8
        self.xal._text = '5-year annualized standard deviation'

        # yal
        self._add(self,Label(),name='yal',validate=None,desc=None)
        self.yal.fontName       = fontName
        self.yal.fontSize         = 8
        self.yal._text = '5-year average\nannual return (%)'
        self.yal.angle          = 90
        self.yal.textAnchor     ='middle'


        # 原始样本数据
        self._data = [('Portfolio', 2.25, 4.4800000000000004),
                      ('Index', 3.5899999999999999, 4.4199999999999999),
                      ('Universe Mean', 3.52, 3.48)]
        self._primary = 2               # 要根据哪个数据点画交叉瞄准线
        self._crossHairStrokeWidth = 2  # 交叉瞄准线的宽度
        # self._colorsList = [red, green, blue] # 颜色方案
        self._colorsList = [toColor(rgb2cmyk(*red.rgb())), toColor(rgb2cmyk(*green.rgb())), toColor(rgb2cmyk(*blue.rgb()))]

        sd             = [tuple(x[1:]) for x in self._data]
        # 从原始样本数据中取出数据部分:[(2.25, 4.48), (3.59, 4.42), (3.52, 3.48)]
        self.chart.data = [[x] for x in sd]
        # 规整了数据存放格式:[[(2.25, 4.48)], [(3.59, 4.42)], [(3.52, 3.48)]]

        nd = len(sd)    # 有几组数据
        for i in range(nd):     # 每条数据线的标记、标记尺寸、颜色和代表的意义
            # print(self.chart.lines[i].symbol.kind, self.chart.lines[i].symbol.size)
            self.chart.lines[i].symbol = self.chart.lines.symbol
            self.chart.lines[i].symbol.size = self.chart.lines.symbol.size
            self.chart.lines[i].strokeColor = self._colorsList[i]
            self.chart.lines[i].name = self._data[i][0]

        # 颜色对应启动自动赋值
        self.legend.colorNamePairs = Auto(obj=self.chart)
        # self.legend.colorNamePairs = [(Auto(obj=self.chart),self._data[i][0]) for i in range(nd)]


        # 选一个数据点做交叉瞄准线
        # 数据点的坐标是(sd[self._primary][0], sd[self._primary][1])
        if 0 < self._primary < nd:
            self.chart.addCrossHair('pch', sd[self._primary][0], sd[self._primary][1],
                                    strokeColor=self.chart.lines[self._primary].strokeColor,
                                    strokeWidth=self._crossHairStrokeWidth)
            # 添加交叉瞄准线的方法函数addCrossHair()所带参数依次为:名字,交叉瞄准线的中心坐标,颜色和宽度

        # 画板宽高
        self.height      = 200
        self.width       =400

        # 图表宽高和坐标
        self.chart.y                = 50
        self.chart.x                = 50
        self.chart.height           = 125
        self.chart.width            = 250

        # 标签锚点和坐标
        self.xal.boxAnchor        = 'w'
        self.xal.x                = 50
        self.xal.y                = 30
        self.yal.boxAnchor        = 'c'
        self.yal.y                = 100
        self.yal.x                = 25

        # 图例坐标
        self.legend.x              = 50
        self.legend.y              = 15

if __name__=="__main__": #NORUNTESTS
    ScatterChart_CrossHair().save(formats=['pdf'],outDir='.',fnRoot=None)