"""
为了简单使用,封装 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