matplotlib绘制的多指标k线图

 下面的代码有了几个增强的功能:

k线图上叠加其他指标

多个字图的高度实现了不同

效果图:

# -*- coding: utf-8 -*-
 
 
 
import os
import sys
import pickle
import math
import datetime
import itertools
import matplotlib
 
matplotlib.use("WXAgg", warn=True)  # 这个要紧跟在 import matplotlib 之后,而且必须安装了 wxpython 2.8 才行。
 
import matplotlib.pyplot as pyplot
import matplotlib.font_manager as font_manager 
 
import numpy
from matplotlib.ticker import NullLocator, FixedLocator, MultipleLocator, FuncFormatter, NullFormatter
from matplotlib.patches import Ellipse
 
 
 
__font_properties__= font_manager.FontProperties(fname='/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc')
__color_lightsalmon__= '#ffa07a'
__color_pink__= '#ffc0cb'
__color_navy__= '#000080'
__color_gold__= '#FDDB05'
__color_gray30__= '0.3'
__color_gray70__= '0.7'
__color_lightblue__= 'lightblue'
 
 
 
__shrink__= 1.0 / 4
__expbase__= 1.1
 
 
 
 
 
class SubPlot_BasicInfo:
    '''
    公司的基本信息
    Note: this is not "real" subplot, no Axes object contained.
    '''
 
    def __init__(self, pdata, parent, name):
        self._name= name
        self._pdata= pdata
        self._cominfo= self._pdata[u'公司信息']
        self._parent= parent
 
        self._Axes= None
 
        self._xsize, \
        self._ysize= self._compute_size()
 
 
 
    def _compute_size(self):
        return (300.0, 1.8)
 
 
 
    def get_size(self):
        return (self._xsize, self._ysize)
 
 
 
    def build_axes(self, figobj, rect):
        axes= figobj.add_axes(rect)
        axes.set_frame_on(False)
        self._Axes= axes
 
        self.set_xticks()
        self.set_yticks()
 
 
 
    def set_xticks(self):
 
        axes= self._Axes
        xaxis= axes.get_xaxis()
 
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_xlim(0, self._xsize)
 
        xaxis.set_major_locator(NullLocator())
 
        for mal in axes.get_xticklabels(minor=False):
            mal.set_visible(False)
 
        for mil in axes.get_xticklabels(minor=True):
            mil.set_visible(False)
 
 
 
    def set_yticks(self):
 
        axes= self._Axes
        yaxis= axes.get_yaxis()
 
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_ylim(0, self._ysize)
 
        yaxis.set_major_locator(NullLocator())
 
        for mal in axes.get_yticklabels(minor=False):
            mal.set_visible(False)
 
        for mil in axes.get_yticklabels(minor=True):
            mil.set_visible(False)
 
 
 
    def plot(self):
 
        self.plot_codesymbol(xbase=0.0, ybase=self._ysize)
        self.plot_codesymbol_2(xbase=self._xsize, ybase=self._ysize)
        self.plot_companyname(xbase=0.0, ybase=self._ysize-0.8)
        self.plot_companylocation(xbase=48.0, ybase=self._ysize)
        self.plot_mainbusiness(xbase=48.0, ybase=self._ysize)
        self.plot_description(xbase=90.0, ybase=self._ysize)
        self.plot_sortinginfo(xbase=165.0, ybase=self._ysize)
 
 
 
    def plot_codesymbol(self, xbase, ybase):
        '''
        交易代码、公司简称
        '''
 
        txtstr= self._cominfo[u'代码'] + u'   ' + self._cominfo[u'简称']
        label= self._Axes.text(xbase, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left')
        label.set_fontsize(16.0)
 
 
 
    def plot_codesymbol_2(self, xbase, ybase):
        txtstr= self._cominfo[u'简称二']
        label= self._Axes.text(xbase, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='right')
        label.set_fontsize(16.0)
 
 
 
    def plot_companyname(self, xbase, ybase):
        '''
        曾用名、全名、英文名
        '''
 
        txtstr= self._cominfo[u'基本情况'][u'曾用名']
        txtlist= txtstr.split('->')
        if len(txtlist) > 15:
            txtstr= ' -> '.join(txtlist[:5]) + ' ->\n' + ' -> '.join(txtlist[5:10]) + ' ->\n' + ' -> '.join(txtlist[10:15]) + ' ->\n' + ' -> '.join(txtlist[15:]) + '\n'
        elif len(txtlist) > 10:
            txtstr= ' -> '.join(txtlist[:5]) + ' ->\n' + ' -> '.join(txtlist[5:10]) + ' ->\n' + ' -> '.join(txtlist[10:]) + '\n'
        elif len(txtlist) > 5:
            txtstr= ' -> '.join(txtlist[:5]) + ' ->\n' + ' -> '.join(txtlist[5:]) + '\n'
        else:
            txtstr= ' -> '.join(txtlist) + '\n'
        txtstr += self._cominfo[u'基本情况'][u'公司名称'] + '\n'
        txtstr += self._cominfo[u'基本情况'][u'英文名称']
 
        label= self._Axes.text(xbase, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left')
        label.set_fontsize(4.5)
 
 
 
    def plot_companylocation(self, xbase, ybase):
        '''
        地域、所属行业、上市日期
        '''
 
        txtstr= self._cominfo[u'公司概况'][u'区域'] + '   ' + self._cominfo[u'公司概况'][u'所属行业'] + '   ' + self._cominfo[u'发行相关'][u'上市日期']
 
        label= self._Axes.text(xbase, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left')
        label.set_fontsize(6.5)
 
 
 
    def plot_mainbusiness(self, xbase, ybase):
        '''
        主营业务
        '''
        # 查找表: (<文字长度>, <每行字数>, <字体大小>, <Y轴偏移量>)
        lookups= (
            (20, 10, 12.0, 0.5), 
            (45, 15, 8.2, 0.5), 
            (80, 20, 6.2, 0.5), 
            (125, 25, 5.0, 0.5), 
            (180, 30, 4.1, 0.5),
            (245, 35, 3.5, 0.4),
            (999999, 37, 3.4, 0.4)
        )
 
        txtstr= self._cominfo[u'基本情况'][u'主营业务']
        length= len(txtstr)
        for sizelimit, linelimit, fontsize, yshift in lookups:
            if length <= sizelimit:
                txtstr= '\n'.join([txtstr[linelimit*idx : linelimit*(idx+1)] for idx in range(length//linelimit + 1)])
                fsize= fontsize
                ycoord= ybase - yshift
                break
 
        label= self._Axes.text(xbase, ycoord, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left', color='blue')
        label.set_fontsize(fsize)
 
 
 
    def plot_description(self, xbase, ybase):
        '''
        公司简介
        '''
        # 查找表: (<文字长度>, <每行字数>, <字体大小>)
        lookups= (
            (150, 30, 7.0),
            (240, 40, 5.6),
            (329, 47, 4.8),
            (432, 54, 4.2),
            (576, 64, 3.5),
            (670, 67, 3.4),
            (792, 72, 3.1),
            (960, 80, 2.8),
            (1222, 94, 2.4),
            (1428, 102, 2.26),
            (1620, 108, 2.12),
            (1938, 114, 2.00),
            (999999, 130, 1.75)
        )
 
        txtstr= self._cominfo[u'公司概况'][u'公司简介']     # 26 ~ 2600 字符
        length= len(txtstr)
 
        for sizelimit, linelimit, fontsize in lookups:
            if length <= sizelimit:
                txtstr= '\n'.join([txtstr[linelimit*idx : linelimit*(idx+1)] for idx in range(length//linelimit + 1)])
                fsize= fontsize
                break
 
        label= self._Axes.text(xbase, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left')
        label.set_fontsize(fsize)
 
 
 
    def plot_sortinginfo(self, xbase, ybase):
        '''
        行业板块信息
        '''
        infolist= self._cominfo[u'行业板块']
 
        for idx in range(len(infolist)//10 + 1):
            txtstr= '\n'.join(infolist[10*idx : 10*(idx+1)])
            if not txtstr:
                break
            xcoord= xbase + 25.0*idx
            label= self._Axes.text(xcoord, ybase, txtstr, fontproperties=__font_properties__, verticalalignment='top', horizontalalignment='left', color='blue')
            label.set_fontsize(3.4)
 
 
 
 
 
 
 
class SubPlot_Financial:
    '''
    换手率子图
    '''
    pass
 
 
 
 
 
 
 
class SubPlot_PriceBase:
    '''
 
    '''
 
    def __init__(self, pdata, parent, xparams, name):
        '''
 
        '''
        self._name= name    # 派生类自己设置
        self._pdata= pdata
        self._parent= parent
        self._expbase= __expbase__
        self._xparams= xparams
        self._shrink= __shrink__ if name == 'pricefs' else 1.0
 
        # 绘图数据
        quotes= pdata[u'行情']
 
        if name == 'pricefs':
            self._dates= quotes[u'日期']
            self._open= quotes[u'开盘']
            self._close= quotes[u'收盘']
            self._high= quotes[u'最高']
            self._low= quotes[u'最低']
            if u'简化' in quotes:   self._simple= quotes[u'简化']
 
            #   if u'换手率' in quotes: self._torate= quotes[u'换手率']
            #   if u'成交量' in quotes: self._volume= quotes[u'成交量']
            #   if u'成交额' in quotes: self._turnover= quotes[u'成交额']
 
            if u'3日均' in quotes:  self._average3= quotes[u'3日均']
            if u'5日均' in quotes:  self._average5= quotes[u'5日均']
            if u'10日均' in quotes: self._average10= quotes[u'10日均']
            if u'30日均' in quotes: self._average30= quotes[u'30日均']
            if u'60日均' in quotes: self._average60= quotes[u'60日均']
 
            if u'开盘二' in quotes:
                self._open_2= quotes[u'开盘二']
                self._close_2= quotes[u'收盘二']
                self._high_2= quotes[u'最高二']
                self._low_2= quotes[u'最低二']
                if u'简化二' in quotes:   self._simple_2= quotes[u'简化二']
 
                #   if u'换手率二' in quotes: self._torate_2= quotes[u'换手率二']
                #   if u'成交量二' in quotes: self._volume_2= quotes[u'成交量二']
                #   if u'成交额二' in quotes: self._turnover_2= quotes[u'成交额二']
 
                if u'3日均二' in quotes:  self._average3_2= quotes[u'3日均二']
                if u'5日均二' in quotes:  self._average5_2= quotes[u'5日均二']
                if u'10日均二' in quotes: self._average10_2= quotes[u'10日均二']
                if u'30日均二' in quotes: self._average30_2= quotes[u'30日均二']
                if u'60日均二' in quotes: self._average60_2= quotes[u'60日均二']
 
        else:
            sidx, eidx= pdata[u'任务描述'][u'起始偏移'], pdata[u'任务描述'][u'结束偏移']
         
            self._dates= quotes[u'日期'][sidx:eidx]
            self._open= quotes[u'开盘'][sidx:eidx]
            self._close= quotes[u'收盘'][sidx:eidx]
            self._high= quotes[u'最高'][sidx:eidx]
            self._low= quotes[u'最低'][sidx:eidx]
            if u'简化' in quotes:   self._simple= quotes[u'简化'][sidx:eidx]
 
            #   if u'换手率' in quotes: self._torate= quotes[u'换手率'][sidx:eidx]
            #   if u'成交量' in quotes: self._volume= quotes[u'成交量'][sidx:eidx]
            #   if u'成交额' in quotes: self._turnover= quotes[u'成交额'][sidx:eidx]
 
            if u'3日均' in quotes:  self._average3= quotes[u'3日均'][sidx:eidx]
            if u'5日均' in quotes:  self._average5= quotes[u'5日均'][sidx:eidx]
            if u'10日均' in quotes: self._average10= quotes[u'10日均'][sidx:eidx]
            if u'30日均' in quotes: self._average30= quotes[u'30日均'][sidx:eidx]
            if u'60日均' in quotes: self._average60= quotes[u'60日均'][sidx:eidx]
 
            if u'开盘二' in quotes:
                self._open_2= quotes[u'开盘二'][sidx:eidx]
                self._close_2= quotes[u'收盘二'][sidx:eidx]
                self._high_2= quotes[u'最高二'][sidx:eidx]
                self._low_2= quotes[u'最低二'][sidx:eidx]
                if u'简化二' in quotes:   self._simple_2= quotes[u'简化二'][sidx:eidx]
 
                #   if u'换手率二' in quotes: self._torate_2= quotes[u'换手率二'][sidx:eidx]
                #   if u'成交量二' in quotes: self._volume_2= quotes[u'成交量二'][sidx:eidx]
                #   if u'成交额二' in quotes: self._turnover_2= quotes[u'成交额二'][sidx:eidx]
 
                if u'3日均二' in quotes:  self._average3_2= quotes[u'3日均二'][sidx:eidx]
                if u'5日均二' in quotes:  self._average5_2= quotes[u'5日均二'][sidx:eidx]
                if u'10日均二' in quotes: self._average10_2= quotes[u'10日均二'][sidx:eidx]
                if u'30日均二' in quotes: self._average30_2= quotes[u'30日均二'][sidx:eidx]
                if u'60日均二' in quotes: self._average60_2= quotes[u'60日均二'][sidx:eidx]
 
        self._length= len(self._dates)  # XXX: 由派生类设定
         
        #   衍生数据
        #==============================================================================================================
        self._xindex= numpy.arange(self._length)    # X 轴上的 index,一个辅助数据
 
        self._zipoc= zip(self._open, self._close)
        self._up=   numpy.array( [ True if po < pc and po is not None else False for po, pc in self._zipoc] )        # 标示出该天股价日内上涨的一个序列
        self._down= numpy.array( [ True if po > pc and po is not None else False for po, pc in self._zipoc] )        # 标示出该天股价日内下跌的一个序列
        self._side= numpy.array( [ True if po == pc and po is not None else False for po, pc in self._zipoc] )      # 标示出该天股价日内走平的一个序列
         
        if u'开盘二' in quotes:
            self._zipoc_2= zip(self._open_2, self._close_2)
            self._up_2=   numpy.array( [ True if po < pc and po is not None else False for po, pc in self._zipoc_2] )        # 标示出该天股价日内上涨的一个序列
            self._down_2= numpy.array( [ True if po > pc and po is not None else False for po, pc in self._zipoc_2] )        # 标示出该天股价日内下跌的一个序列
            self._side_2= numpy.array( [ True if po == pc and po is not None else False for po, pc in self._zipoc_2] )      # 标示出该天股价日内走平的一个序列
         
         
        self._Axes= None
        self._AxisX= None
        self._AxisY= None
 
        self._xsize= 0.0
        self._ysize= 0.0
 
        self._yhighlim= 0   # Y 轴最大坐标
        self._ylowlim= 0    # Y 轴最小坐标
 
        if u'开盘二' in self._pdata[u'行情']:
            self._Axes_2= None  # 如果有第二个行情数据,就建立另一个 Axes 对象
            self._AxisX_2= None
            self._AxisY_2= None
 
            self._yhighlim_2= 0 # Y 轴最大坐标
            self._ylowlim_2= 0  # Y 轴最小坐标
 
        self._compute_size()
        self._ytickset= self._compute_ytickset()    # 需放在前一句后面
 
 
 
 
 
    def _compute_size(self):
        '''
        根据绘图数据 pdata 计算出本子图的尺寸,修改数据成员
        '''
        quotes= self._pdata[u'行情']
 
        popen= self._open[0]    # int 类型
 
        phigh= max( [ph for ph in self._high if ph is not None] )   # 最高价
        plow= min( [pl for pl in self._low if pl is not None] )     # 最低价
 
        # Y 轴范围
        if self._name == 'pricefs':
            yhighlim= phigh * 1.2   # K线子图 Y 轴最大坐标
            ylowlim=  plow / 1.2    # K线子图 Y 轴最小坐标
        else:
            yhighlim= phigh * 1.1   # K线子图 Y 轴最大坐标
            ylowlim=  plow / 1.1    # K线子图 Y 轴最小坐标
 
        self._yhighlim= yhighlim
        self._ylowlim= ylowlim
 
        if u'开盘二' in quotes:
            popen_2= self._open_2[0]    # 同上
            phigh_2= max( [ph for ph in self._high_2 if ph is not None] )   # 第二个行情的最高价
            phigh= max(phigh, int(phigh_2 * popen / float(popen_2)))    # 以第一个行情为基准修正出的总最高价
            plow_2= min( [pl for pl in self._low_2 if pl is not None] ) # 最低价
            plow= min(plow, int(plow_2 * popen / float(popen_2)))       # 以第一个行情为基准修正出的总最低价
 
            if self._name == 'pricefs':
                yhighlim= phigh * 1.2   # K线子图 Y 轴最大坐标
                ylowlim=  plow / 1.2    # K线子图 Y 轴最小坐标
            else:
                yhighlim= phigh * 1.1   # K线子图 Y 轴最大坐标
                ylowlim=  plow / 1.1    # K线子图 Y 轴最小坐标
 
            ylowlim_2=  ylowlim * popen_2 / float(popen)
            yhighlim_2= yhighlim * popen_2 / float(popen)
 
            self._yhighlim= yhighlim
            self._ylowlim= ylowlim
 
            self._yhighlim_2= yhighlim_2
            self._ylowlim_2= ylowlim_2
 
        # XXX: 价格在 Y 轴上的 “份数”。注意,虽然最高与最低价是以第一个行情为基准修正出来的,但其中包含的倍数因子对结果无影响,即:
        #   log(base, num1) - log(base, num2) == 
        #   log(base, num1/num2) ==
        #   log(base, k*num1/k*num2) ==
        #   log(base, k*num1) - log(base, k*num2)
        # ,这是对数运算的性质。
        xmargin= self._xparams['xmargin']
        self._xsize= (self._length + xmargin*2) * self._shrink          # int, 所有数据的长度,就是天数
        self._ysize= (math.log(yhighlim, self._expbase) - math.log(ylowlim, self._expbase)) * self._shrink  # float
 
 
 
 
 
    def get_size(self):
        return (self._xsize, self._ysize)
 
 
 
 
 
    def get_ylimits(self):
        return (self._yhighlim, self._ylowlim)
 
 
 
 
 
    def build_axes(self, figobj, rect):
        '''
        初始化 self._Axes 对象
        '''
        #   添加 Axes 对象
        #==================================================================================================================================================
        if self._name == 'price' and 'torate' in self._parent._subplots:
            sharex= self._parent._subplots['torate'].get_axes()
            axes= figobj.add_axes(rect, axis_bgcolor='black', sharex=sharex)
        elif self._name == 'pricefs' and 'toratefs' in self._parent._subplots:
            sharex= self._parent._subplots['toratefs'].get_axes()
            axes= figobj.add_axes(rect, axis_bgcolor='black', sharex=sharex)
        else:
            axes= figobj.add_axes(rect, axis_bgcolor='black')
 
        axes.set_axisbelow(True)    # 网格线放在底层
        #   axes.set_zorder(1)      # XXX: 不顶用
        #   axes.patch.set_visible(False)   # hide the 'canvas'
        axes.set_yscale('log', basey=self._expbase)     # 使用对数坐标
         
        #   改变坐标线的颜色
        #==================================================================================================================================================
        for child in axes.get_children():
            if isinstance(child, matplotlib.spines.Spine):
                child.set_color(__color_gold__)
 
        #   得到 X 轴 和 Y 轴 的两个 Axis 对象
        #==================================================================================================================================================
        xaxis= axes.get_xaxis()
        yaxis= axes.get_yaxis()
 
        #   设置两个坐标轴上的网格线
        #==================================================================================================================================================
        xaxis.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
        xaxis.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
        if self._name == 'pricefs': # 如果是小图,就不设辅助网格线
            yaxis.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.1)
        else:
            yaxis.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
            yaxis.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
        yaxis.set_label_position('left')
 
        self._Axes= axes
        self._AxisX= xaxis
        self._AxisY= yaxis
 
 
 
        if u'开盘二' in self._pdata[u'行情']:
            #   添加 Axes 对象。注意,设置 axes_2 而不是 axes 的网格线,从而不会跑到 axes 边框上边的做法不顶用。
            #==================================================================================================================================================
            axes_2= axes.twinx()    # twinx is problematic, no use no more.
 
            # XXX: 下面这三行把第一个 axes 放在上面,这样不会被第二个 axes 的图形遮盖。用 zorder 不顶用。
            axes.figure.axes[-2:]= [axes_2, axes]   # XXX: 
            axes.set_frame_on(False)    # 如果不做此设定,axes_2 的内容会看不见
            axes_2.set_frame_on(True)
 
            axes_2.set_axis_bgcolor('black')
            axes_2.set_axisbelow(True)  # 网格线放在底层
            axes_2.set_yscale('log', basey=self._expbase)       # 使用对数坐标
         
            #   改变坐标线的颜色
            #==================================================================================================================================================
            for child in axes_2.get_children():
                if isinstance(child, matplotlib.spines.Spine):
                    child.set_color(__color_gold__)
 
            #   得到 X 轴 和 Y 轴 的两个 Axis 对象
            #==================================================================================================================================================
            xaxis_2= axes_2.get_xaxis()
            yaxis_2= axes_2.get_yaxis()
 
            #   设置两个坐标轴上的网格线
            #==================================================================================================================================================
            #   xaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
            #   xaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
            #   if self._name == 'pricefs': # 如果是小图,就不设辅助网格线
            #       yaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.1)
            #   else:
            #       yaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
            #       yaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
            yaxis_2.set_label_position('right')
 
            self._Axes_2= axes_2
            self._AxisX_2= xaxis_2
            self._AxisY_2= yaxis_2
 
 
 
 
 
 
 
    def set_xticks(self):
 
        xMajorLocator= self._xparams['xMajorLocator']
        xMinorLocator= self._xparams['xMinorLocator']
 
        axes= self._Axes
        xaxis= self._AxisX
 
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        xmargin= self._xparams['xmargin']
        axes.set_xlim(-xmargin, self._length + xmargin)
 
        #   先设置 label 位置,再将 X 轴上的坐标设为不可见。因为与 成交量子图 共用 X 轴
        #==================================================================================================================================================
 
        # 设定 X 轴的 Locator 和 Formatter
        xaxis.set_major_locator(xMajorLocator)
        #   xaxis.set_major_formatter(xMajorFormatter)
 
        xaxis.set_minor_locator(xMinorLocator)
        #   xaxis.set_minor_formatter(xMinorFormatter)
 
        # 将 X 轴上的坐标设为不可见。
        for mal in axes.get_xticklabels(minor=False):
            mal.set_visible(False)
 
        for mil in axes.get_xticklabels(minor=True):
            mil.set_visible(False)
 
 
 
 
 
    def set_xticks_2(self):
        quotes= self._pdata[u'行情']
 
        axes= self._Axes_2
        xaxis= self._AxisX_2
 
        xMajorLocator= self._xparams['xMajorLocator']
        xMinorLocator= self._xparams['xMinorLocator']
         
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        xmargin= self._xparams['xmargin']
        axes.set_xlim(-xmargin, self._length + xmargin)
 
        #   先设置 label 位置,再将 X 轴上的坐标设为不可见。因为与 成交量子图 共用 X 轴
        #==================================================================================================================================================
 
        # 设定 X 轴的 Locator 和 Formatter
        xaxis.set_major_locator(xMajorLocator)
        #   xaxis.set_major_formatter(xMajorFormatter)
 
        xaxis.set_minor_locator(xMinorLocator)
        #   xaxis.set_minor_formatter(xMinorFormatter)
 
        # 将 X 轴上的坐标设为不可见。
        for mal in axes.get_xticklabels(minor=False):
            mal.set_visible(False)
 
        for mil in axes.get_xticklabels(minor=True):
            mil.set_visible(False)
 
 
 
 
 
    def _compute_ytickset(self):
        '''
        计算 Y 轴坐标点的位置,包括第二个行情
        '''
        quotes= self._pdata[u'行情']
        expbase= self._expbase
 
        ytickset= {}
 
        yhighlim= self._yhighlim
        ylowlim= self._ylowlim
 
        if u'开盘二' in quotes:
            yhighlim_2= self._yhighlim_2
            ylowlim_2= self._ylowlim_2
 
 
 
        if self._name == 'price' and 'pricefs' in self._parent._subplots:
            tsetfs= self._parent._subplots['pricefs'].get_ytickset()
 
            majors= tsetfs['major']
            while majors[-1] < yhighlim: majors.append(majors[-1] * expbase)
            while majors[0] > ylowlim: majors.insert(0, majors[0] / expbase)
 
            minors= tsetfs['minor']
            while minors[-1] < yhighlim: minors.append(minors[-1] * expbase)
            while minors[0] > ylowlim: minors.insert(0, minors[0] / expbase)
 
            ytickset['major']= [loc for loc in majors if loc > ylowlim and loc < yhighlim]
            ytickset['minor']= [loc for loc in minors if loc > ylowlim and loc < yhighlim]
 
        else:
 
            #   主要坐标点
            #----------------------------------------------------------------------------
            majors= [ylowlim]
            while majors[-1] < yhighlim: majors.append(majors[-1] * 1.1)
 
            #   辅助坐标点
            #----------------------------------------------------------------------------
            minors= [ylowlim * 1.1**0.5]
            while minors[-1] < yhighlim: minors.append(minors[-1] * 1.1)
 
            ytickset['major']= [loc for loc in majors if loc > ylowlim and loc < yhighlim]    # 注意,第一项(ylowlim)被排除掉了
            ytickset['minor']= [loc for loc in minors if loc > ylowlim and loc < yhighlim]
 
 
 
        if u'开盘二' in quotes:
            popen= self._open[0]        # int 类型
            popen_2= self._open_2[0]    # 同上
 
            ytickset['major_2']= [loc * popen_2 / popen for loc in ytickset['major']]
            ytickset['minor_2']= [loc * popen_2 / popen for loc in ytickset['minor']]
 
 
 
        return ytickset
 
 
 
 
 
    def get_ytickset(self):
        return self._ytickset
 
 
 
 
 
    def set_yticks(self):
        '''
        设置第一只行情的 Y 轴坐标,包括坐标值在图中间的显示
        '''
 
        axes= self._Axes
        ylowlim= self._ylowlim
        yhighlim= self._yhighlim
        yaxis= self._AxisY
 
        majorticks= self._ytickset['major']
        minorticks= self._ytickset['minor']
 
        #   设定 Y 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_ylim(ylowlim, yhighlim)
 
 
 
        #   设定 Y 轴上的坐标
        #==================================================================================================================================================
         
        #   主要坐标点
        #----------------------------------------------------------------------------
 
        yMajorLocator= FixedLocator(numpy.array(majorticks))
 
        # 确定 Y 轴的 MajorFormatter
        def y_major_formatter(num, pos=None):
            return str(round(num/1000.0, 2))
         
        yMajorFormatter= FuncFormatter(y_major_formatter)
 
        # 设定 X 轴的 Locator 和 Formatter
        yaxis.set_major_locator(yMajorLocator)
        yaxis.set_major_formatter(yMajorFormatter)
 
        # 设定 Y 轴主要坐标点与辅助坐标点的样式
        fsize= 4 if self._name == 'pricefs' else 6
         
        for mal in axes.get_yticklabels(minor=False):
            mal.set_fontsize(fsize)
 
 
 
        #   辅助坐标点
        #----------------------------------------------------------------------------
 
        yMinorLocator= FixedLocator(numpy.array(minorticks))
 
        # 确定 Y 轴的 MinorFormatter
        def y_minor_formatter(num, pos=None):
            return str(round(num/1000.0, 2))
             
        yMinorFormatter= FuncFormatter(y_minor_formatter)
         
        # 设定 X 轴的 Locator 和 Formatter
        yaxis.set_minor_locator(yMinorLocator)
        yaxis.set_minor_formatter(yMinorFormatter)
 
        # 设定 Y 轴辅助坐标点的样式
        if self._name == 'pricefs':
            for mil in axes.get_yticklabels(minor=True):
                mil.set_visible(False)
        else:
            for mil in axes.get_yticklabels(minor=True):
                mil.set_fontsize(5)
                mil.set_color('blue')
 
 
 
 
 
    def set_yticks_2(self):
        '''
        子图右侧的 Y 轴坐标
        '''
 
        axes= self._Axes_2
        yaxis= self._AxisY_2
         
        yhighlim_2= self._yhighlim_2
        ylowlim_2= self._ylowlim_2
 
        majorticks_2= self._ytickset['major_2']
        minorticks_2= self._ytickset['minor_2']
 
        #   设定 Y 轴坐标的范围 
        #==================================================================================================================================================
 
        axes.set_ylim(ylowlim_2, yhighlim_2)
 
        #   设定 Y 轴上的坐标
        #==================================================================================================================================================
 
        #   主要坐标点
        #----------------------------------------------------------------------------
 
        yMajorLocator= FixedLocator(numpy.array(majorticks_2))
 
        # 确定 Y 轴的 MajorFormatter
        def y_major_formatter(num, pos=None):
            return str(round(num/1000.0, 2))
         
        yMajorFormatter= FuncFormatter(y_major_formatter)
 
        # 设定 X 轴的 Locator 和 Formatter
        yaxis.set_major_locator(yMajorLocator)
        yaxis.set_major_formatter(yMajorFormatter)
 
        # 设定 Y 轴主要坐标点与辅助坐标点的样式
        fsize= 4 if self._name == 'pricefs' else 6
 
        for mal in axes.get_yticklabels(minor=False):
            mal.set_fontsize(fsize)
 
 
 
        #   辅助坐标点
        #----------------------------------------------------------------------------
 
        yMinorLocator= FixedLocator(numpy.array(minorticks_2))      # XXX minor ticks 已经在上面一并设置,这里不需要了。
 
        # 确定 Y 轴的 MinorFormatter
        def y_minor_formatter(num, pos=None):
            return str(round(num/1000.0, 2))
             
        yMinorFormatter= FuncFormatter(y_minor_formatter)
         
        # 设定 X 轴的 Locator 和 Formatter
        yaxis.set_minor_locator(yMinorLocator)
        yaxis.set_minor_formatter(yMinorFormatter)
        # 设定 Y 轴主要坐标点与辅助坐标点的样式
        if self._name == 'pricefs':
            for mil in axes.get_yticklabels(minor=True):
                mil.set_visible(False)
 
        else:
            for mil in axes.get_yticklabels(minor=True):
                mil.set_fontsize(5)
                mil.set_color('blue')
 
 
 
 
 
    def plot(self):
        '''
        由派生类自己定义
        '''
        pass
 
 
 
 
 
    def plot_candlestick(self):
        '''
        绘制 K 线
        '''
        axes= self._Axes
 
        xindex= self._xindex
 
        up=   self._up
        down= self._down
        side= self._side
 
        #   绘制 K 线部分
        #==================================================================================================================================================
         
        #   对开收盘价进行视觉修正
        for idx, poc in enumerate(self._zipoc):
            if poc[0] == poc[1] and None not in poc:
                variant= int(round((poc[1]+1000)/2000.0, 0))
                self._open[idx]= poc[0] - variant       # 稍微偏离一点,使得在图线上不致于完全看不到
                self._close[idx]= poc[1] + variant
 
        rarray_open= numpy.array(self._open)
        rarray_close= numpy.array(self._close)
        rarray_high= numpy.array(self._high)
        rarray_low= numpy.array(self._low)
 
        # XXX: 如果 up, down, side 里有一个全部为 False 组成,那么 vlines() 会报错。
        # XXX: 可以使用 alpha 参数调节透明度
        if True in up:
            axes.vlines(xindex[up], rarray_low[up], rarray_high[up], edgecolor='red', linewidth=0.6, label='_nolegend_', alpha=0.5)
            axes.vlines(xindex[up], rarray_open[up], rarray_close[up], edgecolor='red', linewidth=3.0, label='_nolegend_', alpha=0.5)
 
        if True in down:
            axes.vlines(xindex[down], rarray_low[down], rarray_high[down], edgecolor='green', linewidth=0.6, label='_nolegend_', alpha=0.5)
            axes.vlines(xindex[down], rarray_open[down], rarray_close[down], edgecolor='green', linewidth=3.0, label='_nolegend_', alpha=0.5)
 
        if True in side:
            axes.vlines(xindex[side], rarray_low[side], rarray_high[side], edgecolor='0.7', linewidth=0.6, label='_nolegend_', alpha=0.5)
            axes.vlines(xindex[side], rarray_open[side], rarray_close[side], edgecolor='0.7', linewidth=3.0, label='_nolegend_', alpha=0.5)
 
 
 
 
 
    def plot_simplified(self):
        '''
        绘制简化行情
        '''
        xindex= self._xindex
        axes= self._Axes
 
        rarray_simple= numpy.array(self._simple)
        axes.plot(xindex, rarray_simple, 'o-', color='white', linewidth=0.3, label='simple', \
            markersize=0.3, markeredgecolor='white', markeredgewidth=0.1, alpha=0.3)    # 简化行情
 
 
 
 
 
    def plot_average(self):
        '''
        绘制均线
        '''
        #   绘制均线部分
        #==================================================================================================================================================
        quotes= self._pdata[u'行情']
 
        xindex= self._xindex
        axes= self._Axes
 
        if self._name == 'pricefs':
            widthw= 0.1
            widthn= 0.07
            mksize= 0.07
            mkwidth= 0.1
            alpha= 1.0
        else:
            widthw= 0.2
            widthn= 0.1
            mksize= 0.3
            mkwidth= 0.1
            alpha= 1.0
 
        if u'3日均' in quotes:
            rarray_3dayave= numpy.array(self._average3)
            axes.plot(xindex, rarray_3dayave, 'o-', color='white', linewidth=widthw, label='avg_3', \
                markersize=mksize, markeredgecolor='white', markeredgewidth=mkwidth, alpha=alpha)   # 3日均线
 
        if u'5日均' in quotes:
            rarray_5dayave= numpy.array(self._average5)
            axes.plot(xindex, rarray_5dayave, 'o-', color='white', linewidth=widthn, label='avg_5', \
                markersize=mksize, markeredgecolor='white', markeredgewidth=mkwidth, alpha=alpha)   # 5日均线
         
        if u'10日均' in quotes:
            rarray_10dayave= numpy.array(self._average10)
            axes.plot(xindex, rarray_10dayave, 'o-', color='yellow', linewidth=widthn, label='avg_10', \
                markersize=mksize, markeredgecolor='yellow', markeredgewidth=mkwidth, alpha=alpha)  # 10日均线
         
        if u'30日均' in quotes:
            rarray_30dayave= numpy.array(self._average30)
            axes.plot(xindex, rarray_30dayave, 'o-', color='cyan', linewidth=widthn, label='avg_30', \
                markersize=mksize, markeredgecolor='cyan', markeredgewidth=mkwidth, alpha=alpha)    # 30日均线
 
        if u'60日均' in quotes:
            rarray_60dayave= numpy.array(self._average60)
            axes.plot(xindex, rarray_60dayave, 'o-', color='magenta', linewidth=widthn, label='avg_60', \
                markersize=mksize, markeredgecolor='magenta', markeredgewidth=mkwidth, alpha=alpha) # 60日均线
 
 
 
    def plot_adjustnotes(self):
        '''
        绘制复权提示
        '''
        quotes= self._pdata[u'行情']
 
        firstday= self._dates[0]
        lastday= self._dates[-1]
        ylowlim= self._ylowlim
        yhighlim= self._yhighlim
        axes= self._Axes
 
        #   使用 annotate() 进行标注。不用了,留作纪念。
        #===========================================================================================================================
        #   adjdict= dict(quotes[u'相对复权'])  # key 是 date string,value 是相对复权因子(float 类型)
        #   el= Ellipse((2, -1), 0.5, 0.5)
        #   for idx, dstr in enumerate(self._dates):
        #       if dstr in adjdict:
        #           axes.plot([idx, idx], [ylowlim, yhighlim], '-', color='purple', linewidth=0.1)
        #           axes.annotate(u'复权\n' + str(adjdict[dstr]), 
        #               fontproperties=__font_properties__,
        #               xy=(idx, yhighlim/1.1),  xycoords='data', 
        #               xytext=(10, 5), textcoords='offset points', size=5, verticalalignment="center",
        #               bbox=dict(boxstyle="round", facecolor='white', edgecolor='purple'),
        #               arrowprops=dict(arrowstyle="wedge,tail_width=1.",
        #                       facecolor='white', edgecolor='purple',
        #                       patchA=None,
        #                       patchB=el,
        #                       relpos=(0.2, 0.8),
        #                       connectionstyle="arc3,rad=-0.1"),
        #               alpha=0.5
        #               )
 
        adjrecs= [rec for rec in quotes[u'相对复权'] if rec[0] >= firstday and rec[0] <= lastday]
 
        if self._name == 'pricefs':
            fsize= 3.0
            ycoord= yhighlim/1.3
            alpha= 1.0
        else:
            fsize= 5.0
            ycoord= yhighlim/1.12
            alpha= 1.0
 
        for dstr, afac in adjrecs:
            idx= self._dates.index(dstr)
            axes.plot([idx, idx], [ylowlim, yhighlim], '-', color='purple', linewidth=0.1)
            label= axes.text( \
                idx, ycoord, \
                u'复权 ' + str(afac) + u'\n' + dstr, \
                fontproperties=__font_properties__, \
                horizontalalignment='left', \
                verticalalignment='top', \
                color='purple', \
                alpha=alpha
            )
            label.set_fontsize(fsize)
 
 
 
 
 
    def plot_capchangenotes(self):
        '''
        绘制流通股本变更提示
        注意两个问题:
            1. 流通股本变更提示中的日期可能不是交易日期
            2. 流通股本变更提示涵盖个股的所有历史,有些内容可能在绘图目标区间以外
        '''
        pdata= self._pdata
        axes= self._Axes
        ylowlim= self._ylowlim
        yhighlim= self._yhighlim
 
        firstday= self._dates[0]
        lastday= self._dates[-1]
 
        # 把目标区间之外的记录滤掉
        changerecs= [rec for rec in pdata[u'公司信息'][u'流通股变更'] if rec[u'变更日期'] >= firstday and rec[u'变更日期'] <= lastday]
 
        #   使用 annotate() 进行标注。不用了,留作纪念。
        #===========================================================================================================================
        #   el= Ellipse((2, -1), 0.5, 0.5)
        #   for datestr, chrate in changerecs:
        #       dstr= [ds for ds in self._dates if ds >= datestr][0]
        #       idx= self._dates.index(dstr)
        #       axes.plot([idx, idx], [ylowlim, yhighlim], '-', color='yellow', linewidth=0.1)
        #       axes.annotate(u'流通股\n' + str(chrate),
        #           fontproperties=__font_properties__,
        #           xy=(idx, yhighlim/1.1),  xycoords='data', 
        #           xytext=(10, 5), textcoords='offset points', size=5, verticalalignment="center",
        #           bbox=dict(boxstyle="round", facecolor='white', edgecolor='yellow'),
        #           arrowprops=dict(arrowstyle="wedge,tail_width=1.",
        #                   facecolor='white', edgecolor='yellow',
        #                   patchA=None,
        #                   patchB=el,
        #                   relpos=(0.2, 0.8),
        #                   connectionstyle="arc3,rad=-0.1"),
        #           alpha=0.5
        #           )
 
        if self._name == 'pricefs':
            fsize= 3.0
            ycoord= yhighlim/1.1
            alpha= 1.0
        else:
            fsize= 5.0
            ycoord= yhighlim/1.05
            alpha= 0.8
 
        for record in changerecs:
            datestr= record[u'变更日期']
            chrate= record[u'变更比']
            dstr= [ds for ds in self._dates if ds >= datestr][0]
            idx= self._dates.index(dstr)
            axes.plot([idx, idx], [ylowlim, yhighlim], '-', color='yellow', linewidth=0.1)
            label= axes.text( \
                idx, ycoord, \
                u'流通股 ' + str(chrate) + u'\n' + datestr, \
                fontproperties=__font_properties__, \
                horizontalalignment='left', \
                verticalalignment='top', \
                color='yellow', \
                alpha=alpha
            )
            label.set_fontsize(fsize)
 
 
 
 
 
    def plot_candlestick_2(self):
        '''
        绘制第二条 K 线
        '''
        xindex= self._xindex
 
        axes= self._Axes_2
 
        up=   self._up_2
        down= self._down_2
        side= self._side_2
         
        #   对开收盘价进行视觉修正
        for idx, poc in enumerate( self._zipoc_2 ):
            if poc[0] == poc[1] and None not in poc:
                self._open_2[idx]= poc[0] - 5       # 稍微偏离一点,使得在图线上不致于完全看不到
                self._close_2[idx]= poc[1] + 5     
 
        rarray_open= numpy.array(self._open_2)
        rarray_close= numpy.array(self._close_2)
        rarray_high= numpy.array(self._high_2)
        rarray_low= numpy.array(self._low_2)
 
        # XXX: 如果 up, down, side 里有一个全部为 False 组成,那么 vlines() 会报错。
        # XXX: 可以使用 alpha 参数调节透明度
        if True in up:
            axes.vlines(xindex[up], rarray_low[up], rarray_high[up], edgecolor='0.7', linewidth=0.6, label='_nolegend_', alpha=0.5)
            axes.vlines(xindex[up], rarray_open[up], rarray_close[up], edgecolor='0.7', linewidth=3.0, label='_nolegend_', alpha=0.5)
 
        if True in down:
            axes.vlines(xindex[down], rarray_low[down], rarray_high[down], edgecolor='0.3', linewidth=0.6, label='_nolegend_', alpha=0.5)
            axes.vlines(xindex[down], rarray_open[down], rarray_close[down], edgecolor='0.3', linewidth=3.0, label='_nolegend_', alpha=0.5)
 
        if True in side:
            axes.vlines(xindex[side], rarray_low[side], rarray_high[side], edgecolor='1.0', linewidth=0.6, label='_nolegend_', alpha=1.0)
            axes.vlines(xindex[side], rarray_open[side], rarray_close[side], edgecolor='1.0', linewidth=3.0, label='_nolegend_', alpha=1.0)
 
 
 
 
 
    def plot_capitalinfo(self):
        '''
        绘制行情首日和尾日的股本信息
        '''
        def find_biggestblank(didx):
            '''
            找出 X 轴某个位置图中最大的空隙
            '''
            pstart= self._open[0]
            ptarget= self._open[didx]
             
            compseq= [yhighlim, ptarget, ylowlim]
 
            if u'开盘二' in quotes:
                pstart_2= self._open_2[0]
                ptarget_2= self._open_2[didx]
                padjust= int(ptarget_2 * pstart / float(pstart_2))
                compseq.append(padjust)
 
            compseq.sort(reverse=True)  # 图中的三个或四个关键点,高到底排序
 
            diff, hi, low= max([(math.log(compseq[idx]/float(compseq[idx+1]), expbase), compseq[idx], compseq[idx+1]) for idx in range(len(compseq)-1)])
 
            # XXX: for debugging
            #   print(compseq)
            #   print([diff, hi, low])
 
            return (hi*low)**0.5    # 相乘再开平方,在 log 坐标系里看起来就是在中间位置。
 
        def repr_int(intnum):
            '''
            123456789 --> '1,2345,6789'
            '''
            if type(intnum) not in (int, long): return str(intnum)
             
            if intnum == 0: return '0'
             
            if intnum < 0:
                intnum= -intnum
                isminus= True
            else:
                isminus= False
 
            intstr= str(intnum)
            intstr= '0'*((4-len(intstr)%4)%4) + intstr
            intlist= [intstr[i:i+4] for i in range(0, len(intstr), 4)]
             
            intlist[0]= intlist[0].lstrip('0')
 
            return ('-' + ','.join(intlist)) if isminus else ','.join(intlist)
 
 
 
        pdata= self._pdata
        quotes= pdata[u'行情']
 
        ylowlim= self._ylowlim
        yhighlim= self._yhighlim
        length= self._length
        expbase= self._expbase
        capinfo= pdata[u'公司信息'][u'股本变更记录']
        axes= self._Axes
        firstday, lastday= self._dates[0], self._dates[-1]
 
        fsize= 5.0 if self._name == 'price' else 3.0
 
        #   首日总股本与流通股信息
        #====================================================================================================================================
        chunk= [rec for rec in capinfo if rec[u'变更日期'] <= firstday]
         
        if chunk:
            allshares= repr_int(chunk[-1][u'总股本'])
            circulating= repr_int(chunk[-1][u'流通股'])
        else:
            allshares= 'None'
            circulating= 'None'
 
        infostr= u'总股本: ' + allshares + '\n' + u'流通股: ' + circulating
 
        label= axes.text(0, find_biggestblank(didx=0), infostr, fontproperties=__font_properties__, color='0.7')
        label.set_fontsize(fsize)
        #   label.set_zorder(0)     # XXX: 放在底层
 
        #   尾日总股本与流通股信息
        #====================================================================================================================================
        chunk= [rec for rec in capinfo if rec[u'变更日期'] <= lastday]
        if chunk:
            allshares= repr_int(chunk[-1][u'总股本'])
            circulating= repr_int(chunk[-1][u'流通股'])
        else:
            allshares= 'None'
            circulating= 'None'
 
        infostr= u'总股本: ' + allshares + '\n' + u'流通股: ' + circulating
 
        label= axes.text(length-1, find_biggestblank(didx=length-1), infostr, fontproperties=__font_properties__, horizontalalignment='right', color='0.7')
        label.set_fontsize(fsize)
        #   label.set_zorder(0)     # XXX: 放在底层
 
 
 
 
 
    def plot_usernotes(self):
        '''
 
        '''
        pdata= self._pdata
        quotes= pdata[u'行情']
        sidx= self._pdata[u'任务描述'][u'起始偏移']
        eidx= self._pdata[u'任务描述'][u'结束偏移']
 
        axes= self._Axes
        usernotes= pdata[u'用户标记']
 
        alldates= quotes[u'日期'][sidx:eidx]
        lowprices= quotes[u'最低'][sidx:eidx]
        expbase= self._expbase
 
        # 绘制短线买点标记
        for note in usernotes:
            if note[u'类型'] == u'筛选结果':
                dstr= note[u'日期']
                 
                # 日期不在绘图区间范围内,忽略
                if dstr not in alldates:
                    continue
                 
                # 决定箭头的颜色
                result= note[u'结果']
                color= 'magenta' if result == u'上涨' else 'cyan' if result == u'下跌' else 'white'
 
                # 箭头的起始位置
                idx= alldates.index(dstr)
                xpos= idx
                ypos= lowprices[idx] / expbase
 
                # 箭头的长度
                dx= 0.0
                dy= ypos * (expbase-1) * 0.9
 
                # 头端的长度
                head_length= dy * 0.2
 
                # 绘制箭头
                arrow_params={'length_includes_head':True, 'shape':'full', 'head_starts_at_zero':False}
                axes.arrow(xpos, ypos, dx, dy, facecolor=color, edgecolor=color, linewidth=0.7, head_width=0.6, head_length=head_length, **arrow_params)
 
 
 
 
 
 
    def plot_vlines(self):
 
        xindex= self._xindex
 
        up=   self._up
        down= self._down
        side= self._side
 
        axes= self._Axes
 
        lwidth= 2.4 * self._shrink
 
        #   绘制 K 线部分
        #==================================================================================================================================================
        rarray_high= numpy.array(self._high)
        rarray_low= numpy.array(self._low)
 
        # XXX: 如果 up, down, side 里有一个全部为 False 组成,那么 vlines() 会报错。
        # XXX: 可以使用 alpha 参数调节透明度
        if True in up:
            axes.vlines(xindex[up], rarray_low[up], rarray_high[up], edgecolor='red', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
        if True in down:
            axes.vlines(xindex[down], rarray_low[down], rarray_high[down], edgecolor='green', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
        if True in side:
            axes.vlines(xindex[side], rarray_low[side], rarray_high[side], edgecolor='0.7', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
 
 
 
 
    def plot_vlines_2(self):
        xindex= self._xindex
 
        axes= self._Axes_2
 
        up=   self._up_2
        down= self._down_2
        side= self._side_2
 
        lwidth= 2.4 * self._shrink
 
        rarray_high= numpy.array(self._high_2)
        rarray_low= numpy.array(self._low_2)
 
        # XXX: 如果 up, down, side 里有一个全部为 False 组成,那么 vlines() 会报错。
        # XXX: 可以使用 alpha 参数调节透明度
        if True in up:
            axes.vlines(xindex[up], rarray_low[up], rarray_high[up], edgecolor='0.7', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
        if True in down:
            axes.vlines(xindex[down], rarray_low[down], rarray_high[down], edgecolor='0.3', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
        if True in side:
            axes.vlines(xindex[side], rarray_low[side], rarray_high[side], edgecolor='1.0', linewidth=lwidth, label='_nolegend_', alpha=1.0)
 
 
 
 
 
    def plot_datenotes(self):
        '''
        内部的日期注释,由派生类定义
        '''
        pass
 
 
 
 
 
    def plot_pricenotes(self):
        '''
        内部的价格注释,由派生类定义
        '''
        pass
 
 
 
 
 
 
 
 
 
 
 
class SubPlot_PriceFullSpan(SubPlot_PriceBase):
    '''
 
    '''
 
    def plot(self):
        '''
        绘图
        '''
        pdata= self._pdata
        #   if u'简化' in pdata:
        #       self.plot_simplified()
        #   else:
        #       self.plot_candlestick()
 
        self.plot_vlines()
        self.plot_average()
 
        if u'相对复权' in pdata[u'行情']:
            self.plot_adjustnotes()
 
        if u'流通股变更' in pdata[u'公司信息']:
            self.plot_capchangenotes()
 
        if u'股本变更记录' in pdata[u'公司信息']:
            self.plot_capitalinfo()
 
        self.set_xticks()
        self.set_yticks()
 
        if u'开盘二' in pdata[u'行情']:
            self.plot_vlines_2()
            self.set_xticks_2()
            self.set_yticks_2()
 
        self.plot_datenotes()
 
        if 'price' in self._parent._subplots:
            self.plot_windowspan()
 
 
 
    def plot_datenotes(self):
        '''
        日期在图中间的显示
        '''
        ylowlim= self._ylowlim
 
        axes= self._Axes
 
        sdindex= self._xparams['sdindex']
        mdindex= self._xparams['mdindex']
 
 
 
        # 每季度第一个交易日
        for ix in sdindex:
            newlab= axes.text(ix - (1/self._shrink), ylowlim*1.03, self._dates[ix])
            newlab.set_font_properties(__font_properties__)
            newlab.set_color('0.3')
            newlab.set_fontsize(4)
            newlab.set_rotation('45')
            #   newlab.set_rotation('vertical')
            #   newlab.set_horizontalalignment('left')
            #   newlab.set_verticalalignment('bottom')
            #   newlab.set_verticalalignment('center')
            newlab.set_zorder(0)        # XXX: 放在底层
 
 
        # 每月第一个交易日
        for ix in mdindex:
            newlab= axes.text(ix - (0.8/self._shrink), ylowlim * 1.03, self._dates[ix])
            newlab.set_font_properties(__font_properties__)
            newlab.set_color('0.3')
            newlab.set_fontsize(3)
            newlab.set_rotation('45')
            #   newlab.set_rotation('vertical')
            #   newlab.set_horizontalalignment('left')
            #   newlab.set_verticalalignment('top') # 不行
            #   newlab.set_verticalalignment('center')
            #   newlab.set_verticalalignment('bottom')
            newlab.set_zorder(0)        # XXX: 放在底层
 
 
 
    def plot_windowspan(self):
 
        axes= self._Axes
        jobstat= self._pdata[u'任务描述']
        sindex, eindex= jobstat[u'起始偏移'], jobstat[u'结束偏移']
        hibdry, lobdry= self._parent._subplots['price'].get_ylimits()
         
        xcoord= sindex - 1
        width= eindex - sindex + 1
        ycoord= lobdry
        height= hibdry - lobdry
        window= matplotlib.patches.Rectangle((xcoord, ycoord), width, height, fill=False, edgecolor=__color_lightblue__, linewidth=0.3, alpha=0.7)
        window.set_zorder(-1)   # 放在底层
        axes.add_patch(window)
 
 
 
 
 
 
 
class SubPlot_Price(SubPlot_PriceBase):
    '''
 
    '''
 
    def plot(self):
        '''
        绘图
        '''
        pdata= self._pdata
        #   if u'简化' in pdata:
        #       self.plot_simplified()
        #   else:
        #       self.plot_candlestick()
 
        self.plot_candlestick()
        self.plot_average()
 
        if u'相对复权' in pdata[u'行情']:
            self.plot_adjustnotes()
 
        if u'流通股变更' in pdata[u'公司信息']:
            self.plot_capchangenotes()
 
        if u'股本变更记录' in pdata[u'公司信息']:
            self.plot_capitalinfo()
 
        if u'用户标记' in pdata:
            self.plot_usernotes()
 
        self.set_xticks()
        self.set_yticks()
 
        if u'开盘二' in pdata[u'行情']:
            self.plot_candlestick_2()
            self.set_xticks_2()
            self.set_yticks_2()
 
        self.plot_pricenotes()
        self.plot_datenotes()
 
 
 
 
 
    def plot_datenotes(self):
        '''
        日期在图中间的显示
        '''
 
        ylowlim= self._ylowlim
        yhighlim= self._yhighlim
 
        axes= self._Axes
 
        mdindex= self._xparams['mdindex']
        wdindex= self._xparams['wdindex']
 
 
 
        # 每月第一个交易日
        for iy in [ylowlim*1.1, yhighlim/1.21]:
            for ix in mdindex:
                newlab= axes.text(ix-1, iy, self._dates[ix])
                newlab.set_font_properties(__font_properties__)
                newlab.set_color('0.3')
                newlab.set_fontsize(4)
                newlab.set_rotation('vertical')
                #   newlab.set_horizontalalignment('left')
                #   newlab.set_verticalalignment('bottom')
                #   newlab.set_verticalalignment('center')
                newlab.set_zorder(0)        # XXX: 放在底层
 
 
        # 每周第一个交易日,根据这个可以推算出全部确切的日期。
        #   for iy in minorticks[0:-1:6]:
        for iy in [ylowlim*1.01, yhighlim/1.09]:
            for ix in wdindex:
                newlab= axes.text(ix-0.8, iy, self._dates[ix])
                newlab.set_font_properties(__font_properties__)
                newlab.set_color('0.3')
                newlab.set_fontsize(3)
                newlab.set_rotation('vertical')
                #   newlab.set_horizontalalignment('left')
                #   newlab.set_verticalalignment('top') # 不行
                #   newlab.set_verticalalignment('center')
                #   newlab.set_verticalalignment('bottom')
                newlab.set_zorder(0)        # XXX: 放在底层
 
 
 
 
 
    def plot_pricenotes(self):
        #   价格数值在图中间的显示
        #==================================================================================================================================================
 
        quotes= self._pdata[u'行情']
 
        axes= self._Axes
        majorticks= self._ytickset['major']
        minorticks= self._ytickset['minor']
         
        mdindex= self._xparams['mdindex']
 
        def price_note(num):
            return str(round(num/1000.0, 2))
 
        if u'开盘二' in quotes:
            majorticks_2= self._ytickset['major_2']
            minorticks_2= self._ytickset['minor_2']
 
            for iy, iy2 in zip(sorted(majorticks[:-1] + minorticks[1:-1]), sorted(majorticks_2[:-1] + minorticks_2[1:-1])):
                for ix in mdindex[1:-1:3]:
                    newlab= axes.text(ix+6, iy*1.001, price_note(iy) + ' / ' + price_note(iy2))
                    newlab.set_font_properties(__font_properties__)
                    newlab.set_color('0.3')
                    newlab.set_fontsize(3)
                    newlab.set_zorder(0)        # XXX: 放在底层
        else:
            for iy in sorted(majorticks[:-1] + minorticks[1:-1]):
                for ix in mdindex[1:-1:3]:
                    newlab= axes.text(ix+9, iy*1.001, price_note(iy))
                    newlab.set_font_properties(__font_properties__)
                    newlab.set_color('0.3')
                    newlab.set_fontsize(3)
                    newlab.set_zorder(0)        # XXX: 放在底层
 
 
 
 
 
 
 
class SubPlot_TORateBase:
    '''
    换手率子图
    '''
 
    def __init__(self, pdata, parent, xparams, name):
        self._name= name
        self._pdata= pdata
        self._parent= parent
        self._xparams= xparams
        self._shrink= __shrink__ if name == 'toratefs' else 1.0
 
        self._tostep= 0     # 每一格代表的换手率数值
 
        self._yrange= 0
 
        self._xsize= 0  # int
        self._ysize= 0  # int
 
        self._Axes= None
        self._AxisX= None
        self._AxisY= None
 
        if u'换手率二' in pdata[u'行情']:
            self._Axes_2= None
            self._AxisX_2= None
            self._AxisY_2= None
            self._tostep_2= 0
 
        # 绘图数据
        quotes= pdata[u'行情']
 
        if name == 'toratefs':
            self._dates= quotes[u'日期']
            self._open= quotes[u'开盘']
            self._close= quotes[u'收盘']
            self._high= quotes[u'最高']
            self._low= quotes[u'最低']
            if u'简化' in quotes:   self._simple= quotes[u'简化']
 
            if u'换手率' in quotes: self._torate= quotes[u'换手率']
            if u'成交量' in quotes: self._volume= quotes[u'成交量']
            if u'成交额' in quotes: self._turnover= quotes[u'成交额']
 
            if u'开盘二' in quotes:
                self._open_2= quotes[u'开盘二']
                self._close_2= quotes[u'收盘二']
                self._high_2= quotes[u'最高二']
                self._low_2= quotes[u'最低二']
                if u'简化二' in quotes:   self._simple_2= quotes[u'简化二']
 
                if u'换手率二' in quotes: self._torate_2= quotes[u'换手率二']
                if u'成交量二' in quotes: self._volume_2= quotes[u'成交量二']
                if u'成交额二' in quotes: self._turnover_2= quotes[u'成交额二']
 
        else:
            sidx, eidx= pdata[u'任务描述'][u'起始偏移'], pdata[u'任务描述'][u'结束偏移']
         
            self._dates= quotes[u'日期'][sidx:eidx]
            self._open= quotes[u'开盘'][sidx:eidx]
            self._close= quotes[u'收盘'][sidx:eidx]
            self._high= quotes[u'最高'][sidx:eidx]
            self._low= quotes[u'最低'][sidx:eidx]
            if u'简化' in quotes:   self._simple= quotes[u'简化'][sidx:eidx]
 
            if u'换手率' in quotes: self._torate= quotes[u'换手率'][sidx:eidx]
            if u'成交量' in quotes: self._volume= quotes[u'成交量'][sidx:eidx]
            if u'成交额' in quotes: self._turnover= quotes[u'成交额'][sidx:eidx]
 
            if u'开盘二' in quotes:
                self._open_2= quotes[u'开盘二'][sidx:eidx]
                self._close_2= quotes[u'收盘二'][sidx:eidx]
                self._high_2= quotes[u'最高二'][sidx:eidx]
                self._low_2= quotes[u'最低二'][sidx:eidx]
                if u'简化二' in quotes:   self._simple_2= quotes[u'简化二'][sidx:eidx]
 
                if u'换手率二' in quotes: self._torate_2= quotes[u'换手率二'][sidx:eidx]
                if u'成交量二' in quotes: self._volume_2= quotes[u'成交量二'][sidx:eidx]
                if u'成交额二' in quotes: self._turnover_2= quotes[u'成交额二'][sidx:eidx]
 
 
        #   衍生数据
        #==============================================================================================================
        self._length= len(self._dates)
        self._xindex= numpy.arange(self._length)    # X 轴上的 index,一个辅助数据
 
        self._zipoc= zip(self._open, self._close)
        self._up=   numpy.array( [ True if po < pc and po is not None else False for po, pc in self._zipoc] )        # 标示出该天股价日内上涨的一个序列
        self._down= numpy.array( [ True if po > pc and po is not None else False for po, pc in self._zipoc] )        # 标示出该天股价日内下跌的一个序列
        self._side= numpy.array( [ True if po == pc and po is not None else False for po, pc in self._zipoc] )      # 标示出该天股价日内走平的一个序列
         
        if u'开盘二' in quotes:
            self._zipoc_2= zip(self._open_2, self._close_2)
            self._up_2=   numpy.array( [ True if po < pc and po is not None else False for po, pc in self._zipoc_2] )        # 标示出该天股价日内上涨的一个序列
            self._down_2= numpy.array( [ True if po > pc and po is not None else False for po, pc in self._zipoc_2] )        # 标示出该天股价日内下跌的一个序列
            self._side_2= numpy.array( [ True if po == pc and po is not None else False for po, pc in self._zipoc_2] )      # 标示出该天股价日内走平的一个序列
 
        self._compute_size()
 
 
 
 
 
    def _compute_size(self):
        '''
        根据 pdata 计算自身尺寸
        '''
        def _compute_step(maxto):
            '''
            maxto 是 换手率 最大值。返回每格单位(最小 500, 代表 0.5%)以及格数
            '''
            for i in range(9):
                if maxto > (4 * 500 * (2**i)):   # 换手率最大是 100000, 代表 100%
                    continue
                else:
                    tostep= 500 * (2**i)
                    tosize= int(round((maxto + tostep/2.0 - 1) / float(tostep), 0))
                    break
            return (tostep, tosize)
 
        quotes= self._pdata[u'行情']
        xmargin= self._xparams['xmargin']
 
        self._xsize= (self._length + xmargin*2) * self._shrink
 
        maxto= max(self._torate)
        self._tostep, self._yrange= _compute_step(maxto=maxto)
 
        if u'换手率二' in quotes:
            maxto_2= max(self._torate_2)
            self._tostep_2, yrange_2= _compute_step(maxto=maxto_2)
            self._yrange= max(self._yrange, yrange_2)   # 成交量部分在 Y 轴所占的 “份数”
 
        self._ysize= self._yrange * self._shrink
 
 
 
 
 
    def get_size(self):
        return (self._xsize, self._ysize)
 
 
 
 
 
    def build_axes(self, figobj, rect):
 
        #   第一只:添加 Axes 对象
        #==================================================================================================================================================
        axes= figobj.add_axes(rect, axis_bgcolor='black')
         
        axes.set_axis_bgcolor('black')
        axes.set_axisbelow(True)    # 网格线放在底层
 
        #   第一只:改变坐标线的颜色
        #==================================================================================================================================================
        for child in axes.get_children():
            if isinstance(child, matplotlib.spines.Spine):
                child.set_color(__color_gold__)
                #   child.set_zorder(3)     # XXX: 放在上层,好像没什么用。
 
        #   得到 X 轴 和 Y 轴 的两个 Axis 对象
        #==================================================================================================================================================
        xaxis= axes.get_xaxis()
        yaxis= axes.get_yaxis()
 
        #   设置两个坐标轴上的 grid
        #==================================================================================================================================================
        xaxis.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
        xaxis.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
        yaxis.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
        yaxis.grid(True, 'minor', color='0.3', linestyle='solid', linewidth=0.1)
 
        self._Axes= axes
        self._AxisX= xaxis
        self._AxisY= yaxis
 
 
 
        if u'换手率二' in self._pdata[u'行情']:
            #   添加 Axes 对象
            #==================================================================================================================================================
            axes_2= axes.twinx()
 
            # XXX: 下面这三行把第一个 axes 放在上面,这样不会被第二个 axes 的图形遮盖。用 zorder 不顶用。
            axes.figure.axes[-2:]= [axes_2, axes]   # XXX: 把第一个 axes 放在上面,用 zorder 不顶用。
            axes.set_frame_on(False)    # 如果不做此设定,axes_2 的内容会看不见
            axes_2.set_frame_on(True)
 
            axes_2.set_axis_bgcolor('black')
            axes_2.set_axisbelow(True)  # 网格线放在底层
 
            #   改变坐标线的颜色
            #==================================================================================================================================================
            for child in axes_2.get_children():
                if isinstance(child, matplotlib.spines.Spine):
                    child.set_color(__color_gold__)
 
            #   得到 X 轴 和 Y 轴 的两个 Axis 对象
            #==================================================================================================================================================
            xaxis_2= axes_2.get_xaxis()
            yaxis_2= axes_2.get_yaxis()
 
            #   设置网格线
            #==================================================================================================================================================
            #   xaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
            #   xaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
            #   yaxis_2.grid(True, 'major', color='0.3', linestyle='solid', linewidth=0.2)
            #   yaxis_2.grid(True, 'minor', color='0.3', linestyle='dotted', linewidth=0.1)
 
            self._Axes_2= axes_2
            self._AxisX_2= xaxis_2
            self._AxisY_2= yaxis_2
         
         
 
 
 
 
    def get_axes(self):
        return self._Axes
 
 
 
 
 
    def plot(self):
        '''
        绘制换手率图形
        '''
        self.plot_torate()
        self.set_xticks()
        self.set_yticks()
 
        if u'换手率二' in self._pdata[u'行情']:
            self.plot_torate_2()
            self.set_xticks_2()
            self.set_yticks_2()
 
 
 
 
 
    def plot_torate(self):
        '''
        绘制换手率
        '''
 
        xindex= self._xindex
        stopset= self._xparams['mdindex'] if self._name == 'torate' else self._xparams['sdindex']
        axes= self._Axes
 
        up=    self._up
        down=  self._down
        side=  self._side
 
        rarray_to= numpy.array(self._torate)
        tozeros= numpy.zeros(self._length)  # 辅助数据
 
        lwidth= 3.0 if self._name == 'torate' else 2.4 * self._shrink
 
        # XXX: 如果 up/down/side 各项全部为 False,那么 vlines() 会报错。
        if True in up:
            axes.vlines(xindex[up], tozeros[up], rarray_to[up], edgecolor='red', linewidth=lwidth, label='_nolegend_', alpha=0.5)
        if True in down:
            axes.vlines(xindex[down], tozeros[down], rarray_to[down], edgecolor='green', linewidth=lwidth, label='_nolegend_', alpha=0.5)
        if True in side:
            axes.vlines(xindex[side], tozeros[side], rarray_to[side], edgecolor='0.7', linewidth=lwidth, label='_nolegend_', alpha=0.5)
 
        #   绘制平均换手率(直线)
        toeffect= [num for num in self._torate if num is not None]
        toaverage= sum(toeffect) / float(len(toeffect))
 
        axes.plot([-1, self._length], [toaverage, toaverage], '-', color='yellow', linewidth=0.2, alpha=0.7)
 
        #   换手率数值在图中间的显示
        #==================================================================================================================================================
        for ix in stopset[2:-1:3]:
            newlab= axes.text(ix+8, toaverage, str(round(toaverage/1000.0, 2)) + '%')
            newlab.set_font_properties(__font_properties__)
            newlab.set_color('yellow')
            newlab.set_fontsize(3)
            #   newlab.set_zorder(0)        # XXX: 放在底层
            #   newlab.set_verticalalignment('center')
 
 
 
 
 
    def plot_torate_2(self):
        '''
        绘制第二条换手率柱状图
        '''
        quotes= self._pdata[u'行情']
        xindex= self._xindex
        axes= self._Axes_2
 
        up=   self._up_2
        down= self._down_2
        side= self._side_2
 
        rarray_to= numpy.array(self._torate_2)
        tozeros= numpy.zeros(self._length)  # 辅助数据
 
        lwidth, alpha= (0.7, 0.5) if self._name == 'torate' else (0.3, 0.7)
 
        # XXX: 如果 up/down/side 各项全部为 False,那么 vlines() 会报错。
        if True in up:
            axes.vlines(xindex[up], tozeros[up], rarray_to[up], edgecolor='0.7', linewidth=lwidth, label='_nolegend_', alpha=alpha)
        if True in down:
            axes.vlines(xindex[down], tozeros[down], rarray_to[down], edgecolor='0.3', linewidth=lwidth, label='_nolegend_', alpha=alpha)
        if True in side:
            axes.vlines(xindex[side], tozeros[side], rarray_to[side], edgecolor='0.7', linewidth=lwidth, label='_nolegend_', alpha=1.0)
 
 
 
 
 
    def set_xticks(self):
        '''
        X 轴坐标
        '''
        length= self._length
        xmargin= self._xparams['xmargin']
 
        axes= self._Axes
        xaxis= self._AxisX
 
        #   xaxis.set_tick_params(which='both', direction='out')    # XXX: 坐标点设到外面去,也可以用 Axes.tick_params(),好像 matplotlib 1.0.1 才有
 
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_xlim(-xmargin, length + xmargin)
 
        xMajorLocator= self._xparams['xMajorLocator']
        xMinorLocator= self._xparams['xMinorLocator']
        xMajorFormatter= self._xparams['xMajorFormatter']
        xMinorFormatter= self._xparams['xMinorFormatter']
 
        # 设定 X 轴的 Locator 和 Formatter
        xaxis.set_major_locator(xMajorLocator)
        xaxis.set_minor_locator(xMinorLocator)
 
        if self._name == 'torate':
            xaxis.set_major_formatter(xMajorFormatter)
            xaxis.set_minor_formatter(xMinorFormatter)
 
            # 设定 X 轴主要坐标点与辅助坐标点的样式
            for mal in axes.get_xticklabels(minor=False):
                mal.set_fontsize(4)
                mal.set_horizontalalignment('right')
                mal.set_rotation('45')
 
            for mil in axes.get_xticklabels(minor=True):
                mil.set_fontsize(4)
                mil.set_color('blue')
                mil.set_horizontalalignment('right')
                mil.set_rotation('45')
        else:
            # 设为不可见
            for mal in axes.get_xticklabels(minor=False):
                mal.set_visible(False)
 
            for mil in axes.get_xticklabels(minor=True):
                mil.set_visible(False)
 
 
 
 
 
    def set_xticks_2(self):
 
        length= self._length
        xmargin= self._xparams['xmargin']
 
        axes= self._Axes_2
        xaxis= self._AxisX_2
 
        #   xaxis.set_tick_params(which='both', direction='out')    # XXX: 坐标点设到外面去,也可以用 Axes.tick_params(),好像 matplotlib 1.0.1 才有
 
        #   设定 X 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_xlim(-xmargin, length + xmargin)
 
        xMajorLocator= self._xparams['xMajorLocator']
        xMinorLocator= self._xparams['xMinorLocator']
 
        # 设定 X 轴的 Locator 和 Formatter
        xaxis.set_major_locator(xMajorLocator)
        xaxis.set_minor_locator(xMinorLocator)
 
        # 设为不可见
        for mal in axes.get_xticklabels(minor=False):
            mal.set_visible(False)
 
        for mil in axes.get_xticklabels(minor=True):
            mil.set_visible(False)
 
 
 
 
 
    def set_yticks(self):
        '''
        设置 Y 轴坐标
        '''
        axes= self._Axes
        yaxis= self._AxisY
        tostep= self._tostep
        yrange= self._yrange
        stopset= self._xparams['mdindex'] if self._name == 'torate' else self._xparams['sdindex']
 
        #   设定换手率 Y 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_ylim(0, tostep*yrange)
 
        #   主要坐标点
        #==================================================================================================================================================
        majorticks= [tostep*i for i in range(yrange)]
        yMajorLocator= FixedLocator(numpy.array(majorticks))
 
        # 确定 Y 轴的 MajorFormatter
        def y_major_formatter(num, pos=None):
            return str(round(num/1000.0, 2)) + '%'
 
        yMajorFormatter= FuncFormatter(y_major_formatter)
 
        # 确定 Y 轴的 MinorFormatter
        yMinorFormatter= NullFormatter()
 
        # 第一只:设定 X 轴的 Locator 和 Formatter
        yaxis.set_major_locator(yMajorLocator)
        yaxis.set_major_formatter(yMajorFormatter)
 
        # 设定 Y 轴主要坐标点的样式
        for mal in axes.get_yticklabels(minor=False):
            mal.set_font_properties(__font_properties__)
            mal.set_fontsize(5) # 这个必须放在前一句后面,否则作用会被覆盖
 
        #   辅助坐标点
        #==================================================================================================================================================
        if self._name == 'torate':
            minorticks= list( itertools.chain.from_iterable( mi for mi in [[ma + (tostep/4.0)*i for i in range(1, 4)] for ma in majorticks] ) )
            yMinorLocator= FixedLocator(numpy.array(minorticks))
            yaxis.set_minor_locator(yMinorLocator)
         
            def y_minor_formatter(num, pos=None):
                return str(round(num/1000.0, 3)) + '%'
             
            yMinorFormatter= FuncFormatter(y_minor_formatter)
             
            yaxis.set_minor_formatter(yMinorFormatter)
 
            # 设定 Y 轴主要坐标点的样式
            for mil in axes.get_yticklabels(minor=True):
                mil.set_font_properties(__font_properties__)
                mil.set_fontsize(4) # 这个必须放在前一句后面,否则作用会被覆盖
 
        else:
             
            #   minorticks= list( itertools.chain.from_iterable( mi for mi in [[ma + (tostep/4.0)*i for i in range(1, 4)] for ma in majorticks] ) )
            minorticks= list( [ma + (tostep/2.0) for ma in majorticks] )
            yMinorLocator= FixedLocator(numpy.array(minorticks))
            yaxis.set_minor_locator(yMinorLocator)
 
            # 设定 Y 轴主要坐标点的样式
            for mil in axes.get_yticklabels(minor=True):
                mil.set_visible(False)
 
        #   换手率数值在图中间的显示
        #==================================================================================================================================================
        for iy in range(int(tostep/2.0), tostep*yrange, int(tostep/2.0)):
            for ix in stopset[1:-1:3]:
                newlab= axes.text(ix+8, iy, y_major_formatter(iy))
                newlab.set_font_properties(__font_properties__)
                newlab.set_color('0.3')
                newlab.set_fontsize(3)
                newlab.set_zorder(0)        # XXX: 放在底层
                #   newlab.set_verticalalignment('center')
 
 
 
 
 
    def set_yticks_2(self):
        '''
        设置 Y 轴坐标
        '''
        axes= self._Axes_2
        yaxis= self._AxisY_2
        tostep= self._tostep_2
        yrange= self._yrange    # 与 1 是一样的
 
        #   设定换手率 Y 轴坐标的范围 
        #==================================================================================================================================================
        axes.set_ylim(0, tostep*yrange)
 
        #   主要坐标点
        #==================================================================================================================================================
        majorticks= [tostep*i for i in range(yrange)]
        yMajorLocator= FixedLocator(numpy.array(majorticks))
 
        # 确定 Y 轴的 MajorFormatter
        def y_major_formatter(num, pos=None):
            return str(round(num/1000.0, 2)) + '%'
 
        yMajorFormatter= FuncFormatter(y_major_formatter)
 
        # 确定 Y 轴的 MinorFormatter
        yMinorFormatter= NullFormatter()
 
        # 第一只:设定 X 轴的 Locator 和 Formatter
        yaxis.set_major_locator(yMajorLocator)
        yaxis.set_major_formatter(yMajorFormatter)
 
        # 设定 Y 轴主要坐标点的样式
        for mal in axes.get_yticklabels(minor=False):
            mal.set_font_properties(__font_properties__)
            mal.set_fontsize(5) # 这个必须放在前一句后面,否则作用会被覆盖
 
        #   辅助坐标点
        #==================================================================================================================================================
        if self._name == 'torate':
            minorticks= list( itertools.chain.from_iterable( mi for mi in [[ma + (tostep/4.0)*i for i in range(1, 4)] for ma in majorticks] ) )
            yMinorLocator= FixedLocator(numpy.array(minorticks))
 
            def y_minor_formatter(num, pos=None):
                return str(round(num/1000.0, 3)) + '%'
 
            yMinorFormatter= FuncFormatter(y_minor_formatter)
 
            yaxis.set_minor_locator(yMinorLocator)
            yaxis.set_minor_formatter(yMinorFormatter)
 
            # 设定 Y 轴主要坐标点的样式
            for mil in axes.get_yticklabels(minor=True):
                mil.set_font_properties(__font_properties__)
                mil.set_fontsize(4) # 这个必须放在前一句后面,否则作用会被覆盖
 
        else:
            minorticks= list( [ma + (tostep/2.0) for ma in majorticks] )
            yMinorLocator= FixedLocator(numpy.array(minorticks))
 
            yaxis.set_minor_locator(yMinorLocator)
 
            # 设定 Y 轴主要坐标点的样式
            for mil in axes.get_yticklabels(minor=True):
                mil.set_visible(False)
 
 
 
 
 
 
 
class SubPlot_TORate(SubPlot_TORateBase):
    pass
 
 
 
 
 
class SubPlot_TORateFullSpan(SubPlot_TORateBase):
    pass
 
 
 
 
 
class MyFigure:
    '''
 
    '''
    def __init__(self, pdata):
        self._pdata= pdata  # 绘图数据
 
        self._figfacecolor= __color_pink__
        self._figedgecolor= __color_navy__
        self._figdpi= 300
        self._figlinewidth= 1.0
 
        self._xfactor= 10.0 / 230.0 # x size * x factor = x length
        self._yfactor= 0.3      # y size * y factor = y length
 
        jobstat= pdata[u'任务描述']
         
        self._xsize_left= 12.0  # left blank
        self._xsize_right= 12.0 # right blank
        self._ysize_top= 0.3    # top blank
        self._ysize_bottom= 1.2 # bottom blank
 
        self._ysize_gap1= 0.2
        self._ysize_gap2= 0.3 if (jobstat[u'历史价格子图'] or jobstat[u'历史换手率子图'] or jobstat[u'财务指标子图']) else 0.0
 
        #   建立 X 轴参数
        #===============================================================================================================
        if jobstat[u'价格子图'] or jobstat[u'换手率子图']:
            xparams= {'xmargin': 1}
            xparams.update(self._compute_xparams()) # 与 X 轴坐标点相关的数据结构
 
        if jobstat[u'历史价格子图'] or jobstat[u'历史换手率子图'] or jobstat[u'财务指标子图']:
            xparams_fs= {'xmargin': 3}
            xparams_fs.update(self._compute_xparams_fullspan())
 
        #   建立子图对象
        #===============================================================================================================
        self._subplots= {}
 
        if jobstat[u'公司信息子图']:
            name= 'basic'
            self._subplots[name]= SubPlot_BasicInfo(pdata=pdata, parent=self, name=name)
 
        if jobstat[u'历史价格子图']:  # XXX: 这个要放在 价格子图 前面,因为后者可能会用到它的 Y 轴坐标点位置
            name= 'pricefs'
            self._subplots[name]= SubPlot_PriceFullSpan(pdata=pdata, parent=self, xparams=xparams_fs, name=name)
 
        if jobstat[u'价格子图']:
            name= 'price'
            self._subplots[name]= SubPlot_Price(pdata=pdata, parent=self, xparams=xparams, name=name)
         
        if jobstat[u'财务指标子图']:
            name= 'financial'
            self._subplots[name]= SubPlot_Financial(pdata=pdata, parent=self, xparams=xparams_fs, name=name)
         
        if jobstat[u'换手率子图']:
            name= 'torate'
            self._subplots[name]= SubPlot_TORate(pdata=pdata, parent=self, xparams=xparams, name=name)
 
        if jobstat[u'历史换手率子图']:
            name= 'toratefs'
            self._subplots[name]= SubPlot_TORateFullSpan(pdata=pdata, parent=self, xparams=xparams_fs, name=name)
 
 
 
 
 
        #   根据子图对象的尺寸计算自身的尺寸
        #===============================================================================================================
        self._xsize, \
        self._ysize= self._compute_size()
 
        self._xlength= self._xsize * self._xfactor
        self._ylength= self._ysize * self._yfactor
 
        #   根据计算出的尺寸建立 Figure 对象
        #===============================================================================================================
        self._Fig= pyplot.figure(figsize=(self._xlength, self._ylength), dpi=self._figdpi, facecolor=self._figfacecolor, \
            edgecolor=self._figedgecolor, linewidth=self._figlinewidth) # Figure 对象
 
        #   用新建立的 Figure 对象交给子图对象,完成子图对象的初始化
        #===============================================================================================================
        rects= self._compute_rect()
 
 
        if 'basic' in self._subplots:
            self._subplots['basic'].build_axes(figobj=self._Fig, rect=rects['basic'])
 
        # XXX: 这个要放在 price 前面,因为后者要用到它的 Axes 对象
        if 'torate' in self._subplots:
            self._subplots['torate'].build_axes(figobj=self._Fig, rect=rects['torate'])
 
        if 'price' in self._subplots:
            self._subplots['price'].build_axes(figobj=self._Fig, rect=rects['price'])
 
        # XXX: 这个要放在 pricefs 前面
        if 'toratefs' in self._subplots:
            self._subplots['toratefs'].build_axes(figobj=self._Fig, rect=rects['toratefs'])
 
        if 'pricefs' in self._subplots:
            self._subplots['pricefs'].build_axes(figobj=self._Fig, rect=rects['pricefs'])
 
 
 
 
 
    def _compute_size(self):
        '''
        根据子图的尺寸计算自身尺寸
        '''
        pdata= self._pdata
        jobstat= pdata[u'任务描述']
 
        x_left, x_right= self._xsize_left, self._xsize_right
        y_top, y_bottom= self._ysize_top, self._ysize_bottom
 
        y_gap1= self._ysize_gap1
        y_gap2= self._ysize_gap2
 
        x_basic, y_basic= self._subplots['basic'].get_size() if 'basic' in self._subplots else (0.0, 0.0)
        x_price, y_price= self._subplots['price'].get_size() if 'price' in self._subplots else (0.0, 0.0)
        x_pricefs, y_pricefs= self._subplots['pricefs'].get_size() if 'pricefs' in self._subplots else (0.0, 0.0)
        x_torate, y_torate= self._subplots['torate'].get_size() if 'torate' in self._subplots else (0.0, 0.0)
        x_toratefs, y_toratefs= self._subplots['toratefs'].get_size() if 'toratefs' in self._subplots else (0.0, 0.0)
        x_financial, y_financial= self._subplots['financial'].get_size() if 'financial' in self._subplots else (0.0, 0.0)
 
        x_all= x_left + max(x_price, x_basic, x_pricefs) + x_right
        y_all= y_top + y_basic + y_gap1 + y_pricefs + y_toratefs + y_financial + y_gap2 + y_price + y_torate + y_bottom
 
        return (x_all, y_all)
 
 
 
 
 
    def get_sizeset(self):
        sizeset= {
            'x': self._xsize,
            'y': self._ysize,
            'top': self._ysize_top,
            'bottom': self._ysize_bottom,
            'left': self._xsize_left,
            'right': self._xsize_right
        }
 
        return sizeset
 
 
 
 
 
    def _compute_rect(self):
        '''
 
        '''
        pdata= self._pdata
        jobstat= pdata[u'任务描述']
 
        x_left= self._xsize_left
        x_right= self._xsize_right
        y_top= self._ysize_top
        y_bottom= self._ysize_bottom
        x_all= self._xsize
        y_all= self._ysize
 
        y_gap1= self._ysize_gap1    # basic 与 financial 之间的空隙
        y_gap2= self._ysize_gap2    # toratefs 与 price 之间的空隙
         
        x_basic, y_basic= self._subplots['basic'].get_size() if 'basic' in self._subplots else (0.0, 0.0)
        x_price, y_price= self._subplots['price'].get_size() if 'price' in self._subplots else (0.0, 0.0)
        x_pricefs, y_pricefs= self._subplots['pricefs'].get_size() if 'pricefs' in self._subplots else (0.0, 0.0)
        x_torate, y_torate= self._subplots['torate'].get_size() if 'torate' in self._subplots else (0.0, 0.0)
        x_toratefs, y_toratefs= self._subplots['toratefs'].get_size() if 'toratefs' in self._subplots else (0.0, 0.0)
        x_financial, y_financial= self._subplots['financial'].get_size() if 'financial' in self._subplots else (0.0, 0.0)
 
        rects= {}
 
        if 'basic' in self._subplots:
            rect= ((x_left + (x_all-x_left-x_right-x_basic)/2) / x_all, (y_all - y_top - y_basic)/y_all, x_basic/x_all, y_basic/y_all)      # K线图部分
            rects['basic']= rect
 
        if 'price' in self._subplots:
            rect= ((x_left + (x_all-x_left-x_right-x_price)/2) / x_all, (y_bottom + y_torate)/y_all, x_price/x_all, y_price/y_all)      # K线图部分
            rects['price']= rect
 
        if 'torate' in self._subplots:
            rect= ((x_left + (x_all-x_left-x_right-x_torate)/2)/x_all, y_bottom/y_all, x_torate/x_all, y_torate/y_all)  # 成交量部分
            rects['torate']= rect
 
        if 'pricefs' in self._subplots:
            rect= ((x_left + (x_all-x_left-x_right-x_pricefs)/2)/x_all, (y_all - y_top - y_basic - y_gap1 - y_pricefs)/y_all, x_pricefs/x_all, y_pricefs/y_all)
            rects['pricefs']= rect
 
        if 'toratefs' in self._subplots:
            rect= ((x_left + (x_all-x_left-x_right-x_toratefs)/2)/x_all, (y_bottom + y_torate + y_price + y_gap2)/y_all, x_toratefs/x_all, y_toratefs/y_all)
            rects['toratefs']= rect
 
        return rects
 
 
 
 
 
    def _compute_xparams(self):
        '''
        主要坐标点是每月第一个交易日,辅助坐标点是每周第一个交易日
        '''
        quotes= self._pdata[u'行情']
        sidx= self._pdata[u'任务描述'][u'起始偏移']
        eidx= self._pdata[u'任务描述'][u'结束偏移']
 
        #   设定 X 轴上的坐标
        #==================================================================================================================================================
        datelist= [ datetime.date(int(ys), int(ms), int(ds)) for ys, ms, ds in [ dstr.split('-') for dstr in quotes[u'日期'][sidx:eidx] ] ]
 
        # 确定 X 轴的 MajorLocator
        mdindex= [] # 每个月第一个交易日在所有日期列表中的 index
        allyears= set([d.year for d in datelist])   # 所有的交易年份
 
        for yr in sorted(allyears):     
            allmonths= set([d.month for d in datelist if d.year == yr])     # 当年所有的交易月份
            for mon in sorted(allmonths):
                monthday= min([dt for dt in datelist if dt.year==yr and dt.month==mon]) # 当月的第一个交易日
                mdindex.append(datelist.index(monthday))
 
        xMajorLocator= FixedLocator(numpy.array(mdindex))
 
        # 确定 X 轴的 MinorLocator
        wdindex= {} # value: 每周第一个交易日在所有日期列表中的 index; key: 当周的序号 week number(当周是第几周)
         
        for d in datelist:
            isoyear, weekno= d.isocalendar()[0:2]
            dmark= isoyear*100 + weekno
            if dmark not in wdindex:
                wdindex[dmark]= datelist.index(d)
 
        wdindex= sorted(wdindex.values())
 
        xMinorLocator= FixedLocator(numpy.array(wdindex))
 
        # 确定 X 轴的 MajorFormatter 和 MinorFormatter
        def x_major_formatter(idx, pos=None):
            return datelist[idx].strftime('%Y-%m-%d')
 
        def x_minor_formatter(idx, pos=None):
            return datelist[idx].strftime('%m-%d')
 
        xMajorFormatter= FuncFormatter(x_major_formatter)
        xMinorFormatter= FuncFormatter(x_minor_formatter)
 
        return {'xMajorLocator': xMajorLocator,
            'xMinorLocator': xMinorLocator,
            'xMajorFormatter': xMajorFormatter,
            'xMinorFormatter': xMinorFormatter,
            'mdindex': mdindex,
            'wdindex': wdindex
        }
 
 
 
    def _compute_xparams_fullspan(self):
        '''
        主要坐标点是每季第一个交易日,辅助坐标点是每月第一个交易日。是给宏观子图用的。
        '''
        quotes= self._pdata[u'行情']
 
        datelist= [ datetime.date(int(ys), int(ms), int(ds)) for ys, ms, ds in [ dstr.split('-') for dstr in quotes[u'日期'] ] ]
 
        # 确定 X 轴的 MinorLocator
        mdindex= [] # 每个月第一个交易日在所有日期列表中的 index
        sdindex= [] # 每季度第一个交易日在所有日期列表中的 index
        ydindex= [] # 每年第一个交易日在所有日期列表中的 index
 
        allyears= set([d.year for d in datelist])   # 所有的交易年份
 
        for yr in sorted(allyears):     
            allmonths= set([d.month for d in datelist if d.year == yr])     # 当年所有的交易月份
            for mon in sorted(allmonths):
                monthday= min([dt for dt in datelist if dt.year==yr and dt.month==mon]) # 当月的第一个交易日
                idx= datelist.index(monthday)
 
                if mon in (1, 4, 7, 10):
                    sdindex.append(idx)
 
                    if mon == 1:
                        ydindex.append(idx)
                else:
                    mdindex.append(idx)
 
 
 
        xMajorLocator= FixedLocator(numpy.array(sdindex))
        xMinorLocator= FixedLocator(numpy.array(mdindex))
 
        # 确定 X 轴的 MajorFormatter 和 MinorFormatter
        def x_major_formatter(idx, pos=None):
            return datelist[idx].strftime('%Y-%m-%d')
 
        def x_minor_formatter(idx, pos=None):
            return datelist[idx].strftime('%m-%d')
 
        xMajorFormatter= FuncFormatter(x_major_formatter)
        xMinorFormatter= FuncFormatter(x_minor_formatter)
 
        return {'xMajorLocator': xMajorLocator,
            'xMinorLocator': xMinorLocator,
            'xMajorFormatter': xMajorFormatter,
            'xMinorFormatter': xMinorFormatter,
            'sdindex': sdindex,
            'mdindex': mdindex,
            'ydindex': ydindex
        }
 
 
 
 
 
    def plot(self):
        '''
        '''
        #   self.plot_title()
 
        # 调用子图对象的绘图函数
        if 'basic' in self._subplots:
            self._subplots['basic'].plot()
 
        if 'price' in self._subplots:
            self._subplots['price'].plot()
 
        if 'torate' in self._subplots:
            self._subplots['torate'].plot()
 
        if 'pricefs' in self._subplots:
            self._subplots['pricefs'].plot()
 
        if 'toratefs' in self._subplots:
            self._subplots['toratefs'].plot()
 
 
 
 
 
    def plot_title(self):
        '''
        绘制整个 Figure 的标题
        '''
        info= self._pdata[u'公司信息']
        figobj= self._Fig
 
        # 整个 figure 的标题
        subtitle= (info[u'代码'] + ' ' if u'代码' in info else '') + info[u'简称']
        subtitle_2= (info[u'代码二'] + '   ' if u'代码二' in info else '') + info[u'简称二']
 
        figobj.suptitle(subtitle + ' / ' + subtitle_2, fontsize=12, fontproperties=__font_properties__)
 
 
 
 
 
    def savefig(self, figpath):
        '''
        保存图片
        '''
        self._Fig.savefig(figpath, dpi=self._figdpi, facecolor=self._figfacecolor, edgecolor=self._figedgecolor, linewidth=self._figlinewidth)
 
 
 
 
 
if __name__ == '__main__':
     
    # pfile 指明存放绘图数据的 pickle file,figpath 指定图片需存放的路径
    pfile= sys.argv[1]
    figpath= sys.argv[2]
 
    # 绘图数据 pdata
    fileobj= open(name=pfile, mode='rb')
    pdata= pickle.load(fileobj)
    fileobj.close()
    os.remove(pfile)
 
    myfig= MyFigure(pdata=pdata)
    myfig.plot()
    myfig.savefig(figpath=figpath)

 

转载于:https://my.oschina.net/zhupengdaniu/blog/1609927

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值