pyecharts 源码分析-直角坐标系图表(源码+解析)

49 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

目录

1、在从pyecharts.charts导入Bar:

2、其实,直角坐标系下所有的图表都继承了 RectChart 类。

3、RectChart 类 继承了 Chart 类,我们再来看 Chart 类,惊喜在最后~


我们以Bar为例:

from pyecharts.charts import Bar

1、在从pyecharts.charts导入Bar:

"""
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:732481539
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
"""

class Bar(RectChart):
    """
    <<< Bar Chart >>>

    Bar chart presents categorical data with rectangular bars
    with heights or lengths proportional to the values that they represent.
    """

    def add_yaxis(
    # 系列名称,用于 tooltip 的显示,legend 的图例筛选。
    series_name: str,

    # 系列数据
    y_axis: Sequence[Numeric, opts.BarItem, dict],

    # 是否选中图例
    is_selected: bool = True,

    # 使用的 x 轴的 index,在单个图表实例中存在多个 x 轴的时候有用。
    xaxis_index: Optional[Numeric] = None,

    # 使用的 y 轴的 index,在单个图表实例中存在多个 y 轴的时候有用。
    yaxis_index: Optional[Numeric] = None,

    # 是否启用图例 hover 时的联动高亮
    is_legend_hover_link: bool = True,

    # 系列 label 颜色
    color: Optional[str] = None,

    # 是否显示柱条的背景色。通过 backgroundStyle 配置背景样式。
    is_show_background: bool = False,

    # 每一个柱条的背景样式。需要将 showBackground 设置为 true 时才有效。
    background_style: types.Union[types.BarBackground, dict, None] = None,

    # 数据堆叠,同个类目轴上系列配置相同的 stack 值可以堆叠放置。
    stack: Optional[str] = None,

    # 柱条的宽度,不设时自适应。
    # 可以是绝对值例如 40 或者百分数例如 '60%'。百分数基于自动计算出的每一类目的宽度。
    # 在同一坐标系上,此属性会被多个 'bar' 系列共享。此属性应设置于此坐标系中最后一个 'bar' 系列上才会生效,并且是对此坐标系中所有 'bar' 系列生效。
    bar_width: types.Union[types.Numeric, str] = None,

    # 柱条的最大宽度。比 barWidth 优先级高。
    bar_max_width: types.Union[types.Numeric, str] = None,

    # 柱条的最小宽度。在直角坐标系中,默认值是 1。否则默认值是 null。比 barWidth 优先级高。
    bar_min_width: types.Union[types.Numeric, str] = None,

    # 柱条最小高度,可用于防止某数据项的值过小而影响交互。
    bar_min_height: types.Numeric = 0,

    # 同一系列的柱间距离,默认为类目间距的 20%,可设固定值
    category_gap: Union[Numeric, str] = "20%",

    # 不同系列的柱间距离,为百分比(如 '30%',表示柱子宽度的 30%)。
    # 如果想要两个系列的柱子重叠,可以设置 gap 为 '-100%'。这在用柱子做背景的时候有用。
    gap: Optional[str] = "30%",

    # 是否开启大数据量优化,在数据图形特别多而出现卡顿时候可以开启。
    # 开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。
    # 缺点:优化后不能自定义设置单个数据项的样式。
    is_large: bool = False,

    # 开启绘制优化的阈值。
    large_threshold: types.Numeric = 400,

    # 使用 dimensions 定义 series.data 或者 dataset.source 的每个维度的信息。
    # 注意:如果使用了 dataset,那么可以在 dataset.source 的第一行/列中给出 dimension 名称。
    # 于是就不用在这里指定 dimension。
    # 但是,如果在这里指定了 dimensions,那么 ECharts 不再会自动从 dataset.source 的第一行/列中获取维度信息。
    dimensions: types.Union[types.Sequence, None] = None,

    # 当使用 dataset 时,seriesLayoutBy 指定了 dataset 中用行还是列对应到系列上,也就是说,系列“排布”到 dataset 的行还是列上。可取值:
    # 'column':默认,dataset 的列对应于系列,从而 dataset 中每一列是一个维度(dimension)。
    # 'row':dataset 的行对应于系列,从而 dataset 中每一行是一个维度(dimension)。
    series_layout_by: str = "column",

    # 如果 series.data 没有指定,并且 dataset 存在,那么就会使用 dataset。
    # datasetIndex 指定本系列使用那个 dataset。
    dataset_index: types.Numeric = 0,

    # 是否裁剪超出坐标系部分的图形。柱状图:裁掉所有超出坐标系的部分,但是依然保留柱子的宽度
    is_clip: bool = True,

    # 柱状图所有图形的 zlevel 值。
    z_level: types.Numeric = 0,

    # 柱状图组件的所有图形的z值。控制图形的前后顺序。
    # z值小的图形会被z值大的图形覆盖。
    # z相比zlevel优先级更低,而且不会创建新的 Canvas。
    z: types.Numeric = 2,

    # 标签配置项,参考 `series_options.LabelOpts`
    label_opts: Union[opts.LabelOpts, dict] = opts.LabelOpts(),

    # 标记点配置项,参考 `series_options.MarkPointOpts`
    markpoint_opts: Union[opts.MarkPointOpts, dict, None] = None,

    # 标记线配置项,参考 `series_options.MarkLineOpts`
    markline_opts: Union[opts.MarkLineOpts, dict, None] = None,

    # 提示框组件配置项,参考 `series_options.TooltipOpts`
    tooltip_opts: Union[opts.TooltipOpts, dict, None] = None,

    # 图元样式配置项,参考 `series_options.ItemStyleOpts`
    itemstyle_opts: Union[opts.ItemStyleOpts, dict, None] = None,

    # 可以定义 data 的哪个维度被编码成什么。
    encode: types.Union[types.JSFunc, dict, None] = None,
)

我们发现Bar继承了

RectChart 类

2、其实,直角坐标系下所有的图表都继承了 RectChart 类。

我们再来看  RectChart 类

class RectChart(Chart):
    def __init__(self, init_opts: types.Init = opts.InitOpts()):
        super().__init__(init_opts=init_opts)
        self.options.update(xAxis=[opts.AxisOpts().opts], yAxis=[opts.AxisOpts().opts])
    # 扩展x/y轴
    def extend_axis(
        self,
            # 扩展 X 坐标轴数据项
            xaxis_data: Sequence = None,

            # 扩展 X 坐标轴配置项,参考 `global_options.AxisOpts`
            xaxis: Union[opts.AxisOpts, dict, None] = None,

            # 新增 Y 坐标轴配置项,参考 `global_options.AxisOpts`
            yaxis: Union[opts.AxisOpts, dict, None] = None,
    ):
        if xaxis is not None:
            if isinstance(xaxis, opts.AxisOpts):
                xaxis = xaxis.opts
            xaxis.update(data=xaxis_data)
            self.options["xAxis"].append(xaxis)
        if yaxis is not None:
            if isinstance(yaxis, opts.AxisOpts):
                yaxis = yaxis.opts
            self.options["yAxis"].append(yaxis)
        return self

    # 新增x轴数据
    def add_xaxis(self, xaxis_data: Sequence):
        self.options["xAxis"][0].update(data=xaxis_data)
        # X 轴数据项
        self._xaxis_data = xaxis_data
        return self
    # 翻转xy轴数据
    def reversal_axis(self):
        self.options["yAxis"][0]["data"] = self._xaxis_data
        self.options["xAxis"][0]["data"] = None
        return self

    # 层叠多图 chart为直角坐标系类型图表
    def overlap(self, chart: Base):
        self.options.get("legend")[0].get("data").extend(
            chart.options.get("legend")[0].get("data")
        )
        self.options.get("legend")[0].get("selected").update(
            chart.options.get("legend")[0].get("selected")
        )
        self.options.get("series").extend(chart.options.get("series"))
        return self

3、RectChart 类 继承了 Chart 类,我们再来看 Chart 类,惊喜在最后~

class Chart(Base):
    def __init__(self, init_opts: types.Init = opts.InitOpts()):
        if isinstance(init_opts, dict):
            temp_opts = opts.InitOpts()
            temp_opts.update(**init_opts)
            init_opts = temp_opts
        super().__init__(init_opts=init_opts)
        self.colors = (
            "#c23531 #2f4554 #61a0a8 #d48265 #749f83 #ca8622 #bda29a #6e7074 "
            "#546570 #c4ccd3 #f05b72 #ef5b9c #f47920 #905a3d #fab27b #2a5caa "
            "#444693 #726930 #b2d235 #6d8346 #ac6767 #1d953f #6950a1 #918597"
        ).split()
        if init_opts.opts.get("theme") == ThemeType.WHITE:
            self.options.update(color=self.colors)
        self.options.update(
            series=[],
            legend=[{"data": [], "selected": dict()}],
            tooltip=opts.TooltipOpts().opts,
        )
        self._chart_type: Optional[str] = None

    def set_colors(self, colors: Sequence[str]):
        self.options.update(color=colors)
        return self

    def set_series_opts(
            self,
            label_opts: types.Label = None,
            linestyle_opts: types.LineStyle = None,
            splitline_opts: types.SplitLine = None,
            areastyle_opts: types.AreaStyle = None,
            axisline_opts: types.AxisLine = None,
            markpoint_opts: types.MarkPoint = None,
            markline_opts: types.MarkLine = None,
            markarea_opts: types.MarkArea = None,
            effect_opts: types.Effect = opts.EffectOpts(),
            tooltip_opts: types.Tooltip = None,
            itemstyle_opts: types.ItemStyle = None,
            **kwargs,
    ):
        for s in self.options.get("series"):
            if label_opts:
                s.update(label=label_opts)

            if linestyle_opts:
                s.update(lineStyle=linestyle_opts)

            if splitline_opts:
                s.update(splitLine=splitline_opts)

            if areastyle_opts:
                s.update(areaStyle=areastyle_opts)

            if axisline_opts:
                s.update(axisLine=axisline_opts)

            if markpoint_opts:
                s.update(markPoint=markpoint_opts)

            if markline_opts:
                s.update(markLine=markline_opts)

            if markarea_opts:
                s.update(markArea=markarea_opts)

            if effect_opts:
                s.update(rippleEffect=effect_opts)

            if tooltip_opts:
                s.update(tooltip=tooltip_opts)

            if itemstyle_opts:
                s.update(itemStyle=itemstyle_opts)

            if len(kwargs) > 0:
                s.update(kwargs)

        return self

    def _append_legend(self, name, is_selected):
        self.options.get("legend")[0].get("data").append(name)
        self.options.get("legend")[0].get("selected").update({name: is_selected})

    def _append_color(self, color: Optional[str]):
        if color:
            self.colors = [color] + self.colors
            if self.theme == ThemeType.WHITE:
                self.options.update(color=self.colors)

    def set_global_opts(
            self,
            title_opts: types.Title = opts.TitleOpts(),
            legend_opts: types.Legend = opts.LegendOpts(),
            tooltip_opts: types.Tooltip = None,
            toolbox_opts: types.Toolbox = None,
            brush_opts: types.Brush = None,
            xaxis_opts: types.Axis = None,
            yaxis_opts: types.Axis = None,
            visualmap_opts: types.VisualMap = None,
            datazoom_opts: types.DataZoom = None,
            graphic_opts: types.Graphic = None,
            axispointer_opts: types.AxisPointer = None,
    ):
        if tooltip_opts is None:
            tooltip_opts = opts.TooltipOpts(
                formatter=ToolTipFormatterType.get(self._chart_type, None)
            )
        self.options.update(
            title=title_opts,
            toolbox=toolbox_opts,
            tooltip=tooltip_opts,
            visualMap=visualmap_opts,
            dataZoom=datazoom_opts,
            graphic=graphic_opts,
            axisPointer=axispointer_opts,
        )

        if brush_opts is not None:
            self.options.update(brush=brush_opts)

        if isinstance(legend_opts, opts.LegendOpts):
            legend_opts = legend_opts.opts
        for _s in self.options["legend"]:
            _s.update(legend_opts)

        if xaxis_opts and self.options.get("xAxis", None):
            if isinstance(xaxis_opts, opts.AxisOpts):
                xaxis_opts = xaxis_opts.opts
            self.options["xAxis"][0].update(xaxis_opts)

        if yaxis_opts and self.options.get("yAxis", None):
            if isinstance(yaxis_opts, opts.AxisOpts):
                yaxis_opts = yaxis_opts.opts
            self.options["yAxis"][0].update(yaxis_opts)

        return self

    def add_dataset(
            self,
            # 原始数据。一般来说,原始数据表达的是二维表。
            source: types.Union[types.Sequence, types.JSFunc] = None,

            # 使用 dimensions 定义 series.data 或者 dataset.source 的每个维度的信息。
            dimensions: types.Optional[types.Sequence] = None,

            # dataset.source 第一行/列是否是 维度名 信息。可选值:
            # null/undefine(对应 Python 的 None 值):默认,自动探测。
            # true:第一行/列是维度名信息。
            # false:第一行/列直接开始是数据。
            source_header: types.Optional[bool] = None,
    ):
        self.options.update(
            dataset={
                "source": source,
                "dimensions": dimensions,
                "sourceHeader": source_header,
            }
        )
        return self

我们又发现 每个class都返回为self,这也就是为什么python支持链式调用的原因了。