File: test.py

import os

# nose2
os.system(
    "nose2 --with-coverage --coverage pyecharts "
    "--coverage-config .coveragerc -s test"
)

# pytest
os.system("pytest -cov-config=.coveragerc --cov=./ test/")

# flake8 for code linting
os.system("flake8 --exclude=build,example,images --max-line-length=89 --ignore=F401")



File: setup.py

import os
import sys
from shutil import rmtree

from setuptools import Command, find_packages, setup

# RELEASE STEPS
# $ python setup.py upload


__title__ = "pyecharts"
__description__ = "Python options, make charting easier"
__url__ = "https://github.com/pyecharts/pyecharts"
__author_email__ = "chenjiandongx@qq.com"
__license__ = "MIT"

__requires__ = ["jinja2", "prettytable", "simplejson"]
__extra_requires__ = {
    "selenium": ["snapshot-selenium"],
    "phantomjs": ["snapshot-phantomjs"],
    "pyppeteer": ["snapshot-pyppeteer"],
    "images": ["PIL"],
}

__keywords__ = ["Echarts", "charts", "plotting-tool"]
# Load the package's _version.py module as a dictionary.
here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, __title__, "_version.py")) as f:
    exec(f.read(), about)


__version__ = about["__version__"]


class UploadCommand(Command):
    description = "Build and publish the package."
    user_options = []

    @staticmethod
    def status(s):
        print("✨✨ {0}".format(s))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        try:
            self.status("Removing previous builds…")
            rmtree(os.path.join(here, "dist"))
            rmtree(os.path.join(here, "build"))
            rmtree(os.path.join(here, "{0}.egg-info".format(__title__)))
        except OSError:
            pass

        self.status("Building Source and Wheel distribution…")
        os.system("{0} setup.py sdist bdist_wheel".format(sys.executable))

        self.status("Uploading the package to PyPI via Twine…")
        os.system("twine upload dist/*")

        self.status("Pushing git tags…")
        os.system('git tag -a v{0} -m "release version v{0}"'.format(__version__))
        os.system("git push origin v{0}".format(__version__))

        sys.exit()


setup(
    name=__title__,
    version=__version__,
    description=__description__,
    url=__url__,
    author=about["__author__"],
    author_email=__author_email__,
    license=__license__,
    packages=find_packages(exclude=("test",)),
    keywords=__keywords__,
    install_requires=__requires__,
    zip_safe=False,
    include_package_data=True,
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        "Environment :: Console",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3.6",
        "Programming Language :: Python :: 3.7",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Topic :: Software Development :: Libraries",
    ],
    cmdclass={"upload": UploadCommand},
    extras_require=__extra_requires__,
)



File: install.py

import os

here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, "pyecharts", "_version.py")) as f:
    exec(f.read(), about)

UNINSTALL = "{} uninstall pyecharts -y"
INSTALL = "{} install -U dist/pyecharts-{}-py3-none-any.whl --no-cache-dir"

os.system("python setup.py bdist_wheel")
os.system(UNINSTALL.format("pip"))
os.system(UNINSTALL.format("pip3"))
os.system(INSTALL.format("pip", about["__version__"]))
os.system(INSTALL.format("pip3", about["__version__"]))



File: pyecharts/_version.py

__version__ = "2.0.6"
__author__ = "chenjiandongx"



File: pyecharts/globals.py

import os

from jinja2 import Environment, FileSystemLoader

from pyecharts.commons.utils import JsCode


class _RenderType:
    CANVAS: str = "canvas"
    SVG: str = "svg"


class _FileType:
    SVG: str = "svg"
    PNG: str = "png"
    JPEG: str = "jpeg"
    HTML: str = "html"


class _SymbolType:
    RECT: str = "rect"
    ROUND_RECT: str = "roundRect"
    TRIANGLE: str = "triangle"
    DIAMOND: str = "diamond"
    ARROW: str = "arrow"


class _ChartType:
    BAR: str = "bar"
    BAR3D: str = "bar3D"
    BOXPLOT: str = "boxplot"
    EFFECT_SCATTER: str = "effectScatter"
    FUNNEL: str = "funnel"
    FLOWGL: str = "flowGL"
    GAUGE: str = "gauge"
    GEO: str = "geo"
    GRAPH: str = "graph"
    GRAPHGL: str = "graphGL"
    HEATMAP: str = "heatmap"
    KLINE: str = "candlestick"
    LINE: str = "line"
    LINE3D: str = "line3D"
    LINES: str = "lines"
    LINESGL: str = "linesGL"
    LINES3D: str = "lines3D"
    LIQUID: str = "liquidFill"
    MAP: str = "map"
    MAP3D: str = "map3D"
    PARALLEL: str = "parallel"
    PICTORIALBAR: str = "pictorialBar"
    PIE: str = "pie"
    POLAR: str = "polar"
    RADAR: str = "radar"
    SANKEY: str = "sankey"
    SCATTER: str = "scatter"
    SCATTER3D: str = "scatter3D"
    SCATTERGL: str = "scatterGL"
    SUNBURST: str = "sunburst"
    SURFACE: str = "surface"
    THEMERIVER: str = "themeRiver"
    TREE: str = "tree"
    TREEMAP: str = "treemap"
    WORDCLOUD: str = "wordCloud"
    CUSTOM: str = "custom"


ToolTipFormatterType = {
    _ChartType.GEO: JsCode(
        """function (params) {
        return params.name + ' : ' + params.value[2];
    }"""
    ),
    _ChartType.GAUGE: "{a} <br/>{b} : {c}%",
}


class _ThemeType:
    BUILTIN_THEMES = ["light", "dark", "white"]
    LIGHT = "light"
    DARK = "dark"
    WHITE = "white"
    CHALK: str = "chalk"
    ESSOS: str = "essos"
    INFOGRAPHIC: str = "infographic"
    MACARONS: str = "macarons"
    PURPLE_PASSION: str = "purple-passion"
    ROMA: str = "roma"
    ROMANTIC: str = "romantic"
    SHINE: str = "shine"
    VINTAGE: str = "vintage"
    WALDEN: str = "walden"
    WESTEROS: str = "westeros"
    WONDERLAND: str = "wonderland"
    HALLOWEEN: str = "halloween"


class _GeoType:
    SCATTER: str = "scatter"
    EFFECT_SCATTER: str = "effectScatter"
    HEATMAP: str = "heatmap"
    LINES: str = "lines"


class _BMapType:
    # BMap Control location
    ANCHOR_TOP_LEFT = 0
    ANCHOR_TOP_RIGHT = 1
    ANCHOR_BOTTOM_LEFT = 2
    ANCHOR_BOTTOM_RIGHT = 3

    # BMap Navigation Control Type
    NAVIGATION_CONTROL_LARGE = 0
    NAVIGATION_CONTROL_SMALL = 1
    NAVIGATION_CONTROL_PAN = 2
    NAVIGATION_CONTROL_ZOOM = 3

    # BMap Maptype Control Type
    MAPTYPE_CONTROL_HORIZONTAL = 0
    MAPTYPE_CONTROL_DROPDOWN = 1
    MAPTYPE_CONTROL_MAP = 2


class _NotebookType:
    JUPYTER_NOTEBOOK = "jupyter_notebook"
    JUPYTER_LAB = "jupyter_lab"
    NTERACT = "nteract"
    ZEPPELIN = "zeppelin"


class _OnlineHost:
    DEFAULT_HOST = "https://assets.pyecharts.org/assets/v5/"
    NOTEBOOK_HOST = "http://localhost:8888/nbextensions/assets/"


class _RenderSepType:
    SepType = os.linesep


RenderType = _RenderType()
FileType = _FileType()
SymbolType = _SymbolType()
ChartType = _ChartType
ThemeType = _ThemeType()
GeoType = _GeoType()
BMapType = _BMapType
NotebookType = _NotebookType()
OnlineHostType = _OnlineHost()
RenderSepType = _RenderSepType()


class _CurrentConfig:
    PAGE_TITLE = "Awesome-pyecharts"
    ONLINE_HOST = OnlineHostType.DEFAULT_HOST
    NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK
    GLOBAL_ENV = Environment(
        keep_trailing_newline=True,
        trim_blocks=True,
        lstrip_blocks=True,
        loader=FileSystemLoader(
            os.path.join(
                os.path.abspath(os.path.dirname(__file__)), "render", "templates"
            )
        ),
    )


CurrentConfig = _CurrentConfig()



File: pyecharts/__init__.py

from pyecharts import charts, commons, components, datasets, options, render, scaffold
from pyecharts._version import __author__, __version__



File: pyecharts/types.py

from typing import (
    Any,
    Callable,
    Iterable,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Union,
)

from . import options as opts
from .options.charts_options import BaseGraphic, GlobeLayersOpts
from .options.series_options import JsCode, JSFunc, Numeric

Init = Union[opts.InitOpts, dict]
RenderInit = Union[opts.RenderOpts, dict]

Axis = Union[opts.AxisOpts, dict, None]
Axis3D = Union[opts.Axis3DOpts, dict]
AxisLine = Union[opts.AxisLineOpts, dict, None]
AxisLabel = Union[opts.LabelOpts, dict, None]
AxisPointer = Union[opts.AxisPointerOpts, dict, None]
AxisTick = Union[opts.AxisTickOpts, dict, None]
AreaStyle = Union[opts.AreaStyleOpts, dict, None]
BarBackground = Union[opts.BarBackgroundStyleOpts, dict, None]
Blur = Union[opts.BlurOpts, dict, None]
Brush = Union[opts.BrushOpts, dict, None]
_DataZoomType = Union[opts.DataZoomOpts, dict]
DataZoom = Union[_DataZoomType, Sequence[_DataZoomType], None]
Effect = Union[opts.EffectOpts, dict, None]
Emphasis = Union[opts.EmphasisOpts, dict, None]
Emphasis3D = Union[opts.Emphasis3DOpts, dict, None]
GaugeAnchor = Union[opts.GaugeAnchorOpts, dict, None]
GaugeTitle = Union[opts.GaugeTitleOpts, dict, None]
GaugeDetail = Union[opts.GaugeDetailOpts, dict, None]
GaugePointer = Union[opts.GaugePointerOpts, dict, None]
GaugeProgress = Union[opts.GaugeProgressOpts, dict, None]
GeoRegions = Union[opts.GeoRegionsOpts, dict, None]
_GraphicType = Union[BaseGraphic, dict]
Graphic = Union[_GraphicType, Sequence[_GraphicType], None]
GraphGLForceAtlas2 = Union[opts.GraphGLForceAtlas2Opts, dict, None]
_GlobeLayersType = Union[GlobeLayersOpts, dict]
GlobeLayers = Union[_GlobeLayersType, Sequence[_GlobeLayersType], None]
ItemStyle = Union[opts.ItemStyleOpts, dict, None]
Map3DColorMaterial = Union[opts.Map3DColorMaterialOpts, dict, None]
Map3DLabel = Union[opts.Map3DLabelOpts, dict, None]
Map3DLight = Union[opts.Map3DLightOpts, dict, None]
Map3DLambertMaterial = Union[opts.Map3DLambertMaterialOpts, dict, None]
Map3DPostEffect = Union[opts.Map3DPostEffectOpts, dict, None]
Map3DRealisticMaterial = Union[opts.Map3DRealisticMaterialOpts, dict, None]
Map3DViewControl = Union[opts.Map3DViewControlOpts, dict, None]
MarkArea = Union[opts.MarkAreaOpts, dict, None]
MarkPoint = Union[opts.MarkPointOpts, dict, None]
MarkLine = Union[opts.MarkLineOpts, dict, None]
MinorTick = Union[opts.MinorTickOpts, dict, None]
Label = Union[opts.LabelOpts, dict, None]
Legend = Union[opts.LegendOpts, dict]
LineStyle = Union[opts.LineStyleOpts, dict, None]
Lines3DEffect = Union[opts.Lines3DEffectOpts, dict, None]
PieLabelLine = Union[opts.PieLabelLineOpts, dict, None]
PieEmptyCircle = Union[opts.PieEmptyCircleStyle, dict, None]
TextStyle = Union[opts.TextStyleOpts, dict, None]
TimeLineControl = Union[opts.TimelineControlStyle, dict, None]
TimeLinkCheckPoint = Union[opts.TimelineCheckPointerStyle, dict, None]
Title = Union[opts.TitleOpts, dict]
Tooltip = Union[opts.TooltipOpts, dict, None]
Toolbox = Union[opts.ToolboxOpts, dict]
TreeMapBreadcrumb = Union[opts.TreeMapBreadcrumbOpts, dict, None]
Select = Union[opts.SelectOpts, dict, None]
SplitLine = Union[opts.SplitLineOpts, dict, None]
SplitArea = Union[opts.SplitAreaOpts, dict, None]
SingleAxis = Union[opts.SingleAxisOpts, dict, None]
SunburstLabelLayout = Union[opts.SunburstLabelLayoutOpts, dict, None]
SunburstLabelLine = Union[opts.SunburstLabelLineOpts, dict, None]
SunburstLevelOpts = Union[opts.SunburstLevelOpts, dict, None]
_VisualMapType = Union[opts.VisualMapOpts, dict]
VisualMap = Union[_VisualMapType, Sequence[_VisualMapType], None]

Calendar = Union[opts.CalendarOpts, dict, None]
CalendarDayLabelOpts = Union[opts.CalendarDayLabelOpts, dict, None]
CalendarMonthLabelOpts = Union[opts.CalendarMonthLabelOpts, dict, None]
CalendarYearLabelOpts = Union[opts.CalendarYearLabelOpts, dict, None]

GraphNode = Union[opts.GraphNode, dict]
GraphLink = Union[opts.GraphLink, dict]
GraphCategory = Union[opts.GraphCategory, dict]
GraphGLNode = Union[opts.GraphGLNode, dict]
GraphGLLink = Union[opts.GraphGLLink, dict]
Grid3D = Union[opts.Grid3DOpts, dict]

RadiusAxis = Union[opts.RadiusAxisOpts, dict]
AngleAxis = Union[opts.AngleAxisOpts, dict]

BMapOverviewMapControl = Union[opts.BMapOverviewMapControlOpts, dict, None]
BMapNavigationControl = Union[opts.BMapNavigationControlOpts, dict, None]
BMapScaleControl = Union[opts.BMapScaleControlOpts, dict, None]
BMapTypeControl = Union[opts.BMapTypeControlOpts, dict, None]
BMapCopyrightType = Union[opts.BMapCopyrightTypeOpts, dict, None]
BMapGeoLocationControl = Union[opts.BMapGeoLocationControlOpts, dict, None]

_SankeyLevelType = Union[opts.SankeyLevelsOpts, dict]
SankeyLevel = Union[Sequence[_SankeyLevelType], None]

TreeLeavesOpts = Union[opts.TreeLeavesOpts, dict, None]

TreeMapItemStyleOpts = Union[opts.TreeMapItemStyleOpts, dict, None]
_TreeMapLevelType = Union[opts.TreeMapLevelsOpts, dict]
TreeMapLevel = Union[_TreeMapLevelType, Sequence[_TreeMapLevelType], None]

Polar = Union[opts.PolarOpts, dict, None]



File: pyecharts/faker.py

import os
import random


class _Faker:
    clothes = ["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"]
    drinks = ["可乐", "雪碧", "橙汁", "绿茶", "奶茶", "百威", "青岛"]
    phones = ["小米", "三星", "华为", "苹果", "魅族", "VIVO", "OPPO"]
    fruits = ["草莓", "芒果", "葡萄", "雪梨", "西瓜", "柠檬", "车厘子"]
    animal = ["河马", "蟒蛇", "老虎", "大象", "兔子", "熊猫", "狮子"]
    cars = ["宝马", "法拉利", "奔驰", "奥迪", "大众", "丰田", "特斯拉"]
    dogs = ["哈士奇", "萨摩耶", "泰迪", "金毛", "牧羊犬", "吉娃娃", "柯基"]
    week = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
    week_en = "Saturday Friday Thursday Wednesday Tuesday Monday Sunday".split()
    clock = (
        "12a 1a 2a 3a 4a 5a 6a 7a 8a 9a 10a 11a 12p "
        "1p 2p 3p 4p 5p 6p 7p 8p 9p 10p 11p".split()
    )
    visual_color = [
        "#313695",
        "#4575b4",
        "#74add1",
        "#abd9e9",
        "#e0f3f8",
        "#ffffbf",
        "#fee090",
        "#fdae61",
        "#f46d43",
        "#d73027",
        "#a50026",
    ]
    months = ["{}月".format(i) for i in range(1, 13)]
    provinces = ["广东省", "北京市", "上海市", "江西省", "湖南省", "浙江省", "江苏省"]
    guangdong_city = ["汕头市", "汕尾市", "揭阳市", "阳江市", "肇庆市", "广州市", "惠州市"]
    country = [
        "China",
        "Canada",
        "Brazil",
        "Russia",
        "United States",
        "Africa",
        "Germany",
    ]
    days_attrs = ["{}天".format(i) for i in range(30)]
    days_values = [random.randint(1, 30) for _ in range(30)]

    def choose(self) -> list:
        return random.choice(
            [
                self.clothes,
                self.drinks,
                self.phones,
                self.fruits,
                self.animal,
                self.dogs,
                self.week,
            ]
        )

    @staticmethod
    def values(start: int = 20, end: int = 150) -> list:
        return [random.randint(start, end) for _ in range(7)]

    @staticmethod
    def rand_color() -> str:
        return random.choice(
            [
                "#c23531",
                "#2f4554",
                "#61a0a8",
                "#d48265",
                "#749f83",
                "#ca8622",
                "#bda29a",
                "#6e7074",
                "#546570",
                "#c4ccd3",
                "#f05b72",
                "#444693",
                "#726930",
                "#b2d235",
                "#6d8346",
                "#ac6767",
                "#1d953f",
                "#6950a1",
            ]
        )

    @staticmethod
    def img_path(path: str, prefix: str = "images") -> str:
        return os.path.join(prefix, path)


Faker = _Faker()


class Collector:
    charts = []

    @staticmethod
    def funcs(fn):
        Collector.charts.append((fn, fn.__name__))



File: pyecharts/exceptions.py

class NonexistentCoordinatesException(Exception):
    def __init__(self, error_msg, coord_name):
        self._error_msg = error_msg
        self._coord_name = coord_name

    def __str__(self):
        return f"当前地点: {self._coord_name} 坐标不存在, 错误原因: {self._error_msg}"


class WordCloudMaskImageException(Exception):
    def __init__(self, data):
        self._data = data

    def __str__(self):
        return f"错误原因: 图片无法加载或者不是一个正确的路径, 数据: {self._data}"



File: pyecharts/options/global_options.py

from ..globals import CurrentConfig, RenderType, ThemeType
from ..options.series_options import (
    BasicOpts,
    AreaStyleOpts,
    ItemStyleOpts,
    JSFunc,
    LabelOpts,
    LineStyleOpts,
    Numeric,
    MinorTickOpts,
    MinorSplitLineOpts,
    Optional,
    Sequence,
    SplitAreaOpts,
    SplitLineOpts,
    TextStyleOpts,
    Union,
)


class AnimationOpts(BasicOpts):
    def __init__(
        self,
        animation: bool = True,
        animation_threshold: Numeric = 2000,
        animation_duration: Union[Numeric, JSFunc] = 1000,
        animation_easing: Union[str] = "cubicOut",
        animation_delay: Union[Numeric, JSFunc] = 0,
        animation_duration_update: Union[Numeric, JSFunc] = 300,
        animation_easing_update: Union[Numeric] = "cubicOut",
        animation_delay_update: Union[Numeric, JSFunc] = 0,
    ):
        self.opts: dict = {
            "animation": animation,
            "animationThreshold": animation_threshold,
            "animationDuration": animation_duration,
            "animationEasing": animation_easing,
            "animationDelay": animation_delay,
            "animationDurationUpdate": animation_duration_update,
            "animationEasingUpdate": animation_easing_update,
            "animationDelayUpdate": animation_delay_update,
        }


class AriaLabelOpts(BasicOpts):
    def __init__(
        self,
        is_enable: bool = True,
        description: Optional[str] = None,
        general_with_title: str = "这是一个关于“{title}”的图表。",
        general_without_title: str = "这是一个图表，",
        series_max_count: int = 10,
        series_single_prefix: str = "",
        series_single_with_name: str = "图表类型是{seriesType}，表示{seriesName}。",
        series_single_without_name: str = "图表类型是{seriesType}。",
        series_multiple_prefix: str = "它由{seriesCount}个图表系列组成。",
        series_multiple_with_name: str = "图表类型是{seriesType}，表示{seriesName}。",
        series_multiple_without_name: str = "图表类型是{seriesType}。",
        series_multiple_separator_middle: str = "；",
        series_multiple_separator_end: str = "。",
        data_max_count: int = 10,
        data_all_data: str = "其数据是——",
        data_partial_data: str = "其中，前{displayCnt}项是——",
        data_with_name: str = "{name}的数据是{value}",
        data_without_name: str = "{value}",
        data_separator_middle: str = "，",
        data_separator_end: str = "",
    ):
        self.opts: dict = {
            "enabled": is_enable,
            "description": description,
            "general": {
                "withTitle": general_with_title,
                "withoutTitle": general_without_title,
            },
            "series": {
                "maxCount": series_max_count,
                "single": {
                    "prefix": series_single_prefix,
                    "withName": series_single_with_name,
                    "withoutName": series_single_without_name,
                },
                "multiple": {
                    "prefix": series_multiple_prefix,
                    "withName": series_multiple_with_name,
                    "withoutName": series_multiple_without_name,
                    "separator": {
                        "middle": series_multiple_separator_middle,
                        "end": series_multiple_separator_end,
                    },
                },
            },
            "data": {
                "maxCount": data_max_count,
                "allData": data_all_data,
                "partialData": data_partial_data,
                "withName": data_with_name,
                "withoutName": data_without_name,
                "separator": {
                    "middle": data_separator_middle,
                    "end": data_separator_end,
                },
            },
        }


class AriaDecalOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        decals_symbol: Union[str, Sequence] = "rect",
        decals_symbol_size: Numeric = 1,
        decals_symbol_keep_aspect: bool = True,
        decals_color: str = "rgba(0, 0, 0, 0.2)",
        decals_background_color: Optional[str] = None,
        decals_dash_array_x: Union[Numeric, Sequence] = 5,
        decals_dash_array_y: Union[Numeric, Sequence] = 5,
        decals_rotation: Numeric = 0,
        decals_max_tile_width: Numeric = 512,
        decals_max_tile_height: Numeric = 512,
    ):
        self.opts: dict = {
            "show": is_show,
            "decals": {
                "symbol": decals_symbol,
                "symbolSize": decals_symbol_size,
                "symbolKeepAspect": decals_symbol_keep_aspect,
                "color": decals_color,
                "backgroundColor": decals_background_color,
                "dashArrayX": decals_dash_array_x,
                "dashArrayY": decals_dash_array_y,
                "rotation": decals_rotation,
                "maxTileWidth": decals_max_tile_width,
                "maxTileHeight": decals_max_tile_height,
            },
        }


class AriaOpts(BasicOpts):
    def __init__(
        self,
        is_enable: bool = False,
        aria_label_opts: Optional[AriaLabelOpts] = None,
        aria_decal_opts: Optional[AriaDecalOpts] = None,
    ):
        self.opts: dict = {
            "enabled": is_enable,
            "label": aria_label_opts,
            "decal": aria_decal_opts,
        }


class InitOpts(BasicOpts):
    def __init__(
        self,
        width: str = "900px",
        height: str = "500px",
        is_horizontal_center: bool = False,
        chart_id: Optional[str] = None,
        renderer: str = RenderType.CANVAS,
        page_title: str = CurrentConfig.PAGE_TITLE,
        theme: str = ThemeType.WHITE,
        bg_color: Union[str, dict] = None,
        is_fill_bg_color: bool = False,
        js_host: str = "",
        animation_opts: Union[AnimationOpts, dict] = AnimationOpts(),
        aria_opts: Union[AriaOpts, dict] = AriaOpts(),
    ):
        self.opts: dict = {
            "width": width,
            "height": height,
            "is_horizontal_center": is_horizontal_center,
            "chart_id": chart_id,
            "renderer": renderer,
            "page_title": page_title,
            "theme": theme,
            "bg_color": bg_color,
            "fill_bg": is_fill_bg_color,
            "js_host": js_host,
            "animationOpts": animation_opts,
            "ariaOpts": aria_opts,
        }


class RenderOpts(BasicOpts):
    def __init__(self, is_embed_js: bool = False):
        self.opts: dict = {
            "embed_js": is_embed_js,
        }


class ToolBoxFeatureSaveAsImageOpts(BasicOpts):
    def __init__(
        self,
        type_: str = "png",
        name: Optional[str] = None,
        background_color: str = "auto",
        connected_background_color: str = "#fff",
        exclude_components: Optional[Sequence[str]] = None,
        is_show: bool = True,
        title: str = "保存为图片",
        icon: Optional[JSFunc] = None,
        pixel_ratio: Numeric = 1,
    ):
        self.opts: dict = {
            "type": type_,
            "name": name,
            "backgroundColor": background_color,
            "connectedBackgroundColor": connected_background_color,
            "excludeComponents": exclude_components,
            "show": is_show,
            "title": title,
            "icon": icon,
            "pixelRatio": pixel_ratio,
        }


class ToolBoxFeatureRestoreOpts(BasicOpts):
    def __init__(
        self, is_show: bool = True, title: str = "还原", icon: Optional[JSFunc] = None
    ):
        self.opts: dict = {"show": is_show, "title": title, "icon": icon}


class ToolBoxFeatureDataViewOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        title: str = "数据视图",
        icon: Optional[JSFunc] = None,
        is_read_only: bool = False,
        option_to_content: Optional[JSFunc] = None,
        content_to_option: Optional[JSFunc] = None,
        lang: Optional[Sequence[str]] = None,
        background_color: str = "#fff",
        text_area_color: str = "#fff",
        text_area_border_color: str = "#333",
        text_color: str = "#000",
        button_color: str = "#c23531",
        button_text_color: str = "#fff",
    ):
        if lang is None:
            lang = ["数据视图", "关闭", "刷新"]

        self.opts: dict = {
            "show": is_show,
            "title": title,
            "icon": icon,
            "readOnly": is_read_only,
            "optionToContent": option_to_content,
            "contentToOption": content_to_option,
            "lang": lang,
            "backgroundColor": background_color,
            "textareaColor": text_area_color,
            "textareaBorderColor": text_area_border_color,
            "textColor": text_color,
            "buttonColor": button_color,
            "buttonTextColor": button_text_color,
        }


class ToolBoxFeatureDataZoomOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        zoom_title: str = "区域缩放",
        back_title: str = "区域缩放还原",
        zoom_icon: Optional[JSFunc] = None,
        back_icon: Optional[JSFunc] = None,
        xaxis_index: Union[Numeric, Sequence, bool] = None,
        yaxis_index: Union[Numeric, Sequence, bool] = None,
        filter_mode: str = "filter",
    ):
        self.opts: dict = {
            "show": is_show,
            "title": {"zoom": zoom_title, "back": back_title},
            "icon": {"zoom": zoom_icon, "back": back_icon},
            "xAxisIndex": xaxis_index,
            "yAxisIndex": yaxis_index,
            "filterMode": filter_mode,
        }


class ToolBoxFeatureMagicTypeOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        type_: Optional[Sequence] = None,
        line_title: str = "切换为折线图",
        bar_title: str = "切换为柱状图",
        stack_title: str = "切换为堆叠",
        tiled_title: str = "切换为平铺",
        line_icon: Optional[JSFunc] = None,
        bar_icon: Optional[JSFunc] = None,
        stack_icon: Optional[JSFunc] = None,
        tiled_icon: Optional[JSFunc] = None,
    ):
        if type_ is None:
            type_ = ["line", "bar", "stack", "tiled"]

        self.opts: dict = {
            "show": is_show,
            "type": type_,
            "title": {
                "line": line_title,
                "bar": bar_title,
                "stack": stack_title,
                "tiled": tiled_title,
            },
            "icon": {
                "line": line_icon,
                "bar": bar_icon,
                "stack": stack_icon,
                "tiled": tiled_icon,
            },
        }


class ToolBoxFeatureBrushOpts(BasicOpts):
    def __init__(
        self,
        type_: Optional[str] = None,
        rect_icon: Optional[JSFunc] = None,
        polygon_icon: Optional[JSFunc] = None,
        line_x_icon: Optional[JSFunc] = None,
        line_y_icon: Optional[JSFunc] = None,
        keep_icon: Optional[JSFunc] = None,
        clear_icon: Optional[JSFunc] = None,
        rect_title: str = "矩形选择",
        polygon_title: str = "圈选",
        line_x_title: str = "横向选择",
        line_y_title: str = "纵向选择",
        keep_title: str = "保持选择",
        clear_title: str = "清除选择",
    ):
        self.opts: dict = {
            "type": type_,
            "icon": {
                "rect": rect_icon,
                "polygon": polygon_icon,
                "lineX": line_x_icon,
                "lineY": line_y_icon,
                "keep": keep_icon,
                "clear": clear_icon,
            },
            "title": {
                "rect": rect_title,
                "polygon": polygon_title,
                "lineX": line_x_title,
                "lineY": line_y_title,
                "keep": keep_title,
                "clear": clear_title,
            },
        }


class ToolBoxFeatureOpts(BasicOpts):
    def __init__(
        self,
        save_as_image: Union[
            ToolBoxFeatureSaveAsImageOpts, dict
        ] = ToolBoxFeatureSaveAsImageOpts(),
        restore: Union[ToolBoxFeatureRestoreOpts, dict] = ToolBoxFeatureRestoreOpts(),
        data_view: Union[
            ToolBoxFeatureDataViewOpts, dict
        ] = ToolBoxFeatureDataViewOpts(),
        data_zoom: Union[
            ToolBoxFeatureDataZoomOpts, dict
        ] = ToolBoxFeatureDataZoomOpts(),
        magic_type: Union[
            ToolBoxFeatureMagicTypeOpts, dict
        ] = ToolBoxFeatureMagicTypeOpts(),
        brush: Union[ToolBoxFeatureBrushOpts, dict] = None,
    ):
        self.opts: dict = {
            "saveAsImage": save_as_image,
            "restore": restore,
            "dataView": data_view,
            "dataZoom": data_zoom,
            "magicType": magic_type,
            "brush": brush,
        }


class ToolboxOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        orient: str = "horizontal",
        item_size: Numeric = 15,
        item_gap: Numeric = 10,
        pos_left: str = "80%",
        pos_right: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        feature: Union[ToolBoxFeatureOpts, dict] = ToolBoxFeatureOpts(),
    ):
        self.opts: dict = {
            "show": is_show,
            "orient": orient,
            "itemSize": item_size,
            "itemGap": item_gap,
            "left": pos_left,
            "right": pos_right,
            "top": pos_top,
            "bottom": pos_bottom,
            "feature": feature,
        }


class BrushOpts(BasicOpts):
    def __init__(
        self,
        tool_box: Optional[Sequence] = None,
        brush_link: Union[Sequence, str] = None,
        series_index: Union[Sequence, Numeric, str] = None,
        geo_index: Union[Sequence, Numeric, str] = None,
        x_axis_index: Union[Sequence, Numeric, str] = None,
        y_axis_index: Union[Sequence, Numeric, str] = None,
        brush_type: str = "rect",
        brush_mode: str = "single",
        transformable: bool = True,
        brush_style: Optional[dict] = None,
        throttle_type: str = "fixRate",
        throttle_delay: Numeric = 0,
        remove_on_click: bool = True,
        in_brush: Optional[dict] = None,
        out_of_brush: Optional[dict] = None,
        z: Numeric = 10000,
    ):
        if tool_box is None:
            tool_box = ["rect", "polygon", "keep", "clear"]

        if brush_style is None:
            brush_style = {
                "borderWidth": 1,
                "color": "rgba(120,140,180,0.3)",
                "borderColor": "rgba(120,140,180,0.8)",
            }

        self.opts: dict = {
            "toolbox": tool_box,
            "brushLink": brush_link,
            "seriesIndex": series_index,
            "geoIndex": geo_index,
            "xAxisIndex": x_axis_index,
            "yAxisIndex": y_axis_index,
            "brushType": brush_type,
            "brushMode": brush_mode,
            "transformable": transformable,
            "brushStyle": brush_style,
            "throttleType": throttle_type,
            "throttleDelay": throttle_delay,
            "removeOnClick": remove_on_click,
            "inBrush": in_brush,
            "outOfBrush": out_of_brush,
            "z": z,
        }


class TitleOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        title: Optional[str] = None,
        title_link: Optional[str] = None,
        title_target: Optional[str] = "blank",
        subtitle: Optional[str] = None,
        subtitle_link: Optional[str] = None,
        subtitle_target: Optional[str] = "blank",
        pos_left: Optional[str] = None,
        pos_right: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        padding: Union[Sequence, Numeric] = 5,
        item_gap: Numeric = 10,
        text_align: str = "auto",
        text_vertical_align: str = "auto",
        is_trigger_event: bool = False,
        title_textstyle_opts: Union[TextStyleOpts, dict, None] = None,
        subtitle_textstyle_opts: Union[TextStyleOpts, dict, None] = None,
    ):
        self.opts: Sequence = [
            {
                "show": is_show,
                "text": title,
                "link": title_link,
                "target": title_target,
                "subtext": subtitle,
                "sublink": subtitle_link,
                "subtarget": subtitle_target,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "bottom": pos_bottom,
                "padding": padding,
                "itemGap": item_gap,
                "textAlign": text_align,
                "textVerticalAlign": text_vertical_align,
                "triggerEvent": is_trigger_event,
                "textStyle": title_textstyle_opts,
                "subtextStyle": subtitle_textstyle_opts,
            }
        ]


class DataZoomOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        type_: str = "slider",
        is_disabled: bool = False,
        is_realtime: bool = True,
        is_show_detail: bool = True,
        is_show_data_shadow: bool = True,
        range_start: Union[Numeric, None] = 20,
        range_end: Union[Numeric, None] = 80,
        start_value: Union[int, str, None] = None,
        end_value: Union[int, str, None] = None,
        min_span: Union[int, None] = None,
        max_span: Union[int, None] = None,
        min_value_span: Union[int, str, None] = None,
        max_value_span: Union[int, str, None] = None,
        orient: str = "horizontal",
        xaxis_index: Union[int, Sequence[int], None] = None,
        yaxis_index: Union[int, Sequence[int], None] = None,
        radius_axis_index: Union[int, Sequence[int], None] = None,
        angle_axis_index: Union[int, Sequence[int], None] = None,
        is_zoom_lock: bool = False,
        throttle: Optional[int] = None,
        range_mode: Optional[Sequence] = None,
        pos_left: Optional[str] = None,
        pos_right: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        filter_mode: str = "filter",
        is_zoom_on_mouse_wheel: bool = True,
        is_move_on_mouse_move: bool = True,
        is_move_on_mouse_wheel: bool = True,
        is_prevent_default_mouse_move: bool = True,
    ):
        self.opts: dict = {
            "show": is_show,
            "type": type_,
            "showDetail": is_show_detail,
            "showDataShadow": is_show_data_shadow,
            "realtime": is_realtime,
            "startValue": start_value,
            "endValue": end_value,
            "start": range_start,
            "end": range_end,
            "minSpan": min_span,
            "maxSpan": max_span,
            "minValueSpan": min_value_span,
            "maxValueSpan": max_value_span,
            "orient": orient,
            "xAxisIndex": xaxis_index,
            "yAxisIndex": yaxis_index,
            "radiusAxisIndex": radius_axis_index,
            "angleAxisIndex": angle_axis_index,
            "zoomLock": is_zoom_lock,
            "throttle": throttle,
            "rangeMode": range_mode,
            "left": pos_left,
            "right": pos_right,
            "top": pos_top,
            "bottom": pos_bottom,
            "filterMode": filter_mode,
        }

        # inside have some different configurations.
        if type_ == "inside":
            self.opts.update({
                "disabled": is_disabled,
                "zoomOnMouseWheel": is_zoom_on_mouse_wheel,
                "moveOnMouseMove": is_move_on_mouse_move,
                "moveOnMouseWheel": is_move_on_mouse_wheel,
                "preventDefaultMouseMove": is_prevent_default_mouse_move,
            })


class LegendOpts(BasicOpts):
    def __init__(
        self,
        type_: Optional[str] = None,
        selected_mode: Union[str, bool, None] = None,
        selected_map: Optional[dict] = None,
        is_show: bool = True,
        pos_left: Union[str, Numeric, None] = None,
        pos_right: Union[str, Numeric, None] = None,
        pos_top: Union[str, Numeric, None] = None,
        pos_bottom: Union[str, Numeric, None] = None,
        orient: Optional[str] = None,
        align: Optional[str] = None,
        padding: int = 5,
        item_gap: int = 10,
        item_width: int = 25,
        item_height: int = 14,
        inactive_color: Optional[str] = None,
        textstyle_opts: Union[TextStyleOpts, dict, None] = None,
        legend_icon: Optional[str] = None,
        background_color: Optional[str] = "transparent",
        border_color: Optional[str] = "#ccc",
        border_width: Optional[int] = None,
        border_radius: Union[int, Sequence] = 0,
        page_button_item_gap: int = 5,
        page_button_gap: Optional[int] = None,
        page_button_position: str = "end",
        page_formatter: JSFunc = "{current}/{total}",
        page_icon: Optional[str] = None,
        page_icon_color: str = "#2f4554",
        page_icon_inactive_color: str = "#aaa",
        page_icon_size: Union[int, Sequence] = 15,
        is_page_animation: Optional[bool] = None,
        page_animation_duration_update: int = 800,
        selector: Union[bool, Sequence] = False,
        selector_position: str = "auto",
        selector_item_gap: int = 7,
        selector_button_gap: int = 10,
    ):
        self.opts: dict = {
            "type": type_,
            "selectedMode": selected_mode,
            "selected": selected_map,
            "show": is_show,
            "left": pos_left,
            "right": pos_right,
            "top": pos_top,
            "bottom": pos_bottom,
            "orient": orient,
            "align": align,
            "padding": padding,
            "itemGap": item_gap,
            "itemWidth": item_width,
            "itemHeight": item_height,
            "inactiveColor": inactive_color,
            "textStyle": textstyle_opts,
            "icon": legend_icon,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderRadius": border_radius,
            "pageButtonItemGap": page_button_item_gap,
            "pageButtonGap": page_button_gap,
            "pageButtonPosition": page_button_position,
            "pageFormatter": page_formatter,
            "pageIcon": page_icon,
            "pageIconColor": page_icon_color,
            "pageIconInactiveColor": page_icon_inactive_color,
            "pageIconSize": page_icon_size,
            "animation": is_page_animation,
            "animationDurationUpdate": page_animation_duration_update,
            "selector": selector,
            "selectorPosition": selector_position,
            "selectorItemGap": selector_item_gap,
            "selectorButtonGap": selector_button_gap,
        }


class VisualMapOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        type_: str = "color",
        min_: Numeric = 0,
        max_: Numeric = 100,
        range_: Sequence[Numeric] = None,
        range_text: Optional[Sequence] = None,
        range_color: Optional[Sequence[str]] = None,
        range_size: Optional[Sequence[int]] = None,
        range_opacity: Union[Numeric, Sequence[Numeric]] = None,
        orient: str = "vertical",
        pos_left: Optional[str] = None,
        pos_right: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        padding: Union[int, Sequence[int]] = 5,
        split_number: int = 5,
        series_index: Union[Numeric, Sequence, None] = None,
        is_hover_link: bool = True,
        dimension: Optional[Numeric] = None,
        is_calculable: bool = True,
        is_piecewise: bool = False,
        is_inverse: bool = False,
        precision: Optional[int] = None,
        pieces: Optional[Sequence] = None,
        out_of_range: Optional[dict] = None,
        item_width: int = 0,
        item_height: int = 0,
        background_color: Optional[str] = None,
        border_color: Optional[str] = None,
        border_width: int = 0,
        textstyle_opts: Union[TextStyleOpts, dict, None] = None,
    ):
        _inrange_op: dict = {}
        if type_ == "color":
            range_color = range_color or ["#50a3ba", "#eac763", "#d94e5d"]
            _inrange_op.update(color=range_color)
        elif type_ == "size":
            range_size = range_size or [20, 50]
            _inrange_op.update(symbolSize=range_size)
        if range_opacity is not None:
            _inrange_op.update(opacity=range_opacity)

        _visual_typ = "piecewise" if is_piecewise else "continuous"

        if is_piecewise and item_width == 0 and item_height == 0:
            item_width, item_height = 20, 14
        elif item_width == 0 and item_height == 0:
            item_width, item_height = 20, 140

        self.opts: dict = {
            "show": is_show,
            "type": _visual_typ,
            "min": min_,
            "max": max_,
            "text": range_text,
            "textStyle": textstyle_opts,
            "range": range_,
            "inRange": _inrange_op,
            "calculable": is_calculable,
            "inverse": is_inverse,
            "precision": precision,
            "splitNumber": split_number,
            "dimension": dimension,
            "seriesIndex": series_index,
            "hoverLink": is_hover_link,
            "orient": orient,
            "left": pos_left,
            "top": pos_top,
            "bottom": pos_bottom,
            "right": pos_right,
            "padding": padding,
            "showLabel": True,
            "itemWidth": item_width,
            "itemHeight": item_height,
            "outOfRange": out_of_range,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
        }
        if is_piecewise:
            self.opts.update(pieces=pieces)


class TooltipOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        trigger: str = "item",
        trigger_on: str = "mousemove|click",
        axis_pointer_type: str = "line",
        is_show_content: bool = True,
        is_always_show_content: bool = False,
        show_delay: Numeric = 0,
        hide_delay: Numeric = 100,
        is_enterable: bool = False,
        is_confine: bool = False,
        is_append_to_body: bool = False,
        transition_duration: Numeric = 0.4,
        position: Union[str, Sequence, JSFunc] = None,
        formatter: Optional[JSFunc] = None,
        value_formatter: Optional[JSFunc] = None,
        background_color: Optional[str] = None,
        border_color: Optional[str] = None,
        border_width: Numeric = 0,
        padding: Numeric = 5,
        textstyle_opts: Optional[TextStyleOpts] = TextStyleOpts(font_size=14),
        extra_css_text: Optional[str] = None,
        order: str = "seriesAsc",
    ):
        self.opts: dict = {
            "show": is_show,
            "trigger": trigger,
            "triggerOn": trigger_on,
            "axisPointer": {"type": axis_pointer_type},
            "showContent": is_show_content,
            "alwaysShowContent": is_always_show_content,
            "showDelay": show_delay,
            "hideDelay": hide_delay,
            "enterable": is_enterable,
            "confine": is_confine,
            "appendToBody": is_append_to_body,
            "transitionDuration": transition_duration,
            "position": position,
            "formatter": formatter,
            "valueFormatter": value_formatter,
            "textStyle": textstyle_opts,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "padding": padding,
            "extraCssText": extra_css_text,
            "order": order,
        }


class AxisLineOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        is_on_zero: bool = True,
        on_zero_axis_index: int = 0,
        symbol: Optional[str] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "onZero": is_on_zero,
            "onZeroAxisIndex": on_zero_axis_index,
            "symbol": symbol,
            "lineStyle": linestyle_opts,
        }


class AxisTickOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        is_align_with_label: bool = False,
        is_inside: bool = False,
        length: Optional[Numeric] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "alignWithLabel": is_align_with_label,
            "inside": is_inside,
            "length": length,
            "lineStyle": linestyle_opts,
        }


class AxisPointerOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        link: Sequence[dict] = None,
        type_: str = "line",
        is_snap: Optional[bool] = None,
        is_trigger_tooltip: bool = True,
        trigger_on: str = "mousemove|click",
        label: Union[LabelOpts, dict, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "type": type_,
            "snap": is_snap,
            "link": link,
            "label": label,
            "lineStyle": linestyle_opts,
            "triggerTooltip": is_trigger_tooltip,
            "triggerOn": trigger_on,
        }


class AxisOpts(BasicOpts):
    def __init__(
        self,
        type_: Optional[str] = None,
        name: Optional[str] = None,
        is_show: bool = True,
        is_scale: bool = False,
        is_inverse: bool = False,
        name_location: str = "end",
        name_gap: Numeric = 15,
        name_rotate: Optional[Numeric] = None,
        interval: Optional[Numeric] = None,
        grid_index: Numeric = 0,
        position: Optional[str] = None,
        offset: Numeric = 0,
        split_number: Numeric = 5,
        boundary_gap: Union[str, bool, None] = None,
        min_: Union[Numeric, str, None] = None,
        max_: Union[Numeric, str, None] = None,
        min_interval: Numeric = 0,
        max_interval: Optional[Numeric] = None,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        axispointer_opts: Union[AxisPointerOpts, dict, None] = None,
        name_textstyle_opts: Union[TextStyleOpts, dict, None] = None,
        splitarea_opts: Union[SplitAreaOpts, dict, None] = None,
        splitline_opts: Union[SplitLineOpts, dict] = SplitLineOpts(is_show=True),
        minor_tick_opts: Union[MinorTickOpts, dict, None] = None,
        minor_split_line_opts: Union[MinorSplitLineOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "type": type_,
            "name": name,
            "show": is_show,
            "scale": is_scale,
            "nameLocation": name_location,
            "nameGap": name_gap,
            "nameRotate": name_rotate,
            "interval": interval,
            "nameTextStyle": name_textstyle_opts,
            "gridIndex": grid_index,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "axisLabel": axislabel_opts,
            "axisPointer": axispointer_opts,
            "inverse": is_inverse,
            "position": position,
            "offset": offset,
            "splitNumber": split_number,
            "boundaryGap": boundary_gap,
            "min": min_,
            "max": max_,
            "minInterval": min_interval,
            "maxInterval": max_interval,
            "splitLine": splitline_opts,
            "splitArea": splitarea_opts,
            "minorTick": minor_tick_opts,
            "minorSplitLine": minor_split_line_opts,
        }


class GridOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        z_level: Numeric = 0,
        z: Numeric = 2,
        pos_left: Union[Numeric, str, None] = None,
        pos_top: Union[Numeric, str, None] = None,
        pos_right: Union[Numeric, str, None] = None,
        pos_bottom: Union[Numeric, str, None] = None,
        width: Union[Numeric, str, None] = None,
        height: Union[Numeric, str, None] = None,
        is_contain_label: bool = False,
        background_color: str = "transparent",
        border_color: str = "#ccc",
        border_width: Numeric = 1,
        shadow_blur: Optional[Numeric] = None,
        shadow_color: Optional[str] = None,
        shadow_offset_x: Numeric = 0,
        shadow_offset_y: Numeric = 0,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "zlevel": z_level,
            "z": z,
            "left": pos_left,
            "top": pos_top,
            "right": pos_right,
            "bottom": pos_bottom,
            "width": width,
            "height": height,
            "containLabel": is_contain_label,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "shadowBlur": shadow_blur,
            "shadowColor": shadow_color,
            "shadowOffsetX": shadow_offset_x,
            "shadowOffsetY": shadow_offset_y,
            "tooltip": tooltip_opts,
        }


class Grid3DOpts(BasicOpts):
    def __init__(
        self,
        is_show: Optional[bool] = None,
        width: Numeric = 200,
        height: Numeric = 100,
        depth: Numeric = 80,
        is_rotate: bool = False,
        rotate_speed: Numeric = 10,
        rotate_sensitivity: Numeric = 1,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        axispointer_opts: Union[AxisPointerOpts, dict, None] = None,
        splitarea_opts: Union[SplitAreaOpts, dict, None] = None,
        splitline_opts: Union[SplitLineOpts, dict] = SplitLineOpts(is_show=True),
        environment: JSFunc = "auto",
        view_control_alpha: Numeric = 20,
        view_control_beta: Numeric = 40,
        view_control_min_alpha: Numeric = -90,
        view_control_max_alpha: Numeric = 90,
        view_control_min_beta: Optional[int] = None,
        view_control_max_beta: Optional[int] = None,
        z_level: Numeric = -10,
        pos_left: Union[str, Numeric] = "auto",
        pos_top: Union[str, Numeric] = "auto",
        pos_right: Union[str, Numeric] = "auto",
        pos_bottom: Union[str, Numeric] = "auto",
    ):
        self.opts: dict = {
            "show": is_show,
            "boxWidth": width,
            "boxHeight": height,
            "boxDepth": depth,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "axisLabel": axislabel_opts,
            "axisPointer": axispointer_opts,
            "splitLine": splitline_opts,
            "splitArea": splitarea_opts,
            "environment": environment,
            "viewControl": {
                "autoRotate": is_rotate,
                "autoRotateSpeed": rotate_speed,
                "rotateSensitivity": rotate_sensitivity,
                "alpha": view_control_alpha,
                "beta": view_control_beta,
                "minAlpha": view_control_min_alpha,
                "maxAlpha": view_control_max_alpha,
                "minBeta": view_control_min_beta,
                "maxBeta": view_control_max_beta,
            },
            "zlevel": z_level,
            "left": pos_left,
            "top": pos_top,
            "right": pos_right,
            "bottom": pos_bottom,
        }


class Axis3DOpts(BasicOpts):
    def __init__(
        self,
        data: Optional[Sequence] = None,
        type_: Optional[str] = None,
        name: Optional[str] = None,
        is_show: bool = True,
        is_scale: bool = False,
        grid_3d_index: Numeric = 0,
        name_gap: Numeric = 20,
        min_: Union[str, Numeric, None] = None,
        max_: Union[str, Numeric, None] = None,
        split_number: Optional[Numeric] = None,
        log_base: Numeric = 10,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        axispointer_opts: Union[AxisPointerOpts, dict, None] = None,
        splitarea_opts: Union[SplitAreaOpts, dict, None] = None,
        splitline_opts: Union[SplitLineOpts, dict] = SplitLineOpts(is_show=True),
        textstyle_opts: Union[TextStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "data": data,
            "name": name,
            "show": is_show,
            "scale": is_scale,
            "grid3DIndex": grid_3d_index,
            "nameGap": name_gap,
            "nameTextStyle": textstyle_opts,
            "splitNumber": split_number,
            "logBase": log_base,
            "type": type_,
            "min": min_,
            "max": max_,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "axisLabel": axislabel_opts,
            "axisPointer": axispointer_opts,
            "splitLine": splitline_opts,
            "splitArea": splitarea_opts,
        }


class ParallelOpts(BasicOpts):
    def __init__(
        self,
        pos_left: str = "5%",
        pos_right: str = "13%",
        pos_bottom: str = "10%",
        pos_top: str = "20%",
        layout: Optional[str] = None,
        is_axis_expandable: bool = False,
        axis_expand_center: Optional[Numeric] = None,
        axis_expand_count: Numeric = 0,
        axis_expand_width: Numeric = 50,
        axis_expand_trigger_on: str = "click",
    ):
        self.opts: dict = {
            "left": pos_left,
            "right": pos_right,
            "bottom": pos_bottom,
            "top": pos_top,
            "layout": layout,
            "axisExpandable": is_axis_expandable,
            "axisExpandCenter": axis_expand_center,
            "axisExpandCount": axis_expand_count,
            "axisExpandWidth": axis_expand_width,
            "axisExpandTriggerOn": axis_expand_trigger_on,
        }


class ParallelAxisOpts(BasicOpts):
    def __init__(
        self,
        dim: Numeric,
        parallel_index: Numeric = 0,
        is_realtime: bool = True,
        name: Optional[str] = None,
        data: Sequence = None,
        type_: Optional[str] = None,
        name_location: str = "end",
        name_gap: Numeric = 15,
        name_rotate: Optional[int] = None,
        is_inverse: bool = False,
        min_: Union[str, Numeric, None] = None,
        max_: Union[str, Numeric, None] = None,
        is_scale: bool = False,
        log_base: Numeric = 10,
        is_silent: bool = False,
        is_trigger_event: bool = False,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        minor_tick_opts: Union[MinorTickOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "dim": dim,
            "parallelIndex": parallel_index,
            "realtime": is_realtime,
            "name": name,
            "data": data,
            "type": type_,
            "name_location": name_location,
            "name_gap": name_gap,
            "name_rotate": name_rotate,
            "inverse": is_inverse,
            "min": min_,
            "max": max_,
            "scale": is_scale,
            "logBase": log_base,
            "silent": is_silent,
            "triggerEvent": is_trigger_event,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "axisLabel": axislabel_opts,
            "minorTick": minor_tick_opts,
        }


class RadarIndicatorItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        min_: Optional[Numeric] = None,
        max_: Optional[Numeric] = None,
        color: Optional[str] = None,
    ):
        self.opts: dict = {"name": name, "max": max_, "min": min_, "color": color}


class CalendarDayLabelOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        first_day: int = 0,
        margin: Optional[int] = None,
        position: str = "start",
        name_map: Union[str, Sequence] = "en",
        label_color: str = "#000",
        label_font_style: str = "normal",
        label_font_weight: str = "normal",
        label_font_family: str = "sans-serif",
        label_font_size: int = 12,
        align: Optional[str] = None,
        vertical_align: Optional[str] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "firstDay": first_day,
            "margin": margin,
            "position": position,
            "nameMap": name_map,
            "color": label_color,
            "fontStyle": label_font_style,
            "fontWeight": label_font_weight,
            "fontFamily": label_font_family,
            "fontSize": label_font_size,
            "align": align,
            "verticalAlign": vertical_align,
        }


class CalendarMonthLabelOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        align: Optional[str] = None,
        margin: Optional[int] = None,
        position: str = "start",
        name_map: Union[str, Sequence] = "en",
        formatter: JSFunc = None,
        label_color: str = "#000",
        label_font_style: str = "normal",
        label_font_weight: str = "normal",
        label_font_family: str = "sans-serif",
        label_font_size: int = 12,
        vertical_align: Optional[str] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "align": align,
            "margin": margin,
            "position": position,
            "nameMap": name_map,
            "formatter": formatter,
            "color": label_color,
            "fontStyle": label_font_style,
            "fontWeight": label_font_weight,
            "fontFamily": label_font_family,
            "fontSize": label_font_size,
            "verticalAlign": vertical_align,
        }


class CalendarYearLabelOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        margin: Optional[int] = None,
        position: Optional[str] = None,
        formatter: JSFunc = None,
        label_color: str = "#000",
        label_font_style: str = "normal",
        label_font_weight: str = "normal",
        label_font_family: str = "sans-serif",
        label_font_size: int = 12,
        align: Optional[str] = None,
        vertical_align: Optional[str] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "margin": margin,
            "position": position,
            "formatter": formatter,
            "color": label_color,
            "fontStyle": label_font_style,
            "fontWeight": label_font_weight,
            "fontFamily": label_font_family,
            "fontSize": label_font_size,
            "align": align,
            "verticalAlign": vertical_align,
        }


class CalendarOpts(BasicOpts):
    def __init__(
        self,
        z_level: Numeric = 0,
        z: Numeric = 2,
        pos_left: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_right: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        width: Optional[str] = "auto",
        height: Optional[str] = None,
        orient: Optional[str] = "horizontal",
        range_: Union[str, Sequence, int] = None,
        cell_size: Union[int, Sequence] = 20,
        splitline_opts: Union[SplitLineOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        daylabel_opts: Union[CalendarDayLabelOpts, dict, None] = None,
        monthlabel_opts: Union[CalendarMonthLabelOpts, dict, None] = None,
        yearlabel_opts: Union[CalendarYearLabelOpts, dict, None] = None,
        is_silent: bool = False,
    ):
        self.opts: dict = {
            "zlevel": z_level,
            "z": z,
            "left": pos_left,
            "top": pos_top,
            "right": pos_right,
            "bottom": pos_bottom,
            "width": width,
            "height": height,
            "orient": orient,
            "range": range_,
            "cellSize": cell_size,
            "splitLine": splitline_opts,
            "itemStyle": itemstyle_opts,
            "dayLabel": daylabel_opts,
            "monthLabel": monthlabel_opts,
            "yearLabel": yearlabel_opts,
            "silent": is_silent,
        }


class SingleAxisOpts(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        max_: Union[str, Numeric, None] = None,
        min_: Union[str, Numeric, None] = None,
        pos_left: Optional[str] = None,
        pos_right: Optional[str] = None,
        pos_top: Optional[str] = None,
        pos_bottom: Optional[str] = None,
        width: Optional[str] = None,
        height: Optional[str] = None,
        orient: Optional[str] = None,
        type_: Optional[str] = None,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        axispointer_opts: Union[AxisPointerOpts, dict, None] = None,
        splitarea_opts: Union[SplitAreaOpts, dict, None] = None,
        splitline_opts: Union[SplitLineOpts, dict, None] = None,
        minor_tick_opts: Union[MinorTickOpts, dict, None] = None,
        minor_split_line_opts: Union[MinorSplitLineOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "max": max_,
            "min": min_,
            "left": pos_left,
            "right": pos_right,
            "top": pos_top,
            "bottom": pos_bottom,
            "width": width,
            "height": height,
            "orient": orient,
            "type": type_,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "minorTick": minor_tick_opts,
            "axisLabel": axislabel_opts,
            "splitLine": splitline_opts,
            "minorSplitLine": minor_split_line_opts,
            "splitArea": splitarea_opts,
            "axisPointer": axispointer_opts,
            "tooltip": tooltip_opts,
        }


class RadiusAxisItem(BasicOpts):
    def __init__(
        self,
        value: Optional[str] = None,
        textstyle_opts: Union[TextStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {"value": value, "textStyle": textstyle_opts}


class AngleAxisItem(RadiusAxisItem):
    def __init__(
        self,
        value: Optional[str] = None,
        textstyle_opts: Optional[TextStyleOpts] = None,
    ):
        super().__init__(value, textstyle_opts)


class RadiusAxisOpts(BasicOpts):
    def __init__(
        self,
        polar_index: Optional[int] = None,
        data: Optional[Sequence[Union[RadiusAxisItem, dict, str]]] = None,
        boundary_gap: Union[bool, Sequence] = None,
        type_: Optional[str] = None,
        name: Optional[str] = None,
        name_location: Optional[str] = None,
        name_gap: Numeric = 15,
        name_rotate: Optional[Numeric] = None,
        is_inverse: bool = False,
        min_: Union[str, Numeric, None] = None,
        max_: Union[str, Numeric, None] = None,
        is_scale: bool = False,
        split_number: Numeric = 5,
        interval: Optional[Numeric] = None,
        min_interval: Numeric = 0,
        max_interval: Optional[Numeric] = None,
        splitline_opts: Union[SplitLineOpts, dict, None] = None,
        splitarea_opts: Union[SplitAreaOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        minor_tick_opts: Union[MinorTickOpts, dict, None] = None,
        minor_split_line_opts: Union[MinorSplitLineOpts, dict, None] = None,
        z: Optional[int] = None,
        z_level: Optional[int] = None,
    ):
        _data = []
        if data:
            for d in data:
                if isinstance(d, RadiusAxisItem):
                    d = d.opts
                _data.append(d)

        self.opts: dict = {
            "polarIndex": polar_index,
            "type": type_,
            "data": data,
            "boundaryGap": boundary_gap,
            "name": name,
            "nameLocation": name_location,
            "nameGap": name_gap,
            "nameRotate": name_rotate,
            "inverse": is_inverse,
            "min": min_,
            "max": max_,
            "scale": is_scale,
            "splitNumber": split_number,
            "interval": interval,
            "minInterval": min_interval,
            "maxInterval": max_interval,
            "splitLine": splitline_opts,
            "splitArea": splitarea_opts,
            "axisTick": axistick_opts,
            "axisLine": axisline_opts,
            "axisLabel": axislabel_opts,
            "minorTick": minor_tick_opts,
            "minorSplitLine": minor_split_line_opts,
            "z": z,
            "zlevel": z_level,
        }


class AngleAxisOpts(BasicOpts):
    def __init__(
        self,
        polar_index: Optional[int] = None,
        data: Optional[Sequence[Union[AngleAxisItem, Numeric, dict, str]]] = None,
        start_angle: Optional[Numeric] = 90,
        is_clockwise: bool = False,
        boundary_gap: Union[bool, Sequence, None] = None,
        type_: Optional[str] = None,
        min_: Union[str, Numeric, None] = None,
        max_: Union[str, Numeric, None] = None,
        is_scale: bool = False,
        split_number: Numeric = 5,
        interval: Optional[Numeric] = None,
        splitline_opts: Union[SplitLineOpts, dict, None] = None,
        axisline_opts: Union[AxisLineOpts, dict, None] = None,
        axistick_opts: Union[AxisTickOpts, dict, None] = None,
        axislabel_opts: Union[LabelOpts, dict, None] = None,
        minor_tick_opts: Union[MinorTickOpts, dict, None] = None,
        minor_split_line_opts: Union[MinorSplitLineOpts, dict, None] = None,
        z: Optional[int] = None,
        z_level: Optional[int] = None,
    ):
        _data = []
        if data:
            for d in data:
                if isinstance(d, AngleAxisItem):
                    d = d.opts
                _data.append(d)

        self.opts: dict = {
            "polarIndex": polar_index,
            "startAngle": start_angle,
            "data": data,
            "clockwise": is_clockwise,
            "boundaryGap": boundary_gap,
            "type": type_,
            "min": min_,
            "max": max_,
            "scale": is_scale,
            "splitNumber": split_number,
            "interval": interval,
            "splitLine": splitline_opts,
            "axisLine": axisline_opts,
            "axisTick": axistick_opts,
            "axisLabel": axislabel_opts,
            "minorTick": minor_tick_opts,
            "minorSplitLine": minor_split_line_opts,
            "z": z,
            "zlevel": z_level,
        }


class PolarOpts(BasicOpts):
    def __init__(
        self,
        center: Optional[Sequence] = None,
        radius: Optional[Union[Sequence, str]] = None,
        tooltip_opts: TooltipOpts = None,
    ):
        self.opts: dict = {"center": center, "radius": radius, "tooltip": tooltip_opts}


class DatasetTransformOpts(BasicOpts):
    def __init__(
        self,
        type_: str = "filter",
        config: Optional[dict] = None,
        is_print: bool = False,
    ):
        self.opts: dict = {"type": type_, "config": config, "print": is_print}


class EmphasisOpts(BasicOpts):
    def __init__(
        self,
        is_disabled: bool = False,
        is_scale: bool = True,
        focus: str = "none",
        blur_scope: str = "coordinateSystem",
        label_opts: Union[LabelOpts, dict, None] = None,
        is_show_label_line: bool = False,
        label_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        areastyle_opts: Union[AreaStyleOpts, dict, None] = None,
        end_label_opts: Union[LabelOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "disabled": is_disabled,
            "scale": is_scale,
            "focus": focus,
            "blurScope": blur_scope,
            "label": label_opts,
            "labelLine": {
                "show": is_show_label_line,
                "lineStyle": label_linestyle_opts
            },
            "itemStyle": itemstyle_opts,
            "lineStyle": linestyle_opts,
            "areaStyle": areastyle_opts,
            "endLabel": end_label_opts,
        }


class Emphasis3DOpts(BasicOpts):
    def __init__(
        self,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "itemStyle": itemstyle_opts,
            "label": label_opts,
        }


class BlurOpts(BasicOpts):
    def __init__(
        self,
        label_opts: Union[LabelOpts, dict, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        is_show_label_line: bool = False,
        label_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "label": label_opts,
            "lineStyle": linestyle_opts,
            "labelLine": {
                "show": is_show_label_line,
                "lineStyle": label_linestyle_opts
            },
            "itemStyle": itemstyle_opts,
        }


class SelectOpts(BasicOpts):
    def __init__(
            self,
            is_disabled: Optional[bool] = None,
            itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
            linestyle_opts: Union[LineStyleOpts, dict, None] = None,
            label_opts: Union[LabelOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "disabled": is_disabled,
            "itemStyle": itemstyle_opts,
            "lineStyle": linestyle_opts,
            "label": label_opts,
        }


class TreeLeavesOpts(BasicOpts):
    def __init__(
        self,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        emphasis_opts: Union[EmphasisOpts, dict, None] = None,
        blur_opts: Union[BlurOpts, dict, None] = None,
        select_opts: Union[SelectOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "emphasis": emphasis_opts,
            "blur": blur_opts,
            "select": select_opts,
        }



File: pyecharts/options/__init__.py

# flake8: noqa

from .charts_options import (
    BarItem,
    BarBackgroundStyleOpts,
    BMapCopyrightTypeOpts,
    BMapGeoLocationControlOpts,
    BMapNavigationControlOpts,
    BMapOverviewMapControlOpts,
    BMapScaleControlOpts,
    BMapTypeControlOpts,
    BoxplotItem,
    CandleStickItem,
    ComponentTitleOpts,
    EffectScatterItem,
    FunnelItem,
    GaugeDetailOpts,
    GaugePointerOpts,
    GaugeAnchorOpts,
    GaugeProgressOpts,
    GaugeTitleOpts,
    GeoItem,
    GeoRegionsOpts,
    GraphCategory,
    GraphicBasicStyleOpts,
    GraphicGroup,
    GraphicImage,
    GraphicImageStyleOpts,
    GraphicItem,
    GraphicRect,
    GraphicShapeOpts,
    GraphicText,
    GraphicTextStyleOpts,
    GraphLink,
    GraphNode,
    GraphGLLink,
    GraphGLNode,
    LineItem,
    MapItem,
    Map3DColorMaterialOpts,
    Map3DLabelOpts,
    Map3DLightOpts,
    Map3DLambertMaterialOpts,
    Map3DPostEffectOpts,
    Map3DRealisticMaterialOpts,
    Map3DViewControlOpts,
    PageLayoutOpts,
    ParallelItem,
    PieItem,
    PieLabelLineOpts,
    PieEmptyCircleStyle,
    RadarItem,
    SankeyLevelsOpts,
    ScatterItem,
    SunburstItem,
    SunburstLabelLayoutOpts,
    SunburstLabelLineOpts,
    SunburstLevelOpts,
    TabChartGlobalOpts,
    ThemeRiverItem,
    TimelineCheckPointerStyle,
    TimelineControlStyle,
    TreeItem,
    TreeMapItemStyleOpts,
    TreeMapLevelsOpts,
)
from .global_options import (
    AngleAxisItem,
    AngleAxisOpts,
    AnimationOpts,
    AriaDecalOpts,
    AriaLabelOpts,
    AriaOpts,
    Axis3DOpts,
    AxisLineOpts,
    AxisOpts,
    AxisPointerOpts,
    AxisTickOpts,
    BlurOpts,
    BrushOpts,
    CalendarOpts,
    CalendarDayLabelOpts,
    CalendarMonthLabelOpts,
    CalendarYearLabelOpts,
    DataZoomOpts,
    DatasetTransformOpts,
    EmphasisOpts,
    Emphasis3DOpts,
    Grid3DOpts,
    GridOpts,
    InitOpts,
    RenderOpts,
    LegendOpts,
    ParallelAxisOpts,
    ParallelOpts,
    PolarOpts,
    RadarIndicatorItem,
    RadiusAxisItem,
    RadiusAxisOpts,
    SelectOpts,
    SingleAxisOpts,
    TitleOpts,
    ToolBoxFeatureBrushOpts,
    ToolBoxFeatureDataViewOpts,
    ToolBoxFeatureDataZoomOpts,
    ToolBoxFeatureMagicTypeOpts,
    ToolBoxFeatureOpts,
    ToolBoxFeatureRestoreOpts,
    ToolBoxFeatureSaveAsImageOpts,
    ToolboxOpts,
    TooltipOpts,
    TreeLeavesOpts,
    VisualMapOpts,
)
from .series_options import (
    AreaStyleOpts,
    EffectOpts,
    GraphGLForceAtlas2Opts,
    ItemStyleOpts,
    LabelOpts,
    LineStyleOpts,
    Lines3DEffectOpts,
    MarkAreaItem,
    MarkAreaOpts,
    MarkLineItem,
    MarkLineOpts,
    MarkPointItem,
    MarkPointOpts,
    MinorSplitLineOpts,
    MinorTickOpts,
    SplitAreaOpts,
    SplitLineOpts,
    TextStyleOpts,
    TreeMapBreadcrumbOpts,
)



File: pyecharts/options/charts_options.py

import simplejson as json

from ..globals import BMapType
from .global_options import TooltipOpts
from .series_options import (
    BasicOpts,
    ItemStyleOpts,
    JSFunc,
    LabelOpts,
    LineStyleOpts,
    Numeric,
    Optional,
    Sequence,
    TextStyleOpts,
    Union,
    AreaStyleOpts,
)


# Chart Options
class GraphNode(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        x: Optional[Numeric] = None,
        y: Optional[Numeric] = None,
        is_fixed: bool = False,
        value: Union[str, Sequence, None] = None,
        category: Optional[int] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[Numeric, Sequence, None] = None,
        symbol_rotate: Optional[int] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        is_disabled_emphasis: Optional[bool] = None,
        emphasis_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        emphasis_label_opts: Union[LabelOpts, dict, None] = None,
        blur_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        blur_label_opts: Union[LabelOpts, dict, None] = None,
        is_disabled_select: Optional[bool] = None,
        select_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        select_label_opts: Union[LabelOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "x": x,
            "y": y,
            "fixed": is_fixed,
            "value": value,
            "category": category,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "itemStyle": itemstyle_opts,
            "label": label_opts,
            "emphasis": {
                "disabled": is_disabled_emphasis,
                "itemStyle": emphasis_itemstyle_opts,
                "label": emphasis_label_opts,
            },
            "blur": {
                "itemStyle": blur_itemstyle_opts,
                "label": blur_label_opts,
            },
            "select": {
                "disabled": is_disabled_select,
                "itemStyle": select_itemstyle_opts,
                "label": select_label_opts,
            },
            "tooltip": tooltip_opts,
        }


class GraphLink(BasicOpts):
    def __init__(
        self,
        source: Union[str, int, None] = None,
        target: Union[str, int, None] = None,
        value: Optional[Numeric] = None,
        symbol: Union[str, Sequence, None] = None,
        symbol_size: Union[Numeric, Sequence, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        is_disabled_emphasis: Optional[bool] = None,
        emphasis_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        emphasis_label_opts: Union[LabelOpts, dict, None] = None,
        blur_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        blur_label_opts: Union[LabelOpts, dict, None] = None,
        is_disabled_select: Optional[bool] = None,
        select_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        select_label_opts: Union[LabelOpts, dict, None] = None,
        is_ignore_force_layout: bool = False
    ):
        self.opts: dict = {
            "source": source,
            "target": target,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "lineStyle": linestyle_opts,
            "label": label_opts,
            "emphasis": {
                "disabled": is_disabled_emphasis,
                "lineStyle": emphasis_linestyle_opts,
                "label": emphasis_label_opts,
            },
            "blur": {
                "lineStyle": blur_linestyle_opts,
                "label": blur_label_opts,
            },
            "select": {
                "disabled": is_disabled_select,
                "lineStyle": select_linestyle_opts,
                "label": select_label_opts,
            },
            "ignoreForceLayout": is_ignore_force_layout,
        }


class GraphCategory(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[Numeric, Sequence, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "label": label_opts,
        }


class BMapNavigationControlOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_TOP_LEFT,
        offset_width: Numeric = 10,
        offset_height: Numeric = 10,
        type_: Numeric = BMapType.NAVIGATION_CONTROL_LARGE,
        is_show_zoom_info: bool = False,
        is_enable_geo_location: bool = False,
    ):
        bmap_nav_config = json.dumps(
            {
                "anchor": position,
                "offset": {"width": offset_width, "height": offset_height},
                "type": type_,
                "showZoomInfo": is_show_zoom_info,
                "enableGeolocation": is_enable_geo_location,
            }
        )

        self.opts: dict = {
            "functions": [
                "bmap.addControl(new BMap.NavigationControl({}));".format(
                    bmap_nav_config
                )
            ]
        }


class BMapOverviewMapControlOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_BOTTOM_RIGHT,
        offset_width: Numeric = 10,
        offset_height: Numeric = 50,
        is_open: bool = False,
    ):
        bmap_overview_config = json.dumps(
            {
                "anchor": position,
                "offset": {"width": offset_width, "height": offset_height},
                "isOpen": is_open,
            }
        )

        self.opts: dict = {
            "functions": [
                "var overview = new BMap.OverviewMapControl({});".format(
                    bmap_overview_config
                ),
                "bmap.addControl(overview);",
            ]
        }


class BMapScaleControlOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_BOTTOM_LEFT,
        offset_width: Numeric = 80,
        offset_height: Numeric = 21,
    ):
        bmap_scale_config = json.dumps(
            {
                "anchor": position,
                "offset": {"width": offset_width, "height": offset_height},
            }
        )

        self.opts: dict = {
            "functions": [
                "bmap.addControl(new BMap.ScaleControl({}));".format(bmap_scale_config)
            ]
        }


class BMapTypeControlOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_TOP_RIGHT,
        type_: Numeric = BMapType.MAPTYPE_CONTROL_HORIZONTAL,
    ):
        bmap_type_config = json.dumps({"anchor": position, "type": type_})

        self.opts: dict = {
            "functions": [
                "bmap.addControl(new BMap.MapTypeControl({}));".format(bmap_type_config)
            ]
        }


class BMapCopyrightTypeOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_BOTTOM_LEFT,
        offset_width: Numeric = 2,
        offset_height: Numeric = 2,
        copyright_: str = "",
    ):
        bmap_copyright_config = json.dumps(
            {
                "anchor": position,
                "offset": {"width": offset_width, "height": offset_height},
            }
        )

        bmap_copyright_content_config = json.dumps({"id": 1, "content": copyright_})

        self.opts: dict = {
            "functions": [
                "var copyright = new BMap.CopyrightControl({});".format(
                    bmap_copyright_config
                ),
                "copyright.addCopyright({});".format(bmap_copyright_content_config),
                "bmap.addControl(copyright);",
            ]
        }


class BMapGeoLocationControlOpts(BasicOpts):
    def __init__(
        self,
        position: Numeric = BMapType.ANCHOR_BOTTOM_LEFT,
        offset_width: Numeric = 10,
        offset_height: Numeric = 10,
        is_show_address_bar: bool = True,
        is_enable_auto_location: bool = False,
    ):
        bmap_geo_location_config = json.dumps(
            {
                "anchor": position,
                "offset": {"width": offset_width, "height": offset_height},
                "showAddressBar": is_show_address_bar,
                "enableAutoLocation": is_enable_auto_location,
            }
        )

        self.opts: dict = {
            "functions": [
                "bmap.addControl(new BMap.GeolocationControl({}))".format(
                    bmap_geo_location_config
                )
            ]
        }


class ComponentTitleOpts:
    def __init__(
        self,
        title: str = "",
        subtitle: str = "",
        title_style: Optional[dict] = None,
        subtitle_style: Optional[dict] = None,
    ):
        self.title = title.replace("\n", "<br/>")
        self.subtitle = subtitle.replace("\n", "<br/>")
        self.title_style: str = ""
        self.subtitle_style: str = ""
        title_style = title_style or {"style": "font-size: 18px; font-weight:bold;"}
        subtitle_style = subtitle_style or {"style": "font-size: 12px;"}
        self._convert_dict_to_string(title_style, subtitle_style)

    def _convert_dict_to_string(self, title_style: dict, subtitle_style: dict):
        for k, v in title_style.items():
            self.title_style += '{}="{}" '.format(k, v)
        for k, v in subtitle_style.items():
            self.subtitle_style += '{}="{}" '.format(k, v)


class PageLayoutOpts(BasicOpts):
    def __init__(
        self,
        justify_content: Optional[str] = None,
        margin: Optional[str] = None,
        display: Optional[str] = None,
        flex_wrap: Optional[str] = None,
    ):
        self.opts: dict = {
            "justify-content": justify_content,
            "margin": margin,
            "display": display,
            "flex-wrap": flex_wrap,
        }


class BaseGraphic(BasicOpts):
    pass


class GraphicShapeOpts(BaseGraphic):
    def __init__(
        self,
        pos_x: Numeric = 0,
        pos_y: Numeric = 0,
        width: Numeric = 0,
        height: Numeric = 0,
        r: Union[Sequence, Numeric, None] = None,
    ):
        self.opts: dict = {
            "x": pos_x,
            "y": pos_y,
            "width": width,
            "height": height,
            "r": r,
        }


class GraphicBasicStyleOpts(BaseGraphic):
    def __init__(
        self,
        fill: str = "#000",
        stroke: Optional[str] = None,
        line_width: Numeric = 0,
        shadow_blur: Optional[Numeric] = None,
        shadow_offset_x: Optional[Numeric] = None,
        shadow_offset_y: Optional[Numeric] = None,
        shadow_color: Optional[str] = None,
    ):
        self.opts: dict = {
            "fill": fill,
            "stroke": stroke,
            "line_width": line_width,
            "shadowBlur": shadow_blur,
            "shadowOffsetX": shadow_offset_x,
            "shadowOffsetY": shadow_offset_y,
            "shadowColor": shadow_color,
        }


class GraphicImageStyleOpts(BaseGraphic):
    def __init__(
        self,
        image: Optional[str] = None,
        pos_x: Numeric = 0,
        pos_y: Numeric = 0,
        width: Numeric = 0,
        height: Numeric = 0,
        opacity: Numeric = 1,
        graphic_basicstyle_opts: Union[GraphicBasicStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "image": image,
            "x": pos_x,
            "y": pos_y,
            "width": width,
            "height": height,
            "opacity": opacity,
        }

        if graphic_basicstyle_opts:
            if isinstance(graphic_basicstyle_opts, GraphicBasicStyleOpts):
                self.opts.update(graphic_basicstyle_opts.opts)
            else:
                self.opts.update(graphic_basicstyle_opts)


class GraphicTextStyleOpts(BaseGraphic):
    def __init__(
        self,
        text: Optional[JSFunc] = None,
        pos_x: Numeric = 0,
        pos_y: Numeric = 0,
        font: Optional[str] = None,
        font_size: Optional[Numeric] = 0,
        text_align: str = "left",
        text_vertical_align: Optional[str] = None,
        graphic_basicstyle_opts: Union[GraphicBasicStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "text": text,
            "x": pos_x,
            "y": pos_y,
            "font": font,
            "fontSize": font_size,
            "textAlign": text_align,
            "textVerticalAlign": text_vertical_align,
        }

        if graphic_basicstyle_opts:
            if isinstance(graphic_basicstyle_opts, GraphicBasicStyleOpts):
                self.opts.update(graphic_basicstyle_opts.opts)
            else:
                self.opts.update(graphic_basicstyle_opts)


class GraphicItem(BaseGraphic):
    def __init__(
        self,
        id_: Optional[str] = None,
        action: str = "merge",
        position: [Sequence, Numeric, None] = None,
        rotation: Union[Numeric, JSFunc, None] = 0,
        scale: Union[Sequence, Numeric, None] = None,
        origin: Union[Numeric, Sequence, None] = None,
        left: Union[Numeric, str, None] = None,
        right: Union[Numeric, str, None] = None,
        top: Union[Numeric, str, None] = None,
        bottom: Union[Numeric, str, None] = None,
        bounding: str = "all",
        z: Numeric = 0,
        z_level: Numeric = 0,
        is_silent: bool = False,
        is_invisible: bool = False,
        is_ignore: bool = False,
        cursor: str = "pointer",
        is_draggable: bool = False,
        is_progressive: bool = False,
        width: Numeric = 0,
        height: Numeric = 0,
    ):
        self.opts: dict = {
            "id": id_,
            "$action": action,
            "position": position,
            "rotation": rotation,
            "scale": scale,
            "origin": origin,
            "left": left,
            "right": right,
            "top": top,
            "bottom": bottom,
            "bounding": bounding,
            "z": z,
            "zlevel": z_level,
            "silent": is_silent,
            "invisible": is_invisible,
            "ignore": is_ignore,
            "cursor": cursor,
            "draggable": is_draggable,
            "progressive": is_progressive,
            "width": width,
            "height": height,
        }


class GraphicGroup(BaseGraphic):
    def __init__(
        self,
        graphic_item: Union[GraphicItem, dict, None] = None,
        is_diff_children_by_name: bool = False,
        children: Optional[Sequence[BaseGraphic]] = None,
    ):
        self.opts: dict = {
            "type": "group",
            "diffChildrenByName": is_diff_children_by_name,
            "children": children,
        }

        if graphic_item:
            if isinstance(graphic_item, GraphicItem):
                self.opts.update(graphic_item.opts)
            else:
                self.opts.update(graphic_item)


class GraphicImage(BaseGraphic):
    def __init__(
        self,
        graphic_item: Union[GraphicItem, dict, None] = None,
        graphic_imagestyle_opts: Union[GraphicImageStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {"type": "image"}

        if graphic_item:
            if isinstance(graphic_item, GraphicItem):
                self.opts.update(graphic_item.opts)
            else:
                self.opts.update(graphic_item)

        if graphic_imagestyle_opts:
            if isinstance(graphic_imagestyle_opts, GraphicImageStyleOpts):
                self.opts.update(style=graphic_imagestyle_opts.opts)
            else:
                self.opts.update(style=graphic_imagestyle_opts)


class GraphicText(BaseGraphic):
    def __init__(
        self,
        graphic_item: Union[GraphicItem, dict, None] = None,
        graphic_textstyle_opts: Union[GraphicTextStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {"type": "text"}

        if graphic_item:
            if isinstance(graphic_item, GraphicItem):
                self.opts.update(graphic_item.opts)
            else:
                self.opts.update(graphic_item)

        if graphic_textstyle_opts:
            if isinstance(graphic_textstyle_opts, GraphicTextStyleOpts):
                self.opts.update(style=graphic_textstyle_opts.opts)
            else:
                self.opts.update(style=graphic_textstyle_opts)


class GraphicRect(BaseGraphic):
    def __init__(
        self,
        graphic_item: Union[GraphicItem, dict, None] = None,
        graphic_shape_opts: Union[GraphicShapeOpts, dict, None] = None,
        graphic_basicstyle_opts: Union[GraphicBasicStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {"type": "rect"}

        if graphic_item:
            if isinstance(graphic_item, GraphicItem):
                self.opts.update(graphic_item.opts)
            else:
                self.opts.update(graphic_item)

        if graphic_shape_opts:
            if isinstance(graphic_shape_opts, GraphicShapeOpts):
                self.opts.update(shape=graphic_shape_opts.opts)
            else:
                self.opts.update(graphic_shape_opts)

        if graphic_basicstyle_opts:
            if isinstance(graphic_basicstyle_opts, GraphicBasicStyleOpts):
                self.opts.update(style=graphic_basicstyle_opts.opts)
            else:
                self.opts.update(style=graphic_basicstyle_opts)


class SankeyLevelsOpts(BasicOpts):
    def __init__(
        self,
        depth: Numeric = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "depth": depth,
            "itemStyle": itemstyle_opts,
            "lineStyle": linestyle_opts,
        }


class TreeMapItemStyleOpts(BasicOpts):
    def __init__(
        self,
        color: Optional[str] = None,
        color_alpha: Union[Numeric, Sequence] = None,
        color_saturation: Union[Numeric, Sequence] = None,
        border_color: Optional[str] = None,
        border_width: Numeric = 0,
        border_color_saturation: Union[Numeric, Sequence] = None,
        gap_width: Numeric = 0,
        stroke_color: Optional[str] = None,
        stroke_width: Optional[Numeric] = None,
    ):
        self.opts: dict = {
            "color": color,
            "colorAlpha": color_alpha,
            "colorSaturation": color_saturation,
            "gapWidth": gap_width,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderColorSaturation": border_color_saturation,
            "strokeColor": stroke_color,
            "strokeWidth": stroke_width,
        }


class TreeMapLevelsOpts(BasicOpts):
    def __init__(
        self,
        color: Union[str, Sequence] = None,
        color_alpha: Union[Numeric, Sequence] = None,
        color_saturation: Union[Numeric, Sequence] = None,
        color_mapping_by: str = "index",
        treemap_itemstyle_opts: Union[TreeMapItemStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        upper_label_opts: Union[LabelOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "color": color,
            "colorAlpha": color_alpha,
            "colorSaturation": color_saturation,
            "colorMappingBy": color_mapping_by,
            "itemStyle": treemap_itemstyle_opts,
            "label": label_opts,
            "upperLabel": upper_label_opts,
        }


class Map3DLabelOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        distance: Numeric = None,
        formatter: Optional[JSFunc] = None,
        text_style: Union[TextStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "distance": distance,
            "formatter": formatter,
            "textStyle": text_style,
        }


class Map3DRealisticMaterialOpts(BasicOpts):
    def __init__(
        self,
        detail_texture: Optional[JSFunc] = None,
        texture_tiling: Numeric = 1,
        texture_offset: Numeric = 0,
        roughness: Numeric = 0.5,
        metalness: Numeric = 0,
        roughness_adjust: Numeric = 0.5,
        metalness_adjust: Numeric = 0.5,
        normal_texture: Optional[JSFunc] = None,
    ):
        self.opts: dict = {
            "detailTexture": detail_texture,
            "textureTiling": texture_tiling,
            "textureOffset": texture_offset,
            "roughness": roughness,
            "metalness": metalness,
            "roughnessAdjust": roughness_adjust,
            "metalnessAdjust": metalness_adjust,
            "normalTexture": normal_texture,
        }


class Map3DLambertMaterialOpts(BasicOpts):
    def __init__(
        self,
        detail_texture: Optional[JSFunc] = None,
        texture_tiling: Numeric = 1,
        texture_offset: Numeric = 0,
    ):
        self.opts: dict = {
            "detailTexture": detail_texture,
            "textureTiling": texture_tiling,
            "textureOffset": texture_offset,
        }


class Map3DColorMaterialOpts(BasicOpts):
    def __init__(
        self,
        detail_texture: Optional[JSFunc] = None,
        texture_tiling: Numeric = 1,
        texture_offset: Numeric = 0,
    ):
        self.opts: dict = {
            "detailTexture": detail_texture,
            "textureTiling": texture_tiling,
            "textureOffset": texture_offset,
        }


class Map3DLightOpts(BasicOpts):
    def __init__(
        self,
        main_color: str = "#fff",
        main_intensity: Numeric = 1,
        is_main_shadow: bool = False,
        main_shadow_quality: str = "medium",
        main_alpha: Numeric = 40,
        main_beta: Numeric = 40,
        ambient_color: str = "#fff",
        ambient_intensity: Numeric = 0.2,
        ambient_cubemap_texture: Optional[str] = None,
        ambient_cubemap_diffuse_intensity: Numeric = 0.5,
        ambient_cubemap_specular_intensity: Numeric = 0.5,
    ):
        self.opts: dict = {
            "main": {
                "color": main_color,
                "intensity": main_intensity,
                "shadow": is_main_shadow,
                "shadowQuality": main_shadow_quality,
                "alpha": main_alpha,
                "beta": main_beta,
            },
            "ambient": {"color": ambient_color, "intensity": ambient_intensity},
            "ambientCubemap": {
                "texture": ambient_cubemap_texture,
                "diffuseIntensity": ambient_cubemap_diffuse_intensity,
                "specularIntensity": ambient_cubemap_specular_intensity,
            },
        }


class Map3DPostEffectOpts(BasicOpts):
    def __init__(
        self,
        is_enable: bool = False,
        is_bloom_enable: bool = False,
        bloom_intensity: Numeric = 0.1,
        is_depth_field_enable: bool = False,
        depth_field_focal_distance: Numeric = 50,
        depth_field_focal_range: Numeric = 20,
        depth_field_fstop: Numeric = 2.8,
        depth_field_blur_radius: Numeric = 10,
        is_ssao_enable: bool = False,
        ssao_quality: str = "medium",
        ssao_radius: Numeric = 2,
        ssao_intensity: Numeric = 1,
        is_color_correction_enable: bool = False,
        color_correction_lookup_texture: Optional[JSFunc] = None,
        color_correction_exposure: Numeric = 0,
        color_correction_brightness: Numeric = 0,
        color_correction_contrast: Numeric = 1,
        color_correction_saturation: Numeric = 1,
        is_fxaa_enable: bool = False,
    ):
        self.opts: dict = {
            "enable": is_enable,
            "bloom": {"enable": is_bloom_enable, "bloomIntensity": bloom_intensity},
            "depthOfField": {
                "enable": is_depth_field_enable,
                "focalDistance": depth_field_focal_distance,
                "focalRange": depth_field_focal_range,
                "fstop": depth_field_fstop,
                "blurRadius": depth_field_blur_radius,
            },
            "SSAO": {
                "enable": is_ssao_enable,
                "quality": ssao_quality,
                "radius": ssao_radius,
                "intensity": ssao_intensity,
            },
            "colorCorrection": {
                "enable": is_color_correction_enable,
                "lookupTexture": color_correction_lookup_texture,
                "exposure": color_correction_exposure,
                "brightness": color_correction_brightness,
                "contrast": color_correction_contrast,
                "saturation": color_correction_saturation,
            },
            "FXAA": {"enable": is_fxaa_enable},
        }


class Map3DViewControlOpts(BasicOpts):
    def __init__(
        self,
        projection: str = "perspective",
        auto_rotate: bool = False,
        auto_rotate_direction: str = "cw",
        auto_rotate_speed: Numeric = 10,
        auto_rotate_after_still: Numeric = 3,
        damping: Numeric = 0.8,
        rotate_sensitivity: Union[Numeric, Sequence] = 1,
        zoom_sensitivity: Numeric = 1,
        pan_sensitivity: Numeric = 1,
        pan_mouse_button: str = "left",
        rotate_mouse_button: str = "middle",
        distance: Numeric = 100,
        min_distance: Numeric = 40,
        max_distance: Numeric = 400,
        orthographic_size: Numeric = 100,
        min_orthographic_size: Numeric = 20,
        max_orthographic_size: Numeric = 400,
        alpha: Numeric = 40,
        beta: Numeric = 0,
        center: Optional[Sequence] = None,
        min_alpha: Numeric = 5,
        max_alpha: Numeric = 90,
        min_beta: Numeric = -80,
        max_beta: Numeric = 80,
        animation: bool = True,
        animation_duration_update: Numeric = 1000,
        animation_easing_update: str = "cubicInOut",
    ):
        self.opts: dict = {
            "projection": projection,
            "autoRotate": auto_rotate,
            "autoRotateDirection": auto_rotate_direction,
            "autoRotateSpeed": auto_rotate_speed,
            "autoRotateAfterStill": auto_rotate_after_still,
            "damping": damping,
            "rotateSensitivity": rotate_sensitivity,
            "zoomSensitivity": zoom_sensitivity,
            "panSensitivity": pan_sensitivity,
            "panMouseButton": pan_mouse_button,
            "rotateMouseButton": rotate_mouse_button,
            "distance": distance,
            "minDistance": min_distance,
            "maxDistance": max_distance,
            "orthographicSize": orthographic_size,
            "minOrthographicSize": min_orthographic_size,
            "maxOrthographicSize": max_orthographic_size,
            "alpha": alpha,
            "beta": beta,
            "center": center,
            "minAlpha": min_alpha,
            "maxAlpha": max_alpha,
            "minBeta": min_beta,
            "maxBeta": max_beta,
            "animation": animation,
            "animationDurationUpdate": animation_duration_update,
            "animationEasingUpdate": animation_easing_update,
        }


class GlobeLayersOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        type_: str = "overlay",
        name: Optional[str] = None,
        blend_to: str = "albedo",
        intensity: Numeric = 1,
        shading: str = "lambert",
        distance: Optional[Numeric] = None,
        texture: Union[JSFunc, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "type": type_,
            "name": name,
            "blendTo": blend_to,
            "intensity": intensity,
            "shading": shading,
            "distance": distance,
            "texture": texture,
        }


class BarBackgroundStyleOpts(BasicOpts):
    def __init__(
        self,
        color: str = "rgba(180, 180, 180, 0.2)",
        border_color: str = "#000",
        border_width: Numeric = 0,
        border_type: str = "solid",
        border_radius: Union[Numeric, Sequence] = 0,
        shadow_blur: Optional[Numeric] = None,
        shadow_color: Optional[str] = None,
        shadow_offset_x: Numeric = 0,
        shadow_offset_y: Numeric = 0,
        opacity: Optional[Numeric] = None,
    ):
        self.opts: dict = {
            "color": color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderType": border_type,
            "borderRadius": border_radius,
            "shadowBlur": shadow_blur,
            "shadowColor": shadow_color,
            "shadowOffsetX": shadow_offset_x,
            "shadowOffsetY": shadow_offset_y,
            "opacity": opacity,
        }


class GaugeTitleOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        offset_center: Sequence = None,
        color: str = "#333",
        font_style: str = "normal",
        font_weight: str = "normal",
        font_family: str = "sans-serif",
        font_size: Numeric = 15,
        background_color: str = "transparent",
        border_color: str = "transparent",
        border_width: Numeric = 0,
        border_radius: Numeric = 0,
        padding: Numeric = 0,
        shadow_color: Optional[str] = "transparent",
        shadow_blur: Optional[Numeric] = 0,
        shadow_offset_x: Numeric = 0,
        shadow_offset_y: Numeric = 0,
        overflow: Optional[str] = "none",
        rich: Optional[dict] = None,
        is_value_animation: bool = True,
    ):
        if offset_center is None:
            offset_center = [0, "-40%"]
        self.opts: dict = {
            "show": is_show,
            "offsetCenter": offset_center,
            "color": color,
            "fontStyle": font_style,
            "fontWeight": font_weight,
            "fontFamily": font_family,
            "fontSize": font_size,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderRadius": border_radius,
            "padding": padding,
            "shadowColor": shadow_color,
            "shadowBlur": shadow_blur,
            "shadowOffsetX": shadow_offset_x,
            "shadowOffsetY": shadow_offset_y,
            "overflow": overflow,
            "rich": rich,
            "valueAnimation": is_value_animation,
        }


class GaugeDetailOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        background_color: str = "transparent",
        border_width: Numeric = 0,
        border_color: str = "transparent",
        offset_center: Sequence = None,
        formatter: Optional[JSFunc] = None,
        color: str = "#464646",
        font_style: str = "normal",
        font_weight: str = "normal",
        font_family: str = "sans-serif",
        font_size: Numeric = 15,
        border_radius: Numeric = 0,
        padding: Numeric = 0,
        shadow_color: Optional[str] = "transparent",
        shadow_blur: Optional[Numeric] = 0,
        shadow_offset_x: Numeric = 0,
        shadow_offset_y: Numeric = 0,
        overflow: Optional[str] = "none",
        rich: Optional[dict] = None,
        is_value_animation: bool = True,
    ):
        if offset_center is None:
            offset_center = [0, "-40%"]
        self.opts: dict = {
            "show": is_show,
            "backgroundColor": background_color,
            "borderWidth": border_width,
            "borderColor": border_color,
            "offsetCenter": offset_center,
            "formatter": formatter,
            "color": color,
            "fontStyle": font_style,
            "fontWeight": font_weight,
            "fontFamily": font_family,
            "fontSize": font_size,
            "borderRadius": border_radius,
            "padding": padding,
            "shadowColor": shadow_color,
            "shadowBlur": shadow_blur,
            "shadowOffsetX": shadow_offset_x,
            "shadowOffsetY": shadow_offset_y,
            "overflow": overflow,
            "rich": rich,
            "valueAnimation": is_value_animation,
        }


class GaugeProgressOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        is_overlap: bool = True,
        width: Numeric = 10,
        is_round_cap: bool = False,
        is_clip: bool = False,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "overlap": is_overlap,
            "width": width,
            "roundCap": is_round_cap,
            "clip": is_clip,
            "itemStyle": itemstyle_opts,
        }


class GaugePointerOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        length: Union[str, Numeric] = "80%",
        width: Numeric = 8,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "length": length,
            "width": width,
            "itemStyle": itemstyle_opts,
        }


class GaugeAnchorOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        is_show_above: bool = False,
        size: Numeric = 6,
        icon: str = "circle",
        offset_center: Optional[Sequence] = None,
        is_keep_aspect: bool = False,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        if offset_center is None:
            offset_center = [0, 0]
        self.opts: dict = {
            "show": is_show,
            "showAbove": is_show_above,
            "size": size,
            "icon": icon,
            "offsetCenter": offset_center,
            "keepAspect": is_keep_aspect,
            "itemStyle": itemstyle_opts,
        }


class PieLabelLineOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        is_show_above: bool = False,
        length: Numeric = 15,
        length_2: Numeric = 15,
        smooth: Union[bool, Numeric] = False,
        min_turn_angle: Numeric = 90,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        max_surface_angle: Numeric = 90,
    ):
        self.opts: dict = {
            "show": is_show,
            "showAbove": is_show_above,
            "length": length,
            "length2": length_2,
            "smooth": smooth,
            "minTurnAngle": min_turn_angle,
            "lineStyle": linestyle_opts,
            "maxSurfaceAngle": max_surface_angle,
        }


class PieEmptyCircleStyle(BasicOpts):
    def __init__(
        self,
        color: str = "lightgray",
        border_color: str = "#000",
        border_width: Numeric = 0,
        border_type: str = "solid",
        border_dash_offset: Numeric = 0,
        border_cap: str = "butt",
        border_join: str = "bevel",
        border_miter_limit: Numeric = 10,
        opacity: Numeric = 1,
    ):
        self.opts: dict = {
            "color": color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderType": border_type,
            "borderDashOffset": border_dash_offset,
            "borderCap": border_cap,
            "borderJoin": border_join,
            "borderMiterLimit": border_miter_limit,
            "opacity": opacity,
        }


class TimelineCheckPointerStyle(BasicOpts):
    def __init__(
        self,
        symbol: str = "circle",
        symbol_size: Union[Numeric, Sequence[Numeric]] = 13,
        symbol_rotate: Optional[Numeric] = None,
        symbol_keep_aspect: bool = False,
        symbol_offset: Optional[Sequence[Union[str, Numeric]]] = None,
        color: str = "#c23531",
        border_width: Numeric = 5,
        border_color: str = "rgba(194,53,49,0.5)",
        is_animation: bool = True,
        animation_duration: Numeric = 300,
        animation_easing: str = "quinticInOut",
    ):
        if symbol_offset is None:
            symbol_offset = [0, 0]

        self.opts: dict = {
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "symbolKeepAspect": symbol_keep_aspect,
            "symbolOffset": symbol_offset,
            "color": color,
            "borderWidth": border_width,
            "borderColor": border_color,
            "animation": is_animation,
            "animationDuration": animation_duration,
            "animationEasing": animation_easing,
        }


class TimelineControlStyle(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        is_show_play_button: bool = True,
        is_show_prev_button: bool = True,
        is_show_next_button: bool = True,
        item_size: Numeric = 22,
        item_gap: Numeric = 12,
        position: str = "left",
        play_icon: Optional[str] = None,
        stop_icon: Optional[str] = None,
        prev_icon: Optional[str] = None,
        next_icon: Optional[str] = None,
        color: str = "#304654",
        border_color: str = "#304654",
        border_width: Numeric = 1,
    ):
        self.opts: dict = {
            "show": is_show,
            "showPlayBtn": is_show_play_button,
            "showPrevBtn": is_show_prev_button,
            "showNextBtn": is_show_next_button,
            "itemSize": item_size,
            "itemGap": item_gap,
            "position": position,
            "playIcon": play_icon,
            "stopIcon": stop_icon,
            "prevIcon": prev_icon,
            "nextIcon": next_icon,
            "color": color,
            "borderColor": border_color,
            "borderWidth": border_width,
        }


class TabChartGlobalOpts(BasicOpts):
    def __init__(
        self,
        is_enable: bool = False,
        tab_base_css: Optional[dict] = None,
        tab_button_css: Optional[dict] = None,
        tab_button_hover_css: Optional[dict] = None,
        tab_button_active_css: Optional[dict] = None,
    ):
        self.opts: dict = {
            "enable": is_enable,
            "base": tab_base_css,
            "button_base": tab_button_css,
            "button_hover": tab_button_hover_css,
            "button_active": tab_button_active_css,
        }


class GraphGLNode(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        x: Optional[Numeric] = None,
        y: Optional[Numeric] = None,
        value: Union[str, Numeric, Sequence, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "x": x,
            "y": y,
            "value": value,
            "itemStyle": itemstyle_opts,
        }


class GraphGLLink(BasicOpts):
    def __init__(
        self,
        source: Union[str, int, None] = None,
        target: Union[str, int, None] = None,
        value: Optional[Numeric] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "source": source,
            "target": target,
            "value": value,
            "lineStyle": linestyle_opts,
        }


class GeoRegionsOpts(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        is_selected: bool = False,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        emphasis_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        emphasis_label_opts: Union[LabelOpts, dict, None] = None,
        select_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        select_label_opts: Union[LabelOpts, dict, None] = None,
        blur_itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        blur_label_opts: Union[LabelOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
        is_silent: bool = False,
    ):
        self.opts: dict = {
            "name": name,
            "selected": is_selected,
            "itemStyle": itemstyle_opts,
            "label": label_opts,
            "emphasis": {
                "itemStyle": emphasis_itemstyle_opts,
                "label": emphasis_label_opts,
            },
            "select": {
                "itemStyle": select_itemstyle_opts,
                "label": select_label_opts,
            },
            "blur": {
                "itemStyle": blur_itemstyle_opts,
                "label": blur_label_opts,
            },
            "tooltip": tooltip_opts,
            "silent": is_silent,
        }


class SunburstLabelLineOpts(BasicOpts):
    def __init__(
        self,
        is_show: Optional[bool] = None,
        is_show_above: Optional[bool] = None,
        length_2: Optional[Numeric] = None,
        smooth: Union[bool, Numeric] = False,
        min_turn_angle: Optional[Numeric] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "showAbove": is_show_above,
            "length2": length_2,
            "smooth": smooth,
            "minTurnAngle": min_turn_angle,
            "lineStyle": linestyle_opts,
        }


class SunburstLabelLayoutOpts(BasicOpts):
    def __init__(
        self,
        is_hide_overlap: Optional[bool] = None,
        is_move_overlap: Optional[bool] = None,
        rotate: Optional[Numeric] = None,
        width: Optional[Numeric] = None,
        height: Optional[Numeric] = None,
        align: Optional[str] = None,
        vertical_align: Optional[str] = None,
        font_size: Optional[Numeric] = None,
        is_draggable: Optional[bool] = None,
    ):
        self.opts: dict = {
            "hideOverlap": is_hide_overlap,
            "moveOverlap": is_move_overlap,
            "rotate": rotate,
            "width": width,
            "height": height,
            "align": align,
            "verticalAlign": vertical_align,
            "fontSize": font_size,
            "draggable": is_draggable,
        }


class SunburstLevelOpts(BasicOpts):
    def __init__(
        self,
        radius: Optional[Sequence] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        label_line_opts: Union[SunburstLabelLineOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "radius": radius,
            "label": label_opts,
            "labelLine": label_line_opts,
            "itemStyle": itemstyle_opts,
        }


# Data Item
class BarItem(BasicOpts):
    def __init__(
        self,
        name: Union[int, str, None],
        value: Numeric,
        *,
        group_id: Optional[str] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        is_show_label_line: Optional[bool] = None,
        label_line_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        label_line = None
        if is_show_label_line:
            label_line = {
                "show": is_show_label_line,
                "lineStyle": label_line_linestyle_opts,
            }

        self.opts: dict = {
            "name": name,
            "value": value,
            "groupId": group_id,
            "label": label_opts,
            "labelLine": label_line,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class BoxplotItem(BasicOpts):
    def __init__(
        self,
        name: Union[int, str],
        value: Sequence,
        *,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class CandleStickItem(BasicOpts):
    def __init__(
        self,
        name: Union[str, int],
        value: Sequence,
        *,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class EffectScatterItem(BasicOpts):
    def __init__(
        self,
        name: Union[str, Numeric],
        value: Union[str, Numeric],
        *,
        symbol: Optional[str] = None,
        symbol_size: Union[Sequence[Numeric], Numeric] = None,
        symbol_rotate: Optional[Numeric] = None,
        symbol_keep_aspect: bool = False,
        symbol_offset: Optional[Sequence] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "symbolKeepAspect": symbol_keep_aspect,
            "symbolOffset": symbol_offset,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class FunnelItem(BasicOpts):
    def __init__(
        self,
        name: Union[str, int],
        value: Union[Sequence, str, Numeric],
        *,
        is_show_label_line: Optional[bool] = None,
        label_line_width: Optional[int] = None,
        label_line_linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "labelLine": {
                "show": is_show_label_line,
                "length": label_line_width,
                "lineStyle": label_line_linestyle_opts,
            },
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class LineItem(BasicOpts):
    def __init__(
        self,
        name: Union[str, Numeric] = None,
        value: Union[str, Numeric] = None,
        *,
        symbol: Optional[str] = "circle",
        symbol_size: Numeric = 4,
        symbol_rotate: Optional[Numeric] = None,
        symbol_keep_aspect: bool = False,
        symbol_offset: Optional[Sequence] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "symbolKeepAspect": symbol_keep_aspect,
            "symbolOffset": symbol_offset,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class MapItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        value: Union[Sequence, Numeric, str] = None,
        group_id: Optional[str] = None,
        is_selected: bool = False,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "groupId": group_id,
            "selected": is_selected,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class GeoItem(BasicOpts):
    def __init__(
        self,
        longitude: Numeric,
        latitude: Numeric,
        name: str,
        value: Union[Sequence, Numeric, str] = None,
    ):
        self.opts: dict = {
            "longitude": longitude,
            "latitude": latitude,
            "name": name,
            "value": value,
        }


class ParallelItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        value: Optional[Sequence] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        color: Union[str, dict] = "#000",
        width: Numeric = 2,
        type_: str = "solid",
        dash_offset: Numeric = 0,
        cap: str = "butt",
        join: str = "bevel",
        miter_limit: Optional[Numeric] = None,
        opacity: Numeric = 0.45,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "lineStyle": linestyle_opts,
            "color": color,
            "width": width,
            "type": type_,
            "dashOffset": dash_offset,
            "cap": cap,
            "join": join,
            "miterLimit": miter_limit,
            "opacity": opacity,
        }


class PieItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        value: Optional[Numeric] = None,
        group_id: Optional[str] = None,
        is_selected: bool = False,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
        label_line_opts: Union[PieLabelLineOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "groupId": group_id,
            "selected": is_selected,
            "label": label_opts,
            "labelLine": label_line_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class RadarItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        value: Union[Sequence, Numeric, str] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[Sequence[Numeric], Numeric] = None,
        symbol_rotate: Optional[Numeric] = None,
        symbol_keep_aspect: bool = False,
        symbol_offset: Optional[Sequence] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        areastyle_opts: Union[AreaStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "symbolKeepAspect": symbol_keep_aspect,
            "symbolOffset": symbol_offset,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
            "lineStyle": linestyle_opts,
            "areaStyle": areastyle_opts,
        }


class ScatterItem(BasicOpts):
    def __init__(
        self,
        name: Union[str, Numeric] = None,
        value: Union[str, Numeric] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[Sequence[Numeric], Numeric] = None,
        symbol_rotate: Optional[Numeric] = None,
        symbol_keep_aspect: bool = False,
        symbol_offset: Optional[Sequence] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        tooltip_opts: Union[TooltipOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "symbolRotate": symbol_rotate,
            "symbolKeepAspect": symbol_keep_aspect,
            "symbolOffset": symbol_offset,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "tooltip": tooltip_opts,
        }


class SunburstItem(BasicOpts):
    def __init__(
        self,
        value: Optional[Numeric] = None,
        name: Optional[str] = None,
        link: Optional[str] = None,
        target: Optional[str] = "blank",
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
        children: Optional[Sequence] = None,
    ):
        self.opts: dict = {
            "value": value,
            "name": name,
            "link": link,
            "target": target,
            "label": label_opts,
            "itemStyle": itemstyle_opts,
            "children": children,
        }


class ThemeRiverItem(BasicOpts):
    def __init__(
        self,
        date: Optional[str] = None,
        value: Optional[Numeric] = None,
        name: Optional[str] = None,
    ):
        self.opts: dict = {"date": date, "value": value, "name": name}


class TreeItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        value: Optional[Numeric] = None,
        label_opts: Union[LabelOpts, dict, None] = None,
        children: Optional[Sequence] = None,
    ):
        self.opts: dict = {
            "name": name,
            "value": value,
            "children": children,
            "label": label_opts,
        }



File: pyecharts/options/series_options.py

from typing import Any, Optional, Sequence, Tuple, Union

from ..commons.utils import JsCode

Numeric = Union[int, float]
JSFunc = Union[str, JsCode]


class BasicOpts:
    __slots__ = ("opts",)

    def update(self, **kwargs):
        self.opts.update(kwargs)

    def get(self, key: str) -> Any:
        return self.opts.get(key)


class ItemStyleOpts(BasicOpts):
    def __init__(
        self,
        color: Optional[JSFunc] = None,
        color0: Optional[str] = None,
        border_color: Optional[str] = None,
        border_color0: Optional[str] = None,
        border_width: Optional[Numeric] = None,
        border_type: Optional[str] = None,
        border_radius: Optional[Numeric] = None,
        opacity: Optional[Numeric] = None,
        area_color: Optional[str] = None,
    ):
        self.opts: dict = {
            "color": color,
            "color0": color0,
            "borderColor": border_color,
            "borderColor0": border_color0,
            "borderWidth": border_width,
            "borderType": border_type,
            "borderRadius": border_radius,
            "opacity": opacity,
            "areaColor": area_color,
        }


class TextStyleOpts(BasicOpts):
    def __init__(
        self,
        color: Optional[str] = None,
        font_style: Optional[str] = None,
        font_weight: Optional[str] = None,
        font_family: Optional[str] = None,
        font_size: Optional[Numeric] = None,
        align: Optional[str] = None,
        vertical_align: Optional[str] = None,
        line_height: Optional[str] = None,
        background_color: Optional[str] = None,
        border_color: Optional[str] = None,
        border_width: Optional[Numeric] = None,
        border_radius: Union[Numeric, Sequence, None] = None,
        padding: Union[Numeric, Sequence, None] = None,
        shadow_color: Optional[str] = None,
        shadow_blur: Optional[Numeric] = None,
        width: Optional[str] = None,
        height: Optional[str] = None,
        rich: Optional[dict] = None,
    ):
        self.opts: dict = {
            "color": color,
            "fontStyle": font_style,
            "fontWeight": font_weight,
            "fontFamily": font_family,
            "fontSize": font_size,
            "align": align,
            "verticalAlign": vertical_align,
            "lineHeight": line_height,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderRadius": border_radius,
            "padding": padding,
            "shadowColor": shadow_color,
            "shadowBlur": shadow_blur,
            "width": width,
            "height": height,
            "rich": rich,
        }


class LabelOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        position: Optional[Union[str, Sequence]] = None,
        color: Optional[str] = None,
        distance: Union[Numeric, Sequence, None] = None,
        font_size: Optional[Numeric] = None,
        font_style: Optional[str] = None,
        font_weight: Optional[str] = None,
        font_family: Optional[str] = None,
        rotate: Optional[Numeric] = None,
        margin: Optional[Numeric] = 8,
        interval: Union[Numeric, str, None] = None,
        horizontal_align: Optional[str] = None,
        vertical_align: Optional[str] = None,
        formatter: Optional[JSFunc] = None,
        background_color: Optional[str] = None,
        border_color: Optional[str] = None,
        border_width: Optional[Numeric] = None,
        border_radius: Optional[Numeric] = None,
        padding: Union[Numeric, Sequence[Numeric], None] = None,
        text_width: Optional[Numeric] = None,
        text_height: Optional[Numeric] = None,
        text_border_color: Optional[str] = None,
        text_border_width: Optional[Numeric] = None,
        text_shadow_color: Optional[str] = None,
        text_shadow_blur: Optional[Numeric] = None,
        text_shadow_offset_x: Optional[Numeric] = None,
        text_shadow_offset_y: Optional[Numeric] = None,
        overflow: Optional[str] = None,
        rich: Optional[dict] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "position": position,
            "color": color,
            "distance": distance,
            "rotate": rotate,
            "margin": margin,
            "interval": interval,
            "fontSize": font_size,
            "fontStyle": font_style,
            "fontWeight": font_weight,
            "fontFamily": font_family,
            "align": horizontal_align,
            "verticalAlign": vertical_align,
            "formatter": formatter,
            "backgroundColor": background_color,
            "borderColor": border_color,
            "borderWidth": border_width,
            "borderRadius": border_radius,
            "padding": padding,
            "width": text_width,
            "height": text_height,
            "textBorderColor": text_border_color,
            "textBorderWidth": text_border_width,
            "textShadowColor": text_shadow_color,
            "textShadowBlur": text_shadow_blur,
            "textShadowOffsetX": text_shadow_offset_x,
            "textShadowOffsetY": text_shadow_offset_y,
            "overflow": overflow,
            "rich": rich,
        }


class LineStyleOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        width: Numeric = 1,
        opacity: Numeric = 1,
        curve: Numeric = 0,
        type_: str = "solid",
        color: Union[str, Sequence, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "width": width,
            "opacity": opacity,
            "curveness": curve,
            "type": type_,
            "color": color,
        }


class SplitLineOpts(BasicOpts):
    def __init__(
        self, is_show: bool = False, linestyle_opts: LineStyleOpts = LineStyleOpts()
    ):
        self.opts: dict = {"show": is_show, "lineStyle": linestyle_opts}


class MarkPointItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        type_: Optional[str] = None,
        value_index: Optional[Numeric] = None,
        value_dim: Optional[str] = None,
        coord: Optional[Sequence] = None,
        x: Optional[Numeric] = None,
        y: Optional[Numeric] = None,
        value: Optional[Numeric] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[Numeric, Sequence, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "name": name,
            "type": type_,
            "valueIndex": value_index,
            "valueDim": value_dim,
            "coord": coord,
            "x": x,
            "y": y,
            "value": value,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "itemStyle": itemstyle_opts,
        }


class MarkPointOpts(BasicOpts):
    def __init__(
        self,
        data: Sequence[Union[MarkPointItem, dict]] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[None, Numeric] = None,
        label_opts: LabelOpts = LabelOpts(position="inside", color="#fff"),
    ):
        self.opts: dict = {
            "symbol": symbol,
            "symbolSize": symbol_size,
            "label": label_opts,
            "data": data,
        }


class MarkLineItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        type_: Optional[str] = None,
        x: Union[str, Numeric, None] = None,
        xcoord: Union[str, Numeric, None] = None,
        y: Union[str, Numeric, None] = None,
        ycoord: Union[str, Numeric, None] = None,
        value_index: Optional[Numeric] = None,
        value_dim: Optional[str] = None,
        coord: Optional[Sequence] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
        symbol: Optional[str] = None,
        symbol_size: Optional[Numeric] = None,
    ):
        self.opts: dict = {
            "name": name,
            "type": type_,
            "valueIndex": value_index,
            "valueDim": value_dim,
            "xAxis": x,
            "x": xcoord,
            "yAxis": y,
            "y": ycoord,
            "lineStyle": linestyle_opts,
            "coord": coord,
            "symbol": symbol,
            "symbolSize": symbol_size,
        }


class MarkLineOpts(BasicOpts):
    def __init__(
        self,
        is_silent: bool = False,
        data: Sequence[Union[MarkLineItem, dict]] = None,
        symbol: Optional[str] = None,
        symbol_size: Union[None, Numeric] = None,
        precision: int = 2,
        label_opts: LabelOpts = LabelOpts(),
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "silent": is_silent,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "precision": precision,
            "label": label_opts,
            "lineStyle": linestyle_opts,
            "data": data,
        }


class MarkAreaItem(BasicOpts):
    def __init__(
        self,
        name: Optional[str] = None,
        type_: Tuple[Optional[str], Optional[str]] = (None, None),
        value_index: Tuple[Optional[Numeric], Optional[Numeric]] = (None, None),
        value_dim: Tuple[Optional[str], Optional[str]] = (None, None),
        x: Tuple[Union[str, Numeric, None], Union[str, Numeric, None]] = (None, None),
        y: Tuple[Union[str, Numeric, None], Union[str, Numeric, None]] = (None, None),
        label_opts: Union[LabelOpts, dict, None] = None,
        itemstyle_opts: Union[ItemStyleOpts, dict, None] = None,
    ):
        self.opts: Sequence = [
            {
                "name": name,
                "type": type_[0],
                "valueIndex": value_index[0],
                "valueDim": value_dim[0],
                "xAxis": x[0],
                "yAxis": y[0],
                "label": label_opts,
                "itemStyle": itemstyle_opts,
            },
            {
                "type": type_[1],
                "valueIndex": value_index[1],
                "valueDim": value_dim[1],
                "xAxis": x[1],
                "yAxis": y[1],
            },
        ]


class MarkAreaOpts(BasicOpts):
    def __init__(
        self,
        is_silent: bool = False,
        label_opts: LabelOpts = LabelOpts(),
        data: Sequence[Union[MarkAreaItem, Sequence, dict]] = None,
        itemstyle_opts: ItemStyleOpts = None,
    ):
        self.opts: dict = {
            "silent": is_silent,
            "label": label_opts,
            "data": data,
            "itemStyle": itemstyle_opts,
        }


class EffectOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        brush_type: str = "stroke",
        scale: Numeric = 2.5,
        period: Numeric = 4,
        color: Optional[str] = None,
        symbol: Optional[str] = None,
        symbol_size: Optional[Numeric] = None,
        trail_length: Optional[Numeric] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "brushType": brush_type,
            "scale": scale,
            "period": period,
            "color": color,
            "symbol": symbol,
            "symbolSize": symbol_size,
            "trailLength": trail_length,
        }


class Lines3DEffectOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        period: Numeric = 4,
        constant_speed: Optional[Numeric] = None,
        trail_width: Numeric = 4,
        trail_length: Numeric = 0.1,
        trail_color: Optional[str] = None,
        trail_opacity: Optional[Numeric] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "period": period,
            "constantSpeed": constant_speed,
            "trailWidth": trail_width,
            "trailLength": trail_length,
            "trailColor": trail_color,
            "trailOpacity": trail_opacity,
        }


class AreaStyleOpts(BasicOpts):
    def __init__(self, opacity: Optional[Numeric] = 0, color: Optional[JSFunc] = None):
        self.opts: dict = {"opacity": opacity, "color": color}


class SplitAreaOpts(BasicOpts):
    def __init__(self, is_show=True, areastyle_opts: AreaStyleOpts = AreaStyleOpts()):
        self.opts: dict = {"show": is_show, "areaStyle": areastyle_opts}


class TreeMapBreadcrumbOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = True,
        pos_left: Union[str, Numeric] = "center",
        pos_right: Union[str, Numeric] = "auto",
        pos_top: Union[str, Numeric] = "auto",
        pos_bottom: Union[str, Numeric] = 0,
        height: Numeric = 22,
        empty_item_width: Numeric = 25,
        item_opts: ItemStyleOpts = ItemStyleOpts(),
    ):
        self.opts: dict = {
            "show": is_show,
            "left": pos_left,
            "right": pos_right,
            "top": pos_top,
            "bottom": pos_bottom,
            "height": height,
            "emptyItemWidth": empty_item_width,
            "itemStyle": item_opts,
        }


class MinorTickOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        split_number: Numeric = 5,
        length: Numeric = 3,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "splitNumber": split_number,
            "length": length,
            "lineStyle": linestyle_opts,
        }


class MinorSplitLineOpts(BasicOpts):
    def __init__(
        self,
        is_show: bool = False,
        width: Numeric = 1,
        type_: str = "solid",
        opacity: Union[Numeric, None] = None,
        linestyle_opts: Union[LineStyleOpts, dict, None] = None,
    ):
        self.opts: dict = {
            "show": is_show,
            "width": width,
            "type": type_,
            "opacity": opacity,
            "lineStyle": linestyle_opts,
        }


class GraphGLForceAtlas2Opts(BasicOpts):
    def __init__(
        self,
        is_gpu: bool = True,
        steps: Numeric = 1,
        stop_threshold: Numeric = 1,
        is_barnes_hut_optimize: Optional[bool] = None,
        is_repulsion_by_degree: bool = True,
        is_lin_log_mode: bool = False,
        gravity: Numeric = 1,
        gravity_center: Optional[Sequence] = None,
        scaling: Optional[Numeric] = None,
        edge_weight_influence: Numeric = 1,
        edge_weight: Union[Sequence, Numeric] = None,
        node_weight: Union[Sequence, Numeric] = None,
        is_prevent_overlap: bool = False,
    ):
        self.opts: dict = {
            "GPU": is_gpu,
            "steps": steps,
            "stopThreshold": stop_threshold,
            "barnesHutOptimize": is_barnes_hut_optimize,
            "repulsionByDegree": is_repulsion_by_degree,
            "linLogMode": is_lin_log_mode,
            "gravity": gravity,
            "gravityCenter": gravity_center,
            "scaling": scaling,
            "edgeWeightInfluence": edge_weight_influence,
            "edgeWeight": edge_weight,
            "nodeWeight": node_weight,
            "preventOverlap": is_prevent_overlap,
        }



File: pyecharts/commons/__init__.py




File: pyecharts/commons/utils.py

import re

from ..datasets import EXTRA, FILENAMES


class JsCode:
    def __init__(self, js_code: str):
        self.js_code = "--x_x--0_0--" + js_code + "--x_x--0_0--"

    def replace(self, pattern: str, repl: str):
        self.js_code = re.sub(pattern, repl, self.js_code)
        return self


class OrderedSet:
    def __init__(self, *args):
        self._values = dict()
        self.items = []
        for a in args:
            self.add(a)

    def add(self, *items):
        for item in items:
            if not self._values.get(item, False):
                self._values.update({item: True})
                self.items.append(item)


def produce_require_dict(js_dependencies, js_host) -> dict:
    confs, libraries = [], []
    for name in js_dependencies.items:
        if name.startswith("https://api.map.baidu.com"):
            confs.append("'baidu_map_api{}':'{}'".format(len(name), name))
            libraries.append("'baidu_map_api{}'".format(len(name)))
        if name in FILENAMES:
            f, _ = FILENAMES[name]
            confs.append("'{}':'{}{}'".format(name, js_host, f))
            libraries.append("'{}'".format(name))
        else:
            for url, files in EXTRA.items():
                if name in files:
                    f, _ = files[name]
                    confs.append("'{}':'{}{}'".format(name, url, f))
                    libraries.append("'{}'".format(name))
                    break
    return dict(config_items=confs, libraries=libraries)


def replace_placeholder(html: str) -> str:
    return re.sub('"?--x_x--0_0--"?', "", html)


def replace_placeholder_with_quotes(html: str) -> str:
    return re.sub("--x_x--0_0--", "", html)


# def _flat(obj):
#     if hasattr(obj, "js_dependencies"):
#         return list(obj.js_dependencies)
#
#     if isinstance(obj, (list, tuple, set)):
#         return obj
#
#     return (obj,)  # tuple


def _expand(dict_generator):
    return dict(list(dict_generator))


def _clean_dict(mydict):
    for key, value in mydict.items():
        if value is not None:
            if isinstance(value, dict):
                value = _expand(_clean_dict(value))

            elif isinstance(value, (list, tuple, set)):
                value = list(_clean_array(value))

            # Not elegant, but effective and less code-intrusive.
            elif type(value).__name__ in ["ndarray", "Series"]:
                raise ValueError(
                    "Can't use non-native data structures "
                    "as axis data to render chart"
                )

            elif isinstance(value, str) and not value:
                # delete key with empty string
                continue

            yield key, value


def _clean_array(myarray):
    for value in myarray:
        if isinstance(value, dict):
            yield _expand(_clean_dict(value))

        elif isinstance(value, (list, tuple, set)):
            yield list(_clean_array(value))

        else:
            yield value


def remove_key_with_none_value(incoming_dict):
    if isinstance(incoming_dict, dict):
        return _expand(_clean_dict(incoming_dict))
    elif incoming_dict:
        return incoming_dict
    else:
        return None



File: pyecharts/datasets/__init__.py

import difflib
import os
import typing
import urllib.request

import simplejson as json


class FuzzyDict(dict):
    """Provides a dictionary that performs fuzzy lookup"""

    def __init__(self, cutoff: float = 0.6):
        """Construct a new FuzzyDict instance

        items is an dictionary to copy items from (optional)
        cutoff is the match ratio below which matches should not be considered
        cutoff needs to be a float between 0 and 1 (where zero is no match
        and 1 is a perfect match)"""
        super(FuzzyDict, self).__init__()
        self.cutoff = cutoff

        # short wrapper around some super (dict) methods
        self._dict_contains = lambda key: super(FuzzyDict, self).__contains__(key)
        self._dict_getitem = lambda key: super(FuzzyDict, self).__getitem__(key)

    def _search(self, lookfor: typing.Any, stop_on_first: bool = False):
        """Returns the value whose key best matches lookfor

        if stop_on_first is True then the method returns as soon
        as it finds the first item
        """

        # if the item is in the dictionary then just return it
        if self._dict_contains(lookfor):
            return True, lookfor, self._dict_getitem(lookfor), 1

        # set up the fuzzy matching tool
        ratio_calc = difflib.SequenceMatcher()
        ratio_calc.set_seq1(lookfor)

        # test each key in the dictionary
        best_ratio = 0
        best_match = None
        best_key = None
        for key in self:
            # if the current key is not a string
            # then we just skip it
            try:
                # set up the SequenceMatcher with other text
                ratio_calc.set_seq2(key)
            except TypeError:
                continue

            # we get an error here if the item to look for is not a
            # string - if it cannot be fuzzy matched and we are here
            # this it is definitely not in the dictionary
            try:
                # calculate the match value
                ratio = ratio_calc.ratio()
            except TypeError:
                break

            # if this is the best ratio so far - save it and the value
            if ratio > best_ratio:
                best_ratio = ratio
                best_key = key
                best_match = self._dict_getitem(key)

            if stop_on_first and ratio >= self.cutoff:
                break

        return best_ratio >= self.cutoff, best_key, best_match, best_ratio

    def __contains__(self, item: typing.Any):
        return self._search(item, True)[0]

    def __getitem__(self, lookfor: typing.Any):
        matched, key, item, ratio = self._search(lookfor)

        if not matched:
            raise KeyError(
                "'%s'. closest match: '%s' with ratio %.3f"
                % (str(lookfor), str(key), ratio)
            )

        return item


__HERE = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(__HERE, "map_filename.json"), "r", encoding="utf8") as f:
    FILENAMES: FuzzyDict = FuzzyDict()
    for k, v in json.load(f).items():
        FILENAMES[k] = v

with open(os.path.join(__HERE, "city_coordinates.json"), "r", encoding="utf8") as f:
    COORDINATES: FuzzyDict = FuzzyDict()
    for k, v in json.load(f).items():
        COORDINATES[k] = v

EXTRA = {}


def register_url(asset_url: str):
    if asset_url:
        registry = asset_url + "/registry.json"
        try:
            contents = urllib.request.urlopen(registry).read()
            contents = json.loads(contents)
        except Exception as e:
            raise e
        files = {}
        pinyin_names = set()
        for name, pinyin in contents["PINYIN_MAP"].items():
            file_name = contents["FILE_MAP"][pinyin]
            files[name] = [file_name, "js"]
            pinyin_names.add(pinyin)

        for key, file_name in contents["FILE_MAP"].items():
            if key not in pinyin_names:
                # English names
                files[key] = [file_name, "js"]

        js_folder_name = contents["JS_FOLDER"]
        if js_folder_name == "/":
            js_file_prefix = f"{asset_url}/"
        else:
            js_file_prefix = f"{asset_url}/{js_folder_name}/"
        EXTRA[js_file_prefix] = files


def register_files(asset_files: dict):
    if asset_files:
        FILENAMES.update(asset_files)


def register_coords(coords: dict):
    if coords:
        COORDINATES.update(coords)



File: pyecharts/charts/mixins.py

from ..render import engine


class ChartMixin:
    def add_js_funcs(self, *fns):
        for fn in fns:
            self.js_functions.add(fn)
        return self

    def add_js_events(self, *fns):
        for fn in fns:
            self.js_events.add(fn)
        return self

    def load_javascript(self):
        return engine.load_javascript(self)


class CompositeMixin(ChartMixin):
    def __iter__(self):
        for chart in self._charts:
            yield chart

    def __len__(self):
        return len(self._charts)



File: pyecharts/charts/__init__.py

# basic Charts
from ..charts.basic_charts.bar import Bar
from ..charts.basic_charts.bmap import BMap
from ..charts.basic_charts.boxplot import Boxplot
from ..charts.basic_charts.calendar import Calendar
from ..charts.basic_charts.custom import Custom
from ..charts.basic_charts.effectscatter import EffectScatter
from ..charts.basic_charts.funnel import Funnel
from ..charts.basic_charts.gauge import Gauge
from ..charts.basic_charts.geo import Geo
from ..charts.basic_charts.graph import Graph
from ..charts.basic_charts.heatmap import HeatMap
from ..charts.basic_charts.kline import Kline
from ..charts.basic_charts.line import Line
from ..charts.basic_charts.liquid import Liquid
from ..charts.basic_charts.map import Map
from ..charts.basic_charts.parallel import Parallel
from ..charts.basic_charts.pictorialbar import PictorialBar
from ..charts.basic_charts.pie import Pie
from ..charts.basic_charts.polar import Polar
from ..charts.basic_charts.radar import Radar
from ..charts.basic_charts.sankey import Sankey
from ..charts.basic_charts.scatter import Scatter
from ..charts.basic_charts.sunburst import Sunburst
from ..charts.basic_charts.themeriver import ThemeRiver
from ..charts.basic_charts.tree import Tree
from ..charts.basic_charts.treemap import TreeMap
from ..charts.basic_charts.wordcloud import WordCloud

# Composite Charts
from ..charts.composite_charts.grid import Grid
from ..charts.composite_charts.page import Page
from ..charts.composite_charts.tab import Tab
from ..charts.composite_charts.timeline import Timeline

# 3d charts
from ..charts.three_axis_charts.bar3D import Bar3D
from ..charts.three_axis_charts.graph_gl import GraphGL
from ..charts.three_axis_charts.line3D import Line3D
from ..charts.three_axis_charts.lines3D import Lines3D
from ..charts.three_axis_charts.map3D import Map3D
from ..charts.three_axis_charts.map_globe import MapGlobe
from ..charts.three_axis_charts.scatter3D import Scatter3D
from ..charts.three_axis_charts.surface3D import Surface3D

# alias
Candlestick = Kline



File: pyecharts/charts/chart.py

from .. import options as opts
from .. import types
from ..charts.base import Base
from ..globals import RenderType, ThemeType, ToolTipFormatterType
from ..types import Optional, Sequence


class Chart(Base):
    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        if isinstance(init_opts, dict):
            temp_opts = opts.InitOpts()
            temp_opts.update(**init_opts)
            init_opts = temp_opts
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        # Change to Echarts V5 default color list
        self.colors = (
            "#5470c6 #91cc75 #fac858 #ee6666 #73c0de #3ba272 #fc8452 #9a60b4 " "#ea7ccc"
        ).split()
        self.default_color_n = len(self.colors)
        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_dark_mode(
        self,
        dark_mode_colors: Optional[Sequence[str]] = None,
        dark_mode_bg_color: str = "#100C2A",
    ):
        # [Hard Code Here] The Echarts default Dark Mode Configurations
        if dark_mode_colors is None:
            dark_mode_colors = (
                "#4992ff #7cffb2 #fddd60 #ff6e76 #58d9f9 #05c091 #ff8a45 "
                "#8d48e3 #dd79ff"
            ).split()
        self.options.update(
            backgroundColor=dark_mode_bg_color,
            darkMode=True,
            color=dark_mode_colors,
        )
        self.theme = ThemeType.DARK
        return self

    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):
        self.options.get("legend")[0].get("data").append(name)

    def _append_color(self, color: Optional[str]):
        if color:
            # 这是一个bug
            # 添加轴（执行add_yaxis操作）的顺序与新添加的color值（设置color属性）未一一对应，正好颠倒
            self.colors.insert(-self.default_color_n, 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)
            _s.update(**{k: v for k, v in legend_opts.items() if v is not None})

        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: types.Optional[types.Sequence] = None,
        source_header: types.Optional[bool] = None,
        transform: types.Optional[Sequence[opts.DatasetTransformOpts]] = None,
        from_dataset_index: types.Optional[types.Numeric] = None,
        from_dataset_id: types.Optional[types.Numeric] = None,
        from_transform_result: types.Optional[types.Numeric] = None,
    ):
        if self.options.get("dataset") is not None:
            self.options.get("dataset").append(
                {
                    "source": source,
                    "dimensions": dimensions,
                    "sourceHeader": source_header,
                    "transform": transform,
                    "fromDatasetIndex": from_dataset_index,
                    "fromDatasetId": from_dataset_id,
                    "fromTransformResult": from_transform_result,
                }
            )
        else:
            self.options.update(
                dataset=[
                    {
                        "source": source,
                        "dimensions": dimensions,
                        "sourceHeader": source_header,
                        "transform": transform,
                        "fromDatasetIndex": from_dataset_index,
                        "fromDatasetId": from_dataset_id,
                        "fromTransformResult": from_transform_result,
                    }
                ]
            )
        return self


class RectChart(Chart):
    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.options.update(xAxis=[opts.AxisOpts().opts], yAxis=[opts.AxisOpts().opts])

    def extend_axis(
        self,
        xaxis_data: Sequence = None,
        xaxis: types.Axis = None,
        yaxis: types.Axis = 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

    def add_xaxis(self, xaxis_data: Sequence):
        self.options["xAxis"][0].update(data=xaxis_data)
        self._xaxis_data = xaxis_data
        return self

    def reversal_axis(self):
        self.options["yAxis"][0]["data"] = self._xaxis_data
        self.options["xAxis"][0]["data"] = None
        return self

    def overlap(self, chart: Base):
        self.options.get("legend")[0].get("data").extend(
            chart.options.get("legend")[0].get("data")
        )
        if self.options.get("legend")[0].get("selected") is not None:
            self.options.get("legend")[0].get("selected").update(
                chart.options.get("legend")[0].get("selected")
            )
        self.options.get("series").extend(chart.options.get("series"))
        # to merge colors of chart
        for c in chart.colors[: len(chart.colors) - self.default_color_n]:
            self.colors.insert(len(self.colors) - self.default_color_n, c)
        return self


class Chart3D(Chart):
    """
    `Chart3D`类是所有 3D 类图表的基类，继承自 `Chart` 类
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        init_opts.renderer = RenderType.CANVAS
        super().__init__(init_opts, render_opts)
        self.js_dependencies.add("echarts-gl")
        self._3d_chart_type: Optional[str] = None  # 3d chart type,don't use it directly

    def add_globe(
        self,
        is_show: bool = True,
        globe_radius: types.Numeric = 100,
        globe_outer_radius: types.Numeric = 150,
        environment: str = "auto",
        base_texture: types.Union[str, types.JsCode, None] = None,
        height_texture: types.Union[str, types.JsCode, None] = None,
        displacement_texture: types.Union[str, types.JsCode, None] = None,
        displacement_scale: types.Numeric = 0,
        displacement_quality: str = "medium",
        shading: types.Optional[str] = None,
        realistic_material_opts: types.Optional[types.Map3DRealisticMaterial] = None,
        lambert_material_opts: types.Optional[types.Map3DLambertMaterial] = None,
        color_material_opts: types.Optional[types.Map3DColorMaterial] = None,
        light_opts: types.Optional[types.Map3DLight] = None,
        post_effect_opts: types.Optional[types.Map3DPostEffect] = None,
        is_enable_super_sampling: types.Union[str, bool] = "auto",
        view_control_opts: types.Optional[types.Map3DViewControl] = None,
        layers: types.Optional[types.GlobeLayers] = None,
        z_level: types.Numeric = -10,
        pos_left: types.Union[str, types.Numeric] = "auto",
        pos_top: types.Union[str, types.Numeric] = "auto",
        pos_right: types.Union[str, types.Numeric] = "auto",
        pos_bottom: types.Union[str, types.Numeric] = "auto",
        width: types.Union[str, types.Numeric] = "auto",
        height: types.Union[str, types.Numeric] = "auto",
    ):
        self.options.update(
            globe={
                "show": is_show,
                "globeRadius": globe_radius,
                "globeOuterRadius": globe_outer_radius,
                "environment": environment,
                "baseTexture": base_texture,
                "heightTexture": height_texture,
                "displacementTexture": displacement_texture,
                "displacementScale": displacement_scale,
                "displacementQuality": displacement_quality,
                "shading": shading,
                "realisticMaterial": realistic_material_opts,
                "lambertMaterial": lambert_material_opts,
                "colorMaterial": color_material_opts,
                "light": light_opts,
                "postEffect": post_effect_opts,
                "temporalSuperSampling": {"enable": is_enable_super_sampling},
                "viewControl": view_control_opts,
                "layers": layers,
                "zlevel": z_level,
                "left": pos_left,
                "top": pos_top,
                "right": pos_right,
                "bottom": pos_bottom,
                "width": width,
                "height": height,
            }
        )
        return self


class ThreeAxisChart(Chart3D):
    def add(
        self,
        series_name: str,
        data: Sequence,
        coordinate_system: Optional[str] = None,
        shading: Optional[str] = None,
        itemstyle_opts: types.ItemStyle = None,
        label_opts: types.Label = opts.LabelOpts(is_show=False),
        grid_3d_index: types.Numeric = 0,
        xaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value", name="X"),
        yaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value", name="Y"),
        zaxis3d_opts: types.Axis3D = opts.Axis3DOpts(type_="value", name="Z"),
        grid3d_opts: types.Grid3D = opts.Grid3DOpts(),
        encode: types.Union[types.JSFunc, dict, None] = None,
        emphasis_opts: types.Optional[types.Emphasis3D] = None,
        is_parametric: types.Optional[bool] = None,
        is_show_wire_frame: types.Optional[bool] = None,
        wire_frame_line_style_opts: types.Optional[opts.LineStyleOpts] = None,
        equation: types.Optional[dict] = None,
        parametric_equation: types.Optional[dict] = None,
    ):
        self.options.get("legend")[0].get("data").append(series_name)
        self.options.update(
            xAxis3D=xaxis3d_opts,
            yAxis3D=yaxis3d_opts,
            zAxis3D=zaxis3d_opts,
            grid3D=grid3d_opts,
        )

        if self._3d_chart_type == "surface":
            self.options.get("series").append(
                {
                    "type": self._3d_chart_type,
                    "name": series_name,
                    "coordinateSystem": coordinate_system,
                    "data": data,
                    "label": label_opts,
                    "shading": shading,
                    "grid3DIndex": grid_3d_index,
                    "itemStyle": itemstyle_opts,
                    "parametric": is_parametric,
                    "wireframe": {
                        "show": is_show_wire_frame,
                        "lineStyle": wire_frame_line_style_opts,
                    },
                    "equation": equation,
                    "parametricEquation": parametric_equation,
                }
            )
        else:
            self.options.get("series").append(
                {
                    "type": self._3d_chart_type,
                    "name": series_name,
                    "coordinateSystem": coordinate_system,
                    "data": data,
                    "label": label_opts,
                    "shading": shading,
                    "grid3DIndex": grid_3d_index,
                    "itemStyle": itemstyle_opts,
                    "emphasis": emphasis_opts,
                    "encode": encode,
                }
            )
        return self



File: pyecharts/charts/base.py

import datetime
import uuid

import simplejson as json
from jinja2 import Environment

from ..commons import utils
from ..globals import CurrentConfig, RenderType, ThemeType
from ..options import InitOpts, RenderOpts
from ..options.global_options import AnimationOpts
from ..options.series_options import BasicOpts
from ..render import engine
from ..types import Optional, Sequence, Union
from .mixins import ChartMixin


class Base(ChartMixin):
    """
    `Base` is the root class for all graphical class, it provides
    part of the initialization parameters and common methods
    """

    def __init__(
        self,
        init_opts: Union[InitOpts, dict] = InitOpts(),
        render_opts: Union[RenderOpts, dict] = RenderOpts(),
    ):
        _opts = init_opts
        if isinstance(init_opts, InitOpts):
            _opts = init_opts.opts

        _render_opts = render_opts
        if isinstance(render_opts, RenderOpts):
            _render_opts = render_opts.opts

        self.width = _opts.get("width", "900px")
        self.height = _opts.get("height", "500px")
        self.horizontal_center = (
            "text-align:center; margin: auto"
            if _opts.get("is_horizontal_center", False)
            else ""
        )
        self.renderer = _opts.get("renderer", RenderType.CANVAS)
        self.page_title = _opts.get("page_title", CurrentConfig.PAGE_TITLE)
        self.theme = _opts.get("theme", ThemeType.WHITE)
        self.chart_id = _opts.get("chart_id") or uuid.uuid4().hex
        self.fill_bg = _opts.get("fill_bg", False)
        self.bg_color = _opts.get("bg_color")

        self.options: dict = {}
        self.render_options: dict = {}
        self.js_host: str = _opts.get("js_host") or CurrentConfig.ONLINE_HOST
        self.js_functions: utils.OrderedSet = utils.OrderedSet()
        self.js_dependencies: utils.OrderedSet = utils.OrderedSet("echarts")
        self.js_events: utils.OrderedSet = utils.OrderedSet()
        self.options.update(backgroundColor=self.bg_color)
        if isinstance(_opts.get("animationOpts", AnimationOpts()), dict):
            self.options.update(_opts.get("animationOpts", AnimationOpts().opts))
        else:
            self.options.update(_opts.get("animationOpts", AnimationOpts()).opts)
        self.options.update(aria=_opts.get("ariaOpts"))

        self._is_geo_chart: bool = False
        self._geo_json_name: Optional[str] = None
        self._geo_json: Optional[dict] = None

        self.render_options.update(embed_js=bool(_render_opts.get("embed_js")))
        self._render_cache: dict = dict()

    def get_chart_id(self) -> str:
        return self.chart_id

    def get_options(self) -> dict:
        return utils.remove_key_with_none_value(self.options)

    def dump_options(self) -> str:
        return utils.replace_placeholder(
            json.dumps(self.get_options(), indent=4, default=default, ignore_nan=True)
        )

    def dump_options_with_quotes(self) -> str:
        return utils.replace_placeholder_with_quotes(
            json.dumps(self.get_options(), indent=4, default=default, ignore_nan=True)
        )

    def render(
        self,
        path: str = "render.html",
        template_name: str = "simple_chart.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render(self, path, template_name, env, **kwargs)

    def render_embed(
        self,
        template_name: str = "simple_chart.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render_embed(self, template_name, env, **kwargs)

    def render_notebook(self):
        self.chart_id = uuid.uuid4().hex
        self._prepare_render()
        return engine.render_notebook(
            self, "nb_jupyter_notebook.html", "nb_jupyter_lab.html"
        )

    def _use_theme(self):
        if self.theme not in ThemeType.BUILTIN_THEMES:
            self.js_dependencies.add(self.theme)

    def _prepare_render(self):
        self.json_contents = self.dump_options()
        self._use_theme()

        self._render_cache.clear()
        if self.render_options.get("embed_js"):
            self._render_cache[
                "javascript"
            ] = self.load_javascript().load_javascript_contents()


def default(o):
    if isinstance(o, (datetime.date, datetime.datetime)):
        return o.isoformat()
    if isinstance(o, utils.JsCode):
        return (
            o.replace("\\n|\\t", "").replace(r"\\n", "\n").replace(r"\\t", "\t").js_code
        )
    if isinstance(o, BasicOpts):
        if isinstance(o.opts, Sequence):
            return [utils.remove_key_with_none_value(item) for item in o.opts]
        else:
            return utils.remove_key_with_none_value(o.opts)



File: pyecharts/charts/basic_charts/geo.py

import simplejson as json

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...datasets import COORDINATES
from ...exceptions import NonexistentCoordinatesException
from ...globals import ChartType


class GeoChartBase(Chart):
    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.set_global_opts()
        self._coordinates = COORDINATES
        self._zlevel = 1
        self._coordinate_system: types.Optional[str] = None
        self._chart_type = ChartType.GEO

    def add_geo_json(self, geo_json: dict):
        self._geo_json = geo_json
        return self

    def add_coordinate(
        self, name: str, longitude: types.Numeric, latitude: types.Numeric
    ):
        self._coordinates.update({name: [longitude, latitude]})
        return self

    def add_coordinate_json(self, json_file: str):
        with open(json_file, "r", encoding="utf-8") as f:
            json_reader = json.load(f)
            for k, v in json_reader.items():
                self.add_coordinate(k, v[0], v[1])
        return self

    def get_coordinate(self, name: str) -> types.Optional[types.Sequence]:
        if name in self._coordinates:
            return self._coordinates[name]

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        type_: str = "scatter",
        *,
        symbol: types.Optional[str] = None,
        symbol_size: types.Numeric = 12,
        blur_size: types.Numeric = 20,
        point_size: types.Numeric = 20,
        radius: types.Optional[types.Sequence] = None,
        color: types.Optional[str] = None,
        is_polyline: bool = False,
        is_large: bool = False,
        large_threshold: types.Numeric = 2000,
        progressive: types.Numeric = 400,
        progressive_threshold: types.Numeric = 3000,
        label_opts: types.Label = None,
        effect_opts: types.Effect = opts.EffectOpts(),
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        render_item: types.JsCode = None,
        encode: types.Union[types.JsCode, dict] = None,
    ):
        self._zlevel += 1
        data = self._feed_data(data_pair, type_) if data_pair else data_pair

        self._append_color(color)
        self._append_legend(series_name)

        if type_ == ChartType.SCATTER:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "data": data,
                    "label": label_opts,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                }
            )
        elif type_ == ChartType.SCATTERGL:
            self.js_dependencies.add("echarts-gl")
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "data": data,
                    "itemStyle": itemstyle_opts,
                }
            )
        elif type_ == ChartType.EFFECT_SCATTER:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "showEffectOn": "render",
                    "rippleEffect": effect_opts,
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "data": data,
                    "label": label_opts,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                }
            )
        elif type_ == ChartType.FLOWGL:
            self.js_dependencies.add("echarts-gl")
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "data": data,
                    "itemStyle": itemstyle_opts,
                }
            )
        elif type_ == ChartType.HEATMAP:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "data": data,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                    "pointSize": point_size,
                    "blurSize": blur_size,
                }
            )
        elif type_ == ChartType.LINES:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "zlevel": self._zlevel,
                    "progressive": progressive,
                    "progressiveThreshold": progressive_threshold,
                    "effect": effect_opts,
                    "symbol": symbol or ["none", "arrow"],
                    "polyline": is_polyline,
                    "large": is_large,
                    "largeThreshold": large_threshold,
                    "symbolSize": symbol_size,
                    "data": data,
                    "lineStyle": linestyle_opts,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                    "label": label_opts,
                }
            )
        elif type_ == ChartType.LINESGL:
            self.js_dependencies.add("echarts-gl")
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "data": data,
                    "polyline": is_polyline,
                    "large": is_large,
                    "lineStyle": linestyle_opts,
                }
            )
        elif type_ == ChartType.CUSTOM:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": self._coordinate_system,
                    "renderItem": render_item,
                    "emphasis": {"itemStyle": itemstyle_opts},
                    "encode": encode,
                    "data": data,
                }
            )
        elif type_ == ChartType.PIE:
            if not radius:
                radius = ["0%", "5%"]

            if not tooltip_opts:
                tooltip_opts = {"formatter": "{b}: {c} ({d}%)"}

            if not isinstance(data[0], opts.PieItem):
                data = [{"name": n, "value": v} for n, v in data]

            self.options.get("series").append(
                {
                    "type": type_,
                    "coordinateSystem": self._coordinate_system,
                    "data": data,
                    "tooltip": tooltip_opts,
                    "label": label_opts,
                    "center": self.get_coordinate(series_name),
                    "radius": radius,
                }
            )
            # Legend (hard code here)
            legend = self.options.get("legend")[0]
            pie_series_name = [d.get("name") for d in data]
            if len(legend.get("data")) < len(pie_series_name):
                legend["data"] = pie_series_name

        return self


class Geo(GeoChartBase):
    """
    <<< geo coordinate system >>>

    support scatter plot and line
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        is_ignore_nonexistent_coord: bool = False,
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self._coordinate_system: types.Optional[str] = "geo"
        self._is_ignore_nonexistent_coord = is_ignore_nonexistent_coord

    def _feed_data(self, data_pair: types.Sequence, type_: str) -> types.Sequence:
        if type_ == ChartType.PIE:
            return data_pair

        result = []
        if isinstance(data_pair[0], opts.GeoItem):
            for item in data_pair:
                result.append({
                    "name": item.get("name"),
                    "value": [
                        item.get("longitude"),
                        item.get("latitude"),
                        item.get("value"),
                    ],
                })

            return result

        for n, v in data_pair:
            try:
                if type_ == ChartType.LINES:
                    f, t = self.get_coordinate(n), self.get_coordinate(v)
                    result.append({"name": "{}->{}".format(n, v), "coords": [f, t]})
                else:
                    lng, lat = self.get_coordinate(n)
                    result.append({"name": n, "value": [lng, lat, v]})
            except TypeError as err:
                if self._is_ignore_nonexistent_coord is not True:
                    raise NonexistentCoordinatesException(err, (n, v))
        return result

    def add_schema(
        self,
        maptype: str = "china",
        is_roam: bool = True,
        zoom: types.Optional[types.Numeric] = None,
        center: types.Optional[types.Sequence] = None,
        aspect_scale: types.Numeric = 0.75,
        bounding_coords: types.Optional[types.Sequence[types.Numeric]] = None,
        min_scale_limit: types.Optional[types.Numeric] = None,
        max_scale_limit: types.Optional[types.Numeric] = None,
        name_property: str = "name",
        selected_mode: types.Union[bool, str] = False,
        layout_center: types.Optional[types.Sequence[str]] = None,
        layout_size: types.Union[str, types.Numeric] = None,
        label_opts: types.Label = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_itemstyle_opts: types.ItemStyle = None,
        emphasis_label_opts: types.Label = None,
        regions_opts: types.Union[
            types.Sequence[types.GeoRegions], types.Sequence[dict]
        ] = None,
    ):
        self.js_dependencies.add(maptype)
        self._geo_json_name = maptype
        if center:
            assert len(center) == 2

        scale_limit: types.Optional[dict] = {
            "min": min_scale_limit,
            "max": max_scale_limit,
        }
        if min_scale_limit is None and max_scale_limit is None:
            scale_limit = None

        self.options.update(
            geo={
                "map": maptype,
                "zoom": zoom,
                "center": center,
                "roam": is_roam,
                "aspectScale": aspect_scale,
                "boundingCoords": bounding_coords,
                "scaleLimit": scale_limit,
                "nameProperty": name_property,
                "selectedMode": selected_mode,
                "layoutCenter": layout_center,
                "layoutSize": layout_size,
                "label": label_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": {
                    "itemStyle": emphasis_itemstyle_opts,
                    "label": emphasis_label_opts,
                },
                "regions": regions_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/polar.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...commons.utils import JsCode
from ...globals import ChartType


class Polar(Chart):
    """
    <<< Polar >>>

    Polar coordinates can be used for scatter and polyline graphs.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.add_schema()

    def add_schema(
        self,
        radiusaxis_opts: types.RadiusAxis = opts.RadiusAxisOpts(),
        angleaxis_opts: types.AngleAxis = opts.AngleAxisOpts(),
    ):
        if isinstance(angleaxis_opts, opts.AngleAxisOpts):
            angleaxis_opts = angleaxis_opts.opts
        if isinstance(radiusaxis_opts, opts.RadiusAxisOpts):
            radiusaxis_opts = radiusaxis_opts.opts
        self.options.update(radiusAxis=radiusaxis_opts, angleAxis=angleaxis_opts)
        return self

    def add(
        self,
        series_name: str,
        data: types.Sequence,
        *,
        type_: str = "line",
        symbol: types.Optional[str] = None,
        symbol_size: types.Numeric = 4,
        stack: types.Optional[str] = None,
        center: types.Optional[types.Sequence] = None,
        label_opts: types.Label = opts.LabelOpts(is_show=False),
        areastyle_opts: types.AreaStyle = opts.AreaStyleOpts(),
        effect_opts: types.Effect = opts.EffectOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_legend(series_name)
        self.options.update(polar={"center": center if center else ["50%", "50%"]})

        if type_ in (ChartType.SCATTER, ChartType.LINE, ChartType.BAR):
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "polar",
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "data": data,
                    "stack": stack,
                    "label": label_opts,
                    "areaStyle": areastyle_opts,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                    "emphasis": emphasis_opts,
                }
            )

        elif type_ == ChartType.EFFECT_SCATTER:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "polar",
                    "showEffectOn": "render",
                    "rippleEffect": effect_opts,
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "data": data,
                    "label": label_opts,
                    "tooltip": tooltip_opts,
                    "itemStyle": itemstyle_opts,
                    "emphasis": emphasis_opts,
                }
            )

        if label_opts.get("show"):
            self.options.get("series").append(
                {
                    "type": ChartType.CUSTOM,
                    "coordinateSystem": "polar",
                    # TODO: 提供自定义 function
                    "renderItem": JsCode(
                        """function(params, api) {
                        var values = [api.value(0), api.value(1)];
                        var coord = api.coord(values);
                        return {
                            type: 'text',
                            position: [3 * Math.sin(coord[3]), 3 * Math.cos(coord[3])],
                            rotation: coord[3] + Math.PI / 2,
                            origin: [coord[0], coord[1]],
                            style: {
                                text: api.value(1),
                                fill: 'black',
                                fontSize: 12,
                                textAlign: 'right',
                                textVerticalAlign: 'middle',
                                x: coord[0],
                                y: coord[1]
                            }
                        };
                    }"""
                    ),
                    "data": data,
                }
            )
        return self



File: pyecharts/charts/basic_charts/tree.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Tree(Chart):
    """
    <<< Tree diagrams >>>

    Tree diagrams are used primarily to visualize tree data structures,
    which are special hierarchical types with unique root nodes, left subtrees,
    and right subtrees.
    """

    @staticmethod
    def _set_collapse_interval(data, interval):
        """
        间隔折叠节点，当节点过多时可以解决节点显示过杂间隔。

        :param data: 节点数据
        :param interval: 指定间隔
        """
        if interval <= 0:
            return data
        if data and isinstance(data, list):
            for d in data:
                children = d.get("children", None)
                if children and interval > 0:
                    for index, value in enumerate(children):
                        if index % interval == 0:
                            value.update(collapsed="false")
            return data

    def add(
        self,
        series_name: str,
        data: types.Sequence[types.Union[opts.TreeItem, dict]],
        *,
        zoom: types.Optional[types.Numeric] = 1,
        layout: str = "orthogonal",
        symbol: types.JSFunc = "emptyCircle",
        symbol_size: types.Union[types.JSFunc, types.Numeric, types.Sequence] = 7,
        orient: str = "LR",
        pos_top: types.Union[str, types.Numeric, None] = None,
        pos_left: types.Union[str, types.Numeric, None] = None,
        pos_bottom: types.Union[str, types.Numeric, None] = None,
        pos_right: types.Union[str, types.Numeric, None] = None,
        width: types.Union[str, types.Numeric, None] = None,
        height: types.Union[str, types.Numeric, None] = None,
        center: types.Optional[types.Sequence[types.Union[str, types.Numeric]]] = None,
        collapse_interval: types.Numeric = 0,
        edge_shape: str = "curve",
        edge_fork_position: str = "50%",
        is_roam: bool = False,
        is_expand_and_collapse: bool = True,
        initial_tree_depth: types.Optional[types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(),
        leaves_opts: types.TreeLeavesOpts = opts.TreeLeavesOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        selected_mode: types.Union[bool, str] = False,
        blur_opts: types.Blur = None,
        select_opts: types.Select = None,
    ):
        _data = self._set_collapse_interval(data, collapse_interval)
        self.options.get("series").append(
            {
                "type": ChartType.TREE,
                "name": series_name,
                "data": _data,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "bottom": pos_bottom,
                "width": width,
                "height": height,
                "center": center,
                "zoom": zoom,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "edgeShape": edge_shape,
                "edgeForkPosition": edge_fork_position,
                "roam": is_roam,
                "expandAndCollapse": is_expand_and_collapse,
                "initialTreeDepth": initial_tree_depth,
                "layout": layout,
                "orient": orient,
                "label": label_opts,
                "leaves": leaves_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "selectedMode": selected_mode,
                "blur": blur_opts,
                "select": select_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/heatmap.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


class HeatMap(RectChart):
    """
    <<< HeatMap >>>

    The heat map is mainly used to represent the size of the value by color,
    which must be used in conjunction with the visualMap component.
    Two categories of axes must be used in rectangular coordinates.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.set_global_opts(visualmap_opts=opts.VisualMapOpts(orient="horizontal"))

    def add_yaxis(
        self,
        series_name: str,
        yaxis_data: types.Sequence[types.Union[dict]],
        value: types.Sequence[types.Union[dict]],
        *,
        coordinate_system: str = "cartesian2d",
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        geo_index: types.Optional[types.Numeric] = None,
        calendar_index: types.Optional[types.Numeric] = None,
        dataset_index: types.Optional[types.Numeric] = None,
        point_size: types.Optional[types.Numeric] = None,
        blur_size: types.Optional[types.Numeric] = None,
        min_opacity: types.Optional[types.Numeric] = None,
        max_opacity: types.Optional[types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(),
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        selected_mode: types.Union[bool, str] = False,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        self._append_legend(series_name)
        self.options.get("yAxis")[0].update(data=yaxis_data)
        self.options.get("series").append(
            {
                "type": ChartType.HEATMAP,
                "name": series_name,
                "coordinateSystem": coordinate_system,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "geoIndex": geo_index,
                "calendarIndex": calendar_index,
                "datasetIndex": dataset_index,
                "pointSize": point_size,
                "blurSize": blur_size,
                "minOpacity": min_opacity,
                "maxOpacity": max_opacity,
                "data": value,
                "label": label_opts,
                "markLine": markline_opts,
                "markPoint": markpoint_opts,
                "markArea": markarea_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "selectedMode": selected_mode,
                "zlevel": z_level,
                "z": z,
                "encode": encode,
            }
        )
        return self



File: pyecharts/charts/basic_charts/gauge.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Gauge(Chart):
    """
    <<< Gauge >>>

    The gauge displays a single key business measure.
    """

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        *,
        min_: types.Numeric = 0,
        max_: types.Numeric = 100,
        split_number: types.Numeric = 10,
        center: types.Sequence = None,
        radius: types.Union[types.Numeric, str] = "75%",
        start_angle: types.Numeric = 225,
        end_angle: types.Numeric = -45,
        is_clock_wise: bool = True,
        title_label_opts: types.GaugeTitle = opts.GaugeTitleOpts(
            offset_center=["0%", "20%"],
        ),
        detail_label_opts: types.GaugeDetail = opts.GaugeDetailOpts(
            formatter="{value}%",
            offset_center=["0%", "40%"],
        ),
        progress: types.GaugeProgress = opts.GaugeProgressOpts(),
        pointer: types.GaugePointer = opts.GaugePointerOpts(),
        anchor: types.GaugeAnchor = opts.GaugeAnchorOpts(),
        tooltip_opts: types.Tooltip = None,
        axisline_opts: types.AxisLine = None,
        axistick_opts: types.AxisTick = None,
        axislabel_opts: types.AxisLabel = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        if center is None:
            center = ["50%", "50%"]

        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.GAUGE,
                "title": title_label_opts,
                "detail": detail_label_opts,
                "name": series_name,
                "min": min_,
                "max": max_,
                "splitNumber": split_number,
                "center": center,
                "radius": radius,
                "startAngle": start_angle,
                "endAngle": end_angle,
                "clockwise": is_clock_wise,
                "data": [{"name": n, "value": v} for n, v in data_pair],
                "tooltip": tooltip_opts,
                "axisLine": axisline_opts,
                "axisTick": axistick_opts,
                "axisLabel": axislabel_opts,
                "progress": progress,
                "anchor": anchor,
                "pointer": pointer,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/effectscatter.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


class EffectScatter(RectChart):
    """
    <<< Scatter plots with ripple effects animation >>>

    Use animation effects to visually highlight designated data set.
    """

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[opts.EffectScatterItem, dict]],
        *,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        polar_index: types.Optional[types.Numeric] = None,
        geo_index: types.Optional[types.Numeric] = None,
        calendar_index: types.Optional[types.Numeric] = None,
        dataset_index: types.Optional[types.Numeric] = None,
        color: types.Optional[str] = None,
        color_by: types.Optional[str] = None,
        is_legend_hover_link: bool = True,
        show_effect_on: str = "render",
        coordinate_system: str = "cartesian2d",
        symbol: types.Optional[str] = None,
        symbol_size: types.Numeric = 10,
        symbol_rotate: types.Optional[types.Numeric] = None,
        selected_mode: types.Union[bool, str] = False,
        label_opts: types.Label = opts.LabelOpts(),
        effect_opts: types.Effect = opts.EffectOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        self._append_color(color)
        self._append_legend(series_name)

        if all([isinstance(d, opts.EffectScatterItem) for d in y_axis]):
            y_axis = y_axis
        else:
            y_axis = [list(z) for z in zip(self._xaxis_data, y_axis)]

        self.options.get("series").append(
            {
                "type": ChartType.EFFECT_SCATTER,
                "name": series_name,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "polarIndex": polar_index,
                "geoIndex": geo_index,
                "calendarIndex": calendar_index,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "showEffectOn": show_effect_on,
                "rippleEffect": effect_opts,
                "coordinateSystem": coordinate_system,
                "datasetIndex": dataset_index,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "symbolRotate": symbol_rotate,
                "selectedMode": selected_mode,
                "data": y_axis,
                "label": label_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "zlevel": z_level,
                "z": z,
                "encode": encode,
            }
        )
        return self



File: pyecharts/charts/basic_charts/boxplot.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


class Boxplot(RectChart):
    """
    <<< Boxplot >>>

    A box-plot is a statistical chart used to show a set of data dispersion data.
    It displays the maximum, minimum, median, lower quartile, and upper quartile
    of a set of data.
    """

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Optional[
            types.Sequence[types.Union[opts.BoxplotItem, dict]]
        ] = None,
        *,
        chart_type: str = ChartType.BOXPLOT,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        dataset_index: types.Optional[types.Numeric] = None,
        color_by: types.Optional[str] = None,
        is_legend_hover_link: bool = True,
        is_hover_animation: bool = True,
        layout: types.Optional[str] = None,
        box_width: types.Optional[types.Sequence] = None,
        selected_mode: types.Union[bool, str] = False,
        dimensions: types.Union[types.Sequence, None] = None,
        label_opts: types.Label = opts.LabelOpts(),
        markpoint_opts: types.MarkPoint = opts.MarkPointOpts(),
        markline_opts: types.MarkLine = opts.MarkLineOpts(),
        markarea_opts: types.MarkArea = None,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        blur_opts: types.Blur = None,
        select_opts: types.Select = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        if box_width is None:
            box_width = [7, 50]

        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": chart_type,
                "name": series_name,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "datasetIndex": dataset_index,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "hoverAnimation": is_hover_animation,
                "layout": layout,
                "boxWidth": box_width,
                "selectedMode": selected_mode,
                "dimensions": dimensions,
                "data": y_axis,
                "label": label_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "zlevel": z_level,
                "z": z,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "blur": blur_opts,
                "select": select_opts,
                "encode": encode,
            }
        )
        return self

    @staticmethod
    def prepare_data(items):
        data = []
        for item in items:
            if not item:
                data.append([])
            try:
                d, res = sorted(item), []
                for i in range(1, 4):
                    n = i * (len(d) + 1) / 4
                    k = int(n)
                    m = n - k
                    if m == 0:
                        res.append(d[k - 1])
                    elif m == 1 / 4:
                        res.append(d[k - 1] * 0.75 + d[k] * 0.25)
                    elif m == 1 / 2:
                        res.append(d[k - 1] * 0.50 + d[k] * 0.50)
                    elif m == 3 / 4:
                        res.append(d[k - 1] * 0.25 + d[k] * 0.75)
                data.append([d[0]] + res + [d[-1]])
            except TypeError:
                # one of the item element is None
                data.append([])
        return data



File: pyecharts/charts/basic_charts/custom.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Custom(Chart):
    """
    <<< Custom >>>

    Custom series allows you to customize the rendering of graphical elements
    in the series. This enables the extension of different charts.
    """

    def add(
        self,
        series_name: str,
        render_item: types.JSFunc,
        *,
        color_by: str = "series",
        is_legend_hover_link: bool = True,
        coordinate_system: str = "cartesian2d",
        x_axis_index: types.Numeric = 0,
        y_axis_index: types.Numeric = 0,
        polar_index: types.Numeric = 0,
        geo_index: types.Numeric = 0,
        calendar_index: types.Numeric = 0,
        dataset_index: types.Numeric = 0,
        series_layout_by: str = "column",
        selected_mode: types.Union[bool, str] = False,
        dimensions: types.Optional[types.Sequence] = None,
        encode: types.Union[types.Sequence, dict, None] = None,
        data: types.Optional[types.Sequence] = None,
        is_clip: bool = True,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        itemstyle_opts: types.ItemStyle = None,
        tooltip_opts: types.Tooltip = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_legend(series_name)

        self.options.get("series").append(
            {
                "type": ChartType.CUSTOM,
                "name": series_name,
                "renderItem": render_item,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "coordinateSystem": coordinate_system,
                "xAxisIndex": x_axis_index,
                "yAxisIndex": y_axis_index,
                "polarIndex": polar_index,
                "geoIndex": geo_index,
                "calendarIndex": calendar_index,
                "datasetIndex": dataset_index,
                "seriesLayoutBy": series_layout_by,
                "itemStyle": itemstyle_opts,
                "selectedMode": selected_mode,
                "dimensions": dimensions,
                "encode": encode,
                "data": data,
                "clip": is_clip,
                "zlevel": z_level,
                "z": z,
                "tooltip": tooltip_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/treemap.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class TreeMap(Chart):
    """
    <<< TreeMap >>>

    TreeMap are a common visual representation of "hierarchical data" and "tree data".
    It mainly uses area to highlight the important nodes in the hierarchy of "tree".
    """

    def add(
        self,
        series_name: str,
        data: types.Sequence[types.Union[opts.TreeItem, dict]],
        *,
        leaf_depth: types.Optional[types.Numeric] = None,
        pos_left: types.Optional[str] = None,
        pos_right: types.Optional[str] = None,
        pos_top: types.Optional[str] = None,
        pos_bottom: types.Optional[str] = None,
        width: types.Union[str, types.Numeric] = "80%",
        height: types.Union[str, types.Numeric] = "80%",
        square_ratio: types.Optional[types.JSFunc] = None,
        drilldown_icon: str = "▶",
        roam: types.Union[bool, str] = True,
        node_click: types.Union[bool, str] = "zoomToNode",
        zoom_to_node_ratio: types.Numeric = 0.32 * 0.32,
        levels: types.TreeMapLevel = None,
        visual_min: types.Optional[types.Numeric] = None,
        visual_max: types.Optional[types.Numeric] = None,
        visual_dimension: types.Optional[types.Numeric] = None,
        color_alpha: types.Union[types.Numeric, types.Sequence] = None,
        color_saturation: types.Union[types.Numeric, types.Sequence] = None,
        color_mapping_by: str = "index",
        visible_min: types.Numeric = 10,
        children_visible_min: types.Optional[types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(position="inside"),
        upper_label_opts: types.Label = opts.LabelOpts(position="inside"),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        breadcrumb_opts: types.TreeMapBreadcrumb = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.TREEMAP,
                "name": series_name,
                "data": data,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "width": width,
                "height": height,
                "bottom": pos_bottom,
                "squareRatio": square_ratio,
                "label": label_opts,
                "upperLabel": upper_label_opts,
                "leafDepth": leaf_depth,
                "drillDownIcon": drilldown_icon,
                "roam": roam,
                "nodeClick": node_click,
                "zoomToNodeRatio": zoom_to_node_ratio,
                "levels": levels,
                "visualMin": visual_min,
                "visualMax": visual_max,
                "visualDimension": visual_dimension,
                "colorAlpha": color_alpha,
                "colorSaturation": color_saturation,
                "colorMappingBy": color_mapping_by,
                "visibleMin": visible_min,
                "childrenVisibleMin": children_visible_min,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "breadcrumb": breadcrumb_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/liquid.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Liquid(Chart):
    """
    <<< Liquid >>>

    The liquid chart is mainly used to highlight the percentage of data.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.js_dependencies.add("echarts-liquidfill")

    def add(
        self,
        series_name: str,
        data: types.Sequence,
        *,
        shape: str = "circle",
        color: types.Optional[types.Sequence[str]] = None,
        background_color: types.Union[str, dict, None] = None,
        is_animation: bool = True,
        is_outline_show: bool = True,
        outline_border_distance: types.Numeric = 8,
        outline_itemstyle_opts: types.ItemStyle = None,
        center: types.Sequence = None,
        tooltip_opts: types.Tooltip = None,
        label_opts: types.Label = opts.LabelOpts(font_size=50, position="inside"),
    ):
        _animation_dur, _animation_dur_update = 2000, 1000
        if not is_animation:
            _animation_dur, _animation_dur_update = 0, 0

        color = color or ["#294D99", "#156ACF", "#1598ED", "#45BDFF"]

        self.options.get("series").append(
            {
                "type": ChartType.LIQUID,
                "name": series_name,
                "data": data,
                "waveAnimation": is_animation,
                "animationDuration": _animation_dur,
                "animationDurationUpdate": _animation_dur_update,
                "color": color,
                "shape": shape,
                "backgroundStyle": {"color": background_color},
                "outline": {
                    "show": is_outline_show,
                    "borderDistance": outline_border_distance,
                    "itemStyle": outline_itemstyle_opts,
                },
                "tooltip": tooltip_opts,
                "label": label_opts,
                "center": center,
            }
        )
        return self



File: pyecharts/charts/basic_charts/line.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


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

    Line chart is a graph that connects all data points
    with single line to show the change trend of data.
    """

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[opts.LineItem, dict]],
        *,
        is_connect_nones: bool = False,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        polar_index: types.Optional[types.Numeric] = None,
        coordinate_system: types.Optional[str] = None,
        color_by: types.Optional[str] = None,
        color: types.Optional[str] = None,
        is_symbol_show: bool = True,
        symbol: types.Optional[str] = None,
        symbol_size: types.Union[types.Numeric, types.Sequence] = 4,
        stack: types.Optional[str] = None,
        stack_strategy: types.Optional[str] = "samesign",
        is_smooth: bool = False,
        is_clip: bool = True,
        is_step: bool = False,
        is_hover_animation: bool = True,
        z_level: types.Numeric = 0,
        z: types.Numeric = 0,
        log_base: types.Numeric = 10,
        sampling: types.Optional[str] = None,
        dimensions: types.Union[types.Sequence, None] = None,
        series_layout_by: str = "column",
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        label_opts: types.Label = opts.LabelOpts(),
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        areastyle_opts: types.AreaStyle = opts.AreaStyleOpts(),
        emphasis_opts: types.Emphasis = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        self._append_color(color)
        self._append_legend(series_name)

        if all([isinstance(d, opts.LineItem) for d in y_axis]):
            data = y_axis
        else:
            # 合并 x 和 y 轴数据，避免当 X 轴的类型设置为 'value' 的时候，
            # X、Y 轴均显示 Y 轴数据
            try:
                xaxis_index = xaxis_index or 0
                data = [
                    list(z)
                    for z in zip(self.options["xAxis"][xaxis_index]["data"], y_axis)
                ]
            except IndexError:
                data = [list(z) for z in zip(self._xaxis_data, y_axis)]

        if self.options.get("dataset") is not None and not y_axis:
            data = None

        self.options.get("series").append(
            {
                "type": ChartType.LINE,
                "name": series_name,
                "connectNulls": is_connect_nones,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "polarIndex": polar_index,
                "coordinateSystem": coordinate_system,
                "colorBy": color_by,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "showSymbol": is_symbol_show,
                "smooth": is_smooth,
                "clip": is_clip,
                "step": is_step,
                "stack": stack,
                "stackStrategy": stack_strategy,
                "data": data,
                "hoverAnimation": is_hover_animation,
                "label": label_opts,
                "logBase": log_base,
                "sampling": sampling,
                "dimensions": dimensions,
                "encode": encode,
                "seriesLayoutBy": series_layout_by,
                "lineStyle": linestyle_opts,
                "areaStyle": areastyle_opts,
                "emphasis": emphasis_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "zlevel": z_level,
                "z": z,
            }
        )
        return self



File: pyecharts/charts/basic_charts/graph.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Graph(Chart):
    """
    <<< Graph >>>

    The graph is used to represent the relational data.
    """

    def add(
        self,
        series_name: str,
        nodes: types.Sequence[types.GraphNode],
        links: types.Sequence[types.GraphLink],
        categories: types.Union[types.Sequence[types.GraphCategory], None] = None,
        *,
        is_focusnode: bool = True,
        is_roam: bool = True,
        is_draggable: bool = False,
        is_rotate_label: bool = False,
        layout: str = "force",
        symbol: types.Optional[str] = None,
        symbol_size: types.Numeric = 10,
        edge_length: types.Numeric = 30,
        gravity: types.Numeric = 0.2,
        friction: types.Numeric = 0.6,
        is_layout_animation: bool = True,
        repulsion: types.Numeric = 50,
        edge_label: types.Label = None,
        edge_symbol: types.Union[types.Sequence[str], str] = None,
        edge_symbol_size: types.Numeric = 10,
        label_opts: types.Label = opts.LabelOpts(),
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        _nodes = []
        for n in nodes:
            if isinstance(n, opts.GraphNode):
                n = n.opts
            _nodes.append(n)

        _links = []
        for link in links:
            if isinstance(link, opts.GraphLink):
                link = link.opts
            _links.append(link)

        if categories:
            for c in categories:
                if isinstance(c, opts.GraphCategory):
                    c = c.opts
                self._append_legend(c.get("name", ""))

        if edge_label is None:
            edge_label = opts.LabelOpts(is_show=False)

        if edge_symbol is None:
            edge_symbol = [None, None]

        self.options.get("series").append(
            {
                "type": ChartType.GRAPH,
                "name": series_name,
                "layout": layout,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "circular": {"rotateLabel": is_rotate_label},
                "force": {
                    "repulsion": repulsion,
                    "gravity": gravity,
                    "edgeLength": edge_length,
                    "friction": friction,
                    "layoutAnimation": is_layout_animation,
                },
                "label": label_opts,
                "lineStyle": linestyle_opts,
                "roam": is_roam,
                "draggable": is_draggable,
                "focusNodeAdjacency": is_focusnode,
                "data": _nodes,
                "categories": categories,
                "edgeLabel": edge_label,
                "edgeSymbol": edge_symbol,
                "edgeSymbolSize": edge_symbol_size,
                "links": _links,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/radar.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Radar(Chart):
    """
    <<< Radar >>>

    Radar maps are mainly used to represent multivariable data.
    """

    def add_schema(
        self,
        schema: types.Sequence[types.Union[opts.RadarIndicatorItem, dict]],
        shape: types.Optional[str] = None,
        center: types.Optional[types.Sequence] = None,
        radius: types.Optional[types.Union[types.Sequence, str]] = None,
        start_angle: types.Numeric = 90,
        textstyle_opts: types.TextStyle = opts.TextStyleOpts(),
        splitline_opt: types.SplitLine = opts.SplitLineOpts(is_show=True),
        splitarea_opt: types.SplitArea = opts.SplitAreaOpts(),
        axisline_opt: types.AxisLine = opts.AxisLineOpts(),
        axistick_opt: types.AxisTick = None,
        minor_tick_opts: types.MinorTick = None,
        axislabel_opt: types.Label = None,
        axispointer_opt: types.AxisPointer = None,
        radiusaxis_opts: types.RadiusAxis = None,
        angleaxis_opts: types.AngleAxis = None,
        polar_opts: types.Polar = None,
    ):
        self.options.update(
            radiusAxis=radiusaxis_opts, angleAxis=angleaxis_opts, polar=polar_opts
        )

        indicators = []
        for s in schema:
            if isinstance(s, opts.RadarIndicatorItem):
                s = s.opts
            indicators.append(s)

        if self.options.get("radar") is None:
            self.options.update(radar=[])

        self.options.get("radar").append(
            {
                "indicator": indicators,
                "shape": shape,
                "center": center,
                "radius": radius,
                "startAngle": start_angle,
                "name": {"textStyle": textstyle_opts},
                "splitLine": splitline_opt,
                "splitArea": splitarea_opt,
                "axisLine": axisline_opt,
                "axisTick": axistick_opt,
                "minorTick": minor_tick_opts,
                "axisLabel": axislabel_opt,
                "axisPointer": axispointer_opt,
            }
        )
        return self

    def add(
        self,
        series_name: str,
        data: types.Sequence[types.Union[opts.RadarItem, dict]],
        *,
        color_by: types.Optional[str] = None,
        symbol: types.Optional[str] = None,
        color: types.Optional[str] = None,
        label_opts: opts.LabelOpts = opts.LabelOpts(),
        radar_index: types.Numeric = None,
        selected_mode: types.Union[bool, str] = False,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        linestyle_opts: opts.LineStyleOpts = opts.LineStyleOpts(),
        areastyle_opts: opts.AreaStyleOpts = opts.AreaStyleOpts(),
        tooltip_opts: types.Tooltip = None,
        emphasis_opts: types.Emphasis = None,
    ):
        if all([isinstance(d, opts.RadarItem) for d in data]):
            for a in data:
                self._append_legend(a.get("name"))
        else:
            self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.RADAR,
                "name": series_name,
                "data": data,
                "colorBy": color_by,
                "symbol": symbol,
                "label": label_opts,
                "radarIndex": radar_index,
                "selectedMode": selected_mode,
                "zlevel": z_level,
                "z": z,
                "itemStyle": {"normal": {"color": color}},
                "lineStyle": linestyle_opts,
                "areaStyle": areastyle_opts,
                "tooltip": tooltip_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/__init__.py




File: pyecharts/charts/basic_charts/bmap.py

from ... import options as opts
from ... import types
from ...charts.basic_charts.geo import GeoChartBase
from ...commons.utils import OrderedSet
from ...exceptions import NonexistentCoordinatesException
from ...globals import ChartType

BAIDU_MAP_API = "https://api.map.baidu.com/api?v=2.0&ak={}"
BAIDU_MAP_GETSCRIPT = "https://api.map.baidu.com/getscript?v=2.0&ak={}"


class BMap(GeoChartBase):
    """
    <<< Baidu coordinate system >>>

    Support scatter plot, line
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        is_ignore_nonexistent_coord: bool = False,
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.js_dependencies.add("bmap")
        self._is_geo_chart = True
        self._coordinate_system: types.Optional[str] = "bmap"
        self.bmap_js_functions: OrderedSet = OrderedSet()
        self._is_ignore_nonexistent_coord = is_ignore_nonexistent_coord

    def _feed_data(self, data_pair: types.Sequence, type_: str) -> types.Sequence:
        result = []
        type_list = [ChartType.LINES, ChartType.CUSTOM]
        if type_ in type_list:
            result = data_pair
        else:
            for n, v in data_pair:
                try:
                    lng, lat = self.get_coordinate(n)
                    result.append({"name": n, "value": [lng, lat, v]})
                except TypeError as err:
                    if self._is_ignore_nonexistent_coord is not True:
                        raise NonexistentCoordinatesException(err, (n, v))
        return result

    def add_schema(
        self,
        baidu_ak: str,
        center: types.Sequence,
        zoom: types.Union[types.Numeric, str] = None,
        is_roam: bool = True,
        map_style: types.Optional[dict] = None,
    ):
        self.js_dependencies.add(
            BAIDU_MAP_API.format(baidu_ak), BAIDU_MAP_GETSCRIPT.format(baidu_ak)
        )
        self.options.update(
            bmap={
                "center": center,
                "zoom": zoom,
                "roam": is_roam,
                "mapStyle": map_style,
            }
        )
        return self

    def add_control_panel(
        self,
        navigation_control_opts: types.BMapNavigationControl = None,
        overview_map_opts: types.BMapOverviewMapControl = None,
        scale_control_opts: types.BMapScaleControl = None,
        maptype_control_opts: types.BMapTypeControl = None,
        copyright_control_opts: types.BMapCopyrightType = None,
        geo_location_control_opts: types.BMapGeoLocationControl = None,
    ):
        panel_options = [
            navigation_control_opts,
            overview_map_opts,
            scale_control_opts,
            maptype_control_opts,
            copyright_control_opts,
            geo_location_control_opts,
        ]

        for panel in panel_options:
            if panel is not None:
                fns = panel.get("functions")
                for fn in fns:
                    self.bmap_js_functions.add(fn)

        return self



File: pyecharts/charts/basic_charts/pie.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Pie(Chart):
    """
    <<< Pie >>>

    The pie chart is mainly used to represent the proportion of data of
    different categories in the total. Each radian represents the ratio of
    the number of data points.
    """

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence[types.Union[types.Sequence, opts.PieItem, dict]],
        *,
        color: types.Optional[str] = None,
        color_by: types.Optional[str] = "data",
        is_legend_hover_link: bool = True,
        selected_mode: types.Union[str, bool] = False,
        selected_offset: types.Numeric = 10,
        radius: types.Optional[types.Sequence] = None,
        center: types.Optional[types.Sequence] = None,
        rosetype: types.Union[str, bool] = None,
        is_clockwise: bool = True,
        start_angle: types.Numeric = 90,
        min_angle: types.Numeric = 0,
        min_show_label_angle: types.Numeric = 0,
        is_avoid_label_overlap: bool = True,
        is_still_show_zero_sum: bool = True,
        percent_precision: types.Numeric = 2,
        is_show_empty_circle: bool = True,
        empty_circle_style_opts: types.PieEmptyCircle = opts.PieEmptyCircleStyle(),
        label_opts: types.Label = opts.LabelOpts(),
        label_line_opts: types.PieLabelLine = opts.PieLabelLineOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
    ):
        if self.options.get("dataset") is not None:
            data = None
            self.options.get("legend")[0].update(
                data=[d[0] for d in self.options.get("dataset")[0].get("source")][1:]
            )
        elif isinstance(data_pair[0], opts.PieItem):
            data = data_pair
        else:
            data = [{"name": n, "value": v} for n, v in data_pair]

            for a, _ in data_pair:
                self.options.get("legend")[0].get("data").append(a)

            _dlst = self.options.get("legend")[0].get("data")
            _dset = list(set(_dlst))
            _dset.sort(key=_dlst.index)
            self.options.get("legend")[0].update(data=list(_dset))

        if not radius:
            radius = ["0%", "75%"]
        if not center:
            center = ["50%", "50%"]

        self._append_color(color)
        self.options.get("series").append(
            {
                "type": ChartType.PIE,
                "name": series_name,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "selectedMode": selected_mode,
                "selectedOffset": selected_offset,
                "clockwise": is_clockwise,
                "startAngle": start_angle,
                "minAngle": min_angle,
                "minShowLabelAngle": min_show_label_angle,
                "avoidLabelOverlap": is_avoid_label_overlap,
                "stillShowZeroSum": is_still_show_zero_sum,
                "percentPrecision": percent_precision,
                "showEmptyCircle": is_show_empty_circle,
                "emptyCircleStyle": empty_circle_style_opts,
                "data": data,
                "radius": radius,
                "center": center,
                "roseType": rosetype,
                "label": label_opts,
                "labelLine": label_line_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "encode": encode,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/pictorialbar.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


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

    PictorialBar is a histogram that can set various figurative graphic
    elements (such as images, SVG PathData, etc.)
    """

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[types.Numeric, opts.BarItem, dict]],
        *,
        symbol: types.Optional[str] = None,
        symbol_size: types.Union[types.Numeric, types.Sequence, None] = None,
        symbol_pos: types.Optional[str] = None,
        symbol_offset: types.Optional[types.Sequence] = None,
        symbol_rotate: types.Optional[types.Numeric] = None,
        symbol_repeat: types.Optional[str] = None,
        symbol_repeat_direction: types.Optional[str] = None,
        symbol_margin: types.Union[types.Numeric, str, None] = None,
        is_symbol_clip: bool = False,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        color: types.Optional[str] = None,
        category_gap: types.Union[types.Numeric, str] = "20%",
        gap: types.Optional[str] = None,
        label_opts: types.Label = opts.LabelOpts(),
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        encode: types.Union[types.JsCode, dict] = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_color(color)
        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.PICTORIALBAR,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "symbolPosition": symbol_pos,
                "symbolOffset": symbol_offset,
                "symbolRotate": symbol_rotate,
                "symbolRepeat": symbol_repeat,
                "symbolRepeatDirection": symbol_repeat_direction,
                "symbolMargin": symbol_margin,
                "symbolClip": is_symbol_clip,
                "name": series_name,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "data": y_axis,
                "barCategoryGap": category_gap,
                "barGap": gap,
                "label": label_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "encode": encode,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/themeriver.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class ThemeRiver(Chart):
    """
    <<< ThemeRiver >>>

    ThemeRiver graph is a special kind of flow graph,
    which is mainly used to show the changes of events or themes
    over a period of time.
    """

    def add(
        self,
        series_name: types.Sequence,
        data: types.Sequence[types.Union[opts.ThemeRiverItem, dict]],
        *,
        label_opts: types.Label = opts.LabelOpts(),
        singleaxis_opts: types.SingleAxis = opts.SingleAxisOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        for n in series_name:
            self._append_legend(n)

        self.options.get("series").append(
            {
                "type": ChartType.THEMERIVER,
                "name": series_name,
                "data": data,
                "label": label_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
            }
        )

        self.options.update(singleAxis=singleaxis_opts)
        self.options.get("tooltip").update(trigger="axis")
        return self



File: pyecharts/charts/basic_charts/wordcloud.py

import base64
import random
from pathlib import Path

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...commons.utils import JsCode
from ...exceptions import WordCloudMaskImageException
from ...globals import ChartType

SHAPES = ("cardioid", "diamond", "triangle-forward", "triangle", "pentagon", "star")


def gen_color():
    """
    generate random color for WordCloud
    """
    return "rgb(%s,%s,%s)" % (
        random.randint(0, 160),
        random.randint(0, 160),
        random.randint(0, 160),
    )


class WordCloud(Chart):
    """
    <<< WordCloud >>>

    Word cloud is to visually highlight the keywords that
    appear frequently in the text.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.js_dependencies.add("echarts-wordcloud")
        self._mask_image_suffix: types.Sequence = ["jpg", "jpeg", "png", "ico"]

    def _create_mask_image_variable(self, data: str) -> JsCode:
        image_str = self._encode_image_to_base64(image_or_path=data)
        if image_str is None:
            raise WordCloudMaskImageException(data=data)
        current_chart_id = self.chart_id
        self.add_js_funcs(
            f"""
        var maskImage_{current_chart_id} = new Image();
        maskImage_{current_chart_id}.src = '{image_str}';
        """
        )
        return JsCode(f"maskImage_{current_chart_id}")

    def _encode_image_to_base64(self, image_or_path: str) -> types.Optional[str]:
        try:
            # 尝试判断是否为图片路径(base64 长度很长会导致 Pathlib 会异常)
            is_image_file = Path(image_or_path).is_file()
            is_image_file_exist = Path(image_or_path).exists()
            if is_image_file and is_image_file_exist:
                ext = Path(image_or_path).suffix[1:]
                if ext in self._mask_image_suffix:
                    with open(Path(image_or_path), "rb") as f:
                        data = base64.b64encode(f.read()).decode()
                        image_str = f"data:image/{ext};base64,{data}"
                    return image_str
        except OSError:
            return image_or_path

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        *,
        shape: str = "circle",
        mask_image: types.Optional[str] = None,
        word_gap: types.Numeric = 20,
        word_size_range: types.Optional[types.Sequence] = None,
        rotate_step: types.Numeric = 45,
        pos_left: types.Optional[str] = None,
        pos_top: types.Optional[str] = None,
        pos_right: types.Optional[str] = None,
        pos_bottom: types.Optional[str] = None,
        width: types.Optional[str] = None,
        height: types.Optional[str] = None,
        is_draw_out_of_bound: bool = False,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        textstyle_opts: types.TextStyle = None,
        emphasis_shadow_blur: types.Optional[types.Numeric] = None,
        emphasis_shadow_color: types.Optional[str] = None,
    ):
        data = []
        for n, v in data_pair:
            data.append({"name": n, "value": v, "textStyle": {"color": gen_color()}})

        word_size_range = word_size_range or (12, 60)

        _rmin, _rmax = -90, 90
        # 确保设置的形状有效，单词的旋转角度应该设置在 [-90, 90]
        if shape in SHAPES:
            _rmin = _rmax = 0
        else:
            shape = "circle"

        if mask_image is not None:
            shape = None
            mask_image = self._create_mask_image_variable(data=mask_image)

        self.options.get("series").append(
            {
                "type": ChartType.WORDCLOUD,
                "name": series_name,
                "shape": shape,
                "maskImage": mask_image,
                "rotationRange": [_rmin, _rmax],
                "rotationStep": rotate_step,
                "girdSize": word_gap,
                "sizeRange": word_size_range,
                "data": data,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "bottom": pos_bottom,
                "width": width,
                "height": height,
                "drawOutOfBound": is_draw_out_of_bound,
                "textStyle": {
                    "normal": textstyle_opts,
                    "emphasis": {
                        "shadowBlur": emphasis_shadow_blur,
                        "shadowColor": emphasis_shadow_color,
                    },
                },
            }
        )
        return self



File: pyecharts/charts/basic_charts/map.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class MapMixin:
    """
    <<< Map >>>

    Map are mainly used for visualization of geographic area data.
    """

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence[types.Union[types.Sequence, opts.MapItem, dict]],
        maptype: str = "china",
        *,
        is_roam: bool = True,
        center: types.Optional[types.Sequence] = None,
        aspect_scale: types.Numeric = 0.75,
        bounding_coords: types.Optional[types.Sequence[types.Numeric]] = None,
        min_scale_limit: types.Optional[types.Numeric] = None,
        max_scale_limit: types.Optional[types.Numeric] = None,
        name_property: str = "name",
        selected_mode: types.Union[bool, str] = False,
        zoom: types.Optional[types.Numeric] = 1,
        name_map: types.Optional[dict] = None,
        symbol: types.Optional[str] = None,
        map_value_calculation: str = "sum",
        is_map_symbol_show: bool = True,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        pos_left: types.Optional[types.Union[str, types.Numeric]] = None,
        pos_top: types.Optional[types.Union[str, types.Numeric]] = None,
        pos_right: types.Optional[types.Union[str, types.Numeric]] = None,
        pos_bottom: types.Optional[types.Union[str, types.Numeric]] = None,
        geo_index: types.Optional[types.Numeric] = None,
        series_layout_by: str = "column",
        dataset_index: types.Optional[types.Numeric] = 0,
        layout_center: types.Optional[types.Sequence[str]] = None,
        layout_size: types.Union[str, types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_label_opts: types.Label = None,
        emphasis_itemstyle_opts: types.ItemStyle = None,
    ):
        self.js_dependencies.add(maptype)
        self._geo_json_name = maptype

        if isinstance(data_pair[0], opts.MapItem):
            data = data_pair
        else:
            data = [{"name": n, "value": v} for n, v in data_pair]

        scale_limit: types.Optional[dict] = {
            "min": min_scale_limit,
            "max": max_scale_limit,
        }
        if min_scale_limit is None and max_scale_limit is None:
            scale_limit = None

        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.MAP,
                "name": series_name,
                "symbol": symbol,
                "label": label_opts,
                "map": maptype,
                "data": data,
                "roam": is_roam,
                "aspectScale": aspect_scale,
                "boundingCoords": bounding_coords,
                "scaleLimit": scale_limit,
                "nameProperty": name_property,
                "selectedMode": selected_mode,
                "center": center,
                "zoom": zoom,
                "nameMap": name_map,
                "zlevel": z_level,
                "z": z,
                "left": pos_left,
                "top": pos_top,
                "right": pos_right,
                "bottom": pos_bottom,
                "geoIndex": geo_index,
                "seriesLayoutBy": series_layout_by,
                "datasetIndex": dataset_index,
                "mapValueCalculation": map_value_calculation,
                "showLegendSymbol": is_map_symbol_show,
                "layoutCenter": layout_center,
                "layoutSize": layout_size,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": {
                    "label": emphasis_label_opts,
                    "itemStyle": emphasis_itemstyle_opts,
                },
            }
        )
        return self


class Map(Chart, MapMixin):
    def add_geo_json(self, geo_json: dict):
        self._geo_json = geo_json
        return self



File: pyecharts/charts/basic_charts/calendar.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Calendar(Chart):
    """
    <<< Calendar Diagram >>>

    The calendar diagram is mainly used to represent the size of a value by
    color and must be used in conjunction with the visualMap component.
    Two categories of axes must be used in rectangular coordinates.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.options.update(calendar=opts.CalendarOpts().opts)

    def add(
        self,
        series_name: str,
        yaxis_data: types.Sequence,
        *,
        type_: types.Union[str, ChartType] = ChartType.HEATMAP,
        calendar_index: types.Optional[types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(is_show=False, position="inside"),
        calendar_opts: types.Union[types.Calendar, types.List[types.Calendar]] = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        visualmap_opts: types.VisualMap = None,
        emphasis_opts: types.Emphasis = None,
        **other_calendar_opts,
    ):
        if calendar_opts:
            self.options.update(calendar=calendar_opts)
        if visualmap_opts:
            self.options.update(visualMap=visualmap_opts)

        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": type_,
                "coordinateSystem": "calendar",
                "calendarIndex": calendar_index,
                "name": series_name,
                "data": yaxis_data,
                "label": label_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                **other_calendar_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/scatter.py

import itertools

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


class Scatter(RectChart):
    """
    <<< Scatter >>>

    The scatter diagram on the rectangular coordinate system can be used to
    show the relationship between x and y of the data. If the data item has
    multiple dimensions, it can be represented by color, and the
    visualmap component can be used.
    """

    def _parse_data(
        self, y_axis: types.Sequence[types.Union[opts.ScatterItem, dict]]
    ) -> types.Optional[types.Sequence]:
        if self.options.get("dataset") is not None:
            return None
        elif len(self._xaxis_data) == 0:
            return y_axis
        elif isinstance(y_axis[0], (opts.ScatterItem, dict)):
            return y_axis
        elif isinstance(y_axis[0], types.Sequence):
            return [
                list(itertools.chain(list([x]), y))
                for x, y in zip(self._xaxis_data, y_axis)
            ]
        else:
            return [list(z) for z in zip(self._xaxis_data, y_axis)]

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[opts.ScatterItem, dict]],
        *,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        color: types.Optional[str] = None,
        symbol: types.Optional[str] = None,
        symbol_size: types.Union[types.Numeric, types.Sequence] = 10,
        symbol_rotate: types.Optional[types.Numeric] = None,
        label_opts: types.Label = opts.LabelOpts(position="right"),
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        self._append_color(color)
        self._append_legend(series_name)

        data = self._parse_data(y_axis=y_axis)

        self.options.get("series").append(
            {
                "type": ChartType.SCATTER,
                "name": series_name,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "symbolRotate": symbol_rotate,
                "data": data,
                "label": label_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "encode": encode,
            }
        )
        return self



File: pyecharts/charts/basic_charts/bar.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


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(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[types.Numeric, opts.BarItem, dict]],
        *,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        polar_index: types.Optional[types.Numeric] = None,
        is_round_cap: types.Optional[bool] = None,
        color_by: types.Optional[str] = None,
        is_legend_hover_link: bool = True,
        color: types.Optional[str] = None,
        is_realtime_sort: bool = False,
        is_show_background: bool = False,
        background_style: types.Union[types.BarBackground, dict, None] = None,
        stack: types.Optional[str] = None,
        stack_strategy: types.Optional[str] = "samesign",
        sampling: types.Optional[str] = None,
        cursor: types.Optional[str] = "pointer",
        bar_width: types.Union[types.Numeric, str] = None,
        bar_max_width: types.Union[types.Numeric, str] = None,
        bar_min_width: types.Union[types.Numeric, str] = None,
        bar_min_height: types.Numeric = 0,
        category_gap: types.Union[types.Numeric, str] = "20%",
        gap: types.Optional[str] = "30%",
        is_large: bool = False,
        large_threshold: types.Numeric = 400,
        progressive: types.Optional[types.Numeric] = None,
        progressive_threshold: types.Optional[types.Numeric] = None,
        progressive_chunk_mode: types.Optional[str] = None,
        dimensions: types.Union[types.Sequence, None] = None,
        series_layout_by: str = "column",
        dataset_index: types.Numeric = 0,
        is_clip: bool = True,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        label_opts: types.Label = opts.LabelOpts(),
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        blur_opts: types.Blur = None,
        select_opts: types.Select = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
    ):
        self._append_color(color)
        self._append_legend(series_name)

        if self.options.get("dataset") is not None:
            y_axis = None

        self.options.get("series").append(
            {
                "type": ChartType.BAR,
                "name": series_name,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "polarIndex": polar_index,
                "roundCap": is_round_cap,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "data": y_axis,
                "realtimeSort": is_realtime_sort,
                "showBackground": is_show_background,
                "backgroundStyle": background_style,
                "stack": stack,
                "stackStrategy": stack_strategy,
                "sampling": sampling,
                "cursor": cursor,
                "barWidth": bar_width,
                "barMaxWidth": bar_max_width,
                "barMinWidth": bar_min_width,
                "barMinHeight": bar_min_height,
                "barCategoryGap": category_gap,
                "barGap": gap,
                "large": is_large,
                "largeThreshold": large_threshold,
                "progressive": progressive,
                "progressiveThreshold": progressive_threshold,
                "progressiveChunkMode": progressive_chunk_mode,
                "dimensions": dimensions,
                "seriesLayoutBy": series_layout_by,
                "datasetIndex": dataset_index,
                "clip": is_clip,
                "zlevel": z_level,
                "z": z,
                "label": label_opts,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "blur": blur_opts,
                "select": select_opts,
                "encode": encode,
            }
        )
        return self



File: pyecharts/charts/basic_charts/kline.py

from ... import options as opts
from ... import types
from ...charts.chart import RectChart
from ...globals import ChartType


class Kline(RectChart):
    """
    <<< K-line >>>

    K-line shows the highest value, the lowest value,
    the starting value and the ending value of the data on the day,
    which is used to show the daily fluctuation of the data or
    the fluctuation of a certain period.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.set_global_opts(
            xaxis_opts=opts.AxisOpts(is_scale=True),
            yaxis_opts=opts.AxisOpts(is_scale=True),
        )

    def add_yaxis(
        self,
        series_name: str,
        y_axis: types.Sequence[types.Union[opts.CandleStickItem, dict]],
        *,
        coordinate_system: str = "cartesian2d",
        color_by: types.Optional[str] = "series",
        bar_width: types.Optional[types.Numeric] = None,
        bar_min_width: types.Optional[types.Numeric] = None,
        bar_max_width: types.Optional[types.Numeric] = None,
        layout: types.Optional[str] = None,
        xaxis_index: types.Optional[types.Numeric] = None,
        yaxis_index: types.Optional[types.Numeric] = None,
        is_legend_hover_link: bool = True,
        is_hover_animation: bool = True,
        markline_opts: types.MarkLine = None,
        markpoint_opts: types.MarkPoint = None,
        markarea_opts: types.MarkArea = None,
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        selected_mode: types.Union[bool, str] = False,
        is_large: bool = False,
        encode: types.Union[types.JSFunc, dict, None] = None,
        is_clip: bool = True,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
    ):
        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.KLINE,
                "name": series_name,
                "coordinateSystem": coordinate_system,
                "colorBy": color_by,
                "legendHoverLink": is_legend_hover_link,
                "hoverAnimation": is_hover_animation,
                "layout": layout,
                "barWidth": bar_width,
                "barMinWidth": bar_min_width,
                "barMaxWidth": bar_max_width,
                "xAxisIndex": xaxis_index,
                "yAxisIndex": yaxis_index,
                "data": y_axis,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "selectedMode": selected_mode,
                "large": is_large,
                "encode": encode,
                "clip": is_clip,
                "zlevel": z_level,
                "z": z,
            }
        )
        return self



File: pyecharts/charts/basic_charts/parallel.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Parallel(Chart):
    """
    <<< Parallel >>>

    Parallel coordinate systems are commonly used to visualize graphs of
    high dimensional data.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.options.update(parallel=opts.ParallelOpts().opts)

    def add_schema(
        self,
        schema: types.Sequence[types.Union[opts.ParallelAxisOpts, dict]],
        parallel_opts: types.Union[opts.ParallelOpts, dict, None] = None,
    ):
        sc = []
        for s in schema:
            if isinstance(s, opts.ParallelAxisOpts):
                s = s.opts
            sc.append(s)
        self.options.update(parallelAxis=sc)

        if parallel_opts:
            if isinstance(parallel_opts, opts.ParallelOpts):
                parallel_opts = parallel_opts.opts
            self.options.update(parallel=parallel_opts)
        return self

    def add(
        self,
        series_name: str,
        data: types.Sequence[types.Union[opts.ParallelItem, dict]],
        *,
        parallel_index: types.Optional[types.Numeric] = None,
        color_by: types.Optional[str] = None,
        inactive_opacity: types.Optional[types.Numeric] = 0.05,
        active_opacity: types.Optional[types.Numeric] = 1,
        is_realtime: bool = True,
        is_smooth: bool = False,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.PARALLEL,
                "coordinateSystem": "parallel",
                "parallelIndex": parallel_index,
                "colorBy": color_by,
                "inactiveOpacity": inactive_opacity,
                "activeOpacity": active_opacity,
                "realTime": is_realtime,
                "zlevel": z_level,
                "z": z,
                "lineStyle": linestyle_opts,
                "name": series_name,
                "data": data,
                "smooth": is_smooth,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/funnel.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Funnel(Chart):
    """
    <<< Funnel >>>

    Funnel diagram is suitable for one-way analysis of single process
    with standardized business process, long cycle and multiple links.
    Through comparison of business data of each link in the funnel,
    the link where the potential problems can be found intuitively,
    and then decisions can be made.
    """

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        *,
        color: types.Optional[str] = None,
        color_by: types.Optional[str] = None,
        min_: types.Numeric = 0,
        max_: types.Numeric = 100,
        min_size: types.Union[str, types.Numeric] = "0%",
        max_size: types.Union[str, types.Numeric] = "100%",
        orient: str = "vertical",
        sort_: str = "descending",
        gap: types.Numeric = 0,
        is_legend_hover_link: bool = True,
        funnel_align: str = "center",
        label_opts: types.Label = opts.LabelOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
        selected_mode: types.Union[bool, str] = False,
        z_level: types.Numeric = 0,
        z: types.Numeric = 2,
        pos_top: types.Union[str, types.Numeric, None] = None,
        pos_left: types.Union[str, types.Numeric, None] = None,
        pos_bottom: types.Union[str, types.Numeric, None] = None,
        pos_right: types.Union[str, types.Numeric, None] = None,
        width: types.Union[str, types.Numeric, None] = None,
        height: types.Union[str, types.Numeric, None] = None,
        dataset_index: types.Optional[types.Numeric] = None,
        encode: types.Union[types.JSFunc, dict, None] = None,
        markpoint_opts: types.MarkPoint = None,
        markline_opts: types.MarkLine = None,
        markarea_opts: types.MarkArea = None,
    ):
        self._append_color(color)
        if all([isinstance(d, opts.FunnelItem) for d in data_pair]):
            data = data_pair
            for a in data_pair:
                self._append_legend(a.opts.get("name"))
        else:
            data = [{"name": n, "value": v} for n, v in data_pair]
            for a, _ in data_pair:
                self._append_legend(a)

        _dset = set(self.options.get("legend")[0].get("data"))
        self.options.get("legend")[0].update(data=list(_dset))

        self.options.get("series").append(
            {
                "type": ChartType.FUNNEL,
                "name": series_name,
                "data": data,
                "colorBy": color_by,
                "min": min_,
                "max": max_,
                "minSize": min_size,
                "maxSize": max_size,
                "orient": orient,
                "sort": sort_,
                "gap": gap,
                "legendHoverLink": is_legend_hover_link,
                "funnelAlign": funnel_align,
                "label": label_opts,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
                "selectedMode": selected_mode,
                "zlevel": z_level,
                "z": z,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "bottom": pos_bottom,
                "width": width,
                "height": height,
                "datasetIndex": dataset_index,
                "encode": encode,
                "markPoint": markpoint_opts,
                "markLine": markline_opts,
                "markArea": markarea_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/sunburst.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Sunburst(Chart):
    """
    <<< Sunburst >>

    Sunburst graphs are composed of multiple layers of ring graphs.
    In terms of data structure, inner circle is the parent node of outer circle.
    Therefore, it can represent local and global proportions like pie charts and
    hierarchical relationships like rectangle tree graphs.
    """

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        *,
        center: types.Optional[types.Sequence] = None,
        radius: types.Optional[types.Sequence] = None,
        highlight_policy: str = "descendant",
        node_click: str = "rootToNode",
        sort_: types.Optional[types.JSFunc] = "desc",
        is_render_label_for_zero_data: bool = False,
        is_clockwise: bool = True,
        start_angle: types.Numeric = 90,
        levels: types.Optional[types.Sequence[types.SunburstLevelOpts]] = None,
        label_opts: types.Label = opts.LabelOpts(),
        label_line_opts: types.SunburstLabelLine = None,
        label_layout_opts: types.SunburstLabelLayout = None,
        itemstyle_opts: types.ItemStyle = None,
        tooltip_opts: types.Tooltip = None,
        emphasis_opts: types.Emphasis = None,
    ):
        if not center:
            center = ["50%", "50%"]
        if not radius:
            radius = ["0%", "75%"]

        self.options.get("series").append(
            {
                "type": ChartType.SUNBURST,
                "name": series_name,
                "data": data_pair,
                "center": center,
                "radius": radius,
                "highlightPolicy": highlight_policy,
                "nodeClick": node_click,
                "sort": sort_,
                "renderLabelForZeroData": is_render_label_for_zero_data,
                "clockwise": is_clockwise,
                "startAngle": start_angle,
                "levels": levels,
                "label": label_opts,
                "labelLine": label_line_opts,
                "labelLayout": label_layout_opts,
                "itemStyle": itemstyle_opts,
                "tooltip": tooltip_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/basic_charts/sankey.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart
from ...globals import ChartType


class Sankey(Chart):
    """
    <<< Sankey >>>

    Sankey diagram is a special flow diagram, which is mainly used to
    express how raw materials, energy and so on from the initial form through
    the intermediate process of processing, transformation to the final form.
    """

    def add(
        self,
        series_name: str,
        nodes: types.Sequence,
        links: types.Sequence,
        *,
        pos_left: types.Union[str, types.Numeric] = "5%",
        pos_top: types.Union[str, types.Numeric] = "5%",
        pos_right: types.Union[str, types.Numeric] = "20%",
        pos_bottom: types.Union[str, types.Numeric] = "5%",
        node_width: types.Numeric = 20,
        node_gap: types.Numeric = 8,
        node_align: str = "justify",
        layout_iterations: types.Optional[types.Numeric] = None,
        orient: str = "horizontal",
        is_draggable: bool = True,
        edge_label_opt: types.Label = None,
        levels: types.SankeyLevel = None,
        label_opts: types.Label = opts.LabelOpts(),
        linestyle_opt: types.LineStyle = opts.LineStyleOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_opts: types.Emphasis = None,
    ):
        self._append_legend(series_name)
        self.options.get("series").append(
            {
                "type": ChartType.SANKEY,
                "name": series_name,
                "data": nodes,
                "links": links,
                "left": pos_left,
                "top": pos_top,
                "right": pos_right,
                "bottom": pos_bottom,
                "nodeWidth": node_width,
                "nodeGap": node_gap,
                "nodeAlign": node_align,
                "layoutIterations": layout_iterations,
                "orient": orient,
                "draggable": is_draggable,
                "edgeLabel": edge_label_opt,
                "levels": levels,
                "label": label_opts,
                "lineStyle": linestyle_opt,
                "tooltip": tooltip_opts,
                "itemStyle": itemstyle_opts,
                "emphasis": emphasis_opts,
            }
        )
        return self



File: pyecharts/charts/composite_charts/grid.py

import copy

from ... import options as opts
from ... import types
from ...globals import ThemeType
from ..basic_charts.radar import Radar
from ..chart import Base, Chart, RectChart


class Grid(Base):
    """
    `Gird` Drawing grid in rectangular coordinate. In a single grid,
    at most two X and Y axes each is allowed. Line chart, bar chart,
    and scatter chart (bubble chart) can be drawn in grid.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.options: types.Optional[dict] = None
        self._axis_index: int = 0
        self._grow_grid_index: int = 0
        self.bg_color = init_opts.opts.get("bg_color")

    def add(
        self,
        chart: Chart,
        grid_opts: types.Union[opts.GridOpts, dict],
        *,
        grid_index: int = 0,
        is_control_axis_index: bool = False,
    ):
        if self.options is None:
            self.options = copy.deepcopy(chart.options)
            self.chart_id = chart.chart_id
            self.options.update(grid=[], title=[])
            if self.theme != ThemeType.WHITE:
                self.options.update(color=[])

            # Priority Order: Grid > Other Chart
            self.options.update(backgroundColor=self.bg_color)

            if not is_control_axis_index:
                for s in self.options.get("series"):
                    s.update(xAxisIndex=self._axis_index, yAxisIndex=self._axis_index)

        # visualMap 配置添加
        visual_map = chart.options.get("visualMap")
        if visual_map is not None:
            if isinstance(self.options.get("visualMap"), opts.VisualMapOpts):
                self.options.update(visualMap=[self.options.get("visualMap")])
            else:
                if self.options.get("visualMap") is None:
                    self.options.update(visualMap=[visual_map])
                else:
                    self.options.get("visualMap").extend([visual_map])

        # dataZoom 配置添加
        data_zoom = chart.options.get("dataZoom")
        if data_zoom is not None:
            if isinstance(self.options.get("dataZoom"), opts.DataZoomOpts):
                self.options.update(dataZoom=[self.options.get("dataZoom")])
            else:
                if self.options.get("dataZoom") is None:
                    self.options.update(dataZoom=[data_zoom])
                else:
                    self.options.get("dataZoom").extend([data_zoom])

        # title 配置添加
        title = chart.options.get("title", opts.TitleOpts().opts)
        if isinstance(title, opts.TitleOpts):
            title = title.opts
        if not isinstance(title, types.Sequence):
            title = (title,)
        self.options.get("title").extend(title)

        if not is_control_axis_index:
            for s in chart.options.get("series"):
                s.update(xAxisIndex=self._axis_index, yAxisIndex=self._axis_index)

        for dep in chart.js_dependencies.items:
            self.js_dependencies.add(dep)

        if chart.options.get("geo") is not None:
            self.options.update(geo=chart.options.get("geo"))

        if isinstance(chart, RectChart):
            if grid_index == 0:
                grid_index = self._grow_grid_index

            for x in chart.options.get("xAxis"):
                x.update(gridIndex=grid_index)
            for y in chart.options.get("yAxis"):
                y.update(gridIndex=grid_index)
            self._grow_grid_index += 1

        if self._axis_index > 0:
            self.options.get("series").extend(chart.options.get("series"))
            self.options.get("legend").extend(chart.options.get("legend"))
            if isinstance(chart, RectChart):
                self.options.get("xAxis").extend(chart.options.get("xAxis"))
                self.options.get("yAxis").extend(chart.options.get("yAxis"))
            if isinstance(chart, Radar):
                self.options.get("radar").extend(chart.options.get("radar"))

        self.options.get("grid").append(grid_opts)
        self._axis_index += 1
        return self



File: pyecharts/charts/composite_charts/timeline.py

from typing import Sequence

from ... import options as opts
from ... import types
from ...charts.chart import Base


class Timeline(Base):
    """
    `Timeline` provides functions like switching and playing between multiple charts.
    """

    def __init__(
        self,
        init_opts: types.Init = opts.InitOpts(),
        render_opts: types.RenderInit = opts.RenderOpts(),
    ):
        super().__init__(init_opts=init_opts, render_opts=render_opts)
        self.options = {"baseOption": {"series": [], "timeline": {}}, "options": []}
        self.add_schema()
        self._time_points: types.Sequence = []

    def add_schema(
        self,
        axis_type: str = "category",
        current_index: types.Numeric = 0,
        orient: str = "horizontal",
        symbol: types.Optional[str] = None,
        symbol_size: types.Optional[types.Numeric] = None,
        play_interval: types.Optional[types.Numeric] = None,
        control_position: str = "left",
        is_auto_play: bool = False,
        is_loop_play: bool = True,
        is_rewind_play: bool = False,
        is_timeline_show: bool = True,
        is_inverse: bool = False,
        pos_left: types.Optional[str] = None,
        pos_right: types.Optional[str] = None,
        pos_top: types.Optional[str] = None,
        pos_bottom: types.Optional[str] = "-5px",
        width: types.Optional[str] = None,
        height: types.Optional[str] = None,
        linestyle_opts: types.LineStyle = None,
        label_opts: types.Label = None,
        itemstyle_opts: types.ItemStyle = None,
        graphic_opts: types.Graphic = None,
        checkpointstyle_opts: types.TimeLinkCheckPoint = None,
        controlstyle_opts: types.TimeLineControl = None,
        progress_linestyle_opts: types.LineStyle = None,
        progress_itemstyle_opts: types.ItemStyle = None,
        progress_label_opts: types.Label = None,
    ):
        self.options.get("baseOption").get("timeline").update(
            {
                "axisType": axis_type,
                "currentIndex": current_index,
                "orient": orient,
                "autoPlay": is_auto_play,
                "controlPosition": control_position,
                "loop": is_loop_play,
                "rewind": is_rewind_play,
                "show": is_timeline_show,
                "inverse": is_inverse,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "playInterval": play_interval,
                "left": pos_left,
                "right": pos_right,
                "top": pos_top,
                "bottom": pos_bottom,
                "width": width,
                "height": height,
                "lineStyle": linestyle_opts,
                "label": label_opts,
                "itemStyle": itemstyle_opts,
                "graphic": graphic_opts,
                "checkpointStyle": checkpointstyle_opts,
                "controlStyle": controlstyle_opts,
                "progress": {
                    "lineStyle": progress_linestyle_opts,
                    "itemStyle": progress_itemstyle_opts,
                    "label": progress_label_opts,
                },
            }
        )
        return self

    def add(self, chart: Base, time_point: str):
        for dep in chart.js_dependencies.items:
            self.js_dependencies.add(dep)
        self._time_points.append(time_point)

        self.options.get("baseOption").get("timeline").update(data=self._time_points)
        self.options.get("options").append(
            {
                "backgroundColor": chart.options.get("backgroundColor"),
                "series": chart.options.get("series"),
                "xAxis": chart.options.get("xAxis"),
                "yAxis": chart.options.get("yAxis"),
                "title": chart.options.get("title"),
                "tooltip": chart.options.get("tooltip"),
                "visualMap": chart.options.get("visualMap"),
                "color": chart.options.get("color"),
                "graphic": chart.options.get("graphic"),
                "bmap": chart.options.get("bmap"),
                "toolbox": chart.options.get("toolbox"),
                "dataset": chart.options.get("dataset"),
                "radiusAxis": chart.options.get("radiusAxis"),
                "angleAxis": chart.options.get("angleAxis"),
                "xAxis3D": chart.options.get("xAxis3D"),
                "yAxis3D": chart.options.get("yAxis3D"),
                "zAxis3D": chart.options.get("zAxis3D"),
                "grid3D": chart.options.get("grid3D"),
            }
        )
        self.__check_components(chart)
        self.options.get("baseOption").update(series=chart.options.get("series"))
        return self

    def __check_components(self, chart: Base):
        components = [
            "grid",
            "xAxis",
            "yAxis",
            "polar",
            "radiusAxis",
            "geo",
            "angleAxis",
            "radar",
            "visualMap",
            "dataZoom",
            "parallelAxis",
            "legend",
        ]

        for component in components:
            c = chart.options.get(component, None)
            if c is not None:
                # eg: legend in timeline
                base_option_component = self.options.get("baseOption").get(component)
                if base_option_component and isinstance(c, Sequence):
                    base_option_component.extend(c)
                else:
                    self.options.get("baseOption").update({component: c})



File: pyecharts/charts/composite_charts/__init__.py




File: pyecharts/charts/composite_charts/page.py

import json
import re
import uuid

from jinja2 import Environment

from ... import types
from ...commons import utils
from ...globals import CurrentConfig, ThemeType
from ...options import PageLayoutOpts
from ...render import engine
from ..mixins import CompositeMixin

_MARK_FREEDOM_LAYOUT = "_MARK_FREEDOM_LAYOUT_"

DOWNLOAD_CFG_FUNC = """
function downloadCfg () {
    const fileName = 'chart_config.json'
    let downLink = document.createElement('a')
    downLink.download = fileName

    let result = []
    for(let i=0; i<charts_id.length; i++) {
        chart = $('#'+charts_id[i])
        result.push({
            cid: charts_id[i],
            width: chart.css("width"),
            height: chart.css("height"),
            top: chart.offset().top + "px",
            left: chart.offset().left + "px"
        })
    }

    let blob = new Blob([JSON.stringify(result)])
    downLink.href = URL.createObjectURL(blob)
    document.body.appendChild(downLink)
    downLink.click()
    document.body.removeChild(downLink)
}"""


class Page(CompositeMixin):
    """
    `Page` A container object to present multiple charts vertically in a single page
    """

    SimplePageLayout = PageLayoutOpts(
        justify_content="center", display="flex", flex_wrap="wrap"
    )
    DraggablePageLayout = PageLayoutOpts()

    def __init__(
        self,
        page_title: str = CurrentConfig.PAGE_TITLE,
        js_host: str = "",
        interval: int = 1,
        is_remove_br: bool = False,
        page_border_color: str = "",
        layout: types.Union[PageLayoutOpts, dict] = PageLayoutOpts(),
    ):
        self.js_host: str = js_host or CurrentConfig.ONLINE_HOST
        self.page_title = page_title
        self.page_interval = interval
        self.remove_br = is_remove_br
        self.page_border_color = page_border_color
        self.layout = self._assembly_layout(layout)
        self.js_functions: utils.OrderedSet = utils.OrderedSet()
        self.js_dependencies = utils.OrderedSet()
        self.download_button: bool = False
        self._charts: list = []

    def add(self, *charts):
        for c in charts:
            self._charts.append(c)
            for d in c.js_dependencies.items:
                self.js_dependencies.add(d)
        return self

    def _assembly_layout(self, layout: types.Union[PageLayoutOpts, dict]) -> str:
        if layout is Page.DraggablePageLayout:
            return _MARK_FREEDOM_LAYOUT
        result = ""
        if isinstance(layout, PageLayoutOpts):
            layout = layout.opts
        layout = utils.remove_key_with_none_value(layout)
        for k, v in layout.items():
            result += "{}:{}; ".format(k, v)
        return result

    def _prepare_render(self):
        for c in self:
            if hasattr(c, "dump_options"):
                c.json_contents = c.dump_options()
            if hasattr(c, "theme"):
                if c.theme not in ThemeType.BUILTIN_THEMES:
                    self.js_dependencies.add(c.theme)
        charts_id = []
        if self.layout == _MARK_FREEDOM_LAYOUT:
            self.download_button = True
            for c in self:
                charts_id.append("'{}'".format(c.chart_id))
                self.add_js_funcs(
                    # make charts resizable and draggable
                    f"$('#{c.chart_id}')"
                    ".resizable()"
                    ".draggable()"
                    ".css('border-style', 'dashed')"
                    ".css('border-width', '1px');"
                    # make charts Responsive
                    f'$("#{c.chart_id}>div:nth-child(1)")'
                    '.width("100%")'
                    '.height("100%");'
                )

                if not hasattr(c, "_component_type"):
                    self.add_js_funcs(
                        # call resize function
                        "new ResizeSensor("
                        f"jQuery('#{c.chart_id}'), "
                        f"function() {{ chart_{c.chart_id}.resize()}});"
                    )
            for lib in ("jquery", "jquery-ui", "resize-sensor"):
                self.js_dependencies.add(lib)

            self.add_js_funcs(
                "var charts_id = [{}];".format(",".join(charts_id)) + DOWNLOAD_CFG_FUNC
            )
            self.css_libs = [self.js_host + link for link in ("jquery-ui.css",)]
            self.layout = ""

    def render(
        self,
        path: str = "render.html",
        template_name: str = "simple_page.html",
        env: types.Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render(self, path, template_name, env, **kwargs)

    def render_embed(
        self,
        template_name: str = "simple_page.html",
        env: types.Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render_embed(self, template_name, env, **kwargs)

    def render_notebook(self):
        for c in self:
            c.chart_id = uuid.uuid4().hex
            if hasattr(c, "dump_options"):
                c.json_contents = c.dump_options()
            if hasattr(c, "theme"):
                if c.theme not in ThemeType.BUILTIN_THEMES:
                    self.js_dependencies.add(c.theme)
        return engine.render_notebook(
            self, "nb_jupyter_notebook.html", "nb_jupyter_lab.html"
        )

    @staticmethod
    def save_resize_html(
        source: str = "render.html",
        *,
        cfg_file: types.Optional[str] = None,
        cfg_dict: types.Optional[list] = None,
        dest: str = "resize_render.html",
    ) -> str:
        with open(source, "r", encoding="utf8") as f:
            html = f.read()

        charts = []
        if cfg_dict is None and cfg_file is None:
            raise ValueError("Chart layout config is empty")

        if cfg_file:
            with open(cfg_file, "r", encoding="utf8") as f:
                charts = json.load(f)

        if cfg_dict:
            charts = cfg_dict

        for chart in charts:
            s = (
                '<div id="{cid}" class="chart-container" style="position: absolute; '
                'width: {width}; height: {height}; top: {top}; left: {left};">'.format(
                    cid=chart["cid"],
                    width=chart["width"],
                    height=chart["height"],
                    top=chart["top"],
                    left=chart["left"],
                )
            )
            html = re.sub(
                '<div id="{}" class="chart-container" style="(.*?)">'.format(
                    chart["cid"]
                ),
                s,
                html,
            )
            html = re.sub("'border-width', '1px'", "'border-width', '0px'", html)
            html = re.sub(
                r'<button onclick="downloadCfg\(\)">Save Config</button>', "", html
            )
            html = re.sub(r".resizable\(\).draggable\(\)", "", html)

        with open(dest, "w+", encoding="utf8") as f:
            f.write(html)

        return html



File: pyecharts/charts/composite_charts/tab.py

import uuid

from jinja2 import Environment

from ... import types
from ...commons import utils
from ...globals import CurrentConfig, ThemeType
from ...options.charts_options import TabChartGlobalOpts
from ...render import engine
from ..mixins import CompositeMixin


DEFAULT_TAB_CSS: str = """
.chart-container {
    display: block;
}

.chart-container:nth-child(n+2) {
    display: none;
}

.tab {
    overflow: hidden;
    border: 1px solid #ccc;
    background-color: #f1f1f1;
}

"""
DEFAULT_TAB_BUTTON_CSS: str = """
.tab button {
    background-color: inherit;
    float: left;
    border: none;
    outline: none;
    cursor: pointer;
    padding: 12px 16px;
    transition: 0.3s;
}

"""
DEFAULT_TAB_BUTTON_HOVER_CSS: str = """
.tab button:hover {
    background-color: #ddd;
}

"""
DEFAULT_TAB_BUTTON_ACTIVE_CSS: str = """
.tab button.active {
    background-color: #ccc;
}

"""


class Tab(CompositeMixin):
    def __init__(
        self,
        page_title: str = CurrentConfig.PAGE_TITLE,
        js_host: str = "",
        bg_color: str = "",
        tab_css_opts: TabChartGlobalOpts = TabChartGlobalOpts(),
    ):
        self.js_host: str = js_host or CurrentConfig.ONLINE_HOST
        self.page_title: str = page_title
        self.bg_color = bg_color
        self.download_button: bool = False
        self.use_custom_tab_css = tab_css_opts.opts.get("enable")
        self.tab_custom_css = self._prepare_tab_css(css_opts=tab_css_opts)
        self.js_functions: utils.OrderedSet = utils.OrderedSet()
        self.js_dependencies: utils.OrderedSet = utils.OrderedSet()
        self._charts: list = []

    def add(self, chart, tab_name):
        chart.tab_name = tab_name
        self._charts.append(chart)
        for d in chart.js_dependencies.items:
            self.js_dependencies.add(d)
        return self

    def _prepare_tab_css(self, css_opts: TabChartGlobalOpts) -> str:
        result = ""
        if isinstance(css_opts, TabChartGlobalOpts):
            css_opts = css_opts.opts
        css_opts = utils.remove_key_with_none_value(css_opts)

        def _dict_to_str(opts: dict, key: str, css_selector: str) -> str:
            _inner_result = ""
            for k, v in opts.get(key, dict()).items():
                _inner_result += "{}:{}; ".format(k, v)
            return (
                f"{css_selector} " + "{ " + _inner_result + " }\n"
                if _inner_result != ""
                else ""
            )

        # .tab
        tab_base = _dict_to_str(opts=css_opts, key="base", css_selector=".tab")
        result += tab_base if tab_base != "" else DEFAULT_TAB_CSS
        # .tab button
        tab_button_base = _dict_to_str(
            opts=css_opts, key="button_base", css_selector=".tab button"
        )
        result += tab_button_base if tab_button_base != "" else DEFAULT_TAB_BUTTON_CSS
        # .tab button:hover
        tab_button_hover = _dict_to_str(
            opts=css_opts, key="button_hover", css_selector=".tab button:hover"
        )
        result += (
            tab_button_hover if tab_button_hover != "" else DEFAULT_TAB_BUTTON_HOVER_CSS
        )
        # .tab button.active
        tab_button_active = _dict_to_str(
            opts=css_opts, key="button_active", css_selector=".tab button.active"
        )
        result += (
            tab_button_active
            if tab_button_active != ""
            else DEFAULT_TAB_BUTTON_ACTIVE_CSS
        )
        if ".chart-container" not in result:
            result += """
            .chart-container { display: block; }

            .chart-container:nth-child(n+2) { display: none; }
            """
        return result

    def _prepare_render(self):
        for c in self:
            if not hasattr(c, "_is_tab_chart"):
                setattr(c, "_is_tab_chart", True)
            if hasattr(c, "dump_options"):
                c.json_contents = c.dump_options()
            if hasattr(c, "theme"):
                if c.theme not in ThemeType.BUILTIN_THEMES:
                    self.js_dependencies.add(c.theme)

    def render(
        self,
        path: str = "render.html",
        template_name: str = "simple_tab.html",
        env: types.Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render(self, path, template_name, env, **kwargs)

    def render_embed(
        self,
        template_name: str = "simple_tab.html",
        env: types.Optional[Environment] = None,
        **kwargs,
    ) -> str:
        self._prepare_render()
        return engine.render_embed(self, template_name, env, **kwargs)

    def render_notebook(self):
        self._prepare_render()
        # only notebook env need to re-generate chart_id
        for c in self:
            c.chart_id = uuid.uuid4().hex
        return engine.render_notebook(
            self, "nb_jupyter_notebook_tab.html", "nb_jupyter_lab_tab.html"
        )



File: pyecharts/charts/three_axis_charts/surface3D.py

from ... import options as opts
from ... import types
from ...charts.chart import ThreeAxisChart
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Surface3D(ThreeAxisChart):
    """
    <<< 3D Surface-Chart >>>
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.SURFACE



File: pyecharts/charts/three_axis_charts/line3D.py

from ... import types
from ...charts.chart import ThreeAxisChart
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Line3D(ThreeAxisChart):
    """
    <<< 3D Line-Chart >>>
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.LINE3D



File: pyecharts/charts/three_axis_charts/map_globe.py

import uuid

from jinja2 import Environment

from ... import types
from ...charts.basic_charts.map import MapMixin
from ...charts.chart import Chart3D
from ...commons import utils
from ...globals import CurrentConfig, NotebookType
from ...options import InitOpts, RenderOpts
from ...render.display import HTML
from ...render.engine import RenderEngine


class MapGlobe(Chart3D, MapMixin):
    """
    Globe Map
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)

    def add_schema(self, maptype: str = "china"):
        self.js_dependencies.add(maptype)
        return self

    def render(
        self,
        path: str = "render.html",
        template_name: str = "simple_globe.html",
        env: types.Optional[Environment] = None,
        **kwargs,
    ) -> str:
        return super().render(path=path, template_name=template_name, env=env, **kwargs)

    def render_notebook(self):
        self.chart_id = uuid.uuid4().hex
        self._prepare_render()
        if CurrentConfig.NOTEBOOK_TYPE == NotebookType.JUPYTER_NOTEBOOK:
            require_config = utils.produce_require_dict(
                self.js_dependencies, self.js_host
            )
            return HTML(
                RenderEngine().render_chart_to_notebook(
                    template_name="nb_jupyter_globe.html",
                    charts=(self,),
                    config_items=require_config["config_items"],
                    libraries=require_config["libraries"],
                )
            )



File: pyecharts/charts/three_axis_charts/__init__.py




File: pyecharts/charts/three_axis_charts/scatter3D.py

from ... import types
from ...charts.chart import ThreeAxisChart
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Scatter3D(ThreeAxisChart):
    """
    <<< 3D Scatter-Chart >>>
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.SCATTER3D



File: pyecharts/charts/three_axis_charts/lines3D.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart3D
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Lines3D(Chart3D):
    """
    Lines 3D
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.LINES3D

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        coordinate_system: str,
        *,
        geo_3d_index: types.Numeric = 0,
        globe_index: types.Numeric = 0,
        is_polyline: bool = False,
        is_show_lines_effect: bool = False,
        lines_effect_period: types.Numeric = 4,
        lines_effect_constant_speed: types.Optional[types.Numeric] = None,
        lines_effect_trail_width: types.Numeric = 4,
        lines_effect_trail_length: types.Numeric = 0.1,
        lines_effect_trail_color: types.Optional[str] = None,
        lines_effect_trail_opacity: types.Optional[types.Numeric] = None,
        blend_mode: str = "source-over",
        linestyle_opts: types.Optional[types.LineStyle] = None,
        z_level: types.Numeric = -10,
        is_silent: bool = False,
    ):
        self.options.get("series").append(
            {
                "type": ChartType.LINES3D,
                "name": series_name,
                "data": data_pair,
                "coordinateSystem": coordinate_system,
                "geo3DIndex": geo_3d_index,
                "globeIndex": globe_index,
                "polyline": is_polyline,
                "effect": {
                    "show": is_show_lines_effect,
                    "period": lines_effect_period,
                    "constantSpeed": lines_effect_constant_speed,
                    "trailWidth": lines_effect_trail_width,
                    "trailLength": lines_effect_trail_length,
                    "trailColor": lines_effect_trail_color,
                    "trailOpacity": lines_effect_trail_opacity,
                },
                "lineStyle": linestyle_opts,
                "blendMode": blend_mode,
                "zlevel": z_level,
                "silent": is_silent,
            }
        )
        return self



File: pyecharts/charts/three_axis_charts/bar3D.py

from ... import types
from ...charts.chart import ThreeAxisChart
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Bar3D(ThreeAxisChart):
    """
    <<< 3D Bar-Chart >>>
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.BAR3D



File: pyecharts/charts/three_axis_charts/map3D.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart3D
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class Map3D(Chart3D):
    """
    3D map
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.MAP3D

    def add(
        self,
        series_name: str,
        data_pair: types.Sequence,
        *,
        type_: ChartType = None,
        maptype: str = "china",
        is_map_symbol_show: bool = True,
        grid_3d_index: types.Numeric = 0,
        geo_3d_index: types.Numeric = 0,
        globe_index: types.Numeric = 0,
        bar_size: types.Optional[types.Numeric] = None,
        bevel_size: types.Numeric = 0,
        bevel_smoothness: types.Numeric = 2,
        stack: types.Optional[str] = None,
        min_height: types.Numeric = 2,
        symbol: str = "circle",
        symbol_size: types.Union[types.Numeric, types.Sequence, types.JSFunc] = 10,
        blend_mode: str = "source-over",
        is_polyline: bool = False,
        effect: types.Lines3DEffect = None,
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        label_opts: types.Label = opts.LabelOpts(),
        tooltip_opts: types.Tooltip = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_label_opts: types.Label = None,
        emphasis_itemstyle_opts: types.ItemStyle = None,
        shading: types.Optional[str] = None,
        realistic_material_opts: types.Optional[types.Map3DRealisticMaterial] = None,
        lambert_material_opts: types.Optional[types.Map3DLambertMaterial] = None,
        color_material_opts: types.Optional[types.Map3DColorMaterial] = None,
        zlevel: types.Numeric = -10,
        is_silent: bool = False,
        is_animation: bool = True,
        animation_duration_update: types.Numeric = 100,
        animation_easing_update: types.Numeric = "cubicOut",
    ):
        if type_ != ChartType.LINES3D:
            data = [{"name": n, "value": v} for n, v in data_pair]
        else:
            data = data_pair
        self._append_legend(series_name)
        if type_ is None or type_ == ChartType.MAP3D:
            self.options.get("series").append(
                {
                    "type": ChartType.MAP3D,
                    "name": series_name,
                    "map": maptype,
                    "coordinateSystem": "geo3D",
                    "label": label_opts,
                    "data": data,
                    "itemStyle": itemstyle_opts,
                    "showLegendSymbol": is_map_symbol_show,
                    "tooltip": tooltip_opts,
                    "emphasis": {
                        "label": emphasis_label_opts,
                        "itemStyle": emphasis_itemstyle_opts,
                    },
                }
            )
        elif type_ == ChartType.BAR3D:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "geo3D",
                    "grid3DIndex": grid_3d_index,
                    "geo3DIndex": geo_3d_index,
                    "globeIndex": globe_index,
                    "barSize": bar_size,
                    "bevelSize": bevel_size,
                    "bevelSmoothness": bevel_smoothness,
                    "stack": stack,
                    "minHeight": min_height,
                    "label": label_opts,
                    "itemStyle": itemstyle_opts,
                    "emphasis": {
                        "label": emphasis_label_opts,
                        "itemStyle": emphasis_itemstyle_opts,
                    },
                    "data": data,
                    "shading": shading,
                    "realisticMaterial": realistic_material_opts,
                    "lambertMaterial": lambert_material_opts,
                    "colorMaterial": color_material_opts,
                    "zlevel": zlevel,
                    "silent": is_silent,
                    "animation": is_animation,
                    "animationDurationUpdate": animation_duration_update,
                    "animationEasingUpdate": animation_easing_update,
                }
            )
        elif type_ == ChartType.SCATTER3D:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "geo3D",
                    "grid3DIndex": grid_3d_index,
                    "geo3DIndex": geo_3d_index,
                    "globeIndex": globe_index,
                    "symbol": symbol,
                    "symbolSize": symbol_size,
                    "label": label_opts,
                    "itemStyle": itemstyle_opts,
                    "emphasis": {
                        "label": emphasis_label_opts,
                        "itemStyle": emphasis_itemstyle_opts,
                    },
                    "data": data,
                    "blendMode": blend_mode,
                    "zlevel": zlevel,
                    "silent": is_silent,
                    "animation": is_animation,
                    "animationDurationUpdate": animation_duration_update,
                    "animationEasingUpdate": animation_easing_update,
                }
            )
        elif type_ == ChartType.LINE3D:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "geo3D",
                    "grid3DIndex": grid_3d_index,
                    "lineStyle": linestyle_opts,
                    "data": data,
                    "zlevel": zlevel,
                    "silent": is_silent,
                    "animation": is_animation,
                    "animationDurationUpdate": animation_duration_update,
                    "animationEasingUpdate": animation_easing_update,
                }
            )
        elif type_ == ChartType.LINES3D:
            self.options.get("series").append(
                {
                    "type": type_,
                    "name": series_name,
                    "coordinateSystem": "geo3D",
                    "geo3DIndex": geo_3d_index,
                    "globeIndex": globe_index,
                    "polyline": is_polyline,
                    "effect": effect,
                    "lineStyle": linestyle_opts,
                    "data": data,
                    "blendMode": blend_mode,
                    "zlevel": zlevel,
                    "silent": is_silent,
                }
            )
        return self

    def add_schema(
        self,
        maptype: str = "china",
        name: types.Optional[str] = None,
        *,
        box_width: types.Optional[types.Numeric] = 100,
        box_height: types.Optional[types.Numeric] = 10,
        box_depth: types.Optional[types.Numeric] = None,
        region_height: types.Optional[types.Numeric] = 3,
        environment: types.Optional[types.JSFunc] = None,
        is_show_ground: bool = False,
        ground_color: str = "#aaa",
        is_instancing: bool = False,
        map3d_label: types.Map3DLabel = None,
        itemstyle_opts: types.ItemStyle = None,
        emphasis_label_opts: types.Label = None,
        emphasis_itemstyle_opts: types.ItemStyle = None,
        shading: types.Optional[str] = None,
        realistic_material_opts: types.Optional[types.Map3DRealisticMaterial] = None,
        lambert_material_opts: types.Optional[types.Map3DLambertMaterial] = None,
        color_material_opts: types.Optional[types.Map3DColorMaterial] = None,
        light_opts: types.Optional[types.Map3DLight] = None,
        post_effect_opts: types.Optional[types.Map3DPostEffect] = None,
        is_enable_super_sampling: types.Union[str, bool] = "auto",
        view_control_opts: types.Optional[types.Map3DViewControl] = None,
        zlevel: types.Optional[types.Numeric] = -10,
        pos_left: types.Union[types.Numeric, str] = "auto",
        pos_top: types.Union[types.Numeric, str] = "auto",
        pos_right: types.Union[types.Numeric, str] = "auto",
        pos_bottom: types.Union[types.Numeric, str] = "auto",
        pos_width: types.Union[types.Numeric, str] = "auto",
        pos_height: types.Union[types.Numeric, str] = "auto",
    ):
        self.js_dependencies.add(maptype)
        self.options.update(
            geo3D={
                "map": maptype,
                "name": name,
                "boxWidth": box_width,
                "boxHeight": box_height,
                "boxDepth": box_depth,
                "regionHeight": region_height,
                "environment": environment,
                "groundPlane": {"show": is_show_ground, "color": ground_color},
                "instancing": is_instancing,
                "itemStyle": itemstyle_opts,
                "label": map3d_label,
                "emphasis": {
                    "label": emphasis_label_opts,
                    "itemStyle": emphasis_itemstyle_opts,
                },
                "shading": shading,
                "realisticMaterial": realistic_material_opts,
                "lambertMaterial": lambert_material_opts,
                "colorMaterial": color_material_opts,
                "light": light_opts,
                "postEffect": post_effect_opts,
                "temporalSuperSampling": {"enable": is_enable_super_sampling},
                "viewControl": view_control_opts,
                "zlevel": zlevel,
                "left": pos_left,
                "top": pos_top,
                "right": pos_right,
                "bottom": pos_bottom,
                "width": pos_width,
                "height": pos_height,
            }
        )
        return self



File: pyecharts/charts/three_axis_charts/graph_gl.py

from ... import options as opts
from ... import types
from ...charts.chart import Chart3D
from ...globals import ChartType
from ...options import InitOpts, RenderOpts


class GraphGL(Chart3D):
    """
    <<< GraphGL Relational graphs using WebGL to support the layout and
    drawing of large-scale network/relational data. >>>
    """

    def __init__(
        self,
        init_opts: types.Init = InitOpts(),
        render_opts: types.RenderInit = RenderOpts(),
    ):
        super().__init__(init_opts, render_opts)
        self._3d_chart_type = ChartType.GRAPHGL

    def add(
        self,
        series_name: str,
        nodes: types.Sequence[types.GraphGLNode],
        links: types.Sequence[types.GraphGLLink],
        *,
        layout: str = "forceAtlas2",
        force_atlas2_opts: types.GraphGLForceAtlas2 = None,
        symbol: types.Optional[str] = "circle",
        symbol_size: types.Numeric = 5,
        itemstyle_opts: types.ItemStyle = None,
        linestyle_opts: types.LineStyle = opts.LineStyleOpts(),
        z_level: types.Numeric = 10,
    ):
        self.options.get("series").append(
            {
                "type": ChartType.GRAPHGL,
                "name": series_name,
                "layout": layout,
                "forceAtlas2": force_atlas2_opts,
                "nodes": nodes,
                "links": links,
                "symbol": symbol,
                "symbolSize": symbol_size,
                "itemStyle": itemstyle_opts,
                "lineStyle": linestyle_opts,
                "zlevel": z_level,
            }
        )
        return self



File: pyecharts/render/__init__.py

from .snapshot import make_snapshot



File: pyecharts/render/display.py

from ..types import Optional, Sequence, Union
from urllib.parse import urlparse
import http.client


class HTML:
    def __init__(self, data: Optional[str] = None):
        self.data = data

    def _repr_html_(self):
        return self.data

    def __html__(self):
        return self._repr_html_()


_lib_t1 = """new Promise(function(resolve, reject) {
    var script = document.createElement("script");
    script.onload = resolve;
    script.onerror = reject;
    script.src = "%s";
    document.head.appendChild(script);
}).then(() => {
"""

_lib_t2 = """
});"""

_css_t = """var link = document.createElement("link");
    link.ref = "stylesheet";
    link.type = "text/css";
    link.href = "%s";
    document.head.appendChild(link);
"""


class Javascript:
    def __init__(
        self,
        data: Optional[str] = None,
        lib: Optional[Union[str, Sequence]] = None,
        css: Optional[Union[str, Sequence]] = None,
    ):
        if isinstance(lib, str):
            lib = [lib]
        elif lib is None:
            lib = []
        if isinstance(css, str):
            css = [css]
        elif css is None:
            css = []
        self.lib = lib
        self.css = css
        self.data = data or ""
        self.javascript_contents = dict()

    def _repr_javascript_(self):
        r = ""
        for c in self.css:
            r += _css_t % c
        for d in self.lib:
            r += _lib_t1 % d
        r += self.data
        r += _lib_t2 * len(self.lib)
        return r

    def load_javascript_contents(self):
        for lib in self.lib:
            parsed_url = urlparse(lib)

            host: str = str(parsed_url.hostname)
            port: int = parsed_url.port
            path: str = parsed_url.path

            resp: Optional[http.client.HTTPResponse] = None
            try:
                conn = http.client.HTTPSConnection(host, port)
                conn.request("GET", path)
                resp = conn.getresponse()
                if resp.status != 200:
                    raise RuntimeError("Cannot load JavaScript lib: %s" % lib)
                self.javascript_contents[lib] = resp.read().decode("utf-8")
            finally:
                if resp is not None:
                    resp.close()
        return self



File: pyecharts/render/engine.py

import os

try:
    from collections.abc import Iterable
except ImportError:
    from collections import Iterable

from jinja2 import Environment

from ..commons import utils
from ..datasets import EXTRA, FILENAMES
from ..globals import CurrentConfig, NotebookType, RenderSepType
from ..types import Any, Optional
from .display import HTML, Javascript


def write_utf8_html_file(file_name: str, html_content: str):
    with open(file=file_name,
              mode="w+",
              encoding="utf-8",
              newline=RenderSepType.SepType) as html_file:
        html_file.write(html_content)


class RenderEngine:
    def __init__(self, env: Optional[Environment] = None):
        self.env = env or CurrentConfig.GLOBAL_ENV

    @staticmethod
    def generate_js_link(chart: Any) -> Any:
        if not chart.js_host:
            chart.js_host = CurrentConfig.ONLINE_HOST
        links = []
        for dep in chart.js_dependencies.items:
            # TODO: if?
            if dep.startswith("https://api.map.baidu.com"):
                links.append(dep)
            if dep in FILENAMES:
                f, ext = FILENAMES[dep]
                links.append("{}{}.{}".format(chart.js_host, f, ext))
            else:
                for url, files in EXTRA.items():
                    if dep in files:
                        f, ext = files[dep]
                        links.append("{}{}.{}".format(url, f, ext))
                        break
        chart.dependencies = links
        return chart

    def render_chart_to_file(self, template_name: str, chart: Any, path: str, **kwargs):
        """
        Render a chart or page to local html files.

        :param chart: A Chart or Page object
        :param path: The destination file which the html code write to
        :param template_name: The name of template file.
        """
        tpl = self.env.get_template(template_name)
        html = utils.replace_placeholder(
            tpl.render(chart=self.generate_js_link(chart), **kwargs)
        )
        write_utf8_html_file(path, html)

    def render_chart_to_template(self, template_name: str, chart: Any, **kwargs) -> str:
        tpl = self.env.get_template(template_name)
        return utils.replace_placeholder(
            tpl.render(chart=self.generate_js_link(chart), **kwargs)
        )

    def render_chart_to_notebook(self, template_name: str, **kwargs) -> str:
        tpl = self.env.get_template(template_name)
        return utils.replace_placeholder(tpl.render(**kwargs))


def render(
    chart, path: str, template_name: str, env: Optional[Environment], **kwargs
) -> str:
    RenderEngine(env).render_chart_to_file(
        template_name=template_name, chart=chart, path=path, **kwargs
    )
    return os.path.abspath(path)


def render_embed(
    chart, template_name: str, env: Optional[Environment], **kwargs
) -> str:
    return RenderEngine(env).render_chart_to_template(
        template_name=template_name, chart=chart, **kwargs
    )


def render_notebook(self, notebook_template, lab_template):
    instance = self if isinstance(self, Iterable) else (self,)
    if CurrentConfig.NOTEBOOK_TYPE == NotebookType.JUPYTER_NOTEBOOK:
        require_config = utils.produce_require_dict(self.js_dependencies, self.js_host)
        return HTML(
            RenderEngine().render_chart_to_notebook(
                template_name=notebook_template,
                charts=instance,
                config_items=require_config["config_items"],
                libraries=require_config["libraries"],
            )
        )

    if CurrentConfig.NOTEBOOK_TYPE == NotebookType.JUPYTER_LAB:
        return HTML(
            RenderEngine().render_chart_to_notebook(
                template_name=lab_template, charts=instance
            )
        )

    if CurrentConfig.NOTEBOOK_TYPE == NotebookType.NTERACT:
        return HTML(self.render_embed())

    if CurrentConfig.NOTEBOOK_TYPE == NotebookType.ZEPPELIN:
        print("%html " + self.render_embed())


def load_javascript(chart):
    scripts = []
    for dep in chart.js_dependencies.items:
        f, ext = FILENAMES[dep]
        scripts.append("{}{}.{}".format(CurrentConfig.ONLINE_HOST, f, ext))
    return Javascript(lib=scripts)



File: pyecharts/render/snapshot.py

import base64
import codecs
import logging
import os
from io import BytesIO

from ..types import Any

logger = logging.getLogger(__name__)

PNG_FORMAT = "png"
JPG_FORMAT = "jpeg"
GIF_FORMAT = "gif"
PDF_FORMAT = "pdf"
SVG_FORMAT = "svg"
EPS_FORMAT = "eps"
B64_FORMAT = "base64"


def make_snapshot(
    engine: Any,
    file_name: str,
    output_name: str,
    delay: float = 2,
    pixel_ratio: int = 2,
    is_remove_html: bool = False,
    **kwargs,
):
    logger.info("Generating file ...")
    file_type = output_name.split(".")[-1]

    content = engine.make_snapshot(
        html_path=file_name,
        file_type=file_type,
        delay=delay,
        pixel_ratio=pixel_ratio,
        **kwargs,
    )
    if file_type in [SVG_FORMAT, B64_FORMAT]:
        save_as_text(content, output_name)
    else:
        # pdf, gif, png, jpeg
        content_array = content.split(",")
        if len(content_array) != 2:
            raise OSError(content_array)

        image_data = decode_base64(content_array[1])

        if file_type in [PDF_FORMAT, GIF_FORMAT, EPS_FORMAT]:
            save_as(image_data, output_name, file_type)
        elif file_type in [PNG_FORMAT, JPG_FORMAT]:
            save_as_png(image_data, output_name)
        else:
            raise TypeError(f"Not supported file type '{file_type}'")

    if "/" not in output_name:
        output_name = os.path.join(os.getcwd(), output_name)

    if is_remove_html and not file_name.startswith("http"):
        os.unlink(file_name)
    logger.info(f"File saved in {output_name}")


def decode_base64(data: str) -> bytes:
    """Decode base64, padding being optional.

    :param data: Base64 data as an ASCII byte string
    :returns: The decoded byte string.
    """
    missing_padding = len(data) % 4
    if missing_padding != 0:
        data += "=" * (4 - missing_padding)
    return base64.decodebytes(data.encode("utf-8"))


def save_as_png(image_data: bytes, output_name: str):
    with open(output_name, "wb") as f:
        f.write(image_data)


def save_as_text(image_data: str, output_name: str):
    with codecs.open(output_name, "w", encoding="utf-8") as f:
        f.write(image_data)


def save_as(image_data: bytes, output_name: str, file_type: str):
    try:
        from PIL import Image

        m = Image.open(BytesIO(image_data))
        m.load()
        color = (255, 255, 255)
        b = Image.new("RGB", m.size, color)
        # BUG for Mac:
        #   b.paste(m, mask=m.split()[3])
        b.paste(m)
        b.save(output_name, file_type, quality=100)
    except ModuleNotFoundError:
        raise Exception(f"Please install PIL for {file_type} image type")



File: pyecharts/components/__init__.py

from .image import Image
from .table import Table



File: pyecharts/components/table.py

import uuid

from jinja2 import Environment
from prettytable import PrettyTable

from ..charts.mixins import ChartMixin
from ..commons.utils import OrderedSet
from ..globals import CurrentConfig
from ..options import ComponentTitleOpts
from ..render import engine
from ..types import Optional, Sequence, Union


class Table(ChartMixin):
    def __init__(self, page_title: str = CurrentConfig.PAGE_TITLE, js_host: str = ""):
        self.page_title = page_title
        self.js_host = js_host or CurrentConfig.ONLINE_HOST
        self.js_dependencies: OrderedSet = OrderedSet()
        self.js_functions: OrderedSet = OrderedSet()
        self.title_opts: ComponentTitleOpts = ComponentTitleOpts()
        self.html_content: str = ""
        self._component_type: str = "table"
        self.chart_id: str = uuid.uuid4().hex

    def add(self, headers: Sequence, rows: Sequence, attributes: Optional[dict] = None):
        attributes = attributes or {"class": "fl-table"}
        table = PrettyTable(headers, attributes=attributes)
        for r in rows:
            table.add_row(r)
        self.html_content = table.get_html_string()
        return self

    def set_global_opts(self, title_opts: Union[ComponentTitleOpts, dict, None] = None):
        self.title_opts = title_opts
        return self

    def render(
        self,
        path: str = "render.html",
        template_name: str = "components.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        return engine.render(self, path, template_name, env, **kwargs)

    def render_embed(
        self,
        template_name: str = "components.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        return engine.render_embed(self, template_name, env, **kwargs)

    def render_notebook(self):
        # only notebook env need to re-generate chart_id
        self.chart_id = uuid.uuid4().hex
        return engine.render_notebook(self, "nb_components.html", "nb_components.html")



File: pyecharts/components/image.py

import uuid

from jinja2 import Environment

from ..charts.mixins import ChartMixin
from ..commons.utils import OrderedSet
from ..globals import CurrentConfig
from ..options import ComponentTitleOpts
from ..render import engine
from ..types import Optional, Union


class Image(ChartMixin):
    def __init__(self, page_title: str = CurrentConfig.PAGE_TITLE, js_host: str = ""):
        self.page_title = page_title
        self.js_host = js_host or CurrentConfig.ONLINE_HOST
        self.js_dependencies: OrderedSet = OrderedSet()
        self.title_opts: ComponentTitleOpts = ComponentTitleOpts()
        self.html_content: str = ""
        self._component_type: str = "image"
        self.chart_id: str = uuid.uuid4().hex

    def add(self, src: str, style_opts: Optional[dict] = None):
        html_tag_args = ""
        html_tag_args += 'src="{}" '.format(src)
        if style_opts:
            for k, v in style_opts.items():
                html_tag_args += '{}="{}" '.format(k, v)
        self.html_content = html_tag_args
        return self

    def set_global_opts(self, title_opts: Union[ComponentTitleOpts, dict, None] = None):
        self.title_opts = title_opts
        return self

    def render(
        self,
        path: str = "render.html",
        template_name: str = "components.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        return engine.render(self, path, template_name, env, **kwargs)

    def render_embed(
        self,
        template_name: str = "components.html",
        env: Optional[Environment] = None,
        **kwargs,
    ) -> str:
        return engine.render_embed(self, template_name, env, **kwargs)

    def render_notebook(self):
        # only notebook env need to re-generate chart_id
        self.chart_id = uuid.uuid4().hex
        return engine.render_notebook(self, "nb_components.html", "nb_components.html")



File: pyecharts/scaffold/__init__.py




<p align="center">
    <img src="https://user-images.githubusercontent.com/19553554/71825144-2d568180-30d6-11ea-8ee0-63c849cfd934.png" alt="pyecharts logo" width=200 height=200 />
</p>
<h1 align="center">pyecharts</h1>
<p align="center">
    <em>Python ❤️ ECharts = pyecharts</em>
</p>
<p align="center">
    <a href="https://github.com/pyecharts/pyecharts/actions">
        <img src="https://github.com/pyecharts/pyecharts/actions/workflows/python-app.yml/badge.svg" alt="Github Actions Status">
    </a>
    <a href="https://codecov.io/gh/pyecharts/pyecharts">
        <img src="https://codecov.io/gh/pyecharts/pyecharts/branch/master/graph/badge.svg" alt="Codecov">
    </a>
    <a href="https://badge.fury.io/py/pyecharts">
        <img src="https://badge.fury.io/py/pyecharts.svg" alt="Package version">
    </a>
    <a href="https://pypi.org/project/pyecharts/">
        <img src="https://img.shields.io/pypi/pyversions/pyecharts.svg?colorB=brightgreen" alt="PyPI - Python Version">
    </a>
</p>
<p align="center">
    <a href="https://pypi.org/project/pyecharts">
        <img src="https://img.shields.io/pypi/format/pyecharts.svg" alt="PyPI - Format">
    </a>
     <a href="https://github.com/pyecharts/pyecharts/pulls">
        <img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" alt="Contributions welcome">
    </a>
    <a href="https://opensource.org/licenses/MIT">
        <img src="https://img.shields.io/badge/License-MIT-brightgreen.svg" alt="License">
    </a>
</p>

[English README](README.en.md)

## 📣 简介

[Apache ECharts](https://github.com/apache/echarts) 是一个由百度开源的数据可视化，凭借着良好的交互性，精巧的图表设计，得到了众多开发者的认可。而 Python 是一门富有表达力的语言，很适合用于数据处理。当数据分析遇上数据可视化时，[pyecharts](https://github.com/pyecharts/pyecharts) 诞生了。

## ✨ 特性

* 简洁的 API 设计，使用如丝滑般流畅，支持链式调用
* 囊括了 30+ 种常见图表，应有尽有
* 支持主流 Notebook 环境，Jupyter Notebook 和 JupyterLab
* 可轻松集成至 Flask，Sanic，Django 等主流 Web 框架
* 高度灵活的配置项，可轻松搭配出精美的图表
* 详细的文档和示例，帮助开发者更快的上手项目
* 多达 400+ 地图文件，并且支持原生百度地图，为地理数据可视化提供强有力的支持

## ⏳ 版本

v0.5.x 和 V1 间不兼容，V1 是一个全新的版本，详见 [ISSUE#892](https://github.com/pyecharts/pyecharts/issues/892)，[ISSUE#1033](https://github.com/pyecharts/pyecharts/issues/1033)。

### V0.5.x

> 支持 Python 2.7，3.4+

经开发团队决定，0.5.x 版本将不再进行维护，0.5.x 版本代码位于 *05x* 分支，文档位于 [05x-docs.pyecharts.org](http://05x-docs.pyecharts.org)。

### V1

> 仅支持 Python 3.7+

新版本系列将从 v1.0.0 开始，文档位于 [pyecharts.org](https://pyecharts.org)；示例位于 [gallery.pyecharts.org](https://gallery.pyecharts.org)

### V2

> 仅支持 Python 3.7+

新版本基于 Echarts 5.4.1+ 进行渲染, 文档和示例位置与 V1 相同

## 🔰 安装

**pip 安装**
```shell
# 安装 v1 以上版本
$ pip install pyecharts -U

# 如果需要安装 0.5.11 版本的开发者，可以使用
# pip install pyecharts==0.5.11
```

**源码安装**
```shell
# 安装 v1 以上版本
$ git clone https://github.com/pyecharts/pyecharts.git
# 如果需要安装 0.5.11 版本，请使用 git clone https://github.com/pyecharts/pyecharts.git -b v05x
$ cd pyecharts
$ pip install -r requirements.txt
$ python setup.py install
```

## 📝 使用

### 本地环境

#### 生成 HTML
```python
from pyecharts.charts import Bar
from pyecharts import options as opts

# V1 版本开始支持链式调用
bar = (
    Bar()
    .add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
    .add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
    .add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
    .set_global_opts(title_opts=opts.TitleOpts(title="某商场销售情况"))
)
bar.render()

# 不习惯链式调用的开发者依旧可以单独调用方法
bar = Bar()
bar.add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
bar.add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
bar.add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
bar.set_global_opts(title_opts=opts.TitleOpts(title="某商场销售情况"))
bar.render()
```
<p align="center">
<img src="https://user-images.githubusercontent.com/19553554/55270272-d6ff1b80-52d7-11e9-820f-30660a068e3e.gif"  width="85%" />
</p>

#### 生成图片
```python
from snapshot_selenium import snapshot as driver

from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.render import make_snapshot


def bar_chart() -> Bar:
    c = (
        Bar()
        .add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
        .add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
        .add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
        .reversal_axis()
        .set_series_opts(label_opts=opts.LabelOpts(position="right"))
        .set_global_opts(title_opts=opts.TitleOpts(title="Bar-测试渲染图片"))
    )
    return c

# 需要安装 snapshot-selenium 或者 snapshot-phantomjs
make_snapshot(driver, bar_chart().render(), "bar.png")
```
<p align="center">
<img src="https://user-images.githubusercontent.com/19553554/56089096-11fc7400-5ec0-11e9-9c21-551624036836.png"  width="85%" />
</p>

### Notebook 环境

#### Jupyter Notebook

![](https://user-images.githubusercontent.com/19553554/55270255-b3d46c00-52d7-11e9-8aa5-f7b3819a1e88.png)

#### JupyterLab

![](https://user-images.githubusercontent.com/19553554/55270259-c0f15b00-52d7-11e9-8811-93bfca1cc027.png)

#### Web 框架

![](https://user-images.githubusercontent.com/19553554/35081158-3faa7c34-fc4d-11e7-80c9-2de79371374f.gif)

## 🔖 Demo

> Demo 代码位于 example 文件夹下，欢迎参考 pyecharts 画廊 [pyecharts-gallery](https://github.com/pyecharts/pyecharts-gallery)。

<div align="center">
<img src="https://user-images.githubusercontent.com/19553554/52197440-843a5200-289a-11e9-8601-3ce8d945b04a.gif" width="33%" alt="bar"/>
<img src="https://user-images.githubusercontent.com/19553554/52360729-ad640980-2a77-11e9-84e2-feff7e11aea5.gif" width="33%" alt="boxplot"/>
<img src="https://user-images.githubusercontent.com/19553554/52535290-4b611800-2d87-11e9-8bf2-b43a54a3bda8.png" width="33%" alt="effectScatter"/>
<img src="https://user-images.githubusercontent.com/19553554/52332816-ac5eb800-2a36-11e9-8227-3538976f447d.gif" width="33%" alt="funnel"/>
<img src="https://user-images.githubusercontent.com/19553554/52332988-0b243180-2a37-11e9-9db8-eb6b8c86a0de.png" width="33%" alt="gague"/>
<img src="https://user-images.githubusercontent.com/19553554/52344575-133f9980-2a56-11e9-93e0-568e484936ce.gif" width="33%" alt="geo"/>
<img src="https://user-images.githubusercontent.com/19553554/35082102-fd8d884a-fc52-11e7-9e40-5f94098d4493.gif" width="33%" alt="geo"/>
<img src="https://user-images.githubusercontent.com/19553554/52727805-f7f20280-2ff0-11e9-91ab-cd99848e3127.gif" width="33%" alt="graph"/>
<img src="https://user-images.githubusercontent.com/19553554/52345115-6534ef00-2a57-11e9-80cd-9cbfed252139.gif" width="33%" alt="heatmap"/>
<img src="https://user-images.githubusercontent.com/19553554/52345490-4a16af00-2a58-11e9-9b43-7bbc86aa05b6.gif" width="33%" alt="kline"/>
<img src="https://user-images.githubusercontent.com/19553554/52346064-b7770f80-2a59-11e9-9e03-6dae3a8c637d.gif" width="33%" alt="line"/>
<img src="https://user-images.githubusercontent.com/19553554/52347117-248ba480-2a5c-11e9-8402-5a94054dca50.gif" width="33%" alt="liquid"/>
<img src="https://user-images.githubusercontent.com/19553554/52347915-0a52c600-2a5e-11e9-8039-41268238576c.gif" width="33%" alt="map"/>
<img src="https://user-images.githubusercontent.com/19553554/57545910-431c7700-738e-11e9-896b-e071b55115c7.png" width="33%" alt="bmap"/>
<img src="https://user-images.githubusercontent.com/19553554/52535013-e48e2f80-2d83-11e9-8886-ac0d2122d6af.png" width="33%" alt="parallel"/>
<img src="https://user-images.githubusercontent.com/19553554/52348202-bb596080-2a5e-11e9-84a7-60732be0743a.gif" width="33%" alt="pie"/>
<img src="https://user-images.githubusercontent.com/19553554/35090457-afc0658e-fc74-11e7-9c58-24c780436287.gif" width="33%" alt="ploar"/>
<img src="https://user-images.githubusercontent.com/19553554/52533994-932b7380-2d76-11e9-93b4-0de3132eb941.gif" width="33%" alt="radar"/>
<img src="https://user-images.githubusercontent.com/19553554/52348431-420e3d80-2a5f-11e9-8cab-7b415592dc77.gif" width="33%" alt="scatter"/>
<img src="https://user-images.githubusercontent.com/19553554/44004598-5636d74e-9e97-11e8-8a5c-92de6278880d.gif" width="33%" alt="tree"/>
<img src="https://user-images.githubusercontent.com/19553554/35082251-b9e23982-fc53-11e7-8341-e7da1842389f.gif" width="33%" alt="treemap"/>
<img src="https://user-images.githubusercontent.com/19553554/52348737-01fb8a80-2a60-11e9-94ac-dacbd7b58811.png" width="33%" alt="wordCloud"/>
<img src="https://user-images.githubusercontent.com/19553554/52433989-4f075b80-2b49-11e9-9979-ef32c2d17c96.gif" width="33%" alt="bar3D"/>
<img src="https://user-images.githubusercontent.com/19553554/52464826-4baab900-2bb7-11e9-8299-776f5ee43670.gif" width="33%" alt="line3D"/>
<img src="https://user-images.githubusercontent.com/19553554/52802261-8d0cfe00-30ba-11e9-8ae7-ae0773770a59.gif" width="33%" alt="sankey"/>
<img src="https://user-images.githubusercontent.com/19553554/52464647-aee81b80-2bb6-11e9-864e-c544392e523a.gif" width="33%" alt="scatter3D"/>
<img src="https://user-images.githubusercontent.com/19553554/52465183-a55fb300-2bb8-11e9-8c10-4519c4e3f758.gif" width="33%" alt="surface3D"/>
<img src="https://user-images.githubusercontent.com/19553554/52798246-7ebae400-30b2-11e9-8489-6c10339c3429.gif" width="33%" alt="themeRiver"/>
<img src="https://user-images.githubusercontent.com/17564655/57567164-bdd5a880-7407-11e9-8d19-9be2776c57fa.png" width="33%" alt="sunburst"/>
<img src="https://user-images.githubusercontent.com/19553554/52349544-c2ce3900-2a61-11e9-82af-28aaaaae0d67.gif" width="33%" alt="overlap"/>
<img src="https://user-images.githubusercontent.com/19553554/35089737-ccc1c01c-fc72-11e7-874d-8ba8b89572eb.png" width="33%" alt="grid"/>
<img src="https://user-images.githubusercontent.com/19553554/56976071-b9f28c80-6ba4-11e9-8efd-603203c77619.png" width="33%" alt="grid">
<img src="https://user-images.githubusercontent.com/19553554/35082279-e111743c-fc53-11e7-9362-580160593715.gif" width="33%" alt="timeline"/>
</div>

更多详细文档，请访问

* [中文文档](http://pyecharts.org/#/zh-cn/)
* [English Documentation](http://pyecharts.org/#/en-us/)
* [示例 Example](https://gallery.pyecharts.org)

## ⛏ 代码质量

### 单元测试

```shell
$ pip install -r test/requirements.txt
$ make
```

### 集成测试

使用 [Travis CI](https://travis-ci.org/) 和 [AppVeyor](https://ci.appveyor.com/) 持续集成环境。

### 代码规范

使用 [flake8](http://flake8.pycqa.org/en/latest/index.html), [Codecov](https://codecov.io/) 以及 [pylint](https://www.pylint.org/) 提升代码质量。

## 😉 Author

pyecharts 主要由以下几位开发者开发维护

* [@chenjiandongx](https://github.com/chenjiandongx)
* [@chfw](https://github.com/chfw)
* [@kinegratii](https://github.com/kinegratii)
* [@sunhailin-Leo](https://github.com/sunhailin-Leo)

更多贡献者信息可以访问 [pyecharts/graphs/contributors](https://github.com/pyecharts/pyecharts/graphs/contributors)

## 💡 贡献

期待能有更多的开发者参与到 pyecharts 的开发中来，我们会保证尽快 Reivew PR 并且及时回复。但提交 PR 请确保

1. 通过所有单元测试，如若是新功能，请为其新增单元测试
2. 遵守开发规范，使用 black 以及 isort 格式化代码（$ pip install -r requirements-dev.txt）
3. 如若需要，请更新相对应的文档

我们也非常欢迎开发者能为 pyecharts 提供更多的示例，共同来完善文档，文档项目位于 [pyecharts/website](https://github.com/pyecharts/website)

## 📃 License

MIT [©chenjiandongx](https://github.com/chenjiandongx)
