earnmi.chart.echarts 源代码

"""
为了简单使用,封装 pyechart的用法, 专门用于处理k线图。

pyechart所有配置手册: https://pyecharts.org/#/zh-cn/global_options
pyechart代码demo:  https://gallery.pyecharts.org/#/Bar/bar_different_series_gap

"""
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import *

from pyecharts import charts

from pyecharts.commons.utils import JsCode

from earnmi.data import BarData,Interval
from pyecharts.charts.base import Base as echartView
import pyecharts.options as opts


[文档] @dataclass class ViewOpts: """ 视觉相关的配置 """ top:int ## 位置 height:int ## 位置 padding_left:int padding_right:int padding_bottom:int padding_top:int total_height:int def padding_height(self): return self.height - self.padding_top -self.padding_bottom
[文档] class K_style(Enum): """ K线图样式 """ Line = "line" Candle = "candle" Pnl = "pnl" ## 每日涨幅
[文档] class Mode(Enum): """ 显示模式 """ History = "history" History_Hour = "history_hour" History_Minute = "history_minute" Today = "today"
[文档] @dataclass class EchartOpts: mode:Mode = Mode.History ### 显示模式 k_style:K_style = K_style.Line ## k线图样式 #hide_main_chart:bool = False ## 是否关闭主chart图 screen_bar_size:int = 150 ## 一个屏幕默认显示的大小 screen_scroll_range_start:int = None ## 滚动条默认开始位置:0到100之间
[文档] @dataclass class ChartData: """ 基本的行情数据 """ bars: List[BarData] = None x_values:List[str] = None ## x 轴的值。 close_list:List[float] = None candle_list:List[float] = None ## [ [open, close, low, high,pre_close] ],[ [open, close, low, high,pre_close] ....] max_value:float = None min_value:float = None precision:int = None ## 浮点精度控制,可以round(3.1415926, 2) x_mark_lines:List[float] = None ### Mode.History_Minute, Mode.History_Hour才有数据 name:str= '价格'
class _BaseChart: def _build_chart(self,echart:'Echart',chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): """ 构造图片的具体实现 :param echart 整个图表对象 :param chart_data 行情基本数据对象 :param view_opt 视图相关的配置信息,如区域信息。 :return 返回echart的chart对象 """ pass
[文档] @dataclass class Line: name:str ## 名称 values:List[float] ## 值 color:str = None width:float = 1
[文档] @dataclass class Dot: """ 点的集合 """ name: str ## 名称 values: List[float] ## 值 color: str = None width: float = 1
@dataclass class _BaseEChart: bars: List[BarData] opts: EchartOpts = None name:str = None def __post_init__(self): self._main = _MainChartImpl(self) self._bottom_layout = _BottomLayout(self) if self.opts is None: self.opts = EchartOpts() if len(self.bars) > 0: first_b = self.bars[0] if first_b.interval < Interval.HALF: self.opts.mode = Mode.History_Minute @property def main(self) -> '_MainChart': return self._main def bottom(self)->'_BottomLayout': return self._bottom_layout
[文档] @dataclass class Echart(_BaseEChart): """ example: from earnmi.chart import echarts as CHART echart = CHART.Echart(bars=bars) echart.opts.mode = CHART.Mode.History echart.opts.k_style = CHART.K_style.Line echart.bottom().add_volume_view() output.put_html(echart.build().render_notebook()) """
[文档] def build(self,width:Union[str,int] = None,height:int = None)->echartView: """ :param width 宽度,如: '80%', 如果是数字表示像素值 :param height 高度像素,只允许数字值。 """ return _builder_echarts_view(self,width,height)
class _BottomLayout: def __init__(self,echart:_BaseEChart): self.echart = echart self._groups:List[_BottomChart] = [] def add_volume_view(self)->'_BottomChart': chart= _Vol_Chart() return self.add_view(chart) def add_view(self,chart:Union['_BottomChart',Type['_BottomChart']] = None)->'_BottomChart': if chart is None: chart = _BottomChart() elif not isinstance(chart,_BottomChart): chart = chart() assert isinstance(chart,_BottomChart) self._groups.append(chart) setattr(chart,'_grid_index',len(self._groups)) return chart def add_lines_view(self,lines:'Lines')->'_BottomChart': chart = _BottomChart() chart.set_lines(lines) return self.add_view(chart) def get_all_views(self)->List['_BottomChart']: return self._groups class _BottomChart(_BaseChart): def get_grid_index(self)->int: return getattr(self,"_grid_index",0) def _build_chart(self,echart:Echart,chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): candles = self.candles() _chart = candles._build_chart(echart,chart_data,view_opt,scrollable,**kwargs) if _chart: return _chart lines = self.lines() return lines._build_chart(echart,chart_data,view_opt,scrollable,**kwargs) def lines(self)->'Lines': lines:Lines = getattr(self,"_lines_",None) if lines is None: lines = Lines() setattr(self,"_lines_",lines) return lines def candles(self)->'Candles': candles:Candles = getattr(self,"_candles_",None) if candles is None: candles = Candles() setattr(self,"_candles_",candles) return candles def set_lines(self,line:'Lines'): setattr(self, "_lines_", line) class _MainChart(_BaseChart): def __init__(self,echart:_BaseEChart): self.echart = echart self._lines = Lines() self._scatter = Scatter() self._mark_points = set() @property def mark_point(self)->Set[int]: """ marke pint 位置。 """ return self._mark_points def lines(self)->'Lines': """ 线图 """ return self._lines def scatter(self)->'Scatter': """ 散点图 """ return self._scatter def build_chart_data(self)->ChartData: echart = self.echart c_data = ChartData() bars: List[BarData] = echart.bars c_data.bars = bars if echart.name is not None: c_data.name = echart.name c_data.close_list = [] c_data.candle_list = [] c_data.max_value = bars[0].high c_data.min_value = bars[0].low for bar in bars: c_data.min_value = min(bar.low, c_data.min_value) c_data.max_value = max(bar.high, c_data.max_value) c_data.precision = 0 ## 浮点精度 _min_value = min(abs(c_data.min_value), abs(c_data.max_value)) if _min_value >= 10000: c_data.precision = 0 elif _min_value >= 1000: c_data.precision = 1 elif _min_value >= 10: c_data.precision = 2 elif _min_value > 0.1: c_data.precision = 3 else: c_data.precision = 4 c_data.max_value = round(c_data.max_value,c_data.precision) c_data.min_value = round(c_data.min_value,c_data.precision) c_data.close_list = [] c_data.candle_list = [] for index,bar in enumerate(bars): c_data.close_list.append(round(bar.close, c_data.precision)) c_data.candle_list.append([ round(bar.open, c_data.precision), round(bar.close, c_data.precision), round(bar.low, c_data.precision), round(bar.high, c_data.precision), round(bar.pre_close, c_data.precision), ]) # [open, close, low, high,pre_close] # k值 x_format = "%Y-%m-%d" if echart.opts.mode == Mode.Today: x_format = "%H:%M" elif echart.opts.mode == Mode.History_Hour: x_format = "%m%d %H" elif echart.opts.mode == Mode.History_Minute: x_format = "%m%d %H:%M" def market_x_value(time: datetime): return f"{time.strftime(x_format)}" c_data.x_values = [market_x_value(k_item.datetime) for k_item in echart.bars] ### x轴的时间值 build_mark_line = echart.opts.mode == Mode.History_Hour or echart.opts.mode == Mode.History_Minute if build_mark_line: c_data.x_mark_lines = [] pre_time:datetime = None for k_bar in echart.bars: cur_time = k_bar.datetime if pre_time is None or pre_time.date() == cur_time.date(): c_data.x_mark_lines.append(False) else: c_data.x_mark_lines.append(True) pre_time = cur_time return c_data
[文档] @dataclass class Scatter(_BaseChart): """ 散点图。 """ title:str = "" x_axis_show: bool = True ## 是否显示x轴 x_axis_on_zero: bool = True ## 如果显示x轴线,true则代表画在0线上,否则则画在底部。 max_value:float = None min_value:float = None def add_dot(self, dot: Dot): dots = self.get_dots() if dots is None: dots = [] setattr(self, "_dots", dots) dots.append(dot) return self def get_dots(self) -> List[Dot]: return getattr(self, "_dots", None) def _build_chart(self,echart:'Echart',chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): dots = self.get_dots() if not dots: return None kline_line = charts.Scatter() kline_line.add_xaxis(xaxis_data=chart_data.x_values) if self.x_axis_show: axisline_opts = None ## 默认显示x轴,且画在zero上 if not self.x_axis_on_zero: axisline_opts =opts.AxisLineOpts(is_show=True,is_on_zero=False) else: axisline_opts =opts.AxisLineOpts(is_show=False) for dot in dots: kline_line.add_yaxis( series_name=dot.name, y_axis=dot.values, color=dot.color, label_opts=opts.LabelOpts(is_show=False), ) min_ = "dataMin" ### 滚动的时候自适应 max_ = "dataMax" if self.min_value is not None: min_ = self.min_value if self.max_value is not None: max_ = self.max_value kline_line.set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", # grid_index=1, axislabel_opts=opts.LabelOpts(is_show=False), splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 axisline_opts=axisline_opts, axistick_opts=opts.AxisTickOpts(is_show=False), ## 不显示刻度 ), legend_opts= opts.LegendOpts(is_show=True,pos_top=f"{view_opt.top}px",pos_left=f"{view_opt.padding_left + 15}px"), yaxis_opts=opts.AxisOpts( position="right", #axisline_opts=opts.AxisLineOpts(is_on_zero=False), axistick_opts=opts.AxisTickOpts(is_show=False), axislabel_opts=opts.LabelOpts(is_show=True), min_=min_, max_ = max_, splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 ), ) return kline_line
[文档] @dataclass class Candles(_BaseChart): """ k线图 """ _name:str = None _bars:List[BarData] = None def set_values(self, name:str,bars:List[BarData]): self._name = name self._bars = bars return self def _build_chart(self,echart:'Echart',chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): bars = self._bars if not bars: return None kline_line = charts.Kline() candle_list = [] for index, bar in enumerate(bars): candle_list.append([ round(bar.open, 3), round(bar.close, 3), round(bar.low, 3), round(bar.high,3), round(bar.pre_close, 3), ]) # [open, close, low, high,pre_close] # k值 ( kline_line .add_xaxis(xaxis_data=chart_data.x_values) .add_yaxis( series_name=self._name, y_axis=candle_list, # splitline_opts=opts.SplitLineOpts(is_show=False, linestyle_opts=opts.LineStyleOpts(opacity=1)), itemstyle_opts=opts.ItemStyleOpts(color="#ec0000", color0="#00da3c"), ) ) min_ = "dataMin" ### 滚动的时候自适应 max_ = "dataMax" # if self.min_value is not None: # min_ = self.min_value # if self.max_value is not None: # max_ = self.max_value kline_line.set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", # grid_index=1, axislabel_opts=opts.LabelOpts(is_show=False), splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 #axisline_opts=axisline_opts, axistick_opts=opts.AxisTickOpts(is_show=False), ## 不显示刻度 ), legend_opts= opts.LegendOpts(is_show=True,pos_top=f"{view_opt.top}px",pos_left=f"{view_opt.padding_left + 15}px"), yaxis_opts=opts.AxisOpts( position="right", #axisline_opts=opts.AxisLineOpts(is_on_zero=False), axistick_opts=opts.AxisTickOpts(is_show=False), axislabel_opts=opts.LabelOpts(is_show=True), min_=min_, max_ = max_, splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 ), ) return kline_line
[文档] @dataclass class Lines(_BaseChart): """ 线的集合。 """ title:str = "" x_axis_show: bool = True ## 是否显示x轴 x_axis_on_zero: bool = True ## 如果显示x轴线,true则代表画在0线上,否则则画在底部。 max_value:float = None min_value:float = None def add_line(self, line: Line): lines = self.get_lines() if lines is None: lines = [] setattr(self, "_lines", lines) lines.append(line) return self def get_lines(self) -> List[Line]: return getattr(self, "_lines", None) def _build_chart(self,echart:'Echart',chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): lines = self.get_lines() if not lines: return None kline_line = charts.Line() kline_line.add_xaxis(xaxis_data=chart_data.x_values) if self.x_axis_show: axisline_opts = None ## 默认显示x轴,且画在zero上 if not self.x_axis_on_zero: axisline_opts =opts.AxisLineOpts(is_show=True,is_on_zero=False) else: axisline_opts =opts.AxisLineOpts(is_show=False) for line in lines: kline_line.add_yaxis( series_name=line.name, y_axis=line.values, is_smooth=True, color=line.color, is_symbol_show=False, ## 不显示连接线的圆点 linestyle_opts=opts.LineStyleOpts(opacity=0.5,width=line.width,color=line.color), label_opts=opts.LabelOpts(is_show=False), ) min_ = "dataMin" ### 滚动的时候自适应 max_ = "dataMax" if self.min_value is not None: min_ = self.min_value if self.max_value is not None: max_ = self.max_value kline_line.set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", # grid_index=1, axislabel_opts=opts.LabelOpts(is_show=False), splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 axisline_opts=axisline_opts, axistick_opts=opts.AxisTickOpts(is_show=False), ## 不显示刻度 ), legend_opts= opts.LegendOpts(is_show=True,pos_top=f"{view_opt.top}px",pos_left=f"{view_opt.padding_left + 15}px"), yaxis_opts=opts.AxisOpts( position="right", #axisline_opts=opts.AxisLineOpts(is_on_zero=False), axistick_opts=opts.AxisTickOpts(is_show=False), axislabel_opts=opts.LabelOpts(is_show=True), min_=min_, max_ = max_, splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 ), ) return kline_line
#######-----------------------以上是具体实现---------------- class _MainChartImpl(_MainChart): def _build_chart(self,echart:Echart,chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): pass def _build_chart(self,echart:Echart,chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): bars: List[BarData] = echart.bars k_bar_size = len(bars) y_line_list:List[float] = None min_value = chart_data.min_value max_value = chart_data.max_value if echart.opts.k_style == K_style.Line: y_line_list = chart_data.close_list main_chart = ( charts.Line() .add_xaxis(xaxis_data=chart_data.x_values) .add_yaxis( series_name=chart_data.name, y_axis=y_line_list, color='black', linestyle_opts=opts.LineStyleOpts(color='black',width=1.5,opacity=1), is_symbol_show = False, ## 不显示连接线的圆点 label_opts=opts.LabelOpts(is_show=False), ) ) elif echart.opts.k_style == K_style.Pnl: y_line_list = [] min_value = chart_data.bars[0].pnl max_value = min_value for bar in chart_data.bars: _v = bar.pnl y_line_list.append(_v) min_value = min(_v,min_value) max_value = max(_v,max_value) main_chart = ( charts.Line() .add_xaxis(xaxis_data=chart_data.x_values) .add_yaxis( series_name="涨幅", y_axis=y_line_list, color='black', linestyle_opts=opts.LineStyleOpts(color='black',width=1.5,opacity=1), is_symbol_show = False, ## 不显示连接线的圆点 label_opts=opts.LabelOpts(is_show=False), ) ) elif echart.opts.k_style == K_style.Candle: y_line_list = chart_data.close_list main_chart = ( charts.Kline() .add_xaxis(xaxis_data=chart_data.x_values) .add_yaxis( series_name="K柱", y_axis=chart_data.candle_list, #splitline_opts=opts.SplitLineOpts(is_show=False, linestyle_opts=opts.LineStyleOpts(opacity=1)), itemstyle_opts=opts.ItemStyleOpts(color="#ec0000", color0="#00da3c"), ) ) else: raise RuntimeError(f"unkonw line stype: {echart.opts.k_style}") datazoom_opts = None min_ = min_value * 0.995 max_ = max_value * 1.005 if scrollable: _range_start = echart.opts.screen_scroll_range_start if _range_start is None: _range_start = int(100 * (1 - echart.opts.screen_bar_size / k_bar_size)) bottom_size = len(echart.bottom().get_all_views()) datazoom_opts = opts.DataZoomOpts( is_show=True, xaxis_index=list(range(0, bottom_size + 1)), type_="slider", # is_show_detail=False, # is_show_data_shadow=False, pos_bottom=f"2px", range_start=_range_start, range_end=100, ) min_ = "dataMin" ### 滚动的时候自适应 max_ = "dataMax" markline_opts:opts.MarkLineOpts = None markpoint_opts:opts.MarkPointOpts = None if chart_data.x_mark_lines: x_mark_line_datas = [] assert len(chart_data.x_mark_lines) == len(chart_data.x_values) for i in range(0,len(chart_data.x_mark_lines)): if chart_data.x_mark_lines[i]: x_mark_line_datas.append(opts.MarkLineItem(name="",x=chart_data.x_values[i])) if x_mark_line_datas: markline_opts = opts.MarkLineOpts( # 标记区域配置项 data=x_mark_line_datas, ) if self.mark_point: x_mark_point_datas = [] for index in self.mark_point: x_mark_point_datas.append( opts.MarkPointItem(name=f"{index}", coord=[chart_data.x_values[index], chart_data.close_list[index]])) if x_mark_point_datas: markpoint_opts = opts.MarkPointOpts(data=x_mark_point_datas,label_opts=opts.LabelOpts(is_show=False)) if markline_opts or markpoint_opts: main_chart.set_series_opts( markline_opts=markline_opts, markpoint_opts=markpoint_opts ) main_chart.set_global_opts( title_opts=opts.TitleOpts(title="", pos_top="100%"), legend_opts=opts.LegendOpts(is_show=True, pos_top=f"{view_opt.top}px", pos_left=f"{view_opt.padding_left}px"), datazoom_opts=datazoom_opts, yaxis_opts=opts.AxisOpts( is_scale=True, splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 min_=min_, max_=max_, position="right" ), tooltip_opts=opts.TooltipOpts( trigger="axis", axis_pointer_type="cross", background_color="rgba(245, 245, 245, 0.8)", border_width=1, border_color="#ccc", formatter=None, textstyle_opts=opts.TextStyleOpts(color="#000"), ), xaxis_opts=opts.AxisOpts( type_="category", is_scale=True, boundary_gap=False, axisline_opts=opts.AxisLineOpts(is_on_zero=False), splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 split_number=10, ), axispointer_opts=opts.AxisPointerOpts( ## 不同轴的鼠标联动 is_show=True, link=[{"xAxisIndex": "all"}], label=opts.LabelOpts(background_color="#777"), ), ) custom_lines = self.lines() custom_chart = custom_lines._build_chart(echart=echart,chart_data=chart_data,view_opt=view_opt,scrollable=scrollable,**kwargs) if custom_chart is None: custom_scatters = self.scatter() custom_chart = custom_scatters._build_chart(echart=echart,chart_data=chart_data,view_opt=view_opt,scrollable=scrollable,**kwargs) if custom_chart is None and y_line_list: ma5_list = [] ma10_list = [] ma30_list = [] def _sum(index:int,period:int): _s = 0 for i in range(index-period+1,index+1): _s += y_line_list[i] return _s for _i,y in enumerate(y_line_list): if _i > 5: ma5_list.append(_sum(_i,5)/5) else: ma5_list.append(None) if _i > 10: ma10_list.append(_sum(_i,10) / 10) else: ma10_list.append(None) if _i > 30: ma30_list.append(_sum(_i,30) / 30) else: ma30_list.append(None) ma_lines = Lines() ma_lines.add_line(Line(name="ma5",values=ma5_list)) ma_lines.add_line(Line(name="ma10",values=ma10_list)) ma_lines.add_line(Line(name="ma30",values=ma30_list)) custom_chart = ma_lines._build_chart(echart=echart, chart_data=chart_data, view_opt=view_opt,scrollable=scrollable, **kwargs) if custom_chart: return main_chart.overlap(custom_chart) return main_chart class _Vol_Chart(_BottomChart): def _build_chart(self,echart:Echart,chart_data:ChartData,view_opt:ViewOpts,scrollable:bool,**kwargs): super_chart = super()._build_chart(echart,chart_data,view_opt,scrollable,**kwargs) volume_list = [int(bar.volume) for bar in chart_data.bars] grid_index = self.get_grid_index() self.title = "成交量" bar_1 = ( charts.Bar() .add_xaxis(xaxis_data=chart_data.x_values) .add_yaxis( series_name=self.title, y_axis=volume_list, xaxis_index=grid_index, yaxis_index=grid_index, label_opts=opts.LabelOpts(is_show=False), itemstyle_opts=opts.ItemStyleOpts( ## bar_data =[[ [open, close, low, high,pre_close] ],[ [open, close, low, high,pre_close] ]] color=JsCode( """ function(params) { var colorList; if (barData[params.dataIndex][1] > barData[params.dataIndex][0]) { colorList = '#ef232a'; } else { colorList = '#14b143'; } return colorList; } """ ) ), ) .set_global_opts( xaxis_opts=opts.AxisOpts( type_="category", grid_index=grid_index, axislabel_opts=opts.LabelOpts(is_show=False), splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 ), yaxis_opts=opts.AxisOpts( is_scale=True, position="right", splitarea_opts=opts.SplitAreaOpts(is_show=False), ## 不显示分割区域 splitline_opts=opts.SplitLineOpts(is_show=False), ## 不显示分割线 ), title_opts=opts.TitleOpts(title=self.title,pos_top=f"{view_opt.top}px"), legend_opts=opts.LegendOpts(is_show=False), ) ) if super_chart: return bar_1.overlap(super_chart) return bar_1 def _builder_echarts_view(echart:Echart,width:Union[str,int] = None,height:int = None)->echartView: main_builder:_MainChart = echart.main chart_data = main_builder.build_chart_data() scrollable = len(chart_data.bars) > echart.opts.screen_bar_size ### 数据多的时候才需要滚动 if echart.opts.mode == Mode.Today: scrollable = False bottom_view_list = echart.bottom().get_all_views() bottom_count = len(bottom_view_list) if height is None: height = 600 if width is None: width_str = "100%" elif isinstance(width,int): width_str = f"{width}px" else: assert isinstance(width,str) width_str = width grid_chart = charts.Grid( init_opts=opts.InitOpts( width=width_str, height=f"{height}px", #theme = "white", #bg_color='yellow', animation_opts=opts.AnimationOpts(animation=False), ) ) # 定义全局js变量,给后续的成交量图变化颜色调用。 ## bar_data =[[ [open, close, low, high,pre_close] ],[ [open, close, low, high,pre_close] ]] bar_data_list = chart_data.candle_list grid_chart.add_js_funcs("var barData = {}".format(bar_data_list)) pos_right = 65 pos_left = 10 main_pos_top = 30 #main_pos_bottom = 30 ## 需要额外的30像素显示底部的文字标签。 main_opts = ViewOpts(top=0, height=0, padding_left=pos_left, padding_right=pos_right, padding_top=main_pos_top, padding_bottom=0,total_height=height) view_height = height - main_opts.top if bottom_count == 0: main_opts.height = int(view_height * 1) ## 整个main高度像素 main_opts.padding_bottom = 35 ## 留给底部文字标签(顺便滚动条) chart_main = main_builder._build_chart(echart=echart,chart_data=chart_data,view_opt=main_opts,scrollable=scrollable) grid_chart.add( chart_main, grid_opts=opts.GridOpts(pos_left=f"{main_opts.padding_left}px", pos_right=f"{main_opts.padding_right}px",pos_top=f"{main_opts.padding_top+main_opts.top}px", height=f"{main_opts.padding_height()}px"), ) elif bottom_count == 1: if scrollable: view_height -= 35 ## 留给底部滚动条 else: view_height -= 10 main_opts.height = int(view_height * 0.65) ## 整个main高度像素 main_opts.padding_bottom = 20 ## 留给底部文字 bottom_height = int(view_height * 0.35) chart_main = main_builder._build_chart(echart=echart, chart_data=chart_data, view_opt=main_opts,scrollable=scrollable) grid_chart.add( chart_main, grid_opts=opts.GridOpts(pos_left=f"{main_opts.padding_left}px", pos_right=f"{main_opts.padding_right}px",pos_top=f"{main_opts.padding_top+main_opts.top}px", height=f"{main_opts.padding_height()}px"), ) bottom_padding = 25 bottom_panel = bottom_view_list[0] bottom_pos_top = main_opts.top + main_opts.height bottom_opts = ViewOpts(top=bottom_pos_top, height=bottom_height, padding_left=pos_left, padding_right=pos_right, padding_top=bottom_padding, padding_bottom=0,total_height=height) chart_bottom = bottom_panel._build_chart(echart=echart, chart_data=chart_data, view_opt=bottom_opts,scrollable=scrollable) if chart_bottom: grid_chart.add( chart_bottom, grid_opts=opts.GridOpts(pos_left=f"{bottom_opts.padding_left}px", pos_right=f"{bottom_opts.padding_right}px", pos_top=f"{bottom_opts.padding_top + bottom_opts.top}px", height=f"{bottom_opts.padding_height()}px"), ) else: if scrollable: view_height -= 35 ## 留给底部滚动条 else: view_height -= 10 main_opts.padding_bottom = 20 ## 留给底部文字 main_opts.height = int(view_height * 0.5) ## 整个main高度像素 bottom_height = int(view_height*0.25) chart_main = main_builder._build_chart(echart=echart, chart_data=chart_data, view_opt=main_opts,scrollable=scrollable) grid_chart.add( chart_main, grid_opts=opts.GridOpts(pos_left=f"{main_opts.padding_left}px", pos_right=f"{main_opts.padding_right}px",pos_top=f"{main_opts.padding_top+main_opts.top}px", height=f"{main_opts.padding_height()}px"), ) bottom_padding = 25 for i in range(0,bottom_count): bottom_panel = bottom_view_list[i] bottom_pos_top = main_opts.top + main_opts.height + i * bottom_height bottom_opts = ViewOpts(top=bottom_pos_top, height=bottom_height, padding_left=pos_left, padding_right=pos_right, padding_top=bottom_padding, padding_bottom=0,total_height=height) chart_bottom = bottom_panel._build_chart(echart=echart, chart_data=chart_data, view_opt=bottom_opts, scrollable=scrollable) if chart_bottom: grid_chart.add( chart_bottom, grid_opts=opts.GridOpts(pos_left=f"{bottom_opts.padding_left}px", pos_right=f"{bottom_opts.padding_right}px", pos_top=f"{bottom_opts.padding_top + bottom_opts.top}px", height=f"{bottom_opts.padding_height()}px"), ) return grid_chart